feat: find duplicate objects init (#4038)

* feat: find duplicate objects backend init

* refactor: move duplicate criteria to constants

* fix: correct constant usage after type change

* feat: skip query generation in case its not necessary

* feat: filter out existing duplicate

* feat: FE queries and hooks

* feat: show duplicates on FE

* refactor: should-skip-query moved to workspace utils

* refactor: naming improvements

* refactor: current record typings/parsing improvements

* refactor: throw error if existing record not found

* fix: domain -> domainName duplicate criteria

* refactor: fieldNames -> columnNames

* docs: add explanation to duplicate criteria collection

* feat: add person linkedinLinkUrl as duplicate criteria

* feat: throw early when bot id and data are empty

* refactor: trying to improve readability of filter criteria query

* refactor: naming improvements

* refactor: remove shouldSkipQuery

* feat: resolve empty array in case of empty filter

* feat: hide whole section in case of no duplicates

* feat: FE display list the same way as relations

* test: basic unit test coverage

* Refactor Record detail section front

* Use Create as input argument of findDuplicates

* Improve coverage

* Fix

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
rostaklein
2024-02-24 19:12:21 +01:00
committed by GitHub
parent 05c206073d
commit 1b04dfe3c6
30 changed files with 875 additions and 100 deletions

View File

@ -3,6 +3,7 @@ import {
CreateOneResolverArgs,
DeleteManyResolverArgs,
DeleteOneResolverArgs,
FindDuplicatesResolverArgs,
FindManyResolverArgs,
FindOneResolverArgs,
UpdateManyResolverArgs,
@ -16,6 +17,7 @@ export type ExecutePreHookMethod =
| 'deleteOne'
| 'findMany'
| 'findOne'
| 'findDuplicates'
| 'updateMany'
| 'updateOne';
@ -45,4 +47,6 @@ export type WorkspacePreQueryHookPayload<T> = T extends 'createMany'
? UpdateManyResolverArgs
: T extends 'updateOne'
? UpdateOneResolverArgs
: never;
: T extends 'findDuplicates'
? FindDuplicatesResolverArgs
: never;

View File

@ -6,6 +6,8 @@ import {
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import isEmpty from 'lodash.isempty';
import { IConnection } from 'src/utils/pagination/interfaces/connection.interface';
import {
Record as IRecord,
@ -17,6 +19,7 @@ import {
CreateOneResolverArgs,
DeleteManyResolverArgs,
DeleteOneResolverArgs,
FindDuplicatesResolverArgs,
FindManyResolverArgs,
FindOneResolverArgs,
UpdateManyResolverArgs,
@ -40,6 +43,7 @@ import { ObjectRecordCreateEvent } from 'src/integrations/event-emitter/types/ob
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
import { WorkspacePreQueryHookService } from 'src/workspace/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.service';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
import { NotFoundError } from 'src/filters/utils/graphql-errors.util';
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-option.interface';
import {
@ -136,6 +140,74 @@ export class WorkspaceQueryRunnerService {
return parsedResult?.edges?.[0]?.node;
}
async findDuplicates<TRecord extends IRecord = IRecord>(
args: FindDuplicatesResolverArgs<TRecord>,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<TRecord> | undefined> {
if (!args.data && !args.id) {
throw new BadRequestException(
'You have to provide either "data" or "id" argument',
);
}
if (!args.id && isEmpty(args.data)) {
throw new BadRequestException(
'The "data" condition can not be empty when ID input not provided',
);
}
const { workspaceId, userId, objectMetadataItem } = options;
let existingRecord: Record<string, unknown> | undefined;
if (args.id) {
const existingRecordQuery =
this.workspaceQueryBuilderFactory.findDuplicatesExistingRecord(
args.id,
options,
);
const existingRecordResult = await this.execute(
existingRecordQuery,
workspaceId,
);
const parsedResult = this.parseResult<Record<string, unknown>>(
existingRecordResult,
objectMetadataItem,
'',
);
existingRecord = parsedResult?.edges?.[0]?.node;
if (!existingRecord) {
throw new NotFoundError(`Object with id ${args.id} not found`);
}
}
const query = await this.workspaceQueryBuilderFactory.findDuplicates(
args,
options,
existingRecord,
);
await this.workspacePreQueryHookService.executePreHooks(
userId,
workspaceId,
objectMetadataItem.nameSingular,
'findDuplicates',
args,
);
const result = await this.execute(query, workspaceId);
return this.parseResult<IConnection<TRecord>>(
result,
objectMetadataItem,
'',
);
}
async createMany<Record extends IRecord = IRecord>(
args: CreateManyResolverArgs<Record>,
options: WorkspaceQueryRunnerOptions,