From a7a79090cbe6964e4299c8875be5c828ea608790 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:35:42 +0530 Subject: [PATCH] fix: QueryRunner `overrideDataByFieldMetadata` sub fields not being stringified (#14187) Adding `secondaryLinks` to company is broken, this fixes it Regression can be traced back to this commit https://github.com/twentyhq/twenty/pull/10912/files#diff-8a992c6bff80abc8c9075d585d293f6aa98c246df097efb4f904b82dc3ff8114R210-R214 https://github.com/user-attachments/assets/463cee62-8ee3-45dc-9912-fd8fb5244579 --- .../factories/query-runner-args.factory.ts | 24 +++++++++++-------- .../record-input-transformer.service.ts | 2 +- .../transform-links-value.util.spec.ts | 18 +++++++------- .../utils/transform-links-value.util.ts | 19 ++++++++------- .../utils/transform-phones-value.util.ts | 20 ++++++++++++---- 5 files changed, 50 insertions(+), 33 deletions(-) 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 bebcf3ded69..607b9a6101b 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 @@ -114,6 +114,7 @@ export class QueryRunnerArgsFactory { 'id', id, fieldMetadataMapByNameByName, + options.objectMetadataItemWithFieldMaps, ), ) ?? [], )) as string[], @@ -132,6 +133,7 @@ export class QueryRunnerArgsFactory { 'id', id, fieldMetadataMapByNameByName, + options.objectMetadataItemWithFieldMaps, ), ) ?? [], )) as string[], @@ -191,13 +193,14 @@ export class QueryRunnerArgsFactory { case FieldMetadataType.RICH_TEXT_V2: case FieldMetadataType.LINKS: case FieldMetadataType.EMAILS: { - const transformedValue = - await this.recordInputTransformerService.transformFieldValue( - fieldMetadata.type, - value, - ); + const transformedRecord = + await this.recordInputTransformerService.process({ + recordInput: { [key]: value }, + objectMetadataMapItem: + options.objectMetadataItemWithFieldMaps, + }); - return [key, transformedValue]; + return [key, transformedRecord[key]]; } default: return [key, value]; @@ -283,6 +286,7 @@ export class QueryRunnerArgsFactory { // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, fieldMetadataMapByName: FieldMetadataMap, + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ) { const fieldMetadata = fieldMetadataMapByName[key]; @@ -290,9 +294,9 @@ export class QueryRunnerArgsFactory { return value; } - return this.recordInputTransformerService.transformFieldValue( - fieldMetadata.type, - value, - ); + return this.recordInputTransformerService.process({ + recordInput: { [key]: value }, + objectMetadataMapItem: objectMetadataItemWithFieldMaps, + }); } } diff --git a/packages/twenty-server/src/engine/core-modules/record-transformer/services/record-input-transformer.service.ts b/packages/twenty-server/src/engine/core-modules/record-transformer/services/record-input-transformer.service.ts index 993a44bb278..3a2c0a9a865 100644 --- a/packages/twenty-server/src/engine/core-modules/record-transformer/services/record-input-transformer.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-transformer/services/record-input-transformer.service.ts @@ -52,7 +52,7 @@ export class RecordInputTransformerService { return transformedEntries; } - async transformFieldValue( + private async transformFieldValue( fieldType: FieldMetadataType, // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, diff --git a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/__tests__/transform-links-value.util.spec.ts b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/__tests__/transform-links-value.util.spec.ts index 507c53f3769..f51a9df1a5e 100644 --- a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/__tests__/transform-links-value.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/__tests__/transform-links-value.util.spec.ts @@ -7,7 +7,7 @@ describe('transformLinksValue', () => { expect(transformLinksValue({})).toEqual({ primaryLinkLabel: null, primaryLinkUrl: null, - secondaryLinks: '[]', + secondaryLinks: null, }); }); @@ -16,13 +16,13 @@ describe('transformLinksValue', () => { const input = { primaryLinkUrl: 'HTTPS://EXAMPLE.COM', primaryLinkLabel: 'Example', - secondaryLinks: '[]', + secondaryLinks: null, }; const expected = { primaryLinkUrl: 'https://example.com', primaryLinkLabel: 'Example', - secondaryLinks: '[]', + secondaryLinks: null, }; expect(transformLinksValue(input)).toEqual(expected); @@ -32,13 +32,13 @@ describe('transformLinksValue', () => { const input = { primaryLinkUrl: 'https://example.com/', primaryLinkLabel: 'Example', - secondaryLinks: '[]', + secondaryLinks: null, }; const expected = { primaryLinkUrl: 'https://example.com', primaryLinkLabel: 'Example', - secondaryLinks: '[]', + secondaryLinks: null, }; expect(transformLinksValue(input)).toEqual(expected); @@ -48,13 +48,13 @@ describe('transformLinksValue', () => { const input = { primaryLinkUrl: 'example.com', primaryLinkLabel: 'Example', - secondaryLinks: '[]', + secondaryLinks: null, }; const expected = { primaryLinkUrl: 'example.com', primaryLinkLabel: 'Example', - secondaryLinks: '[]', + secondaryLinks: null, }; expect(transformLinksValue(input)).toEqual(expected); @@ -64,13 +64,13 @@ describe('transformLinksValue', () => { const input = { primaryLinkUrl: 'www.example.com', primaryLinkLabel: 'Example', - secondaryLinks: '[]', + secondaryLinks: null, }; const expected = { primaryLinkUrl: 'www.example.com', primaryLinkLabel: 'Example', - secondaryLinks: '[]', + secondaryLinks: null, }; expect(transformLinksValue(input)).toEqual(expected); diff --git a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-links-value.util.ts b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-links-value.util.ts index 39f6dc49196..cd1c8aa79ad 100644 --- a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-links-value.util.ts +++ b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-links-value.util.ts @@ -1,4 +1,5 @@ import { isNonEmptyString } from '@sniptt/guards'; +import isEmpty from 'lodash.isempty'; import { isDefined, lowercaseUrlOriginAndRemoveTrailingSlash, @@ -41,19 +42,21 @@ export const transformLinksValue = ( }, ); + const processedSecondaryLinks = secondaryLinks?.map((link) => ({ + ...link, + url: isDefined(link.url) + ? lowercaseUrlOriginAndRemoveTrailingSlash(link.url) + : link.url, + })); + return { ...value, primaryLinkUrl: isDefined(primaryLinkUrl) ? lowercaseUrlOriginAndRemoveTrailingSlash(primaryLinkUrl) : primaryLinkUrl, primaryLinkLabel, - secondaryLinks: JSON.stringify( - secondaryLinks?.map((link) => ({ - ...link, - url: isDefined(link.url) - ? lowercaseUrlOriginAndRemoveTrailingSlash(link.url) - : link.url, - })), - ), + secondaryLinks: isEmpty(processedSecondaryLinks) + ? null + : JSON.stringify(processedSecondaryLinks), }; }; diff --git a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-phones-value.util.ts b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-phones-value.util.ts index 4bd4a4beaa2..37ecccf70fe 100644 --- a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-phones-value.util.ts +++ b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-phones-value.util.ts @@ -4,10 +4,12 @@ import { type CountryCallingCode, parsePhoneNumberWithError, } from 'libphonenumber-js'; +import isEmpty from 'lodash.isempty'; import { getCountryCodesForCallingCode, isDefined, isValidCountryCode, + parseJson, removeUndefinedFields, } from 'twenty-shared/utils'; @@ -23,7 +25,7 @@ import { export type PhonesFieldGraphQLInput = | Partial< Omit & { - additionalPhones: Partial[]; + additionalPhones: string | null; } > | null @@ -194,12 +196,20 @@ export const transformPhonesValue = ({ number: primary.primaryPhoneNumber, }); - const parsedAdditionalPhones = isArray(additionalPhones) - ? additionalPhones - : []; + const parsedAdditionalPhones = isNonEmptyString(additionalPhones) + ? (parseJson[]>(additionalPhones) ?? []) + : isArray(additionalPhones) + ? additionalPhones + : []; + + const validatedAdditionalPhones = parsedAdditionalPhones.map( + validateAndInferPhoneInput, + ); return removeUndefinedFields({ - additionalPhones: parsedAdditionalPhones.map(validateAndInferPhoneInput), + additionalPhones: isEmpty(validatedAdditionalPhones) + ? null + : JSON.stringify(validatedAdditionalPhones), primaryPhoneCallingCode, primaryPhoneCountryCode, primaryPhoneNumber,