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
This commit is contained in:
neo773
2025-09-02 10:05:42 +02:00
committed by GitHub
parent d94e984a80
commit a7a79090cb
5 changed files with 50 additions and 33 deletions
@@ -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,
});
}
}
@@ -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,
@@ -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);
@@ -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),
};
};
@@ -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<PhonesMetadata, 'additionalPhones'> & {
additionalPhones: Partial<AdditionalPhoneMetadata>[];
additionalPhones: string | null;
}
>
| null
@@ -194,12 +196,20 @@ export const transformPhonesValue = ({
number: primary.primaryPhoneNumber,
});
const parsedAdditionalPhones = isArray(additionalPhones)
? additionalPhones
: [];
const parsedAdditionalPhones = isNonEmptyString(additionalPhones)
? (parseJson<Partial<AdditionalPhoneMetadata>[]>(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,