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/19 18:23:01 UTC
(superset) branch master updated: fix(chart API): apply dashboard filters by live scope, not stale chartsInScope (#41214)
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 334b13c3d94 fix(chart API): apply dashboard filters by live scope, not stale chartsInScope (#41214)
334b13c3d94 is described below
commit 334b13c3d94c12681f3f66b11d2c56a415e662ce
Author: Vitor Avila <96...@users.noreply.github.com>
AuthorDate: Fri Jun 19 15:22:54 2026 -0300
fix(chart API): apply dashboard filters by live scope, not stale chartsInScope (#41214)
---
superset/charts/data/dashboard_filter_context.py | 20 +++++---------------
.../charts/test_dashboard_filter_context.py | 21 +++++++++++++++------
.../mcp_service/chart/tool/test_get_chart_info.py | 5 ++++-
3 files changed, 24 insertions(+), 22 deletions(-)
diff --git a/superset/charts/data/dashboard_filter_context.py b/superset/charts/data/dashboard_filter_context.py
index fa4227a9c04..b7d745b49bc 100644
--- a/superset/charts/data/dashboard_filter_context.py
+++ b/superset/charts/data/dashboard_filter_context.py
@@ -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", [])
@@ -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
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 bfc2097f725..3bc062330bf 100644
--- a/tests/unit_tests/charts/test_dashboard_filter_context.py
+++ b/tests/unit_tests/charts/test_dashboard_filter_context.py
@@ -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:
diff --git a/tests/unit_tests/mcp_service/chart/tool/test_get_chart_info.py b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_info.py
index dc245342c8a..5bcf1ff7ebe 100644
--- a/tests/unit_tests/mcp_service/chart/tool/test_get_chart_info.py
+++ b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_info.py
@@ -183,6 +183,7 @@ class TestBuildAppliedDashboardFilters:
"type": "NATIVE_FILTER",
"filterType": "filter_select",
"chartsInScope": [1],
+ "scope": {"rootPath": ["ROOT_ID"], "excluded": []},
"targets": [{"column": {"name": "country"}, "datasetId": 7}],
"defaultDataMask": {
"filterState": {"value": ["US"]},
@@ -226,7 +227,8 @@ class TestBuildAppliedDashboardFilters:
"name": "Region",
"type": "NATIVE_FILTER",
"filterType": "filter_select",
- "chartsInScope": [2, 3], # chart 1 excluded
+ "chartsInScope": [2, 3],
+ "scope": {"rootPath": ["ROOT_ID"], "excluded": [1]}, # chart 1 excluded
"targets": [{"column": {"name": "region"}, "datasetId": 7}],
"defaultDataMask": {
"filterState": {"value": ["NA"]},
@@ -257,6 +259,7 @@ class TestBuildAppliedDashboardFilters:
"type": "NATIVE_FILTER",
"filterType": "filter_select",
"chartsInScope": [1],
+ "scope": {"rootPath": ["ROOT_ID"], "excluded": []},
"targets": [{"column": {"name": "region"}, "datasetId": 7}],
"controlValues": {"defaultToFirstItem": True},
"defaultDataMask": {},