From 37d85a716ac5c8436f23e46188c118d13c5a54b5 Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 16 Sep 2024 12:20:04 +0200 Subject: [PATCH] [flexible-schema] Add createOne/createMany with upsert to graphql query runner (#7041) ## Context This PR introduces createOne/createMany through the new graphql query runner. Trying to use twentyOrm wrapper as much as possible, in this case here the args are already converted from "metadata-like" structure (including composite fields) as graphql input to typeorm / raw columns (I had to introduce a little fix there). Keep in mind that I'm not using the new graphql query runner parsing classes here, especially the selected-fields part, because typeorm already returns all the record columns in the InsertResult object (including default values such as id, createdAt, ...). That also means relation objects will be returned as NULL in the gql response but we don't handle nested creation for the moment so it should be fine. Note: also removing the feature flag from findOne/findMany --- .../graphql-query-runner.service.ts | 13 +++ ...phql-query-create-many-resolver.service.ts | 33 ++++++++ .../workspace-query-runner.service.ts | 82 ++++--------------- .../repository/workspace.repository.ts | 18 +++- 4 files changed, 78 insertions(+), 68 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts 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 78a98aa72..c709a711e 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 @@ -8,10 +8,12 @@ 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 { + CreateManyResolverArgs, FindManyResolverArgs, FindOneResolverArgs, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator'; @@ -51,4 +53,15 @@ export class GraphqlQueryRunnerService { return graphqlQueryFindManyResolverService.findMany(args, options); } + + @LogExecutionTime() + async createMany( + args: CreateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const graphqlQueryCreateManyResolverService = + new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); + + return graphqlQueryCreateManyResolverService.createMany(args, options); + } } 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 new file mode 100644 index 000000000..34ab5e849 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -0,0 +1,33 @@ +import { InsertResult } from 'typeorm'; + +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/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'; + +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +export class GraphqlQueryCreateManyResolverService { + private twentyORMGlobalManager: TwentyORMGlobalManager; + + constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { + this.twentyORMGlobalManager = twentyORMGlobalManager; + } + + async createMany( + args: CreateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { authContext, objectMetadataItem } = options; + const repository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + authContext.workspace.id, + objectMetadataItem.nameSingular, + ); + + const insertResult: InsertResult = !args.upsert + ? await repository.insert(args.data) + : await repository.upsert(args.data, ['id']); + + return insertResult.generatedMaps as ObjectRecord[]; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts index 6c7a36c9c..cc70a9c5b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts @@ -36,19 +36,18 @@ import { } from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { parseResult } from 'src/engine/api/graphql/workspace-query-runner/utils/parse-result.util'; -import { withSoftDeleted } from 'src/engine/api/graphql/workspace-query-runner/utils/with-soft-deleted.util'; import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; import { WorkspaceQueryRunnerException, WorkspaceQueryRunnerExceptionCode, } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; import { DuplicateService } from 'src/engine/core-modules/duplicate/duplicate.service'; -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 { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; 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 { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; +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 { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; @@ -99,13 +98,6 @@ export class WorkspaceQueryRunnerService { options: WorkspaceQueryRunnerOptions, ): Promise | undefined> { const { authContext, objectMetadataItem } = options; - const start = performance.now(); - - const isQueryRunnerTwentyORMEnabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, - authContext.workspace.id, - ); const hookedArgs = await this.workspaceQueryHookService.executePreQueryHooks( @@ -121,34 +113,7 @@ export class WorkspaceQueryRunnerService { ResolverArgsType.FindMany, )) as FindManyResolverArgs; - if (isQueryRunnerTwentyORMEnabled) { - return this.graphqlQueryRunnerService.findMany(computedArgs, options); - } - - const query = await this.workspaceQueryBuilderFactory.findMany( - computedArgs, - { - ...options, - withSoftDeleted: withSoftDeleted(args.filter), - }, - ); - - const result = await this.execute(query, authContext.workspace.id); - - const end = performance.now(); - - this.logger.log( - `query time: ${end - start} ms on query ${ - options.objectMetadataItem.nameSingular - }`, - ); - - return this.parseResult>( - result, - objectMetadataItem, - '', - authContext.workspace.id, - ); + return this.graphqlQueryRunnerService.findMany(computedArgs, options); } async findOne< @@ -166,12 +131,6 @@ export class WorkspaceQueryRunnerService { } const { authContext, objectMetadataItem } = options; - const isQueryRunnerTwentyORMEnabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, - authContext.workspace.id, - ); - const hookedArgs = await this.workspaceQueryHookService.executePreQueryHooks( authContext, @@ -186,27 +145,7 @@ export class WorkspaceQueryRunnerService { ResolverArgsType.FindOne, )) as FindOneResolverArgs; - if (isQueryRunnerTwentyORMEnabled) { - return this.graphqlQueryRunnerService.findOne(computedArgs, options); - } - - const query = await this.workspaceQueryBuilderFactory.findOne( - computedArgs, - { - ...options, - withSoftDeleted: withSoftDeleted(args.filter), - }, - ); - - const result = await this.execute(query, authContext.workspace.id); - const parsedResult = await this.parseResult>( - result, - objectMetadataItem, - '', - authContext.workspace.id, - ); - - return parsedResult?.edges?.[0]?.node; + return this.graphqlQueryRunnerService.findOne(computedArgs, options); } async findDuplicates( @@ -283,6 +222,12 @@ export class WorkspaceQueryRunnerService { ): Promise { const { authContext, objectMetadataItem } = options; + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + authContext.workspace.id, + ); + assertMutationNotOnRemoteObject(objectMetadataItem); if (args.upsert) { @@ -309,6 +254,13 @@ export class WorkspaceQueryRunnerService { ResolverArgsType.CreateMany, )) as CreateManyResolverArgs; + if (isQueryRunnerTwentyORMEnabled) { + return (await this.graphqlQueryRunnerService.createMany( + computedArgs, + options, + )) as Record[]; + } + const query = await this.workspaceQueryBuilderFactory.createMany( computedArgs, options, 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 06335af63..a264b44bf 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 @@ -428,9 +428,13 @@ export class WorkspaceRepository< const formatedEntity = await this.formatData(entity); const result = await manager.insert(this.target, formatedEntity); - const formattedResult = await this.formatResult(result); + const formattedResult = await this.formatResult(result.generatedMaps); - return formattedResult; + return { + raw: result.raw, + generatedMaps: formattedResult, + identifiers: result.identifiers, + }; } /** @@ -470,11 +474,19 @@ export class WorkspaceRepository< const formattedEntityOrEntities = await this.formatData(entityOrEntities); - return manager.upsert( + const result = await manager.upsert( this.target, formattedEntityOrEntities, conflictPathsOrOptions, ); + + const formattedResult = await this.formatResult(result.generatedMaps); + + return { + raw: result.raw, + generatedMaps: formattedResult, + identifiers: result.identifiers, + }; } /**