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

jli 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 3fba967856 fix(delete-filter): deleted native filters are still shown 
until [sc-96553] (#37012)
3fba967856 is described below

commit 3fba9678565cbbffd048172eb3e01fac50ba31bc
Author: Ramiro Aquino Romero <[email protected]>
AuthorDate: Tue Jan 20 14:18:47 2026 -0400

    fix(delete-filter): deleted native filters are still shown until [sc-96553] 
(#37012)
---
 .../src/dashboard/actions/nativeFilters.ts         |   7 +-
 .../src/dashboard/reducers/nativeFilters.test.ts   | 180 ++++++++++++++++++++-
 .../src/dashboard/reducers/nativeFilters.ts        |  15 +-
 3 files changed, 195 insertions(+), 7 deletions(-)

diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts 
b/superset-frontend/src/dashboard/actions/nativeFilters.ts
index 4a628af3f4..af3d711495 100644
--- a/superset-frontend/src/dashboard/actions/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts
@@ -21,6 +21,9 @@ import {
   FilterConfiguration,
   Filters,
   makeApi,
+  Divider,
+  ChartCustomization,
+  ChartCustomizationDivider,
 } from '@superset-ui/core';
 import { Dispatch } from 'redux';
 import { RootState } from 'src/dashboard/types';
@@ -44,7 +47,9 @@ export const SET_NATIVE_FILTERS_CONFIG_COMPLETE =
   'SET_NATIVE_FILTERS_CONFIG_COMPLETE';
 export interface SetNativeFiltersConfigComplete {
   type: typeof SET_NATIVE_FILTERS_CONFIG_COMPLETE;
-  filterChanges: Filter[];
+  filterChanges: Array<
+    Filter | Divider | ChartCustomization | ChartCustomizationDivider
+  >;
 }
 
 export const SET_NATIVE_FILTERS_CONFIG_FAIL = 'SET_NATIVE_FILTERS_CONFIG_FAIL';
diff --git a/superset-frontend/src/dashboard/reducers/nativeFilters.test.ts 
b/superset-frontend/src/dashboard/reducers/nativeFilters.test.ts
index 65c4e54635..ceac844317 100644
--- a/superset-frontend/src/dashboard/reducers/nativeFilters.test.ts
+++ b/superset-frontend/src/dashboard/reducers/nativeFilters.test.ts
@@ -16,7 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Filter, NativeFilterType } from '@superset-ui/core';
+import {
+  Filter,
+  NativeFilterType,
+  ChartCustomization,
+  ChartCustomizationType,
+} from '@superset-ui/core';
 import nativeFilterReducer from './nativeFilters';
 import { SET_NATIVE_FILTERS_CONFIG_COMPLETE } from '../actions/nativeFilters';
 import { HYDRATE_DASHBOARD } from '../actions/hydrate';
@@ -58,6 +63,40 @@ const createMockFilter = (
   tabsInScope,
 });
 
