Improve record group aggregate query performance (#15828)

This PR is a first step for improving the performance on boards and
table with groups.

It is related to :
https://github.com/twentyhq/core-team-issues/issues/1870

Here we implement only a groupBy query for aggregate values in the group
section.

This also allows to improve the DX of aggregate computing and group by
query creation and parsing.

## Demo 

Main : 



https://github.com/user-attachments/assets/5d2a8077-5322-4928-a551-f03583bcfb87



This PR : 



https://github.com/user-attachments/assets/d0e82b28-72c3-40f0-b5cb-045f1a736ffb



## Aggregate update bug fix

This PR also solves a bug with aggregate update that was already present
on main.

The bug is linked to core views not being updated properly during a
modification of the aggregate operation on a view.

We should probably improve the view lifecycle and state management
because it is a bit too complex right now.

Main : 


https://github.com/user-attachments/assets/10dbfb8b-dfa0-4f21-8698-d222871a43e7

This PR : 


https://github.com/user-attachments/assets/bac41890-5191-4e4c-b82b-19b1039e9ab5

## Miscellaneous 

- Fixed optimistic rendering of group by queries, when adding a new
record, the aggregate recomputes well.

## TODO 

- We might want to improve the optimistic for group by queries that
don't have records nor more than one dimension.
This commit is contained in:
Lucas Bordeau
2025-11-23 20:40:23 +01:00
committed by GitHub
parent 4d55fef874
commit 061cc897af
61 changed files with 1356 additions and 930 deletions
@@ -0,0 +1,60 @@
import { upsertIntoArrayOfObjectsComparingId } from '@/utils/array/upsertIntoArrayOfObjectComparingId';
type TestObject = {
id: string;
name: string;
};
const mockTestObjects: TestObject[] = [
{
id: '1',
name: 'Test 1',
},
{
id: '2',
name: 'Test 2',
},
{
id: '3',
name: 'Test 3',
},
{
id: '4',
name: 'Test 4',
},
];
describe('upsertIntoArrayOfObjectsComparingId', () => {
it('should insert in empty array', () => {
expect(
upsertIntoArrayOfObjectsComparingId([], mockTestObjects[0]),
).toStrictEqual([mockTestObjects[0]]);
});
it('should insert in array', () => {
const newItem: TestObject = {
id: '5',
name: 'Test 5',
};
expect(
upsertIntoArrayOfObjectsComparingId(mockTestObjects, newItem),
).toStrictEqual([...mockTestObjects, newItem]);
});
it('should replace in array', () => {
const itemToReplace: TestObject = {
id: '4',
name: 'Test 4 replaced',
};
expect(
upsertIntoArrayOfObjectsComparingId(mockTestObjects, itemToReplace),
).toStrictEqual([
mockTestObjects[0],
mockTestObjects[1],
mockTestObjects[2],
itemToReplace,
]);
});
});
@@ -0,0 +1,22 @@
import { findById } from '@/utils/array/findById';
export const upsertIntoArrayOfObjectsComparingId = <T extends { id: string }>(
arrayToUpsertInto: T[],
itemToUpsert: T,
): T[] => {
const alreadyExistingItemIndex = arrayToUpsertInto.findIndex(
findById(itemToUpsert.id),
);
const shouldReplaceItem = alreadyExistingItemIndex > -1;
if (shouldReplaceItem) {
const newArray = [...arrayToUpsertInto];
newArray.splice(alreadyExistingItemIndex, 1, itemToUpsert);
return newArray;
} else {
return arrayToUpsertInto.concat(itemToUpsert);
}
};