Skip to content

Commit 8faedf0

Browse files
committed
update: filters ux improvement
1 parent e09bec8 commit 8faedf0

File tree

5 files changed

+71
-14
lines changed

5 files changed

+71
-14
lines changed

superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/components/CustomHeader.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* under the License.
2020
*/
2121

22-
import { useRef, useState } from 'react';
22+
import { useRef, useState, useEffect } from 'react';
2323
import { t } from '@superset-ui/core';
2424
import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
2525
import FilterIcon from './Filter';
@@ -61,7 +61,7 @@ const CustomHeader: React.FC<CustomHeaderParams> = ({
6161
column,
6262
api,
6363
}) => {
64-
const { initialSortState, onColumnHeaderClicked } = context;
64+
const { initialSortState, onColumnHeaderClicked, lastFilteredColumn, activeFilterColumns } = context;
6565
const colId = column?.getColId();
6666
const colDef = column?.getColDef() as CustomColDef;
6767
const userColDef = column.getUserProvidedColDef() as UserProvidedColDef;
@@ -70,7 +70,19 @@ const CustomHeader: React.FC<CustomHeaderParams> = ({
7070
const [isFilterVisible, setFilterVisible] = useState(false);
7171
const [isMenuVisible, setMenuVisible] = useState(false);
7272
const filterRef = useRef<HTMLDivElement>(null);
73-
const isFilterActive = column?.isFilterActive();
73+
const filterButtonRef = useRef<HTMLDivElement>(null);
74+
// Use activeFilterColumns from context as reliable backup for AG Grid's isFilterActive
75+
const isFilterActive = column?.isFilterActive() || (colId ? activeFilterColumns?.has(colId) : false);
76+
77+
// Auto-open filter popover if this column was last filtered
78+
useEffect(() => {
79+
setTimeout(() => {
80+
if (lastFilteredColumn === colId && !isFilterVisible) {
81+
handleFilterClick(undefined)
82+
}
83+
}, 200)
84+
85+
}, [lastFilteredColumn, colId]);
7486

7587
const currentSort = initialSortState?.[0];
7688
const isMain = userColDef?.isMain;
@@ -102,8 +114,10 @@ const CustomHeader: React.FC<CustomHeaderParams> = ({
102114
else clearSort();
103115
};
104116

105-
const handleFilterClick = async (e: React.MouseEvent) => {
106-
e.stopPropagation();
117+
const handleFilterClick = async (e: React.MouseEvent | undefined) => {
118+
if(e) {
119+
e?.stopPropagation();
120+
}
107121
setFilterVisible(!isFilterVisible);
108122

109123
const filterInstance = await api.getColumnFilterInstance<any>(column);
@@ -161,6 +175,7 @@ const CustomHeader: React.FC<CustomHeaderParams> = ({
161175
onClose={() => setFilterVisible(false)}
162176
>
163177
<FilterIconWrapper
178+
ref={filterButtonRef}
164179
className="header-filter"
165180
onClick={handleFilterClick}
166181
isFilterActive={isFilterActive}

superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTable/index.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export interface AgGridTableProps {
7676
onSearchColChange: (searchCol: string) => void;
7777
onSearchChange: (searchText: string) => void;
7878
onSortChange: (sortBy: SortByItem[]) => void;
79-
onAgGridColumnFiltersChange?: (filterModel: AgGridFilterModel) => void;
79+
onAgGridColumnFiltersChange?: (filterModel: AgGridFilterModel, lastFilteredColumn?: string) => void;
8080
id: number;
8181
percentMetrics: string[];
8282
serverPageLength: number;
@@ -289,6 +289,12 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
289289
}
290290
}, [serverPaginationData?.agGridFilterModel, serverPagination]);
291291

292+
// Calculate active filter columns from ownState filter model
293+
const activeFilterColumns = useMemo(() => {
294+
const filterModel = serverPaginationData?.agGridFilterModel || {};
295+
return new Set(Object.keys(filterModel));
296+
}, [serverPaginationData?.agGridFilterModel]);
297+
292298
const onGridReady = (params: GridReadyEvent) => {
293299
// This will make columns fill the grid width
294300
params.api.sizeColumnsToFit();
@@ -315,6 +321,23 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
315321
return;
316322
}
317323

324+
// Determine which column was just filtered by comparing models
325+
const previousModel = serverPaginationData?.agGridFilterModel || {};
326+
let lastFilteredColumn: string | undefined;
327+
328+
// Find the column that changed
329+
const allColumns = new Set([
330+
...Object.keys(filterModel),
331+
...Object.keys(previousModel),
332+
]);
333+
334+
for (const colId of allColumns) {
335+
if (!isEqual(filterModel[colId], previousModel[colId])) {
336+
lastFilteredColumn = colId;
337+
break; // Use the first changed column
338+
}
339+
}
340+
318341
// Convert AG Grid filters to SQLAlchemy format
319342
const convertedFilters = convertAgGridFiltersToSQL(
320343
filterModel as AgGridFilterModel,
@@ -327,7 +350,7 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
327350

328351
// Call the handler to update ownState if server pagination is enabled
329352
if (onAgGridColumnFiltersChange && serverPagination) {
330-
onAgGridColumnFiltersChange(filterModel as AgGridFilterModel);
353+
onAgGridColumnFiltersChange(filterModel as AgGridFilterModel, lastFilteredColumn);
331354
}
332355
}, [onAgGridColumnFiltersChange, serverPagination, serverPaginationData?.agGridFilterModel]);
333356

@@ -480,6 +503,8 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
480503
serverPaginationData?.sortBy || [],
481504
),
482505
isActiveFilterValue,
506+
lastFilteredColumn: serverPaginationData?.lastFilteredColumn, // Pass last filtered column for auto-opening popover
507+
activeFilterColumns, // Pass active filter columns as reliable backup for isFilterActive
483508
}}
484509
/>
485510
{serverPagination && (

superset-frontend/plugins/plugin-chart-ag-grid-table/src/AgGridTableChart.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,11 @@ export default function TableChart<D extends DataRecord = DataRecord>(
196196
...serverPaginationData,
197197
currentPage: pageNumber,
198198
pageSize,
199+
lastFilteredColumn: undefined, // Clear filter popover state on pagination
199200
};
200201
updateTableOwnState(setDataMask, modifiedOwnState);
201202
},
202-
[setDataMask],
203+
[setDataMask, serverPaginationData],
203204
);
204205

205206
const handlePageSizeChange = useCallback(
@@ -208,10 +209,11 @@ export default function TableChart<D extends DataRecord = DataRecord>(
208209
...serverPaginationData,
209210
currentPage: 0,
210211
pageSize,
212+
lastFilteredColumn: undefined, // Clear filter popover state on page size change
211213
};
212214
updateTableOwnState(setDataMask, modifiedOwnState);
213215
},
214-
[setDataMask],
216+
[setDataMask, serverPaginationData],
215217
);
216218

217219
const handleChangeSearchCol = (searchCol: string) => {
@@ -220,6 +222,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
220222
...(serverPaginationData || {}),
221223
searchColumn: searchCol,
222224
searchText: '',
225+
lastFilteredColumn: undefined, // Clear filter popover state on search column change
223226
};
224227
updateTableOwnState(setDataMask, modifiedOwnState);
225228
}
@@ -233,10 +236,11 @@ export default function TableChart<D extends DataRecord = DataRecord>(
233236
serverPaginationData?.searchColumn || searchOptions[0]?.value,
234237
searchText,
235238
currentPage: 0, // Reset to first page when searching
239+
lastFilteredColumn: undefined, // Clear filter popover state on search
236240
};
237241
updateTableOwnState(setDataMask, modifiedOwnState);
238242
},
239-
[setDataMask, searchOptions],
243+
[setDataMask, searchOptions, serverPaginationData],
240244
);
241245

