Skip to content
Merged
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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ jobs:
ssh-private-key: ${{ secrets.STRIDE_DATA_DEPLOY_KEY }}
- name: Download test data
run: |
git clone git@github.com:dsgrid/stride-data.git /tmp/stride-data
STRIDE_DATA_REF=$(cat .stride-data-ref | tr -d '[:space:]')
echo "Cloning stride-data at ref: $STRIDE_DATA_REF"
git clone --branch "$STRIDE_DATA_REF" --single-branch git@github.com:dsgrid/stride-data.git /tmp/stride-data
mkdir -p ~/.stride/data
cp -r /tmp/stride-data/global ~/.stride/data/
cp -r /tmp/stride-data/global-test ~/.stride/data/
Expand Down
1 change: 1 addition & 0 deletions .stride-data-ref
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v2.0.0
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dependencies = [
"dash-bootstrap-components>=2.0.3",
"dbt-core >= 1.10.5, < 2",
"dbt-duckdb",
"dsgrid-toolkit >= 0.3.3, < 0.4.0",
"dsgrid-toolkit >= 0.4.0, < 0.5.0",
"duckdb >= 1.1, < 2",
"loguru",
"pandas>=2.2,<3",
Expand Down
24 changes: 17 additions & 7 deletions src/stride/ui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@
get_temp_edits_for_category,
parse_temp_edit_key,
)
from stride.config import CACHED_PROJECTS_UPPER_BOUND, DEFAULT_MAX_CACHED_PROJECTS, get_max_cached_projects as _get_config_max_cached
from stride.config import (
CACHED_PROJECTS_UPPER_BOUND,
DEFAULT_MAX_CACHED_PROJECTS,
get_max_cached_projects as _get_config_max_cached,
)
from stride.ui.palette_utils import get_default_user_palette, list_user_palettes

