RICH_TEXT_V2 backend (#9848)

- Add RICH_TEXT_V2 composite type to backend.
- Add `bodyV2` field to tasks and notes.
- Minimum required frontend changes to avoid errors when creating a note

[Testing
instructions](https://github.com/twentyhq/twenty/pull/9690#issuecomment-2602378218)

---------

Co-authored-by: ad-elias <elias@autodiligence.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
eliasylonen
2025-01-28 14:05:06 +01:00
committed by GitHub
parent 6f72f1af33
commit b63ae14318
34 changed files with 517 additions and 102 deletions

View File

@ -9,6 +9,7 @@ import { emailsCompositeType } from 'src/engine/metadata-modules/field-metadata/
import { fullNameCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
import { linksCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type';
import { phonesCompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/phones.composite-type';
import { richTextV2CompositeType } from 'src/engine/metadata-modules/field-metadata/composite-types/rich-text-v2.composite-type';
export const compositeTypeDefinitions = new Map<
FieldMetadataType,
@ -21,4 +22,5 @@ export const compositeTypeDefinitions = new Map<
[FieldMetadataType.ACTOR, actorCompositeType],
[FieldMetadataType.EMAILS, emailsCompositeType],
[FieldMetadataType.PHONES, phonesCompositeType],
[FieldMetadataType.RICH_TEXT_V2, richTextV2CompositeType],
]);

View File

@ -0,0 +1,29 @@
import { FieldMetadataType } from 'twenty-shared';
import { z } from 'zod';
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
export const richTextV2CompositeType: CompositeType = {
type: FieldMetadataType.RICH_TEXT_V2,
properties: [
{
name: 'blocknote',
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
},
{
name: 'markdown',
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
},
],
};
export const richTextV2ValueSchema = z.object({
blocknote: z.string().nullable(),
markdown: z.string().nullable(),
});
export type RichTextV2Metadata = z.infer<typeof richTextV2ValueSchema>;

View File

@ -35,11 +35,22 @@ export class FieldMetadataDefaultValueRawJson {
value: object | null;
}
export class FieldMetadataDefaultValueRichTextV2 {
@ValidateIf((object, value) => value !== null)
@IsQuotedString()
blocknote: string | null;
@ValidateIf((object, value) => value !== null)
@IsQuotedString()
markdown: string | null;
}
export class FieldMetadataDefaultValueRichText {
@ValidateIf((_object, value) => value !== null)
@IsString()
value: string | null;
}
export class FieldMetadataDefaultValueNumber {
@ValidateIf((object, value) => value !== null)
@IsNumber()

View File

@ -0,0 +1,23 @@
import { FieldMetadataType } from 'twenty-shared';
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
describe('computeCompositeColumnName', () => {
it('should compute composite column name for rich text v2 field', () => {
const fieldMetadata = {
name: 'bodyV2',
type: FieldMetadataType.RICH_TEXT_V2,
};
const property = {
name: 'markdown',
type: FieldMetadataType.TEXT,
hidden: false,
isRequired: false,
};
expect(computeCompositeColumnName(fieldMetadata, property)).toEqual(
'bodyV2Markdown',
);
});
});

View File

@ -47,6 +47,11 @@ export function generateDefaultValue(
primaryPhoneCallingCode: "''",
additionalPhones: null,
};
case FieldMetadataType.RICH_TEXT_V2:
return {
blocknote: "''",
markdown: "''",
};
default:
return null;
}

View File

@ -9,7 +9,8 @@ export const isCompositeFieldMetadataType = (
| FieldMetadataType.LINKS
| FieldMetadataType.ACTOR
| FieldMetadataType.EMAILS
| FieldMetadataType.PHONES => {
| FieldMetadataType.PHONES
| FieldMetadataType.RICH_TEXT_V2 => {
return [
FieldMetadataType.CURRENCY,
FieldMetadataType.FULL_NAME,
@ -18,5 +19,6 @@ export const isCompositeFieldMetadataType = (
FieldMetadataType.ACTOR,
FieldMetadataType.EMAILS,
FieldMetadataType.PHONES,
FieldMetadataType.RICH_TEXT_V2,
].includes(type);
};

View File

@ -21,6 +21,7 @@ import {
FieldMetadataDefaultValueNumber,
FieldMetadataDefaultValuePhones,
FieldMetadataDefaultValueRawJson,
FieldMetadataDefaultValueRichTextV2,
FieldMetadataDefaultValueString,
FieldMetadataDefaultValueStringArray,
FieldMetadataDefaultValueUuidFunction,
@ -47,6 +48,7 @@ export const defaultValueValidatorsMap = {
[FieldMetadataType.SELECT]: [FieldMetadataDefaultValueString],
[FieldMetadataType.MULTI_SELECT]: [FieldMetadataDefaultValueStringArray],
[FieldMetadataType.ADDRESS]: [FieldMetadataDefaultValueAddress],
[FieldMetadataType.RICH_TEXT_V2]: [FieldMetadataDefaultValueRichTextV2],
[FieldMetadataType.RICH_TEXT]: [FieldMetadataDefaultValueString],
[FieldMetadataType.RAW_JSON]: [FieldMetadataDefaultValueRawJson],
[FieldMetadataType.LINKS]: [FieldMetadataDefaultValueLinks],

View File

@ -26,7 +26,8 @@ export type CompositeFieldMetadataType =
| FieldMetadataType.FULL_NAME
| FieldMetadataType.LINKS
| FieldMetadataType.EMAILS
| FieldMetadataType.PHONES;
| FieldMetadataType.PHONES
| FieldMetadataType.RICH_TEXT_V2;
@Injectable()
export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<CompositeFieldMetadataType> {

View File

@ -94,6 +94,10 @@ export class WorkspaceMigrationFactory {
FieldMetadataType.TS_VECTOR,
{ factory: this.tsVectorColumnActionFactory },
],
[
FieldMetadataType.RICH_TEXT_V2,
{ factory: this.compositeColumnActionFactory },
],
]);
}