diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregateForView.test.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregateForView.test.ts index b442b22f9..a078013e8 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregateForView.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/buildRecordGqlFieldsAggregateForView.test.ts @@ -6,7 +6,6 @@ import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/Agg import { FieldMetadataType } from '~/generated-metadata/graphql'; const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a'; -const MOCK_KANBAN_FIELD = 'stage'; describe('buildRecordGqlFieldsAggregateForView', () => { const mockObjectMetadata: ObjectMetadataItem = { @@ -53,7 +52,6 @@ describe('buildRecordGqlFieldsAggregateForView', () => { const result = buildRecordGqlFieldsAggregateForView({ objectMetadataItem: mockObjectMetadata, recordIndexKanbanAggregateOperation: kanbanAggregateOperation, - fieldNameForCount: MOCK_KANBAN_FIELD, }); expect(result).toEqual({ @@ -70,11 +68,10 @@ describe('buildRecordGqlFieldsAggregateForView', () => { const result = buildRecordGqlFieldsAggregateForView({ objectMetadataItem: mockObjectMetadata, recordIndexKanbanAggregateOperation: operation, - fieldNameForCount: MOCK_KANBAN_FIELD, }); expect(result).toEqual({ - [MOCK_KANBAN_FIELD]: [AGGREGATE_OPERATIONS.count], + id: [AGGREGATE_OPERATIONS.count], }); }); @@ -88,7 +85,6 @@ describe('buildRecordGqlFieldsAggregateForView', () => { buildRecordGqlFieldsAggregateForView({ objectMetadataItem: mockObjectMetadata, recordIndexKanbanAggregateOperation: operation, - fieldNameForCount: MOCK_KANBAN_FIELD, }), ).toThrow( `No field found to compute aggregate operation ${AGGREGATE_OPERATIONS.sum} on object ${mockObjectMetadata.nameSingular}`, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts index f04a92574..7a48508d3 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/__tests__/computeAggregateValueAndLabel.test.ts @@ -9,7 +9,6 @@ import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constant import { FieldMetadataType } from '~/generated/graphql'; const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a'; -const MOCK_KANBAN_FIELD_NAME = 'stage'; describe('computeAggregateValueAndLabel', () => { const mockObjectMetadata: ObjectMetadataItem = { @@ -36,7 +35,6 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadata, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.sum, - fallbackFieldName: MOCK_KANBAN_FIELD_NAME, ...defaultParams, }); @@ -55,7 +53,6 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadata, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.sum, - fallbackFieldName: MOCK_KANBAN_FIELD_NAME, ...defaultParams, }); @@ -93,7 +90,6 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadataWithPercentageField, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.avg, - fallbackFieldName: MOCK_KANBAN_FIELD_NAME, ...defaultParams, }); @@ -131,7 +127,6 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadataWithDecimalsField, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: AGGREGATE_OPERATIONS.sum, - fallbackFieldName: MOCK_KANBAN_FIELD_NAME, ...defaultParams, }); @@ -166,7 +161,6 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadataWithDatetimeField, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: DATE_AGGREGATE_OPERATIONS.earliest, - fallbackFieldName: MOCK_KANBAN_FIELD_NAME, ...defaultParams, }); @@ -201,7 +195,6 @@ describe('computeAggregateValueAndLabel', () => { objectMetadataItem: mockObjectMetadataWithDatetimeField, fieldMetadataId: MOCK_FIELD_ID, aggregateOperation: DATE_AGGREGATE_OPERATIONS.latest, - fallbackFieldName: MOCK_KANBAN_FIELD_NAME, ...defaultParams, }); @@ -214,7 +207,7 @@ describe('computeAggregateValueAndLabel', () => { it('should default to count when field not found', () => { const mockData = { - [MOCK_KANBAN_FIELD_NAME]: { + id: { [AGGREGATE_OPERATIONS.count]: 42, }, } as AggregateRecordsData; @@ -222,7 +215,6 @@ describe('computeAggregateValueAndLabel', () => { const result = computeAggregateValueAndLabel({ data: mockData, objectMetadataItem: mockObjectMetadata, - fallbackFieldName: MOCK_KANBAN_FIELD_NAME, ...defaultParams, }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView.ts index 0c0b1b82d..ad27e0355 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView.ts @@ -2,16 +2,15 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate'; import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; +import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared'; import { isDefined } from '~/utils/isDefined'; export const buildRecordGqlFieldsAggregateForView = ({ objectMetadataItem, recordIndexKanbanAggregateOperation, - fieldNameForCount, }: { objectMetadataItem: ObjectMetadataItem; recordIndexKanbanAggregateOperation: KanbanAggregateOperation; - fieldNameForCount: string; }): RecordGqlFieldsAggregate => { let recordGqlFieldsAggregate = {}; @@ -31,7 +30,9 @@ export const buildRecordGqlFieldsAggregateForView = ({ ); } else { recordGqlFieldsAggregate = { - [fieldNameForCount]: [AGGREGATE_OPERATIONS.count], + [FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: [ + AGGREGATE_OPERATIONS.count, + ], }; } } else { diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts index d71a4dc80..1fcadf4d8 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel.ts @@ -9,6 +9,7 @@ import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/ import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import isEmpty from 'lodash.isempty'; +import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { formatAmount } from '~/utils/format/formatAmount'; import { formatNumber } from '~/utils/format/number'; @@ -21,7 +22,6 @@ export const computeAggregateValueAndLabel = ({ objectMetadataItem, fieldMetadataId, aggregateOperation, - fallbackFieldName, dateFormat, timeFormat, timeZone, @@ -30,7 +30,6 @@ export const computeAggregateValueAndLabel = ({ objectMetadataItem: ObjectMetadataItem; fieldMetadataId?: string | null; aggregateOperation?: ExtendedAggregateOperations | null; - fallbackFieldName?: string; dateFormat: DateFormat; timeFormat: TimeFormat; timeZone: string; @@ -43,11 +42,11 @@ export const computeAggregateValueAndLabel = ({ ); if (!isDefined(field)) { - if (!fallbackFieldName) { - throw new Error('Missing fallback field name'); - } return { - value: data?.[fallbackFieldName]?.[AGGREGATE_OPERATIONS.count], + value: + data?.[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]?.[ + AGGREGATE_OPERATIONS.count + ], label: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`, labelWithFieldName: `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`, }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useAggregateRecordsForHeader.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useAggregateRecordsForHeader.ts index 42d5cff5d..60a92cc4c 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useAggregateRecordsForHeader.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useAggregateRecordsForHeader.ts @@ -21,7 +21,6 @@ type UseAggregateRecordsProps = { export const useAggregateRecordsForHeader = ({ objectMetadataItem, additionalFilters = {}, - fallbackFieldName, }: UseAggregateRecordsProps) => { const recordIndexViewFilterGroups = useRecoilValue( recordIndexViewFilterGroupsState, @@ -47,7 +46,6 @@ export const useAggregateRecordsForHeader = ({ const recordGqlFieldsAggregate = buildRecordGqlFieldsAggregateForView({ objectMetadataItem, recordIndexKanbanAggregateOperation, - fieldNameForCount: fallbackFieldName, }); const { data } = useAggregateRecords({ @@ -61,7 +59,6 @@ export const useAggregateRecordsForHeader = ({ objectMetadataItem, fieldMetadataId: recordIndexKanbanAggregateOperation?.fieldMetadataId, aggregateOperation: recordIndexKanbanAggregateOperation?.operation, - fallbackFieldName, dateFormat, timeFormat, timeZone, diff --git a/packages/twenty-front/src/modules/object-record/utils/getAvailableAggregationsFromObjectFields.ts b/packages/twenty-front/src/modules/object-record/utils/getAvailableAggregationsFromObjectFields.ts index 5ace36080..ee50c8eb8 100644 --- a/packages/twenty-front/src/modules/object-record/utils/getAvailableAggregationsFromObjectFields.ts +++ b/packages/twenty-front/src/modules/object-record/utils/getAvailableAggregationsFromObjectFields.ts @@ -2,7 +2,11 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; -import { capitalize, isFieldMetadataDateKind } from 'twenty-shared'; +import { + capitalize, + FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION, + isFieldMetadataDateKind, +} from 'twenty-shared'; import { FieldMetadataType } from '~/generated-metadata/graphql'; type NameForAggregation = { @@ -16,59 +20,66 @@ type Aggregations = { export const getAvailableAggregationsFromObjectFields = ( fields: FieldMetadataItem[], ): Aggregations => { - return fields.reduce>((acc, field) => { - if (field.isSystem === true) { - return acc; - } + return fields.reduce>( + (acc, field) => { + if (field.isSystem === true) { + return acc; + } + + if (field.type === FieldMetadataType.RELATION) { + acc[field.name] = { + [AGGREGATE_OPERATIONS.count]: 'totalCount', + }; + return acc; + } - if (field.type === FieldMetadataType.RELATION) { acc[field.name] = { + [AGGREGATE_OPERATIONS.countUniqueValues]: `countUniqueValues${capitalize(field.name)}`, + [AGGREGATE_OPERATIONS.countEmpty]: `countEmpty${capitalize(field.name)}`, + [AGGREGATE_OPERATIONS.countNotEmpty]: `countNotEmpty${capitalize(field.name)}`, + [AGGREGATE_OPERATIONS.percentageEmpty]: `percentageEmpty${capitalize(field.name)}`, + [AGGREGATE_OPERATIONS.percentageNotEmpty]: `percentageNotEmpty${capitalize(field.name)}`, [AGGREGATE_OPERATIONS.count]: 'totalCount', }; + + if (field.type === FieldMetadataType.NUMBER) { + acc[field.name] = { + ...acc[field.name], + [AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}`, + [AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}`, + [AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}`, + [AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}`, + }; + } + + if (field.type === FieldMetadataType.CURRENCY) { + acc[field.name] = { + ...acc[field.name], + [AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}AmountMicros`, + [AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}AmountMicros`, + [AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}AmountMicros`, + [AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}AmountMicros`, + }; + } + + if (isFieldMetadataDateKind(field.type) === true) { + acc[field.name] = { + ...acc[field.name], + [DATE_AGGREGATE_OPERATIONS.earliest]: `min${capitalize(field.name)}`, + [DATE_AGGREGATE_OPERATIONS.latest]: `max${capitalize(field.name)}`, + }; + } + + if (acc[field.name] === undefined) { + acc[field.name] = {}; + } + return acc; - } - - acc[field.name] = { - [AGGREGATE_OPERATIONS.countUniqueValues]: `countUniqueValues${capitalize(field.name)}`, - [AGGREGATE_OPERATIONS.countEmpty]: `countEmpty${capitalize(field.name)}`, - [AGGREGATE_OPERATIONS.countNotEmpty]: `countNotEmpty${capitalize(field.name)}`, - [AGGREGATE_OPERATIONS.percentageEmpty]: `percentageEmpty${capitalize(field.name)}`, - [AGGREGATE_OPERATIONS.percentageNotEmpty]: `percentageNotEmpty${capitalize(field.name)}`, - [AGGREGATE_OPERATIONS.count]: 'totalCount', - }; - - if (field.type === FieldMetadataType.NUMBER) { - acc[field.name] = { - ...acc[field.name], - [AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}`, - [AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}`, - [AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}`, - [AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}`, - }; - } - - if (field.type === FieldMetadataType.CURRENCY) { - acc[field.name] = { - ...acc[field.name], - [AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}AmountMicros`, - [AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}AmountMicros`, - [AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}AmountMicros`, - [AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}AmountMicros`, - }; - } - - if (isFieldMetadataDateKind(field.type) === true) { - acc[field.name] = { - ...acc[field.name], - [DATE_AGGREGATE_OPERATIONS.earliest]: `min${capitalize(field.name)}`, - [DATE_AGGREGATE_OPERATIONS.latest]: `max${capitalize(field.name)}`, - }; - } - - if (acc[field.name] === undefined) { - acc[field.name] = {}; - } - - return acc; - }, {}); + }, + { + [FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: { + [AGGREGATE_OPERATIONS.count]: 'totalCount', + }, + }, + ); }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts index 5f80f9266..f659720bf 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts @@ -3,6 +3,7 @@ import { GraphQLISODateTime } from '@nestjs/graphql'; import { GraphQLFloat, GraphQLInt, GraphQLScalarType } from 'graphql'; import { capitalize, + FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION, FieldMetadataType, isFieldMetadataDateKind, } from 'twenty-shared'; @@ -176,7 +177,7 @@ export const getAvailableAggregationsFromObjectFields = ( totalCount: { type: GraphQLInt, description: `Total number of records in the connection`, - fromField: 'id', + fromField: FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION, fromFieldType: FieldMetadataType.UUID, aggregateOperation: AGGREGATE_OPERATIONS.count, }, diff --git a/packages/twenty-shared/src/constants/FieldForTotalCountAggregateOperation.ts b/packages/twenty-shared/src/constants/FieldForTotalCountAggregateOperation.ts new file mode 100644 index 000000000..3a10ea479 --- /dev/null +++ b/packages/twenty-shared/src/constants/FieldForTotalCountAggregateOperation.ts @@ -0,0 +1 @@ +export const FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION = 'id'; diff --git a/packages/twenty-shared/src/index.ts b/packages/twenty-shared/src/index.ts index a568cdebb..3d08c2280 100644 --- a/packages/twenty-shared/src/index.ts +++ b/packages/twenty-shared/src/index.ts @@ -1,3 +1,4 @@ +export * from './constants/FieldForTotalCountAggregateOperation'; export * from './constants/TwentyCompaniesBaseUrl'; export * from './constants/TwentyIconsBaseUrl'; export * from './types/FieldMetadataType'; @@ -5,3 +6,4 @@ export * from './utils/fieldMetadata/isFieldMetadataDateKind'; export * from './utils/image/getImageAbsoluteURI'; export * from './utils/strings'; export * from './workspace'; +