From 4b51b589094a2105644136863069d94a92d274a4 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Tue, 5 Nov 2024 19:48:48 +0400 Subject: [PATCH 01/30] wip --- .../graphql-selected-fields.parser.ts | 10 +++- .../graphql-query.parser.ts | 6 ++ ...raphql-query-find-many-resolver.service.ts | 58 ++++++++++++++++--- .../query-runner-option.interface.ts | 4 +- .../connection-type-definition.factory.ts | 23 +++++++- ...le-aggregations-from-object-fields.util.ts | 55 ++++++++++++++++++ .../api/graphql/workspace-schema.factory.ts | 2 + 7 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts index d1b69345fee2..a23b7195dff3 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts @@ -33,12 +33,18 @@ export class GraphqlQuerySelectedFieldsParser { relations: {}, }; + const hasEdges = Object.keys(graphqlSelectedFields).includes('edges'); + for (const [fieldKey, fieldValue] of Object.entries( graphqlSelectedFields, )) { + if (hasEdges && fieldKey !== 'edges') { + continue; + } if (this.shouldNotParseField(fieldKey)) { continue; } + if (this.isConnectionField(fieldKey, fieldValue)) { const subResult = this.parse(fieldValue, fieldMetadataMap); @@ -83,9 +89,7 @@ export class GraphqlQuerySelectedFieldsParser { } private shouldNotParseField(fieldKey: string): boolean { - return ['__typename', 'totalCount', 'pageInfo', 'cursor'].includes( - fieldKey, - ); + return ['__typename', 'cursor'].includes(fieldKey); } private parseCompositeField( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts index 0aa047fc31f7..39dc7b9558cf 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts @@ -62,6 +62,12 @@ export class GraphqlQueryParser { return queryBuilder; } + public addGetMinCreatedAtToBuilder( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + return queryBuilder.addSelect('MIN("createdAt") OVER()', 'minCreatedAt'); + } + private checkForDeletedAtFilter = ( filter: FindOptionsWhere | FindOptionsWhere[], ): boolean => { 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 9411c5502103..46557a788d63 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 @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { isDefined } from 'class-validator'; import graphqlFields from 'graphql-fields'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; @@ -27,8 +26,10 @@ import { getCursor, getPaginationInfo, } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; +import { getAvailableAggregationsFromObjectFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { isDefined } from 'src/utils/is-defined'; @Injectable() export class GraphqlQueryFindManyResolverService @@ -85,8 +86,6 @@ export class GraphqlQueryFindManyResolverService ); const isForwardPagination = !isDefined(args.before); - const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS; - const withDeletedCountQueryBuilder = graphqlQueryParser.applyDeletedAtToBuilder( withFilterCountQueryBuilder, @@ -95,7 +94,7 @@ export class GraphqlQueryFindManyResolverService const totalCount = isDefined(selectedFields.totalCount) ? await withDeletedCountQueryBuilder.getCount() - : 0; + : 0; // Blocked at 60 it seems const cursor = getCursor(args); @@ -139,12 +138,57 @@ export class GraphqlQueryFindManyResolverService args.filter ?? ({} as Filter), ); + const allAggregatedFields = getAvailableAggregationsFromObjectFields( + Object.values(objectMetadataMapItem.fields), + ); + + const selectedAggregatedFields = allAggregatedFields.reduce( + (acc, aggregatedField) => { + const key = Object.keys(aggregatedField)[0]; + + if (!Object.keys(selectedFields).includes(key)) return acc; + if (acc.some((field) => Object.keys(field)[0] === key)) return acc; + + return [...acc, aggregatedField]; + }, + [] as typeof allAggregatedFields, + ); + + if (selectedAggregatedFields.length > 0) { + selectedAggregatedFields.forEach((aggregatedField) => { + const [[aggregatedFieldName, aggregatedFieldDetails]] = + Object.entries(aggregatedField); + const operation = aggregatedFieldDetails.aggregationType; + const fieldName = aggregatedFieldDetails.fromField; + + withDeletedQueryBuilder.addSelect( + `${operation}("${fieldName}") OVER()`, + `${aggregatedFieldName}`, + ); + }); + } + + const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS; + const nonFormattedObjectRecords = await withDeletedQueryBuilder .take(limit + 1) - .getMany(); + .getRawAndEntities(); + + const aggregatedFieldsResults = selectedAggregatedFields.reduce( + (acc, aggregatedField) => { + const aggregatedFieldName = Object.keys(aggregatedField)[0]; + + return { + ...acc, + [aggregatedFieldName]: + nonFormattedObjectRecords.raw[0][aggregatedFieldName], + }; + }, + {}, + ); const objectRecords = formatResult( - nonFormattedObjectRecords, + nonFormattedObjectRecords.entities, objectMetadataMapItem, objectMetadataMap, ); @@ -186,7 +230,7 @@ export class GraphqlQueryFindManyResolverService hasPreviousPage, }); - return result; + return { ...result, ...aggregatedFieldsResults }; } async validate( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts index d960c3d45a7f..2d53d8f3c95c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts @@ -13,8 +13,8 @@ export interface WorkspaceQueryRunnerOptions { authContext: AuthContext; info: GraphQLResolveInfo; objectMetadataItem: ObjectMetadataInterface; - fieldMetadataCollection: FieldMetadataInterface[]; - objectMetadataCollection: ObjectMetadataInterface[]; + fieldMetadataCollection: FieldMetadataInterface[]; // Legacy + objectMetadataCollection: ObjectMetadataInterface[]; // Legacy objectMetadataMap: ObjectMetadataMap; objectMetadataMapItem: ObjectMetadataMapItem; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts index dfaf2f37e99e..77bb5c1c09de 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts @@ -5,13 +5,14 @@ import { GraphQLFieldConfigMap, GraphQLInt, GraphQLObjectType } from 'graphql'; import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; +import { getAvailableAggregationsFromObjectFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { pascalCase } from 'src/utils/pascal-case'; +import { ConnectionTypeFactory } from './connection-type.factory'; import { ObjectTypeDefinition, ObjectTypeDefinitionKind, } from './object-type-definition.factory'; -import { ConnectionTypeFactory } from './connection-type.factory'; export enum ConnectionTypeDefinitionKind { Edge = 'Edge', @@ -43,7 +44,25 @@ export class ConnectionTypeDefinitionFactory { objectMetadata: ObjectMetadataInterface, options: WorkspaceBuildSchemaOptions, ): GraphQLFieldConfigMap { - const fields: GraphQLFieldConfigMap = {}; + const fields: GraphQLFieldConfigMap = Object.assign( + {}, + ...getAvailableAggregationsFromObjectFields(objectMetadata.fields).map( + (agg) => { + const [ + [ + key, + { + aggregationType: _aggregationType, + fromField: _fromField, + ...rest + }, + ], + ] = Object.entries(agg); + + return { [key]: rest }; + }, + ), + ); fields.edges = { type: this.connectionTypeFactory.create( 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 new file mode 100644 index 000000000000..563eef2310d6 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts @@ -0,0 +1,55 @@ +import { GraphQLISODateTime } from '@nestjs/graphql'; + +import { GraphQLString } from 'graphql'; + +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { capitalize } from 'src/utils/capitalize'; + +enum AGGREGATIONS_TYPES { + min = 'MIN', + max = 'MAX', + avg = 'Avg', +} + +type AggregationValue = { + type: typeof GraphQLString; + description: string; + fromField: string; + aggregationType: AGGREGATIONS_TYPES; +}; + +type AggregationField = { + [key: string]: AggregationValue; +}; + +export const getAvailableAggregationsFromObjectFields = ( + fields: FieldMetadataInterface[], +): AggregationField[] => { + return fields.reduce>>((acc, field) => { + if (field.type === FieldMetadataType.DATE_TIME) { + return [ + ...acc, + { + [`min${capitalize(field.name)}`]: { + type: GraphQLISODateTime, + description: `Oldest date contained in the field ${field.name}`, + fromField: field.name, + aggregationType: AGGREGATIONS_TYPES.min, + }, + }, + { + [`max${capitalize(field.name)}`]: { + type: GraphQLISODateTime, + description: `Most recent date contained in the field ${field.name}`, + fromField: field.name, + aggregationType: AGGREGATIONS_TYPES.max, + }, + }, + ]; + } + + return acc; + }, []); +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index 558ece2b4768..ad7732bb42f7 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -85,6 +85,8 @@ export class WorkspaceSchemaFactory { authContext.workspace.id, currentCacheVersion, ); + + typeDefs = undefined; let usedScalarNames = await this.workspaceCacheStorageService.getGraphQLUsedScalarNames( authContext.workspace.id, From 3f929c9d584e3f4c9c6a0ce16b1e0ebed52bc1d3 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 6 Nov 2024 15:19:23 +0400 Subject: [PATCH 02/30] Refactor code --- .../graphql-query.parser.ts | 6 - ...raphql-query-find-many-resolver.service.ts | 127 ++++++++++++------ .../connection-type-definition.factory.ts | 2 +- ...le-aggregations-from-object-fields.util.ts | 10 +- packages/twenty-server/src/utils/is-uuid.ts | 14 ++ 5 files changed, 104 insertions(+), 55 deletions(-) create mode 100644 packages/twenty-server/src/utils/is-uuid.ts diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts index 39dc7b9558cf..0aa047fc31f7 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts @@ -62,12 +62,6 @@ export class GraphqlQueryParser { return queryBuilder; } - public addGetMinCreatedAtToBuilder( - queryBuilder: SelectQueryBuilder, - ): SelectQueryBuilder { - return queryBuilder.addSelect('MIN("createdAt") OVER()', 'minCreatedAt'); - } - private checkForDeletedAtFilter = ( filter: FindOptionsWhere | FindOptionsWhere[], ): boolean => { 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 46557a788d63..0f16ece5f9c3 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 @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import graphqlFields from 'graphql-fields'; +import { SelectQueryBuilder } from 'typeorm'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { @@ -12,6 +13,7 @@ import { import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; import { @@ -26,10 +28,14 @@ import { getCursor, getPaginationInfo, } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; -import { getAvailableAggregationsFromObjectFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; +import { + AggregationField, + getAvailableAggregationsFromObjectFields, +} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { isDefined } from 'src/utils/is-defined'; +import { isUuid } from 'src/utils/is-uuid'; @Injectable() export class GraphqlQueryFindManyResolverService @@ -94,7 +100,7 @@ export class GraphqlQueryFindManyResolverService const totalCount = isDefined(selectedFields.totalCount) ? await withDeletedCountQueryBuilder.getCount() - : 0; // Blocked at 60 it seems + : 0; const cursor = getCursor(args); @@ -138,35 +144,20 @@ export class GraphqlQueryFindManyResolverService args.filter ?? ({} as Filter), ); - const allAggregatedFields = getAvailableAggregationsFromObjectFields( - Object.values(objectMetadataMapItem.fields), - ); - - const selectedAggregatedFields = allAggregatedFields.reduce( - (acc, aggregatedField) => { - const key = Object.keys(aggregatedField)[0]; - - if (!Object.keys(selectedFields).includes(key)) return acc; - if (acc.some((field) => Object.keys(field)[0] === key)) return acc; - - return [...acc, aggregatedField]; - }, - [] as typeof allAggregatedFields, - ); + const selectedAggregatedFields = this.getSelectedAggregatedFields({ + objectFields: Object.values( + Object.fromEntries( + Object.entries(objectMetadataMapItem.fields).filter( + ([key, _value]) => !isUuid(key), // remove objectMetadataMapItem fields duplicates + ), + ), + ), + }); - if (selectedAggregatedFields.length > 0) { - selectedAggregatedFields.forEach((aggregatedField) => { - const [[aggregatedFieldName, aggregatedFieldDetails]] = - Object.entries(aggregatedField); - const operation = aggregatedFieldDetails.aggregationType; - const fieldName = aggregatedFieldDetails.fromField; - - withDeletedQueryBuilder.addSelect( - `${operation}("${fieldName}") OVER()`, - `${aggregatedFieldName}`, - ); - }); - } + this.addSelectedAggregatedFieldsQueriesToQueryBuilder({ + selectedAggregatedFields, + queryBuilder: withDeletedQueryBuilder, + }); const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS; @@ -174,19 +165,6 @@ export class GraphqlQueryFindManyResolverService .take(limit + 1) .getRawAndEntities(); - const aggregatedFieldsResults = selectedAggregatedFields.reduce( - (acc, aggregatedField) => { - const aggregatedFieldName = Object.keys(aggregatedField)[0]; - - return { - ...acc, - [aggregatedFieldName]: - nonFormattedObjectRecords.raw[0][aggregatedFieldName], - }; - }, - {}, - ); - const objectRecords = formatResult( nonFormattedObjectRecords.entities, objectMetadataMapItem, @@ -230,6 +208,11 @@ export class GraphqlQueryFindManyResolverService hasPreviousPage, }); + const aggregatedFieldsResults = this.extractAggregatedFieldsResults({ + selectedAggregatedFields, + rawObjectRecords: nonFormattedObjectRecords.raw, + }); + return { ...result, ...aggregatedFieldsResults }; } @@ -274,4 +257,62 @@ export class GraphqlQueryFindManyResolverService ); } } + + private addSelectedAggregatedFieldsQueriesToQueryBuilder = ({ + selectedAggregatedFields, + queryBuilder, + }: { + selectedAggregatedFields: AggregationField[]; + queryBuilder: SelectQueryBuilder; + }) => { + selectedAggregatedFields.forEach((aggregatedField) => { + const [[aggregatedFieldName, aggregatedFieldDetails]] = + Object.entries(aggregatedField); + const operation = aggregatedFieldDetails.aggregationOperation; + const fieldName = aggregatedFieldDetails.fromField; + + queryBuilder.addSelect( + `${operation}("${fieldName}") OVER()`, + `${aggregatedFieldName}`, + ); + }); + }; + + private getSelectedAggregatedFields = ({ + objectFields, + }: { + objectFields: FieldMetadataInterface[]; + }) => { + const allAggregatedFields = + getAvailableAggregationsFromObjectFields(objectFields); + + return allAggregatedFields.reduce( + (acc, aggregatedField) => { + const aggregatedFieldName = Object.keys(aggregatedField)[0]; + + if (acc.some((field) => Object.keys(field)[0] === aggregatedFieldName)) + return acc; + + return [...acc, aggregatedField]; + }, + [] as typeof allAggregatedFields, + ); + }; + + private extractAggregatedFieldsResults = ({ + selectedAggregatedFields, + rawObjectRecords, + }: { + selectedAggregatedFields: AggregationField[]; + rawObjectRecords: any[]; + }) => { + return selectedAggregatedFields.reduce((acc, aggregatedField) => { + const aggregatedFieldName = Object.keys(aggregatedField)[0]; + + return { + ...acc, + [aggregatedFieldName]: rawObjectRecords[0][aggregatedFieldName], + }; + }, {}); + }; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts index 77bb5c1c09de..71afe756bf33 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts @@ -52,7 +52,7 @@ export class ConnectionTypeDefinitionFactory { [ key, { - aggregationType: _aggregationType, + aggregationOperation: _aggregationOperation, fromField: _fromField, ...rest }, 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 563eef2310d6..3f9f9b1c736d 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 @@ -7,7 +7,7 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { capitalize } from 'src/utils/capitalize'; -enum AGGREGATIONS_TYPES { +enum AGGREGATION_OPERATIONS { min = 'MIN', max = 'MAX', avg = 'Avg', @@ -17,10 +17,10 @@ type AggregationValue = { type: typeof GraphQLString; description: string; fromField: string; - aggregationType: AGGREGATIONS_TYPES; + aggregationOperation: AGGREGATION_OPERATIONS; }; -type AggregationField = { +export type AggregationField = { [key: string]: AggregationValue; }; @@ -36,7 +36,7 @@ export const getAvailableAggregationsFromObjectFields = ( type: GraphQLISODateTime, description: `Oldest date contained in the field ${field.name}`, fromField: field.name, - aggregationType: AGGREGATIONS_TYPES.min, + aggregationOperation: AGGREGATION_OPERATIONS.min, }, }, { @@ -44,7 +44,7 @@ export const getAvailableAggregationsFromObjectFields = ( type: GraphQLISODateTime, description: `Most recent date contained in the field ${field.name}`, fromField: field.name, - aggregationType: AGGREGATIONS_TYPES.max, + aggregationOperation: AGGREGATION_OPERATIONS.max, }, }, ]; diff --git a/packages/twenty-server/src/utils/is-uuid.ts b/packages/twenty-server/src/utils/is-uuid.ts new file mode 100644 index 000000000000..9b17499a6b8c --- /dev/null +++ b/packages/twenty-server/src/utils/is-uuid.ts @@ -0,0 +1,14 @@ +export const isUuid = (value: unknown): boolean => { + if (typeof value !== 'string') { + return false; + } + + if (value.length !== 36) { + return false; + } + + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + + return uuidRegex.test(value); +}; From 49bc25aa0897acbb0b08cc6bee58b5dd5319544e Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 6 Nov 2024 15:19:35 +0400 Subject: [PATCH 03/30] Add test for isUuid util --- packages/twenty-front/tsconfig.build.json | 2 +- packages/twenty-front/tsconfig.dev.json | 2 +- packages/twenty-front/tsconfig.spec.json | 2 +- .../src/utils/is-uuid.utils.spec.ts | 65 +++++++++++++++++++ 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 packages/twenty-server/src/utils/is-uuid.utils.spec.ts diff --git a/packages/twenty-front/tsconfig.build.json b/packages/twenty-front/tsconfig.build.json index 7a17e21fdea8..255cb4285350 100644 --- a/packages/twenty-front/tsconfig.build.json +++ b/packages/twenty-front/tsconfig.build.json @@ -18,5 +18,5 @@ "src/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx" - ] +, "../twenty-server/src/utils/is-uuid.ts", "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] } diff --git a/packages/twenty-front/tsconfig.dev.json b/packages/twenty-front/tsconfig.dev.json index f90dcae862bb..a248a5fb741a 100644 --- a/packages/twenty-front/tsconfig.dev.json +++ b/packages/twenty-front/tsconfig.dev.json @@ -9,5 +9,5 @@ "src/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx" - ] +, "../twenty-server/src/utils/is-uuid.ts", "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] } diff --git a/packages/twenty-front/tsconfig.spec.json b/packages/twenty-front/tsconfig.spec.json index 7b115f8bbc82..9d18a60ac508 100644 --- a/packages/twenty-front/tsconfig.spec.json +++ b/packages/twenty-front/tsconfig.spec.json @@ -15,5 +15,5 @@ "tsup.config.ts", "tsup.ui.index.tsx", "vite.config.ts" - ] +, "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] } diff --git a/packages/twenty-server/src/utils/is-uuid.utils.spec.ts b/packages/twenty-server/src/utils/is-uuid.utils.spec.ts new file mode 100644 index 000000000000..8319849f3247 --- /dev/null +++ b/packages/twenty-server/src/utils/is-uuid.utils.spec.ts @@ -0,0 +1,65 @@ +import { isUuid } from 'src/utils/is-uuid'; + +describe('isUuid', () => { + describe('when input is a valid UUID', () => { + it('should return true for a valid UUID v4', () => { + expect(isUuid('123e4567-e89b-12d3-a456-426614174000')).toBe(true); + }); + + it('should return true for a UUID with uppercase letters', () => { + expect(isUuid('123E4567-E89B-12D3-A456-426614174000')).toBe(true); + }); + }); + + describe('when input is not a valid UUID', () => { + it('should return false for non-string values', () => { + expect(isUuid(undefined)).toBe(false); + expect(isUuid(null)).toBe(false); + expect(isUuid(123)).toBe(false); + expect(isUuid({})).toBe(false); + expect(isUuid([])).toBe(false); + }); + + it('should return false for strings with incorrect length', () => { + expect(isUuid('123')).toBe(false); + expect(isUuid('123e4567-e89b-12d3-a456-4266141740000')).toBe(false); // too long + expect(isUuid('123e4567-e89b-12d3-a456-42661417400')).toBe(false); // too short + }); + + it('should return false for strings with incorrect format', () => { + expect(isUuid('123e4567e89b12d3a456426614174000')).toBe(false); // missing hyphens + expect(isUuid('123e4567-e89b-12d3-a456_426614174000')).toBe(false); // wrong separator + expect(isUuid('123e4567-e89b-12d3-a456-42661417400g')).toBe(false); // invalid character + }); + + it('should return false for similar looking strings', () => { + expect(isUuid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')).toBe(false); + expect(isUuid('00000000-0000-0000-0000-000000000000')).toBe(true); // valid but all zeros + expect(isUuid('not-a-uuid-at-all')).toBe(false); + }); + + it('should return false for malformed UUIDs', () => { + expect(isUuid('-123e4567-e89b-12d3-a456-426614174000')).toBe(false); // extra hyphen + expect(isUuid('123e4567-e89b--12d3-a456-426614174000')).toBe(false); // double hyphen + expect(isUuid(' 123e4567-e89b-12d3-a456-426614174000')).toBe(false); // leading space + expect(isUuid('123e4567-e89b-12d3-a456-426614174000 ')).toBe(false); // trailing space + }); + }); + + describe('edge cases', () => { + it('should handle empty string', () => { + expect(isUuid('')).toBe(false); + }); + + it('should handle whitespace strings', () => { + expect(isUuid(' ')).toBe(false); + }); + + it('should return true for valid UUIDs with different versions', () => { + // Different valid UUID versions should all return true + expect(isUuid('550e8400-e29b-41d4-a716-446655440000')).toBe(true); + expect(isUuid('6ba7b810-9dad-11d1-80b4-00c04fd430c8')).toBe(true); + expect(isUuid('6ba7b810-9dad-31d1-80b4-00c04fd430c8')).toBe(true); + }); + }); +}); From 10fdfd507aab37efcb12b726c4c9b774d5e7c29f Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 6 Nov 2024 15:42:13 +0400 Subject: [PATCH 04/30] fix selectedFields and add number fields aggregations --- ...raphql-query-find-many-resolver.service.ts | 5 ++- ...le-aggregations-from-object-fields.util.ts | 43 ++++++++++++++++++- .../api/graphql/workspace-schema.factory.ts | 1 - 3 files changed, 45 insertions(+), 4 deletions(-) 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 0f16ece5f9c3..6533f0bfebad 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 @@ -152,6 +152,7 @@ export class GraphqlQueryFindManyResolverService ), ), ), + selectedFields, }); this.addSelectedAggregatedFieldsQueriesToQueryBuilder({ @@ -280,8 +281,10 @@ export class GraphqlQueryFindManyResolverService private getSelectedAggregatedFields = ({ objectFields, + selectedFields, }: { objectFields: FieldMetadataInterface[]; + selectedFields: any[]; }) => { const allAggregatedFields = getAvailableAggregationsFromObjectFields(objectFields); @@ -290,7 +293,7 @@ export class GraphqlQueryFindManyResolverService (acc, aggregatedField) => { const aggregatedFieldName = Object.keys(aggregatedField)[0]; - if (acc.some((field) => Object.keys(field)[0] === aggregatedFieldName)) + if (!Object.keys(selectedFields).includes(aggregatedFieldName)) return acc; return [...acc, aggregatedField]; 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 3f9f9b1c736d..c53ebc49eafb 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 @@ -1,6 +1,6 @@ import { GraphQLISODateTime } from '@nestjs/graphql'; -import { GraphQLString } from 'graphql'; +import { GraphQLFloat, GraphQLString } from 'graphql'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; @@ -10,7 +10,8 @@ import { capitalize } from 'src/utils/capitalize'; enum AGGREGATION_OPERATIONS { min = 'MIN', max = 'MAX', - avg = 'Avg', + avg = 'AVG', + sum = 'SUM', } type AggregationValue = { @@ -50,6 +51,44 @@ export const getAvailableAggregationsFromObjectFields = ( ]; } + if (field.type === FieldMetadataType.NUMBER) { + return [ + ...acc, + { + [`min${capitalize(field.name)}`]: { + type: GraphQLFloat, + description: `Minimum value contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.min, + }, + }, + { + [`max${capitalize(field.name)}`]: { + type: GraphQLFloat, + description: `Maximum value contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.max, + }, + }, + { + [`avg${capitalize(field.name)}`]: { + type: GraphQLFloat, + description: `Average value contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.avg, + }, + }, + { + [`sum${capitalize(field.name)}`]: { + type: GraphQLFloat, + description: `Sum of amounts contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.sum, + }, + }, + ]; + } + return acc; }, []); }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index ad7732bb42f7..ea9b9ff6b87d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -86,7 +86,6 @@ export class WorkspaceSchemaFactory { currentCacheVersion, ); - typeDefs = undefined; let usedScalarNames = await this.workspaceCacheStorageService.getGraphQLUsedScalarNames( authContext.workspace.id, From 6b3e533ba82ec391d83fef3c97770dc1fc989cf8 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 6 Nov 2024 15:47:31 +0400 Subject: [PATCH 05/30] minor fixes --- .../get-available-aggregations-from-object-fields.util.ts | 6 +++--- .../src/engine/api/graphql/workspace-schema.factory.ts | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) 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 c53ebc49eafb..758d8a754661 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 @@ -57,7 +57,7 @@ export const getAvailableAggregationsFromObjectFields = ( { [`min${capitalize(field.name)}`]: { type: GraphQLFloat, - description: `Minimum value contained in the field ${field.name}`, + description: `Minimum amount contained in the field ${field.name}`, fromField: field.name, aggregationOperation: AGGREGATION_OPERATIONS.min, }, @@ -65,7 +65,7 @@ export const getAvailableAggregationsFromObjectFields = ( { [`max${capitalize(field.name)}`]: { type: GraphQLFloat, - description: `Maximum value contained in the field ${field.name}`, + description: `Maximum amount contained in the field ${field.name}`, fromField: field.name, aggregationOperation: AGGREGATION_OPERATIONS.max, }, @@ -73,7 +73,7 @@ export const getAvailableAggregationsFromObjectFields = ( { [`avg${capitalize(field.name)}`]: { type: GraphQLFloat, - description: `Average value contained in the field ${field.name}`, + description: `Average amount contained in the field ${field.name}`, fromField: field.name, aggregationOperation: AGGREGATION_OPERATIONS.avg, }, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index ea9b9ff6b87d..558ece2b4768 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -85,7 +85,6 @@ export class WorkspaceSchemaFactory { authContext.workspace.id, currentCacheVersion, ); - let usedScalarNames = await this.workspaceCacheStorageService.getGraphQLUsedScalarNames( authContext.workspace.id, From 3fb0fe02b0eca43cf09f4e719da34a60b78c7d29 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 8 Nov 2024 15:50:20 +0400 Subject: [PATCH 06/30] Change way to filter out duplicates of fieldMetadataMap --- ...raphql-query-find-many-resolver.service.ts | 18 ++--- packages/twenty-server/src/utils/is-uuid.ts | 14 ---- .../src/utils/is-uuid.utils.spec.ts | 65 ------------------- 3 files changed, 10 insertions(+), 87 deletions(-) delete mode 100644 packages/twenty-server/src/utils/is-uuid.ts delete mode 100644 packages/twenty-server/src/utils/is-uuid.utils.spec.ts 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 6533f0bfebad..b9ffb043d9e6 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 @@ -35,7 +35,6 @@ import { import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { isDefined } from 'src/utils/is-defined'; -import { isUuid } from 'src/utils/is-uuid'; @Injectable() export class GraphqlQueryFindManyResolverService @@ -145,13 +144,7 @@ export class GraphqlQueryFindManyResolverService ); const selectedAggregatedFields = this.getSelectedAggregatedFields({ - objectFields: Object.values( - Object.fromEntries( - Object.entries(objectMetadataMapItem.fields).filter( - ([key, _value]) => !isUuid(key), // remove objectMetadataMapItem fields duplicates - ), - ), - ), + objectFields: Object.values(objectMetadataMapItem.fields), selectedFields, }); @@ -296,6 +289,15 @@ export class GraphqlQueryFindManyResolverService if (!Object.keys(selectedFields).includes(aggregatedFieldName)) return acc; + const isDuplicate = acc.some( + (existingField) => + Object.keys(existingField)[0] === aggregatedFieldName, + ); + + if (isDuplicate) { + return acc; + } + return [...acc, aggregatedField]; }, [] as typeof allAggregatedFields, diff --git a/packages/twenty-server/src/utils/is-uuid.ts b/packages/twenty-server/src/utils/is-uuid.ts deleted file mode 100644 index 9b17499a6b8c..000000000000 --- a/packages/twenty-server/src/utils/is-uuid.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const isUuid = (value: unknown): boolean => { - if (typeof value !== 'string') { - return false; - } - - if (value.length !== 36) { - return false; - } - - const uuidRegex = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - - return uuidRegex.test(value); -}; diff --git a/packages/twenty-server/src/utils/is-uuid.utils.spec.ts b/packages/twenty-server/src/utils/is-uuid.utils.spec.ts deleted file mode 100644 index 8319849f3247..000000000000 --- a/packages/twenty-server/src/utils/is-uuid.utils.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { isUuid } from 'src/utils/is-uuid'; - -describe('isUuid', () => { - describe('when input is a valid UUID', () => { - it('should return true for a valid UUID v4', () => { - expect(isUuid('123e4567-e89b-12d3-a456-426614174000')).toBe(true); - }); - - it('should return true for a UUID with uppercase letters', () => { - expect(isUuid('123E4567-E89B-12D3-A456-426614174000')).toBe(true); - }); - }); - - describe('when input is not a valid UUID', () => { - it('should return false for non-string values', () => { - expect(isUuid(undefined)).toBe(false); - expect(isUuid(null)).toBe(false); - expect(isUuid(123)).toBe(false); - expect(isUuid({})).toBe(false); - expect(isUuid([])).toBe(false); - }); - - it('should return false for strings with incorrect length', () => { - expect(isUuid('123')).toBe(false); - expect(isUuid('123e4567-e89b-12d3-a456-4266141740000')).toBe(false); // too long - expect(isUuid('123e4567-e89b-12d3-a456-42661417400')).toBe(false); // too short - }); - - it('should return false for strings with incorrect format', () => { - expect(isUuid('123e4567e89b12d3a456426614174000')).toBe(false); // missing hyphens - expect(isUuid('123e4567-e89b-12d3-a456_426614174000')).toBe(false); // wrong separator - expect(isUuid('123e4567-e89b-12d3-a456-42661417400g')).toBe(false); // invalid character - }); - - it('should return false for similar looking strings', () => { - expect(isUuid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')).toBe(false); - expect(isUuid('00000000-0000-0000-0000-000000000000')).toBe(true); // valid but all zeros - expect(isUuid('not-a-uuid-at-all')).toBe(false); - }); - - it('should return false for malformed UUIDs', () => { - expect(isUuid('-123e4567-e89b-12d3-a456-426614174000')).toBe(false); // extra hyphen - expect(isUuid('123e4567-e89b--12d3-a456-426614174000')).toBe(false); // double hyphen - expect(isUuid(' 123e4567-e89b-12d3-a456-426614174000')).toBe(false); // leading space - expect(isUuid('123e4567-e89b-12d3-a456-426614174000 ')).toBe(false); // trailing space - }); - }); - - describe('edge cases', () => { - it('should handle empty string', () => { - expect(isUuid('')).toBe(false); - }); - - it('should handle whitespace strings', () => { - expect(isUuid(' ')).toBe(false); - }); - - it('should return true for valid UUIDs with different versions', () => { - // Different valid UUID versions should all return true - expect(isUuid('550e8400-e29b-41d4-a716-446655440000')).toBe(true); - expect(isUuid('6ba7b810-9dad-11d1-80b4-00c04fd430c8')).toBe(true); - expect(isUuid('6ba7b810-9dad-31d1-80b4-00c04fd430c8')).toBe(true); - }); - }); -}); From 7adb8ce7f8ed407f54e56bbd83aed7ea546703dc Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 8 Nov 2024 15:52:14 +0400 Subject: [PATCH 07/30] Revert changes to tsconfig files --- packages/twenty-front/tsconfig.build.json | 2 +- packages/twenty-front/tsconfig.dev.json | 2 +- packages/twenty-front/tsconfig.spec.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/tsconfig.build.json b/packages/twenty-front/tsconfig.build.json index 255cb4285350..7a17e21fdea8 100644 --- a/packages/twenty-front/tsconfig.build.json +++ b/packages/twenty-front/tsconfig.build.json @@ -18,5 +18,5 @@ "src/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx" -, "../twenty-server/src/utils/is-uuid.ts", "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] + ] } diff --git a/packages/twenty-front/tsconfig.dev.json b/packages/twenty-front/tsconfig.dev.json index a248a5fb741a..f90dcae862bb 100644 --- a/packages/twenty-front/tsconfig.dev.json +++ b/packages/twenty-front/tsconfig.dev.json @@ -9,5 +9,5 @@ "src/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx" -, "../twenty-server/src/utils/is-uuid.ts", "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] + ] } diff --git a/packages/twenty-front/tsconfig.spec.json b/packages/twenty-front/tsconfig.spec.json index 9d18a60ac508..7b115f8bbc82 100644 --- a/packages/twenty-front/tsconfig.spec.json +++ b/packages/twenty-front/tsconfig.spec.json @@ -15,5 +15,5 @@ "tsup.config.ts", "tsup.ui.index.tsx", "vite.config.ts" -, "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] + ] } From cd4465f417f5aee418ab0d6361dd7b9538d7e530 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:11:52 +0100 Subject: [PATCH 08/30] Fix --- .github/workflows/ci-tinybird.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index 44328556408c..cdeb97af15c7 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -14,7 +14,6 @@ jobs: ci: timeout-minutes: 10 runs-on: ubuntu-latest - uses: tinybirdco/ci/.github/workflows/ci.yml@main steps: - name: Check for changed files id: changed-files @@ -29,6 +28,7 @@ jobs: run: echo "No relevant changes. Skipping CI." - name: Check twenty-tinybird package + uses: tinybirdco/ci/.github/workflows/ci.yml@main with: data_project_dir: packages/twenty-tinybird tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }} From d26a1bd2201d4481a16f5f0e3bfd9e9a80dfc259 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:23:08 +0100 Subject: [PATCH 09/30] Fix --- .github/workflows/ci-tinybird.yaml | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index cdeb97af15c7..377b835e5e12 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -1,10 +1,14 @@ name: CI Tinybird on: push: + paths: + - packages/twenty-tinybird/** branches: - main pull_request: + paths: + - packages/twenty-tinybird/** concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -14,22 +18,8 @@ jobs: ci: timeout-minutes: 10 runs-on: ubuntu-latest - steps: - - name: Check for changed files - id: changed-files - uses: tj-actions/changed-files@v11 - with: - files: | - package.json - packages/twenty-tinybird/** - - - name: Skip if no relevant changes - if: steps.changed-files.outputs.any_changed == 'false' - run: echo "No relevant changes. Skipping CI." - - - name: Check twenty-tinybird package - uses: tinybirdco/ci/.github/workflows/ci.yml@main - with: - data_project_dir: packages/twenty-tinybird - tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }} - tb_host: https://api.eu-central-1.aws.tinybird.co + uses: tinybirdco/ci/.github/workflows/ci.yml@main + with: + data_project_dir: packages/twenty-tinybird + tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }} + tb_host: https://api.eu-central-1.aws.tinybird.co From 870fc618e3ac0a34bbf36e7969776f64f02a2b8f Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:38:06 +0100 Subject: [PATCH 10/30] Fix ci --- .github/workflows/ci-tinybird.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index 377b835e5e12..26ad852be48b 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -1,14 +1,16 @@ name: CI Tinybird on: push: - paths: - - packages/twenty-tinybird/** branches: - main + paths: + - 'package.json' + - 'packages/twenty-tinybird/**' pull_request: paths: - - packages/twenty-tinybird/** + - 'package.json' + - 'packages/twenty-tinybird/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} From dc72de9359bcc363024d53bd6227960e640a4cea Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:38:57 +0100 Subject: [PATCH 11/30] Try ci --- .github/workflows/ci-tinybird.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index 26ad852be48b..a5acbdd67db4 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -11,6 +11,7 @@ on: paths: - 'package.json' - 'packages/twenty-tinybird/**' + - 'packages/twenty-front/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} From d3dbf46fd0255ff4eb37d4867c6afae1b187e163 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:39:37 +0100 Subject: [PATCH 12/30] Fix tests --- .github/workflows/ci-tinybird.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index a5acbdd67db4..9ba1866a3339 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -11,7 +11,7 @@ on: paths: - 'package.json' - 'packages/twenty-tinybird/**' - - 'packages/twenty-front/**' + - 'packages/twenty-server/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 52e8a88cd6ed6743547fc06bed11ff2257d124b2 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:44:31 +0100 Subject: [PATCH 13/30] Fix --- .github/workflows/ci-tinybird.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index 9ba1866a3339..cf6ec0cb9c85 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -19,8 +19,6 @@ concurrency: jobs: ci: - timeout-minutes: 10 - runs-on: ubuntu-latest uses: tinybirdco/ci/.github/workflows/ci.yml@main with: data_project_dir: packages/twenty-tinybird From 55ce64d88b89f8c722b290404358a9506c30a423 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:45:10 +0100 Subject: [PATCH 14/30] Fix --- .github/workflows/ci-tinybird.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index cf6ec0cb9c85..e254a72e76d9 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -22,5 +22,6 @@ jobs: uses: tinybirdco/ci/.github/workflows/ci.yml@main with: data_project_dir: packages/twenty-tinybird + secrets: tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }} tb_host: https://api.eu-central-1.aws.tinybird.co From f8d4c2a27aa9ff0a4e7512aabcfa6fd8d1cad028 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:47:59 +0100 Subject: [PATCH 15/30] Fix --- .github/workflows/ci-tinybird.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index e254a72e76d9..d9926418c342 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -19,6 +19,8 @@ concurrency: jobs: ci: + runs-on: ubuntu-latest + timeout-minutes: 10 uses: tinybirdco/ci/.github/workflows/ci.yml@main with: data_project_dir: packages/twenty-tinybird From 5af68f8ef50ca13491fc63e723940a378641bb1c Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:49:20 +0100 Subject: [PATCH 16/30] Fix --- .github/workflows/ci-tinybird.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index d9926418c342..27faf6b935c9 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -19,11 +19,11 @@ concurrency: jobs: ci: - runs-on: ubuntu-latest - timeout-minutes: 10 uses: tinybirdco/ci/.github/workflows/ci.yml@main with: data_project_dir: packages/twenty-tinybird secrets: tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }} tb_host: https://api.eu-central-1.aws.tinybird.co + runs-on: ubuntu-latest + timeout-minutes: 10 From db028673b1fe12c57be81bcbbe555b0db531fba4 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 14:51:03 +0100 Subject: [PATCH 17/30] Fix --- .github/workflows/ci-tinybird.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci-tinybird.yaml b/.github/workflows/ci-tinybird.yaml index 27faf6b935c9..46849e3150e0 100644 --- a/.github/workflows/ci-tinybird.yaml +++ b/.github/workflows/ci-tinybird.yaml @@ -11,7 +11,6 @@ on: paths: - 'package.json' - 'packages/twenty-tinybird/**' - - 'packages/twenty-server/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -25,5 +24,3 @@ jobs: secrets: tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }} tb_host: https://api.eu-central-1.aws.tinybird.co - runs-on: ubuntu-latest - timeout-minutes: 10 From 2c6549a7d78ffe7f37bf1e7fdc221031d763bf69 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 15:01:21 +0100 Subject: [PATCH 18/30] Add feature flag --- .../components/MainNavigationDrawerItems.tsx | 18 +++--------------- .../modules/workspace/types/FeatureFlagKey.ts | 5 ++--- .../typeorm-seeds/core/feature-flags.ts | 10 +++++----- .../enums/feature-flag-key.enum.ts | 3 +-- 4 files changed, 11 insertions(+), 25 deletions(-) diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx index 0169ac150c4c..fa3c813981e0 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx @@ -13,7 +13,6 @@ import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNaviga import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; const StyledMainSection = styled(NavigationDrawerSection)` @@ -27,9 +26,7 @@ export const MainNavigationDrawerItems = () => { const setNavigationMemorizedUrl = useSetRecoilState( navigationMemorizedUrlState, ); - const isWorkspaceFavoriteEnabled = useIsFeatureEnabled( - 'IS_WORKSPACE_FAVORITE_ENABLED', - ); + const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = useRecoilState(isNavigationDrawerExpandedState); const setNavigationDrawerExpandedMemorized = useSetRecoilState( @@ -58,18 +55,9 @@ export const MainNavigationDrawerItems = () => { /> )} - - {isWorkspaceFavoriteEnabled && } - + - - {isWorkspaceFavoriteEnabled ? ( - - ) : ( - - )} + ); diff --git a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts index c8f427d8cff6..337f252be7de 100644 --- a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts +++ b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts @@ -9,12 +9,11 @@ export type FeatureFlagKey = | 'IS_FREE_ACCESS_ENABLED' | 'IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED' | 'IS_WORKFLOW_ENABLED' - | 'IS_WORKSPACE_FAVORITE_ENABLED' - | 'IS_QUERY_RUNNER_TWENTY_ORM_ENABLED' | 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED' | 'IS_ANALYTICS_V2_ENABLED' | 'IS_SSO_ENABLED' | 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_ARRAY_AND_JSON_FILTER_ENABLED' | 'IS_MICROSOFT_SYNC_ENABLED' - | 'IS_ADVANCED_FILTERS_ENABLED'; + | 'IS_ADVANCED_FILTERS_ENABLED' + | 'IS_AGGREGATE_QUERY_ENABLED'; diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts index 0c75053e1560..064d1794600a 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts @@ -50,11 +50,6 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: false, }, - { - key: FeatureFlagKey.IsWorkspaceFavoriteEnabled, - workspaceId: workspaceId, - value: true, - }, { key: FeatureFlagKey.IsAnalyticsV2Enabled, workspaceId: workspaceId, @@ -85,6 +80,11 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: false, }, + { + key: FeatureFlagKey.IsAggregateQueryEnabled, + workspaceId: workspaceId, + value: false, + }, ]) .execute(); }; diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index 9bf22bc2a18e..099ee18a2c54 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -8,12 +8,11 @@ export enum FeatureFlagKey { IsFunctionSettingsEnabled = 'IS_FUNCTION_SETTINGS_ENABLED', IsWorkflowEnabled = 'IS_WORKFLOW_ENABLED', IsMessageThreadSubscriberEnabled = 'IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED', - IsQueryRunnerTwentyORMEnabled = 'IS_QUERY_RUNNER_TWENTY_ORM_ENABLED', - IsWorkspaceFavoriteEnabled = 'IS_WORKSPACE_FAVORITE_ENABLED', IsSSOEnabled = 'IS_SSO_ENABLED', IsGmailSendEmailScopeEnabled = 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED', IsAnalyticsV2Enabled = 'IS_ANALYTICS_V2_ENABLED', IsUniqueIndexesEnabled = 'IS_UNIQUE_INDEXES_ENABLED', IsMicrosoftSyncEnabled = 'IS_MICROSOFT_SYNC_ENABLED', IsAdvancedFiltersEnabled = 'IS_ADVANCED_FILTERS_ENABLED', + IsAggregateQueryEnabled = 'IS_AGGREGATE_QUERY_ENABLED', } From b347f39140c04fbdac3993ebbee69fcda592970f Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 16:27:28 +0100 Subject: [PATCH 19/30] Refacto maps --- .../graphql-query-filter-condition.parser.ts | 18 +- .../graphql-query-filter-field.parser.ts | 10 +- .../graphql-query-order.parser.ts | 16 +- ...graphql-selected-fields-relation.parser.ts | 14 +- .../graphql-selected-fields.parser.ts | 6 +- .../graphql-query.parser.ts | 43 +++-- .../graphql-query-runner.service.ts | 168 +++++++++--------- ...ct-records-to-graphql-connection.helper.ts | 2 +- .../process-nested-relations.helper.ts | 79 ++++---- ...phql-query-create-many-resolver.service.ts | 47 ++--- ...hql-query-destroy-many-resolver.service.ts | 43 +++-- ...phql-query-destroy-one-resolver.service.ts | 41 +++-- ...-query-find-duplicates-resolver.service.ts | 74 ++++---- ...raphql-query-find-many-resolver.service.ts | 62 ++++--- ...graphql-query-find-one-resolver.service.ts | 50 +++--- .../graphql-query-search-resolver.service.ts | 37 ++-- ...phql-query-update-many-resolver.service.ts | 54 +++--- ...aphql-query-update-one-resolver.service.ts | 52 +++--- .../services/api-event-emitter.service.ts | 39 ++-- .../utils/compute-cursor-arg-filter.ts | 14 +- .../utils/cursors.util.ts | 2 +- .../get-object-metadata-or-throw.util.ts | 4 +- .../get-relation-object-metadata.util.ts | 8 +- ...nterface.ts => object-record.interface.ts} | 12 +- .../factories/query-runner-args.factory.ts | 55 +++--- .../interfaces/pg-graphql.interface.ts | 2 +- .../query-runner-option.interface.ts | 16 +- .../listeners/telemetry.listener.ts | 4 +- .../utils/with-soft-deleted.util.ts | 2 +- .../workspace-query-hook.service.ts | 2 +- .../factories/create-many-resolver.factory.ts | 2 +- .../factories/create-one-resolver.factory.ts | 2 +- .../factories/delete-many-resolver.factory.ts | 2 +- .../factories/delete-one-resolver.factory.ts | 2 +- .../destroy-many-resolver.factory.ts | 2 +- .../factories/destroy-one-resolver.factory.ts | 2 +- .../find-duplicates-resolver.factory.ts | 2 +- .../factories/find-many-resolver.factory.ts | 2 +- .../factories/find-one-resolver.factory.ts | 2 +- .../restore-many-resolver.factory.ts | 2 +- .../factories/search-resolver-factory.ts | 2 +- .../factories/update-many-resolver.factory.ts | 2 +- .../factories/update-one-resolver.factory.ts | 2 +- .../interfaces/pg-graphql.interface.ts | 2 +- .../workspace-resolvers-builder.interface.ts | 2 +- .../workspace-resolver.factory.ts | 4 +- ...kspace-schema-builder-context.interface.ts | 4 +- .../utils/check-order-by.utils.ts | 2 +- .../__tests__/order-by-input.factory.spec.ts | 2 +- .../input-factories/order-by-input.factory.ts | 2 +- .../constants/duplicate-criteria.constants.ts | 2 +- .../types/object-record.base.event.ts | 4 +- .../object-record-changed-properties.util.ts | 2 +- .../utils/object-record-changed-values.ts | 12 +- .../utils/__tests__/parameters.utils.spec.ts | 2 +- .../open-api/utils/parameters.utils.ts | 2 +- .../types/field-metadata-map.ts | 3 + .../object-metadata-item-with-field-maps.ts | 8 + .../types/object-metadata-maps.ts | 7 + .../generate-object-metadata-map.util.ts | 43 ++--- .../factories/entity-schema.factory.ts | 4 +- .../repository/workspace.repository.ts | 4 +- .../utils/determine-relation-details.util.ts | 15 +- .../twenty-orm/utils/format-data.util.ts | 10 +- .../twenty-orm/utils/format-result.util.ts | 39 ++-- ...get-composite-field-metadata-collection.ts | 10 +- .../workspace-cache-storage.service.ts | 20 +-- .../timeline-activity.repository.ts | 4 +- 68 files changed, 629 insertions(+), 582 deletions(-) rename packages/twenty-server/src/engine/api/graphql/workspace-query-builder/interfaces/{record.interface.ts => object-record.interface.ts} (57%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/types/field-metadata-map.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/types/object-metadata-item-with-field-maps.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/types/object-metadata-maps.ts diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts index 21f9bdbdccb0..2b7949613f1e 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts @@ -5,27 +5,27 @@ import { WhereExpressionBuilder, } from 'typeorm'; -import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; -import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.parser'; export class GraphqlQueryFilterConditionParser { - private fieldMetadataMap: FieldMetadataMap; + private fieldMetadataMapByName: FieldMetadataMap; private queryFilterFieldParser: GraphqlQueryFilterFieldParser; - constructor(fieldMetadataMap: FieldMetadataMap) { - this.fieldMetadataMap = fieldMetadataMap; + constructor(fieldMetadataMapByName: FieldMetadataMap) { + this.fieldMetadataMapByName = fieldMetadataMapByName; this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser( - this.fieldMetadataMap, + this.fieldMetadataMapByName, ); } public parse( queryBuilder: SelectQueryBuilder, objectNameSingular: string, - filter: Partial, + filter: Partial, ): SelectQueryBuilder { if (!filter || Object.keys(filter).length === 0) { return queryBuilder; @@ -50,7 +50,7 @@ export class GraphqlQueryFilterConditionParser { switch (key) { case 'and': { const andWhereCondition = new Brackets((qb) => { - value.forEach((filter: RecordFilter, index: number) => { + value.forEach((filter: ObjectRecordFilter, index: number) => { const whereCondition = new Brackets((qb2) => { Object.entries(filter).forEach( ([subFilterkey, subFilterValue], index) => { @@ -82,7 +82,7 @@ export class GraphqlQueryFilterConditionParser { } case 'or': { const orWhereCondition = new Brackets((qb) => { - value.forEach((filter: RecordFilter, index: number) => { + value.forEach((filter: ObjectRecordFilter, index: number) => { const whereCondition = new Brackets((qb2) => { Object.entries(filter).forEach( ([subFilterkey, subFilterValue], index) => { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts index 5d35ebf5ecba..01a7517285cf 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts @@ -9,17 +9,17 @@ import { import { computeWhereConditionParts } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { capitalize } from 'src/utils/capitalize'; const ARRAY_OPERATORS = ['in', 'contains', 'not_contains']; export class GraphqlQueryFilterFieldParser { - private fieldMetadataMap: FieldMetadataMap; + private fieldMetadataMapByName: FieldMetadataMap; - constructor(fieldMetadataMap: FieldMetadataMap) { - this.fieldMetadataMap = fieldMetadataMap; + constructor(fieldMetadataMapByName: FieldMetadataMap) { + this.fieldMetadataMapByName = fieldMetadataMapByName; } public parse( @@ -29,7 +29,7 @@ export class GraphqlQueryFilterFieldParser { filterValue: any, isFirst = false, ): void { - const fieldMetadata = this.fieldMetadataMap[`${key}`]; + const fieldMetadataMapByName = this.fieldMetadataMapByName[`${key}`]; if (!fieldMetadata) { throw new Error(`Field metadata not found for field: ${key}`); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser.ts index aaa242d804aa..a16a9c0c149c 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser.ts @@ -1,7 +1,7 @@ import { + ObjectRecordOrderBy, OrderByDirection, - RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { @@ -10,25 +10,25 @@ import { } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { capitalize } from 'src/utils/capitalize'; export class GraphqlQueryOrderFieldParser { - private fieldMetadataMap: FieldMetadataMap; + private fieldMetadataMapByName: FieldMetadataMap; - constructor(fieldMetadataMap: FieldMetadataMap) { - this.fieldMetadataMap = fieldMetadataMap; + constructor(fieldMetadataMapByName: FieldMetadataMap) { + this.fieldMetadataMapByName = fieldMetadataMapByName; } parse( - orderBy: RecordOrderBy, + orderBy: ObjectRecordOrderBy, objectNameSingular: string, isForwardPagination = true, ): Record { return orderBy.reduce( (acc, item) => { Object.entries(item).forEach(([key, value]) => { - const fieldMetadata = this.fieldMetadataMap[key]; + const fieldMetadata = this.fieldMetadataMapByName[key]; if (!fieldMetadata || value === undefined) { throw new GraphqlQueryRunnerException( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts index 19308a44989c..1924b743fea8 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts @@ -2,13 +2,13 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; import { getRelationObjectMetadata } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util'; -import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; export class GraphqlQuerySelectedFieldsRelationParser { - private objectMetadataMap: ObjectMetadataMap; + private objectMetadataMaps: ObjectMetadataMaps; - constructor(objectMetadataMap: ObjectMetadataMap) { - this.objectMetadataMap = objectMetadataMap; + constructor(objectMetadataMaps: ObjectMetadataMaps) { + this.objectMetadataMaps = objectMetadataMaps; } parseRelationField( @@ -25,12 +25,12 @@ export class GraphqlQuerySelectedFieldsRelationParser { const referencedObjectMetadata = getRelationObjectMetadata( fieldMetadata, - this.objectMetadataMap, + this.objectMetadataMaps, ); - const relationFields = referencedObjectMetadata.fields; + const relationFields = referencedObjectMetadata.fieldsById; const fieldParser = new GraphqlQuerySelectedFieldsParser( - this.objectMetadataMap, + this.objectMetadataMaps, ); const subResult = fieldParser.parse(fieldValue, relationFields); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts index a23b7195dff3..6c60df475959 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts @@ -7,7 +7,7 @@ import { import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; import { capitalize } from 'src/utils/capitalize'; @@ -16,9 +16,9 @@ import { isPlainObject } from 'src/utils/is-plain-object'; export class GraphqlQuerySelectedFieldsParser { private graphqlQuerySelectedFieldsRelationParser: GraphqlQuerySelectedFieldsRelationParser; - constructor(objectMetadataMap: ObjectMetadataMap) { + constructor(objectMetadataMaps: ObjectMetadataMaps) { this.graphqlQuerySelectedFieldsRelationParser = - new GraphqlQuerySelectedFieldsRelationParser(objectMetadataMap); + new GraphqlQuerySelectedFieldsRelationParser(objectMetadataMaps); } parse( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts index 0aa047fc31f7..7142ab71d529 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts @@ -6,43 +6,41 @@ import { } from 'typeorm'; import { - RecordFilter, - RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; + ObjectRecordFilter, + ObjectRecordOrderBy, +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser'; import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser'; import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; -import { - FieldMetadataMap, - ObjectMetadataMap, - ObjectMetadataMapItem, -} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; export class GraphqlQueryParser { - private fieldMetadataMap: FieldMetadataMap; - private objectMetadataMap: ObjectMetadataMap; + private fieldMetadataMapByName: FieldMetadataMap; + private objectMetadataMaps: ObjectMetadataMaps; private filterConditionParser: GraphqlQueryFilterConditionParser; private orderFieldParser: GraphqlQueryOrderFieldParser; constructor( - fieldMetadataMap: FieldMetadataMap, - objectMetadataMap: ObjectMetadataMap, + fieldMetadataMapByName: FieldMetadataMap, + objectMetadataMaps: ObjectMetadataMaps, ) { - this.objectMetadataMap = objectMetadataMap; - this.fieldMetadataMap = fieldMetadataMap; + this.objectMetadataMaps = objectMetadataMaps; + this.fieldMetadataMapByName = fieldMetadataMapByName; this.filterConditionParser = new GraphqlQueryFilterConditionParser( - this.fieldMetadataMap, + this.fieldMetadataMapByName, ); this.orderFieldParser = new GraphqlQueryOrderFieldParser( - this.fieldMetadataMap, + this.fieldMetadataMapByName, ); } public applyFilterToBuilder( queryBuilder: SelectQueryBuilder, objectNameSingular: string, - recordFilter: Partial, + recordFilter: Partial, ): SelectQueryBuilder { return this.filterConditionParser.parse( queryBuilder, @@ -53,7 +51,7 @@ export class GraphqlQueryParser { public applyDeletedAtToBuilder( queryBuilder: SelectQueryBuilder, - recordFilter: RecordFilter, + recordFilter: ObjectRecordFilter, ): SelectQueryBuilder { if (this.checkForDeletedAtFilter(recordFilter)) { queryBuilder.withDeleted(); @@ -90,7 +88,7 @@ export class GraphqlQueryParser { public applyOrderToBuilder( queryBuilder: SelectQueryBuilder, - orderBy: RecordOrderBy, + orderBy: ObjectRecordOrderBy, objectNameSingular: string, isForwardPagination = true, ): SelectQueryBuilder { @@ -104,11 +102,12 @@ export class GraphqlQueryParser { } public parseSelectedFields( - parentObjectMetadata: ObjectMetadataMapItem, + parentObjectMetadata: ObjectMetadataItemWithFieldMaps, graphqlSelectedFields: Partial>, ): { select: Record; relations: Record } { const parentFields = - this.objectMetadataMap[parentObjectMetadata.nameSingular]?.fields; + this.objectMetadataMaps.byNameSingular[parentObjectMetadata.nameSingular] + ?.fieldsById; if (!parentFields) { throw new Error( @@ -117,7 +116,7 @@ export class GraphqlQueryParser { } const selectedFieldsParser = new GraphqlQuerySelectedFieldsParser( - this.objectMetadataMap, + this.objectMetadataMaps, ); return selectedFieldsParser.parse(graphqlSelectedFields, parentFields); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts index c3fe76e2e07b..0e02201065ad 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { - Record as IRecord, - RecordFilter, - RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; + ObjectRecord, + ObjectRecordFilter, + ObjectRecordOrderBy, +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; @@ -48,11 +48,11 @@ export class GraphqlQueryRunnerService { /** QUERIES */ @LogExecutionTime() - async findOne( + async findOne( args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - return this.executeQuery, ObjectRecord>( + ): Promise { + return this.executeQuery, T>( 'findOne', args, options, @@ -61,36 +61,36 @@ export class GraphqlQueryRunnerService { @LogExecutionTime() async findMany< - ObjectRecord extends IRecord, - Filter extends RecordFilter, - OrderBy extends RecordOrderBy, + T extends ObjectRecord, + Filter extends ObjectRecordFilter, + OrderBy extends ObjectRecordOrderBy, >( args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise>> { + ): Promise>> { return this.executeQuery< FindManyResolverArgs, - IConnection> + IConnection> >('findMany', args, options); } @LogExecutionTime() - async findDuplicates( - args: FindDuplicatesResolverArgs>, + async findDuplicates( + args: FindDuplicatesResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise[]> { + ): Promise[]> { return this.executeQuery< - FindDuplicatesResolverArgs>, - IConnection[] + FindDuplicatesResolverArgs>, + IConnection[] >('findDuplicates', args, options); } @LogExecutionTime() - async search( + async search( args: SearchResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise> { - return this.executeQuery>( + ): Promise> { + return this.executeQuery>( 'search', args, options, @@ -100,13 +100,13 @@ export class GraphqlQueryRunnerService { /** MUTATIONS */ @LogExecutionTime() - async createOne( - args: CreateOneResolverArgs>, + async createOne( + args: CreateOneResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { + ): Promise { const results = await this.executeQuery< - CreateManyResolverArgs>, - ObjectRecord[] + CreateManyResolverArgs>, + T[] >('createMany', { data: [args.data], upsert: args.upsert }, options); // TODO: emitCreateEvents should be moved to the ORM layer @@ -114,7 +114,7 @@ export class GraphqlQueryRunnerService { this.apiEventEmitterService.emitCreateEvents( results, options.authContext, - options.objectMetadataItem, + options.objectMetadataItemWithFieldMaps, ); } @@ -122,20 +122,20 @@ export class GraphqlQueryRunnerService { } @LogExecutionTime() - async createMany( - args: CreateManyResolverArgs>, + async createMany( + args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { + ): Promise { const results = await this.executeQuery< - CreateManyResolverArgs>, - ObjectRecord[] + CreateManyResolverArgs>, + T[] >('createMany', args, options); if (results) { this.apiEventEmitterService.emitCreateEvents( results, options.authContext, - options.objectMetadataItem, + options.objectMetadataItemWithFieldMaps, ); } @@ -143,14 +143,11 @@ export class GraphqlQueryRunnerService { } @LogExecutionTime() - public async updateOne( - args: UpdateOneResolverArgs>, + public async updateOne( + args: UpdateOneResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { - const existingRecord = await this.executeQuery< - FindOneResolverArgs, - ObjectRecord - >( + ): Promise { + const existingRecord = await this.executeQuery( 'findOne', { filter: { id: { eq: args.id } }, @@ -159,8 +156,8 @@ export class GraphqlQueryRunnerService { ); const result = await this.executeQuery< - UpdateOneResolverArgs>, - ObjectRecord + UpdateOneResolverArgs>, + T >('updateOne', args, options); this.apiEventEmitterService.emitUpdateEvents( @@ -168,20 +165,20 @@ export class GraphqlQueryRunnerService { [result], Object.keys(args.data), options.authContext, - options.objectMetadataItem, + options.objectMetadataItemWithFieldMaps, ); return result; } @LogExecutionTime() - public async updateMany( - args: UpdateManyResolverArgs>, + public async updateMany( + args: UpdateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { + ): Promise { const existingRecords = await this.executeQuery< FindManyResolverArgs, - IConnection> + IConnection> >( 'findMany', { @@ -191,8 +188,8 @@ export class GraphqlQueryRunnerService { ); const result = await this.executeQuery< - UpdateManyResolverArgs>, - ObjectRecord[] + UpdateManyResolverArgs>, + T[] >('updateMany', args, options); this.apiEventEmitterService.emitUpdateEvents( @@ -200,25 +197,25 @@ export class GraphqlQueryRunnerService { result, Object.keys(args.data), options.authContext, - options.objectMetadataItem, + options.objectMetadataItemWithFieldMaps, ); return result; } @LogExecutionTime() - public async deleteOne( + public async deleteOne( args: DeleteOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { + ): Promise { const result = await this.executeQuery< - UpdateOneResolverArgs>, - ObjectRecord + UpdateOneResolverArgs>, + T >( 'deleteOne', { id: args.id, - data: { deletedAt: new Date() } as Partial, + data: { deletedAt: new Date() } as Partial, }, options, ); @@ -226,26 +223,26 @@ export class GraphqlQueryRunnerService { this.apiEventEmitterService.emitDeletedEvents( [result], options.authContext, - options.objectMetadataItem, + options.objectMetadataItemWithFieldMaps, ); return result; } @LogExecutionTime() - public async deleteMany( + public async deleteMany( args: DeleteManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { + ): Promise { const result = await this.executeQuery< - UpdateManyResolverArgs>, - ObjectRecord[] + UpdateManyResolverArgs>, + T[] >( 'deleteMany', { filter: args.filter, - data: { deletedAt: new Date() } as Partial, + data: { deletedAt: new Date() } as Partial, }, options, ); @@ -253,63 +250,62 @@ export class GraphqlQueryRunnerService { this.apiEventEmitterService.emitDeletedEvents( result, options.authContext, - options.objectMetadataItem, + options.objectMetadataItemWithFieldMaps, ); return result; } @LogExecutionTime() - async destroyOne( + async destroyOne( args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const result = await this.executeQuery< - DestroyOneResolverArgs, - ObjectRecord - >('destroyOne', args, options); + ): Promise { + const result = await this.executeQuery( + 'destroyOne', + args, + options, + ); this.apiEventEmitterService.emitDestroyEvents( [result], options.authContext, - options.objectMetadataItem, + options.objectMetadataItemWithFieldMaps, ); return result; } @LogExecutionTime() - async destroyMany( + async destroyMany( args: DestroyManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const result = await this.executeQuery< - DestroyManyResolverArgs, - ObjectRecord[] - >('destroyMany', args, options); + ): Promise { + const result = await this.executeQuery( + 'destroyMany', + args, + options, + ); this.apiEventEmitterService.emitDestroyEvents( result, options.authContext, - options.objectMetadataItem, + options.objectMetadataItemWithFieldMaps, ); return result; } @LogExecutionTime() - public async restoreMany( + public async restoreMany( args: RestoreManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - return await this.executeQuery< - UpdateManyResolverArgs>, - ObjectRecord - >( + ): Promise { + return await this.executeQuery>, T>( 'restoreMany', { filter: args.filter, - data: { deletedAt: null } as Partial, + data: { deletedAt: null } as Partial, }, options, ); @@ -320,7 +316,7 @@ export class GraphqlQueryRunnerService { args: Input, options: WorkspaceQueryRunnerOptions, ): Promise { - const { authContext, objectMetadataItem } = options; + const { authContext, objectMetadataItemWithFieldMaps } = options; const resolver = this.graphqlQueryResolverFactory.getResolver(operationName); @@ -330,7 +326,7 @@ export class GraphqlQueryRunnerService { const hookedArgs = await this.workspaceQueryHookService.executePreQueryHooks( authContext, - objectMetadataItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, operationName, args, ); @@ -345,7 +341,7 @@ export class GraphqlQueryRunnerService { const resultWithGetters = await this.queryResultGettersFactory.create( results, - objectMetadataItem, + objectMetadataItemWithFieldMaps, authContext.workspace.id, ); @@ -355,7 +351,7 @@ export class GraphqlQueryRunnerService { await this.workspaceQueryHookService.executePostQueryHooks( authContext, - objectMetadataItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, operationName, resultWithGettersArray, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index 54220315345a..93fea1d8200a 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -1,7 +1,7 @@ import { Record as IRecord, RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; 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 dd3e5abd4020..572c7706db2e 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 @@ -7,25 +7,23 @@ import { Repository, } from 'typeorm'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { getRelationMetadata, getRelationObjectMetadata, } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util'; -import { - ObjectMetadataMap, - ObjectMetadataMapItem, -} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util'; export class ProcessNestedRelationsHelper { constructor() {} - public async processNestedRelations( - objectMetadataMap: ObjectMetadataMap, - parentObjectMetadataItem: ObjectMetadataMapItem, - parentObjectRecords: ObjectRecord[], + public async processNestedRelations( + objectMetadataMaps: ObjectMetadataMaps, + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, + parentObjectRecords: T[], relations: Record>, limit: number, authContext: any, @@ -34,7 +32,7 @@ export class ProcessNestedRelationsHelper { const processRelationTasks = Object.entries(relations).map( ([relationName, nestedRelations]) => this.processRelation( - objectMetadataMap, + objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, relationName, @@ -48,17 +46,18 @@ export class ProcessNestedRelationsHelper { await Promise.all(processRelationTasks); } - private async processRelation( - objectMetadataMap: ObjectMetadataMap, - parentObjectMetadataItem: ObjectMetadataMapItem, - parentObjectRecords: ObjectRecord[], + private async processRelation( + objectMetadataMaps: ObjectMetadataMaps, + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, + parentObjectRecords: T[], relationName: string, nestedRelations: any, limit: number, authContext: any, dataSource: DataSource, ): Promise { - const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; + const relationFieldMetadata = + parentObjectMetadataItem.fieldsByName[relationName]; const relationMetadata = getRelationMetadata(relationFieldMetadata); const relationDirection = deduceRelationDirection( relationFieldMetadata, @@ -72,7 +71,7 @@ export class ProcessNestedRelationsHelper { await processor.call( this, - objectMetadataMap, + objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, relationName, @@ -83,10 +82,10 @@ export class ProcessNestedRelationsHelper { ); } - private async processFromRelation( - objectMetadataMap: ObjectMetadataMap, - parentObjectMetadataItem: ObjectMetadataMapItem, - parentObjectRecords: ObjectRecord[], + private async processFromRelation( + objectMetadataMaps: ObjectMetadataMaps, + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, + parentObjectRecords: T[], relationName: string, nestedRelations: any, limit: number, @@ -95,7 +94,7 @@ export class ProcessNestedRelationsHelper { ): Promise { const { inverseRelationName, referenceObjectMetadata } = this.getRelationMetadata( - objectMetadataMap, + objectMetadataMaps, parentObjectMetadataItem, relationName, ); @@ -120,8 +119,8 @@ export class ProcessNestedRelationsHelper { if (Object.keys(nestedRelations).length > 0) { await this.processNestedRelations( - objectMetadataMap, - objectMetadataMap[referenceObjectMetadata.nameSingular], + objectMetadataMaps, + objectMetadataMaps.byNameSingular[referenceObjectMetadata.nameSingular], relationResults as ObjectRecord[], nestedRelations as Record>, limit, @@ -131,10 +130,10 @@ export class ProcessNestedRelationsHelper { } } - private async processToRelation( - objectMetadataMap: ObjectMetadataMap, - parentObjectMetadataItem: ObjectMetadataMapItem, - parentObjectRecords: ObjectRecord[], + private async processToRelation( + objectMetadataMaps: ObjectMetadataMaps, + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, + parentObjectRecords: T[], relationName: string, nestedRelations: any, limit: number, @@ -142,7 +141,7 @@ export class ProcessNestedRelationsHelper { dataSource: DataSource, ): Promise { const { referenceObjectMetadata } = this.getRelationMetadata( - objectMetadataMap, + objectMetadataMaps, parentObjectMetadataItem, relationName, ); @@ -169,8 +168,8 @@ export class ProcessNestedRelationsHelper { if (Object.keys(nestedRelations).length > 0) { await this.processNestedRelations( - objectMetadataMap, - objectMetadataMap[referenceObjectMetadata.nameSingular], + objectMetadataMaps, + objectMetadataMaps.byNameSingular[referenceObjectMetadata.nameSingular], relationResults as ObjectRecord[], nestedRelations as Record>, limit, @@ -181,25 +180,25 @@ export class ProcessNestedRelationsHelper { } private getRelationMetadata( - objectMetadataMap: ObjectMetadataMap, - parentObjectMetadataItem: ObjectMetadataMapItem, + objectMetadataMaps: ObjectMetadataMaps, + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, relationName: string, ) { - const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; + const relationFieldMetadata = + parentObjectMetadataItem.fieldsByName[relationName]; const relationMetadata = getRelationMetadata(relationFieldMetadata); const referenceObjectMetadata = getRelationObjectMetadata( relationFieldMetadata, - objectMetadataMap, + objectMetadataMaps, ); const inverseRelationName = - objectMetadataMap[relationMetadata.toObjectMetadataId]?.fields[ - relationMetadata.toFieldMetadataId - ]?.name; + objectMetadataMaps.byNameSingular[relationMetadata.toObjectMetadataId] + ?.fieldsById[relationMetadata.toFieldMetadataId]?.name; return { inverseRelationName, referenceObjectMetadata }; } - private getUniqueIds(records: IRecord[], idField: string): any[] { + private getUniqueIds(records: ObjectRecord[], idField: string): any[] { return [...new Set(records.map((item) => item[idField]))]; } @@ -221,7 +220,7 @@ export class ProcessNestedRelationsHelper { } private assignRelationResults( - parentRecords: IRecord[], + parentRecords: ObjectRecord[], relationResults: any[], relationName: string, joinField: string, @@ -234,7 +233,7 @@ export class ProcessNestedRelationsHelper { } private assignToRelationResults( - parentRecords: IRecord[], + parentRecords: ObjectRecord[], relationResults: any[], relationName: string, ): void { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index 6cd7a111138e..a951e03e4e4d 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -4,7 +4,7 @@ import graphqlFields from 'graphql-fields'; import { In, InsertResult } from 'typeorm'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -19,35 +19,40 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryCreateManyResolverService - implements ResolverService + implements ResolverService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} - async resolve( + async resolve( args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, info, objectMetadataMap, objectMetadataMapItem } = - options; + ): Promise { + const { + authContext, + info, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, + } = options; const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, ); + const repository = dataSource.getRepository( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMapItem.fields, - objectMetadataMap, + objectMetadataItemWithFieldMaps.fieldsById, + objectMetadataMaps, ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, selectedFields, ); @@ -59,7 +64,7 @@ export class GraphqlQueryCreateManyResolverService }); const queryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const nonFormattedUpsertedRecords = (await queryBuilder @@ -71,16 +76,16 @@ export class GraphqlQueryCreateManyResolverService const upsertedRecords = formatResult( nonFormattedUpsertedRecords, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, ); const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { await processNestedRelationsHelper.processNestedRelations( - objectMetadataMap, - objectMetadataMapItem, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, upsertedRecords, relations, QUERY_MAX_RECORDS, @@ -90,23 +95,23 @@ export class GraphqlQueryCreateManyResolverService } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return upsertedRecords.map((record: ObjectRecord) => + return upsertedRecords.map((record: T) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, totalCount: 1, }), ); } - async validate( - args: CreateManyResolverArgs>, + async validate( + args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { - assertMutationNotOnRemoteObject(options.objectMetadataItem); + assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); args.data.forEach((record) => { if (record?.id) { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts index 04ceddf9ac9d..09b919705525 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import graphqlFields from 'graphql-fields'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -16,46 +16,51 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryDestroyManyResolverService - implements ResolverService + implements ResolverService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} - async resolve( + async resolve( args: DestroyManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataMapItem, objectMetadataMap, info } = - options; + ): Promise { + const { + authContext, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + info, + } = options; + const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, ); const repository = dataSource.getRepository( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMapItem.fields, - objectMetadataMap, + objectMetadataItemWithFieldMaps.fieldsById, + objectMetadataMaps, ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, selectedFields, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, args.filter, ); @@ -66,16 +71,16 @@ export class GraphqlQueryDestroyManyResolverService const deletedRecords = formatResult( nonFormattedDeletedObjectRecords.raw, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, ); const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { await processNestedRelationsHelper.processNestedRelations( - objectMetadataMap, - objectMetadataMapItem, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, deletedRecords, relations, QUERY_MAX_RECORDS, @@ -85,12 +90,12 @@ export class GraphqlQueryDestroyManyResolverService } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return deletedRecords.map((record: ObjectRecord) => + return deletedRecords.map((record: T) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, totalCount: 1, }), diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts index 5467d6c0ff0a..4e1e6fb7a0d2 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import graphqlFields from 'graphql-fields'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -20,45 +20,50 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryDestroyOneResolverService - implements ResolverService + implements ResolverService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} - async resolve( + async resolve( args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataMapItem, objectMetadataMap, info } = - options; + ): Promise { + const { + authContext, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + info, + } = options; + const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, ); const repository = dataSource.getRepository( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMapItem.fields, - objectMetadataMap, + objectMetadataItemWithFieldMaps.fieldsByName, + objectMetadataMaps, ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, selectedFields, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const nonFormattedDeletedObjectRecords = await queryBuilder - .where(`"${objectMetadataMapItem.nameSingular}".id = :id`, { + .where(`"${objectMetadataItemWithFieldMaps.nameSingular}".id = :id`, { id: args.id, }) .take(1) @@ -75,16 +80,16 @@ export class GraphqlQueryDestroyOneResolverService const recordBeforeDeletion = formatResult( nonFormattedDeletedObjectRecords.raw, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, )[0]; const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { await processNestedRelationsHelper.processNestedRelations( - objectMetadataMap, - objectMetadataMapItem, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, [recordBeforeDeletion], relations, QUERY_MAX_RECORDS, @@ -94,11 +99,11 @@ export class GraphqlQueryDestroyOneResolverService } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); return typeORMObjectRecordsParser.processRecord({ objectRecord: recordBeforeDeletion, - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, totalCount: 1, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts index d3bc72fa8220..aca931d12610 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -5,10 +5,10 @@ import { In } from 'typeorm'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { - Record as IRecord, + ObjectRecord, + ObjectRecordFilter, OrderByDirection, - RecordFilter, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { FindDuplicatesResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -21,7 +21,7 @@ import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { settings } from 'src/engine/constants/settings'; import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; -import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @@ -29,60 +29,63 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryFindDuplicatesResolverService implements - ResolverService[]> + ResolverService[]> { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} - async resolve( - args: FindDuplicatesResolverArgs>, + async resolve( + args: FindDuplicatesResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise[]> { - const { authContext, objectMetadataMapItem, objectMetadataMap } = options; + ): Promise[]> { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + options; const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, ); const repository = dataSource.getRepository( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const existingRecordsQueryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const duplicateRecordsQueryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMap[objectMetadataMapItem.nameSingular].fields, - objectMetadataMap, + objectMetadataMaps.byNameSingular[ + objectMetadataItemWithFieldMaps.nameSingular + ].fieldsById, + objectMetadataMaps, ); const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - let objectRecords: Partial[] = []; + let objectRecords: Partial[] = []; if (args.ids) { const nonFormattedObjectRecords = (await existingRecordsQueryBuilder .where({ id: In(args.ids) }) - .getMany()) as ObjectRecord[]; + .getMany()) as T[]; objectRecords = formatResult( nonFormattedObjectRecords, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, ); } else if (args.data && !isEmpty(args.data)) { - objectRecords = formatData(args.data, objectMetadataMapItem); + objectRecords = formatData(args.data, objectMetadataItemWithFieldMaps); } - const duplicateConnections: IConnection[] = await Promise.all( + const duplicateConnections: IConnection[] = await Promise.all( objectRecords.map(async (record) => { const duplicateConditions = this.buildDuplicateConditions( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, [record], record.id, ); @@ -90,7 +93,7 @@ export class GraphqlQueryFindDuplicatesResolverService if (isEmpty(duplicateConditions)) { return typeORMObjectRecordsParser.createConnection({ objectRecords: [], - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 0, totalCount: 0, order: [{ id: OrderByDirection.AscNullsFirst }], @@ -101,22 +104,22 @@ export class GraphqlQueryFindDuplicatesResolverService const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( duplicateRecordsQueryBuilder, - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, duplicateConditions, ); const nonFormattedDuplicates = - (await withFilterQueryBuilder.getMany()) as ObjectRecord[]; + (await withFilterQueryBuilder.getMany()) as T[]; const duplicates = formatResult( nonFormattedDuplicates, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, ); return typeORMObjectRecordsParser.createConnection({ objectRecords: duplicates, - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: duplicates.length, totalCount: duplicates.length, order: [{ id: OrderByDirection.AscNullsFirst }], @@ -130,16 +133,16 @@ export class GraphqlQueryFindDuplicatesResolverService } private buildDuplicateConditions( - objectMetadataMapItem: ObjectMetadataMapItem, - records?: Partial[] | undefined, + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + records?: Partial[] | undefined, filteringByExistingRecordId?: string, - ): Partial { + ): Partial { if (!records || records.length === 0) { return {}; } const criteriaCollection = this.getApplicableDuplicateCriteriaCollection( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, ); const conditions = records.flatMap((record) => { @@ -164,7 +167,7 @@ export class GraphqlQueryFindDuplicatesResolverService }); }); - const filter: Partial = {}; + const filter: Partial = {}; if (conditions && !isEmpty(conditions)) { filter.or = conditions; @@ -178,11 +181,12 @@ export class GraphqlQueryFindDuplicatesResolverService } private getApplicableDuplicateCriteriaCollection( - objectMetadataMapItem: ObjectMetadataMapItem, + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ) { return DUPLICATE_CRITERIA_COLLECTION.filter( (duplicateCriteria) => - duplicateCriteria.objectName === objectMetadataMapItem.nameSingular, + duplicateCriteria.objectName === + objectMetadataItemWithFieldMaps.nameSingular, ); } 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 b9ffb043d9e6..88a6922f257d 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 @@ -5,11 +5,11 @@ import { SelectQueryBuilder } from 'typeorm'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { - Record as IRecord, + ObjectRecord, + ObjectRecordFilter, + ObjectRecordOrderBy, OrderByDirection, - RecordFilter, - RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -38,22 +38,26 @@ import { isDefined } from 'src/utils/is-defined'; @Injectable() export class GraphqlQueryFindManyResolverService - implements ResolverService> + implements ResolverService> { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} async resolve< - ObjectRecord extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, - OrderBy extends RecordOrderBy = RecordOrderBy, + T extends ObjectRecord = ObjectRecord, + Filter extends ObjectRecordFilter = ObjectRecordFilter, + OrderBy extends ObjectRecordOrderBy = ObjectRecordOrderBy, >( args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise> { - const { authContext, objectMetadataMapItem, info, objectMetadataMap } = - options; + ): Promise> { + const { + authContext, + objectMetadataItemWithFieldMaps, + info, + objectMetadataMaps, + } = options; const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( @@ -61,32 +65,32 @@ export class GraphqlQueryFindManyResolverService ); const repository = dataSource.getRepository( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const countQueryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMapItem.fields, - objectMetadataMap, + objectMetadataItemWithFieldMaps.fieldsByName, + objectMetadataMaps, ); const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder( countQueryBuilder, - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, args.filter ?? ({} as Filter), ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, selectedFields, ); const isForwardPagination = !isDefined(args.before); @@ -114,7 +118,7 @@ export class GraphqlQueryFindManyResolverService const cursorArgFilter = computeCursorArgFilter( cursor, orderByWithIdCondition, - objectMetadataMapItem.fields, + objectMetadataItemWithFieldMaps.fieldsByName, isForwardPagination, ); @@ -127,14 +131,14 @@ export class GraphqlQueryFindManyResolverService const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, appliedFilters, ); const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder( withFilterQueryBuilder, orderByWithIdCondition, - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, isForwardPagination, ); @@ -144,7 +148,7 @@ export class GraphqlQueryFindManyResolverService ); const selectedAggregatedFields = this.getSelectedAggregatedFields({ - objectFields: Object.values(objectMetadataMapItem.fields), + objectFields: Object.values(objectMetadataItemWithFieldMaps.fields), selectedFields, }); @@ -161,8 +165,8 @@ export class GraphqlQueryFindManyResolverService const objectRecords = formatResult( nonFormattedObjectRecords.entities, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, ); const { hasNextPage, hasPreviousPage } = getPaginationInfo( @@ -179,8 +183,8 @@ export class GraphqlQueryFindManyResolverService if (relations) { await processNestedRelationsHelper.processNestedRelations( - objectMetadataMap, - objectMetadataMapItem, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, objectRecords, relations, limit, @@ -190,11 +194,11 @@ export class GraphqlQueryFindManyResolverService } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); const result = typeORMObjectRecordsParser.createConnection({ objectRecords, - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: limit, totalCount, order: orderByWithIdCondition, @@ -210,7 +214,7 @@ export class GraphqlQueryFindManyResolverService return { ...result, ...aggregatedFieldsResults }; } - async validate( + async validate( args: FindManyResolverArgs, _options: WorkspaceQueryRunnerOptions, ): Promise { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts index 42c8daae8079..4a249bad56fa 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -4,9 +4,9 @@ import graphqlFields from 'graphql-fields'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { - Record as IRecord, - RecordFilter, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; + ObjectRecord, + ObjectRecordFilter, +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -27,21 +27,25 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryFindOneResolverService - implements ResolverService + implements ResolverService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} async resolve< - ObjectRecord extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, + T extends ObjectRecord = ObjectRecord, + Filter extends ObjectRecordFilter = ObjectRecordFilter, >( args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataMapItem, info, objectMetadataMap } = - options; + ): Promise { + const { + authContext, + objectMetadataItemWithFieldMaps, + info, + objectMetadataMaps, + } = options; const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( @@ -49,28 +53,28 @@ export class GraphqlQueryFindOneResolverService ); const repository = dataSource.getRepository( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMapItem.fields, - objectMetadataMap, + objectMetadataItemWithFieldMaps.fieldsByName, + objectMetadataMaps, ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, selectedFields, ); const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, args.filter ?? ({} as Filter), ); @@ -83,8 +87,8 @@ export class GraphqlQueryFindOneResolverService const objectRecord = formatResult( nonFormattedObjectRecord, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, ); if (!objectRecord) { @@ -100,8 +104,8 @@ export class GraphqlQueryFindOneResolverService if (relations) { await processNestedRelationsHelper.processNestedRelations( - objectMetadataMap, - objectMetadataMapItem, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, objectRecords, relations, QUERY_MAX_RECORDS, @@ -111,17 +115,17 @@ export class GraphqlQueryFindOneResolverService } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); return typeORMObjectRecordsParser.processRecord({ objectRecord: objectRecords[0], - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, totalCount: 1, - }) as ObjectRecord; + }) as T; } - async validate( + async validate( args: FindOneResolverArgs, _options: WorkspaceQueryRunnerOptions, ): Promise { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts index 4cde38cfe6b4..a1d8f2dd5623 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -5,10 +5,10 @@ import { Brackets } from 'typeorm'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { - Record as IRecord, + ObjectRecord, + ObjectRecordFilter, OrderByDirection, - RecordFilter, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { SearchResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -22,40 +22,39 @@ import { isDefined } from 'src/utils/is-defined'; @Injectable() export class GraphqlQuerySearchResolverService - implements ResolverService> + implements ResolverService> { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} async resolve< - ObjectRecord extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, + T extends ObjectRecord = ObjectRecord, + Filter extends ObjectRecordFilter = ObjectRecordFilter, >( args: SearchResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise> { + ): Promise> { const { authContext, - objectMetadataItem, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, info, } = options; const repository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( authContext.workspace.id, - objectMetadataItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); if (!isDefined(args.searchInput)) { return typeORMObjectRecordsParser.createConnection({ objectRecords: [], - objectName: objectMetadataItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 0, totalCount: 0, order: [{ id: OrderByDirection.AscNullsFirst }], @@ -69,16 +68,16 @@ export class GraphqlQuerySearchResolverService const limit = args?.limit ?? QUERY_MAX_RECORDS; const queryBuilder = repository.createQueryBuilder( - objectMetadataItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMapItem.fields, - objectMetadataMap, + objectMetadataItemWithFieldMaps.fieldsById, + objectMetadataMaps, ); const queryBuilderWithFilter = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, args.filter ?? ({} as Filter), ); @@ -109,7 +108,7 @@ export class GraphqlQuerySearchResolverService .setParameter('searchTerms', searchTerms) .setParameter('searchTermsOr', searchTermsOr) .take(limit) - .getMany()) as ObjectRecord[]; + .getMany()) as T[]; const objectRecords = await repository.formatResult(resultsWithTsVector); @@ -122,7 +121,7 @@ export class GraphqlQuerySearchResolverService return typeORMObjectRecordsParser.createConnection({ objectRecords: objectRecords ?? [], - objectName: objectMetadataItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: limit, totalCount, order, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts index 020bf08fa722..16967651efd3 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import graphqlFields from 'graphql-fields'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -20,18 +20,22 @@ import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryUpdateManyResolverService - implements ResolverService + implements ResolverService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} - async resolve( - args: UpdateManyResolverArgs>, + async resolve( + args: UpdateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataMapItem, objectMetadataMap, info } = - options; + ): Promise { + const { + authContext, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + info, + } = options; const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( @@ -39,28 +43,28 @@ export class GraphqlQueryUpdateManyResolverService ); const repository = dataSource.getRepository( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMapItem.fields, - objectMetadataMap, + objectMetadataItemWithFieldMaps.fieldsByName, + objectMetadataMaps, ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, selectedFields, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const tableName = computeTableName( - objectMetadataMapItem.nameSingular, - objectMetadataMapItem.isCustom, + objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, ); const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( @@ -69,7 +73,7 @@ export class GraphqlQueryUpdateManyResolverService args.filter, ); - const data = formatData(args.data, objectMetadataMapItem); + const data = formatData(args.data, objectMetadataItemWithFieldMaps); const nonFormattedUpdatedObjectRecords = await withFilterQueryBuilder .update(data) @@ -78,16 +82,16 @@ export class GraphqlQueryUpdateManyResolverService const updatedRecords = formatResult( nonFormattedUpdatedObjectRecords.raw, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, ); const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { await processNestedRelationsHelper.processNestedRelations( - objectMetadataMap, - objectMetadataMapItem, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, updatedRecords, relations, QUERY_MAX_RECORDS, @@ -97,23 +101,23 @@ export class GraphqlQueryUpdateManyResolverService } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return updatedRecords.map((record: ObjectRecord) => + return updatedRecords.map((record: T) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, totalCount: 1, }), ); } - async validate( - args: UpdateManyResolverArgs>, + async validate( + args: UpdateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { - assertMutationNotOnRemoteObject(options.objectMetadataMapItem); + assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); if (!args.filter) { throw new Error('Filter is required'); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts index 8fe4396d2413..25ffbdfe3479 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import graphqlFields from 'graphql-fields'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -23,18 +23,22 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryUpdateOneResolverService - implements ResolverService + implements ResolverService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} - async resolve( - args: UpdateOneResolverArgs>, + async resolve( + args: UpdateOneResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataMapItem, objectMetadataMap, info } = - options; + ): Promise { + const { + authContext, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + info, + } = options; const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( @@ -42,26 +46,26 @@ export class GraphqlQueryUpdateOneResolverService ); const repository = dataSource.getRepository( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataMapItem.fields, - objectMetadataMap, + objectMetadataItemWithFieldMaps.fieldsById, + objectMetadataMaps, ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataMapItem, + objectMetadataItemWithFieldMaps, selectedFields, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataMapItem.nameSingular, + objectMetadataItemWithFieldMaps.nameSingular, ); - const data = formatData(args.data, objectMetadataMapItem); + const data = formatData(args.data, objectMetadataItemWithFieldMaps); const result = await queryBuilder .update(data) @@ -73,8 +77,8 @@ export class GraphqlQueryUpdateOneResolverService const updatedRecords = formatResult( nonFormattedUpdatedObjectRecords, - objectMetadataMapItem, - objectMetadataMap, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, ); if (updatedRecords.length === 0) { @@ -84,14 +88,14 @@ export class GraphqlQueryUpdateOneResolverService ); } - const updatedRecord = updatedRecords[0] as ObjectRecord; + const updatedRecord = updatedRecords[0] as T; const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { await processNestedRelationsHelper.processNestedRelations( - objectMetadataMap, - objectMetadataMapItem, + objectMetadataMaps, + objectMetadataItemWithFieldMaps, [updatedRecord], relations, QUERY_MAX_RECORDS, @@ -101,21 +105,21 @@ export class GraphqlQueryUpdateOneResolverService } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return typeORMObjectRecordsParser.processRecord({ + return typeORMObjectRecordsParser.processRecord({ objectRecord: updatedRecord, - objectName: objectMetadataMapItem.nameSingular, + objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, totalCount: 1, }); } - async validate( - args: UpdateOneResolverArgs>, + async validate( + args: UpdateOneResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { - assertMutationNotOnRemoteObject(options.objectMetadataMapItem); + assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); assertIsValidUuid(args.id); } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts index a96a52d99a3b..26beda3ef5f5 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts @@ -1,18 +1,19 @@ import { Injectable } from '@nestjs/common'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; @Injectable() export class ApiEventEmitterService { constructor(private readonly workspaceEventEmitter: WorkspaceEventEmitter) {} - public emitCreateEvents( + public emitCreateEvents( records: T[], authContext: AuthContext, objectMetadataItem: ObjectMetadataInterface, @@ -32,12 +33,12 @@ export class ApiEventEmitterService { ); } - public emitUpdateEvents( + public emitUpdateEvents( existingRecords: T[], records: T[], updatedFields: string[], authContext: AuthContext, - objectMetadataItem: ObjectMetadataInterface, + ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ): void { const mappedExistingRecords = existingRecords.reduce( (acc, { id, ...record }) => ({ @@ -48,7 +49,7 @@ export class ApiEventEmitterService { ); this.workspaceEventEmitter.emit( - `${objectMetadataItem.nameSingular}.${DatabaseEventAction.UPDATED}`, + `${ObjectMetadataItemWithFieldMaps.nameSingular}.${DatabaseEventAction.UPDATED}`, records.map((record) => { const before = this.removeGraphQLAndNestedProperties( mappedExistingRecords[record.id], @@ -58,13 +59,13 @@ export class ApiEventEmitterService { before, after, updatedFields, - objectMetadataItem, + ObjectMetadataItemWithFieldMaps, ); return { userId: authContext.user?.id, recordId: record.id, - objectMetadata: objectMetadataItem, + objectMetadata: ObjectMetadataItemWithFieldMaps, properties: { before, after, @@ -77,18 +78,18 @@ export class ApiEventEmitterService { ); } - public emitDeletedEvents( + public emitDeletedEvents( records: T[], authContext: AuthContext, - objectMetadataItem: ObjectMetadataInterface, + ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ): void { this.workspaceEventEmitter.emit( - `${objectMetadataItem.nameSingular}.${DatabaseEventAction.DELETED}`, + `${ObjectMetadataItemWithFieldMaps.nameSingular}.${DatabaseEventAction.DELETED}`, records.map((record) => { return { userId: authContext.user?.id, recordId: record.id, - objectMetadata: objectMetadataItem, + objectMetadata: ObjectMetadataItemWithFieldMaps, properties: { before: this.removeGraphQLAndNestedProperties(record), after: null, @@ -99,18 +100,18 @@ export class ApiEventEmitterService { ); } - public emitDestroyEvents( + public emitDestroyEvents( records: T[], authContext: AuthContext, - objectMetadataItem: ObjectMetadataInterface, + ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ): void { this.workspaceEventEmitter.emit( - `${objectMetadataItem.nameSingular}.${DatabaseEventAction.DESTROYED}`, + `${ObjectMetadataItemWithFieldMaps.nameSingular}.${DatabaseEventAction.DESTROYED}`, records.map((record) => { return { userId: authContext.user?.id, recordId: record.id, - objectMetadata: objectMetadataItem, + objectMetadata: ObjectMetadataItemWithFieldMaps, properties: { before: this.removeGraphQLAndNestedProperties(record), after: null, @@ -121,9 +122,7 @@ export class ApiEventEmitterService { ); } - private removeGraphQLAndNestedProperties( - record: ObjectRecord, - ) { + private removeGraphQLAndNestedProperties(record: T) { if (!record) { return {}; } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter.ts index c602aef7fcba..37678f2f6f34 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter.ts @@ -1,8 +1,8 @@ import { + ObjectRecordFilter, + ObjectRecordOrderBy, OrderByDirection, - RecordFilter, - RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { GraphqlQueryRunnerException, @@ -11,14 +11,14 @@ import { import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; export const computeCursorArgFilter = ( cursor: Record, - orderBy: RecordOrderBy, + orderBy: ObjectRecordOrderBy, fieldMetadataMap: FieldMetadataMap, isForwardPagination = true, -): RecordFilter[] => { +): ObjectRecordFilter[] => { const cursorKeys = Object.keys(cursor ?? {}); const cursorValues = Object.values(cursor ?? {}); @@ -69,7 +69,7 @@ export const computeCursorArgFilter = ( return { ...whereCondition, ...buildWhereCondition(key, value, fieldMetadataMap, operator), - } as RecordFilter; + } as ObjectRecordFilter; }); }; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts index bd27522ce1b2..f7c0a1fa96d9 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts @@ -1,7 +1,7 @@ import { Record as IRecord, RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util.ts index 00ef040204e0..08c0197d30c4 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util.ts @@ -2,12 +2,12 @@ import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; -import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; export const getObjectMetadataOrThrow = ( objectMetadataMap: Record, objectName: string, -): ObjectMetadataMapItem => { +): ObjectMetadataItemWithFieldMaps => { const objectMetadata = objectMetadataMap[objectName]; if (!objectMetadata) { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util.ts index d05bdcced27a..2e26962403cc 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util.ts @@ -1,7 +1,7 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; -import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { deduceRelationDirection, RelationDirection, @@ -9,7 +9,7 @@ import { export const getRelationObjectMetadata = ( fieldMetadata: FieldMetadataInterface, - objectMetadataMap: ObjectMetadataMap, + objectMetadataMaps: ObjectMetadataMaps, ) => { const relationMetadata = getRelationMetadata(fieldMetadata); @@ -20,8 +20,8 @@ export const getRelationObjectMetadata = ( const referencedObjectMetadata = relationDirection === RelationDirection.TO - ? objectMetadataMap[relationMetadata.fromObjectMetadataId] - : objectMetadataMap[relationMetadata.toObjectMetadataId]; + ? objectMetadataMaps.byId[relationMetadata.fromObjectMetadataId] + : objectMetadataMaps.byId[relationMetadata.toObjectMetadataId]; if (!referencedObjectMetadata) { throw new Error( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/interfaces/record.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface.ts similarity index 57% rename from packages/twenty-server/src/engine/api/graphql/workspace-query-builder/interfaces/record.interface.ts rename to packages/twenty-server/src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface.ts index 54d2c373b642..a93e752e2267 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/interfaces/record.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface.ts @@ -1,4 +1,4 @@ -export interface Record { +export interface ObjectRecord { id: string; [key: string]: any; createdAt: string; @@ -6,8 +6,8 @@ export interface Record { deletedAt: string | null; } -export type RecordFilter = { - [Property in keyof Record]: any; +export type ObjectRecordFilter = { + [Property in keyof ObjectRecord]: any; }; export enum OrderByDirection { @@ -17,11 +17,11 @@ export enum OrderByDirection { DescNullsLast = 'DescNullsLast', } -export type RecordOrderBy = Array<{ - [Property in keyof Record]?: OrderByDirection; +export type ObjectRecordOrderBy = Array<{ + [Property in keyof ObjectRecord]?: OrderByDirection; }>; -export interface RecordDuplicateCriteria { +export interface ObjectRecordDuplicateCriteria { objectName: string; columnNames: string[]; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts index bdde719563b4..2d98ffe719cf 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts @@ -1,6 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; +import { + ObjectRecord, + ObjectRecordFilter, +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs, @@ -10,13 +13,11 @@ import { ResolverArgs, ResolverArgsType, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; -import { - Record, - RecordFilter, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { hasPositionField } from 'src/engine/metadata-modules/object-metadata/utils/has-position-field.util'; +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; import { RecordPositionFactory } from './record-position.factory'; @@ -34,17 +35,13 @@ export class QueryRunnerArgsFactory { options: WorkspaceQueryRunnerOptions, resolverArgsType: ResolverArgsType, ) { - const fieldMetadataCollection = options.fieldMetadataCollection; + const fieldMetadataMap = + options.objectMetadataItemWithFieldMaps.fieldsByName; - const fieldMetadataMap = new Map( - fieldMetadataCollection.map((fieldMetadata) => [ - fieldMetadata.name, - fieldMetadata, - ]), + const shouldBackfillPosition = hasPositionField( + options.objectMetadataItemWithFieldMaps, ); - const shouldBackfillPosition = hasPositionField(options.objectMetadataItem); - switch (resolverArgsType) { case ResolverArgsType.CreateMany: return { @@ -98,9 +95,9 @@ export class QueryRunnerArgsFactory { } private async overrideDataByFieldMetadata( - data: Partial | undefined, + data: Partial | undefined, options: WorkspaceQueryRunnerOptions, - fieldMetadataMap: Map, + fieldMetadataMap: Record, argPositionBackfillInput: ArgPositionBackfillInput, ) { if (!data) { @@ -111,7 +108,7 @@ export class QueryRunnerArgsFactory { const createArgPromiseByArgKey = Object.entries(data).map( async ([key, value]) => { - const fieldMetadata = fieldMetadataMap.get(key); + const fieldMetadata = fieldMetadataMap[key]; if (!fieldMetadata) { return [key, await Promise.resolve(value)]; @@ -126,8 +123,9 @@ export class QueryRunnerArgsFactory { await this.recordPositionFactory.create( value, { - isCustom: options.objectMetadataItem.isCustom, - nameSingular: options.objectMetadataItem.nameSingular, + isCustom: options.objectMetadataItemWithFieldMaps.isCustom, + nameSingular: + options.objectMetadataItemWithFieldMaps.nameSingular, }, options.authContext.workspace.id, argPositionBackfillInput.argIndex, @@ -154,8 +152,9 @@ export class QueryRunnerArgsFactory { await this.recordPositionFactory.create( 'first', { - isCustom: options.objectMetadataItem.isCustom, - nameSingular: options.objectMetadataItem.nameSingular, + isCustom: options.objectMetadataItemWithFieldMaps.isCustom, + nameSingular: + options.objectMetadataItemWithFieldMaps.nameSingular, }, options.authContext.workspace.id, argPositionBackfillInput.argIndex, @@ -168,17 +167,17 @@ export class QueryRunnerArgsFactory { } private overrideFilterByFieldMetadata( - filter: RecordFilter | undefined, - fieldMetadataMap: Map, + filter: ObjectRecordFilter | undefined, + fieldMetadataMap: Record, ) { if (!filter) { return; } - const overrideFilter = (filterObject: RecordFilter) => { + const overrideFilter = (filterObject: ObjectRecordFilter) => { return Object.entries(filterObject).reduce((acc, [key, value]) => { if (key === 'and' || key === 'or') { - acc[key] = value.map((nestedFilter: RecordFilter) => + acc[key] = value.map((nestedFilter: ObjectRecordFilter) => overrideFilter(nestedFilter), ); } else if (key === 'not') { @@ -197,9 +196,9 @@ export class QueryRunnerArgsFactory { private transformValueByType( key: string, value: any, - fieldMetadataMap: Map, + fieldMetadataMap: FieldMetadataMap, ) { - const fieldMetadata = fieldMetadataMap.get(key); + const fieldMetadata = fieldMetadataMap[key]; if (!fieldMetadata) { return value; @@ -226,9 +225,9 @@ export class QueryRunnerArgsFactory { private async overrideValueByFieldMetadata( key: string, value: any, - fieldMetadataMap: Map, + fieldMetadataMap: FieldMetadataMap, ) { - const fieldMetadata = fieldMetadataMap.get(key); + const fieldMetadata = fieldMetadataMap[key]; if (!fieldMetadata) { return value; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/pg-graphql.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/pg-graphql.interface.ts index c015f34a3b86..f44f384d555a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/pg-graphql.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/pg-graphql.interface.ts @@ -1,4 +1,4 @@ -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; export interface PGGraphQLResponse { resolve: { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts index 2d53d8f3c95c..5f8268c7102c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts @@ -1,20 +1,12 @@ import { GraphQLResolveInfo } from 'graphql'; -import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; - import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { - ObjectMetadataMap, - ObjectMetadataMapItem, -} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; export interface WorkspaceQueryRunnerOptions { authContext: AuthContext; info: GraphQLResolveInfo; - objectMetadataItem: ObjectMetadataInterface; - fieldMetadataCollection: FieldMetadataInterface[]; // Legacy - objectMetadataCollection: ObjectMetadataInterface[]; // Legacy - objectMetadataMap: ObjectMetadataMap; - objectMetadataMapItem: ObjectMetadataMapItem; + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; + objectMetadataMaps: ObjectMetadataMaps; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts index 67c2f321f97d..df7d196711d1 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener.ts @@ -1,12 +1,12 @@ import { Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; +import { OnDatabaseEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-event.decorator'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { AnalyticsService } from 'src/engine/core-modules/analytics/analytics.service'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { TelemetryService } from 'src/engine/core-modules/telemetry/telemetry.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/workspace-event.type'; -import { OnDatabaseEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-event.decorator'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; @Injectable() export class TelemetryListener { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util.ts index a972782a6ad1..5d81a8308da2 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util.ts @@ -1,4 +1,4 @@ -import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { isDefined } from 'src/utils/is-defined'; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service.ts index f1cc8f6e93c8..bc9cf188c42f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import merge from 'lodash.merge'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceQueryHookKey } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts index c6d3303fb42f..bbe24cd7dbbb 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts @@ -35,7 +35,7 @@ export class CreateManyResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.createMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts index 3c2d9095e62c..ac52f4a44a72 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts @@ -35,7 +35,7 @@ export class CreateOneResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.createOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts index 191514f86309..c9a2f50e8ed5 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts @@ -35,7 +35,7 @@ export class DeleteManyResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.deleteMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts index 7cbd7bf3bddd..f1ba1a2589aa 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts @@ -35,7 +35,7 @@ export class DeleteOneResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.deleteOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts index 80da084e630c..a9babbeb57fe 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts @@ -35,7 +35,7 @@ export class DestroyManyResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.destroyMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts index c3dd4416918b..41acc81e55ed 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts @@ -35,7 +35,7 @@ export class DestroyOneResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphQLQueryRunnerService.destroyOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts index 154c2c88646e..8a9c77c985e4 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts @@ -35,7 +35,7 @@ export class FindDuplicatesResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.findDuplicates( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts index d46db50962b8..efde88503721 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts @@ -35,7 +35,7 @@ export class FindManyResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.findMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts index 7543d59eccd3..1d8b8cbaf2bb 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts @@ -35,7 +35,7 @@ export class FindOneResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.findOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts index 709dcc40d312..8e8fcb56895b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts @@ -35,7 +35,7 @@ export class RestoreManyResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.restoreMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts index 35520538b0f1..a15a2f8ba4a3 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts @@ -33,7 +33,7 @@ export class SearchResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.search(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts index af9f0935eeb4..1f1b39b35bc1 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts @@ -35,7 +35,7 @@ export class UpdateManyResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.updateMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts index b1198cf1a361..b6a2dedaa71e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts @@ -35,7 +35,7 @@ export class UpdateOneResolverFactory fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, objectMetadataMap: internalContext.objectMetadataMap, - objectMetadataMapItem: internalContext.objectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.updateOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/pg-graphql.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/pg-graphql.interface.ts index 4a3f3e13fa13..a2b9f526543d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/pg-graphql.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/pg-graphql.interface.ts @@ -1,4 +1,4 @@ -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; export interface PGGraphQLResponse { resolve: { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts index 69bc97777b10..8cac9ac6bd75 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts @@ -4,7 +4,7 @@ import { Record, RecordFilter, RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories'; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts index a652e3065c81..e7c1b0ce6706 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts @@ -98,7 +98,7 @@ export class WorkspaceResolverFactory { fieldMetadataCollection: objectMetadata.fields, objectMetadataCollection, objectMetadataMap, - objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular], + ObjectMetadataItemWithFieldMaps: objectMetadataMap[objectMetadata.nameSingular], }); } @@ -123,7 +123,7 @@ export class WorkspaceResolverFactory { fieldMetadataCollection: objectMetadata.fields, objectMetadataCollection, objectMetadataMap, - objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular], + ObjectMetadataItemWithFieldMaps: objectMetadataMap[objectMetadata.nameSingular], }); } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts index d0ab66983309..10edf38e6833 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts @@ -3,8 +3,8 @@ import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metad import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { + ObjectMetadataItemWithFieldMaps, ObjectMetadataMap, - ObjectMetadataMapItem, } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; export interface WorkspaceSchemaBuilderContext { @@ -13,5 +13,5 @@ export interface WorkspaceSchemaBuilderContext { objectMetadataCollection: ObjectMetadataInterface[]; objectMetadataItem: ObjectMetadataInterface; objectMetadataMap: ObjectMetadataMap; - objectMetadataMapItem: ObjectMetadataMapItem; + ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; } diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts index 851282b6727b..9b1317a074e3 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts @@ -1,6 +1,6 @@ import { BadRequestException } from '@nestjs/common'; -import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; diff --git a/packages/twenty-server/src/engine/api/rest/input-factories/__tests__/order-by-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/input-factories/__tests__/order-by-input.factory.spec.ts index 9f6923c23802..d61fc5bcb542 100644 --- a/packages/twenty-server/src/engine/api/rest/input-factories/__tests__/order-by-input.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/input-factories/__tests__/order-by-input.factory.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory'; diff --git a/packages/twenty-server/src/engine/api/rest/input-factories/order-by-input.factory.ts b/packages/twenty-server/src/engine/api/rest/input-factories/order-by-input.factory.ts index 832de44c52f9..54ebfa489af3 100644 --- a/packages/twenty-server/src/engine/api/rest/input-factories/order-by-input.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/input-factories/order-by-input.factory.ts @@ -5,7 +5,7 @@ import { Request } from 'express'; import { OrderByDirection, RecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { checkArrayFields } from 'src/engine/api/rest/core/query-builder/utils/check-order-by.utils'; diff --git a/packages/twenty-server/src/engine/core-modules/duplicate/constants/duplicate-criteria.constants.ts b/packages/twenty-server/src/engine/core-modules/duplicate/constants/duplicate-criteria.constants.ts index eac026e84e0e..5eac783001b0 100644 --- a/packages/twenty-server/src/engine/core-modules/duplicate/constants/duplicate-criteria.constants.ts +++ b/packages/twenty-server/src/engine/core-modules/duplicate/constants/duplicate-criteria.constants.ts @@ -1,4 +1,4 @@ -import { RecordDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { RecordDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; /** * objectName: directly reference the name of the object from the metadata tables. diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record.base.event.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record.base.event.ts index 0295724e88f5..c79e53fb7342 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record.base.event.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record.base.event.ts @@ -1,9 +1,9 @@ -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; export class ObjectRecordBaseEvent { recordId: string; userId?: string; workspaceMemberId?: string; - objectMetadata: ObjectMetadataInterface; + objectMetadata: ObjectMetadataItemWithFieldMaps; properties: any; } diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util.ts index 5d77c207623c..b8cfeedf3b7b 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util.ts @@ -1,6 +1,6 @@ import deepEqual from 'deep-equal'; -import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts index 363c4059ef5e..3fe17e3aff3c 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts @@ -1,23 +1,19 @@ import deepEqual from 'deep-equal'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; export const objectRecordChangedValues = ( oldRecord: Partial, newRecord: Partial, updatedKeys: string[] | undefined, - objectMetadata: ObjectMetadataInterface, + ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ) => { - const fieldsByKey = new Map( - objectMetadata.fields.map((field) => [field.name, field]), - ); - return Object.keys(newRecord).reduce( (acc, key) => { - const field = fieldsByKey.get(key); + const field = ObjectMetadataItemWithFieldMaps.fieldsByName[key]; const oldRecordValue = oldRecord[key]; const newRecordValue = newRecord[key]; diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts index 6d5ab0751d5a..0f3198ce624b 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts @@ -1,4 +1,4 @@ -import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/add-default-conjunction.utils'; import { FilterComparators } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils'; diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts index 26679a18fc34..6924deae86ed 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts @@ -1,6 +1,6 @@ import { OpenAPIV3_1 } from 'openapi-types'; -import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/add-default-conjunction.utils'; import { FilterComparators } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils'; diff --git a/packages/twenty-server/src/engine/metadata-modules/types/field-metadata-map.ts b/packages/twenty-server/src/engine/metadata-modules/types/field-metadata-map.ts new file mode 100644 index 000000000000..f3dffb8c6e8a --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/types/field-metadata-map.ts @@ -0,0 +1,3 @@ +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; + +export type FieldMetadataMap = Record; diff --git a/packages/twenty-server/src/engine/metadata-modules/types/object-metadata-item-with-field-maps.ts b/packages/twenty-server/src/engine/metadata-modules/types/object-metadata-item-with-field-maps.ts new file mode 100644 index 000000000000..b46cbe3cc2cc --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/types/object-metadata-item-with-field-maps.ts @@ -0,0 +1,8 @@ +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; + +export type ObjectMetadataItemWithFieldMaps = ObjectMetadataInterface & { + fieldsById: FieldMetadataMap; + fieldsByName: FieldMetadataMap; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/types/object-metadata-maps.ts b/packages/twenty-server/src/engine/metadata-modules/types/object-metadata-maps.ts new file mode 100644 index 000000000000..a10603c36f6b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/types/object-metadata-maps.ts @@ -0,0 +1,7 @@ +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; + +export type ObjectMetadataMaps = { + byId: Record; + byNameSingular: Record; + byNamePlural: Record; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/generate-object-metadata-map.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/generate-object-metadata-map.util.ts index 3455efa5ff13..abaea68e1007 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/generate-object-metadata-map.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/generate-object-metadata-map.util.ts @@ -1,36 +1,39 @@ -import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -export type FieldMetadataMap = Record; +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; -export type ObjectMetadataMapItem = Omit & { - fields: FieldMetadataMap; -}; - -export type ObjectMetadataMap = Record; - -export const generateObjectMetadataMap = ( +export const generateObjectMetadataMaps = ( objectMetadataCollection: ObjectMetadataInterface[], -): ObjectMetadataMap => { - const objectMetadataMap: ObjectMetadataMap = {}; +): ObjectMetadataMaps => { + const objectMetadataMaps: ObjectMetadataMaps = { + byId: {}, + byNameSingular: {}, + byNamePlural: {}, + }; for (const objectMetadata of objectMetadataCollection) { - const fieldsMap: FieldMetadataMap = {}; + const fieldsByIdMap: FieldMetadataMap = {}; + const fieldsByNameMap: FieldMetadataMap = {}; for (const fieldMetadata of objectMetadata.fields) { - fieldsMap[fieldMetadata.name] = fieldMetadata; - fieldsMap[fieldMetadata.id] = fieldMetadata; + fieldsByNameMap[fieldMetadata.name] = fieldMetadata; + fieldsByIdMap[fieldMetadata.id] = fieldMetadata; } - const processedObjectMetadata: ObjectMetadataMapItem = { + const processedObjectMetadata: ObjectMetadataItemWithFieldMaps = { ...objectMetadata, - fields: fieldsMap, + fieldsById: fieldsByIdMap, + fieldsByName: fieldsByNameMap, }; - objectMetadataMap[objectMetadata.id] = processedObjectMetadata; - objectMetadataMap[objectMetadata.nameSingular] = processedObjectMetadata; - objectMetadataMap[objectMetadata.namePlural] = processedObjectMetadata; + objectMetadataMaps.byId[objectMetadata.id] = processedObjectMetadata; + objectMetadataMaps.byNameSingular[objectMetadata.nameSingular] = + processedObjectMetadata; + objectMetadataMaps.byNamePlural[objectMetadata.namePlural] = + processedObjectMetadata; } - return objectMetadataMap; + return objectMetadataMaps; }; diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema.factory.ts index eeb697e8867e..9a1b32500a88 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema.factory.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { EntitySchema } from 'typeorm'; import { + ObjectMetadataItemWithFieldMaps, ObjectMetadataMap, - ObjectMetadataMapItem, } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; import { EntitySchemaColumnFactory } from 'src/engine/twenty-orm/factories/entity-schema-column.factory'; import { EntitySchemaRelationFactory } from 'src/engine/twenty-orm/factories/entity-schema-relation.factory'; @@ -21,7 +21,7 @@ export class EntitySchemaFactory { async create( workspaceId: string, metadataVersion: number, - objectMetadata: ObjectMetadataMapItem, + objectMetadata: ObjectMetadataItemWithFieldMaps, objectMetadataMap: ObjectMetadataMap, ): Promise { const columns = this.entitySchemaColumnFactory.create( diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts index bb4327cc8b1b..027d8158749b 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts @@ -22,7 +22,7 @@ import { UpsertOptions } from 'typeorm/repository/UpsertOptions'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; -import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @@ -666,7 +666,7 @@ export class WorkspaceRepository< async formatResult( data: T, - objectMetadata?: ObjectMetadataMapItem, + objectMetadata?: ObjectMetadataItemWithFieldMaps, ): Promise { objectMetadata ??= await this.getObjectMetadataFromTarget(); diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/determine-relation-details.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/determine-relation-details.util.ts index 783995afb0a9..3e8dd64e1a0e 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/determine-relation-details.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/determine-relation-details.util.ts @@ -3,7 +3,7 @@ import { RelationType } from 'typeorm/metadata/types/RelationTypes'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; -import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { computeRelationType } from 'src/engine/twenty-orm/utils/compute-relation-type.util'; interface RelationDetails { @@ -16,22 +16,25 @@ interface RelationDetails { export async function determineRelationDetails( fieldMetadata: FieldMetadataInterface, relationMetadata: RelationMetadataEntity, - objectMetadataMap: ObjectMetadataMap, + objectMetadataMaps: ObjectMetadataMaps, ): Promise { const relationType = computeRelationType(fieldMetadata, relationMetadata); - const fromObjectMetadata = objectMetadataMap[fieldMetadata.objectMetadataId]; - let toObjectMetadata = objectMetadataMap[relationMetadata.toObjectMetadataId]; + const fromObjectMetadata = + objectMetadataMaps.byId[fieldMetadata.objectMetadataId]; + let toObjectMetadata = + objectMetadataMaps.byId[relationMetadata.toObjectMetadataId]; // RelationMetadata always store the relation from the perspective of the `from` object, MANY_TO_ONE relations are not stored yet if (relationType === 'many-to-one') { - toObjectMetadata = objectMetadataMap[relationMetadata.fromObjectMetadataId]; + toObjectMetadata = + objectMetadataMaps.byId[relationMetadata.fromObjectMetadataId]; } if (!fromObjectMetadata || !toObjectMetadata) { throw new Error('Object metadata not found'); } - const toFieldMetadata = Object.values(toObjectMetadata.fields).find( + const toFieldMetadata = Object.values(toObjectMetadata.fieldsById).find( (field) => relationType === 'many-to-one' ? field.id === relationMetadata.fromFieldMetadataId diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts index cd3851393678..780f3a0926d1 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts @@ -3,26 +3,28 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { capitalize } from 'src/utils/capitalize'; export function formatData( data: T, - objectMetadata: ObjectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ): T { if (!data) { return data; } if (Array.isArray(data)) { - return data.map((item) => formatData(item, objectMetadata)) as T; + return data.map((item) => + formatData(item, ObjectMetadataItemWithFieldMaps), + ) as T; } const newData: Record = {}; for (const [key, value] of Object.entries(data)) { - const fieldMetadata = objectMetadata.fields[key]; + const fieldMetadata = ObjectMetadataItemWithFieldMaps.fieldsByName[key]; if (!fieldMetadata) { throw new Error( diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts index 0780db58831e..2cba94d43678 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts @@ -6,18 +6,16 @@ import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-meta import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; -import { - ObjectMetadataMap, - ObjectMetadataMapItem, -} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { computeRelationType } from 'src/engine/twenty-orm/utils/compute-relation-type.util'; import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; export function formatResult( data: T, - objectMetadata: ObjectMetadataMapItem, - objectMetadataMap: ObjectMetadataMap, + ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: ObjectMetadataMaps, ): T { if (!data) { return data; @@ -25,7 +23,7 @@ export function formatResult( if (Array.isArray(data)) { return data.map((item) => - formatResult(item, objectMetadata, objectMetadataMap), + formatResult(item, ObjectMetadataItemWithFieldMaps, objectMetadataMaps), ) as T; } @@ -33,12 +31,13 @@ export function formatResult( return data; } - if (!objectMetadata) { + if (!ObjectMetadataItemWithFieldMaps) { throw new Error('Object metadata is missing'); } - const compositeFieldMetadataCollection = - getCompositeFieldMetadataCollection(objectMetadata); + const compositeFieldMetadataCollection = getCompositeFieldMetadataCollection( + ObjectMetadataItemWithFieldMaps, + ); const compositeFieldMetadataMap = new Map( compositeFieldMetadataCollection.flatMap((fieldMetadata) => { @@ -58,7 +57,7 @@ export function formatResult( ); const relationMetadataMap = new Map( - Object.values(objectMetadata.fields) + Object.values(ObjectMetadataItemWithFieldMaps.fieldsById) .filter(({ type }) => isRelationFieldMetadataType(type)) .map((fieldMetadata) => [ fieldMetadata.name, @@ -75,6 +74,8 @@ export function formatResult( ]), ); const newData: object = {}; + const objectMetadaItemFieldsByName = + objectMetadataMaps.byId[ObjectMetadataItemWithFieldMaps.id]?.fieldsByName; for (const [key, value] of Object.entries(data)) { const compositePropertyArgs = compositeFieldMetadataMap.get(key); @@ -83,11 +84,15 @@ export function formatResult( if (!compositePropertyArgs && !relationMetadata) { if (isPlainObject(value)) { - newData[key] = formatResult(value, objectMetadata, objectMetadataMap); - } else if (objectMetadata.fields[key]) { + newData[key] = formatResult( + value, + ObjectMetadataItemWithFieldMaps, + objectMetadataMaps, + ); + } else if (objectMetadaItemFieldsByName[key]) { newData[key] = formatFieldMetadataValue( value, - objectMetadata.fields[key], + objectMetadaItemFieldsByName[key], ); } else { newData[key] = value; @@ -98,10 +103,10 @@ export function formatResult( if (relationMetadata) { const toObjectMetadata = - objectMetadataMap[relationMetadata.toObjectMetadataId]; + objectMetadataMaps.byId[relationMetadata.toObjectMetadataId]; const fromObjectMetadata = - objectMetadataMap[relationMetadata.fromObjectMetadataId]; + objectMetadataMaps.byId[relationMetadata.fromObjectMetadataId]; if (!toObjectMetadata) { throw new Error( @@ -118,7 +123,7 @@ export function formatResult( newData[key] = formatResult( value, relationType === 'one-to-many' ? toObjectMetadata : fromObjectMetadata, - objectMetadataMap, + objectMetadataMaps, ); continue; } diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/get-composite-field-metadata-collection.ts b/packages/twenty-server/src/engine/twenty-orm/utils/get-composite-field-metadata-collection.ts index 88ac2820c1b1..de829cb19a3a 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/get-composite-field-metadata-collection.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/get-composite-field-metadata-collection.ts @@ -1,12 +1,14 @@ import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; export function getCompositeFieldMetadataCollection( - objectMetadata: ObjectMetadataMapItem, + ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ) { const compositeFieldMetadataCollection = Object.values( - objectMetadata.fields, - ).filter((fieldMetadata) => isCompositeFieldMetadataType(fieldMetadata.type)); + ObjectMetadataItemWithFieldMaps.fieldsById, + ).filter((fieldMetadataItem) => + isCompositeFieldMetadataType(fieldMetadataItem.type), + ); return compositeFieldMetadataCollection; } diff --git a/packages/twenty-server/src/engine/workspace-cache-storage/workspace-cache-storage.service.ts b/packages/twenty-server/src/engine/workspace-cache-storage/workspace-cache-storage.service.ts index 9d3da728566f..bd35c60cbfba 100644 --- a/packages/twenty-server/src/engine/workspace-cache-storage/workspace-cache-storage.service.ts +++ b/packages/twenty-server/src/engine/workspace-cache-storage/workspace-cache-storage.service.ts @@ -5,14 +5,14 @@ import { EntitySchemaOptions } from 'typeorm'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; export enum WorkspaceCacheKeys { GraphQLTypeDefs = 'graphql:type-defs', GraphQLUsedScalarNames = 'graphql:used-scalar-names', GraphQLOperations = 'graphql:operations', ORMEntitySchemas = 'orm:entity-schemas', - MetadataObjectMetadataMap = 'metadata:object-metadata-map', + MetadataObjectMetadataMaps = 'metadata:object-metadata-maps', MetadataObjectMetadataOngoingCachingLock = 'metadata:object-metadata-ongoing-caching-lock', MetadataVersion = 'metadata:workspace-metadata-version', } @@ -91,20 +91,20 @@ export class WorkspaceCacheStorageService { setObjectMetadataMap( workspaceId: string, metadataVersion: number, - objectMetadataMap: ObjectMetadataMap, + objectMetadataMap: ObjectMetadataMaps, ) { - return this.cacheStorageService.set( - `${WorkspaceCacheKeys.MetadataObjectMetadataMap}:${workspaceId}:${metadataVersion}`, + return this.cacheStorageService.set( + `${WorkspaceCacheKeys.MetadataObjectMetadataMaps}:${workspaceId}:${metadataVersion}`, objectMetadataMap, ); } - getObjectMetadataMap( + getObjectMetadataMaps( workspaceId: string, metadataVersion: number, - ): Promise { - return this.cacheStorageService.get( - `${WorkspaceCacheKeys.MetadataObjectMetadataMap}:${workspaceId}:${metadataVersion}`, + ): Promise { + return this.cacheStorageService.get( + `${WorkspaceCacheKeys.MetadataObjectMetadataMaps}:${workspaceId}:${metadataVersion}`, ); } @@ -150,7 +150,7 @@ export class WorkspaceCacheStorageService { async flush(workspaceId: string, metadataVersion: number): Promise { await this.cacheStorageService.del( - `${WorkspaceCacheKeys.MetadataObjectMetadataMap}:${workspaceId}:${metadataVersion}`, + `${WorkspaceCacheKeys.MetadataObjectMetadataMaps}:${workspaceId}:${metadataVersion}`, ); await this.cacheStorageService.del( `${WorkspaceCacheKeys.MetadataVersion}:${workspaceId}:${metadataVersion}`, diff --git a/packages/twenty-server/src/modules/timeline/repositiories/timeline-activity.repository.ts b/packages/twenty-server/src/modules/timeline/repositiories/timeline-activity.repository.ts index ec858797e536..ff8491f29d17 100644 --- a/packages/twenty-server/src/modules/timeline/repositiories/timeline-activity.repository.ts +++ b/packages/twenty-server/src/modules/timeline/repositiories/timeline-activity.repository.ts @@ -2,10 +2,10 @@ import { Injectable } from '@nestjs/common'; import { EntityManager } from 'typeorm'; -import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; -import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { objectRecordDiffMerge } from 'src/engine/core-modules/event-emitter/utils/object-record-diff-merge'; +import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; @Injectable() export class TimelineActivityRepository { From 6ca4628a0dbaf13e31a9e13403d8552650774d25 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 16:50:23 +0100 Subject: [PATCH 20/30] Fix --- ...ct-records-to-graphql-connection.helper.ts | 28 +++++++++---------- .../utils/cursors.util.ts | 10 +++---- .../get-object-metadata-or-throw.util.ts | 21 -------------- .../interfaces/pg-graphql.interface.ts | 15 ---------- .../utils/with-soft-deleted.util.ts | 4 +-- .../workspace-query-hook.service.ts | 6 ++-- .../factories/create-many-resolver.factory.ts | 8 ++---- .../factories/create-one-resolver.factory.ts | 8 ++---- .../factories/delete-many-resolver.factory.ts | 8 ++---- .../factories/delete-one-resolver.factory.ts | 8 ++---- .../destroy-many-resolver.factory.ts | 8 ++---- .../factories/destroy-one-resolver.factory.ts | 8 ++---- .../find-duplicates-resolver.factory.ts | 8 ++---- .../factories/find-many-resolver.factory.ts | 8 ++---- .../factories/find-one-resolver.factory.ts | 8 ++---- .../restore-many-resolver.factory.ts | 8 ++---- .../factories/search-resolver-factory.ts | 8 ++---- .../factories/update-many-resolver.factory.ts | 8 ++---- .../factories/update-one-resolver.factory.ts | 8 ++---- .../interfaces/pg-graphql.interface.ts | 14 ---------- .../workspace-resolvers-builder.interface.ts | 22 +++++++-------- .../workspace-resolver.factory.ts | 23 +++++---------- ...kspace-schema-builder-context.interface.ts | 16 +++-------- .../workspace-graphql-schema.factory.ts | 4 +-- .../api/graphql/workspace-schema.factory.ts | 13 ++++----- .../utils/check-order-by.utils.ts | 4 +-- .../object-record-changed-properties.util.ts | 6 ++-- .../utils/object-record-changed-values.ts | 6 ++-- .../relation-metadata.service.ts | 16 +++++------ ... => generate-object-metadata-maps.util.ts} | 0 .../workspace-metadata-cache.service.ts | 12 ++++---- .../factories/entity-schema-column.factory.ts | 6 ++-- .../entity-schema-relation.factory.ts | 14 ++++------ .../factories/entity-schema.factory.ts | 16 +++++------ .../factories/workspace-datasource.factory.ts | 23 +++++++-------- .../workspace-internal-context.interface.ts | 4 +-- .../repository/workspace.repository.ts | 12 ++++---- .../workspace-cache-storage.service.ts | 6 ++-- .../timeline-activity.repository.ts | 10 +++---- 39 files changed, 162 insertions(+), 253 deletions(-) delete mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/pg-graphql.interface.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/pg-graphql.interface.ts rename packages/twenty-server/src/engine/metadata-modules/utils/{generate-object-metadata-map.util.ts => generate-object-metadata-maps.util.ts} (100%) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index 93fea1d8200a..c4ef79efcfc0 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -1,6 +1,6 @@ import { - Record as IRecord, - RecordOrderBy, + ObjectRecord, + ObjectRecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; @@ -15,19 +15,19 @@ import { getRelationObjectMetadata } from 'src/engine/api/graphql/graphql-query- import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; import { isPlainObject } from 'src/utils/is-plain-object'; export class ObjectRecordsToGraphqlConnectionHelper { - private objectMetadataMap: ObjectMetadataMap; + private objectMetadataMaps: ObjectMetadataMaps; - constructor(objectMetadataMap: ObjectMetadataMap) { - this.objectMetadataMap = objectMetadataMap; + constructor(objectMetadataMaps: ObjectMetadataMaps) { + this.objectMetadataMaps = objectMetadataMaps; } - public createConnection({ + public createConnection({ objectRecords, objectName, take, @@ -37,15 +37,15 @@ export class ObjectRecordsToGraphqlConnectionHelper { hasPreviousPage, depth = 0, }: { - objectRecords: ObjectRecord[]; + objectRecords: T[]; objectName: string; take: number; totalCount: number; - order?: RecordOrderBy; + order?: ObjectRecordOrderBy; hasNextPage: boolean; hasPreviousPage: boolean; depth?: number; - }): IConnection { + }): IConnection { const edges = (objectRecords ?? []).map((objectRecord) => ({ node: this.processRecord({ objectRecord, @@ -82,7 +82,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { objectName: string; take: number; totalCount: number; - order?: RecordOrderBy; + order?: ObjectRecordOrderBy; depth?: number; }): T { if (depth >= CONNECTION_MAX_DEPTH) { @@ -92,7 +92,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { ); } - const objectMetadata = this.objectMetadataMap[objectName]; + const objectMetadata = this.objectMetadataMaps.byNameSingular[objectName]; if (!objectMetadata) { throw new GraphqlQueryRunnerException( @@ -117,7 +117,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { objectRecords: value, objectName: getRelationObjectMetadata( fieldMetadata, - this.objectMetadataMap, + this.objectMetadataMaps, ).nameSingular, take, totalCount: value.length, @@ -131,7 +131,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { objectRecord: value, objectName: getRelationObjectMetadata( fieldMetadata, - this.objectMetadataMap, + this.objectMetadataMaps, ).nameSingular, take, totalCount, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts index f7c0a1fa96d9..8ae1486c08ff 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts @@ -1,6 +1,6 @@ import { - Record as IRecord, - RecordOrderBy, + ObjectRecord, + ObjectRecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -24,9 +24,9 @@ export const decodeCursor = (cursor: string): CursorData => { } }; -export const encodeCursor = ( - objectRecord: ObjectRecord, - order: RecordOrderBy | undefined, +export const encodeCursor = ( + objectRecord: T, + order: ObjectRecordOrderBy | undefined, ): string => { const orderByValues: Record = {}; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util.ts deleted file mode 100644 index 08c0197d30c4..000000000000 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - GraphqlQueryRunnerException, - GraphqlQueryRunnerExceptionCode, -} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; - -export const getObjectMetadataOrThrow = ( - objectMetadataMap: Record, - objectName: string, -): ObjectMetadataItemWithFieldMaps => { - const objectMetadata = objectMetadataMap[objectName]; - - if (!objectMetadata) { - throw new GraphqlQueryRunnerException( - `Object metadata not found for ${objectName}`, - GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND, - ); - } - - return objectMetadata; -}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/pg-graphql.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/pg-graphql.interface.ts deleted file mode 100644 index f44f384d555a..000000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/pg-graphql.interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; - -export interface PGGraphQLResponse { - resolve: { - data: Data; - errors: any[]; - }; -} - -export type PGGraphQLResult = [PGGraphQLResponse]; - -export interface PGGraphQLMutation { - affectedRows: number; - records: Record[]; -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util.ts index 5d81a8308da2..0ed2d7391821 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util.ts @@ -1,8 +1,8 @@ -import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { isDefined } from 'src/utils/is-defined'; -export const withSoftDeleted = ( +export const withSoftDeleted = ( filter: T | undefined | null, ): boolean => { if (!isDefined(filter)) { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service.ts index bc9cf188c42f..5d180984699f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import merge from 'lodash.merge'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceQueryHookKey } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; @@ -53,13 +53,13 @@ export class WorkspaceQueryHookService { public async executePostQueryHooks< T extends WorkspaceResolverBuilderMethodNames, - Record extends IRecord = IRecord, + U extends ObjectRecord = ObjectRecord, >( authContext: AuthContext, // TODO: We should allow wildcard for object name objectName: string, methodName: T, - payload: Record[], + payload: U[], ): Promise { const key: WorkspaceQueryHookKey = `${objectName}.${methodName}`; const postHookInstances = diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts index bbe24cd7dbbb..84e16ce844c4 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts @@ -30,12 +30,10 @@ export class CreateManyResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.createMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts index ac52f4a44a72..650b0bda1f4b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts @@ -30,12 +30,10 @@ export class CreateOneResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.createOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts index c9a2f50e8ed5..3be04fdd7fdb 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts @@ -30,12 +30,10 @@ export class DeleteManyResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.deleteMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts index f1ba1a2589aa..596a1d4db260 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts @@ -30,12 +30,10 @@ export class DeleteOneResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.deleteOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts index a9babbeb57fe..3eacd530b062 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts @@ -30,12 +30,10 @@ export class DestroyManyResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.destroyMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts index 41acc81e55ed..7dbf01a8307f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts @@ -30,12 +30,10 @@ export class DestroyOneResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphQLQueryRunnerService.destroyOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts index 8a9c77c985e4..9fe6fc822135 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts @@ -30,12 +30,10 @@ export class FindDuplicatesResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.findDuplicates( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts index efde88503721..064927d211e5 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts @@ -30,12 +30,10 @@ export class FindManyResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.findMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts index 1d8b8cbaf2bb..a6ce32c33b63 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts @@ -30,12 +30,10 @@ export class FindOneResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.findOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts index 8e8fcb56895b..dbbe7eb31b16 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts @@ -30,12 +30,10 @@ export class RestoreManyResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.restoreMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts index a15a2f8ba4a3..d2510862cf5a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts @@ -28,12 +28,10 @@ export class SearchResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.search(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts index 1f1b39b35bc1..dce0a58a2c4d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts @@ -30,12 +30,10 @@ export class UpdateManyResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.updateMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts index b6a2dedaa71e..258b51b4a5aa 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts @@ -30,12 +30,10 @@ export class UpdateOneResolverFactory try { const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - objectMetadataMap: internalContext.objectMetadataMap, - ObjectMetadataItemWithFieldMaps: internalContext.ObjectMetadataItemWithFieldMaps, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, }; return await this.graphqlQueryRunnerService.updateOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/pg-graphql.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/pg-graphql.interface.ts deleted file mode 100644 index a2b9f526543d..000000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/pg-graphql.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; - -export interface PGGraphQLResponse { - resolve: { - data: Data; - }; -} - -export type PGGraphQLResult = [PGGraphQLResponse]; - -export interface PGGraphQLMutation { - affectedRows: number; - records: Record[]; -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts index 8cac9ac6bd75..ea40e9c8cfcf 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts @@ -1,9 +1,9 @@ import { GraphQLFieldResolver } from 'graphql'; import { - Record, - RecordFilter, - RecordOrderBy, + ObjectRecord, + ObjectRecordFilter, + ObjectRecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories'; @@ -26,8 +26,8 @@ export enum ResolverArgsType { } export interface FindManyResolverArgs< - Filter extends RecordFilter = RecordFilter, - OrderBy extends RecordOrderBy = RecordOrderBy, + Filter extends ObjectRecordFilter = ObjectRecordFilter, + OrderBy extends ObjectRecordOrderBy = ObjectRecordOrderBy, > { first?: number; last?: number; @@ -42,14 +42,14 @@ export interface FindOneResolverArgs { } export interface FindDuplicatesResolverArgs< - Data extends Partial = Partial, + Data extends Partial = Partial, > { ids?: string[]; data?: Data[]; } export interface SearchResolverArgs< - Filter extends RecordFilter = RecordFilter, + Filter extends ObjectRecordFilter = ObjectRecordFilter, > { searchInput?: string; filter?: Filter; @@ -57,28 +57,28 @@ export interface SearchResolverArgs< } export interface CreateOneResolverArgs< - Data extends Partial = Partial, + Data extends Partial = Partial, > { data: Data; upsert?: boolean; } export interface CreateManyResolverArgs< - Data extends Partial = Partial, + Data extends Partial = Partial, > { data: Data[]; upsert?: boolean; } export interface UpdateOneResolverArgs< - Data extends Partial = Partial, + Data extends Partial = Partial, > { id: string; data: Data; } export interface UpdateManyResolverArgs< - Data extends Partial = Partial, + Data extends Partial = Partial, Filter = any, > { filter: Filter; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts index e7c1b0ce6706..9d5370546cc2 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts @@ -2,8 +2,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { IResolvers } from '@graphql-tools/utils'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; - import { DeleteManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory'; import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory'; import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory'; @@ -11,7 +9,7 @@ import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-res import { SearchResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory'; import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { getResolverName } from 'src/engine/utils/get-resolver-name.util'; import { CreateManyResolverFactory } from './factories/create-many-resolver.factory'; @@ -49,8 +47,7 @@ export class WorkspaceResolverFactory { async create( authContext: AuthContext, - objectMetadataCollection: ObjectMetadataInterface[], - objectMetadataMap: ObjectMetadataMap, + objectMetadataMaps: ObjectMetadataMaps, workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods, ): Promise { const factories = new Map< @@ -76,7 +73,7 @@ export class WorkspaceResolverFactory { Mutation: {}, }; - for (const objectMetadata of objectMetadataCollection) { + for (const objectMetadata of Object.values(objectMetadataMaps.byId)) { // Generate query resolvers for (const methodName of workspaceResolverBuilderMethods.queries) { const resolverName = getResolverName(objectMetadata, methodName); @@ -94,11 +91,8 @@ export class WorkspaceResolverFactory { resolvers.Query[resolverName] = resolverFactory.create({ authContext, - objectMetadataItem: objectMetadata, - fieldMetadataCollection: objectMetadata.fields, - objectMetadataCollection, - objectMetadataMap, - ObjectMetadataItemWithFieldMaps: objectMetadataMap[objectMetadata.nameSingular], + objectMetadataMaps, + objectMetadataItemWithFieldMaps: objectMetadata, }); } @@ -119,11 +113,8 @@ export class WorkspaceResolverFactory { resolvers.Mutation[resolverName] = resolverFactory.create({ authContext, - objectMetadataItem: objectMetadata, - fieldMetadataCollection: objectMetadata.fields, - objectMetadataCollection, - objectMetadataMap, - ObjectMetadataItemWithFieldMaps: objectMetadataMap[objectMetadata.nameSingular], + objectMetadataMaps, + objectMetadataItemWithFieldMaps: objectMetadata, }); } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts index 10edf38e6833..e95e27b13d4a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts @@ -1,17 +1,9 @@ -import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; - import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { - ObjectMetadataItemWithFieldMaps, - ObjectMetadataMap, -} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; export interface WorkspaceSchemaBuilderContext { authContext: AuthContext; - fieldMetadataCollection: FieldMetadataInterface[]; - objectMetadataCollection: ObjectMetadataInterface[]; - objectMetadataItem: ObjectMetadataInterface; - objectMetadataMap: ObjectMetadataMap; - ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; + objectMetadataMaps: ObjectMetadataMaps; + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory.ts index 4f905032f8b7..d39f1c59e675 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory.ts @@ -7,10 +7,10 @@ import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metad import { TypeDefinitionsGenerator } from './type-definitions.generator'; -import { WorkspaceBuildSchemaOptions } from './interfaces/workspace-build-schema-optionts.interface'; -import { QueryTypeFactory } from './factories/query-type.factory'; import { MutationTypeFactory } from './factories/mutation-type.factory'; import { OrphanedTypesFactory } from './factories/orphaned-types.factory'; +import { QueryTypeFactory } from './factories/query-type.factory'; +import { WorkspaceBuildSchemaOptions } from './interfaces/workspace-build-schema-optionts.interface'; @Injectable() export class WorkspaceGraphQLSchemaFactory { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index 558ece2b4768..c80bb0c2e6a3 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -56,13 +56,13 @@ export class WorkspaceSchemaFactory { ); } - const objectMetadataMap = - await this.workspaceCacheStorageService.getObjectMetadataMap( + const objectMetadataMaps = + await this.workspaceCacheStorageService.getObjectMetadataMaps( authContext.workspace.id, currentCacheVersion, ); - if (!objectMetadataMap) { + if (!objectMetadataMaps) { await this.workspaceMetadataCacheService.recomputeMetadataCache({ workspaceId: authContext.workspace.id, }); @@ -72,10 +72,10 @@ export class WorkspaceSchemaFactory { ); } - const objectMetadataCollection = Object.values(objectMetadataMap).map( + const objectMetadataCollection = Object.values(objectMetadataMaps.byId).map( (objectMetadataItem) => ({ ...objectMetadataItem, - fields: Object.values(objectMetadataItem.fields), + fields: objectMetadataItem.fields, indexes: objectMetadataItem.indexMetadatas, }), ); @@ -117,8 +117,7 @@ export class WorkspaceSchemaFactory { const autoGeneratedResolvers = await this.workspaceResolverFactory.create( authContext, - objectMetadataCollection, - objectMetadataMap, + objectMetadataMaps, workspaceResolverBuilderMethodNames, ); const scalarsResolvers = diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts index 9b1317a074e3..55f92979116d 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts @@ -1,6 +1,6 @@ import { BadRequestException } from '@nestjs/common'; -import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; @@ -9,7 +9,7 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target export const checkArrayFields = ( objectMetadata: ObjectMetadataInterface, - fields: Array>, + fields: Array>, ): void => { const fieldMetadataNames = objectMetadata.fields .map((field) => { diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util.ts index b8cfeedf3b7b..60bc708a1542 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-properties.util.ts @@ -1,11 +1,13 @@ import deepEqual from 'deep-equal'; -import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; export const objectRecordChangedProperties = < - PRecord extends Partial = Partial, + PRecord extends Partial< + ObjectRecord | BaseWorkspaceEntity + > = Partial, >( oldRecord: PRecord, newRecord: PRecord, diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts index 3fe17e3aff3c..0a3e6331d4ec 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts @@ -1,13 +1,13 @@ import deepEqual from 'deep-equal'; -import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; export const objectRecordChangedValues = ( - oldRecord: Partial, - newRecord: Partial, + oldRecord: Partial, + newRecord: Partial, updatedKeys: string[] | undefined, ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ) => { diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index d9a1850a5df6..ea59bf92cc72 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -467,13 +467,13 @@ export class RelationMetadataService extends TypeOrmQueryService { const objectMetadata = - objectMetadataMap[fieldMetadataItem.objectMetadataId]; + objectMetadataMaps.byId[fieldMetadataItem.objectMetadataId]; const fieldMetadata = objectMetadata.fields[fieldMetadataItem.id]; @@ -495,18 +495,18 @@ export class RelationMetadataService extends TypeOrmQueryService { const entitySchemaRelationMap: EntitySchemaRelationMap = {}; - const fieldMetadataCollection = Object.values(fieldMetadataMap); + const fieldMetadataCollection = Object.values(fieldMetadataMapByName); for (const fieldMetadata of fieldMetadataCollection) { if (!isRelationFieldMetadataType(fieldMetadata.type)) { @@ -42,7 +40,7 @@ export class EntitySchemaRelationFactory { const relationDetails = await determineRelationDetails( fieldMetadata, relationMetadata, - objectMetadataMap, + objectMetadataMaps, ); entitySchemaRelationMap[fieldMetadata.name] = { diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema.factory.ts index 9a1b32500a88..2d71c065efb9 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema.factory.ts @@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common'; import { EntitySchema } from 'typeorm'; -import { - ObjectMetadataItemWithFieldMaps, - ObjectMetadataMap, -} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { EntitySchemaColumnFactory } from 'src/engine/twenty-orm/factories/entity-schema-column.factory'; import { EntitySchemaRelationFactory } from 'src/engine/twenty-orm/factories/entity-schema-relation.factory'; import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage'; @@ -20,17 +18,17 @@ export class EntitySchemaFactory { async create( workspaceId: string, - metadataVersion: number, + _metadataVersion: number, objectMetadata: ObjectMetadataItemWithFieldMaps, - objectMetadataMap: ObjectMetadataMap, + objectMetadataMaps: ObjectMetadataMaps, ): Promise { const columns = this.entitySchemaColumnFactory.create( - objectMetadata.fields, + objectMetadata.fieldsByName, ); const relations = await this.entitySchemaRelationFactory.create( - objectMetadata.fields, - objectMetadataMap, + objectMetadata.fieldsByName, + objectMetadataMaps, ); const entitySchema = new EntitySchema({ diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts index 60aecca27029..7a2dbbfa47b3 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts @@ -86,13 +86,13 @@ export class WorkspaceDatasourceFactory { let cachedEntitySchemas: EntitySchema[]; - const cachedObjectMetadataMap = - await this.workspaceCacheStorageService.getObjectMetadataMap( + const cachedObjectMetadataMaps = + await this.workspaceCacheStorageService.getObjectMetadataMaps( workspaceId, cachedWorkspaceMetadataVersion, ); - if (!cachedObjectMetadataMap) { + if (!cachedObjectMetadataMaps) { throw new TwentyORMException( `Workspace Schema not found for workspace ${workspaceId}`, TwentyORMExceptionCode.METADATA_COLLECTION_NOT_FOUND, @@ -105,13 +105,14 @@ export class WorkspaceDatasourceFactory { ); } else { const entitySchemas = await Promise.all( - Object.values(cachedObjectMetadataMap).map((objectMetadata) => - this.entitySchemaFactory.create( - workspaceId, - cachedWorkspaceMetadataVersion, - objectMetadata, - cachedObjectMetadataMap, - ), + Object.values(cachedObjectMetadataMaps.byId).map( + (objectMetadata) => + this.entitySchemaFactory.create( + workspaceId, + cachedWorkspaceMetadataVersion, + objectMetadata, + cachedObjectMetadataMaps, + ), ), ); @@ -127,7 +128,7 @@ export class WorkspaceDatasourceFactory { const workspaceDataSource = new WorkspaceDataSource( { workspaceId, - objectMetadataMap: cachedObjectMetadataMap, + objectMetadataMaps: cachedObjectMetadataMaps, }, { url: diff --git a/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-internal-context.interface.ts b/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-internal-context.interface.ts index f68611f678db..be7d9c712c8b 100644 --- a/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-internal-context.interface.ts +++ b/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-internal-context.interface.ts @@ -1,6 +1,6 @@ -import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; export interface WorkspaceInternalContext { workspaceId: string; - objectMetadataMap: ObjectMetadataMap; + objectMetadataMaps: ObjectMetadataMaps; } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts index 027d8158749b..b81eaf1c0d72 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts @@ -22,7 +22,7 @@ import { UpsertOptions } from 'typeorm/repository/UpsertOptions'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @@ -631,13 +631,15 @@ export class WorkspaceRepository< } const objectMetadata = - this.internalContext.objectMetadataMap[objectMetadataName]; + this.internalContext.objectMetadataMaps.byNameSingular[ + objectMetadataName + ]; if (!objectMetadata) { throw new Error( `Object metadata for object "${objectMetadataName}" is missing ` + `in workspace "${this.internalContext.workspaceId}" ` + - `with object metadata collection length: ${this.internalContext.objectMetadataMap.length}`, + `with object metadata collection length: ${this.internalContext.objectMetadataMaps.byNameSingular.length}`, ); } @@ -670,8 +672,8 @@ export class WorkspaceRepository< ): Promise { objectMetadata ??= await this.getObjectMetadataFromTarget(); - const objectMetadataMap = this.internalContext.objectMetadataMap; + const objectMetadataMaps = this.internalContext.objectMetadataMaps; - return formatResult(data, objectMetadata, objectMetadataMap) as T; + return formatResult(data, objectMetadata, objectMetadataMaps) as T; } } diff --git a/packages/twenty-server/src/engine/workspace-cache-storage/workspace-cache-storage.service.ts b/packages/twenty-server/src/engine/workspace-cache-storage/workspace-cache-storage.service.ts index bd35c60cbfba..d549f14c171f 100644 --- a/packages/twenty-server/src/engine/workspace-cache-storage/workspace-cache-storage.service.ts +++ b/packages/twenty-server/src/engine/workspace-cache-storage/workspace-cache-storage.service.ts @@ -88,14 +88,14 @@ export class WorkspaceCacheStorageService { ); } - setObjectMetadataMap( + setObjectMetadataMaps( workspaceId: string, metadataVersion: number, - objectMetadataMap: ObjectMetadataMaps, + objectMetadataMaps: ObjectMetadataMaps, ) { return this.cacheStorageService.set( `${WorkspaceCacheKeys.MetadataObjectMetadataMaps}:${workspaceId}:${metadataVersion}`, - objectMetadataMap, + objectMetadataMaps, ); } diff --git a/packages/twenty-server/src/modules/timeline/repositiories/timeline-activity.repository.ts b/packages/twenty-server/src/modules/timeline/repositiories/timeline-activity.repository.ts index ff8491f29d17..cae34c1d9b92 100644 --- a/packages/twenty-server/src/modules/timeline/repositiories/timeline-activity.repository.ts +++ b/packages/twenty-server/src/modules/timeline/repositiories/timeline-activity.repository.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { EntityManager } from 'typeorm'; -import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { objectRecordDiffMerge } from 'src/engine/core-modules/event-emitter/utils/object-record-diff-merge'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; @@ -15,7 +15,7 @@ export class TimelineActivityRepository { async upsertOne( name: string, - properties: Partial, + properties: Partial, objectName: string, recordId: string, workspaceId: string, @@ -105,7 +105,7 @@ export class TimelineActivityRepository { private async updateTimelineActivity( dataSourceSchema: string, id: string, - properties: Partial, + properties: Partial, workspaceMemberId: string | undefined, workspaceId: string, ) { @@ -121,7 +121,7 @@ export class TimelineActivityRepository { private async insertTimelineActivity( dataSourceSchema: string, name: string, - properties: Partial, + properties: Partial, objectName: string, recordId: string, workspaceMemberId: string | undefined, @@ -151,7 +151,7 @@ export class TimelineActivityRepository { objectName: string, activities: { name: string; - properties: Partial | null; + properties: Partial | null; workspaceMemberId: string | undefined; recordId: string | null; linkedRecordCachedName: string; From 7c7ee441ecfd38410890db2dcdf9d5c171bb9a32 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 16:53:57 +0100 Subject: [PATCH 21/30] Fix --- .../graphql-query-filter-field.parser.ts | 2 +- ...nner-graphql-api-exception-handler.util.ts | 29 ++++++++++--------- .../input-factories/order-by-input.factory.ts | 4 +-- .../constants/duplicate-criteria.constants.ts | 4 +-- .../types/object-record.base.event.ts | 4 +-- .../generate-fake-object-record-event.ts | 2 +- 6 files changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts index 01a7517285cf..95fb650826a4 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts @@ -29,7 +29,7 @@ export class GraphqlQueryFilterFieldParser { filterValue: any, isFirst = false, ): void { - const fieldMetadataMapByName = this.fieldMetadataMapByName[`${key}`]; + const fieldMetadata = this.fieldMetadataMapByName[`${key}`]; if (!fieldMetadata) { throw new Error(`Field metadata not found for field: ${key}`); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts index fc2f0fbbaef9..7afd8133eb17 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts @@ -31,22 +31,23 @@ export const workspaceQueryRunnerGraphqlApiExceptionHandler = ( if (indexNameMatch) { const indexName = indexNameMatch[1]; - const deletedAtFieldMetadata = context.objectMetadataItem.fields.find( - (field) => field.name === 'deletedAt', - ); + const deletedAtFieldMetadata = + context.objectMetadataItemWithFieldMaps.fieldsByName['deletedAt']; - const affectedColumns = context.objectMetadataItem.indexMetadatas - .find((index) => index.name === indexName) - ?.indexFieldMetadatas?.filter( - (field) => field.fieldMetadataId !== deletedAtFieldMetadata?.id, - ) - .map((indexField) => { - const fieldMetadata = context.objectMetadataItem.fields.find( - (objectField) => indexField.fieldMetadataId === objectField.id, - ); + const affectedColumns = + context.objectMetadataItemWithFieldMaps.indexMetadatas + .find((index) => index.name === indexName) + ?.indexFieldMetadatas?.filter( + (field) => field.fieldMetadataId !== deletedAtFieldMetadata?.id, + ) + .map((indexField) => { + const fieldMetadata = + context.objectMetadataItemWithFieldMaps.fieldsById[ + indexField.fieldMetadataId + ]; - return fieldMetadata?.label; - }); + return fieldMetadata?.label; + }); const columnNames = affectedColumns?.join(', '); diff --git a/packages/twenty-server/src/engine/api/rest/input-factories/order-by-input.factory.ts b/packages/twenty-server/src/engine/api/rest/input-factories/order-by-input.factory.ts index 54ebfa489af3..25a3700e0ba8 100644 --- a/packages/twenty-server/src/engine/api/rest/input-factories/order-by-input.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/input-factories/order-by-input.factory.ts @@ -3,8 +3,8 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { Request } from 'express'; import { + ObjectRecordOrderBy, OrderByDirection, - RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { checkArrayFields } from 'src/engine/api/rest/core/query-builder/utils/check-order-by.utils'; @@ -13,7 +13,7 @@ export const DEFAULT_ORDER_DIRECTION = OrderByDirection.AscNullsFirst; @Injectable() export class OrderByInputFactory { - create(request: Request, objectMetadata): RecordOrderBy { + create(request: Request, objectMetadata): ObjectRecordOrderBy { const orderByQuery = request.query.order_by; if (typeof orderByQuery !== 'string') { diff --git a/packages/twenty-server/src/engine/core-modules/duplicate/constants/duplicate-criteria.constants.ts b/packages/twenty-server/src/engine/core-modules/duplicate/constants/duplicate-criteria.constants.ts index 5eac783001b0..e79a55d37dd4 100644 --- a/packages/twenty-server/src/engine/core-modules/duplicate/constants/duplicate-criteria.constants.ts +++ b/packages/twenty-server/src/engine/core-modules/duplicate/constants/duplicate-criteria.constants.ts @@ -1,4 +1,4 @@ -import { RecordDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ObjectRecordDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; /** * objectName: directly reference the name of the object from the metadata tables. @@ -6,7 +6,7 @@ import { RecordDuplicateCriteria } from 'src/engine/api/graphql/workspace-query- * So if we need to reference a custom field, we should directly add the column name like `_customColumn`. * If we need to terence a composite field, we should add all children of the composite like `nameFirstName` and `nameLastName` */ -export const DUPLICATE_CRITERIA_COLLECTION: RecordDuplicateCriteria[] = [ +export const DUPLICATE_CRITERIA_COLLECTION: ObjectRecordDuplicateCriteria[] = [ { objectName: 'company', columnNames: ['domainName'], diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record.base.event.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record.base.event.ts index c79e53fb7342..0295724e88f5 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record.base.event.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record.base.event.ts @@ -1,9 +1,9 @@ -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; export class ObjectRecordBaseEvent { recordId: string; userId?: string; workspaceMemberId?: string; - objectMetadata: ObjectMetadataItemWithFieldMaps; + objectMetadata: ObjectMetadataInterface; properties: any; } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event.ts index 84025618a8dd..ac2d065e6f99 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/utils/generate-fake-object-record-event.ts @@ -1,12 +1,12 @@ import { v4 } from 'uuid'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event'; import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event'; import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/utils/generate-fake-object-record'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; export const generateFakeObjectRecordEvent = ( objectMetadataEntity: ObjectMetadataEntity, From 22d0fb96c5bd20f032637d1e4ef359dd211b026a Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 16:56:26 +0100 Subject: [PATCH 22/30] Fix --- .../graphql-selected-fields-relation.parser.ts | 2 +- .../graphql-selected-fields.parser.ts | 6 +++--- .../graphql-query-parsers/graphql-query.parser.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts index 1924b743fea8..0b703946dcdd 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts @@ -28,7 +28,7 @@ export class GraphqlQuerySelectedFieldsRelationParser { this.objectMetadataMaps, ); - const relationFields = referencedObjectMetadata.fieldsById; + const relationFields = referencedObjectMetadata.fieldsByName; const fieldParser = new GraphqlQuerySelectedFieldsParser( this.objectMetadataMaps, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts index 6c60df475959..15579b261c5d 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts @@ -23,7 +23,7 @@ export class GraphqlQuerySelectedFieldsParser { parse( graphqlSelectedFields: Partial>, - fieldMetadataMap: Record, + fieldMetadataMapByName: Record, ): { select: Record; relations: Record } { const result: { select: Record; @@ -46,14 +46,14 @@ export class GraphqlQuerySelectedFieldsParser { } if (this.isConnectionField(fieldKey, fieldValue)) { - const subResult = this.parse(fieldValue, fieldMetadataMap); + const subResult = this.parse(fieldValue, fieldMetadataMapByName); Object.assign(result.select, subResult.select); Object.assign(result.relations, subResult.relations); continue; } - const fieldMetadata = fieldMetadataMap[fieldKey]; + const fieldMetadata = fieldMetadataMapByName[fieldKey]; if (!fieldMetadata) { throw new GraphqlQueryRunnerException( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts index 7142ab71d529..fdbba862b7fa 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts @@ -107,7 +107,7 @@ export class GraphqlQueryParser { ): { select: Record; relations: Record } { const parentFields = this.objectMetadataMaps.byNameSingular[parentObjectMetadata.nameSingular] - ?.fieldsById; + ?.fieldsByName; if (!parentFields) { throw new Error( From b182252f4f9310876e51e590531a77353201a0d2 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 17:00:06 +0100 Subject: [PATCH 23/30] Fix --- .../utils/compute-cursor-arg-filter.ts | 10 ++-- .../factories/query-runner-args.factory.ts | 58 ++++++++++++------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter.ts index 37678f2f6f34..02cd804447db 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter.ts @@ -16,7 +16,7 @@ import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metada export const computeCursorArgFilter = ( cursor: Record, orderBy: ObjectRecordOrderBy, - fieldMetadataMap: FieldMetadataMap, + fieldMetadataMapByName: FieldMetadataMap, isForwardPagination = true, ): ObjectRecordFilter[] => { const cursorKeys = Object.keys(cursor ?? {}); @@ -39,7 +39,7 @@ export const computeCursorArgFilter = ( ...buildWhereCondition( cursorKeys[subConditionIndex], cursorValues[subConditionIndex], - fieldMetadataMap, + fieldMetadataMapByName, 'eq', ), }; @@ -68,7 +68,7 @@ export const computeCursorArgFilter = ( return { ...whereCondition, - ...buildWhereCondition(key, value, fieldMetadataMap, operator), + ...buildWhereCondition(key, value, fieldMetadataMapByName, operator), } as ObjectRecordFilter; }); }; @@ -76,10 +76,10 @@ export const computeCursorArgFilter = ( const buildWhereCondition = ( key: string, value: any, - fieldMetadataMap: FieldMetadataMap, + fieldMetadataMapByName: FieldMetadataMap, operator: string, ): Record => { - const fieldMetadata = fieldMetadataMap[key]; + const fieldMetadata = fieldMetadataMapByName[key]; if (!fieldMetadata) { throw new GraphqlQueryRunnerException( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts index 2d98ffe719cf..ddcf2ff282c6 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts @@ -35,7 +35,7 @@ export class QueryRunnerArgsFactory { options: WorkspaceQueryRunnerOptions, resolverArgsType: ResolverArgsType, ) { - const fieldMetadataMap = + const fieldMetadataMapByNameByName = options.objectMetadataItemWithFieldMaps.fieldsByName; const shouldBackfillPosition = hasPositionField( @@ -48,10 +48,15 @@ export class QueryRunnerArgsFactory { ...args, data: await Promise.all( (args as CreateManyResolverArgs).data?.map((arg, index) => - this.overrideDataByFieldMetadata(arg, options, fieldMetadataMap, { - argIndex: index, - shouldBackfillPosition, - }), + this.overrideDataByFieldMetadata( + arg, + options, + fieldMetadataMapByNameByName, + { + argIndex: index, + shouldBackfillPosition, + }, + ), ) ?? [], ), } satisfies CreateManyResolverArgs; @@ -60,7 +65,7 @@ export class QueryRunnerArgsFactory { ...args, filter: await this.overrideFilterByFieldMetadata( (args as FindOneResolverArgs).filter, - fieldMetadataMap, + fieldMetadataMapByNameByName, ), }; case ResolverArgsType.FindMany: @@ -68,7 +73,7 @@ export class QueryRunnerArgsFactory { ...args, filter: await this.overrideFilterByFieldMetadata( (args as FindManyResolverArgs).filter, - fieldMetadataMap, + fieldMetadataMapByNameByName, ), }; @@ -77,15 +82,24 @@ export class QueryRunnerArgsFactory { ...args, ids: (await Promise.all( (args as FindDuplicatesResolverArgs).ids?.map((id) => - this.overrideValueByFieldMetadata('id', id, fieldMetadataMap), + this.overrideValueByFieldMetadata( + 'id', + id, + fieldMetadataMapByNameByName, + ), ) ?? [], )) as string[], data: await Promise.all( (args as FindDuplicatesResolverArgs).data?.map((arg, index) => - this.overrideDataByFieldMetadata(arg, options, fieldMetadataMap, { - argIndex: index, - shouldBackfillPosition, - }), + this.overrideDataByFieldMetadata( + arg, + options, + fieldMetadataMapByNameByName, + { + argIndex: index, + shouldBackfillPosition, + }, + ), ) ?? [], ), } satisfies FindDuplicatesResolverArgs; @@ -97,7 +111,7 @@ export class QueryRunnerArgsFactory { private async overrideDataByFieldMetadata( data: Partial | undefined, options: WorkspaceQueryRunnerOptions, - fieldMetadataMap: Record, + fieldMetadataMapByNameByName: Record, argPositionBackfillInput: ArgPositionBackfillInput, ) { if (!data) { @@ -108,7 +122,7 @@ export class QueryRunnerArgsFactory { const createArgPromiseByArgKey = Object.entries(data).map( async ([key, value]) => { - const fieldMetadata = fieldMetadataMap[key]; + const fieldMetadata = fieldMetadataMapByNameByName[key]; if (!fieldMetadata) { return [key, await Promise.resolve(value)]; @@ -168,7 +182,7 @@ export class QueryRunnerArgsFactory { private overrideFilterByFieldMetadata( filter: ObjectRecordFilter | undefined, - fieldMetadataMap: Record, + fieldMetadataMapByName: Record, ) { if (!filter) { return; @@ -183,7 +197,11 @@ export class QueryRunnerArgsFactory { } else if (key === 'not') { acc[key] = overrideFilter(value); } else { - acc[key] = this.transformValueByType(key, value, fieldMetadataMap); + acc[key] = this.transformValueByType( + key, + value, + fieldMetadataMapByName, + ); } return acc; @@ -196,9 +214,9 @@ export class QueryRunnerArgsFactory { private transformValueByType( key: string, value: any, - fieldMetadataMap: FieldMetadataMap, + fieldMetadataMapByName: FieldMetadataMap, ) { - const fieldMetadata = fieldMetadataMap[key]; + const fieldMetadata = fieldMetadataMapByName[key]; if (!fieldMetadata) { return value; @@ -225,9 +243,9 @@ export class QueryRunnerArgsFactory { private async overrideValueByFieldMetadata( key: string, value: any, - fieldMetadataMap: FieldMetadataMap, + fieldMetadataMapByName: FieldMetadataMap, ) { - const fieldMetadata = fieldMetadataMap[key]; + const fieldMetadata = fieldMetadataMapByName[key]; if (!fieldMetadata) { return value; From 2d7ff9c7a628c3a5556e8168aa760bbbf5eb675d Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 17:04:33 +0100 Subject: [PATCH 24/30] Fix --- .../resolvers/graphql-query-create-many-resolver.service.ts | 2 +- .../resolvers/graphql-query-destroy-many-resolver.service.ts | 2 +- .../resolvers/graphql-query-find-duplicates-resolver.service.ts | 2 +- .../resolvers/graphql-query-search-resolver.service.ts | 2 +- .../resolvers/graphql-query-update-one-resolver.service.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index a951e03e4e4d..e37cd78d8132 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -45,7 +45,7 @@ export class GraphqlQueryCreateManyResolverService ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsById, + objectMetadataItemWithFieldMaps.fieldsByName, objectMetadataMaps, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts index 09b919705525..1a012384a836 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -43,7 +43,7 @@ export class GraphqlQueryDestroyManyResolverService ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsById, + objectMetadataItemWithFieldMaps.fieldsByName, objectMetadataMaps, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts index aca931d12610..ebc2200c1cc1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -59,7 +59,7 @@ export class GraphqlQueryFindDuplicatesResolverService const graphqlQueryParser = new GraphqlQueryParser( objectMetadataMaps.byNameSingular[ objectMetadataItemWithFieldMaps.nameSingular - ].fieldsById, + ].fieldsByName, objectMetadataMaps, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts index a1d8f2dd5623..c9e7455a37b1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -71,7 +71,7 @@ export class GraphqlQuerySearchResolverService objectMetadataItemWithFieldMaps.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsById, + objectMetadataItemWithFieldMaps.fieldsByName, objectMetadataMaps, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts index 25ffbdfe3479..a44412b2c5f5 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -50,7 +50,7 @@ export class GraphqlQueryUpdateOneResolverService ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsById, + objectMetadataItemWithFieldMaps.fieldsByName, objectMetadataMaps, ); From fce5c81b086e4e5d0fa4ea9dfed1fb43b6578197 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 18:12:55 +0100 Subject: [PATCH 25/30] Fixes --- .../helpers/object-records-to-graphql-connection.helper.ts | 2 +- .../helpers/process-nested-relations.helper.ts | 5 +++-- .../relation-metadata/relation-metadata.service.ts | 6 +++--- .../src/engine/twenty-orm/utils/format-data.util.ts | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index c4ef79efcfc0..e1091e33d083 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -104,7 +104,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { const processedObjectRecord: Record = {}; for (const [key, value] of Object.entries(objectRecord)) { - const fieldMetadata = objectMetadata.fields[key]; + const fieldMetadata = objectMetadata.fieldsByName[key]; if (!fieldMetadata) { processedObjectRecord[key] = value; 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 572c7706db2e..dfd8634c1d31 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 @@ -192,8 +192,9 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps, ); const inverseRelationName = - objectMetadataMaps.byNameSingular[relationMetadata.toObjectMetadataId] - ?.fieldsById[relationMetadata.toFieldMetadataId]?.name; + objectMetadataMaps.byId[relationMetadata.toObjectMetadataId]?.fieldsById[ + relationMetadata.toFieldMetadataId + ]?.name; return { inverseRelationName, referenceObjectMetadata }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index ea59bf92cc72..19daa9c46bf9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -483,7 +483,7 @@ export class RelationMetadataService extends TypeOrmQueryService( data: T, - ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ): T { if (!data) { return data; @@ -17,14 +17,14 @@ export function formatData( if (Array.isArray(data)) { return data.map((item) => - formatData(item, ObjectMetadataItemWithFieldMaps), + formatData(item, objectMetadataItemWithFieldMaps), ) as T; } const newData: Record = {}; for (const [key, value] of Object.entries(data)) { - const fieldMetadata = ObjectMetadataItemWithFieldMaps.fieldsByName[key]; + const fieldMetadata = objectMetadataItemWithFieldMaps.fieldsByName[key]; if (!fieldMetadata) { throw new Error( From c1df0e53553ac404290b52502275584aac162df4 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 18:14:39 +0100 Subject: [PATCH 26/30] Remove unused date time scalar --- .../graphql-types/scalars/date-time.scalar.ts | 38 ------------------- .../graphql-types/scalars/index.ts | 9 ++--- .../folder-architecture-server.mdx | 4 -- 3 files changed, 3 insertions(+), 48 deletions(-) delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/date-time.scalar.ts diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/date-time.scalar.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/date-time.scalar.ts deleted file mode 100644 index c907b4dd4baf..000000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/date-time.scalar.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { GraphQLScalarType } from 'graphql'; -import { Kind } from 'graphql/language'; - -export const DateTimeScalarType = new GraphQLScalarType({ - name: 'DateTime', - description: 'A custom scalar that represents a datetime in ISO format', - serialize(value: string): string { - const date = new Date(value); - - if (isNaN(date.getTime())) { - throw new Error('Invalid date format, expected ISO date string'); - } - - return date.toISOString(); - }, - parseValue(value: string): Date { - const date = new Date(value); - - if (isNaN(date.getTime())) { - throw new Error('Invalid date format, expected ISO date string'); - } - - return date; - }, - parseLiteral(ast): Date { - if (ast.kind !== Kind.STRING) { - throw new Error('Invalid date format, expected ISO date string'); - } - - const date = new Date(ast.value); - - if (isNaN(date.getTime())) { - throw new Error('Invalid date format, expected ISO date string'); - } - - return date; - }, -}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts index 071e99b1dba3..e5aec2019868 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts @@ -1,10 +1,9 @@ -import { RawJSONScalar } from './raw-json.scalar'; -import { PositionScalarType } from './position.scalar'; -import { CursorScalarType } from './cursor.scalar'; import { BigFloatScalarType } from './big-float.scalar'; import { BigIntScalarType } from './big-int.scalar'; +import { CursorScalarType } from './cursor.scalar'; import { DateScalarType } from './date.scalar'; -import { DateTimeScalarType } from './date-time.scalar'; +import { PositionScalarType } from './position.scalar'; +import { RawJSONScalar } from './raw-json.scalar'; import { TimeScalarType } from './time.scalar'; import { UUIDScalarType } from './uuid.scalar'; @@ -12,7 +11,6 @@ export * from './big-float.scalar'; export * from './big-int.scalar'; export * from './cursor.scalar'; export * from './date.scalar'; -export * from './date-time.scalar'; export * from './time.scalar'; export * from './uuid.scalar'; @@ -20,7 +18,6 @@ export const scalars = [ BigFloatScalarType, BigIntScalarType, DateScalarType, - DateTimeScalarType, TimeScalarType, UUIDScalarType, CursorScalarType, diff --git a/packages/twenty-website/src/content/developers/backend-development/folder-architecture-server.mdx b/packages/twenty-website/src/content/developers/backend-development/folder-architecture-server.mdx index ac34f8c69547..2d47d1fd57da 100644 --- a/packages/twenty-website/src/content/developers/backend-development/folder-architecture-server.mdx +++ b/packages/twenty-website/src/content/developers/backend-development/folder-architecture-server.mdx @@ -123,10 +123,6 @@ Creates resolver functions for querying and mutating the GraphQL schema. Each factory in this directory is responsible for producing a distinct resolver type, such as the `FindManyResolverFactory`, designed for adaptable application across various tables. -### Workspace Query Builder - -Includes factories that generate `pg_graphql` queries. - ### Workspace Query Runner Runs the generated queries on the database and parses the result. From 765d9a66bb73bd7743cb0bc4d4a0b7b6f22a2618 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 11 Nov 2024 18:26:12 +0100 Subject: [PATCH 27/30] Fix tests --- .../services/api-event-emitter.service.ts | 21 +++++++++---------- .../query-runner-args.factory.spec.ts | 12 ----------- .../utils/object-record-changed-values.ts | 6 +++--- 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts index 26beda3ef5f5..1f5c96fd91ca 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts @@ -6,7 +6,6 @@ import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metad import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; @Injectable() @@ -38,7 +37,7 @@ export class ApiEventEmitterService { records: T[], updatedFields: string[], authContext: AuthContext, - ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + objectMetadataItem: ObjectMetadataInterface, ): void { const mappedExistingRecords = existingRecords.reduce( (acc, { id, ...record }) => ({ @@ -49,7 +48,7 @@ export class ApiEventEmitterService { ); this.workspaceEventEmitter.emit( - `${ObjectMetadataItemWithFieldMaps.nameSingular}.${DatabaseEventAction.UPDATED}`, + `${objectMetadataItem.nameSingular}.${DatabaseEventAction.UPDATED}`, records.map((record) => { const before = this.removeGraphQLAndNestedProperties( mappedExistingRecords[record.id], @@ -59,13 +58,13 @@ export class ApiEventEmitterService { before, after, updatedFields, - ObjectMetadataItemWithFieldMaps, + objectMetadataItem, ); return { userId: authContext.user?.id, recordId: record.id, - objectMetadata: ObjectMetadataItemWithFieldMaps, + objectMetadata: objectMetadataItem, properties: { before, after, @@ -81,15 +80,15 @@ export class ApiEventEmitterService { public emitDeletedEvents( records: T[], authContext: AuthContext, - ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + objectMetadataItem: ObjectMetadataInterface, ): void { this.workspaceEventEmitter.emit( - `${ObjectMetadataItemWithFieldMaps.nameSingular}.${DatabaseEventAction.DELETED}`, + `${objectMetadataItem.nameSingular}.${DatabaseEventAction.DELETED}`, records.map((record) => { return { userId: authContext.user?.id, recordId: record.id, - objectMetadata: ObjectMetadataItemWithFieldMaps, + objectMetadata: objectMetadataItem, properties: { before: this.removeGraphQLAndNestedProperties(record), after: null, @@ -103,15 +102,15 @@ export class ApiEventEmitterService { public emitDestroyEvents( records: T[], authContext: AuthContext, - ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + objectMetadataItem: ObjectMetadataInterface, ): void { this.workspaceEventEmitter.emit( - `${ObjectMetadataItemWithFieldMaps.nameSingular}.${DatabaseEventAction.DESTROYED}`, + `${objectMetadataItem.nameSingular}.${DatabaseEventAction.DESTROYED}`, records.map((record) => { return { userId: authContext.user?.id, recordId: record.id, - objectMetadata: ObjectMetadataItemWithFieldMaps, + objectMetadata: objectMetadataItem, properties: { before: this.removeGraphQLAndNestedProperties(record), after: null, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/__tests__/query-runner-args.factory.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/__tests__/query-runner-args.factory.spec.ts index 5c5650538b09..ea8e3f6c9e3a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/__tests__/query-runner-args.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/__tests__/query-runner-args.factory.spec.ts @@ -1,11 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; -import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { ResolverArgsType } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory'; -import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory'; describe('QueryRunnerArgsFactory', () => { @@ -14,11 +12,6 @@ describe('QueryRunnerArgsFactory', () => { }; const workspaceId = 'workspaceId'; const options = { - fieldMetadataCollection: [ - { name: 'position', type: FieldMetadataType.POSITION }, - { name: 'testNumber', type: FieldMetadataType.NUMBER }, - ] as FieldMetadataInterface[], - objectMetadataItem: { isCustom: true, nameSingular: 'test' }, authContext: { workspace: { id: workspaceId } }, } as WorkspaceQueryRunnerOptions; @@ -144,11 +137,6 @@ describe('QueryRunnerArgsFactory', () => { it('findDuplicates type should override number in data and id', async () => { const optionsDuplicate = { - fieldMetadataCollection: [ - { name: 'id', type: FieldMetadataType.NUMBER }, - { name: 'testNumber', type: FieldMetadataType.NUMBER }, - ] as FieldMetadataInterface[], - objectMetadataItem: { isCustom: true, nameSingular: 'test' }, authContext: { workspace: { id: workspaceId } }, } as WorkspaceQueryRunnerOptions; diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts index 0a3e6331d4ec..c8439f7c0b28 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts @@ -1,19 +1,19 @@ import deepEqual from 'deep-equal'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; export const objectRecordChangedValues = ( oldRecord: Partial, newRecord: Partial, updatedKeys: string[] | undefined, - ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + objectMetadataItem: ObjectMetadataInterface, ) => { return Object.keys(newRecord).reduce( (acc, key) => { - const field = ObjectMetadataItemWithFieldMaps.fieldsByName[key]; + const field = objectMetadataItem.fields.find((f) => f.name === key); const oldRecordValue = oldRecord[key]; const newRecordValue = newRecord[key]; From b44ceb9854c4b64db6d0ff573cadbba8a55dd4b7 Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 14 Nov 2024 14:57:41 +0100 Subject: [PATCH 28/30] Add aggregation to nested queries + totalCount + fix aggregate for paginated results --- ...raphql-selected-fields-aggregate.parser.ts | 30 ++ ...graphql-selected-fields-relation.parser.ts | 8 +- .../graphql-selected-fields.parser.ts | 88 ++-- .../graphql-query.parser.ts | 7 +- ...ct-records-to-graphql-connection.helper.ts | 46 +- .../helpers/process-aggregate.helper.ts | 37 ++ .../process-nested-relations.helper.ts | 407 +++++++++++++----- ...phql-query-create-many-resolver.service.ts | 10 +- ...hql-query-destroy-many-resolver.service.ts | 10 +- ...phql-query-destroy-one-resolver.service.ts | 10 +- ...-query-find-duplicates-resolver.service.ts | 2 + ...raphql-query-find-many-resolver.service.ts | 168 +++----- ...graphql-query-find-one-resolver.service.ts | 10 +- ...phql-query-update-many-resolver.service.ts | 10 +- ...aphql-query-update-one-resolver.service.ts | 10 +- .../factories/aggregation-type.factory.ts | 32 ++ .../connection-type-definition.factory.ts | 36 +- .../factories/factories.ts | 26 +- ...le-aggregations-from-object-fields.util.ts | 118 +++-- 19 files changed, 660 insertions(+), 405 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory.ts diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser.ts new file mode 100644 index 000000000000..18f3b8c2d9f7 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser.ts @@ -0,0 +1,30 @@ +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; + +import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; +import { + AggregationField, + getAvailableAggregationsFromObjectFields, +} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; + +export class GraphqlQuerySelectedFieldsAggregateParser { + parse( + graphqlSelectedFields: Partial>, + fieldMetadataMapByName: Record, + result: GraphqlQuerySelectedFieldsResult, + ): void { + const availableAggregations: Record = + getAvailableAggregationsFromObjectFields( + Object.values(fieldMetadataMapByName), + ); + + for (const selectedField of Object.keys(graphqlSelectedFields)) { + const selectedAggregation = availableAggregations[selectedField]; + + if (!selectedAggregation) { + continue; + } + + result.aggregate[selectedField] = selectedAggregation; + } + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts index 0b703946dcdd..94f8aa71b97a 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts @@ -1,6 +1,9 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; -import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; +import { + GraphqlQuerySelectedFieldsParser, + GraphqlQuerySelectedFieldsResult, +} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; import { getRelationObjectMetadata } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; @@ -15,7 +18,7 @@ export class GraphqlQuerySelectedFieldsRelationParser { fieldMetadata: FieldMetadataInterface, fieldKey: string, fieldValue: any, - result: { select: Record; relations: Record }, + result: GraphqlQuerySelectedFieldsResult, ): void { if (!fieldValue || typeof fieldValue !== 'object') { return; @@ -39,5 +42,6 @@ export class GraphqlQuerySelectedFieldsRelationParser { ...subResult.select, }; result.relations[fieldKey] = subResult.relations; + result.aggregate[fieldKey] = subResult.aggregate; } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts index 15579b261c5d..e1a9b1f752a1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts @@ -1,9 +1,6 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; -import { - GraphqlQueryRunnerException, - GraphqlQueryRunnerExceptionCode, -} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { GraphqlQuerySelectedFieldsAggregateParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser'; import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; @@ -11,55 +8,62 @@ import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-met import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; import { capitalize } from 'src/utils/capitalize'; -import { isPlainObject } from 'src/utils/is-plain-object'; + +export type GraphqlQuerySelectedFieldsResult = { + select: Record; + relations: Record; + aggregate: Record; +}; export class GraphqlQuerySelectedFieldsParser { private graphqlQuerySelectedFieldsRelationParser: GraphqlQuerySelectedFieldsRelationParser; + private aggregateParser: GraphqlQuerySelectedFieldsAggregateParser; constructor(objectMetadataMaps: ObjectMetadataMaps) { this.graphqlQuerySelectedFieldsRelationParser = new GraphqlQuerySelectedFieldsRelationParser(objectMetadataMaps); + this.aggregateParser = new GraphqlQuerySelectedFieldsAggregateParser(); } parse( graphqlSelectedFields: Partial>, fieldMetadataMapByName: Record, - ): { select: Record; relations: Record } { - const result: { - select: Record; - relations: Record; - } = { + ): GraphqlQuerySelectedFieldsResult { + const result: GraphqlQuerySelectedFieldsResult = { select: {}, relations: {}, + aggregate: {}, }; - const hasEdges = Object.keys(graphqlSelectedFields).includes('edges'); + if (this.isRootConnection(graphqlSelectedFields)) { + this.parseConnectionField( + graphqlSelectedFields, + fieldMetadataMapByName, + result, + ); + } - for (const [fieldKey, fieldValue] of Object.entries( + this.parseRecordField( graphqlSelectedFields, - )) { - if (hasEdges && fieldKey !== 'edges') { - continue; - } - if (this.shouldNotParseField(fieldKey)) { - continue; - } - - if (this.isConnectionField(fieldKey, fieldValue)) { - const subResult = this.parse(fieldValue, fieldMetadataMapByName); + fieldMetadataMapByName, + result, + ); - Object.assign(result.select, subResult.select); - Object.assign(result.relations, subResult.relations); - continue; - } + return result; + } + private parseRecordField( + graphqlSelectedFields: Partial>, + fieldMetadataMapByName: Record, + result: GraphqlQuerySelectedFieldsResult, + ): void { + for (const [fieldKey, fieldValue] of Object.entries( + graphqlSelectedFields, + )) { const fieldMetadata = fieldMetadataMapByName[fieldKey]; if (!fieldMetadata) { - throw new GraphqlQueryRunnerException( - `Field "${fieldKey}" does not exist or is not selectable`, - GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND, - ); + continue; } if (isRelationFieldMetadataType(fieldMetadata.type)) { @@ -80,16 +84,28 @@ export class GraphqlQuerySelectedFieldsParser { result.select[fieldKey] = true; } } - - return result; } - private isConnectionField(fieldKey: string, fieldValue: any): boolean { - return ['edges', 'node'].includes(fieldKey) && isPlainObject(fieldValue); + private parseConnectionField( + graphqlSelectedFields: Partial>, + fieldMetadataMapByName: Record, + result: GraphqlQuerySelectedFieldsResult, + ): void { + this.aggregateParser.parse( + graphqlSelectedFields, + fieldMetadataMapByName, + result, + ); + + const node = graphqlSelectedFields.edges.node; + + this.parseRecordField(node, fieldMetadataMapByName, result); } - private shouldNotParseField(fieldKey: string): boolean { - return ['__typename', 'cursor'].includes(fieldKey); + private isRootConnection( + graphqlSelectedFields: Partial>, + ): boolean { + return Object.keys(graphqlSelectedFields).includes('edges'); } private parseCompositeField( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts index fdbba862b7fa..aa7700c5678d 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts @@ -12,7 +12,10 @@ import { import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser'; import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser'; -import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; +import { + GraphqlQuerySelectedFieldsParser, + GraphqlQuerySelectedFieldsResult, +} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; @@ -104,7 +107,7 @@ export class GraphqlQueryParser { public parseSelectedFields( parentObjectMetadata: ObjectMetadataItemWithFieldMaps, graphqlSelectedFields: Partial>, - ): { select: Record; relations: Record } { + ): GraphqlQuerySelectedFieldsResult { const parentFields = this.objectMetadataMaps.byNameSingular[parentObjectMetadata.nameSingular] ?.fieldsByName; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index e1091e33d083..12e6248cdec1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -12,6 +12,7 @@ import { } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { encodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; import { getRelationObjectMetadata } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util'; +import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; @@ -29,6 +30,8 @@ export class ObjectRecordsToGraphqlConnectionHelper { public createConnection({ objectRecords, + objectRecordsAggregatedFields = {}, + selectedAggregatedFields = [], objectName, take, totalCount, @@ -38,6 +41,8 @@ export class ObjectRecordsToGraphqlConnectionHelper { depth = 0, }: { objectRecords: T[]; + objectRecordsAggregatedFields?: Record; + selectedAggregatedFields?: Record; objectName: string; take: number; totalCount: number; @@ -50,6 +55,8 @@ export class ObjectRecordsToGraphqlConnectionHelper { node: this.processRecord({ objectRecord, objectName, + objectRecordsAggregatedFields, + selectedAggregatedFields, take, totalCount, order, @@ -59,6 +66,10 @@ export class ObjectRecordsToGraphqlConnectionHelper { })); return { + ...this.extractAggregatedFieldsResults({ + selectedAggregatedFields, + objectRecordsAggregatedFields, + }), edges, pageInfo: { hasNextPage, @@ -70,9 +81,30 @@ export class ObjectRecordsToGraphqlConnectionHelper { }; } + private extractAggregatedFieldsResults = ({ + selectedAggregatedFields, + objectRecordsAggregatedFields, + }: { + selectedAggregatedFields: Record; + objectRecordsAggregatedFields: Record; + }) => { + return Object.entries(selectedAggregatedFields).reduce( + (acc, [aggregatedFieldName]) => { + return { + ...acc, + [aggregatedFieldName]: + objectRecordsAggregatedFields[aggregatedFieldName], + }; + }, + {}, + ); + }; + public processRecord>({ objectRecord, objectName, + objectRecordsAggregatedFields = {}, + selectedAggregatedFields = [], take, totalCount, order, @@ -80,6 +112,8 @@ export class ObjectRecordsToGraphqlConnectionHelper { }: { objectRecord: T; objectName: string; + objectRecordsAggregatedFields?: Record; + selectedAggregatedFields?: Record; take: number; totalCount: number; order?: ObjectRecordOrderBy; @@ -115,12 +149,18 @@ export class ObjectRecordsToGraphqlConnectionHelper { if (Array.isArray(value)) { processedObjectRecord[key] = this.createConnection({ objectRecords: value, + objectRecordsAggregatedFields: + objectRecordsAggregatedFields[fieldMetadata.name], + selectedAggregatedFields: + selectedAggregatedFields[fieldMetadata.name], objectName: getRelationObjectMetadata( fieldMetadata, this.objectMetadataMaps, ).nameSingular, take, - totalCount: value.length, + totalCount: + objectRecordsAggregatedFields[fieldMetadata.name]?.totalCount ?? + value.length, order, hasNextPage: false, hasPreviousPage: false, @@ -129,6 +169,10 @@ export class ObjectRecordsToGraphqlConnectionHelper { } else if (isPlainObject(value)) { processedObjectRecord[key] = this.processRecord({ objectRecord: value, + objectRecordsAggregatedFields: + objectRecordsAggregatedFields[fieldMetadata.name], + selectedAggregatedFields: + selectedAggregatedFields[fieldMetadata.name], objectName: getRelationObjectMetadata( fieldMetadata, this.objectMetadataMaps, 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 new file mode 100644 index 000000000000..3ac1b554d0d4 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper.ts @@ -0,0 +1,37 @@ +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'; + +export class ProcessAggregateHelper { + public addSelectedAggregatedFieldsQueriesToQueryBuilder = ({ + fieldMetadataMapByName, + selectedAggregatedFields, + queryBuilder, + }: { + fieldMetadataMapByName: Record; + selectedAggregatedFields: Record; + queryBuilder: SelectQueryBuilder; + }) => { + queryBuilder.select([]); + + for (const [aggregatedFieldName, aggregatedField] of Object.entries( + selectedAggregatedFields, + )) { + const fieldMetadata = fieldMetadataMapByName[aggregatedField.fromField]; + + if (!fieldMetadata) { + continue; + } + + const fieldName = fieldMetadata.name; + const operation = aggregatedField.aggregationOperation; + + queryBuilder.addSelect( + `${operation}("${fieldName}")`, + `${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 dfd8634c1d31..f6a08f24f551 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 @@ -1,61 +1,93 @@ import { DataSource, - FindManyOptions, FindOptionsRelations, - In, ObjectLiteral, - Repository, + SelectQueryBuilder, } from 'typeorm'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper'; import { getRelationMetadata, getRelationObjectMetadata, } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util'; +import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util'; export class ProcessNestedRelationsHelper { - constructor() {} - - public async processNestedRelations( - objectMetadataMaps: ObjectMetadataMaps, - parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, - parentObjectRecords: T[], - relations: Record>, - limit: number, - authContext: any, - dataSource: DataSource, - ): Promise { + private processAggregateHelper: ProcessAggregateHelper; + + constructor() { + this.processAggregateHelper = new ProcessAggregateHelper(); + } + + public async processNestedRelations({ + objectMetadataMaps, + parentObjectMetadataItem, + parentObjectRecords, + parentObjectRecordsAggregatedFields = {}, + relations, + aggregate = {}, + limit, + authContext, + dataSource, + }: { + objectMetadataMaps: ObjectMetadataMaps; + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; + parentObjectRecords: T[]; + parentObjectRecordsAggregatedFields?: Record; + relations: Record>; + aggregate?: Record; + limit: number; + authContext: any; + dataSource: DataSource; + }): Promise { const processRelationTasks = Object.entries(relations).map( ([relationName, nestedRelations]) => - this.processRelation( + this.processRelation({ objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, + parentObjectRecordsAggregatedFields, relationName, nestedRelations, + aggregate, limit, authContext, dataSource, - ), + }), ); await Promise.all(processRelationTasks); } - private async processRelation( - objectMetadataMaps: ObjectMetadataMaps, - parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, - parentObjectRecords: T[], - relationName: string, - nestedRelations: any, - limit: number, - authContext: any, - dataSource: DataSource, - ): Promise { + private async processRelation({ + objectMetadataMaps, + parentObjectMetadataItem, + parentObjectRecords, + parentObjectRecordsAggregatedFields, + relationName, + nestedRelations, + aggregate, + limit, + authContext, + dataSource, + }: { + objectMetadataMaps: ObjectMetadataMaps; + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; + parentObjectRecords: T[]; + parentObjectRecordsAggregatedFields: Record; + relationName: string; + nestedRelations: any; + aggregate: Record; + limit: number; + authContext: any; + dataSource: DataSource; + }): Promise { const relationFieldMetadata = parentObjectMetadataItem.fieldsByName[relationName]; const relationMetadata = getRelationMetadata(relationFieldMetadata); @@ -69,121 +101,193 @@ export class ProcessNestedRelationsHelper { ? this.processToRelation : this.processFromRelation; - await processor.call( - this, + await processor.call(this, { objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, + parentObjectRecordsAggregatedFields, relationName, nestedRelations, + aggregate, limit, authContext, dataSource, - ); + }); } - private async processFromRelation( - objectMetadataMaps: ObjectMetadataMaps, - parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, - parentObjectRecords: T[], - relationName: string, - nestedRelations: any, - limit: number, - authContext: any, - dataSource: DataSource, - ): Promise { + private async processFromRelation({ + objectMetadataMaps, + parentObjectMetadataItem, + parentObjectRecords, + parentObjectRecordsAggregatedFields, + relationName, + nestedRelations, + aggregate, + limit, + authContext, + dataSource, + }: { + objectMetadataMaps: ObjectMetadataMaps; + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; + parentObjectRecords: T[]; + parentObjectRecordsAggregatedFields: Record; + relationName: string; + nestedRelations: any; + aggregate: Record; + limit: number; + authContext: any; + dataSource: DataSource; + }): Promise { const { inverseRelationName, referenceObjectMetadata } = - this.getRelationMetadata( + this.getRelationMetadata({ objectMetadataMaps, parentObjectMetadataItem, relationName, - ); + }); const relationRepository = dataSource.getRepository( referenceObjectMetadata.nameSingular, ); - const relationIds = this.getUniqueIds(parentObjectRecords, 'id'); - const relationResults = await this.findRelations( - relationRepository, - inverseRelationName, - relationIds, - limit * parentObjectRecords.length, + const queryBuilder = relationRepository.createQueryBuilder( + referenceObjectMetadata.nameSingular, ); - this.assignRelationResults( - parentObjectRecords, + const relationIds = this.getUniqueIds({ + records: parentObjectRecords, + idField: 'id', + }); + const { relationResults, relationAggregatedFieldsResult } = + await this.findRelations({ + queryBuilder, + column: `"${inverseRelationName}Id"`, + ids: relationIds, + limit: limit * parentObjectRecords.length, + objectMetadataMaps, + referenceObjectMetadata, + aggregate, + relationName, + }); + + this.assignFromRelationResults({ + parentRecords: parentObjectRecords, + parentObjectRecordsAggregatedFields, relationResults, + relationAggregatedFieldsResult, relationName, - `${inverseRelationName}Id`, - ); + joinField: `${inverseRelationName}Id`, + }); if (Object.keys(nestedRelations).length > 0) { - await this.processNestedRelations( + await this.processNestedRelations({ objectMetadataMaps, - objectMetadataMaps.byNameSingular[referenceObjectMetadata.nameSingular], - relationResults as ObjectRecord[], - nestedRelations as Record>, + parentObjectMetadataItem: + objectMetadataMaps.byNameSingular[ + referenceObjectMetadata.nameSingular + ], + parentObjectRecords: relationResults as ObjectRecord[], + parentObjectRecordsAggregatedFields: relationAggregatedFieldsResult, + relations: nestedRelations as Record< + string, + FindOptionsRelations + >, + aggregate, limit, authContext, dataSource, - ); + }); } } - private async processToRelation( - objectMetadataMaps: ObjectMetadataMaps, - parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, - parentObjectRecords: T[], - relationName: string, - nestedRelations: any, - limit: number, - authContext: any, - dataSource: DataSource, - ): Promise { - const { referenceObjectMetadata } = this.getRelationMetadata( + private async processToRelation({ + objectMetadataMaps, + parentObjectMetadataItem, + parentObjectRecords, + parentObjectRecordsAggregatedFields, + relationName, + nestedRelations, + aggregate, + limit, + authContext, + dataSource, + }: { + objectMetadataMaps: ObjectMetadataMaps; + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; + parentObjectRecords: T[]; + parentObjectRecordsAggregatedFields: Record; + relationName: string; + nestedRelations: any; + aggregate: Record; + limit: number; + authContext: any; + dataSource: DataSource; + }): Promise { + const { referenceObjectMetadata } = this.getRelationMetadata({ objectMetadataMaps, parentObjectMetadataItem, relationName, - ); + }); const relationRepository = dataSource.getRepository( referenceObjectMetadata.nameSingular, ); - const relationIds = this.getUniqueIds( - parentObjectRecords, - `${relationName}Id`, - ); - const relationResults = await this.findRelations( - relationRepository, - 'id', - relationIds, - limit, + const queryBuilder = relationRepository.createQueryBuilder( + referenceObjectMetadata.nameSingular, ); - this.assignToRelationResults( - parentObjectRecords, + const relationIds = this.getUniqueIds({ + records: parentObjectRecords, + idField: `${relationName}Id`, + }); + const { relationResults, relationAggregatedFieldsResult } = + await this.findRelations({ + queryBuilder, + column: 'id', + ids: relationIds, + limit, + objectMetadataMaps, + referenceObjectMetadata, + aggregate, + relationName, + }); + + this.assignToRelationResults({ + parentRecords: parentObjectRecords, + parentObjectRecordsAggregatedFields, relationResults, + relationAggregatedFieldsResult, relationName, - ); + }); if (Object.keys(nestedRelations).length > 0) { - await this.processNestedRelations( + await this.processNestedRelations({ objectMetadataMaps, - objectMetadataMaps.byNameSingular[referenceObjectMetadata.nameSingular], - relationResults as ObjectRecord[], - nestedRelations as Record>, + parentObjectMetadataItem: + objectMetadataMaps.byNameSingular[ + referenceObjectMetadata.nameSingular + ], + parentObjectRecords: relationResults as ObjectRecord[], + parentObjectRecordsAggregatedFields: relationAggregatedFieldsResult, + relations: nestedRelations as Record< + string, + FindOptionsRelations + >, + aggregate, limit, authContext, dataSource, - ); + }); } } - private getRelationMetadata( - objectMetadataMaps: ObjectMetadataMaps, - parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps, - relationName: string, - ) { + private getRelationMetadata({ + objectMetadataMaps, + parentObjectMetadataItem, + relationName, + }: { + objectMetadataMaps: ObjectMetadataMaps; + parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; + relationName: string; + }) { const relationFieldMetadata = parentObjectMetadataItem.fieldsByName[relationName]; const relationMetadata = getRelationMetadata(relationFieldMetadata); @@ -199,52 +303,121 @@ export class ProcessNestedRelationsHelper { return { inverseRelationName, referenceObjectMetadata }; } - private getUniqueIds(records: ObjectRecord[], idField: string): any[] { + private getUniqueIds({ + records, + idField, + }: { + records: ObjectRecord[]; + idField: string; + }): any[] { return [...new Set(records.map((item) => item[idField]))]; } - private async findRelations( - repository: Repository, - field: string, - ids: any[], - limit: number, - ): Promise { + private async findRelations({ + queryBuilder, + column, + ids, + limit, + objectMetadataMaps, + referenceObjectMetadata, + aggregate, + relationName, + }: { + queryBuilder: SelectQueryBuilder; + column: string; + ids: any[]; + limit: number; + objectMetadataMaps: ObjectMetadataMaps; + referenceObjectMetadata: ObjectMetadataItemWithFieldMaps; + aggregate: Record; + relationName: string; + }): Promise<{ relationResults: any[]; relationAggregatedFieldsResult: any }> { if (ids.length === 0) { - return []; + return { relationResults: [], relationAggregatedFieldsResult: {} }; } - const findOptions: FindManyOptions = { - where: { [field]: In(ids) }, - take: limit, - }; - return repository.find(findOptions); + const aggregateForRelation = aggregate[relationName]; + let relationAggregatedFieldsResult = {}; + + if (aggregateForRelation) { + const aggregateQueryBuilder = queryBuilder.clone(); + + this.processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder( + { + fieldMetadataMapByName: referenceObjectMetadata.fieldsByName, + selectedAggregatedFields: aggregateForRelation, + queryBuilder: aggregateQueryBuilder, + }, + ); + + relationAggregatedFieldsResult = + (await aggregateQueryBuilder.getRawOne()) ?? {}; + } + + const result = await queryBuilder + .where(`${column} IN (:...ids)`, { + ids, + }) + .take(limit) + .getMany(); + + const relationResults = formatResult( + result, + referenceObjectMetadata, + objectMetadataMaps, + ); + + return { relationResults, relationAggregatedFieldsResult }; } - private assignRelationResults( - parentRecords: ObjectRecord[], - relationResults: any[], - relationName: string, - joinField: string, - ): void { + private assignFromRelationResults({ + parentRecords, + parentObjectRecordsAggregatedFields, + relationResults, + relationAggregatedFieldsResult, + relationName, + joinField, + }: { + parentRecords: ObjectRecord[]; + parentObjectRecordsAggregatedFields: Record; + relationResults: any[]; + relationAggregatedFieldsResult: Record; + relationName: string; + joinField: string; + }): void { parentRecords.forEach((item) => { - (item as any)[relationName] = relationResults.filter( + item[relationName] = relationResults.filter( (rel) => rel[joinField] === item.id, ); }); + + parentObjectRecordsAggregatedFields[relationName] = + relationAggregatedFieldsResult; } - private assignToRelationResults( - parentRecords: ObjectRecord[], - relationResults: any[], - relationName: string, - ): void { + private assignToRelationResults({ + parentRecords, + parentObjectRecordsAggregatedFields, + relationResults, + relationAggregatedFieldsResult, + relationName, + }: { + parentRecords: ObjectRecord[]; + parentObjectRecordsAggregatedFields: Record; + relationResults: any[]; + relationAggregatedFieldsResult: Record; + relationName: string; + }): void { parentRecords.forEach((item) => { if (relationResults.length === 0) { - (item as any)[`${relationName}Id`] = null; + item[`${relationName}Id`] = null; } - (item as any)[relationName] = + item[relationName] = relationResults.find((rel) => rel.id === item[`${relationName}Id`]) ?? null; }); + + parentObjectRecordsAggregatedFields[relationName] = + relationAggregatedFieldsResult; } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index e37cd78d8132..19bc28cf0ddd 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -83,15 +83,15 @@ export class GraphqlQueryCreateManyResolverService const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { - await processNestedRelationsHelper.processNestedRelations( + await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, - objectMetadataItemWithFieldMaps, - upsertedRecords, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: upsertedRecords, relations, - QUERY_MAX_RECORDS, + limit: QUERY_MAX_RECORDS, authContext, dataSource, - ); + }); } const typeORMObjectRecordsParser = diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts index 1a012384a836..8b4176d267f2 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -78,15 +78,15 @@ export class GraphqlQueryDestroyManyResolverService const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { - await processNestedRelationsHelper.processNestedRelations( + await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, - objectMetadataItemWithFieldMaps, - deletedRecords, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: deletedRecords, relations, - QUERY_MAX_RECORDS, + limit: QUERY_MAX_RECORDS, authContext, dataSource, - ); + }); } const typeORMObjectRecordsParser = diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts index 4e1e6fb7a0d2..044370a0730a 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts @@ -87,15 +87,15 @@ export class GraphqlQueryDestroyOneResolverService const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { - await processNestedRelationsHelper.processNestedRelations( + await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, - objectMetadataItemWithFieldMaps, - [recordBeforeDeletion], + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: [recordBeforeDeletion], relations, - QUERY_MAX_RECORDS, + limit: QUERY_MAX_RECORDS, authContext, dataSource, - ); + }); } const typeORMObjectRecordsParser = diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts index ebc2200c1cc1..d6c7335559fb 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -94,6 +94,7 @@ export class GraphqlQueryFindDuplicatesResolverService return typeORMObjectRecordsParser.createConnection({ objectRecords: [], objectName: objectMetadataItemWithFieldMaps.nameSingular, + objectRecordsAggregatedFields: {}, take: 0, totalCount: 0, order: [{ id: OrderByDirection.AscNullsFirst }], @@ -120,6 +121,7 @@ export class GraphqlQueryFindDuplicatesResolverService return typeORMObjectRecordsParser.createConnection({ objectRecords: duplicates, objectName: objectMetadataItemWithFieldMaps.nameSingular, + objectRecordsAggregatedFields: {}, take: duplicates.length, totalCount: duplicates.length, order: [{ id: OrderByDirection.AscNullsFirst }], 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 88a6922f257d..b712729dda2c 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 @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import graphqlFields from 'graphql-fields'; -import { SelectQueryBuilder } from 'typeorm'; import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { @@ -13,25 +12,24 @@ import { import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; -import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; import { computeCursorArgFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter'; import { getCursor, getPaginationInfo, } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; -import { - AggregationField, - getAvailableAggregationsFromObjectFields, -} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { isDefined } from 'src/utils/is-defined'; @@ -42,6 +40,7 @@ export class GraphqlQueryFindManyResolverService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly featureFlagService: FeatureFlagService, ) {} async resolve< @@ -72,7 +71,7 @@ export class GraphqlQueryFindManyResolverService objectMetadataItemWithFieldMaps.nameSingular, ); - const countQueryBuilder = repository.createQueryBuilder( + const aggregateQueryBuilder = repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); @@ -81,30 +80,28 @@ export class GraphqlQueryFindManyResolverService objectMetadataMaps, ); - const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder( - countQueryBuilder, - objectMetadataItemWithFieldMaps.nameSingular, - args.filter ?? ({} as Filter), - ); + const withFilterAggregateQueryBuilder = + graphqlQueryParser.applyFilterToBuilder( + aggregateQueryBuilder, + objectMetadataItemWithFieldMaps.nameSingular, + args.filter ?? ({} as Filter), + ); const selectedFields = graphqlFields(info); - const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); + const graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult = + graphqlQueryParser.parseSelectedFields( + objectMetadataItemWithFieldMaps, + selectedFields, + ); const isForwardPagination = !isDefined(args.before); - const withDeletedCountQueryBuilder = + const withDeletedAggregateQueryBuilder = graphqlQueryParser.applyDeletedAtToBuilder( - withFilterCountQueryBuilder, + withFilterAggregateQueryBuilder, args.filter ?? ({} as Filter), ); - const totalCount = isDefined(selectedFields.totalCount) - ? await withDeletedCountQueryBuilder.getCount() - : 0; - const cursor = getCursor(args); let appliedFilters = args.filter ?? ({} as Filter); @@ -147,24 +144,34 @@ export class GraphqlQueryFindManyResolverService args.filter ?? ({} as Filter), ); - const selectedAggregatedFields = this.getSelectedAggregatedFields({ - objectFields: Object.values(objectMetadataItemWithFieldMaps.fields), - selectedFields, - }); + const isAggregationsEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsAggregateQueryEnabled, + authContext.workspace.id, + ); - this.addSelectedAggregatedFieldsQueriesToQueryBuilder({ - selectedAggregatedFields, - queryBuilder: withDeletedQueryBuilder, + if (!isAggregationsEnabled) { + graphqlQuerySelectedFieldsResult.aggregate = { + totalCount: graphqlQuerySelectedFieldsResult.aggregate.totalCount, + }; + } + + const processAggregateHelper = new ProcessAggregateHelper(); + + processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({ + fieldMetadataMapByName: objectMetadataItemWithFieldMaps.fieldsByName, + selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, + queryBuilder: withDeletedAggregateQueryBuilder, }); const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS; const nonFormattedObjectRecords = await withDeletedQueryBuilder .take(limit + 1) - .getRawAndEntities(); + .getMany(); const objectRecords = formatResult( - nonFormattedObjectRecords.entities, + nonFormattedObjectRecords, objectMetadataItemWithFieldMaps, objectMetadataMaps, ); @@ -179,39 +186,39 @@ export class GraphqlQueryFindManyResolverService objectRecords.pop(); } + const objectRecordsAggregatedFields = + await withDeletedAggregateQueryBuilder.getRawOne(); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - if (relations) { - await processNestedRelationsHelper.processNestedRelations( + if (graphqlQuerySelectedFieldsResult.relations) { + await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, - objectMetadataItemWithFieldMaps, - objectRecords, - relations, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: objectRecords, + parentObjectRecordsAggregatedFields: objectRecordsAggregatedFields, + relations: graphqlQuerySelectedFieldsResult.relations, + aggregate: graphqlQuerySelectedFieldsResult.aggregate, limit, authContext, dataSource, - ); + }); } const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - const result = typeORMObjectRecordsParser.createConnection({ + return typeORMObjectRecordsParser.createConnection({ objectRecords, + objectRecordsAggregatedFields, + selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, objectName: objectMetadataItemWithFieldMaps.nameSingular, take: limit, - totalCount, + totalCount: objectRecordsAggregatedFields.totalCount, order: orderByWithIdCondition, hasNextPage, hasPreviousPage, }); - - const aggregatedFieldsResults = this.extractAggregatedFieldsResults({ - selectedAggregatedFields, - rawObjectRecords: nonFormattedObjectRecords.raw, - }); - - return { ...result, ...aggregatedFieldsResults }; } async validate( @@ -255,73 +262,4 @@ export class GraphqlQueryFindManyResolverService ); } } - - private addSelectedAggregatedFieldsQueriesToQueryBuilder = ({ - selectedAggregatedFields, - queryBuilder, - }: { - selectedAggregatedFields: AggregationField[]; - queryBuilder: SelectQueryBuilder; - }) => { - selectedAggregatedFields.forEach((aggregatedField) => { - const [[aggregatedFieldName, aggregatedFieldDetails]] = - Object.entries(aggregatedField); - const operation = aggregatedFieldDetails.aggregationOperation; - const fieldName = aggregatedFieldDetails.fromField; - - queryBuilder.addSelect( - `${operation}("${fieldName}") OVER()`, - `${aggregatedFieldName}`, - ); - }); - }; - - private getSelectedAggregatedFields = ({ - objectFields, - selectedFields, - }: { - objectFields: FieldMetadataInterface[]; - selectedFields: any[]; - }) => { - const allAggregatedFields = - getAvailableAggregationsFromObjectFields(objectFields); - - return allAggregatedFields.reduce( - (acc, aggregatedField) => { - const aggregatedFieldName = Object.keys(aggregatedField)[0]; - - if (!Object.keys(selectedFields).includes(aggregatedFieldName)) - return acc; - - const isDuplicate = acc.some( - (existingField) => - Object.keys(existingField)[0] === aggregatedFieldName, - ); - - if (isDuplicate) { - return acc; - } - - return [...acc, aggregatedField]; - }, - [] as typeof allAggregatedFields, - ); - }; - - private extractAggregatedFieldsResults = ({ - selectedAggregatedFields, - rawObjectRecords, - }: { - selectedAggregatedFields: AggregationField[]; - rawObjectRecords: any[]; - }) => { - return selectedAggregatedFields.reduce((acc, aggregatedField) => { - const aggregatedFieldName = Object.keys(aggregatedField)[0]; - - return { - ...acc, - [aggregatedFieldName]: rawObjectRecords[0][aggregatedFieldName], - }; - }, {}); - }; } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts index 4a249bad56fa..bcd076a1729d 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -103,15 +103,15 @@ export class GraphqlQueryFindOneResolverService const objectRecords = [objectRecord]; if (relations) { - await processNestedRelationsHelper.processNestedRelations( + await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, - objectMetadataItemWithFieldMaps, - objectRecords, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: objectRecords, relations, - QUERY_MAX_RECORDS, + limit: QUERY_MAX_RECORDS, authContext, dataSource, - ); + }); } const typeORMObjectRecordsParser = diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts index 16967651efd3..461940be3f3f 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -89,15 +89,15 @@ export class GraphqlQueryUpdateManyResolverService const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { - await processNestedRelationsHelper.processNestedRelations( + await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, - objectMetadataItemWithFieldMaps, - updatedRecords, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: updatedRecords, relations, - QUERY_MAX_RECORDS, + limit: QUERY_MAX_RECORDS, authContext, dataSource, - ); + }); } const typeORMObjectRecordsParser = diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts index a44412b2c5f5..6475e6488d50 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -93,15 +93,15 @@ export class GraphqlQueryUpdateOneResolverService const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { - await processNestedRelationsHelper.processNestedRelations( + await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, - objectMetadataItemWithFieldMaps, - [updatedRecord], + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: [updatedRecord], relations, - QUERY_MAX_RECORDS, + limit: QUERY_MAX_RECORDS, authContext, dataSource, - ); + }); } const typeORMObjectRecordsParser = diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory.ts new file mode 100644 index 000000000000..dec6bd49adb3 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; + +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { + AggregationField, + getAvailableAggregationsFromObjectFields, +} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; + +type AggregationGraphQLType = Pick; + +@Injectable() +export class AggregationTypeFactory { + public create( + objectMetadata: ObjectMetadataInterface, + ): Record { + const availableAggregations = getAvailableAggregationsFromObjectFields( + objectMetadata.fields, + ); + + return Object.entries(availableAggregations).reduce< + Record + >((acc, [key, agg]) => { + acc[key] = { + type: agg.type, + description: agg.description, + }; + + return acc; + }, {}); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts index 71afe756bf33..e691609b01d2 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { GraphQLFieldConfigMap, GraphQLInt, GraphQLObjectType } from 'graphql'; +import { GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql'; import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -import { getAvailableAggregationsFromObjectFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; +import { AggregationTypeFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory'; import { pascalCase } from 'src/utils/pascal-case'; import { ConnectionTypeFactory } from './connection-type.factory'; @@ -21,7 +21,10 @@ export enum ConnectionTypeDefinitionKind { @Injectable() export class ConnectionTypeDefinitionFactory { - constructor(private readonly connectionTypeFactory: ConnectionTypeFactory) {} + constructor( + private readonly connectionTypeFactory: ConnectionTypeFactory, + private readonly aggregationTypeFactory: AggregationTypeFactory, + ) {} public create( objectMetadata: ObjectMetadataInterface, @@ -44,25 +47,11 @@ export class ConnectionTypeDefinitionFactory { objectMetadata: ObjectMetadataInterface, options: WorkspaceBuildSchemaOptions, ): GraphQLFieldConfigMap { - const fields: GraphQLFieldConfigMap = Object.assign( - {}, - ...getAvailableAggregationsFromObjectFields(objectMetadata.fields).map( - (agg) => { - const [ - [ - key, - { - aggregationOperation: _aggregationOperation, - fromField: _fromField, - ...rest - }, - ], - ] = Object.entries(agg); + const fields: GraphQLFieldConfigMap = {}; - return { [key]: rest }; - }, - ), - ); + const aggregatedFields = this.aggregationTypeFactory.create(objectMetadata); + + Object.assign(fields, aggregatedFields); fields.edges = { type: this.connectionTypeFactory.create( @@ -88,11 +77,6 @@ export class ConnectionTypeDefinitionFactory { ), }; - fields.totalCount = { - type: GraphQLInt, - description: 'Total number of records in the connection', - }; - return fields; } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/factories.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/factories.ts index b28c497092aa..1e1219192d5c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/factories.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/factories.ts @@ -1,23 +1,24 @@ -import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory'; -import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory'; -import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory'; +import { AggregationTypeFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory'; import { CompositeEnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-enum-type-definition.factory'; +import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory'; +import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory'; +import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory'; import { ArgsFactory } from './args.factory'; -import { InputTypeFactory } from './input-type.factory'; +import { ConnectionTypeDefinitionFactory } from './connection-type-definition.factory'; +import { ConnectionTypeFactory } from './connection-type.factory'; +import { EdgeTypeDefinitionFactory } from './edge-type-definition.factory'; +import { EdgeTypeFactory } from './edge-type.factory'; +import { ExtendObjectTypeDefinitionFactory } from './extend-object-type-definition.factory'; import { InputTypeDefinitionFactory } from './input-type-definition.factory'; +import { InputTypeFactory } from './input-type.factory'; +import { MutationTypeFactory } from './mutation-type.factory'; import { ObjectTypeDefinitionFactory } from './object-type-definition.factory'; +import { OrphanedTypesFactory } from './orphaned-types.factory'; import { OutputTypeFactory } from './output-type.factory'; import { QueryTypeFactory } from './query-type.factory'; -import { RootTypeFactory } from './root-type.factory'; -import { ConnectionTypeFactory } from './connection-type.factory'; -import { ConnectionTypeDefinitionFactory } from './connection-type-definition.factory'; -import { EdgeTypeFactory } from './edge-type.factory'; -import { EdgeTypeDefinitionFactory } from './edge-type-definition.factory'; -import { MutationTypeFactory } from './mutation-type.factory'; import { RelationTypeFactory } from './relation-type.factory'; -import { ExtendObjectTypeDefinitionFactory } from './extend-object-type-definition.factory'; -import { OrphanedTypesFactory } from './orphaned-types.factory'; +import { RootTypeFactory } from './root-type.factory'; export const workspaceSchemaBuilderFactories = [ ArgsFactory, @@ -39,4 +40,5 @@ export const workspaceSchemaBuilderFactories = [ QueryTypeFactory, MutationTypeFactory, OrphanedTypesFactory, + AggregationTypeFactory, ]; 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 758d8a754661..20ea94644538 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 @@ -1,6 +1,6 @@ import { GraphQLISODateTime } from '@nestjs/graphql'; -import { GraphQLFloat, GraphQLString } from 'graphql'; +import { GraphQLFloat, GraphQLInt, GraphQLScalarType } from 'graphql'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; @@ -12,83 +12,73 @@ enum AGGREGATION_OPERATIONS { max = 'MAX', avg = 'AVG', sum = 'SUM', + count = 'COUNT', } -type AggregationValue = { - type: typeof GraphQLString; +export type AggregationField = { + type: GraphQLScalarType; description: string; fromField: string; aggregationOperation: AGGREGATION_OPERATIONS; }; -export type AggregationField = { - [key: string]: AggregationValue; -}; - export const getAvailableAggregationsFromObjectFields = ( fields: FieldMetadataInterface[], -): AggregationField[] => { - return fields.reduce>>((acc, field) => { +): Record => { + return fields.reduce>((acc, field) => { + acc['totalCount'] = { + type: GraphQLInt, + description: `Total number of records in the connection`, + fromField: 'id', + aggregationOperation: AGGREGATION_OPERATIONS.count, + }; + if (field.type === FieldMetadataType.DATE_TIME) { - return [ - ...acc, - { - [`min${capitalize(field.name)}`]: { - type: GraphQLISODateTime, - description: `Oldest date contained in the field ${field.name}`, - fromField: field.name, - aggregationOperation: AGGREGATION_OPERATIONS.min, - }, - }, - { - [`max${capitalize(field.name)}`]: { - type: GraphQLISODateTime, - description: `Most recent date contained in the field ${field.name}`, - fromField: field.name, - aggregationOperation: AGGREGATION_OPERATIONS.max, - }, - }, - ]; + acc[`min${capitalize(field.name)}`] = { + type: GraphQLISODateTime, + description: `Oldest date contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.min, + }; + + acc[`max${capitalize(field.name)}`] = { + type: GraphQLISODateTime, + description: `Most recent date contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.max, + }; } if (field.type === FieldMetadataType.NUMBER) { - return [ - ...acc, - { - [`min${capitalize(field.name)}`]: { - type: GraphQLFloat, - description: `Minimum amount contained in the field ${field.name}`, - fromField: field.name, - aggregationOperation: AGGREGATION_OPERATIONS.min, - }, - }, - { - [`max${capitalize(field.name)}`]: { - type: GraphQLFloat, - description: `Maximum amount contained in the field ${field.name}`, - fromField: field.name, - aggregationOperation: AGGREGATION_OPERATIONS.max, - }, - }, - { - [`avg${capitalize(field.name)}`]: { - type: GraphQLFloat, - description: `Average amount contained in the field ${field.name}`, - fromField: field.name, - aggregationOperation: AGGREGATION_OPERATIONS.avg, - }, - }, - { - [`sum${capitalize(field.name)}`]: { - type: GraphQLFloat, - description: `Sum of amounts contained in the field ${field.name}`, - fromField: field.name, - aggregationOperation: AGGREGATION_OPERATIONS.sum, - }, - }, - ]; + acc[`min${capitalize(field.name)}`] = { + type: GraphQLFloat, + description: `Minimum amount contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.min, + }; + + acc[`max${capitalize(field.name)}`] = { + type: GraphQLFloat, + description: `Maximum amount contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.max, + }; + + acc[`avg${capitalize(field.name)}`] = { + type: GraphQLFloat, + description: `Average amount contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.avg, + }; + + acc[`sum${capitalize(field.name)}`] = { + type: GraphQLFloat, + description: `Sum of amounts contained in the field ${field.name}`, + fromField: field.name, + aggregationOperation: AGGREGATION_OPERATIONS.sum, + }; } return acc; - }, []); + }, {}); }; From 1aee63c9afd725ebad3a39db6e63465dc84c469d Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 14 Nov 2024 17:20:14 +0100 Subject: [PATCH 29/30] fix --- ...raphql-selected-fields-aggregate.parser.ts | 4 +- ...graphql-selected-fields-relation.parser.ts | 14 ++-- .../graphql-selected-fields.parser.ts | 24 +++--- ...ct-records-to-graphql-connection.helper.ts | 54 ++++++++----- .../process-nested-relations.helper.ts | 80 ++++++++++++------- ...-query-find-duplicates-resolver.service.ts | 2 - ...raphql-query-find-many-resolver.service.ts | 8 +- 7 files changed, 111 insertions(+), 75 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser.ts index 18f3b8c2d9f7..fcd3ed6a1458 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser.ts @@ -10,7 +10,7 @@ export class GraphqlQuerySelectedFieldsAggregateParser { parse( graphqlSelectedFields: Partial>, fieldMetadataMapByName: Record, - result: GraphqlQuerySelectedFieldsResult, + accumulator: GraphqlQuerySelectedFieldsResult, ): void { const availableAggregations: Record = getAvailableAggregationsFromObjectFields( @@ -24,7 +24,7 @@ export class GraphqlQuerySelectedFieldsAggregateParser { continue; } - result.aggregate[selectedField] = selectedAggregation; + accumulator.aggregate[selectedField] = selectedAggregation; } } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts index 94f8aa71b97a..54f294acb455 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser.ts @@ -18,13 +18,13 @@ export class GraphqlQuerySelectedFieldsRelationParser { fieldMetadata: FieldMetadataInterface, fieldKey: string, fieldValue: any, - result: GraphqlQuerySelectedFieldsResult, + accumulator: GraphqlQuerySelectedFieldsResult, ): void { if (!fieldValue || typeof fieldValue !== 'object') { return; } - result.relations[fieldKey] = true; + accumulator.relations[fieldKey] = true; const referencedObjectMetadata = getRelationObjectMetadata( fieldMetadata, @@ -35,13 +35,13 @@ export class GraphqlQuerySelectedFieldsRelationParser { const fieldParser = new GraphqlQuerySelectedFieldsParser( this.objectMetadataMaps, ); - const subResult = fieldParser.parse(fieldValue, relationFields); + const relationAccumulator = fieldParser.parse(fieldValue, relationFields); - result.select[fieldKey] = { + accumulator.select[fieldKey] = { id: true, - ...subResult.select, + ...relationAccumulator.select, }; - result.relations[fieldKey] = subResult.relations; - result.aggregate[fieldKey] = subResult.aggregate; + accumulator.relations[fieldKey] = relationAccumulator.relations; + accumulator.aggregate[fieldKey] = relationAccumulator.aggregate; } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts index e1a9b1f752a1..5c35b1eff0c4 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts @@ -29,7 +29,7 @@ export class GraphqlQuerySelectedFieldsParser { graphqlSelectedFields: Partial>, fieldMetadataMapByName: Record, ): GraphqlQuerySelectedFieldsResult { - const result: GraphqlQuerySelectedFieldsResult = { + const accumulator: GraphqlQuerySelectedFieldsResult = { select: {}, relations: {}, aggregate: {}, @@ -39,23 +39,25 @@ export class GraphqlQuerySelectedFieldsParser { this.parseConnectionField( graphqlSelectedFields, fieldMetadataMapByName, - result, + accumulator, ); + + return accumulator; } this.parseRecordField( graphqlSelectedFields, fieldMetadataMapByName, - result, + accumulator, ); - return result; + return accumulator; } private parseRecordField( graphqlSelectedFields: Partial>, fieldMetadataMapByName: Record, - result: GraphqlQuerySelectedFieldsResult, + accumulator: GraphqlQuerySelectedFieldsResult, ): void { for (const [fieldKey, fieldValue] of Object.entries( graphqlSelectedFields, @@ -71,7 +73,7 @@ export class GraphqlQuerySelectedFieldsParser { fieldMetadata, fieldKey, fieldValue, - result, + accumulator, ); } else if (isCompositeFieldMetadataType(fieldMetadata.type)) { const compositeResult = this.parseCompositeField( @@ -79,9 +81,9 @@ export class GraphqlQuerySelectedFieldsParser { fieldValue, ); - Object.assign(result.select, compositeResult); + Object.assign(accumulator.select, compositeResult); } else { - result.select[fieldKey] = true; + accumulator.select[fieldKey] = true; } } } @@ -89,17 +91,17 @@ export class GraphqlQuerySelectedFieldsParser { private parseConnectionField( graphqlSelectedFields: Partial>, fieldMetadataMapByName: Record, - result: GraphqlQuerySelectedFieldsResult, + accumulator: GraphqlQuerySelectedFieldsResult, ): void { this.aggregateParser.parse( graphqlSelectedFields, fieldMetadataMapByName, - result, + accumulator, ); const node = graphqlSelectedFields.edges.node; - this.parseRecordField(node, fieldMetadataMapByName, result); + this.parseRecordField(node, fieldMetadataMapByName, accumulator); } private isRootConnection( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index 12e6248cdec1..0553c2e73666 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -30,7 +30,8 @@ export class ObjectRecordsToGraphqlConnectionHelper { public createConnection({ objectRecords, - objectRecordsAggregatedFields = {}, + parentObjectRecord, + objectRecordsAggregatedValues = {}, selectedAggregatedFields = [], objectName, take, @@ -41,7 +42,8 @@ export class ObjectRecordsToGraphqlConnectionHelper { depth = 0, }: { objectRecords: T[]; - objectRecordsAggregatedFields?: Record; + parentObjectRecord?: T; + objectRecordsAggregatedValues?: Record; selectedAggregatedFields?: Record; objectName: string; take: number; @@ -55,7 +57,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { node: this.processRecord({ objectRecord, objectName, - objectRecordsAggregatedFields, + objectRecordsAggregatedValues, selectedAggregatedFields, take, totalCount, @@ -65,11 +67,15 @@ export class ObjectRecordsToGraphqlConnectionHelper { cursor: encodeCursor(objectRecord, order), })); + const aggregatedFieldsValues = this.extractAggregatedFieldsValues({ + selectedAggregatedFields, + objectRecordsAggregatedValues: parentObjectRecord + ? objectRecordsAggregatedValues[parentObjectRecord.id] + : objectRecordsAggregatedValues, + }); + return { - ...this.extractAggregatedFieldsResults({ - selectedAggregatedFields, - objectRecordsAggregatedFields, - }), + ...aggregatedFieldsValues, edges, pageInfo: { hasNextPage, @@ -81,19 +87,30 @@ export class ObjectRecordsToGraphqlConnectionHelper { }; } - private extractAggregatedFieldsResults = ({ + private extractAggregatedFieldsValues = ({ selectedAggregatedFields, - objectRecordsAggregatedFields, + objectRecordsAggregatedValues, }: { selectedAggregatedFields: Record; - objectRecordsAggregatedFields: Record; + objectRecordsAggregatedValues: Record; }) => { + if (!objectRecordsAggregatedValues) { + return {}; + } + return Object.entries(selectedAggregatedFields).reduce( (acc, [aggregatedFieldName]) => { + const aggregatedFieldValue = + objectRecordsAggregatedValues[aggregatedFieldName]; + + if (!aggregatedFieldValue) { + return acc; + } + return { ...acc, [aggregatedFieldName]: - objectRecordsAggregatedFields[aggregatedFieldName], + objectRecordsAggregatedValues[aggregatedFieldName], }; }, {}, @@ -103,7 +120,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { public processRecord>({ objectRecord, objectName, - objectRecordsAggregatedFields = {}, + objectRecordsAggregatedValues = {}, selectedAggregatedFields = [], take, totalCount, @@ -112,7 +129,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { }: { objectRecord: T; objectName: string; - objectRecordsAggregatedFields?: Record; + objectRecordsAggregatedValues?: Record; selectedAggregatedFields?: Record; take: number; totalCount: number; @@ -149,8 +166,9 @@ export class ObjectRecordsToGraphqlConnectionHelper { if (Array.isArray(value)) { processedObjectRecord[key] = this.createConnection({ objectRecords: value, - objectRecordsAggregatedFields: - objectRecordsAggregatedFields[fieldMetadata.name], + parentObjectRecord: objectRecord, + objectRecordsAggregatedValues: + objectRecordsAggregatedValues[fieldMetadata.name], selectedAggregatedFields: selectedAggregatedFields[fieldMetadata.name], objectName: getRelationObjectMetadata( @@ -159,7 +177,7 @@ export class ObjectRecordsToGraphqlConnectionHelper { ).nameSingular, take, totalCount: - objectRecordsAggregatedFields[fieldMetadata.name]?.totalCount ?? + objectRecordsAggregatedValues[fieldMetadata.name]?.totalCount ?? value.length, order, hasNextPage: false, @@ -169,8 +187,8 @@ export class ObjectRecordsToGraphqlConnectionHelper { } else if (isPlainObject(value)) { processedObjectRecord[key] = this.processRecord({ objectRecord: value, - objectRecordsAggregatedFields: - objectRecordsAggregatedFields[fieldMetadata.name], + objectRecordsAggregatedValues: + objectRecordsAggregatedValues[fieldMetadata.name], selectedAggregatedFields: selectedAggregatedFields[fieldMetadata.name], objectName: getRelationObjectMetadata( 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 f6a08f24f551..1a4e7896d1b9 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 @@ -29,7 +29,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, - parentObjectRecordsAggregatedFields = {}, + parentObjectRecordsAggregatedValues = {}, relations, aggregate = {}, limit, @@ -39,7 +39,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps: ObjectMetadataMaps; parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; parentObjectRecords: T[]; - parentObjectRecordsAggregatedFields?: Record; + parentObjectRecordsAggregatedValues?: Record; relations: Record>; aggregate?: Record; limit: number; @@ -52,7 +52,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relationName, nestedRelations, aggregate, @@ -69,7 +69,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relationName, nestedRelations, aggregate, @@ -80,7 +80,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps: ObjectMetadataMaps; parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; parentObjectRecords: T[]; - parentObjectRecordsAggregatedFields: Record; + parentObjectRecordsAggregatedValues: Record; relationName: string; nestedRelations: any; aggregate: Record; @@ -105,7 +105,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relationName, nestedRelations, aggregate, @@ -119,7 +119,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relationName, nestedRelations, aggregate, @@ -130,7 +130,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps: ObjectMetadataMaps; parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; parentObjectRecords: T[]; - parentObjectRecordsAggregatedFields: Record; + parentObjectRecordsAggregatedValues: Record; relationName: string; nestedRelations: any; aggregate: Record; @@ -148,7 +148,7 @@ export class ProcessNestedRelationsHelper { referenceObjectMetadata.nameSingular, ); - const queryBuilder = relationRepository.createQueryBuilder( + const referenceQueryBuilder = relationRepository.createQueryBuilder( referenceObjectMetadata.nameSingular, ); @@ -158,7 +158,7 @@ export class ProcessNestedRelationsHelper { }); const { relationResults, relationAggregatedFieldsResult } = await this.findRelations({ - queryBuilder, + referenceQueryBuilder, column: `"${inverseRelationName}Id"`, ids: relationIds, limit: limit * parentObjectRecords.length, @@ -170,7 +170,7 @@ export class ProcessNestedRelationsHelper { this.assignFromRelationResults({ parentRecords: parentObjectRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relationResults, relationAggregatedFieldsResult, relationName, @@ -185,7 +185,7 @@ export class ProcessNestedRelationsHelper { referenceObjectMetadata.nameSingular ], parentObjectRecords: relationResults as ObjectRecord[], - parentObjectRecordsAggregatedFields: relationAggregatedFieldsResult, + parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult, relations: nestedRelations as Record< string, FindOptionsRelations @@ -202,7 +202,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps, parentObjectMetadataItem, parentObjectRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relationName, nestedRelations, aggregate, @@ -213,7 +213,7 @@ export class ProcessNestedRelationsHelper { objectMetadataMaps: ObjectMetadataMaps; parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; parentObjectRecords: T[]; - parentObjectRecordsAggregatedFields: Record; + parentObjectRecordsAggregatedValues: Record; relationName: string; nestedRelations: any; aggregate: Record; @@ -230,7 +230,7 @@ export class ProcessNestedRelationsHelper { referenceObjectMetadata.nameSingular, ); - const queryBuilder = relationRepository.createQueryBuilder( + const referenceQueryBuilder = relationRepository.createQueryBuilder( referenceObjectMetadata.nameSingular, ); @@ -240,7 +240,7 @@ export class ProcessNestedRelationsHelper { }); const { relationResults, relationAggregatedFieldsResult } = await this.findRelations({ - queryBuilder, + referenceQueryBuilder, column: 'id', ids: relationIds, limit, @@ -252,7 +252,7 @@ export class ProcessNestedRelationsHelper { this.assignToRelationResults({ parentRecords: parentObjectRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues: parentObjectRecordsAggregatedValues, relationResults, relationAggregatedFieldsResult, relationName, @@ -266,7 +266,7 @@ export class ProcessNestedRelationsHelper { referenceObjectMetadata.nameSingular ], parentObjectRecords: relationResults as ObjectRecord[], - parentObjectRecordsAggregatedFields: relationAggregatedFieldsResult, + parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult, relations: nestedRelations as Record< string, FindOptionsRelations @@ -314,7 +314,7 @@ export class ProcessNestedRelationsHelper { } private async findRelations({ - queryBuilder, + referenceQueryBuilder, column, ids, limit, @@ -323,7 +323,7 @@ export class ProcessNestedRelationsHelper { aggregate, relationName, }: { - queryBuilder: SelectQueryBuilder; + referenceQueryBuilder: SelectQueryBuilder; column: string; ids: any[]; limit: number; @@ -337,10 +337,10 @@ export class ProcessNestedRelationsHelper { } const aggregateForRelation = aggregate[relationName]; - let relationAggregatedFieldsResult = {}; + let relationAggregatedFieldsResult: Record = {}; if (aggregateForRelation) { - const aggregateQueryBuilder = queryBuilder.clone(); + const aggregateQueryBuilder = referenceQueryBuilder.clone(); this.processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder( { @@ -350,11 +350,29 @@ export class ProcessNestedRelationsHelper { }, ); - relationAggregatedFieldsResult = - (await aggregateQueryBuilder.getRawOne()) ?? {}; + const aggregatedFieldsValues = await aggregateQueryBuilder + .addSelect(column) + .where(`${column} IN (:...ids)`, { + ids, + }) + .groupBy(column) + .getRawMany(); + + relationAggregatedFieldsResult = aggregatedFieldsValues.reduce( + (acc, item) => { + const columnWithoutQuotes = column.replace(/["']/g, ''); + const key = item[columnWithoutQuotes]; + const { [column]: _, ...itemWithoutColumn } = item; + + acc[key] = itemWithoutColumn; + + return acc; + }, + {}, + ); } - const result = await queryBuilder + const result = await referenceQueryBuilder .where(`${column} IN (:...ids)`, { ids, }) @@ -372,14 +390,14 @@ export class ProcessNestedRelationsHelper { private assignFromRelationResults({ parentRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relationResults, relationAggregatedFieldsResult, relationName, joinField, }: { parentRecords: ObjectRecord[]; - parentObjectRecordsAggregatedFields: Record; + parentObjectRecordsAggregatedValues: Record; relationResults: any[]; relationAggregatedFieldsResult: Record; relationName: string; @@ -391,19 +409,19 @@ export class ProcessNestedRelationsHelper { ); }); - parentObjectRecordsAggregatedFields[relationName] = + parentObjectRecordsAggregatedValues[relationName] = relationAggregatedFieldsResult; } private assignToRelationResults({ parentRecords, - parentObjectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relationResults, relationAggregatedFieldsResult, relationName, }: { parentRecords: ObjectRecord[]; - parentObjectRecordsAggregatedFields: Record; + parentObjectRecordsAggregatedValues: Record; relationResults: any[]; relationAggregatedFieldsResult: Record; relationName: string; @@ -417,7 +435,7 @@ export class ProcessNestedRelationsHelper { null; }); - parentObjectRecordsAggregatedFields[relationName] = + parentObjectRecordsAggregatedValues[relationName] = relationAggregatedFieldsResult; } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts index d6c7335559fb..ebc2200c1cc1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -94,7 +94,6 @@ export class GraphqlQueryFindDuplicatesResolverService return typeORMObjectRecordsParser.createConnection({ objectRecords: [], objectName: objectMetadataItemWithFieldMaps.nameSingular, - objectRecordsAggregatedFields: {}, take: 0, totalCount: 0, order: [{ id: OrderByDirection.AscNullsFirst }], @@ -121,7 +120,6 @@ export class GraphqlQueryFindDuplicatesResolverService return typeORMObjectRecordsParser.createConnection({ objectRecords: duplicates, objectName: objectMetadataItemWithFieldMaps.nameSingular, - objectRecordsAggregatedFields: {}, take: duplicates.length, totalCount: duplicates.length, order: [{ id: OrderByDirection.AscNullsFirst }], 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 b712729dda2c..9b4c850de096 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 @@ -186,7 +186,7 @@ export class GraphqlQueryFindManyResolverService objectRecords.pop(); } - const objectRecordsAggregatedFields = + const parentObjectRecordsAggregatedValues = await withDeletedAggregateQueryBuilder.getRawOne(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); @@ -196,7 +196,7 @@ export class GraphqlQueryFindManyResolverService objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectRecords: objectRecords, - parentObjectRecordsAggregatedFields: objectRecordsAggregatedFields, + parentObjectRecordsAggregatedValues, relations: graphqlQuerySelectedFieldsResult.relations, aggregate: graphqlQuerySelectedFieldsResult.aggregate, limit, @@ -210,11 +210,11 @@ export class GraphqlQueryFindManyResolverService return typeORMObjectRecordsParser.createConnection({ objectRecords, - objectRecordsAggregatedFields, + objectRecordsAggregatedValues: parentObjectRecordsAggregatedValues, selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, objectName: objectMetadataItemWithFieldMaps.nameSingular, take: limit, - totalCount: objectRecordsAggregatedFields.totalCount, + totalCount: parentObjectRecordsAggregatedValues.totalCount, order: orderByWithIdCondition, hasNextPage, hasPreviousPage, From 853126a31823aac5566f578c795ac07c2468e13f Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 14 Nov 2024 17:57:20 +0100 Subject: [PATCH 30/30] fix tests --- .../query-runner-args.factory.spec.ts | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/__tests__/query-runner-args.factory.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/__tests__/query-runner-args.factory.spec.ts index ea8e3f6c9e3a..f814e04f7c97 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/__tests__/query-runner-args.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/__tests__/query-runner-args.factory.spec.ts @@ -5,6 +5,8 @@ import { ResolverArgsType } from 'src/engine/api/graphql/workspace-resolver-buil import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory'; import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; describe('QueryRunnerArgsFactory', () => { const recordPositionFactory = { @@ -13,7 +15,28 @@ describe('QueryRunnerArgsFactory', () => { const workspaceId = 'workspaceId'; const options = { authContext: { workspace: { id: workspaceId } }, - } as WorkspaceQueryRunnerOptions; + objectMetadataItemWithFieldMaps: { + isCustom: true, + nameSingular: 'testNumber', + fieldsByName: { + position: { + type: FieldMetadataType.POSITION, + isCustom: true, + nameSingular: 'position', + }, + testNumber: { + type: FieldMetadataType.NUMBER, + isCustom: true, + nameSingular: 'testNumber', + }, + otherField: { + type: FieldMetadataType.TEXT, + isCustom: true, + nameSingular: 'otherField', + }, + } as unknown as FieldMetadataMap, + }, + } as unknown as WorkspaceQueryRunnerOptions; let factory: QueryRunnerArgsFactory; @@ -54,7 +77,7 @@ describe('QueryRunnerArgsFactory', () => { it('createMany type should override data position and number', async () => { const args = { id: 'uuid', - data: [{ position: 'last', testNumber: '1' }], + data: [{ position: 'last', testNumber: 1 }], }; const result = await factory.create( @@ -65,7 +88,7 @@ describe('QueryRunnerArgsFactory', () => { expect(recordPositionFactory.create).toHaveBeenCalledWith( 'last', - { isCustom: true, nameSingular: 'test' }, + { isCustom: true, nameSingular: 'testNumber' }, workspaceId, 0, ); @@ -78,7 +101,7 @@ describe('QueryRunnerArgsFactory', () => { it('createMany type should override position if not present', async () => { const args = { id: 'uuid', - data: [{ testNumber: '1' }], + data: [{ testNumber: 1 }], }; const result = await factory.create( @@ -89,7 +112,7 @@ describe('QueryRunnerArgsFactory', () => { expect(recordPositionFactory.create).toHaveBeenCalledWith( 'first', - { isCustom: true, nameSingular: 'test' }, + { isCustom: true, nameSingular: 'testNumber' }, workspaceId, 0, ); @@ -102,7 +125,7 @@ describe('QueryRunnerArgsFactory', () => { it('findMany type should override data position and number', async () => { const args = { id: 'uuid', - filter: { testNumber: { eq: '1' }, otherField: { eq: 'test' } }, + filter: { testNumber: { eq: 1 }, otherField: { eq: 'test' } }, }; const result = await factory.create( @@ -120,7 +143,7 @@ describe('QueryRunnerArgsFactory', () => { it('findOne type should override number in filter', async () => { const args = { id: 'uuid', - filter: { testNumber: { eq: '1' }, otherField: { eq: 'test' } }, + filter: { testNumber: { eq: 1 }, otherField: { eq: 'test' } }, }; const result = await factory.create( @@ -136,18 +159,14 @@ describe('QueryRunnerArgsFactory', () => { }); it('findDuplicates type should override number in data and id', async () => { - const optionsDuplicate = { - authContext: { workspace: { id: workspaceId } }, - } as WorkspaceQueryRunnerOptions; - const args = { - ids: ['123'], - data: [{ testNumber: '1', otherField: 'test' }], + ids: [123], + data: [{ testNumber: 1, otherField: 'test' }], }; const result = await factory.create( args, - optionsDuplicate, + options, ResolverArgsType.FindDuplicates, );