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:
@@ -1,14 +1,19 @@
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
|
||||
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { usePersistView } from '@/views/hooks/internal/usePersistView';
|
||||
import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { useCallback } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import {
|
||||
isDefined,
|
||||
upsertIntoArrayOfObjectsComparingId,
|
||||
} from 'twenty-shared/utils';
|
||||
import { type CoreView } from '~/generated/graphql';
|
||||
|
||||
export const useUpdateViewAggregate = () => {
|
||||
const { canPersistChanges } = useCanPersistViewChanges();
|
||||
@@ -16,62 +21,64 @@ export const useUpdateViewAggregate = () => {
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
const { updateView } = usePersistView();
|
||||
const { loadRecordIndexStates } = useLoadRecordIndexStates();
|
||||
|
||||
const setRecordIndexKanbanAggregateOperationState = useSetRecoilState(
|
||||
recordIndexKanbanAggregateOperationState,
|
||||
);
|
||||
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const updateViewAggregate = useCallback(
|
||||
({
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
kanbanAggregateOperation,
|
||||
objectMetadataId,
|
||||
}: {
|
||||
kanbanAggregateOperationFieldMetadataId: string | null;
|
||||
kanbanAggregateOperation: ExtendedAggregateOperations | null;
|
||||
objectMetadataId: string;
|
||||
}) => {
|
||||
if (!canPersistChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
const convertedKanbanAggregateOperation = isDefined(
|
||||
const updateViewAggregate = useRecoilCallback(
|
||||
({ set }) =>
|
||||
async ({
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
kanbanAggregateOperation,
|
||||
)
|
||||
? convertExtendedAggregateOperationToAggregateOperation(
|
||||
kanbanAggregateOperation,
|
||||
)
|
||||
: null;
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
kanbanAggregateOperationFieldMetadataId: string | null;
|
||||
kanbanAggregateOperation: ExtendedAggregateOperations | null;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
if (!canPersistChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDefined(currentViewId)) {
|
||||
return;
|
||||
}
|
||||
const convertedKanbanAggregateOperation = isDefined(
|
||||
kanbanAggregateOperation,
|
||||
)
|
||||
? convertExtendedAggregateOperationToAggregateOperation(
|
||||
kanbanAggregateOperation,
|
||||
)
|
||||
: null;
|
||||
|
||||
updateView({
|
||||
id: currentViewId,
|
||||
input: {
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
kanbanAggregateOperation: convertedKanbanAggregateOperation,
|
||||
},
|
||||
});
|
||||
if (!isDefined(currentViewId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRecordIndexKanbanAggregateOperationState({
|
||||
operation: kanbanAggregateOperation,
|
||||
fieldMetadataId: kanbanAggregateOperationFieldMetadataId,
|
||||
});
|
||||
const updatedViewResult = await updateView({
|
||||
id: currentViewId,
|
||||
input: {
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
kanbanAggregateOperation: convertedKanbanAggregateOperation,
|
||||
},
|
||||
});
|
||||
|
||||
refreshCoreViewsByObjectMetadataId(objectMetadataId);
|
||||
},
|
||||
[
|
||||
canPersistChanges,
|
||||
currentViewId,
|
||||
updateView,
|
||||
setRecordIndexKanbanAggregateOperationState,
|
||||
refreshCoreViewsByObjectMetadataId,
|
||||
],
|
||||
if (updatedViewResult.status === 'successful') {
|
||||
const updatedCoreView = updatedViewResult.response.data
|
||||
?.updateCoreView as CoreView;
|
||||
|
||||
if (!isDefined(updatedCoreView)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set(coreViewsState, (currentCoreViews) =>
|
||||
upsertIntoArrayOfObjectsComparingId(
|
||||
currentCoreViews,
|
||||
updatedCoreView,
|
||||
),
|
||||
);
|
||||
|
||||
const updatedView = convertCoreViewToView(updatedCoreView);
|
||||
|
||||
loadRecordIndexStates(updatedView, objectMetadataItem);
|
||||
}
|
||||
},
|
||||
[canPersistChanges, currentViewId, updateView, loadRecordIndexStates],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user