mirror of
https://github.com/apache/superset.git
synced 2026-04-18 23:55:00 +00:00
feat: show search filtered total (#36083)
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
CSSProperties,
|
||||
DragEvent,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { typedMemo, usePrevious } from '@superset-ui/core';
|
||||
import {
|
||||
@@ -88,6 +89,7 @@ export interface DataTableProps<D extends object> extends TableOptions<D> {
|
||||
searchInputId?: string;
|
||||
onSearchColChange: (searchCol: string) => void;
|
||||
searchOptions: SearchOption[];
|
||||
onFilteredDataChange?: (rows: Row<D>[], filterValue?: string) => void;
|
||||
}
|
||||
|
||||
export interface RenderHTMLCellProps extends HTMLProps<HTMLTableCellElement> {
|
||||
@@ -130,6 +132,7 @@ export default typedMemo(function DataTable<D extends object>({
|
||||
searchInputId,
|
||||
onSearchColChange,
|
||||
searchOptions,
|
||||
onFilteredDataChange,
|
||||
...moreUseTableOptions
|
||||
}: DataTableProps<D>): JSX.Element {
|
||||
const tableHooks: PluginHook<D>[] = [
|
||||
@@ -215,6 +218,7 @@ export default typedMemo(function DataTable<D extends object>({
|
||||
wrapStickyTable,
|
||||
setColumnOrder,
|
||||
allColumns,
|
||||
rows,
|
||||
state: {
|
||||
pageIndex,
|
||||
pageSize,
|
||||
@@ -237,6 +241,30 @@ export default typedMemo(function DataTable<D extends object>({
|
||||
...tableHooks,
|
||||
);
|
||||
|
||||
const rowSignature = useMemo(
|
||||
// sort the rows by id to ensure the total is not recalculated when the rows are only reordered
|
||||
() =>
|
||||
rows
|
||||
.map((row, index) => row.id ?? index)
|
||||
.sort()
|
||||
.join('|'),
|
||||
[rows],
|
||||
);
|
||||
|
||||
const rowsRef = useRef(rows);
|
||||
rowsRef.current = rows;
|
||||
|
||||
useEffect(() => {
|
||||
if (!onFilteredDataChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchText =
|
||||
typeof filterValue === 'string' ? filterValue : undefined;
|
||||
|
||||
onFilteredDataChange(rowsRef.current, searchText);
|
||||
}, [filterValue, onFilteredDataChange, rowSignature]);
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
(query: string) => {
|
||||
if (manualSearch && onSearchChange) {
|
||||
|
||||
@@ -361,8 +361,14 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
comparisonColumns[0].key,
|
||||
]);
|
||||
const [hideComparisonKeys, setHideComparisonKeys] = useState<string[]>([]);
|
||||
// recalculated totals to display when the search filter is applied (client-side pagination)
|
||||
const [displayedTotals, setDisplayedTotals] = useState<D | undefined>(totals);
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayedTotals(totals);
|
||||
}, [totals]);
|
||||
|
||||
// only take relevant page size options
|
||||
const pageSizeOptions = useMemo(() => {
|
||||
const getServerPagination = (n: number) => n <= rowCount;
|
||||
@@ -1132,7 +1138,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
</th>
|
||||
),
|
||||
|
||||
Footer: totals ? (
|
||||
Footer: displayedTotals ? (
|
||||
i === 0 ? (
|
||||
<th key={`footer-summary-${i}`}>
|
||||
<div
|
||||
@@ -1157,7 +1163,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
</th>
|
||||
) : (
|
||||
<td key={`footer-total-${i}`} style={sharedStyle}>
|
||||
<strong>{formatColumnValue(column, totals[key])[1]}</strong>
|
||||
<strong>
|
||||
{formatColumnValue(column, displayedTotals[key])[1]}
|
||||
</strong>
|
||||
</td>
|
||||
)
|
||||
) : undefined,
|
||||
@@ -1177,7 +1185,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
getValueRange,
|
||||
emitCrossFilters,
|
||||
comparisonLabels,
|
||||
totals,
|
||||
displayedTotals,
|
||||
theme,
|
||||
sortDesc,
|
||||
groupHeaderColumns,
|
||||
@@ -1202,6 +1210,36 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
|
||||
const [searchOptions, setSearchOptions] = useState<SearchOption[]>([]);
|
||||
|
||||
const handleFilteredDataChange = useCallback(
|
||||
(rows: Row<D>[], searchText?: string) => {
|
||||
if (!totals || serverPagination) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!searchText?.trim()) {
|
||||
setDisplayedTotals(totals);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedTotals: Record<string, DataRecordValue> = { ...totals };
|
||||
|
||||
filteredColumnsMeta.forEach(column => {
|
||||
if (column.isMetric || column.isPercentMetric) {
|
||||
const aggregatedValue = rows.reduce<number>((acc, row) => {
|
||||
const rawValue = row.original?.[column.key];
|
||||
const numValue = Number(String(rawValue ?? '').replace(/,/g, ''));
|
||||
return Number.isFinite(numValue) ? acc + numValue : acc;
|
||||
}, 0);
|
||||
|
||||
updatedTotals[column.key] = aggregatedValue;
|
||||
}
|
||||
});
|
||||
|
||||
setDisplayedTotals(updatedTotals as D);
|
||||
},
|
||||
[filteredColumnsMeta, serverPagination, totals],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const options = (
|
||||
columns as unknown as ColumnWithLooseAccessor &
|
||||
@@ -1356,6 +1394,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
manualSearch={serverPagination}
|
||||
onSearchChange={debouncedSearch}
|
||||
searchOptions={searchOptions}
|
||||
onFilteredDataChange={handleFilteredDataChange}
|
||||
/>
|
||||
</Styles>
|
||||
);
|
||||
|
||||
@@ -271,6 +271,24 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
};
|
||||
updateTableOwnState(options?.hooks?.setDataMask, modifiedOwnState);
|
||||
}
|
||||
|
||||
if (formData.server_pagination) {
|
||||
// Add search filter if search text exists
|
||||
if (ownState.searchText && ownState?.searchColumn) {
|
||||
queryObject = {
|
||||
...queryObject,
|
||||
filters: [
|
||||
...(queryObject.filters || []),
|
||||
{
|
||||
col: ownState?.searchColumn,
|
||||
op: 'ILIKE',
|
||||
val: `${ownState.searchText}%`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Because we use same buildQuery for all table on the page we need split them by id
|
||||
options?.hooks?.setCachedChanges({
|
||||
[formData.slice_id]: queryObject.filters,
|
||||
@@ -320,23 +338,6 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
];
|
||||
}
|
||||
|
||||
if (formData.server_pagination) {
|
||||
// Add search filter if search text exists
|
||||
if (ownState.searchText && ownState?.searchColumn) {
|
||||
queryObject = {
|
||||
...queryObject,
|
||||
filters: [
|
||||
...(queryObject.filters || []),
|
||||
{
|
||||
col: ownState?.searchColumn,
|
||||
op: 'ILIKE',
|
||||
val: `${ownState.searchText}%`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Now since row limit control is always visible even
|
||||
// in case of server pagination
|
||||
// we must use row limit from form data
|
||||
|
||||
Reference in New Issue
Block a user