This is an automated email from the ASF dual-hosted git repository. diegopucci pushed a commit to branch geido/fix/fitlers-auto-apply in repository https://gitbox.apache.org/repos/asf/superset.git
commit d14e0681ab62f19d69d318a1ac744e9a253bf3ad Author: Diego Pucci <[email protected]> AuthorDate: Tue Jan 6 18:06:11 2026 +0100 fix(Dashboard): Auto-apply filters with default values when extraFormData is empty --- .../dashboard/components/FiltersBadge/index.tsx | 45 ++++---- .../nativeFilters/FilterBar/FilterBar.test.tsx | 115 +++++++++++++++++++++ .../components/nativeFilters/FilterBar/index.tsx | 36 +++++-- 3 files changed, 165 insertions(+), 31 deletions(-) diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx index aad6024033..e2425779d0 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx @@ -201,30 +201,29 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { const prevChartConfig = usePrevious(chartConfiguration); useEffect(() => { - if (!showIndicators && nativeIndicators.length > 0) { + const shouldRecalculate = + prevChartStatus !== 'success' || + dataMask !== prevDataMask || + chart?.queriesResponse?.[0]?.rejected_filters !== + prevChart?.queriesResponse?.[0]?.rejected_filters || + chart?.queriesResponse?.[0]?.applied_filters !== + prevChart?.queriesResponse?.[0]?.applied_filters || + nativeFilters !== prevNativeFilters || + chartLayoutItems !== prevChartLayoutItems || + prevChartConfig !== chartConfiguration; + + if (shouldRecalculate) { + const newIndicators = selectNativeIndicatorsForChart( + nativeFilters, + dataMask, + chartId, + chart, + chartLayoutItems, + chartConfiguration, + ); + setNativeIndicators(newIndicators); + } else if (!showIndicators && nativeIndicators.length > 0) { setNativeIndicators(indicatorsInitialState); - } else if (prevChartStatus !== 'success') { - if ( - chart?.queriesResponse?.[0]?.rejected_filters !== - prevChart?.queriesResponse?.[0]?.rejected_filters || - chart?.queriesResponse?.[0]?.applied_filters !== - prevChart?.queriesResponse?.[0]?.applied_filters || - nativeFilters !== prevNativeFilters || - chartLayoutItems !== prevChartLayoutItems || - dataMask !== prevDataMask || - prevChartConfig !== chartConfiguration - ) { - setNativeIndicators( - selectNativeIndicatorsForChart( - nativeFilters, - dataMask, - chartId, - chart, - chartLayoutItems, - chartConfiguration, - ), - ); - } } }, [ chart, diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx index 96e2770603..17ed9cba22 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx @@ -27,6 +27,7 @@ import { FilterBarOrientation } from 'src/dashboard/types'; import { FILTER_BAR_TEST_ID } from './utils'; import FilterBar from '.'; import { FILTERS_CONFIG_MODAL_TEST_ID } from '../FiltersConfigModal/FiltersConfigModal'; +import * as dataMaskActions from 'src/dataMask/actions'; jest.useFakeTimers(); @@ -350,4 +351,118 @@ describe('FilterBar', () => { const { container } = renderWrapper(openedBarProps, stateWithFilter); expect(container).toBeInTheDocument(); }); + + test('auto-applies filter when extraFormData is empty in applied state', async () => { + const filterId = 'test-filter-auto-apply'; + const updateDataMaskSpy = jest.spyOn(dataMaskActions, 'updateDataMask'); + + const stateWithIncompleteFilter = { + ...stateWithoutNativeFilters, + dashboardInfo: { + id: 1, + dash_edit_perm: true, + }, + dataMask: { + [filterId]: { + id: filterId, + filterState: { value: ['value1', 'value2'] }, + extraFormData: {}, + }, + }, + nativeFilters: { + filters: { + [filterId]: { + id: filterId, + name: 'Test Filter', + filterType: 'filter_select', + targets: [{ datasetId: 1, column: { name: 'test_column' } }], + defaultDataMask: { + filterState: { value: ['value1', 'value2'] }, + extraFormData: {}, + }, + controlValues: { + enableEmptyFilter: true, + }, + cascadeParentIds: [], + scope: { + rootPath: ['ROOT_ID'], + excluded: [], + }, + type: 'NATIVE_FILTER', + description: '', + chartsInScope: [], + tabsInScope: [], + }, + }, + filtersState: {}, + }, + }; + + renderWrapper(openedBarProps, stateWithIncompleteFilter); + + await act(async () => { + jest.advanceTimersByTime(200); + }); + + expect(screen.getByTestId(getTestId('filter-icon'))).toBeInTheDocument(); + + updateDataMaskSpy.mockRestore(); + }); + + test('renders correctly when filter has complete extraFormData', async () => { + const filterId = 'test-filter-complete'; + const stateWithCompleteFilter = { + ...stateWithoutNativeFilters, + dashboardInfo: { + id: 1, + dash_edit_perm: true, + }, + dataMask: { + [filterId]: { + id: filterId, + filterState: { value: ['value1'] }, + extraFormData: { + filters: [{ col: 'test_column', op: 'IN', val: ['value1'] }], + }, + }, + }, + nativeFilters: { + filters: { + [filterId]: { + id: filterId, + name: 'Test Filter', + filterType: 'filter_select', + targets: [{ datasetId: 1, column: { name: 'test_column' } }], + defaultDataMask: { + filterState: { value: ['value1'] }, + extraFormData: { + filters: [{ col: 'test_column', op: 'IN', val: ['value1'] }], + }, + }, + controlValues: { + enableEmptyFilter: true, + }, + cascadeParentIds: [], + scope: { + rootPath: ['ROOT_ID'], + excluded: [], + }, + type: 'NATIVE_FILTER', + description: '', + chartsInScope: [], + tabsInScope: [], + }, + }, + filtersState: {}, + }, + }; + + renderWrapper(openedBarProps, stateWithCompleteFilter); + + await act(async () => { + jest.advanceTimersByTime(100); + }); + + expect(screen.getByTestId(getTestId('filter-icon'))).toBeInTheDocument(); + }); }); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx index 74d90cbb99..accaa9747f 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx @@ -204,24 +204,38 @@ const FilterBar: FC<FiltersBarProps> = ({ dataMask: Partial<DataMask>, ) => { setDataMaskSelected(draft => { + const existingDataMask = dataMaskSelectedRef.current[filter.id]; + const appliedDataMask = dataMaskApplied[filter.id]; const isFirstTimeInitialization = !initializedFilters.has(filter.id) && - dataMaskSelectedRef.current[filter.id]?.filterState?.value === - undefined; + existingDataMask?.filterState?.value === undefined; + + // Auto-apply when filter has value but empty extraFormData in applied state + // This fixes the bug where defaultDataMask.filterState.value exists but extraFormData is empty + const needsAutoApply = + appliedDataMask?.filterState?.value !== undefined && + (!appliedDataMask?.extraFormData || + Object.keys(appliedDataMask.extraFormData || {}).length === 0) && + dataMask.filterState?.value !== undefined && + dataMask.extraFormData && + Object.keys(dataMask.extraFormData).length > 0; - // force instant updating on initialization for filters with `requiredFirst` is true or instant filters + // Force instant updating for requiredFirst filters or auto-apply when needed if ( - // filterState.value === undefined - means that value not initialized dataMask.filterState?.value !== undefined && - isFirstTimeInitialization && - filter.requiredFirst + ((isFirstTimeInitialization && filter.requiredFirst) || + needsAutoApply) ) { dispatch(updateDataMask(filter.id, dataMask)); } - // Mark filter as initialized after getting its first value + // Mark filter as initialized after getting its first value WITH extraFormData + // This ensures we don't mark it as initialized on the first sync (value but no extraFormData) + // but do mark it after the second sync (value AND extraFormData) if ( dataMask.filterState?.value !== undefined && + dataMask.extraFormData && + Object.keys(dataMask.extraFormData).length > 0 && !initializedFilters.has(filter.id) ) { setInitializedFilters(prev => new Set(prev).add(filter.id)); @@ -252,7 +266,13 @@ const FilterBar: FC<FiltersBarProps> = ({ }; }); }, - [dispatch, setDataMaskSelected, initializedFilters, setInitializedFilters], + [ + dispatch, + setDataMaskSelected, + initializedFilters, + setInitializedFilters, + dataMaskApplied, + ], ); useEffect(() => {
