Add DestroyMany to graphql query runner (#7507)

## Context
destroyMany was not implemented, this PR adds it
This commit is contained in:
Weiko
2024-10-08 17:40:48 +02:00
committed by GitHub
parent e662f6ccb3
commit d5bd320b8d
14 changed files with 341 additions and 118 deletions

View File

@ -8,6 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service';
import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service';
import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service';
import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service';
import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service';
@ -37,6 +38,8 @@ export class GraphqlQueryResolverFactory {
return this.moduleRef.get(GraphqlQueryCreateManyResolverService); return this.moduleRef.get(GraphqlQueryCreateManyResolverService);
case 'destroyOne': case 'destroyOne':
return this.moduleRef.get(GraphqlQueryDestroyOneResolverService); return this.moduleRef.get(GraphqlQueryDestroyOneResolverService);
case 'destroyMany':
return this.moduleRef.get(GraphqlQueryDestroyManyResolverService);
case 'updateOne': case 'updateOne':
case 'deleteOne': case 'deleteOne':
return this.moduleRef.get(GraphqlQueryUpdateOneResolverService); return this.moduleRef.get(GraphqlQueryUpdateOneResolverService);

View File

@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory'; import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory';
import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service';
import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service';
import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service';
import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service';
import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service';
import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service';
@ -16,14 +17,15 @@ import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-que
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
const graphqlQueryResolvers = [ const graphqlQueryResolvers = [
GraphqlQueryFindOneResolverService,
GraphqlQueryFindManyResolverService,
GraphqlQueryFindDuplicatesResolverService,
GraphqlQueryCreateManyResolverService, GraphqlQueryCreateManyResolverService,
GraphqlQueryDestroyManyResolverService,
GraphqlQueryDestroyOneResolverService, GraphqlQueryDestroyOneResolverService,
GraphqlQueryUpdateOneResolverService, GraphqlQueryFindDuplicatesResolverService,
GraphqlQueryUpdateManyResolverService, GraphqlQueryFindManyResolverService,
GraphqlQueryFindOneResolverService,
GraphqlQuerySearchResolverService, GraphqlQuerySearchResolverService,
GraphqlQueryUpdateManyResolverService,
GraphqlQueryUpdateOneResolverService,
]; ];
@Module({ @Module({

View File

@ -13,6 +13,7 @@ import {
CreateOneResolverArgs, CreateOneResolverArgs,
DeleteManyResolverArgs, DeleteManyResolverArgs,
DeleteOneResolverArgs, DeleteOneResolverArgs,
DestroyManyResolverArgs,
DestroyOneResolverArgs, DestroyOneResolverArgs,
FindDuplicatesResolverArgs, FindDuplicatesResolverArgs,
FindManyResolverArgs, FindManyResolverArgs,
@ -285,6 +286,25 @@ export class GraphqlQueryRunnerService {
return result; return result;
} }
@LogExecutionTime()
async destroyMany<ObjectRecord extends IRecord>(
args: DestroyManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord[]> {
const result = await this.executeQuery<
DestroyManyResolverArgs,
ObjectRecord[]
>('destroyMany', args, options);
this.apiEventEmitterService.emitDestroyEvents(
result,
options.authContext,
options.objectMetadataItem,
);
return result;
}
@LogExecutionTime() @LogExecutionTime()
public async restoreMany<ObjectRecord extends IRecord>( public async restoreMany<ObjectRecord extends IRecord>(
args: RestoreManyResolverArgs, args: RestoreManyResolverArgs,

View File

@ -27,25 +27,34 @@ export class ObjectRecordsToGraphqlConnectionHelper {
this.objectMetadataMap = objectMetadataMap; this.objectMetadataMap = objectMetadataMap;
} }
public createConnection<ObjectRecord extends IRecord = IRecord>( public createConnection<ObjectRecord extends IRecord = IRecord>({
objectRecords: ObjectRecord[], objectRecords,
objectName: string, objectName,
take: number, take,
totalCount: number, totalCount,
order: RecordOrderBy | undefined, order,
hasNextPage: boolean, hasNextPage,
hasPreviousPage: boolean, hasPreviousPage,
depth = 0, depth = 0,
): IConnection<ObjectRecord> { }: {
objectRecords: ObjectRecord[];
objectName: string;
take: number;
totalCount: number;
order?: RecordOrderBy;
hasNextPage: boolean;
hasPreviousPage: boolean;
depth?: number;
}): IConnection<ObjectRecord> {
const edges = (objectRecords ?? []).map((objectRecord) => ({ const edges = (objectRecords ?? []).map((objectRecord) => ({
node: this.processRecord( node: this.processRecord({
objectRecord, objectRecord,
objectName, objectName,
take, take,
totalCount, totalCount,
order, order,
depth, depth,
), }),
cursor: encodeCursor(objectRecord, order), cursor: encodeCursor(objectRecord, order),
})); }));
@ -61,14 +70,21 @@ export class ObjectRecordsToGraphqlConnectionHelper {
}; };
} }
public processRecord<T extends Record<string, any>>( public processRecord<T extends Record<string, any>>({
objectRecord: T, objectRecord,
objectName: string, objectName,
take: number, take,
totalCount: number, totalCount,
order?: RecordOrderBy, order,
depth = 0, depth = 0,
): T { }: {
objectRecord: T;
objectName: string;
take: number;
totalCount: number;
order?: RecordOrderBy;
depth?: number;
}): T {
if (depth >= CONNECTION_MAX_DEPTH) { if (depth >= CONNECTION_MAX_DEPTH) {
throw new GraphqlQueryRunnerException( throw new GraphqlQueryRunnerException(
`Maximum depth of ${CONNECTION_MAX_DEPTH} reached`, `Maximum depth of ${CONNECTION_MAX_DEPTH} reached`,
@ -97,27 +113,31 @@ export class ObjectRecordsToGraphqlConnectionHelper {
if (isRelationFieldMetadataType(fieldMetadata.type)) { if (isRelationFieldMetadataType(fieldMetadata.type)) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
processedObjectRecord[key] = this.createConnection( processedObjectRecord[key] = this.createConnection({
value, objectRecords: value,
getRelationObjectMetadata(fieldMetadata, this.objectMetadataMap) objectName: getRelationObjectMetadata(
.nameSingular, fieldMetadata,
this.objectMetadataMap,
).nameSingular,
take, take,
value.length, totalCount: value.length,
order, order,
false, hasNextPage: false,
false, hasPreviousPage: false,
depth + 1, depth: depth + 1,
); });
} else if (isPlainObject(value)) { } else if (isPlainObject(value)) {
processedObjectRecord[key] = this.processRecord( processedObjectRecord[key] = this.processRecord({
value, objectRecord: value,
getRelationObjectMetadata(fieldMetadata, this.objectMetadataMap) objectName: getRelationObjectMetadata(
.nameSingular, fieldMetadata,
this.objectMetadataMap,
).nameSingular,
take, take,
totalCount, totalCount,
order, order,
depth + 1, depth: depth + 1,
); });
} }
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) { } else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
processedObjectRecord[key] = this.processCompositeField( processedObjectRecord[key] = this.processCompositeField(

View File

@ -93,12 +93,12 @@ export class GraphqlQueryCreateManyResolverService
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
return upsertedRecords.map((record: ObjectRecord) => return upsertedRecords.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord( typeORMObjectRecordsParser.processRecord({
record, objectRecord: record,
objectMetadataMapItem.nameSingular, objectName: objectMetadataMapItem.nameSingular,
1, take: 1,
1, totalCount: 1,
), }),
); );
} }

View File

@ -0,0 +1,108 @@
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 { 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';
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
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 { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQueryDestroyManyResolverService
implements ResolverService<DestroyManyResolverArgs, IRecord[]>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<ObjectRecord extends IRecord = IRecord>(
args: DestroyManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
);
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataMapItem.nameSingular,
args.filter,
);
const nonFormattedDeletedObjectRecords = await withFilterQueryBuilder
.delete()
.returning('*')
.execute();
const deletedRecords = formatResult(
nonFormattedDeletedObjectRecords.raw,
objectMetadataMapItem,
objectMetadataMap,
);
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
deletedRecords,
relations,
QUERY_MAX_RECORDS,
authContext,
dataSource,
);
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
return deletedRecords.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataMapItem.nameSingular,
take: 1,
totalCount: 1,
}),
);
}
async validate(
args: DestroyManyResolverArgs,
_options: WorkspaceQueryRunnerOptions,
): Promise<void> {
if (!args.filter) {
throw new Error('Filter is required');
}
}
}

View File

@ -1,14 +1,20 @@
import { Injectable } from '@nestjs/common'; 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 { 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 { 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 { 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'; import { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
import { import {
GraphqlQueryRunnerException, GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode, GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
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 { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@ -24,19 +30,43 @@ export class GraphqlQueryDestroyOneResolverService
args: DestroyOneResolverArgs, args: DestroyOneResolverArgs,
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord> { ): Promise<ObjectRecord> {
const { authContext, objectMetadataMapItem, objectMetadataMap } = options; const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
const repository = options;
await this.twentyORMGlobalManager.getRepositoryForWorkspace( const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id, authContext.workspace.id,
objectMetadataMapItem.nameSingular,
); );
const nonFormattedRecordBeforeDeletion = await repository.findOne({ const repository = dataSource.getRepository(
where: { id: args.id }, objectMetadataMapItem.nameSingular,
withDeleted: true, );
});
if (!nonFormattedRecordBeforeDeletion) { const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
);
const nonFormattedDeletedObjectRecords = await queryBuilder
.where({
id: args.id,
})
.take(1)
.delete()
.returning('*')
.execute();
if (!nonFormattedDeletedObjectRecords.affected) {
throw new GraphqlQueryRunnerException( throw new GraphqlQueryRunnerException(
'Record not found', 'Record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
@ -44,14 +74,34 @@ export class GraphqlQueryDestroyOneResolverService
} }
const recordBeforeDeletion = formatResult( const recordBeforeDeletion = formatResult(
[nonFormattedRecordBeforeDeletion], nonFormattedDeletedObjectRecords.raw,
objectMetadataMapItem, objectMetadataMapItem,
objectMetadataMap, objectMetadataMap,
)[0]; )[0];
await repository.delete(args.id); const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
return recordBeforeDeletion as ObjectRecord; if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
[recordBeforeDeletion],
relations,
QUERY_MAX_RECORDS,
authContext,
dataSource,
);
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
return typeORMObjectRecordsParser.processRecord({
objectRecord: recordBeforeDeletion,
objectName: objectMetadataMapItem.nameSingular,
take: 1,
totalCount: 1,
});
} }
async validate( async validate(

View File

@ -88,15 +88,15 @@ export class GraphqlQueryFindDuplicatesResolverService
); );
if (isEmpty(duplicateConditions)) { if (isEmpty(duplicateConditions)) {
return typeORMObjectRecordsParser.createConnection( return typeORMObjectRecordsParser.createConnection({
[], objectRecords: [],
objectMetadataMapItem.nameSingular, objectName: objectMetadataMapItem.nameSingular,
0, take: 0,
0, totalCount: 0,
[{ id: OrderByDirection.AscNullsFirst }], order: [{ id: OrderByDirection.AscNullsFirst }],
false, hasNextPage: false,
false, hasPreviousPage: false,
); });
} }
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
@ -114,15 +114,15 @@ export class GraphqlQueryFindDuplicatesResolverService
objectMetadataMap, objectMetadataMap,
); );
return typeORMObjectRecordsParser.createConnection( return typeORMObjectRecordsParser.createConnection({
duplicates, objectRecords: duplicates,
objectMetadataMapItem.nameSingular, objectName: objectMetadataMapItem.nameSingular,
duplicates.length, take: duplicates.length,
duplicates.length, totalCount: duplicates.length,
[{ id: OrderByDirection.AscNullsFirst }], order: [{ id: OrderByDirection.AscNullsFirst }],
false, hasNextPage: false,
false, hasPreviousPage: false,
); });
}), }),
); );

View File

@ -176,15 +176,15 @@ export class GraphqlQueryFindManyResolverService
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
const result = typeORMObjectRecordsParser.createConnection( const result = typeORMObjectRecordsParser.createConnection({
objectRecords, objectRecords,
objectMetadataMapItem.nameSingular, objectName: objectMetadataMapItem.nameSingular,
limit, take: limit,
totalCount, totalCount,
orderByWithIdCondition, order: orderByWithIdCondition,
hasNextPage, hasNextPage,
hasPreviousPage, hasPreviousPage,
); });
return result; return result;
} }

