You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by va...@apache.org on 2026/06/04 19:43:45 UTC
(superset) branch master updated: fix(dashboard-filter): Consider dashboard filters to charts not declared in the dashboard position (#40774)
This is an automated email from the ASF dual-hosted git repository.
Vitor-Avila pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 74060987082 fix(dashboard-filter): Consider dashboard filters to charts not declared in the dashboard position (#40774)
74060987082 is described below
commit 74060987082e115664e4b0c7b6ae35764f59cfec
Author: Vitor Avila <96...@users.noreply.github.com>
AuthorDate: Thu Jun 4 16:43:38 2026 -0300
fix(dashboard-filter): Consider dashboard filters to charts not declared in the dashboard position (#40774)
---
superset/charts/data/dashboard_filter_context.py | 30 +++++---
.../charts/test_dashboard_filter_context.py | 80 +++++++++++++++++++++-
2 files changed, 99 insertions(+), 11 deletions(-)
diff --git a/superset/charts/data/dashboard_filter_context.py b/superset/charts/data/dashboard_filter_context.py
index df889b52ace..50b6ef4244e 100644
--- a/superset/charts/data/dashboard_filter_context.py
+++ b/superset/charts/data/dashboard_filter_context.py
@@ -78,13 +78,21 @@ def _is_filter_in_scope_for_chart(
position_json: dict[str, Any],
) -> bool:
"""
- 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.
+ 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.
"""
if (charts_in_scope := filter_config.get("chartsInScope")) is not None:
- return chart_id in charts_in_scope
+ 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", [])
@@ -93,12 +101,14 @@ def _is_filter_in_scope_for_chart(
if chart_id in excluded:
return False
- chart_layout_item = _find_chart_layout_item(chart_id, position_json)
- if not chart_layout_item:
- return False
+ if chart_layout_item := _find_chart_layout_item(chart_id, position_json):
+ parents: list[str] = chart_layout_item.get("parents", [])
+ return any(parent in root_path for parent in parents)
- parents: list[str] = chart_layout_item.get("parents", [])
- return any(parent in root_path for parent in parents)
+ # If the chart doesn't exist in the dashboard layout, treat it as a
+ # root-level chart.
+ else:
+ return "ROOT_ID" in root_path
def _find_chart_layout_item(
diff --git a/tests/unit_tests/charts/test_dashboard_filter_context.py b/tests/unit_tests/charts/test_dashboard_filter_context.py
index 9be05929f64..bfc2097f725 100644
--- a/tests/unit_tests/charts/test_dashboard_filter_context.py
+++ b/tests/unit_tests/charts/test_dashboard_filter_context.py
@@ -166,11 +166,40 @@ def test_filter_not_in_scope_different_root() -> None:
assert _is_filter_in_scope_for_chart(flt, 10, SAMPLE_POSITION_JSON) is False
-def test_filter_in_scope_chart_not_in_layout() -> None:
+def test_filter_in_scope_chart_not_in_layout_whole_dashboard_scope() -> None:
+ """Charts not in position_json receive whole-dashboard (ROOT_ID) filters."""
flt = _make_filter(scope_root=["ROOT_ID"])
+ assert _is_filter_in_scope_for_chart(flt, 999, SAMPLE_POSITION_JSON) is True
+
+
+def test_filter_not_in_scope_chart_not_in_layout_tab_scope() -> None:
+ """Charts not in position_json do not receive tab/section-specific filters."""
+ flt = _make_filter(scope_root=["TAB-some-tab"])
+ assert _is_filter_in_scope_for_chart(flt, 999, SAMPLE_POSITION_JSON) is False
+
+
+def test_filter_not_in_scope_chart_not_in_layout_excluded() -> None:
+ """Explicit exclusion is respected even when chart is not in layout."""
+ flt = _make_filter(scope_root=["ROOT_ID"], scope_excluded=[999])
assert _is_filter_in_scope_for_chart(flt, 999, SAMPLE_POSITION_JSON) is False
+def test_filter_in_scope_stale_charts_in_scope_unpositioned_chart() -> None:
+ """
+ chartsInScope is a denormalized cache. When a chart is absent from both
+ chartsInScope and position_json (added to the dashboard after the filter
+ was saved), fall through to rootPath — a whole-dashboard filter applies.
+ """
+ flt = _make_filter(charts_in_scope=[10, 20], scope_root=["ROOT_ID"])
+ assert _is_filter_in_scope_for_chart(flt, 999, SAMPLE_POSITION_JSON) is True
+
+
+def test_filter_in_scope_chart_not_in_layout_empty_position_json() -> None:
+ """With empty position_json, a root-scoped filter still applies."""
+ flt = _make_filter(scope_root=["ROOT_ID"])
+ assert _is_filter_in_scope_for_chart(flt, 999, {}) is True
+
+
# --- _extract_filter_extra_form_data ---
@@ -523,3 +552,52 @@ def test_get_dashboard_filter_context_out_of_scope_filter_excluded(
ctx = get_dashboard_filter_context(dashboard_id=1, chart_id=10)
assert len(ctx.filters) == 1
assert ctx.filters[0].id == "f1"
+
+
+@patch("superset.charts.data.dashboard_filter_context._check_dashboard_access")
+@patch("superset.charts.data.dashboard_filter_context.db")
+def test_get_dashboard_filter_context_chart_not_in_layout_receives_root_filters(
+ mock_db: MagicMock,
+ mock_check_access: MagicMock,
+) -> None:
+ """
+ Charts added to a dashboard via the Save Chart dialog exist in dashboard.slices
+ but not in position_json. They should receive whole-dashboard filters and be
+ excluded from tab/section-specific filters.
+ """
+ filter_config = [
+ _make_filter(
+ flt_id="f1",
+ name="Region",
+ scope_root=["ROOT_ID"],
+ default_value=["US"],
+ target_column="region",
+ ),
+ _make_filter(
+ flt_id="f2",
+ name="Tab filter",
+ scope_root=["TAB-some-tab"],
+ default_value=["active"],
+ target_column="status",
+ ),
+ ]
+ metadata = {"native_filter_configuration": filter_config}
+
+ dashboard = MagicMock()
+ dashboard.id = 1
+ slice_obj = MagicMock()
+ slice_obj.id = 42 # chart 42 is in slices but NOT in SAMPLE_POSITION_JSON
+ dashboard.slices = [slice_obj]
+ dashboard.json_metadata = json.dumps(metadata)
+ dashboard.position_json = json.dumps(SAMPLE_POSITION_JSON)
+ (
+ mock_db.session.query.return_value.filter_by.return_value.one_or_none.return_value
+ ) = dashboard
+
+ ctx = get_dashboard_filter_context(dashboard_id=1, chart_id=42)
+
+ # Only the whole-dashboard filter (ROOT_ID) applies; tab filter does not
+ assert len(ctx.filters) == 1
+ assert ctx.filters[0].id == "f1"
+ assert ctx.filters[0].status == DashboardFilterStatus.APPLIED
+ assert ctx.extra_form_data["filters"][0]["val"] == ["US"]