Skip to content
Open
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
20 changes: 5 additions & 15 deletions superset/charts/data/dashboard_filter_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,11 @@ def _is_filter_in_scope_for_chart(
"""
Determines whether a native filter applies to a given chart.

When chartsInScope is present on the filter config, uses that directly.
Otherwise falls back to scope.rootPath and scope.excluded with the
dashboard layout. Also considers charts that were added to the dashboard
but were not instantiated in the layout.
A chart is in scope when one of its layout ancestors is in ``rootPath`` and
it's not excluded. The persisted ``chartsInScope`` is intentionally NOT used
here (it's a denormalized cache that the frontend recomputes from ``scope``
on every load, so it can be stale).
"""
if (charts_in_scope := filter_config.get("chartsInScope")) is not None:
if chart_id in charts_in_scope:
return True

# If the chart is found in position_json and not in chartsInScope,
# it was explicitly excluded by the filter scope config.
if _find_chart_layout_item(chart_id, position_json) is not None:
return False

scope = filter_config.get("scope", {})
root_path: list[str] = scope.get("rootPath", [])
excluded: list[int] = scope.get("excluded", [])
Expand All @@ -107,8 +98,7 @@ def _is_filter_in_scope_for_chart(

# If the chart doesn't exist in the dashboard layout, treat it as a
# root-level chart.
else:
return "ROOT_ID" in root_path
return "ROOT_ID" in root_path
Comment on lines 88 to +101


def _find_chart_layout_item(
Expand Down
21 changes: 15 additions & 6 deletions tests/unit_tests/charts/test_dashboard_filter_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,24 @@ def test_filter_in_scope_via_charts_in_scope() -> None:
assert _is_filter_in_scope_for_chart(flt, 10, SAMPLE_POSITION_JSON) is True


def test_filter_not_in_scope_via_charts_in_scope() -> None:
flt = _make_filter(charts_in_scope=[20, 30])
assert _is_filter_in_scope_for_chart(flt, 10, SAMPLE_POSITION_JSON) is False
def test_filter_in_scope_ignores_stale_charts_in_scope() -> None:
"""
Regression: a chart present in the layout and within scope.rootPath is in
scope even when the (stale) chartsInScope cache omits it. chartsInScope is a
denormalized cache the frontend recomputes from scope on load (persisted
only on save).
"""
flt = _make_filter(charts_in_scope=[20, 30], scope_root=["ROOT_ID"])
assert _is_filter_in_scope_for_chart(flt, 10, SAMPLE_POSITION_JSON) is True


def test_filter_empty_charts_in_scope_not_in_scope() -> None:
"""Empty chartsInScope means in scope for no charts; do not fall back to rootPath"""
def test_filter_in_scope_ignores_empty_charts_in_scope() -> None:
"""
An empty (stale) chartsInScope must not exclude a chart that scope.rootPath
includes; scope is the source of truth.
"""
flt = _make_filter(charts_in_scope=[], scope_root=["ROOT_ID"])
assert _is_filter_in_scope_for_chart(flt, 10, SAMPLE_POSITION_JSON) is False
assert _is_filter_in_scope_for_chart(flt, 10, SAMPLE_POSITION_JSON) is True


def test_filter_in_scope_via_root_path() -> None:
Expand Down
Loading