assets_path = Path(__file__).parent.absolute() / "assets"
Expand Down Expand Up @@ -434,7 +438,11 @@ def create_app( # noqa: C901
value=current_project_path,
placeholder="Switch project...",
className="mb-2",
style={"fontSize": "0.85rem", "width": "calc(100% - 35px)", "display": "inline-block"},
style={
"fontSize": "0.85rem",
"width": "calc(100% - 35px)",
"display": "inline-block",
},
clearable=False,
),
html.Button(
Expand Down Expand Up @@ -1094,7 +1102,7 @@ def update_navigation_tabs(project_path: str) -> tuple[list[dict[str, str]], str
]
return options, "compare" # Reset to home view

# Refresh dropdown options when refresh button is clicked
# Refresh dropdown options when refresh button is clicked
@callback(
Output("project-switcher-dropdown", "options", allow_duplicate=True),
Input("refresh-projects-btn", "n_clicks"),
Expand Down Expand Up @@ -1376,7 +1384,11 @@ def create_app_no_project(
value=None,
placeholder="Select a recent project...",
className="mb-2",
style={"fontSize": "0.85rem", "width": "calc(100% - 35px)", "display": "inline-block"},
style={
"fontSize": "0.85rem",
"width": "calc(100% - 35px)",
"display": "inline-block",
},
clearable=False,
),
html.Button(
Expand Down Expand Up @@ -1721,9 +1733,7 @@ def _register_refresh_projects_callback() -> None:
State("current-project-path", "data"),
prevent_initial_call=True,
)
def refresh_dropdown_options(
n_clicks: int | None, current_path: str
) -> list[dict[str, str]]:
def refresh_dropdown_options(n_clicks: int | None, current_path: str) -> list[dict[str, str]]:
"""Refresh the project switcher dropdown options with latest recent projects."""
if not n_clicks:
raise PreventUpdate
Expand Down
54 changes: 38 additions & 16 deletions src/stride/ui/settings/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,12 @@ def create_settings_layout(
step=1,
value=max_cached_value,
className="form-control form-control-sm",
style={"width": "100px", "display": "inline-block", "height": "31px", "fontSize": "0.85rem"},
style={
"width": "100px",
"display": "inline-block",
"height": "31px",
"fontSize": "0.85rem",
},
readOnly=is_overridden,
disabled=is_overridden,
),
Expand Down Expand Up @@ -212,8 +217,7 @@ def create_settings_layout(
value=current_palette_name,
placeholder="Select a user palette...",
disabled=(
current_palette_type
!= "user"
current_palette_type != "user"
),
),
dbc.Button(
Expand All @@ -224,8 +228,7 @@ def create_settings_layout(
size="sm",
className="ms-2 mt-2",
disabled=(
current_palette_type
!= "user"
current_palette_type != "user"
or not current_palette_name
),
),
Expand Down Expand Up @@ -253,8 +256,7 @@ def create_settings_layout(
size="sm",
className="ms-2 mt-2 theme-text",
disabled=(
current_palette_type
!= "user"
current_palette_type != "user"
or not current_palette_name
),
),
Expand Down Expand Up @@ -320,7 +322,10 @@ def create_settings_layout(
html.Div(
[
_create_color_item(
ColorCategory.SCENARIO.value, label, color, temp_edits
ColorCategory.SCENARIO.value,
label,
color,
temp_edits,
)
for label, color in scenario_colors.items()
],
Expand All @@ -340,7 +345,10 @@ def create_settings_layout(
html.Div(
[
_create_color_item(
ColorCategory.MODEL_YEAR.value, label, color, temp_edits
ColorCategory.MODEL_YEAR.value,
label,
color,
temp_edits,
)
for label, color in model_year_colors.items()
],
Expand All @@ -360,7 +368,10 @@ def create_settings_layout(
html.Div(
[
_create_color_item(
ColorCategory.SECTOR.value, label, color, temp_edits
ColorCategory.SECTOR.value,
label,
color,
temp_edits,
)
for label, color in sector_colors.items()
],
Expand All @@ -380,7 +391,10 @@ def create_settings_layout(
html.Div(
[
_create_color_item(
ColorCategory.END_USE.value, label, color, temp_edits
ColorCategory.END_USE.value,
label,
color,
temp_edits,
)
for label, color in end_use_colors.items()
],
Expand Down Expand Up @@ -801,7 +815,7 @@ def get_temp_edits_for_category(category_value: str) -> dict[str, str]:
"""
prefix = f"{category_value}:"
return {
key[len(prefix):]: color
key[len(prefix) :]: color
for key, color in _temp_color_edits.items()
if key.startswith(prefix)
}
Expand Down Expand Up @@ -861,7 +875,9 @@ def create_color_preview_content(color_manager: ColorManager) -> list[html.Div]:
),
html.Div(
[
_create_color_item(ColorCategory.SCENARIO.value, label, color, temp_edits)
_create_color_item(
ColorCategory.SCENARIO.value, label, color, temp_edits
)
for label, color in scenario_colors.items()
],
className="d-flex flex-wrap gap-2 mb-3",
Expand All @@ -881,7 +897,9 @@ def create_color_preview_content(color_manager: ColorManager) -> list[html.Div]:
),
html.Div(
[
_create_color_item(ColorCategory.MODEL_YEAR.value, label, color, temp_edits)
_create_color_item(
ColorCategory.MODEL_YEAR.value, label, color, temp_edits
)
for label, color in model_year_colors.items()
],
className="d-flex flex-wrap gap-2 mb-3",
Expand All @@ -901,7 +919,9 @@ def create_color_preview_content(color_manager: ColorManager) -> list[html.Div]:
),
html.Div(
[
_create_color_item(ColorCategory.SECTOR.value, label, color, temp_edits)
_create_color_item(
ColorCategory.SECTOR.value, label, color, temp_edits
)
for label, color in sector_colors.items()
],
className="d-flex flex-wrap gap-2 mb-3",
Expand All @@ -921,7 +941,9 @@ def create_color_preview_content(color_manager: ColorManager) -> list[html.Div]:
),
html.Div(
[
_create_color_item(ColorCategory.END_USE.value, label, color, temp_edits)
_create_color_item(
ColorCategory.END_USE.value, label, color, temp_edits
)
for label, color in end_use_colors.items()
],
className="d-flex flex-wrap gap-2",
Expand Down
12 changes: 9 additions & 3 deletions tests/palette/test_color_manager_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ def test_color_manager_updates_with_new_palette() -> None:

def test_color_manager_singleton_behavior() -> None:
"""Test that ColorManager maintains singleton behavior."""
palette1 = ColorPalette.from_dict({"scenarios": {}, "model_years": {}, "metrics": {"A": "#111111"}})
palette1 = ColorPalette.from_dict(
{"scenarios": {}, "model_years": {}, "metrics": {"A": "#111111"}}
)
cm1 = ColorManager(palette1)

palette2 = ColorPalette.from_dict({"scenarios": {}, "model_years": {}, "metrics": {"A": "#222222"}})
palette2 = ColorPalette.from_dict(
{"scenarios": {}, "model_years": {}, "metrics": {"A": "#222222"}}
)
cm2 = ColorManager(palette2)

# Should be the same instance
Expand Down Expand Up @@ -169,7 +173,9 @@ def test_color_manager_scenario_styling_updates() -> None:

def test_color_manager_preserves_palette_reference() -> None:
"""Test that ColorManager properly references the provided palette."""
palette = ColorPalette.from_dict({"scenarios": {}, "model_years": {}, "metrics": {"Label": "#123456"}})
palette = ColorPalette.from_dict(
{"scenarios": {}, "model_years": {}, "metrics": {"Label": "#123456"}}
)
cm = ColorManager(palette)

# Get the palette back
Expand Down
21 changes: 18 additions & 3 deletions tests/palette/test_palette.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,12 @@ class TestMergeWithProjectDimensions:
def test_matched_names_keep_colors(self) -> None:
"""Entries in both palette and project keep their stored color."""
palette = ColorPalette.from_dict(
{"scenarios": {"baseline": "#AA0000", "high": "#BB0000"}, "model_years": {}, "sectors": {}, "end_uses": {}}
{
"scenarios": {"baseline": "#AA0000", "high": "#BB0000"},
"model_years": {},
"sectors": {},
"end_uses": {},
}
)
palette.merge_with_project_dimensions(scenarios=["baseline", "high"])
assert palette.scenarios["baseline"] == "#AA0000"
Expand All @@ -571,7 +576,12 @@ def test_matched_names_keep_colors(self) -> None:
def test_new_project_names_get_colors(self) -> None:
"""Names in the project but not in the palette get auto-assigned colors."""
palette = ColorPalette.from_dict(
{"scenarios": {"baseline": "#AA0000"}, "model_years": {}, "sectors": {}, "end_uses": {}}
{
"scenarios": {"baseline": "#AA0000"},
"model_years": {},
"sectors": {},
"end_uses": {},
}
)
palette.merge_with_project_dimensions(scenarios=["baseline", "new_scenario"])
assert palette.scenarios["baseline"] == "#AA0000"
Expand Down Expand Up @@ -681,7 +691,12 @@ def test_none_categories_skipped(self) -> None:
def test_case_insensitive_matching(self) -> None:
"""Merge normalizes names to lowercase for matching."""
palette = ColorPalette.from_dict(
{"scenarios": {"baseline": "#AA0000"}, "model_years": {}, "sectors": {}, "end_uses": {}}
{
"scenarios": {"baseline": "#AA0000"},
"model_years": {},
"sectors": {},
"end_uses": {},
}
)
palette.merge_with_project_dimensions(scenarios=["Baseline"])
assert palette.scenarios["baseline"] == "#AA0000"
Expand Down
2 changes: 1 addition & 1 deletion tests/palette/test_palette_merge.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for ColorPalette.merge_with_project_dimensions."""

from stride.ui.palette import ColorCategory, ColorPalette, TOL_BRIGHT, TOL_METRICS_LIGHT
from stride.ui.palette import ColorPalette, TOL_BRIGHT


class TestMergeMatchedNames:
Expand Down
Loading
Loading