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": {},