diff --git a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.test.tsx b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.test.tsx index 2427047908d..310d6fa5b9f 100644 --- a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.test.tsx +++ b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.test.tsx @@ -1131,3 +1131,121 @@ test('Clear boolean TRUE value', async () => { }); }); }); + +test('preserves dependent filter value restored from URL when it exists in data', async () => { + const setDataMaskMock = jest.fn(); + const testProps = { + ...selectMultipleProps, + formData: { + ...selectMultipleProps.formData, + defaultToFirstItem: true, + multiSelect: false, + // Non-empty extraFormData indicates parent filter dependency + extraFormData: { + filters: [{ col: 'region', op: 'IN', val: ['North America'] }], + }, + }, + // 'girl' is NOT the first item but exists in data — simulates a + // value restored from URL/permalink for a dependent filter + filterState: { value: ['girl'] }, + }; + + render( + // @ts-expect-error + , + { + useRedux: true, + initialState: { + nativeFilters: { + filters: { + 'test-filter': { + name: 'Test Filter', + }, + }, + }, + dataMask: { + 'test-filter': { + extraFormData: {}, + filterState: { + value: ['girl'], + }, + }, + }, + }, + }, + ); + + await waitFor(() => { + expect(setDataMaskMock).toHaveBeenLastCalledWith( + expect.objectContaining({ + filterState: expect.objectContaining({ + value: ['girl'], + }), + }), + ); + }); +}); + +test('resets dependent filter to first item when value does not exist in data', async () => { + const setDataMaskMock = jest.fn(); + const testProps = { + ...selectMultipleProps, + formData: { + ...selectMultipleProps.formData, + defaultToFirstItem: true, + multiSelect: false, + extraFormData: { + filters: [{ col: 'region', op: 'IN', val: ['North America'] }], + }, + }, + // 'unknown' does NOT exist in data — simulates a stale value after + // parent filter changed to a different selection + filterState: { value: ['unknown'] }, + }; + + render( + // @ts-expect-error + , + { + useRedux: true, + initialState: { + nativeFilters: { + filters: { + 'test-filter': { + name: 'Test Filter', + }, + }, + }, + dataMask: { + 'test-filter': { + extraFormData: {}, + filterState: { + value: ['unknown'], + }, + }, + }, + }, + }, + ); + + await waitFor(() => { + expect(setDataMaskMock).toHaveBeenLastCalledWith( + expect.objectContaining({ + filterState: expect.objectContaining({ + // Should reset to first item ('boy') since 'unknown' is not in data + value: ['boy'], + }), + }), + ); + }); +}); diff --git a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx index 8f94817b481..58cbfc5dceb 100644 --- a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx @@ -151,7 +151,6 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { const [col] = groupby; const [initialColtypeMap] = useState(coltypeMap); const [search, setSearch] = useState(''); - const isChangedByUser = useRef(false); const prevDataRef = useRef(data); const [dataMask, dispatchDataMask] = useImmerReducer(reducer, { extraFormData: {}, @@ -273,8 +272,6 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { } else { updateDataMask(values); } - - isChangedByUser.current = true; }, [updateDataMask, formData.nativeFilterId, clearAllTrigger], ); @@ -400,14 +397,12 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { // If data actually changed (e.g., due to parent filter), reset flag if (hasDataChanged) { - isChangedByUser.current = false; prevDataRef.current = data; } }, [data, col]); useEffect(() => { if ( - isChangedByUser.current && filterState.value?.every((value?: any) => data.some(row => row[col] === value), )