View File

@ -113,12 +113,12 @@ export class GraphqlQueryFindOneResolverService
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
return typeORMObjectRecordsParser.processRecord( return typeORMObjectRecordsParser.processRecord({
objectRecords[0], objectRecord: objectRecords[0],
objectMetadataMapItem.nameSingular, objectName: objectMetadataMapItem.nameSingular,
1, take: 1,
1, totalCount: 1,
) as ObjectRecord; }) as ObjectRecord;
} }
async validate<Filter extends RecordFilter>( async validate<Filter extends RecordFilter>(

View File

@ -44,15 +44,15 @@ export class GraphqlQuerySearchResolverService
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
if (!args.searchInput) { if (!args.searchInput) {
return typeORMObjectRecordsParser.createConnection( return typeORMObjectRecordsParser.createConnection({
[], objectRecords: [],
objectMetadataItem.nameSingular, objectName: objectMetadataItem.nameSingular,
0, take: 0,
0, totalCount: 0,
[{ id: OrderByDirection.AscNullsFirst }], order: [{ id: OrderByDirection.AscNullsFirst }],
false, hasNextPage: false,
false, hasPreviousPage: false,
); });
} }
const searchTerms = this.formatSearchTerms(args.searchInput); const searchTerms = this.formatSearchTerms(args.searchInput);
@ -76,15 +76,15 @@ export class GraphqlQuerySearchResolverService
const totalCount = await repository.count(); const totalCount = await repository.count();
const order = undefined; const order = undefined;
return typeORMObjectRecordsParser.createConnection( return typeORMObjectRecordsParser.createConnection({
objectRecords ?? [], objectRecords: objectRecords ?? [],
objectMetadataItem.nameSingular, objectName: objectMetadataItem.nameSingular,
limit, take: limit,
totalCount, totalCount,
order, order,
false, hasNextPage: false,
false, hasPreviousPage: false,
); });
} }
private formatSearchTerms(searchTerm: string) { private formatSearchTerms(searchTerm: string) {

View File

@ -65,15 +65,13 @@ export class GraphqlQueryUpdateManyResolverService
const data = formatData(args.data, objectMetadataMapItem); const data = formatData(args.data, objectMetadataMapItem);
const result = await withFilterQueryBuilder const nonFormattedUpdatedObjectRecords = await withFilterQueryBuilder
.update(data) .update(data)
.returning('*') .returning('*')
.execute(); .execute();
const nonFormattedUpdatedObjectRecords = result.raw;
const updatedRecords = formatResult( const updatedRecords = formatResult(
nonFormattedUpdatedObjectRecords, nonFormattedUpdatedObjectRecords.raw,
objectMetadataMapItem, objectMetadataMapItem,
objectMetadataMap, objectMetadataMap,
); );
@ -96,12 +94,12 @@ export class GraphqlQueryUpdateManyResolverService
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
return updatedRecords.map((record: ObjectRecord) => return updatedRecords.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord( typeORMObjectRecordsParser.processRecord({
record, objectRecord: record,
objectMetadataMapItem.nameSingular, objectName: objectMetadataMapItem.nameSingular,
1, take: 1,
1, totalCount: 1,
), }),
); );
} }
@ -110,6 +108,10 @@ export class GraphqlQueryUpdateManyResolverService
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<void> { ): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataMapItem); assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
args.filter?.id?.in?.forEach((id: string) => assertIsValidUuid(id)); if (!args.filter) {
throw new Error('Filter is required');
}
args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id));
} }
} }

