[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
This commit is contained in:
Weiko 2024-09-16 12:20:04 +02:00 committed by GitHub
parent 64756dc699
commit 37d85a716a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 78 additions and 68 deletions

View File

@ -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<ObjectRecord extends IRecord = IRecord>(
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord[] | undefined> {
const graphqlQueryCreateManyResolverService =
new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager);
return graphqlQueryCreateManyResolverService.createMany(args, options);
}
}

View File

@ -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<ObjectRecord extends IRecord = IRecord>(
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord[] | undefined> {
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[];
}
}

View File

@ -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<IConnection<Record> | 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<Filter, OrderBy>;
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<IConnection<Record>>(
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<Filter>;
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<IConnection<Record>>(
result,
objectMetadataItem,
'',
authContext.workspace.id,
);
return parsedResult?.edges?.[0]?.node;
return this.graphqlQueryRunnerService.findOne(computedArgs, options);
}
async findDuplicates<TRecord extends IRecord = IRecord>(
@ -283,6 +222,12 @@ export class WorkspaceQueryRunnerService {
): Promise<Record[] | undefined> {
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<Record>;
if (isQueryRunnerTwentyORMEnabled) {
return (await this.graphqlQueryRunnerService.createMany(
computedArgs,
options,
)) as Record[];
}
const query = await this.workspaceQueryBuilderFactory.createMany(
computedArgs,
options,

View File

@ -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,
};
}
/**