242246
const handleSortByChange = useCallback(
@@ -245,14 +249,15 @@ export default function TableChart<D extends DataRecord = DataRecord>(
245249
const modifiedOwnState = {
246250
...serverPaginationData,
247251
sortBy,
252+
lastFilteredColumn: undefined, // Clear filter popover state on sort
248253
};
249254
updateTableOwnState(setDataMask, modifiedOwnState);
250255
},
251-
[setDataMask, serverPagination],
256+
[setDataMask, serverPagination, serverPaginationData],
252257
);
253258

254259
const handleAgGridColumnFiltersChange = useCallback(
255-
(filterModel: AgGridFilterModel) => {
260+
(filterModel: AgGridFilterModel, lastFilteredColumn?: string) => {
256261
if (!serverPagination) return;
257262

258263
// Convert AG Grid filters to SQLAlchemy format
@@ -263,6 +268,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
263268
agGridFilterModel: filterModel, // Store raw filter model for state restoration
264269
agGridSimpleFilters: converted.simpleFilters,
265270
agGridComplexWhere: converted.complexWhere,
271+
lastFilteredColumn, // Track which column was filtered to keep popover open
266272
currentPage: 0, // Reset to first page when filtering
267273
};
268274

superset-frontend/plugins/plugin-chart-ag-grid-table/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export interface ServerPaginationData {
147147
agGridFilterModel?: Record<string, any>; // Raw AG Grid filter model for state restoration
148148
agGridSimpleFilters?: AgGridColumnFilter[];
149149
agGridComplexWhere?: string;
150+
lastFilteredColumn?: string; // Track which column was last filtered to keep popover open
150151
}
151152

152153
export interface AgGridTableChartTransformedProps<
@@ -200,6 +201,9 @@ export interface SortState {
200201
export interface CustomContext {
201202
initialSortState: SortState[];
202203
onColumnHeaderClicked: (args: { column: SortState }) => void;
204+
isActiveFilterValue: (key: string, val: DataRecordValue) => boolean;
205+
lastFilteredColumn?: string; // Column that was last filtered (to keep popover open)
206+
activeFilterColumns?: Set<string>; // Columns that have active filters (from ownState)
203207
}
204208

205209
export interface CustomHeaderParams extends IHeaderParams {

superset-frontend/src/components/Chart/ChartRenderer.jsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,16 @@ class ChartRenderer extends Component {
355355
?.behaviors.find(behavior => behavior === Behavior.DrillToDetail)
356356
? { inContextMenu: this.state.inContextMenu }
357357
: {};
358-
// By pass no result component when server pagination is enabled & the table has a backend search query
358+
// By pass no result component when server pagination is enabled & the table has:
359+
// - a backend search query, OR
360+
// - AG Grid column filters applied
361+
const hasSearchQuery = (ownState?.searchText?.length || 0) > 0;
362+
const hasAgGridFilters =
363+
(ownState?.agGridSimpleFilters?.length || 0) > 0 ||
364+
(ownState?.agGridComplexWhere?.length || 0) > 0;
365+
359366
const bypassNoResult = !(
360-
formData?.server_pagination && (ownState?.searchText?.length || 0) > 0
367+
formData?.server_pagination && (hasSearchQuery || hasAgGridFilters)
361368
);
362369

363370
return (

0 commit comments

Comments
 (0)