mirror of
https://github.com/apache/superset.git
synced 2026-04-18 15:44:57 +00:00
feat(ag-grid): Server Side Filtering for Column Level Filters (#35683)
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
ensureIsArray,
|
||||
getColumnLabel,
|
||||
getMetricLabel,
|
||||
isDefined,
|
||||
isPhysicalColumn,
|
||||
QueryFormColumn,
|
||||
QueryFormMetric,
|
||||
@@ -253,13 +254,23 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
};
|
||||
|
||||
sortByFromOwnState = sortSource
|
||||
.map((sortItem: any) => {
|
||||
const colId = sortItem?.colId || sortItem?.key;
|
||||
const sortKey = mapColIdToIdentifier(colId);
|
||||
if (!sortKey) return null;
|
||||
const isDesc = sortItem?.sort === 'desc' || sortItem?.desc;
|
||||
return [sortKey, !isDesc] as QueryFormOrderBy;
|
||||
})
|
||||
.map(
|
||||
(sortItem: {
|
||||
colId?: string | number;
|
||||
key?: string | number;
|
||||
sort?: string;
|
||||
desc?: boolean;
|
||||
}) => {
|
||||
const colId = isDefined(sortItem?.colId)
|
||||
? sortItem.colId
|
||||
: sortItem?.key;
|
||||
if (!isDefined(colId)) return null;
|
||||
const sortKey = mapColIdToIdentifier(String(colId));
|
||||
if (!sortKey) return null;
|
||||
const isDesc = sortItem?.sort === 'desc' || sortItem?.desc;
|
||||
return [sortKey, !isDesc] as QueryFormOrderBy;
|
||||
},
|
||||
)
|
||||
.filter((item): item is QueryFormOrderBy => item !== null);
|
||||
|
||||
// Add secondary sort for stable ordering (matches AG Grid's stable sort behavior)
|
||||
@@ -361,6 +372,8 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
...options?.ownState,
|
||||
currentPage: 0,
|
||||
pageSize: queryObject.row_limit ?? 0,
|
||||
lastFilteredColumn: undefined,
|
||||
lastFilteredInputPosition: undefined,
|
||||
};
|
||||
updateTableOwnState(options?.hooks?.setDataMask, modifiedOwnState);
|
||||
}
|
||||
@@ -370,21 +383,6 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
});
|
||||
|
||||
const extraQueries: QueryObject[] = [];
|
||||
if (
|
||||
metrics?.length &&
|
||||
formData.show_totals &&
|
||||
queryMode === QueryMode.Aggregate
|
||||
) {
|
||||
extraQueries.push({
|
||||
...queryObject,
|
||||
columns: [],
|
||||
row_limit: 0,
|
||||
row_offset: 0,
|
||||
post_processing: [],
|
||||
order_desc: undefined, // we don't need orderby stuff here,
|
||||
orderby: undefined, // because this query will be used for get total aggregation.
|
||||
});
|
||||
}
|
||||
|
||||
const interactiveGroupBy = formData.extra_form_data?.interactive_groupby;
|
||||
if (interactiveGroupBy && queryObject.columns) {
|
||||
@@ -445,6 +443,70 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
],
|
||||
};
|
||||
}
|
||||
// Add AG Grid column filters from ownState (non-metric filters only)
|
||||
if (
|
||||
ownState.agGridSimpleFilters &&
|
||||
ownState.agGridSimpleFilters.length > 0
|
||||
) {
|
||||
// Get columns that have AG Grid filters
|
||||
const agGridFilterColumns = new Set(
|
||||
ownState.agGridSimpleFilters.map(
|
||||
(filter: { col: string }) => filter.col,
|
||||
),
|
||||
);
|
||||
|
||||
// Remove existing TEMPORAL_RANGE filters for columns that have new AG Grid filters
|
||||
// This prevents duplicate filters like "No filter" and actual date ranges
|
||||
const existingFilters = (queryObject.filters || []).filter(filter => {
|
||||
// Keep filter if it doesn't have the expected structure
|
||||
if (!filter || typeof filter !== 'object' || !filter.col) {
|
||||
return true;
|
||||
}
|
||||
// Keep filter if it's not a temporal range filter
|
||||
if (filter.op !== 'TEMPORAL_RANGE') {
|
||||
return true;
|
||||
}
|
||||
// Remove if this column has an AG Grid filter
|
||||
return !agGridFilterColumns.has(filter.col);
|
||||
});
|
||||
|
||||
queryObject = {
|
||||
...queryObject,
|
||||
filters: [...existingFilters, ...ownState.agGridSimpleFilters],
|
||||
};
|
||||
}
|
||||
|
||||
// Add AG Grid complex WHERE clause from ownState (non-metric filters)
|
||||
if (ownState.agGridComplexWhere && ownState.agGridComplexWhere.trim()) {
|
||||
const existingWhere = queryObject.extras?.where;
|
||||
const combinedWhere = existingWhere
|
||||
? `${existingWhere} AND ${ownState.agGridComplexWhere}`
|
||||
: ownState.agGridComplexWhere;
|
||||
|
||||
queryObject = {
|
||||
...queryObject,
|
||||
extras: {
|
||||
...queryObject.extras,
|
||||
where: combinedWhere,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Add AG Grid HAVING clause from ownState (metric filters only)
|
||||
if (ownState.agGridHavingClause && ownState.agGridHavingClause.trim()) {
|
||||
const existingHaving = queryObject.extras?.having;
|
||||
const combinedHaving = existingHaving
|
||||
? `${existingHaving} AND ${ownState.agGridHavingClause}`
|
||||
: ownState.agGridHavingClause;
|
||||
|
||||
queryObject = {
|
||||
...queryObject,
|
||||
extras: {
|
||||
...queryObject.extras,
|
||||
having: combinedHaving,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (isDownloadQuery) {
|
||||
@@ -481,6 +543,54 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
}
|
||||
}
|
||||
|
||||
// Create totals query AFTER all filters (including AG Grid filters) are applied
|
||||
// This ensures we can properly exclude AG Grid WHERE filters from the totals
|
||||
if (
|
||||
metrics?.length &&
|
||||
formData.show_totals &&
|
||||
queryMode === QueryMode.Aggregate
|
||||
) {
|
||||
// Create a copy of extras without the AG Grid WHERE clause
|
||||
// AG Grid filters in extras.where can reference calculated columns
|
||||
// which aren't available in the totals subquery
|
||||
const totalsExtras = { ...queryObject.extras };
|
||||
if (ownState.agGridComplexWhere) {
|
||||
// Remove AG Grid WHERE clause from totals query
|
||||
const whereClause = totalsExtras.where;
|
||||
if (whereClause) {
|
||||
// Remove the AG Grid filter part from the WHERE clause using string methods
|
||||
const agGridWhere = ownState.agGridComplexWhere;
|
||||
let newWhereClause = whereClause;
|
||||
|
||||
// Try to remove with " AND " before
|
||||
newWhereClause = newWhereClause.replace(` AND ${agGridWhere}`, '');
|
||||
// Try to remove with " AND " after
|
||||
newWhereClause = newWhereClause.replace(`${agGridWhere} AND `, '');
|
||||
// If it's the only clause, remove it entirely
|
||||
if (newWhereClause === agGridWhere) {
|
||||
newWhereClause = '';
|
||||
}
|
||||
|
||||
if (newWhereClause.trim()) {
|
||||
totalsExtras.where = newWhereClause;
|
||||
} else {
|
||||
delete totalsExtras.where;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extraQueries.push({
|
||||
...queryObject,
|
||||
columns: [],
|
||||
extras: totalsExtras, // Use extras with AG Grid WHERE removed
|
||||
row_limit: 0,
|
||||
row_offset: 0,
|
||||
post_processing: [],
|
||||
order_desc: undefined, // we don't need orderby stuff here,
|
||||
orderby: undefined, // because this query will be used for get total aggregation.
|
||||
});
|
||||
}
|
||||
|
||||
// Now since row limit control is always visible even
|
||||
// in case of server pagination
|
||||
// we must use row limit from form data
|
||||
@@ -506,8 +616,8 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
// Use this closure to cache changing of external filters, if we have server pagination we need reset page to 0, after
|
||||
// external filter changed
|
||||
export const cachedBuildQuery = (): BuildQuery<TableChartFormData> => {
|
||||
let cachedChanges: any = {};
|
||||
const setCachedChanges = (newChanges: any) => {
|
||||
let cachedChanges: Record<string, unknown> = {};
|
||||
const setCachedChanges = (newChanges: Record<string, unknown>) => {
|
||||
cachedChanges = { ...cachedChanges, ...newChanges };
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user