+const createMockChartCustomization = (
+  id: string,
+  chartsInScope?: number[],
+  tabsInScope?: string[],
+): ChartCustomization => ({
+  id,
+  type: ChartCustomizationType.ChartCustomization,
+  name: `Chart Customization ${id}`,
+  filterType: 'filter_select',
+  targets: [
+    {
+      datasetId: 0,
+      column: {
+        name: 'test column',
+        displayName: 'test column',
+      },
+    },
+  ],
+  scope: {
+    rootPath: [],
+    excluded: [],
+  },
+  defaultDataMask: {
+    filterState: {
+      value: null,
+    },
+  },
+  controlValues: {
+    sortAscending: true,
+  },
+  chartsInScope,
+  tabsInScope,
+});
+
 test('SET_NATIVE_FILTERS_CONFIG_COMPLETE updates filters with complete scope 
properties', () => {
   const initialState = {
     filters: {
@@ -237,3 +276,142 @@ test('HYDRATE_DASHBOARD handles new filters without 
existing state', () => {
   expect(result.filters.filter1.chartsInScope).toEqual([1, 2]);
   expect(result.filters.filter1.tabsInScope).toEqual(['tab1']);
 });
+
+test('SET_NATIVE_FILTERS_CONFIG_COMPLETE removes deleted filters from state', 
() => {
+  const initialState = {
+    filters: {
+      filter1: createMockFilter('filter1', [1, 2], ['tab1']),
+      filter2: createMockFilter('filter2', [3, 4], ['tab2']),
+      filter3: createMockFilter('filter3', [5, 6], ['tab3']),
+    },
+  };
+
+  // Backend response only includes filter1 and filter3 (filter2 was deleted)
+  const action = {
+    type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof 
SET_NATIVE_FILTERS_CONFIG_COMPLETE,
+    filterChanges: [
+      createMockFilter('filter1', [1, 2], ['tab1']),
+      createMockFilter('filter3', [5, 6], ['tab3']),
+    ],
+  };
+
+  const result = nativeFilterReducer(initialState, action);
+
+  // filter2 should be removed from state
+  expect(result.filters.filter1).toBeDefined();
+  expect(result.filters.filter2).toBeUndefined();
+  expect(result.filters.filter3).toBeDefined();
+  expect(Object.keys(result.filters)).toHaveLength(2);
+});
+
+test('SET_NATIVE_FILTERS_CONFIG_COMPLETE removes all filters when backend 
returns empty array', () => {
+  const initialState = {
+    filters: {
+      filter1: createMockFilter('filter1', [1, 2], ['tab1']),
+      filter2: createMockFilter('filter2', [3, 4], ['tab2']),
+    },
+  };
+
+  const action = {
+    type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof 
SET_NATIVE_FILTERS_CONFIG_COMPLETE,
+    filterChanges: [],
+  };
+
+  const result = nativeFilterReducer(initialState, action);
+
+  expect(Object.keys(result.filters)).toHaveLength(0);
+  expect(result.filters).toEqual({});
+});
+
+test('SET_NATIVE_FILTERS_CONFIG_COMPLETE handles mixed Filter and 
ChartCustomization types', () => {
+  const initialState = {
+    filters: {
+      filter1: createMockFilter('filter1', [1, 2], ['tab1']),
+      customization1: createMockChartCustomization(
+        'customization1',
+        [3, 4],
+        ['tab2'],
+      ),
+    },
+  };
+
+  const action = {
+    type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof 
SET_NATIVE_FILTERS_CONFIG_COMPLETE,
+    filterChanges: [
+      createMockFilter('filter1', [5, 6], ['tab3']),
+      createMockChartCustomization('customization1', [7, 8], ['tab4']),
+    ],
+  };
+
+  const result = nativeFilterReducer(initialState, action);
+
+  expect(result.filters.filter1.chartsInScope).toEqual([5, 6]);
+  expect(result.filters.filter1.tabsInScope).toEqual(['tab3']);
+  expect(result.filters.customization1.chartsInScope).toEqual([7, 8]);
+  expect(result.filters.customization1.tabsInScope).toEqual(['tab4']);
+});
+
+test('SET_NATIVE_FILTERS_CONFIG_COMPLETE adds new filters while removing 
deleted ones', () => {
+  const initialState = {
+    filters: {
+      filter1: createMockFilter('filter1', [1, 2], ['tab1']),
+      filter2: createMockFilter('filter2', [3, 4], ['tab2']),
+    },
+  };
+
+  // Backend response: keep filter1, delete filter2, add filter3
+  const action = {
+    type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof 
SET_NATIVE_FILTERS_CONFIG_COMPLETE,
+    filterChanges: [
+      createMockFilter('filter1', [1, 2], ['tab1']),
+      createMockFilter('filter3', [5, 6], ['tab3']),
+    ],
+  };
+
+  const result = nativeFilterReducer(initialState, action);
+
+  expect(result.filters.filter1).toBeDefined();
+  expect(result.filters.filter2).toBeUndefined();
+  expect(result.filters.filter3).toBeDefined();
+  expect(result.filters.filter3.chartsInScope).toEqual([5, 6]);
+  expect(Object.keys(result.filters)).toHaveLength(2);
+});
+
+test('SET_NATIVE_FILTERS_CONFIG_COMPLETE treats backend response as source of 
truth', () => {
+  const initialState = {
+    filters: {
+      filter1: createMockFilter('filter1', [1, 2], ['tab1']),
+      filter2: createMockFilter('filter2', [3, 4], ['tab2']),
+      filter3: createMockFilter('filter3', [5, 6], ['tab3']),
+      customization1: createMockChartCustomization(
+        'customization1',
+        [7, 8],
+        ['tab4'],
+      ),
+    },
+  };
+
+  // Backend only returns filter2 and customization1
+  const action = {
+    type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof 
SET_NATIVE_FILTERS_CONFIG_COMPLETE,
+    filterChanges: [
+      createMockFilter('filter2', [10, 11], ['tab5']),
+      createMockChartCustomization('customization1', [12, 13], ['tab6']),
+    ],
+  };
+
+  const result = nativeFilterReducer(initialState, action);
+
+  // Only filter2 and customization1 should remain
+  expect(result.filters.filter1).toBeUndefined();
+  expect(result.filters.filter2).toBeDefined();
+  expect(result.filters.filter3).toBeUndefined();
+  expect(result.filters.customization1).toBeDefined();
+  expect(Object.keys(result.filters)).toHaveLength(2);
+
+  // Values should be from backend response
+  expect(result.filters.filter2.chartsInScope).toEqual([10, 11]);
+  expect(result.filters.filter2.tabsInScope).toEqual(['tab5']);
+  expect(result.filters.customization1.chartsInScope).toEqual([12, 13]);
+  expect(result.filters.customization1.tabsInScope).toEqual(['tab6']);
+});
diff --git a/superset-frontend/src/dashboard/reducers/nativeFilters.ts 
b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
index c1b4c924fa..37f406dbaf 100644
--- a/superset-frontend/src/dashboard/reducers/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
@@ -77,13 +77,18 @@ function handleFilterChangesComplete(
     Filter | Divider | ChartCustomization | ChartCustomizationDivider
   >,
 ) {
-  const modifiedFilters = { ...state.filters };
+  // Create new filters object from backend response (deleted filters won't be 
included)
+  const newFilters: Record<
+    string,
+    Filter | Divider | ChartCustomization | ChartCustomizationDivider
+  > = {};
+
   filters.forEach(filter => {
+    const existingFilter = state.filters[filter.id];
     if (filter.chartsInScope != null && filter.tabsInScope != null) {
-      modifiedFilters[filter.id] = filter;
+      newFilters[filter.id] = filter;
     } else {
-      const existingFilter = modifiedFilters[filter.id];
-      modifiedFilters[filter.id] = {
+      newFilters[filter.id] = {
         ...filter,
         chartsInScope: filter.chartsInScope ?? existingFilter?.chartsInScope,
         tabsInScope: filter.tabsInScope ?? existingFilter?.tabsInScope,
@@ -93,7 +98,7 @@ function handleFilterChangesComplete(
 
   return {
     ...state,
-    filters: modifiedFilters,
+    filters: newFilters,
   } as ExtendedNativeFiltersState;
 }
 

Reply via email to