From 04a62e9749fed50228d8fe344d0c80f2f4f5c859 Mon Sep 17 00:00:00 2001 From: Guillim Date: Thu, 20 Feb 2025 15:40:49 +0100 Subject: [PATCH] Deduplicate-emails (#10355) Following User request to remove duplicate emails --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../factories/query-runner-args.factory.ts | 32 +++++++++++++++++++ .../utils/__tests__/query-runner.util.spec.ts | 17 ++++++++++ .../utils/query-runner-links.util.ts | 7 ++++ .../get-unique-contacts-and-handles.spec.ts | 13 ++++++++ .../get-unique-contacts-and-handles.util.ts | 8 +++-- 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/__tests__/query-runner.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util.ts diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts index adddd2af7..a53335bc2 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts @@ -21,6 +21,7 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; +import { lowercaseDomain } from 'src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util'; import { RichTextV2Metadata, richTextV2ValueSchema, @@ -230,6 +231,37 @@ export class QueryRunnerArgsFactory { return [key, valueInBothFormats]; } + case FieldMetadataType.LINKS: { + const newPrimaryLinkUrl = lowercaseDomain(value?.primaryLinkUrl); + + return [key, { ...value, primaryLinkUrl: newPrimaryLinkUrl }]; + } + case FieldMetadataType.EMAILS: { + let additionalEmails = value?.additionalEmails; + const primaryEmail = value?.primaryEmail + ? value.primaryEmail.toLowerCase() + : ''; + + if (additionalEmails) { + try { + const emailArray = JSON.parse(additionalEmails) as string[]; + + additionalEmails = JSON.stringify( + emailArray.map((email) => email.toLowerCase()), + ); + } catch { + /* empty */ + } + } + + return [ + key, + { + primaryEmail, + additionalEmails, + }, + ]; + } default: return [key, value]; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/__tests__/query-runner.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/__tests__/query-runner.util.spec.ts new file mode 100644 index 000000000..07241ffd5 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/__tests__/query-runner.util.spec.ts @@ -0,0 +1,17 @@ +import { lowercaseDomain } from 'src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util'; + +describe('queryRunner LINKS util', () => { + it('should leave lowcased domain unchanged', () => { + const primaryLinkUrl = 'https://www.example.com/test'; + const result = lowercaseDomain(primaryLinkUrl); + + expect(result).toBe('https://www.example.com/test'); + }); + + it('should lowercase the domain of the primary link url', () => { + const primaryLinkUrl = 'htTps://wwW.exAmple.coM/TEST'; + const result = lowercaseDomain(primaryLinkUrl); + + expect(result).toBe('https://www.example.com/TEST'); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util.ts new file mode 100644 index 000000000..6c9515040 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util.ts @@ -0,0 +1,7 @@ +export const lowercaseDomain = (url: string) => { + try { + return new URL(url).toString(); + } catch { + return url; + } +}; diff --git a/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-unique-contacts-and-handles.spec.ts b/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-unique-contacts-and-handles.spec.ts index 7bafaa4aa..26dd76fa2 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-unique-contacts-and-handles.spec.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-unique-contacts-and-handles.spec.ts @@ -29,4 +29,17 @@ describe('getUniqueContactsAndHandles', () => { 'jane@twenty.com', ]); }); + + it('should deduplicate handles when they are in different cases', () => { + const contacts: Contact[] = [ + { handle: 'john@twenty.com', displayName: 'John Doe' }, + { handle: 'John@twenty.com', displayName: 'John Doe' }, + ]; + const result = getUniqueContactsAndHandles(contacts); + + expect(result.uniqueContacts).toEqual([ + { handle: 'john@twenty.com', displayName: 'John Doe' }, + ]); + expect(result.uniqueHandles).toEqual(['john@twenty.com']); + }); }); diff --git a/packages/twenty-server/src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util.ts b/packages/twenty-server/src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util.ts index 26cd2e892..4025397e2 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util.ts @@ -11,9 +11,13 @@ export function getUniqueContactsAndHandles(contacts: Contact[]): { return { uniqueContacts: [], uniqueHandles: [] }; } - const uniqueHandles = uniq(contacts.map((participant) => participant.handle)); + const uniqueHandles = uniq( + contacts.map((participant) => participant.handle.toLocaleLowerCase()), + ); - const uniqueContacts = uniqBy(contacts, 'handle'); + const uniqueContacts = uniqBy(contacts, (contact) => + contact.handle.toLocaleLowerCase(), + ); return { uniqueContacts, uniqueHandles }; }