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:
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user