Compare commits

...

1 Commits

Author SHA1 Message Date
amaannawab923
70843d668a fix(plugin-chart-ag-grid-table): persist Value Aggregation "None" selection (#107166)
The grid-state change detector hashed only column order, sorts and filters,
so changing a column's Value Aggregation (aggFunc) never triggered
onColumnStateChange. On reload the column fell back to its default
initialAggFunc (sum-like), which is why selecting "None" reverted to Sum
while other values appeared to stick.

Extract the signature into getColumnStateSignature() and include per-column
aggFunc, normalizing the "None" case (null/undefined) to a stable sentinel so
the change is captured and persisted. Add unit tests.
2026-06-24 17:10:13 +05:30
3 changed files with 135 additions and 5 deletions

View File

@@ -57,6 +57,7 @@ import { SearchOption, SortByItem } from '../types';
import getInitialSortState, { shouldSort } from '../utils/getInitialSortState';
import getInitialFilterModel from '../utils/getInitialFilterModel';
import reconcileColumnState from '../utils/reconcileColumnState';
import getColumnStateSignature from '../utils/getColumnStateSignature';
import { PAGE_SIZE_OPTIONS } from '../consts';
import { getCompleteFilterState } from '../utils/filterStateManager';
@@ -337,11 +338,11 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
timestamp: Date.now(),
};
const stateHash = JSON.stringify({
columnOrder: columnState.map(c => c.colId),
sorts: sortModel,
filters: filterModel,
});
const stateHash = getColumnStateSignature(
columnState,
sortModel,
filterModel,
);
if (stateHash !== lastCapturedStateRef.current) {
lastCapturedStateRef.current = stateHash;

View File

@@ -0,0 +1,54 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { ColumnState } from '@superset-ui/core/components/ThemedAgGridReact';
type SortModelEntry = {
colId: string;
sort: 'asc' | 'desc';
sortIndex: number;
};
/**
* Builds a stable signature of the parts of AG Grid column state that must be
* persisted (and therefore must trigger an `onColumnStateChange` when they
* change): column order, per-column value aggregation, sorts and filters.
*
* The value aggregation (`aggFunc`) is normalized so that "no aggregation"
* (the AG Grid "None" option, represented as `null`/`undefined`) produces a
* distinct, stable signature. Without this, switching a column's Value
* Aggregation to "None" did not change the signature, so the change was never
* captured and the column reverted to its default aggregation on reload.
*/
export default function getColumnStateSignature(
columnState: ColumnState[],
sortModel: SortModelEntry[],
filterModel: Record<string, unknown>,
): string {
return JSON.stringify({
columnOrder: columnState.map(col => col.colId),
aggregations: columnState.map(col => ({
colId: col.colId,
// Normalize falsy "None" (null/undefined) to an explicit sentinel so it
// is preserved and distinguishable from a real aggregation function.
aggFunc: col.aggFunc ?? null,
})),
sorts: sortModel,
filters: filterModel,
});
}

View File

@@ -0,0 +1,75 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import getColumnStateSignature from '../../src/utils/getColumnStateSignature';
const sortModel: any[] = [];
const filterModel = {};
test('signature changes when a column value aggregation changes', () => {
const before = getColumnStateSignature(
[{ colId: 'sales', aggFunc: 'sum' }] as any,
sortModel,
filterModel,
);
const after = getColumnStateSignature(
[{ colId: 'sales', aggFunc: 'avg' }] as any,
sortModel,
filterModel,
);
expect(after).not.toEqual(before);
});
test('switching value aggregation to "None" (null/undefined) changes the signature', () => {
// Regression test for #107166: selecting "None" must be captured so it
// persists instead of reverting to the default Sum aggregation on reload.
const sumState = getColumnStateSignature(
[{ colId: 'sales', aggFunc: 'sum' }] as any,
sortModel,
filterModel,
);
const noneNull = getColumnStateSignature(
[{ colId: 'sales', aggFunc: null }] as any,
sortModel,
filterModel,
);
const noneUndefined = getColumnStateSignature(
[{ colId: 'sales' }] as any,
sortModel,
filterModel,
);
expect(noneNull).not.toEqual(sumState);
expect(noneUndefined).not.toEqual(sumState);
// null and undefined both represent "None" and must be treated identically.
expect(noneNull).toEqual(noneUndefined);
});
test('signature is stable when nothing changes', () => {
const a = getColumnStateSignature(
[{ colId: 'sales', aggFunc: 'sum' }] as any,
sortModel,
filterModel,
);
const b = getColumnStateSignature(
[{ colId: 'sales', aggFunc: 'sum' }] as any,
sortModel,
filterModel,
);
expect(a).toEqual(b);
});