Common - Field validation (#15491)
Closes : https://github.com/twentyhq/core-team-issues/issues/1622 To do in other PR : - Add migration command for non nullable text, raw_json & array fields - Add null transformation --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
+4
-8
@@ -32,9 +32,9 @@ describe('computeSchemaComponents', () => {
|
||||
"lastName": "Osinski",
|
||||
},
|
||||
"fieldLinks": {
|
||||
"additionalLinks": [],
|
||||
"primaryLinkLabel": "",
|
||||
"primaryLinkUrl": "https://narrow-help.net/",
|
||||
"secondaryLinks": [],
|
||||
},
|
||||
"fieldMultiSelect": [
|
||||
"OPTION_1",
|
||||
@@ -46,9 +46,7 @@ describe('computeSchemaComponents', () => {
|
||||
"primaryPhoneCountryCode": "FR",
|
||||
"primaryPhoneNumber": "06 10 20 30 40",
|
||||
},
|
||||
"fieldSelect": [
|
||||
"OPTION_1",
|
||||
],
|
||||
"fieldSelect": "OPTION_1",
|
||||
},
|
||||
"properties": {
|
||||
"fieldActor": {
|
||||
@@ -535,9 +533,9 @@ describe('computeSchemaComponents', () => {
|
||||
"lastName": "Jones",
|
||||
},
|
||||
"fieldLinks": {
|
||||
"additionalLinks": [],
|
||||
"primaryLinkLabel": "",
|
||||
"primaryLinkUrl": "https://unlawful-blowgun.biz",
|
||||
"secondaryLinks": [],
|
||||
},
|
||||
"fieldMultiSelect": [
|
||||
"OPTION_1",
|
||||
@@ -549,9 +547,7 @@ describe('computeSchemaComponents', () => {
|
||||
"primaryPhoneCountryCode": "FR",
|
||||
"primaryPhoneNumber": "06 10 20 30 40",
|
||||
},
|
||||
"fieldSelect": [
|
||||
"OPTION_1",
|
||||
],
|
||||
"fieldSelect": "OPTION_1",
|
||||
},
|
||||
"properties": {
|
||||
"fieldActor": {
|
||||
|
||||
+3
-3
@@ -58,7 +58,7 @@ export const generateRandomFieldValue = ({
|
||||
return {
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: faker.internet.url(),
|
||||
additionalLinks: [],
|
||||
secondaryLinks: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,10 +82,10 @@ export const generateRandomFieldValue = ({
|
||||
|
||||
case FieldMetadataType.SELECT: {
|
||||
if (!isDefined(field.options) || !isDefined(field.options[0].value)) {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
|
||||
return [field.options[0].value];
|
||||
return field.options[0].value;
|
||||
}
|
||||
|
||||
case FieldMetadataType.MULTI_SELECT: {
|
||||
|
||||
+8
-80
@@ -2,15 +2,16 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectRecord,
|
||||
compositeTypeDefinitions,
|
||||
type RichTextV2Metadata,
|
||||
richTextV2ValueSchema,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { transformEmailsValue } from 'src/engine/core-modules/record-transformer/utils/transform-emails-value.util';
|
||||
import { transformLinksValue } from 'src/engine/core-modules/record-transformer/utils/transform-links-value.util';
|
||||
import { transformPhonesValue } from 'src/engine/core-modules/record-transformer/utils/transform-phones-value.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { transformRichTextV2Value } from 'src/engine/core-modules/record-transformer/utils/transform-rich-text-v2.util';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
@Injectable()
|
||||
export class RecordInputTransformerService {
|
||||
@@ -18,15 +19,9 @@ export class RecordInputTransformerService {
|
||||
recordInput,
|
||||
objectMetadataMapItem,
|
||||
}: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
recordInput: Record<string, any>;
|
||||
recordInput: Partial<ObjectRecord>;
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
}): Promise<Record<string, any>> {
|
||||
if (!recordInput) {
|
||||
return recordInput;
|
||||
}
|
||||
|
||||
}): Promise<Partial<ObjectRecord>> {
|
||||
let transformedEntries = {};
|
||||
|
||||
for (const [key, value] of Object.entries(recordInput)) {
|
||||
@@ -72,11 +67,11 @@ export class RecordInputTransformerService {
|
||||
'Rich text is not supported, please use RICH_TEXT_V2 instead',
|
||||
);
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
return this.transformRichTextV2Value(value);
|
||||
return await transformRichTextV2Value(value);
|
||||
case FieldMetadataType.LINKS:
|
||||
return transformLinksValue(value);
|
||||
case FieldMetadataType.EMAILS:
|
||||
return this.transformEmailsValue(value);
|
||||
return transformEmailsValue(value);
|
||||
case FieldMetadataType.PHONES:
|
||||
return transformPhonesValue({ input: value });
|
||||
default:
|
||||
@@ -84,73 +79,6 @@ export class RecordInputTransformerService {
|
||||
}
|
||||
}
|
||||
|
||||
private async transformRichTextV2Value(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
richTextValue: any,
|
||||
): Promise<RichTextV2Metadata> {
|
||||
const parsedValue = richTextV2ValueSchema.parse(richTextValue);
|
||||
|
||||
const { ServerBlockNoteEditor } = await import('@blocknote/server-util');
|
||||
|
||||
const serverBlockNoteEditor = ServerBlockNoteEditor.create();
|
||||
|
||||
// Patch: Handle cases where blocknote to markdown conversion fails for certain block types (custom/code blocks)
|
||||
// Todo : This may be resolved once the server-utils library is updated with proper conversion support - #947
|
||||
let convertedMarkdown: string | null = null;
|
||||
|
||||
try {
|
||||
convertedMarkdown = isDefined(parsedValue.blocknote)
|
||||
? await serverBlockNoteEditor.blocksToMarkdownLossy(
|
||||
JSON.parse(parsedValue.blocknote),
|
||||
)
|
||||
: null;
|
||||
} catch {
|
||||
convertedMarkdown = parsedValue.blocknote || null;
|
||||
}
|
||||
|
||||
const convertedBlocknote = parsedValue.markdown
|
||||
? JSON.stringify(
|
||||
await serverBlockNoteEditor.tryParseMarkdownToBlocks(
|
||||
parsedValue.markdown,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
return {
|
||||
markdown: parsedValue.markdown || convertedMarkdown,
|
||||
blocknote: parsedValue.blocknote || convertedBlocknote,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private transformEmailsValue(value: any): any {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
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 {
|
||||
primaryEmail,
|
||||
additionalEmails,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private stringifySubFields(fieldMetadataType: FieldMetadataType, value: any) {
|
||||
const compositeType = compositeTypeDefinitions.get(fieldMetadataType);
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const transformEmailsValue = (value: any): any => {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let additionalEmails = value?.additionalEmails;
|
||||
const primaryEmail = value?.primaryEmail
|
||||
? value.primaryEmail.toLowerCase()
|
||||
: '';
|
||||
|
||||
if (additionalEmails) {
|
||||
try {
|
||||
const emailArray = (
|
||||
isNonEmptyString(additionalEmails)
|
||||
? JSON.parse(additionalEmails)
|
||||
: additionalEmails
|
||||
) as string[];
|
||||
|
||||
additionalEmails = JSON.stringify(
|
||||
emailArray.map((email) => email.toLowerCase()),
|
||||
);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
primaryEmail,
|
||||
additionalEmails,
|
||||
};
|
||||
};
|
||||
+1
-1
@@ -32,7 +32,7 @@ export const transformLinksValue = (
|
||||
|
||||
const secondaryLinksArray = isNonEmptyString(secondaryLinksRaw)
|
||||
? parseJson<LinkMetadataNullable[]>(secondaryLinksRaw)
|
||||
: null;
|
||||
: secondaryLinksRaw;
|
||||
|
||||
const { primaryLinkLabel, primaryLinkUrl, secondaryLinks } = removeEmptyLinks(
|
||||
{
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import {
|
||||
type RichTextV2Metadata,
|
||||
richTextV2ValueSchema,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const transformRichTextV2Value = async (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
richTextValue: any,
|
||||
): Promise<RichTextV2Metadata> => {
|
||||
const parsedValue = isNonEmptyString(richTextValue)
|
||||
? richTextV2ValueSchema.parse(richTextValue)
|
||||
: richTextValue;
|
||||
|
||||
const { ServerBlockNoteEditor } = await import('@blocknote/server-util');
|
||||
|
||||
const serverBlockNoteEditor = ServerBlockNoteEditor.create();
|
||||
|
||||
// Patch: Handle cases where blocknote to markdown conversion fails for certain block types (custom/code blocks)
|
||||
// Todo : This may be resolved once the server-utils library is updated with proper conversion support - #947
|
||||
let convertedMarkdown: string | null = null;
|
||||
|
||||
try {
|
||||
convertedMarkdown = isDefined(parsedValue.blocknote)
|
||||
? await serverBlockNoteEditor.blocksToMarkdownLossy(
|
||||
JSON.parse(parsedValue.blocknote),
|
||||
)
|
||||
: null;
|
||||
} catch {
|
||||
convertedMarkdown = parsedValue.blocknote || null;
|
||||
}
|
||||
|
||||
const convertedBlocknote = parsedValue.markdown
|
||||
? JSON.stringify(
|
||||
await serverBlockNoteEditor.tryParseMarkdownToBlocks(
|
||||
parsedValue.markdown,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
return {
|
||||
markdown: parsedValue.markdown || convertedMarkdown,
|
||||
blocknote: parsedValue.blocknote || convertedBlocknote,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user