diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper.ts index 3ac1b554d0..6f229a0860 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper.ts @@ -1,16 +1,14 @@ import { SelectQueryBuilder } from 'typeorm'; -import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; - import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; +import { formatColumnNameFromCompositeFieldAndSubfield } from 'src/engine/twenty-orm/utils/format-column-name-from-composite-field-and-subfield.util'; +import { isDefined } from 'src/utils/is-defined'; export class ProcessAggregateHelper { public addSelectedAggregatedFieldsQueriesToQueryBuilder = ({ - fieldMetadataMapByName, selectedAggregatedFields, queryBuilder, }: { - fieldMetadataMapByName: Record; selectedAggregatedFields: Record; queryBuilder: SelectQueryBuilder; }) => { @@ -19,17 +17,21 @@ export class ProcessAggregateHelper { for (const [aggregatedFieldName, aggregatedField] of Object.entries( selectedAggregatedFields, )) { - const fieldMetadata = fieldMetadataMapByName[aggregatedField.fromField]; - - if (!fieldMetadata) { + if ( + !isDefined(aggregatedField?.fromField) || + !isDefined(aggregatedField?.aggregationOperation) + ) { continue; } - const fieldName = fieldMetadata.name; + const columnName = formatColumnNameFromCompositeFieldAndSubfield( + aggregatedField.fromField, + aggregatedField.fromSubField, + ); const operation = aggregatedField.aggregationOperation; queryBuilder.addSelect( - `${operation}("${fieldName}")`, + `${operation}("${columnName}")`, `${aggregatedFieldName}`, ); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts index 1a4e7896d1..63fa227279 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts @@ -344,7 +344,6 @@ export class ProcessNestedRelationsHelper { this.processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder( { - fieldMetadataMapByName: referenceObjectMetadata.fieldsByName, selectedAggregatedFields: aggregateForRelation, queryBuilder: aggregateQueryBuilder, }, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts index 9b4c850de0..6557b2052b 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts @@ -159,7 +159,6 @@ export class GraphqlQueryFindManyResolverService const processAggregateHelper = new ProcessAggregateHelper(); processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({ - fieldMetadataMapByName: objectMetadataItemWithFieldMaps.fieldsByName, selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, queryBuilder: withDeletedAggregateQueryBuilder, }); @@ -214,7 +213,7 @@ export class GraphqlQueryFindManyResolverService selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, objectName: objectMetadataItemWithFieldMaps.nameSingular, take: limit, - totalCount: parentObjectRecordsAggregatedValues.totalCount, + totalCount: parentObjectRecordsAggregatedValues?.totalCount, order: orderByWithIdCondition, hasNextPage, hasPreviousPage, 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 20ea946445..0a0e475357 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 @@ -19,6 +19,7 @@ export type AggregationField = { type: GraphQLScalarType; description: string; fromField: string; + fromSubField?: string; aggregationOperation: AGGREGATION_OPERATIONS; }; @@ -79,6 +80,16 @@ export const getAvailableAggregationsFromObjectFields = ( }; } + if (field.type === FieldMetadataType.CURRENCY) { + acc[`avg${capitalize(field.name)}AmountMicros`] = { + type: GraphQLFloat, + description: `Average amount contained in the field ${field.name}`, + fromField: field.name, + fromSubField: 'amountMicros', + aggregationOperation: AGGREGATION_OPERATIONS.avg, + }; + } + return acc; }, {}); }; diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/format-column-name-from-composite-field-and-subfield.util.spec.ts b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/format-column-name-from-composite-field-and-subfield.util.spec.ts new file mode 100644 index 0000000000..c1f015e27d --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/utils/__tests__/format-column-name-from-composite-field-and-subfield.util.spec.ts @@ -0,0 +1,18 @@ +import { formatColumnNameFromCompositeFieldAndSubfield } from 'src/engine/twenty-orm/utils/format-column-name-from-composite-field-and-subfield.util'; + +describe('formatColumnNameFromCompositeFieldAndSubfield', () => { + it('should return fieldName when subFieldName is not defined', () => { + const result = formatColumnNameFromCompositeFieldAndSubfield('firstName'); + + expect(result).toBe('firstName'); + }); + + it('should return concatenated fieldName and capitalized subFieldName when subFieldName is defined', () => { + const result = formatColumnNameFromCompositeFieldAndSubfield( + 'user', + 'firstName', + ); + + expect(result).toBe('userFirstName'); + }); +}); diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-column-name-from-composite-field-and-subfield.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-column-name-from-composite-field-and-subfield.util.ts new file mode 100644 index 0000000000..a84299c64b --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-column-name-from-composite-field-and-subfield.util.ts @@ -0,0 +1,13 @@ +import { capitalize } from 'src/utils/capitalize'; +import { isDefined } from 'src/utils/is-defined'; + +export const formatColumnNameFromCompositeFieldAndSubfield = ( + fieldName: string, + subFieldName?: string, +): string => { + if (isDefined(subFieldName)) { + return `${fieldName}${capitalize(subFieldName)}`; + } + + return fieldName; +};