This is an automated email from the ASF dual-hosted git repository.

jli pushed a commit to branch fix-cannot-view-dashboard-if-no-theme-role
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to 
refs/heads/fix-cannot-view-dashboard-if-no-theme-role by this push:
     new 23173c91804 fix(dashboard): preserve native filter scopes during 
metadata refresh
23173c91804 is described below

commit 23173c9180457533537413fa05f9200481cfffaf
Author: Joe Li <[email protected]>
AuthorDate: Thu Mar 5 20:22:54 2026 -0800

    fix(dashboard): preserve native filter scopes during metadata refresh
    
    The DASHBOARD_INFO_UPDATED reducer case did a shallow metadata overwrite,
    losing client-only chartsInScope/tabsInScope when the server response
    (via setDashboardMetadata after save) replaced native_filter_configuration
    and chart_customization_config.
    
    Apply preserveScopes() for both configs during DASHBOARD_INFO_UPDATED,
    matching the existing HYDRATE_DASHBOARD pattern. Add regression tests
    for scope preservation, chart customization scopes, and metadata
    passthrough when no metadata key is present.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../src/dashboard/reducers/dashboardInfo.test.ts   | 80 ++++++++++++++++++++++
 .../src/dashboard/reducers/dashboardInfo.ts        | 20 ++++++
 2 files changed, 100 insertions(+)

diff --git a/superset-frontend/src/dashboard/reducers/dashboardInfo.test.ts 
b/superset-frontend/src/dashboard/reducers/dashboardInfo.test.ts
index 752e1f9b3a6..ff50597a945 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardInfo.test.ts
+++ b/superset-frontend/src/dashboard/reducers/dashboardInfo.test.ts
@@ -36,6 +36,35 @@ const stateWithTheme = {
   metadata: {},
 } as Partial<DashboardInfo>;
 
+const stateWithScopes = {
+  id: 1,
+  metadata: {
+    native_filter_configuration: [
+      {
+        id: 'FILTER-1',
+        name: 'Region',
+        chartsInScope: [10, 20, 30],
+        tabsInScope: ['TAB-A'],
+        targets: [{ datasetId: 1 }],
+      },
+      {
+        id: 'FILTER-2',
+        name: 'Date Range',
+        chartsInScope: [40],
+        tabsInScope: [],
+        targets: [{ datasetId: 2 }],
+      },
+    ],
+    chart_customization_config: [
+      {
+        id: 'CUSTOM-1',
+        chartsInScope: [100],
+        tabsInScope: ['TAB-X'],
+      },
+    ],
+  },
+} as unknown as Partial<DashboardInfo>;
+
 test('preserves existing theme when DASHBOARD_INFO_UPDATED payload omits theme 
key', () => {
   // Simulates the fixed Header behavior: conditional spread omits `theme`
   // key when PropertiesModal returns theme: undefined (theme not in fetched 
list).
@@ -76,3 +105,54 @@ test('overwrites theme when DASHBOARD_INFO_UPDATED payload 
has theme: undefined'
   const themeId = result.theme ? result.theme.id : null;
   expect(themeId).toBeNull();
 });
+
+test('preserves native_filter_configuration scopes during 
DASHBOARD_INFO_UPDATED metadata refresh', () => {
+  // Simulates the save flow: onUpdateSuccess → 
setDashboardMetadata(serverMetadata)
+  // → dashboardInfoChanged({ metadata: ... }) → DASHBOARD_INFO_UPDATED.
+  // The server response contains filters WITHOUT chartsInScope/tabsInScope 
(client-only).
+  const serverMetadata = {
+    native_filter_configuration: [
+      { id: 'FILTER-1', name: 'Region (updated)', targets: [{ datasetId: 1 }] 
},
+      { id: 'FILTER-2', name: 'Date Range', targets: [{ datasetId: 2 }] },
+    ],
+  } as unknown as DashboardInfo['metadata'];
+
+  const action = dashboardInfoChanged({ metadata: serverMetadata });
+  const result = dashboardInfoReducer(stateWithScopes, action);
+
+  const filters = result.metadata?.native_filter_configuration;
+  expect(filters).toHaveLength(2);
+
+  // FILTER-1: server updated the name, scopes preserved from existing state
+  expect(filters![0].name).toBe('Region (updated)');
+  expect(filters![0].chartsInScope).toEqual([10, 20, 30]);
+  expect(filters![0].tabsInScope).toEqual(['TAB-A']);
+
+  // FILTER-2: unchanged, scopes preserved
+  expect(filters![1].chartsInScope).toEqual([40]);
+  expect(filters![1].tabsInScope).toEqual([]);
+});
+
+test('preserves chart_customization_config scopes during 
DASHBOARD_INFO_UPDATED metadata refresh', () => {
+  const serverMetadata = {
+    chart_customization_config: [
+      { id: 'CUSTOM-1' },
+    ],
+  } as unknown as DashboardInfo['metadata'];
+
+  const action = dashboardInfoChanged({ metadata: serverMetadata });
+  const result = dashboardInfoReducer(stateWithScopes, action);
+
+  const customizations = result.metadata?.chart_customization_config;
+  expect(customizations).toHaveLength(1);
+  expect(customizations![0].chartsInScope).toEqual([100]);
+  expect(customizations![0].tabsInScope).toEqual(['TAB-X']);
+});
+
+test('does not affect metadata when DASHBOARD_INFO_UPDATED has no metadata 
key', () => {
+  const action = dashboardInfoChanged({ slug: 'new-slug' });
+  const result = dashboardInfoReducer(stateWithScopes, action);
+
+  // Metadata untouched — same reference
+  expect(result.metadata).toBe(stateWithScopes.metadata);
+});
diff --git a/superset-frontend/src/dashboard/reducers/dashboardInfo.ts 
b/superset-frontend/src/dashboard/reducers/dashboardInfo.ts
index 4e5a7fb45e6..f6dbe8b7bd8 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardInfo.ts
+++ b/superset-frontend/src/dashboard/reducers/dashboardInfo.ts
@@ -122,9 +122,29 @@ export default function dashboardInfoReducer(
     case DASHBOARD_INFO_UPDATED: {
       const dashAction = action as DashboardInfoAction;
       const newInfo = dashAction.newInfo || {};
+      const incomingMeta = newInfo.metadata;
       return {
         ...state,
         ...newInfo,
+        // Preserve client-only scope data (chartsInScope, tabsInScope) when
+        // metadata is refreshed from the server, matching HYDRATE_DASHBOARD.
+        ...(incomingMeta && {
+          metadata: {
+            ...incomingMeta,
+            ...(incomingMeta.native_filter_configuration && {
+              native_filter_configuration: preserveScopes(
+                state.metadata?.native_filter_configuration,
+                incomingMeta.native_filter_configuration,
+              ),
+            }),
+            ...(incomingMeta.chart_customization_config && {
+              chart_customization_config: preserveScopes(
+                state.metadata?.chart_customization_config,
+                incomingMeta.chart_customization_config,
+              ),
+            }),
+          },
+        }),
         last_modified_time: Math.round(new Date().getTime() / 1000),
       };
     }

Reply via email to