Display and update aggregate queries in kanban views (#8833)

Closes #8752, #8753, #8754

Implements usage of aggregate queries in kanban views.

https://github.com/user-attachments/assets/732590ca-2785-4c57-82d5-d999a2279e92

TO DO

1. write tests + storybook
2. Fix values displayed should have the same format as defined in number
fields + Fix display for amountMicros

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Marie
2024-12-03 22:46:57 +01:00
committed by GitHub
parent 5e891a135b
commit 2fc247cb21
67 changed files with 1670 additions and 104 deletions

View File

@ -74,7 +74,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
'id' | 'name' | 'icon' | 'kanbanFieldMetadataId' | 'type'
>
>,
shouldCopyFiltersAndSorts?: boolean,
shouldCopyFiltersAndSortsAndAggregate?: boolean,
) => {
const currentViewId = getSnapshotValue(
snapshot,
@ -101,6 +101,13 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
key: null,
kanbanFieldMetadataId:
kanbanFieldMetadataId ?? sourceView.kanbanFieldMetadataId,
kanbanAggregateOperation: shouldCopyFiltersAndSortsAndAggregate
? sourceView.kanbanAggregateOperation
: undefined,
kanbanAggregateOperationFieldMetadataId:
shouldCopyFiltersAndSortsAndAggregate
? sourceView.kanbanAggregateOperationFieldMetadataId
: undefined,
type: type ?? sourceView.type,
objectMetadataId: sourceView.objectMetadataId,
});
@ -143,7 +150,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
await createViewGroupRecords(viewGroupsToCreate, newView);
}
if (shouldCopyFiltersAndSorts === true) {
if (shouldCopyFiltersAndSortsAndAggregate === true) {
const sourceViewCombinedFilterGroups = getViewFilterGroupsCombined(
sourceView.id,
);

View File

@ -0,0 +1,29 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useUpdateView } from '@/views/hooks/useUpdateView';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { useCallback } from 'react';
export const useUpdateViewAggregate = () => {
const currentViewId = useRecoilComponentValueV2(currentViewIdComponentState);
const { updateView } = useUpdateView();
const updateViewAggregate = useCallback(
({
kanbanAggregateOperationFieldMetadataId,
kanbanAggregateOperation,
}: {
kanbanAggregateOperationFieldMetadataId: string | null;
kanbanAggregateOperation: AGGREGATE_OPERATIONS;
}) =>
updateView({
id: currentViewId,
kanbanAggregateOperationFieldMetadataId,
kanbanAggregateOperation,
}),
[currentViewId, updateView],
);
return {
updateViewAggregate,
};
};

View File

@ -1,3 +1,4 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { ViewField } from '@/views/types/ViewField';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
@ -12,6 +13,8 @@ export type GraphQLView = {
type: ViewType;
key: ViewKey | null;
kanbanFieldMetadataId: string;
kanbanAggregateOperation?: AGGREGATE_OPERATIONS | null;
kanbanAggregateOperationFieldMetadataId?: string | null;
objectMetadataId: string;
isCompact: boolean;
viewFields: ViewField[];

View File

@ -1,3 +1,4 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { ViewField } from '@/views/types/ViewField';
import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
@ -19,6 +20,8 @@ export type View = {
viewFilterGroups?: ViewFilterGroup[];
viewSorts: ViewSort[];
kanbanFieldMetadataId: string;
kanbanAggregateOperation: AGGREGATE_OPERATIONS | null;
kanbanAggregateOperationFieldMetadataId: string | null;
position: number;
icon: string;
__typename: 'View';

View File

@ -1,5 +1,6 @@
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
export type ViewField = {
@ -9,6 +10,7 @@ export type ViewField = {
position: number;
isVisible: boolean;
size: number;
aggregateOperation?: AGGREGATE_OPERATIONS | null;
definition:
| ColumnDefinition<FieldMetadata>
| RecordBoardFieldDefinition<FieldMetadata>;

View File

@ -17,6 +17,9 @@ export const getObjectMetadataItemViews = (
position: view.position,
objectMetadataId: view.objectMetadataId,
kanbanFieldMetadataId: view.kanbanFieldMetadataId,
kanbanAggregateOperation: view.kanbanAggregateOperation,
kanbanAggregateOperationFieldMetadataId:
view.kanbanAggregateOperationFieldMetadataId,
icon: view.icon,
}));
};

View File

@ -47,6 +47,7 @@ export const mapViewFieldsToColumnDefinitions = ({
isLabelIdentifier,
isVisible: isLabelIdentifier || viewField.isVisible,
viewFieldId: viewField.id,
aggregateOperation: viewField.aggregateOperation,
isSortable: correspondingColumnDefinition.isSortable,
isFilterable: correspondingColumnDefinition.isFilterable,
defaultValue: correspondingColumnDefinition.defaultValue,

View File

@ -13,48 +13,40 @@ import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPic
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid';
export const useCreateViewFromCurrentState = (viewBarInstanceId?: string) => {
export const useCreateViewFromCurrentState = () => {
const { closeAndResetViewPicker } = useCloseAndResetViewPicker();
const viewPickerInputNameCallbackState = useRecoilComponentCallbackStateV2(
viewPickerInputNameComponentState,
viewBarInstanceId,
);
const viewPickerSelectedIconCallbackState = useRecoilComponentCallbackStateV2(
viewPickerSelectedIconComponentState,
viewBarInstanceId,
);
const viewPickerTypeCallbackState = useRecoilComponentCallbackStateV2(
viewPickerTypeComponentState,
viewBarInstanceId,
);
const viewPickerKanbanFieldMetadataIdCallbackState =
useRecoilComponentCallbackStateV2(
viewPickerKanbanFieldMetadataIdComponentState,
viewBarInstanceId,
);
const viewPickerIsPersistingCallbackState = useRecoilComponentCallbackStateV2(
viewPickerIsPersistingComponentState,
viewBarInstanceId,
);
const viewPickerIsDirtyCallbackState = useRecoilComponentCallbackStateV2(
viewPickerIsDirtyComponentState,
viewBarInstanceId,
);
const viewPickerModeCallbackState = useRecoilComponentCallbackStateV2(
viewPickerModeComponentState,
viewBarInstanceId,
);
const { createViewFromCurrentView } =
useCreateViewFromCurrentView(viewBarInstanceId);
const { changeView } = useChangeView(viewBarInstanceId);
const { createViewFromCurrentView } = useCreateViewFromCurrentView();
const { changeView } = useChangeView();
const createViewFromCurrentState = useRecoilCallback(
({ snapshot, set }) =>
@ -78,7 +70,7 @@ export const useCreateViewFromCurrentState = (viewBarInstanceId?: string) => {
viewPickerModeCallbackState,
);
const shouldCopyFiltersAndSorts =
const shouldCopyFiltersAndSortsAndAggregate =
viewPickerMode === 'create-from-current';
const id = v4();
@ -94,7 +86,7 @@ export const useCreateViewFromCurrentState = (viewBarInstanceId?: string) => {
type,
kanbanFieldMetadataId,
},
shouldCopyFiltersAndSorts,
shouldCopyFiltersAndSortsAndAggregate,
);
closeAndResetViewPicker();