diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/merge-field-values.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/merge-field-values.util.spec.ts index 7d0afe3a952..69ef4ef938e 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/merge-field-values.util.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/merge-field-values.util.spec.ts @@ -83,7 +83,112 @@ describe('mergeFieldValues', () => { expect(result).toEqual({ primaryEmail: 'priority@example.com', - additionalEmails: ['extra1@example.com', 'extra2@example.com'], + additionalEmails: [ + 'first@example.com', + 'extra1@example.com', + 'extra2@example.com', + ], + }); + }); + + it('should merge phones for PHONES field', () => { + const phoneRecords = [ + { + value: { + primaryPhoneNumber: '+11234567890', + primaryPhoneCountryCode: 'US', + primaryPhoneCallingCode: '+1', + additionalPhones: [ + { + number: '+19876543210', + countryCode: 'US', + callingCode: '+1', + }, + ], + }, + recordId: 'record1', + }, + { + value: { + primaryPhoneNumber: '+14445556666', + primaryPhoneCountryCode: 'US', + primaryPhoneCallingCode: '+1', + additionalPhones: [ + { + number: '+17778889999', + countryCode: 'US', + callingCode: '+1', + }, + ], + }, + recordId: PRIORITY_RECORD_ID, + }, + ]; + + const result = mergeFieldValues( + FieldMetadataType.PHONES, + phoneRecords, + PRIORITY_RECORD_ID, + ); + + expect(result).toEqual({ + primaryPhoneNumber: '+14445556666', + primaryPhoneCountryCode: 'US', + primaryPhoneCallingCode: '+1', + additionalPhones: [ + { + number: '+11234567890', + countryCode: 'US', + callingCode: '+1', + }, + { + number: '+19876543210', + countryCode: 'US', + callingCode: '+1', + }, + { + number: '+17778889999', + countryCode: 'US', + callingCode: '+1', + }, + ], + }); + }); + + it('should merge links for LINKS field', () => { + const linkRecords = [ + { + value: { + primaryLinkUrl: 'https://first.com', + primaryLinkLabel: 'First Link', + secondaryLinks: [{ url: 'https://extra1.com', label: 'Extra 1' }], + }, + recordId: 'record1', + }, + { + value: { + primaryLinkUrl: 'https://priority.com', + primaryLinkLabel: 'Priority Link', + secondaryLinks: [{ url: 'https://extra2.com', label: 'Extra 2' }], + }, + recordId: PRIORITY_RECORD_ID, + }, + ]; + + const result = mergeFieldValues( + FieldMetadataType.LINKS, + linkRecords, + PRIORITY_RECORD_ID, + ); + + expect(result).toEqual({ + primaryLinkUrl: 'https://priority.com', + primaryLinkLabel: 'Priority Link', + secondaryLinks: [ + { url: 'https://first.com', label: 'First Link' }, + { url: 'https://extra1.com', label: 'Extra 1' }, + { url: 'https://extra2.com', label: 'Extra 2' }, + ], }); }); }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-emails-field-values.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-emails-field-values.util.ts index 1112875f6e4..91d6dc1da21 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-emails-field-values.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-emails-field-values.util.ts @@ -31,23 +31,28 @@ export const mergeEmailsFieldValues = ( primaryEmail = fallbackRecord?.value.primaryEmail || ''; } - const allAdditionalEmails: string[] = []; + const allEmails: string[] = []; recordsWithValues.forEach((record) => { + if (hasRecordFieldValue(record.value.primaryEmail)) { + allEmails.push(record.value.primaryEmail); + } + const additionalEmails = parseArrayOrJsonStringToArray( record.value.additionalEmails, ); - allAdditionalEmails.push( + allEmails.push( ...additionalEmails.filter((email) => hasRecordFieldValue(email)), ); }); - const uniqueAdditionalEmails = Array.from(new Set(allAdditionalEmails)); + const uniqueEmails = Array.from(new Set(allEmails)).filter( + (email) => email !== primaryEmail, + ); return { primaryEmail, - additionalEmails: - uniqueAdditionalEmails.length > 0 ? uniqueAdditionalEmails : null, + additionalEmails: uniqueEmails.length > 0 ? uniqueEmails : null, }; }; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-links-field-values.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-links-field-values.util.ts index a2d9fbd9ee7..c8f4eafa3e6 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-links-field-values.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-links-field-values.util.ts @@ -44,25 +44,33 @@ export const mergeLinksFieldValues = ( } } - const allSecondaryLinks: LinkMetadata[] = []; + const allLinks: LinkMetadata[] = []; recordsWithValues.forEach((record) => { + if (hasRecordFieldValue(record.value.primaryLinkUrl)) { + allLinks.push({ + url: record.value.primaryLinkUrl, + label: record.value.primaryLinkLabel, + }); + } + const secondaryLinks = parseArrayOrJsonStringToArray( record.value.secondaryLinks, ); - allSecondaryLinks.push( + allLinks.push( ...secondaryLinks.filter((link) => hasRecordFieldValue(link.url)), ); }); - const uniqueSecondaryLinks = uniqBy(allSecondaryLinks, 'url'); + const uniqueLinks = uniqBy(allLinks, 'url').filter( + (link) => link.url !== primaryLinkUrl, + ); const result = { primaryLinkLabel, primaryLinkUrl, - secondaryLinks: - uniqueSecondaryLinks.length > 0 ? uniqueSecondaryLinks : null, + secondaryLinks: uniqueLinks.length > 0 ? uniqueLinks : null, }; return result; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-phones-field-values.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-phones-field-values.util.ts index caba7a0237c..e7e95d3d884 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-phones-field-values.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-phones-field-values.util.ts @@ -47,11 +47,19 @@ export const mergePhonesFieldValues = ( } } - const allAdditionalPhones: AdditionalPhoneMetadata[] = []; + const allPhones: AdditionalPhoneMetadata[] = []; recordsWithValues.forEach((record) => { + if (hasRecordFieldValue(record.value.primaryPhoneNumber)) { + allPhones.push({ + number: record.value.primaryPhoneNumber, + countryCode: record.value.primaryPhoneCountryCode, + callingCode: record.value.primaryPhoneCallingCode, + }); + } + if (Array.isArray(record.value.additionalPhones)) { - allAdditionalPhones.push( + allPhones.push( ...record.value.additionalPhones.filter((phone) => hasRecordFieldValue(phone.number), ), @@ -59,13 +67,14 @@ export const mergePhonesFieldValues = ( } }); - const uniqueAdditionalPhones = uniqBy(allAdditionalPhones, 'number'); + const uniquePhones = uniqBy(allPhones, 'number').filter( + (phone) => phone.number !== primaryPhoneNumber, + ); return { primaryPhoneNumber, primaryPhoneCountryCode: primaryPhoneCountryCode!, primaryPhoneCallingCode, - additionalPhones: - uniqueAdditionalPhones.length > 0 ? uniqueAdditionalPhones : null, + additionalPhones: uniquePhones.length > 0 ? uniquePhones : null, }; }; diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/companies-merge-many.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/companies-merge-many.integration-spec.ts index c8e11340996..ec16912b070 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/companies-merge-many.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-generated/companies-merge-many.integration-spec.ts @@ -88,6 +88,9 @@ describe('companies merge resolvers (integration)', () => { expect(mergedCompany.linkedinLink.primaryLinkLabel).toBe('Main LinkedIn'); expect(mergedCompany.linkedinLink.secondaryLinks).toEqual( expect.arrayContaining([ + expect.objectContaining({ + url: 'https://linkedin.com/company/company-b', + }), expect.objectContaining({ url: 'linkedin.com/company/subsidiary-a1', }), @@ -102,7 +105,7 @@ describe('companies merge resolvers (integration)', () => { }), ]), ); - expect(mergedCompany.linkedinLink.secondaryLinks).toHaveLength(4); + expect(mergedCompany.linkedinLink.secondaryLinks).toHaveLength(5); }); it('should merge links with deduplication', async () => { @@ -171,12 +174,13 @@ describe('companies merge resolvers (integration)', () => { ); const secondaryLinks = mergedCompany.linkedinLink.secondaryLinks; - expect(secondaryLinks).toHaveLength(3); + expect(secondaryLinks).toHaveLength(4); const urls = secondaryLinks.map((link: { url: string }) => link.url); expect(urls).toEqual( expect.arrayContaining([ + 'https://linkedin.com/company/corp-tech', 'linkedin.com/company/shared-subsidiary', 'linkedin.com/company/tech-division', 'linkedin.com/company/corp-division', @@ -243,6 +247,9 @@ describe('companies merge resolvers (integration)', () => { expect(mergedCompany.linkedinLink.primaryLinkLabel).toBe('Second Label'); expect(mergedCompany.linkedinLink.secondaryLinks).toEqual( expect.arrayContaining([ + expect.objectContaining({ + url: 'https://linkedin.com/company/first-priority', + }), expect.objectContaining({ url: 'linkedin.com/company/first-sub' }), expect.objectContaining({ url: 'linkedin.com/company/second-sub' }), ]), diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/people-merge-many.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/people-merge-many.integration-spec.ts index bb1c48fc007..1900e093cd3 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/people-merge-many.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-generated/people-merge-many.integration-spec.ts @@ -81,13 +81,14 @@ describe('people merge resolvers (integration)', () => { expect(mergedPerson.emails.primaryEmail).toBe('john@example.com'); expect(mergedPerson.emails.additionalEmails).toEqual( expect.arrayContaining([ + 'jane@example.com', 'john.alt@example.com', 'john.work@example.com', 'jane.alt@example.com', 'jane.personal@example.com', ]), ); - expect(mergedPerson.emails.additionalEmails).toHaveLength(4); + expect(mergedPerson.emails.additionalEmails).toHaveLength(5); }); it('should merge emails with deduplication', async () => { @@ -145,9 +146,10 @@ describe('people merge resolvers (integration)', () => { expect(mergedPerson.emails.primaryEmail).toBe('alice@example.com'); const additionalEmails = mergedPerson.emails.additionalEmails; - expect(additionalEmails).toHaveLength(3); + expect(additionalEmails).toHaveLength(4); expect(additionalEmails).toEqual( expect.arrayContaining([ + 'bob@example.com', 'shared@example.com', 'alice.work@example.com', 'bob.work@example.com', @@ -213,6 +215,7 @@ describe('people merge resolvers (integration)', () => { expect(mergedPerson.emails.primaryEmail).toBe('second@example.com'); expect(mergedPerson.emails.additionalEmails).toEqual( expect.arrayContaining([ + 'first@example.com', 'first.extra@example.com', 'second.extra@example.com', ]), @@ -279,6 +282,7 @@ describe('people merge resolvers (integration)', () => { expect(dryRunResult.emails.primaryEmail).toBe('test1@example.com'); expect(dryRunResult.emails.additionalEmails).toEqual( expect.arrayContaining([ + 'test2@example.com', 'test1.extra@example.com', 'test2.extra@example.com', ]), @@ -401,22 +405,24 @@ describe('people merge resolvers (integration)', () => { expect(mergedPerson.phones.primaryPhoneCallingCode).toBe('+1'); expect(mergedPerson.phones.additionalPhones).toEqual( expect.arrayContaining([ + expect.objectContaining({ number: '4445556789' }), expect.objectContaining({ number: '5559876543' }), expect.objectContaining({ number: '4441112222' }), ]), ); - expect(mergedPerson.phones.additionalPhones).toHaveLength(2); + expect(mergedPerson.phones.additionalPhones).toHaveLength(3); expect(mergedPerson.whatsapp.primaryPhoneNumber).toBe('810407803'); expect(mergedPerson.whatsapp.primaryPhoneCountryCode).toBe('FR'); expect(mergedPerson.whatsapp.primaryPhoneCallingCode).toBe('+33'); expect(mergedPerson.whatsapp.additionalPhones).toEqual( expect.arrayContaining([ + expect.objectContaining({ number: '987654321' }), expect.objectContaining({ number: '8104078034' }), expect.objectContaining({ number: '123456789' }), ]), ); - expect(mergedPerson.whatsapp.additionalPhones).toHaveLength(2); + expect(mergedPerson.whatsapp.additionalPhones).toHaveLength(3); }); }); });