Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions 8Knot/assets/color.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
/* MultiSelect pill colors */
--pill-default-bg: #555555;
--pill-default-color: #ffffff;
--pill-pending-bg: #B03A2E;
--pill-pending-color: #ffffff;

/* Table colors */
--table-bg-dark: #222;
Expand All @@ -84,18 +86,18 @@
The .searching class is toggled by a callback in index_callbacks.py


/* Style for filter pills (grey by default when selected) */
/* Style for filter pills (red by default when selected but search not executed) */

.mantine-MultiSelect-pill,
.mantine-MultiSelect-value {
background-color: var(--gray-medium);
color: var(--color-white);
background-color: var(--pill-pending-bg);
color: var(--pill-pending-color);
}

.searchbar-dropdown .mantine-MultiSelect-pill,
.searchbar-dropdown .mantine-MultiSelect-value {
background-color: var(--gray-medium);
color: var(--color-white);
background-color: var(--pill-pending-bg);
color: var(--pill-pending-color);
}


Expand Down
5 changes: 5 additions & 0 deletions 8Knot/assets/main_layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,11 @@ button.icon-button.btn:active,
padding: 0 6px;
}

.search-submit-button {
margin-top: var(--spacing-sm);
width: 100%;
}

.search-controls-stack {
width: 100%;
justify-content: center;
Expand Down
44 changes: 44 additions & 0 deletions 8Knot/assets/search_keyboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Cascading Enter key handler for the search bar.
*
* When the Mantine MultiSelect dropdown has visible options, Enter
* selects the highlighted item (Mantine's default behavior — we do nothing).
*
* When there are no visible options (dropdown closed or empty), Enter
* triggers the search by clicking the search button.
*/
document.addEventListener("DOMContentLoaded", function () {
// Use MutationObserver to attach the handler once the MultiSelect input exists
var observer = new MutationObserver(function () {
var input = document.querySelector("#projects input");
if (!input) return;

// Stop observing once we've found the input
observer.disconnect();

input.addEventListener("keydown", function (e) {
if (e.key !== "Enter") return;

// Check if the dropdown has visible selectable options.
// Mantine keeps the dropdown element in the DOM while focused,
// so we check for actual option elements instead.
var options = document.querySelectorAll(
".mantine-MultiSelect-option"
);
if (options.length > 0) {
// Dropdown has options — let Mantine handle Enter
// to select the highlighted item
return;
}

// Dropdown is closed — trigger search
e.preventDefault();
Comment on lines +25 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldnt be trying to look into the children to decide what to do in a parent event handler - this is backwards from the natural cascading effect of event handlers.

instead we should allow the menu items to handle their own key events (we already do this somewhere i think since pressing enter on one option already works), and simply e.PreventDefault() after doing that so it doesnt bubble up to the search bar. Then, once the menu is closed, the search bar should be in focus and a subsequent enter will hit this event handler, eliminating the need for this logic and simplifying it down to just calling the search function

var searchButton = document.getElementById("search-button");
if (searchButton) {
searchButton.click();
}
Comment on lines +36 to +39
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im not a fan of simulating a click on the button - would be better if we could call the actual search function or whatever the next stage of this process is.

});
});

observer.observe(document.body, { childList: true, subtree: true });
});
17 changes: 9 additions & 8 deletions 8Knot/pages/index/index_callbacks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta

Check warning on line 1 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 W0611: Unused timedelta imported from datetime (unused-import) Raw Output: 8Knot/pages/index/index_callbacks.py:1:0: W0611: Unused timedelta imported from datetime (unused-import)

Check warning on line 1 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 W0611: Unused datetime imported from datetime (unused-import) Raw Output: 8Knot/pages/index/index_callbacks.py:1:0: W0611: Unused datetime imported from datetime (unused-import)
import re

Check warning on line 2 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 W0611: Unused import re (unused-import) Raw Output: 8Knot/pages/index/index_callbacks.py:2:0: W0611: Unused import re (unused-import)
import os
import time
Expand All @@ -8,7 +8,7 @@
import dash_bootstrap_components as dbc
import dash
from dash import callback, html
from dash.dependencies import Input, Output, State, MATCH

Check warning on line 11 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 W0611: Unused MATCH imported from dash.dependencies (unused-import) Raw Output: 8Knot/pages/index/index_callbacks.py:11:0: W0611: Unused MATCH imported from dash.dependencies (unused-import)
from app import augur
from flask_login import current_user
from cache_manager.cache_manager import CacheManager as cm
Expand All @@ -32,7 +32,7 @@
from queries.repo_info_query import repo_info_query as riq
from models import SearchItem
import redis
import flask

Check warning on line 35 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 W0611: Unused import flask (unused-import) Raw Output: 8Knot/pages/index/index_callbacks.py:35:0: W0611: Unused import flask (unused-import)
from .search_utils import fuzzy_search
from .search_utils import clean_repo_name

Expand Down Expand Up @@ -78,7 +78,7 @@
# TODO: check how old groups are. If they're pretty old (threshold tbd) then requery

# check if groups are not already cached, or if the refresh-button was pressed
if not users_cache.exists(f"{user_id}_groups") or (dash.ctx.triggered_id == "refresh-button"):

Check warning on line 81 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return) Raw Output: 8Knot/pages/index/index_callbacks.py:81:8: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
# kick off celery task to collect groups
# on query worker queue,
return [ugq.apply_async(args=[user_id], queue="data").id]
Expand Down Expand Up @@ -423,7 +423,7 @@
elif search_item == SearchItem.REPO:
formatted_v["label"] = f"repo: {v['label']}"
default_options.append(formatted_v)
except:

Check warning on line 426 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 W0702: No exception type(s) specified (bare-except) Raw Output: 8Knot/pages/index/index_callbacks.py:426:12: W0702: No exception type(s) specified (bare-except)
# If that fails, just return the raw selection values
default_options = [{"value": v, "label": f"ID: {v}"} for v in selections]

Expand All @@ -437,10 +437,11 @@
[Output("results-output-container", "children"), Output("repo-choices", "data")],
[
Input("search", "n_clicks"),
Input("search-button", "n_clicks"),
State("projects", "value"),
],
)
def multiselect_values_to_repo_ids(n_clicks, user_vals):
def multiselect_values_to_repo_ids(n_clicks, search_button_clicks, user_vals):
if not user_vals:
logging.warning("NOTHING SELECTED IN SEARCH BAR")
raise dash.exceptions.PreventUpdate
Expand Down Expand Up @@ -789,7 +790,7 @@
ctx = dash.callback_context

