From 145878ebe0bbb6d5a9b0da7aac2cce5e1ccb66d8 Mon Sep 17 00:00:00 2001 From: Weiko Date: Wed, 11 Sep 2024 14:00:49 +0200 Subject: [PATCH 1/2] Refactor graphql query runner + fix nested or --- .../errors/graphql-query-runner.exception.ts | 2 + .../graphql-query-filter-condition.parser.ts | 64 +++-- .../graphql-query-runner.service.ts | 237 +----------------- ...raphql-query-find-many-resolver.service.ts | 194 ++++++++++++++ ...graphql-query-find-one-resolver.service.ts | 80 ++++++ .../convert-object-metadata-to-map.util.ts | 21 ++ ...nner-graphql-api-exception-handler.util.ts | 3 +- 7 files changed, 338 insertions(+), 263 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception.ts index 2d43ac86fe1c..efbbf1183afe 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception.ts @@ -17,4 +17,6 @@ export enum GraphqlQueryRunnerExceptionCode { FIELD_NOT_FOUND = 'FIELD_NOT_FOUND', OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND', RECORD_NOT_FOUND = 'RECORD_NOT_FOUND', + INVALID_ARGS_FIRST = 'INVALID_ARGS_FIRST', + INVALID_ARGS_LAST = 'INVALID_ARGS_LAST', } 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 2a86d032ea1c..7889c779ec5a 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 @@ -26,16 +26,22 @@ export class GraphqlQueryFilterConditionParser { } const result: FindOptionsWhere = {}; - let orCondition: FindOptionsWhere[] | null = null; for (const [key, value] of Object.entries(conditions)) { switch (key) { - case 'and': - Object.assign(result, this.parseAndCondition(value, isNegated)); - break; - case 'or': - orCondition = this.parseOrCondition(value, isNegated); - break; + case 'and': { + const andConditions = this.parseAndCondition(value, isNegated); + + return andConditions.map((condition) => ({ + ...result, + ...condition, + })); + } + case 'or': { + const orConditions = this.parseOrCondition(value, isNegated); + + return orConditions.map((condition) => ({ ...result, ...condition })); + } case 'not': Object.assign(result, this.parse(value, !isNegated)); break; @@ -47,10 +53,6 @@ export class GraphqlQueryFilterConditionParser { } } - if (orCondition) { - return orCondition.map((condition) => ({ ...result, ...condition })); - } - return result; } @@ -84,32 +86,28 @@ export class GraphqlQueryFilterConditionParser { combineType: 'and' | 'or', ): FindOptionsWhere[] { if (combineType === 'and') { - let result: FindOptionsWhere[] = [{}]; - - for (const condition of conditions) { - if (Array.isArray(condition)) { - const newResult: FindOptionsWhere[] = []; - - for (const existingCondition of result) { - for (const orCondition of condition) { - newResult.push({ - ...existingCondition, - ...orCondition, - }); - } + return conditions.reduce[]>( + (acc, condition) => { + if (Array.isArray(condition)) { + return acc.flatMap((accCondition) => + condition.map((subCondition) => ({ + ...accCondition, + ...subCondition, + })), + ); } - result = newResult; - } else { - result = result.map((existingCondition) => ({ - ...existingCondition, + + return acc.map((accCondition) => ({ + ...accCondition, ...condition, })); - } - } - - return result; + }, + [{}], + ); } - return conditions.flat(); + return conditions.flatMap((condition) => + Array.isArray(condition) ? condition : [condition], + ); } } 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 895110b63629..78a98aa7216a 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,9 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { isDefined } from 'class-validator'; -import graphqlFields from 'graphql-fields'; -import { FindManyOptions, ObjectLiteral } from 'typeorm'; - import { Record as IRecord, RecordFilter, @@ -16,16 +12,8 @@ import { FindOneResolverArgs, } 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 { - GraphqlQueryRunnerException, - GraphqlQueryRunnerExceptionCode, -} 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 { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; -import { applyRangeFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/apply-range-filter.util'; -import { convertObjectMetadataToMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util'; -import { decodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; +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'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; @@ -43,48 +31,10 @@ export class GraphqlQueryRunnerService { args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { - const { authContext, objectMetadataItem, info, objectMetadataCollection } = - options; - const repository = await this.getRepository( - authContext.workspace.id, - objectMetadataItem.nameSingular, - ); - const objectMetadataMap = convertObjectMetadataToMap( - objectMetadataCollection, - ); - const objectMetadata = this.getObjectMetadata( - objectMetadataMap, - objectMetadataItem.nameSingular, - ); - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadata.fields, - objectMetadataMap, - ); - - const { select, relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItem, - graphqlFields(info), - ); - const where = graphqlQueryParser.parseFilter(args.filter ?? ({} as Filter)); - - const objectRecord = await repository.findOne({ where, select, relations }); - - if (!objectRecord) { - throw new GraphqlQueryRunnerException( - 'Record not found', - GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, - ); - } + const graphqlQueryFindOneResolverService = + new GraphqlQueryFindOneResolverService(this.twentyORMGlobalManager); - const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); - - return typeORMObjectRecordsParser.processRecord( - objectRecord, - objectMetadataItem.nameSingular, - 1, - 1, - ) as ObjectRecord; + return graphqlQueryFindOneResolverService.findOne(args, options); } @LogExecutionTime() @@ -96,180 +46,9 @@ export class GraphqlQueryRunnerService { args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise> { - const { authContext, objectMetadataItem, info, objectMetadataCollection } = - options; - - this.validateArgsOrThrow(args); - - const repository = await this.getRepository( - authContext.workspace.id, - objectMetadataItem.nameSingular, - ); - const objectMetadataMap = convertObjectMetadataToMap( - objectMetadataCollection, - ); - const objectMetadata = this.getObjectMetadata( - objectMetadataMap, - objectMetadataItem.nameSingular, - ); - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadata.fields, - objectMetadataMap, - ); - - const { select, relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItem, - graphqlFields(info), - ); - const isForwardPagination = !isDefined(args.before); - const order = graphqlQueryParser.parseOrder( - args.orderBy ?? [], - isForwardPagination, - ); - const where = graphqlQueryParser.parseFilter( - args.filter ?? ({} as Filter), - true, - ); - - const cursor = this.getCursor(args); - const limit = this.getLimit(args); - - this.addOrderByColumnsToSelect(order, select); - - const findOptions: FindManyOptions = { - where, - order, - select, - relations, - take: limit + 1, - }; - const totalCount = await repository.count({ where }); - - if (cursor) { - applyRangeFilter(where, cursor, isForwardPagination); - } - - const objectRecords = await repository.find(findOptions); - const { hasNextPage, hasPreviousPage } = this.getPaginationInfo( - objectRecords, - limit, - isForwardPagination, - ); - - if (objectRecords.length > limit) { - objectRecords.pop(); - } - - const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); - - return typeORMObjectRecordsParser.createConnection( - objectRecords as ObjectRecord[], - objectMetadataItem.nameSingular, - limit, - totalCount, - order, - hasNextPage, - hasPreviousPage, - ); - } - - private async getRepository(workspaceId: string, objectName: string) { - return this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - objectName, - ); - } - - private getObjectMetadata( - objectMetadataMap: Record, - objectName: string, - ) { - const objectMetadata = objectMetadataMap[objectName]; - - if (!objectMetadata) { - throw new GraphqlQueryRunnerException( - `Object metadata not found for ${objectName}`, - GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND, - ); - } - - return objectMetadata; - } - - private getCursor( - args: FindManyResolverArgs, - ): Record | undefined { - if (args.after) return decodeCursor(args.after); - if (args.before) return decodeCursor(args.before); - - return undefined; - } - - private getLimit(args: FindManyResolverArgs): number { - return args.first ?? args.last ?? QUERY_MAX_RECORDS; - } - - private addOrderByColumnsToSelect( - order: Record, - select: Record, - ) { - for (const column of Object.keys(order || {})) { - if (!select[column]) { - select[column] = true; - } - } - } - - private getPaginationInfo( - objectRecords: any[], - limit: number, - isForwardPagination: boolean, - ) { - const hasMoreRecords = objectRecords.length > limit; - - return { - hasNextPage: isForwardPagination && hasMoreRecords, - hasPreviousPage: !isForwardPagination && hasMoreRecords, - }; - } + const graphqlQueryFindManyResolverService = + new GraphqlQueryFindManyResolverService(this.twentyORMGlobalManager); - private validateArgsOrThrow(args: FindManyResolverArgs) { - if (args.first && args.last) { - throw new GraphqlQueryRunnerException( - 'Cannot provide both first and last', - GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT, - ); - } - if (args.before && args.after) { - throw new GraphqlQueryRunnerException( - 'Cannot provide both before and after', - GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT, - ); - } - if (args.before && args.first) { - throw new GraphqlQueryRunnerException( - 'Cannot provide both before and first', - GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT, - ); - } - if (args.after && args.last) { - throw new GraphqlQueryRunnerException( - 'Cannot provide both after and last', - GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT, - ); - } - if (args.first !== undefined && args.first < 0) { - throw new GraphqlQueryRunnerException( - 'First argument must be non-negative', - GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, - ); - } - if (args.last !== undefined && args.last < 0) { - throw new GraphqlQueryRunnerException( - 'Last argument must be non-negative', - GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, - ); - } + return graphqlQueryFindManyResolverService.findMany(args, options); } } 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 new file mode 100644 index 000000000000..d05a4f7f4591 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts @@ -0,0 +1,194 @@ +import { isDefined } from 'class-validator'; +import graphqlFields from 'graphql-fields'; +import { FindManyOptions, ObjectLiteral } from 'typeorm'; + +import { + Record as IRecord, + RecordFilter, + RecordOrderBy, +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/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'; + +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 { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; +import { applyRangeFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/apply-range-filter.util'; +import { + convertObjectMetadataToMap, + getObjectMetadata, +} from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util'; +import { decodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +export class GraphqlQueryFindManyResolverService { + private twentyORMGlobalManager: TwentyORMGlobalManager; + + constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { + this.twentyORMGlobalManager = twentyORMGlobalManager; + } + + async findMany< + ObjectRecord extends IRecord = IRecord, + Filter extends RecordFilter = RecordFilter, + OrderBy extends RecordOrderBy = RecordOrderBy, + >( + args: FindManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise> { + const { authContext, objectMetadataItem, info, objectMetadataCollection } = + options; + + this.validateArgsOrThrow(args); + + const repository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + authContext.workspace.id, + objectMetadataItem.nameSingular, + ); + const objectMetadataMap = convertObjectMetadataToMap( + objectMetadataCollection, + ); + const objectMetadata = getObjectMetadata( + objectMetadataMap, + objectMetadataItem.nameSingular, + ); + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadata.fields, + objectMetadataMap, + ); + + const { select, relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataItem, + graphqlFields(info), + ); + const isForwardPagination = !isDefined(args.before); + const order = graphqlQueryParser.parseOrder( + args.orderBy ?? [], + isForwardPagination, + ); + const where = graphqlQueryParser.parseFilter( + args.filter ?? ({} as Filter), + true, + ); + + const cursor = this.getCursor(args); + const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS; + + this.addOrderByColumnsToSelect(order, select); + + const findOptions: FindManyOptions = { + where, + order, + select, + relations, + take: limit + 1, + }; + const totalCount = await repository.count({ where }); + + if (cursor) { + applyRangeFilter(where, cursor, isForwardPagination); + } + + const objectRecords = await repository.find(findOptions); + const { hasNextPage, hasPreviousPage } = this.getPaginationInfo( + objectRecords, + limit, + isForwardPagination, + ); + + if (objectRecords.length > limit) { + objectRecords.pop(); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + + return typeORMObjectRecordsParser.createConnection( + objectRecords as ObjectRecord[], + objectMetadataItem.nameSingular, + limit, + totalCount, + order, + hasNextPage, + hasPreviousPage, + ); + } + + private validateArgsOrThrow(args: FindManyResolverArgs) { + if (args.first && args.last) { + throw new GraphqlQueryRunnerException( + 'Cannot provide both first and last', + GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT, + ); + } + if (args.before && args.after) { + throw new GraphqlQueryRunnerException( + 'Cannot provide both before and after', + GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT, + ); + } + if (args.before && args.first) { + throw new GraphqlQueryRunnerException( + 'Cannot provide both before and first', + GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT, + ); + } + if (args.after && args.last) { + throw new GraphqlQueryRunnerException( + 'Cannot provide both after and last', + GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT, + ); + } + if (args.first !== undefined && args.first < 0) { + throw new GraphqlQueryRunnerException( + 'First argument must be non-negative', + GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST, + ); + } + if (args.last !== undefined && args.last < 0) { + throw new GraphqlQueryRunnerException( + 'Last argument must be non-negative', + GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST, + ); + } + } + + private getCursor( + args: FindManyResolverArgs, + ): Record | undefined { + if (args.after) return decodeCursor(args.after); + if (args.before) return decodeCursor(args.before); + + return undefined; + } + + private addOrderByColumnsToSelect( + order: Record, + select: Record, + ) { + for (const column of Object.keys(order || {})) { + if (!select[column]) { + select[column] = true; + } + } + } + + private getPaginationInfo( + objectRecords: any[], + limit: number, + isForwardPagination: boolean, + ) { + const hasMoreRecords = objectRecords.length > limit; + + return { + hasNextPage: isForwardPagination && hasMoreRecords, + hasPreviousPage: !isForwardPagination && hasMoreRecords, + }; + } +} 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 new file mode 100644 index 000000000000..063fb5c83754 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -0,0 +1,80 @@ +import graphqlFields from 'graphql-fields'; + +import { + Record as IRecord, + RecordFilter, +} 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 { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} 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 { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; +import { + convertObjectMetadataToMap, + getObjectMetadata, +} from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +export class GraphqlQueryFindOneResolverService { + private twentyORMGlobalManager: TwentyORMGlobalManager; + + constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { + this.twentyORMGlobalManager = twentyORMGlobalManager; + } + + async findOne< + ObjectRecord extends IRecord = IRecord, + Filter extends RecordFilter = RecordFilter, + >( + args: FindOneResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { authContext, objectMetadataItem, info, objectMetadataCollection } = + options; + const repository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + authContext.workspace.id, + objectMetadataItem.nameSingular, + ); + const objectMetadataMap = convertObjectMetadataToMap( + objectMetadataCollection, + ); + const objectMetadata = getObjectMetadata( + objectMetadataMap, + objectMetadataItem.nameSingular, + ); + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadata.fields, + objectMetadataMap, + ); + + const { select, relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataItem, + graphqlFields(info), + ); + const where = graphqlQueryParser.parseFilter(args.filter ?? ({} as Filter)); + + const objectRecord = await repository.findOne({ where, select, relations }); + + if (!objectRecord) { + throw new GraphqlQueryRunnerException( + 'Record not found', + GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + + return typeORMObjectRecordsParser.processRecord( + objectRecord, + objectMetadataItem.nameSingular, + 1, + 1, + ) as ObjectRecord; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util.ts index 6f470eb89254..0db7cd4017d9 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util.ts @@ -1,6 +1,11 @@ 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 { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; + export type FieldMetadataMap = Record; export type ObjectMetadataMapItem = Omit & { @@ -33,3 +38,19 @@ export const convertObjectMetadataToMap = ( return objectMetadataMap; }; + +export const getObjectMetadata = ( + objectMetadataMap: Record, + objectName: string, +): ObjectMetadataMapItem => { + 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/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 3c416b9dbb27..0a05be29da2e 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 @@ -38,7 +38,8 @@ export const workspaceQueryRunnerGraphqlApiExceptionHandler = ( if (error instanceof GraphqlQueryRunnerException) { switch (error.code) { - case GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT: + case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST: + case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST: case GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND: case GraphqlQueryRunnerExceptionCode.MAX_DEPTH_REACHED: case GraphqlQueryRunnerExceptionCode.INVALID_CURSOR: From 497d2793f7d2e24d7b03c9b266eaa92353ebc9d4 Mon Sep 17 00:00:00 2001 From: Weiko Date: Wed, 11 Sep 2024 14:21:26 +0200 Subject: [PATCH 2/2] fix tests --- .../__tests__/graphql-query-filter-operator.parser.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-operator.parser.spec.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-operator.parser.spec.ts index 7b3d2e5a9226..4d1494141822 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-operator.parser.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-operator.parser.spec.ts @@ -80,7 +80,8 @@ describe('GraphqlQueryFilterOperatorParser', () => { it('should parse is operator with non-NULL value correctly', () => { const result = parser.parseOperator({ is: 'NOT_NULL' }, false); - expect(result).toBe('NOT_NULL'); + expect(result).toBeInstanceOf(FindOperator); + expect(result).toEqual(Not(IsNull())); }); it('should parse like operator correctly', () => {