From 8b50b04bb5bf98d7fed704aa887a4c98b87105e1 Mon Sep 17 00:00:00 2001 From: Jordan Jenkins Date: Tue, 5 May 2026 12:57:43 -0500 Subject: [PATCH 1/2] fix(native-filters): populate granularity_sqla from datasource main_dttm_col for time-range parent cascade --- .../FilterBar/FilterControls/FilterValue.tsx | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx index c23f2578718d..1e4bbe3bfc55 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx @@ -53,6 +53,7 @@ import { onFiltersRefreshSuccess, setDirectPathToChild, } from 'src/dashboard/actions/dashboardState'; +import { fetchDatasourceMetadata } from 'src/dashboard/actions/datasources'; import { setHoveredChartCustomization, unsetHoveredChartCustomization, @@ -185,6 +186,28 @@ const FilterValue: FC = ({ const [isRefreshing, setIsRefreshing] = useState(false); const dispatch = useDispatch(); + // When a Date Range parent filter is active, the child filter needs + // granularity_sqla to apply the temporal WHERE clause. Read main_dttm_col + // from the Redux datasource cache as a fallback. + // + // The dashboard datasets API (/api/v1/dashboard/:id/datasets) only returns + // datasets used by chart slices, so native-filter-only datasets are never + // in state.datasources. Dispatch fetchDatasourceMetadata at mount time to + // populate the cache explicitly for those datasets. + const datasourceMainDttmCol = useSelector( + state => + datasetId != null + ? (state.datasources as Record)?.[`${datasetId}__table`] + ?.main_dttm_col + : undefined, + ); + + useEffect(() => { + if (datasetId != null) { + dispatch(fetchDatasourceMetadata(`${datasetId}__table`)); + } + }, [datasetId, dispatch]); + const { outlinedFilterId, lastUpdated } = useFilterOutlined(); const handleFilterLoadFinish = useCallback(() => { @@ -216,7 +239,11 @@ const FilterValue: FC = ({ groupby, adhoc_filters: adhocFilters, time_range: timeRange, - granularity_sqla: granularitySqla, + granularity_sqla: + granularitySqla || + (dependencies.time_range + ? datasourceMainDttmCol ?? undefined + : undefined), dashboardId, }); const filterOwnState = filter.dataMask?.ownState || {}; @@ -347,6 +374,7 @@ const FilterValue: FC = ({ dataMaskSelected, setHasDepsFilterValue, transitiveParentIds, + datasourceMainDttmCol, ]); useEffect(() => { From b582c8afee7d743dee454f827a3c9bbf11aa1216 Mon Sep 17 00:00:00 2001 From: Jordan Jenkins Date: Tue, 5 May 2026 13:11:09 -0500 Subject: [PATCH 2/2] fix(native-filters): deduplicate in-flight fetchDatasourceMetadata requests When multiple FilterValue components share the same datasetId and mount simultaneously, each sees an empty Redux cache and dispatches fetchDatasourceMetadata. The thunk already short-circuits once the datasource is cached, but has no guard for concurrent in-flight requests. Add a module-level Set to track keys currently being fetched so that parallel dispatches for the same key skip the network call. The Set entry is cleared on both success and error so a failed request can be retried. Addresses review feedback on the FilterValue granularity_sqla fix. Co-Authored-By: Claude Sonnet 4.6 --- .../src/dashboard/actions/datasources.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/superset-frontend/src/dashboard/actions/datasources.ts b/superset-frontend/src/dashboard/actions/datasources.ts index f48c12428459..1745bcbb5cc6 100644 --- a/superset-frontend/src/dashboard/actions/datasources.ts +++ b/superset-frontend/src/dashboard/actions/datasources.ts @@ -52,6 +52,8 @@ export function setDatasource(datasource: Datasource, key: string) { }; } +const inFlightDatasourceKeys = new Set(); + export function fetchDatasourceMetadata(key: string) { return (dispatch: Dispatch, getState: () => RootState) => { const { datasources } = getState(); @@ -61,8 +63,21 @@ export function fetchDatasourceMetadata(key: string) { return dispatch(setDatasource(datasource, key)); } + if (inFlightDatasourceKeys.has(key)) { + return undefined; + } + + inFlightDatasourceKeys.add(key); return SupersetClient.get({ endpoint: `/superset/fetch_datasource_metadata?datasourceKey=${key}`, - }).then(({ json }) => dispatch(setDatasource(json as Datasource, key))); + }) + .then(({ json }) => { + inFlightDatasourceKeys.delete(key); + return dispatch(setDatasource(json as Datasource, key)); + }) + .catch((err: unknown) => { + inFlightDatasourceKeys.delete(key); + throw err; + }); }; }