# Check which input triggered the callback
if ctx.triggered_id == "sidebar-toggle" and n_clicks:

Check warning on line 793 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 R1705: Unnecessary "elif" after "return", remove the leading "el" from "elif" (no-else-return) Raw Output: 8Knot/pages/index/index_callbacks.py:793:4: R1705: Unnecessary "elif" after "return", remove the leading "el" from "elif" (no-else-return)
# Manual toggle - simply toggle the collapse state
return not is_open
elif ctx.triggered_id == "url":
Expand Down Expand Up @@ -852,7 +853,7 @@
)
def hide_loading_on_landing(pathname):
"""Hide loading components when on landing page."""
if pathname == "/" or pathname is None:

Check warning on line 856 in 8Knot/pages/index/index_callbacks.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return) Raw Output: 8Knot/pages/index/index_callbacks.py:856:4: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
# Hide loading components on landing page
return {"display": "none"}, {"display": "none"}
else:
Expand All @@ -867,35 +868,35 @@
# Callback to change pill color when search is clicked
#
# This callback implements dynamic pill coloring:
# - When user selects repos/orgs: pills are grey (pending)
# - When user selects repos/orgs: pills are red (pending)
# - When user clicks search icon: pills turn blue (active search)
# - Default selection (chaoss) starts blue since search is auto-triggered
#
# Works in conjunction with CSS in main_layout.css
# ============================================================================
@callback(
Output("projects", "className"),
[Input("search", "n_clicks"), Input("projects", "value")],
[Input("search", "n_clicks"), Input("search-button", "n_clicks"), Input("projects", "value")],
prevent_initial_call=True,
)
def update_pill_color_on_search(_, selected_repos_orgs):
def update_pill_color_on_search(_, search_button_clicks, selected_repos_orgs):
"""Update pill color based on search action.

When search icon is clicked, add 'searching' class to turn pills blue.
When values change (user is selecting), remove 'searching' class to keep pills grey.
When values change (user is selecting), remove 'searching' class to keep pills red.
"""
if not dash.ctx.triggered:
return dash.no_update

triggered_id = dash.ctx.triggered_id

if triggered_id == "search":
if triggered_id in ("search", "search-button"):
# Search button clicked - add 'searching' class to turn pills blue
logging.info(f"PILL COLOR: Search clicked - turning pills BLUE")
return "searchbar-dropdown searching"
if triggered_id == "projects":
# Values changed (user selecting) - remove 'searching' class to keep pills grey
logging.info(f"PILL COLOR: Values changed - turning pills GREY. Selected: {selected_repos_orgs}")
# Values changed (user selecting) - remove 'searching' class to keep pills red
logging.info(f"PILL COLOR: Values changed - turning pills RED. Selected: {selected_repos_orgs}")
return "searchbar-dropdown"

return dash.no_update
19 changes: 13 additions & 6 deletions 8Knot/pages/index/search_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
return options

# For very short queries (1-2 chars), use a simpler and faster matching approach
if len(query) <= 2:

Check warning on line 178 in 8Knot/pages/index/search_utils.py

View workflow job for this annotation

GitHub Actions / python-lint

[pylint] reported by reviewdog 🐶 R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return) Raw Output: 8Knot/pages/index/search_utils.py:178:4: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
return search_short_query(query, options)
else:
return search_with_fuzzy_matching(query, options, threshold, limit)
Expand Down Expand Up @@ -443,14 +443,14 @@
"margin": "var(--multiselect-item-margin)",
"color": "var(--color-white)",
},
# Inline styles for grey pill default color (turns blue with "searching" class)
# Inline styles for pending pill color (red by default, turns blue with "searching" class)
"value": {
"backgroundColor": "var(--pill-default-bg)",
"color": "var(--pill-default-color)",
"backgroundColor": "var(--pill-pending-bg)",
"color": "var(--pill-pending-color)",
},
"pill": {
"backgroundColor": "var(--pill-default-bg)",
"color": "var(--pill-default-color)",
"backgroundColor": "var(--pill-pending-bg)",
"color": "var(--pill-pending-color)",
},
}

Expand Down Expand Up @@ -517,13 +517,20 @@
],
className="search-input-wrapper",
),
dbc.Button(
"Search",
id="search-button",
color="primary",
size="sm",
className="search-submit-button",
),
create_alert(
alert_id="help-alert",
children='Please ensure that your spelling is correct. \
If your selection definitely isn\'t present, please request that \
it be loaded using the help button "REPO/ORG Request" \
in the bottom right corner of the screen. \
The search is only confirmed when you click the search icon and the pill turns blue.',
The search is only confirmed when you click the Search button (or press Enter) and the pill turns blue.',
color="info",
),
create_alert(
Expand Down
Loading