add WorkspaceDuplicateCriteria decorator + update duplicate resolver logic (#10128)

## Context

All objects have '...duplicates' resolver but only companies and people
have duplicate criteria (hard coded constant).
Gql schema and resolver should be created only if duplicate criteria
exist.

## Solution

- Add a new @WorkspaceDuplicateCriteria decorator at object level,
defining duplicate criteria for given object.
- Add a new duplicate criteria field in ObjectMetadata table
- Update schema and resolver building logic
- Update front requests for duplicate check (only for object with
criteria defined)



closes https://github.com/twentyhq/twenty/issues/9828
This commit is contained in:
Etienne
2025-02-12 17:32:59 +01:00
committed by GitHub
parent b66289c44c
commit 0609b31c64
29 changed files with 491 additions and 121 deletions

View File

@ -6,6 +6,7 @@ import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/work
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { WorkspaceResolverBuilderService } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.service';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
@ -28,6 +29,7 @@ export class RootTypeFactory {
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
private readonly typeMapperService: TypeMapperService,
private readonly argsFactory: ArgsFactory,
private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService,
) {}
create(
@ -70,53 +72,60 @@ export class RootTypeFactory {
for (const objectMetadata of objectMetadataCollection) {
for (const methodName of workspaceResolverMethodNames) {
const name = getResolverName(objectMetadata, methodName);
const args = getResolverArgs(methodName);
const objectType = this.typeDefinitionsStorage.getObjectTypeByKey(
objectMetadata.id,
this.getObjectTypeDefinitionKindByMethodName(methodName),
);
const argsType = this.argsFactory.create(
{
args,
objectMetadataId: objectMetadata.id,
},
options,
);
if (!objectType) {
this.logger.error(
`Could not find a GraphQL type for ${objectMetadata.id} for method ${methodName}`,
if (
this.workspaceResolverBuilderService.shouldBuildResolver(
objectMetadata,
methodName,
)
) {
const name = getResolverName(objectMetadata, methodName);
const args = getResolverArgs(methodName);
const objectType = this.typeDefinitionsStorage.getObjectTypeByKey(
objectMetadata.id,
this.getObjectTypeDefinitionKindByMethodName(methodName),
);
const argsType = this.argsFactory.create(
{
objectMetadata,
methodName,
options,
args,
objectMetadataId: objectMetadata.id,
},
options,
);
throw new Error(
`Could not find a GraphQL type for ${objectMetadata.id} for method ${methodName}`,
);
if (!objectType) {
this.logger.error(
`Could not find a GraphQL type for ${objectMetadata.id} for method ${methodName}`,
{
objectMetadata,
methodName,
options,
},
);
throw new Error(
`Could not find a GraphQL type for ${objectMetadata.id} for method ${methodName}`,
);
}
const allowedMethodNames = [
'updateMany',
'deleteMany',
'createMany',
'findDuplicates',
'restoreMany',
'destroyMany',
];
const outputType = this.typeMapperService.mapToGqlType(objectType, {
isArray: allowedMethodNames.includes(methodName),
});
fieldConfigMap[name] = {
type: outputType,
args: argsType,
resolve: undefined,
};
}
const allowedMethodNames = [
'updateMany',
'deleteMany',
'createMany',
'findDuplicates',
'restoreMany',
'destroyMany',
];
const outputType = this.typeMapperService.mapToGqlType(objectType, {
isArray: allowedMethodNames.includes(methodName),
});
fieldConfigMap[name] = {
type: outputType,
args: argsType,
resolve: undefined,
};
}
}

View File

@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { WorkspaceResolverBuilderModule } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
@ -11,7 +12,11 @@ import { TypeMapperService } from './services/type-mapper.service';
import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
@Module({
imports: [ObjectMetadataModule, FeatureFlagModule],
imports: [
ObjectMetadataModule,
FeatureFlagModule,
WorkspaceResolverBuilderModule,
],
providers: [
TypeDefinitionsStorage,
TypeMapperService,