View File

@ -103,12 +103,12 @@ export class GraphqlQueryUpdateOneResolverService
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
return typeORMObjectRecordsParser.processRecord<ObjectRecord>( return typeORMObjectRecordsParser.processRecord<ObjectRecord>({
updatedRecord, objectRecord: updatedRecord,
objectMetadataMapItem.nameSingular, objectName: objectMetadataMapItem.nameSingular,
1, take: 1,
1, totalCount: 1,
); });
} }
async validate<ObjectRecord extends IRecord = IRecord>( async validate<ObjectRecord extends IRecord = IRecord>(

View File

@ -8,8 +8,11 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.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';
@Injectable() @Injectable()
export class DestroyManyResolverFactory export class DestroyManyResolverFactory
@ -19,6 +22,8 @@ export class DestroyManyResolverFactory
constructor( constructor(
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
private readonly featureFlagService: FeatureFlagService,
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService,
) {} ) {}
create( create(
@ -38,6 +43,19 @@ export class DestroyManyResolverFactory
objectMetadataMapItem: internalContext.objectMetadataMapItem, objectMetadataMapItem: internalContext.objectMetadataMapItem,
}; };
const isQueryRunnerTwentyORMEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsQueryRunnerTwentyORMEnabled,
internalContext.authContext.workspace.id,
);
if (isQueryRunnerTwentyORMEnabled) {
return await this.graphqlQueryRunnerService.destroyMany(
args,
options,
);
}
return await this.workspaceQueryRunnerService.destroyMany( return await this.workspaceQueryRunnerService.destroyMany(
args, args,
options, options,