Files
twenty/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts
Weiko 0234c8d707 Fix featureFlag N+1 queries (#10261)
## Context
Regression was introduced 3 weeks ago when we added relations v2.
Because the relation logic is recursive during the life of a request, we
were querying the featureFlags many times.

We are now always using the featureFlag map and it's now available in
the base resolver so we don't need to query it everywhere, preferably
passing it as a parameter instead.

Note: We should introduce a cache for featureFlags in the future, this
is something easy to control and invalidate when needed.
2025-02-18 14:43:42 +01:00

101 lines
3.9 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import {
GraphqlQueryBaseResolverService,
GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
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 { DeleteOneResolverArgs } 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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService<
DeleteOneResolverArgs,
ObjectRecord
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<DeleteOneResolverArgs>,
featureFlagsMap: Record<FeatureFlagKey, boolean>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const nonFormattedDeletedObjectRecords = await queryBuilder
.softDelete()
.where({ id: executionArgs.args.id })
.returning('*')
.execute();
const formattedDeletedRecords = formatResult<ObjectRecord[]>(
nonFormattedDeletedObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
this.apiEventEmitterService.emitDeletedEvents(
formattedDeletedRecords,
authContext,
objectMetadataItemWithFieldMaps,
);
if (formattedDeletedRecords.length === 0) {
throw new GraphqlQueryRunnerException(
'Record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
const deletedRecord = formattedDeletedRecords[0];
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: [deletedRecord],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
dataSource: executionArgs.dataSource,
isNewRelationEnabled:
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
objectMetadataMaps,
featureFlagsMap,
);
return typeORMObjectRecordsParser.processRecord({
objectRecord: deletedRecord,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
}
async validate(
args: DeleteOneResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
assertIsValidUuid(args.id);
}
}