From ab52bd14c4c448c5ddab4e51bc151f5efb295ddd Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Fri, 17 Apr 2026 16:36:33 -0400 Subject: [PATCH] Working on filters --- .../src/components/ListView/Filters/index.tsx | 6 ++ .../src/components/ListView/ListView.tsx | 18 +++- .../src/pages/DatasetList/index.tsx | 95 ++++++++++++++++++- 3 files changed, 115 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/components/ListView/Filters/index.tsx b/superset-frontend/src/components/ListView/Filters/index.tsx index c32d2393b21..aca1fb16750 100644 --- a/superset-frontend/src/components/ListView/Filters/index.tsx +++ b/superset-frontend/src/components/ListView/Filters/index.tsx @@ -60,6 +60,12 @@ function UIFilters( filter.current?.clearFilter?.(); }); }, + clearFilterById: (id: string) => { + const index = filters.findIndex(f => f.id === id); + if (index >= 0) { + filterRefs[index]?.current?.clearFilter?.(); + } + }, })); return ( diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx index 87135397554..6a3e60f852c 100644 --- a/superset-frontend/src/components/ListView/ListView.tsx +++ b/superset-frontend/src/components/ListView/ListView.tsx @@ -264,6 +264,11 @@ export interface ListViewProps { columnsForWrapText?: string[]; enableBulkTag?: boolean; bulkTagResourceName?: string; + /** Optional ref exposed to callers for programmatic filter control. */ + filtersRef?: React.RefObject<{ + clearFilters: () => void; + clearFilterById: (id: string) => void; + }>; } export function ListView({ @@ -290,6 +295,7 @@ export function ListView({ columnsForWrapText, enableBulkTag = false, bulkTagResourceName, + filtersRef, addSuccessToast, addDangerToast, }: ListViewProps) { @@ -337,7 +343,17 @@ export function ListView({ }); } - const filterControlsRef = useRef<{ clearFilters: () => void }>(null); + const filterControlsRef = useRef<{ + clearFilters: () => void; + clearFilterById: (id: string) => void; + }>(null); + + // Wire the optional external filtersRef to our internal filterControlsRef. + useEffect(() => { + if (filtersRef) { + (filtersRef as any).current = filterControlsRef.current; + } + }); const handleClearFilterControls = useCallback(() => { if (query.filters) { diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx index b189e87759a..b9bb136cfcb 100644 --- a/superset-frontend/src/pages/DatasetList/index.tsx +++ b/superset-frontend/src/pages/DatasetList/index.tsx @@ -24,7 +24,7 @@ import { FeatureFlag, } from '@superset-ui/core'; import { styled, useTheme, css } from '@apache-superset/core/theme'; -import { FunctionComponent, useState, useMemo, useCallback, Key } from 'react'; +import { FunctionComponent, useState, useMemo, useCallback, useRef, Key } from 'react'; import type { CellProps } from 'react-table'; import { Link, useHistory } from 'react-router-dom'; import rison from 'rison'; @@ -194,7 +194,6 @@ const DatasetList: FunctionComponent = ({ const [loading, setLoading] = useState(true); const [lastFetchConfig, setLastFetchConfig] = useState(null); - const currentSourceFilter = useMemo(() => { const sourceTypeFilter = lastFetchConfig?.filters.find( filter => filter.id === 'source_type', @@ -209,6 +208,84 @@ const DatasetList: FunctionComponent = ({ return (sourceTypeFilter?.value as string | undefined) ?? ''; }, [lastFetchConfig]); + // Track the current type and connection filter values so cascade-clear logic + // can inspect them when a different filter changes. + const currentTypeFilter = useRef(undefined); + const currentConnectionFilter = useRef(undefined); + + // Ref wired to ListView's filter controls for programmatic per-filter clearing. + const filtersRef = useRef<{ + clearFilters: () => void; + clearFilterById: (id: string) => void; + }>(null); + + /** + * Cascade-clear incompatible filters when one filter changes. + * + * Rules: + * - Selecting a DB connection → clear "Semantic View" type + * - Selecting a SL connection → clear "Physical" / "Virtual" type + * - Selecting Physical/Virtual type → clear any SL connection + * - Selecting Semantic View type → clear any DB connection + * - Selecting Source=Database → clear SL connection + Semantic View type + * - Selecting Source=Semantic Layer → clear DB connection + Physical/Virtual type + */ + const cascadeClear = useCallback( + ( + changed: 'source' | 'type' | 'connection', + newValue: unknown, + ) => { + if (!isFeatureEnabled(FeatureFlag.SemanticLayers)) return; + + const isSlConnection = (v: unknown) => + typeof v === 'string' && v.startsWith('sl:'); + const isDbConnection = (v: unknown) => + v !== undefined && v !== null && v !== '' && !isSlConnection(v); + const isSemanticViewType = (v: unknown) => v === 'semantic_view'; + const isPhysicalVirtualType = (v: unknown) => + v === true || v === false; + + if (changed === 'connection') { + if (isSlConnection(newValue) && isPhysicalVirtualType(currentTypeFilter.current)) { + filtersRef.current?.clearFilterById('sql'); + } + if (isDbConnection(newValue) && isSemanticViewType(currentTypeFilter.current)) { + filtersRef.current?.clearFilterById('sql'); + } + } + + if (changed === 'type') { + if (isSemanticViewType(newValue) && isDbConnection(currentConnectionFilter.current)) { + filtersRef.current?.clearFilterById('database'); + } + if (isPhysicalVirtualType(newValue) && isSlConnection(currentConnectionFilter.current)) { + filtersRef.current?.clearFilterById('database'); + } + } + + if (changed === 'source') { + const src = newValue as string; + if (src === 'database') { + if (isSemanticViewType(currentTypeFilter.current)) { + filtersRef.current?.clearFilterById('sql'); + } + if (isSlConnection(currentConnectionFilter.current)) { + filtersRef.current?.clearFilterById('database'); + } + } + if (src === 'semantic_layer') { + if (isPhysicalVirtualType(currentTypeFilter.current)) { + filtersRef.current?.clearFilterById('sql'); + } + if (isDbConnection(currentConnectionFilter.current)) { + filtersRef.current?.clearFilterById('database'); + } + } + } + }, + [], + ); + /** * Fetches "Data connection" filter options — a combined list of databases * and semantic layers. @@ -333,7 +410,7 @@ const DatasetList: FunctionComponent = ({ otherFilters.push({ col: 'database', opr: databaseFilter.operator, - value: raw, + value: raw as string | number, }); } } @@ -859,6 +936,9 @@ const DatasetList: FunctionComponent = ({ { label: t('Database'), value: 'database' }, { label: t('Semantic Layer'), value: 'semantic_layer' }, ], + onFilterUpdate: (option: any) => { + cascadeClear('source', option?.value); + }, }, ] : []), @@ -889,6 +969,10 @@ const DatasetList: FunctionComponent = ({ ? [{ label: t('Semantic View'), value: 'semantic_view' }] : []), ], + onFilterUpdate: (option: any) => { + currentTypeFilter.current = option?.value; + cascadeClear('type', option?.value); + }, }, ] : [ @@ -915,6 +999,10 @@ const DatasetList: FunctionComponent = ({ fetchSelects: fetchConnectionOptions, paginate: true, dropdownStyle: { minWidth: WIDER_DROPDOWN_WIDTH }, + onFilterUpdate: (option: any) => { + currentConnectionFilter.current = option?.value; + cascadeClear('connection', option?.value); + }, }, { Header: t('Schema'), @@ -1381,6 +1469,7 @@ const DatasetList: FunctionComponent = ({ pageSize={PAGE_SIZE} fetchData={fetchData} filters={filterTypes} + filtersRef={filtersRef} loading={loading} initialSort={initialSort} bulkActions={bulkActions}