Deprecate legacy RICH_TEXT field metadata type (#18623)

## Summary

- Removes the deprecated `RICH_TEXT` (V1) field metadata type from the
codebase entirely
- Adds a 1.20 upgrade command that migrates existing `RICH_TEXT` fields
to `TEXT` in `core.fieldMetadata`
- Cleans up ~70 files across `twenty-shared`, `twenty-server`,
`twenty-front`, `twenty-sdk`, and `twenty-zapier`

## Context

`RICH_TEXT` was a legacy field type that stored rich text as a single
`text` column. It was already **read-only** — writes threw errors
directing users to `RICH_TEXT_V2` instead. `RICH_TEXT_V2` is the current
approach: a composite type with `blocknote` (editor JSON) and `markdown`
subfields. Keeping the deprecated type added maintenance burden without
any value.

Since the underlying database column type for `RICH_TEXT` was already
`text` (same as `TEXT`), the migration only needs to update the metadata
— no data migration or column changes required.

## Changes

### Upgrade command (new)
- `1-20-migrate-rich-text-to-text.command.ts` — runs `UPDATE
core."fieldMetadata" SET "type" = 'TEXT' WHERE "type" = 'RICH_TEXT'` per
workspace, with cache invalidation

### Enum & shared types
- Removed `RICH_TEXT` from `FieldMetadataType` enum
- Removed from `FieldMetadataDefaultValueMapping`,
`isFieldMetadataTextKind`

### Server (~30 files)
- Removed from type mapper (scalar, filter, order-by), data processors,
input transformer, filter operators, zod schemas, column type mapping,
searchable fields, RLS matching, OpenAPI schema, fake value generators
- Removed from field creation flow and field metadata type validator
- Updated dev seeder Pet `bio` field to `TEXT`
- Cleaned up mocks, snapshots, integration tests

### Frontend (~25 files)
- Deleted: `RichTextFieldDisplay`, `isFieldRichText`,
`isFieldRichTextValue`, `useRichTextFieldDisplay`
- Removed from `FieldDisplay`, `usePersistField`, `isFieldValueEmpty`,
`isRecordMatchingFilter`, `generateEmptyFieldValue`,
`isFieldCellSupported`, spreadsheet import, workflow fake values
- Removed from settings types, field type configs, and field creation
exclusion list
- Updated tests, mocks, and stories

### SDK & Zapier
- Removed from generated GraphQL schema and TypeScript types
- Removed from Zapier `computeInputFields`
This commit is contained in:
Charles Bochet 2026-03-13 17:25:40 +01:00 committed by GitHub
parent 49bdcd6bd5
commit 46e515436e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 165 additions and 368 deletions

View file

@ -1825,7 +1825,6 @@ export enum FieldMetadataType {
RATING = 'RATING',
RAW_JSON = 'RAW_JSON',
RELATION = 'RELATION',
RICH_TEXT = 'RICH_TEXT',
RICH_TEXT_V2 = 'RICH_TEXT_V2',
SELECT = 'SELECT',
TEXT = 'TEXT',

View file

@ -96,12 +96,12 @@ describe('filterSortableFieldMetadataItems', () => {
expect(filterSortableFieldMetadataItems(field)).toBe(false);
});
it('should NOT allow unsortable field types like RICH_TEXT', () => {
it('should NOT allow unsortable field types like RICH_TEXT_V2', () => {
const field = {
type: FieldMetadataType.RICH_TEXT,
type: FieldMetadataType.RICH_TEXT_V2,
isSystem: false,
isActive: true,
name: 'richText',
name: 'richTextV2',
};
expect(filterSortableFieldMetadataItems(field)).toBe(false);

View file

@ -17,7 +17,6 @@ export const isNonCompositeField = (type: FieldType) => {
FieldMetadataType.MULTI_SELECT,
FieldMetadataType.POSITION,
FieldMetadataType.RAW_JSON,
FieldMetadataType.RICH_TEXT,
FieldMetadataType.ARRAY,
].includes(type as any);

View file

@ -10,7 +10,6 @@ import { LinksFieldDisplay } from '@/object-record/record-field/ui/meta-types/di
import { PhonesFieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/PhonesFieldDisplay';
import { RatingFieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/RatingFieldDisplay';
import { RelationFromManyFieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/RelationFromManyFieldDisplay';
import { RichTextFieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/RichTextFieldDisplay';
import { RichTextV2FieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/RichTextV2FieldDisplay';
import { isFieldIdentifierDisplay } from '@/object-record/record-field/ui/meta-types/display/utils/isFieldIdentifierDisplay';
import { isFieldActor } from '@/object-record/record-field/ui/types/guards/isFieldActor';
@ -21,7 +20,6 @@ import { isFieldFiles } from '@/object-record/record-field/ui/types/guards/isFie
import { isFieldLinks } from '@/object-record/record-field/ui/types/guards/isFieldLinks';
import { isFieldPhones } from '@/object-record/record-field/ui/types/guards/isFieldPhones';
import { isFieldRating } from '@/object-record/record-field/ui/types/guards/isFieldRating';
import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/isFieldRichText';
import { isFieldRichTextV2 } from '@/object-record/record-field/ui/types/guards/isFieldRichTextV2';
import { MorphRelationManyToOneFieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/MorphRelationManyToOneFieldDisplay';
@ -112,8 +110,6 @@ export const FieldDisplay = () => {
<BooleanFieldDisplay />
) : isFieldRating(fieldDefinition) ? (
<RatingFieldDisplay readonly={isRecordFieldReadOnly} />
) : isFieldRichText(fieldDefinition) ? (
<RichTextFieldDisplay />
) : isFieldRichTextV2(fieldDefinition) ? (
<RichTextV2FieldDisplay />
) : isFieldActor(fieldDefinition) ? (

View file

@ -49,9 +49,7 @@ import { isFieldRating } from '@/object-record/record-field/ui/types/guards/isFi
import { isFieldRatingValue } from '@/object-record/record-field/ui/types/guards/isFieldRatingValue';
import { isFieldRelationManyToOne } from '@/object-record/record-field/ui/types/guards/isFieldRelationManyToOne';
import { isFieldRelationManyToOneValue } from '@/object-record/record-field/ui/types/guards/isFieldRelationManyToOneValue';
import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/isFieldRichText';
import { isFieldRichTextV2 } from '@/object-record/record-field/ui/types/guards/isFieldRichTextV2';
import { isFieldRichTextValue } from '@/object-record/record-field/ui/types/guards/isFieldRichTextValue';
import { isFieldRichTextV2Value } from '@/object-record/record-field/ui/types/guards/isFieldRichTextValueV2';
import { isFieldText } from '@/object-record/record-field/ui/types/guards/isFieldText';
import { isFieldTextValue } from '@/object-record/record-field/ui/types/guards/isFieldTextValue';
@ -145,10 +143,6 @@ export const usePersistField = ({
const fieldIsRawJson =
isFieldRawJson(fieldDefinition) && isFieldRawJsonValue(valueToPersist);
const fieldIsRichText =
isFieldRichText(fieldDefinition) &&
isFieldRichTextValue(valueToPersist);
const fieldIsRichTextV2 =
isFieldRichTextV2(fieldDefinition) &&
isFieldRichTextV2Value(valueToPersist);
@ -185,7 +179,6 @@ export const usePersistField = ({
fieldIsRawJson ||
fieldIsArray ||
fieldIsFiles ||
fieldIsRichText ||
fieldIsRichTextV2;
if (isValuePersistable) {

View file

@ -1,12 +0,0 @@
import { useRichTextFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay';
import { getFirstNonEmptyLineOfRichText } from '@/blocknote-editor/utils/getFirstNonEmptyLineOfRichText';
export const RichTextFieldDisplay = () => {
const { fieldValue } = useRichTextFieldDisplay();
return (
<div>
<span>{getFirstNonEmptyLineOfRichText(fieldValue)}</span>
</div>
);
};

View file

@ -1,37 +0,0 @@
import { useContext } from 'react';
import { type FieldRichTextValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/isFieldRichText';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import type { PartialBlock } from '@blocknote/core';
import { isDefined, parseJson } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
export const useRichTextFieldDisplay = () => {
const { recordId, fieldDefinition } = useContext(FieldContext);
assertFieldMetadata(
FieldMetadataType.RICH_TEXT,
isFieldRichText,
fieldDefinition,
);
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = useRecordFieldValue<FieldRichTextValue | undefined>(
recordId,
fieldName,
fieldDefinition,
);
const fieldValueParsed = isDefined(fieldValue)
? parseJson<PartialBlock[]>(fieldValue)
: null;
return {
fieldDefinition,
fieldValue: fieldValueParsed,
};
};

View file

@ -65,7 +65,7 @@ const RichTextFieldInputWithContext = () => {
fieldDefinition: {
fieldMetadataId: 'richText',
label: 'Rich Text',
type: FieldMetadataType.RICH_TEXT,
type: FieldMetadataType.RICH_TEXT_V2,
iconName: 'IconRichText',
metadata: {
fieldName: 'richText',

View file

@ -135,10 +135,6 @@ export type FieldRichTextV2Metadata = BaseFieldMetadata & {
settings?: null;
};
export type FieldRichTextMetadata = BaseFieldMetadata & {
settings?: null;
};
export type FieldPositionMetadata = BaseFieldMetadata & {
settings?: null;
};
@ -229,8 +225,7 @@ export type FieldMetadata =
| FieldArrayMetadata
| FieldTsVectorMetadata
| FieldRawJsonMetadata
| FieldRichTextV2Metadata
| FieldRichTextMetadata;
| FieldRichTextV2Metadata;
export type FieldTextValue = string;
export type FieldUUidValue = string; // TODO: can we replace with a template literal type, or maybe overkill ?
@ -289,8 +284,6 @@ export type FieldRichTextV2Value = {
markdown: string | null;
};
export type FieldRichTextValue = null | string;
const FieldActorSourceSchema = z.union([
z.literal('API'),
z.literal('IMPORT'),

View file

@ -24,7 +24,6 @@ import {
type FieldRatingMetadata,
type FieldRawJsonMetadata,
type FieldRelationMetadata,
type FieldRichTextMetadata,
type FieldRichTextV2Metadata,
type FieldSelectMetadata,
type FieldTextMetadata,
@ -77,15 +76,13 @@ type AssertFieldMetadataFunction = <
? FieldRawJsonMetadata
: E extends 'RICH_TEXT_V2'
? FieldRichTextV2Metadata
: E extends 'RICH_TEXT'
? FieldRichTextMetadata
: E extends 'ACTOR'
? FieldActorMetadata
: E extends 'ARRAY'
? FieldArrayMetadata
: E extends 'PHONES'
? FieldPhonesMetadata
: never,
: E extends 'ACTOR'
? FieldActorMetadata
: E extends 'ARRAY'
? FieldArrayMetadata
: E extends 'PHONES'
? FieldPhonesMetadata
: never,
>(
fieldType: E,
fieldTypeGuard: (

View file

@ -1,12 +0,0 @@
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
import {
type FieldMetadata,
type FieldRichTextMetadata,
} from '@/object-record/record-field/ui/types/FieldMetadata';
export const isFieldRichText = (
field: Pick<FieldDefinition<FieldMetadata>, 'type'>,
): field is FieldDefinition<FieldRichTextMetadata> =>
field.type === FieldMetadataType.RICH_TEXT;

View file

@ -1,12 +0,0 @@
import { z } from 'zod';
import { type FieldRichTextValue } from '@/object-record/record-field/ui/types/FieldMetadata';
export const richTextSchema: z.ZodType<FieldRichTextValue> = z.union([
z.null(),
z.string(),
]);
export const isFieldRichTextValue = (
fieldValue: unknown,
): fieldValue is FieldRichTextValue =>
richTextSchema.safeParse(fieldValue).success;

View file

@ -32,7 +32,6 @@ import { isFieldPosition } from '@/object-record/record-field/ui/types/guards/is
import { isFieldRating } from '@/object-record/record-field/ui/types/guards/isFieldRating';
import { isFieldRawJson } from '@/object-record/record-field/ui/types/guards/isFieldRawJson';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/isFieldRichText';
import { isFieldRichTextV2 } from '@/object-record/record-field/ui/types/guards/isFieldRichTextV2';
import { isFieldRichTextV2Value } from '@/object-record/record-field/ui/types/guards/isFieldRichTextValueV2';
import { isFieldSelect } from '@/object-record/record-field/ui/types/guards/isFieldSelect';
@ -63,7 +62,6 @@ export const isFieldValueEmpty = ({
isFieldRating(fieldDefinition) ||
isFieldBoolean(fieldDefinition) ||
isFieldRawJson(fieldDefinition) ||
isFieldRichText(fieldDefinition) ||
isFieldPosition(fieldDefinition)
) {
return isValueEmpty(fieldValue);

View file

@ -235,15 +235,6 @@ export const isRecordMatchingFilter = ({
value: record[filterKey],
});
}
case FieldMetadataType.RICH_TEXT: {
// TODO: Implement a better rich text filter once it becomes a composite field
// See this issue for more context: https://github.com/twentyhq/twenty/issues/7613#issuecomment-2408944585
// This should be tackled in Q4'24
return isMatchingStringFilter({
stringFilter: filterValue as StringFilter,
value: record[filterKey],
});
}
case FieldMetadataType.RICH_TEXT_V2: {
return isMatchingRichTextV2Filter({
richTextV2Filter: filterValue as RichTextV2Filter,

View file

@ -91,7 +91,6 @@ export const useBuildSpreadsheetImportFields = () => {
case FieldMetadataType.MORPH_RELATION:
case FieldMetadataType.ACTOR:
case FieldMetadataType.TS_VECTOR:
case FieldMetadataType.RICH_TEXT:
return [];
default:

View file

@ -377,7 +377,6 @@ export const buildRecordFromImportedStructuredRow = ({
case FieldMetadataType.FILES:
case FieldMetadataType.MORPH_RELATION:
case FieldMetadataType.POSITION:
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.TS_VECTOR:
break;
default:

View file

@ -13,11 +13,9 @@ export const spreadsheetImportFilterAvailableFieldMetadataItems = (
(!isHiddenSystemField(fieldMetadataItem) ||
fieldMetadataItem.name === 'id') &&
fieldMetadataItem.name !== 'deletedAt' &&
(![
FieldMetadataType.RELATION,
FieldMetadataType.RICH_TEXT,
FieldMetadataType.ACTOR,
].includes(fieldMetadataItem.type) ||
(![FieldMetadataType.RELATION, FieldMetadataType.ACTOR].includes(
fieldMetadataItem.type,
) ||
fieldMetadataItem.relation?.type === RelationType.MANY_TO_ONE),
)
.sort((fieldMetadataItemA, fieldMetadataItemB) =>

View file

@ -95,9 +95,6 @@ export const generateEmptyFieldValue = ({
case FieldMetadataType.RAW_JSON: {
return null;
}
case FieldMetadataType.RICH_TEXT: {
return null;
}
case FieldMetadataType.RICH_TEXT_V2: {
return {
blocknote: null,

View file

@ -9,11 +9,7 @@ export const isFieldCellSupported = (
fieldMetadataItem: FieldMetadataItem,
objectMetadataItems: ObjectMetadataItem[],
) => {
if (
[FieldMetadataType.POSITION, FieldMetadataType.RICH_TEXT].includes(
fieldMetadataItem.type,
)
) {
if (fieldMetadataItem.type === FieldMetadataType.POSITION) {
return false;
}

View file

@ -3,5 +3,5 @@ import { type PickLiteral } from '~/types/PickLiteral';
export type SettingsExcludedFieldType = PickLiteral<
FieldType,
'POSITION' | 'TS_VECTOR' | 'RICH_TEXT' | 'RICH_TEXT_V2' | 'NUMERIC'
'POSITION' | 'TS_VECTOR' | 'RICH_TEXT_V2' | 'NUMERIC'
>;

View file

@ -51,15 +51,6 @@ describe('getSortIconForFieldType', () => {
).toBe(IconSortDescendingLetters);
});
it('should return IconSortAscendingLetters for RICH_TEXT field ascending', () => {
expect(
getSortIconForFieldType({
fieldType: FieldMetadataType.RICH_TEXT,
orderBy: GraphOrderBy.FIELD_ASC,
}),
).toBe(IconSortAscendingLetters);
});
it('should return IconSortAscendingLetters for RICH_TEXT_V2 field ascending', () => {
expect(
getSortIconForFieldType({

View file

@ -141,15 +141,6 @@ describe('generateFakeValue', () => {
expect(result).toBeNull();
});
it('should generate RICH_TEXT value', () => {
const result = generateFakeValue(
FieldMetadataType.RICH_TEXT,
'FieldMetadataType',
);
expect(result).toBe('My rich text');
});
it('should generate UUID value', () => {
const result = generateFakeValue(
FieldMetadataType.UUID,

View file

@ -68,8 +68,6 @@ const generateFieldMetadataTypeValue = (
return 'Tim Cook';
case FieldMetadataType.RAW_JSON:
return null;
case FieldMetadataType.RICH_TEXT:
return 'My rich text';
case FieldMetadataType.UUID:
return '123e4567-e89b-12d3-a456-426614174000';
default:

View file

@ -24,7 +24,6 @@ export const DEFAULT_ICONS_BY_FIELD_TYPE: Record<FieldMetadataType, string> = {
[FieldMetadataType.ACTOR]: 'IconUsers',
[FieldMetadataType.NUMERIC]: 'IconUsers',
[FieldMetadataType.POSITION]: 'IconUsers',
[FieldMetadataType.RICH_TEXT]: 'IconUsers',
[FieldMetadataType.RICH_TEXT_V2]: 'IconUsers',
[FieldMetadataType.TS_VECTOR]: 'IconUsers',
};

View file

@ -47,7 +47,6 @@ export const SettingsObjectNewFieldSelect = () => {
const excludedFieldTypes: FieldType[] = (
[
FieldMetadataType.NUMERIC,
FieldMetadataType.RICH_TEXT,
FieldMetadataType.RICH_TEXT_V2,
FieldMetadataType.ACTOR,
FieldMetadataType.UUID,

View file

@ -13456,7 +13456,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
"__typename": "Field",
"id": "7700d2ac-0643-44a2-acd0-f0bb446bc08c",
"universalIdentifier": "107ed3e9-2ae0-43ef-a2d6-3f24bc54c2a5",
"type": "RICH_TEXT",
"type": "TEXT",
"name": "bio",
"label": "Bio",
"description": "",

View file

@ -374,7 +374,6 @@ enum FieldMetadataType {
RATING
RAW_JSON
RELATION
RICH_TEXT
RICH_TEXT_V2
SELECT
TEXT

View file

@ -309,7 +309,7 @@ export interface Field {
/** Type of the field */
export type FieldMetadataType = 'ACTOR' | 'ADDRESS' | 'ARRAY' | 'BOOLEAN' | 'CURRENCY' | 'DATE' | 'DATE_TIME' | 'EMAILS' | 'FILES' | 'FULL_NAME' | 'LINKS' | 'MORPH_RELATION' | 'MULTI_SELECT' | 'NUMBER' | 'NUMERIC' | 'PHONES' | 'POSITION' | 'RATING' | 'RAW_JSON' | 'RELATION' | 'RICH_TEXT' | 'RICH_TEXT_V2' | 'SELECT' | 'TEXT' | 'TS_VECTOR' | 'UUID'
export type FieldMetadataType = 'ACTOR' | 'ADDRESS' | 'ARRAY' | 'BOOLEAN' | 'CURRENCY' | 'DATE' | 'DATE_TIME' | 'EMAILS' | 'FILES' | 'FULL_NAME' | 'LINKS' | 'MORPH_RELATION' | 'MULTI_SELECT' | 'NUMBER' | 'NUMERIC' | 'PHONES' | 'POSITION' | 'RATING' | 'RAW_JSON' | 'RELATION' | 'RICH_TEXT_V2' | 'SELECT' | 'TEXT' | 'TS_VECTOR' | 'UUID'
export interface IndexField {
id: Scalars['UUID']
@ -8290,7 +8290,6 @@ export const enumFieldMetadataType = {
RATING: 'RATING' as const,
RAW_JSON: 'RAW_JSON' as const,
RELATION: 'RELATION' as const,
RICH_TEXT: 'RICH_TEXT' as const,
RICH_TEXT_V2: 'RICH_TEXT_V2' as const,
SELECT: 'SELECT' as const,
TEXT: 'TEXT' as const,

View file

@ -0,0 +1,114 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { type WorkspaceCacheKeyName } from 'src/engine/workspace-cache/types/workspace-cache-key.type';
@Command({
name: 'upgrade:1-20:migrate-rich-text-to-text',
description:
'Migrate deprecated RICH_TEXT field metadata type to TEXT. The underlying column type is already text, so only the metadata needs updating.',
})
export class MigrateRichTextToTextCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
@InjectDataSource()
private readonly coreDataSource: DataSource,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const dryRun = options?.dryRun ?? false;
this.logger.log(
`${dryRun ? '[DRY RUN] ' : ''}Migrating RICH_TEXT fields to TEXT in workspace ${workspaceId}`,
);
if (dryRun) {
this.logger.log(
`[DRY RUN] Would update RICH_TEXT -> TEXT in core.fieldMetadata for workspace ${workspaceId}. Skipping.`,
);
return;
}
const queryRunner = this.coreDataSource.createQueryRunner();
await queryRunner.connect();
try {
const result = await queryRunner.query(
`UPDATE core."fieldMetadata"
SET "type" = 'TEXT'
WHERE "workspaceId" = $1
AND "type" = 'RICH_TEXT'
RETURNING "id"`,
[workspaceId],
);
const updatedCount = result.length;
if (updatedCount > 0) {
this.logger.log(
`Migrated ${updatedCount} RICH_TEXT field(s) to TEXT in workspace ${workspaceId}`,
);
await this.invalidateCaches(workspaceId);
} else {
this.logger.log(
`No RICH_TEXT fields found in workspace ${workspaceId}`,
);
}
} finally {
await queryRunner.release();
}
}
private async invalidateCaches(workspaceId: string): Promise<void> {
const modifiedMetadataNames = ['fieldMetadata'] as const;
const cacheKeysToInvalidate: WorkspaceCacheKeyName[] = [
...new Set(
modifiedMetadataNames
.flatMap((name) => [name, ...getMetadataRelatedMetadataNames(name)])
.map(getMetadataFlatEntityMapsKey),
),
'ORMEntityMetadatas',
];
await this.workspaceCacheService.invalidateAndRecompute(
workspaceId,
cacheKeysToInvalidate,
);
await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);
await this.workspaceCacheStorageService.flush(workspaceId);
this.logger.log(
`Cache invalidated and metadata version incremented for workspace ${workspaceId}`,
);
}
}

View file

@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
@ -25,7 +26,15 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
WorkspaceMigrationModule,
FeatureFlagModule,
],
providers: [BackfillCommandMenuItemsCommand, BackfillPageLayoutsCommand],
exports: [BackfillCommandMenuItemsCommand, BackfillPageLayoutsCommand],
providers: [
BackfillCommandMenuItemsCommand,
BackfillPageLayoutsCommand,
MigrateRichTextToTextCommand,
],
exports: [
BackfillCommandMenuItemsCommand,
BackfillPageLayoutsCommand,
MigrateRichTextToTextCommand,
],
})
export class V1_20_UpgradeVersionCommandModule {}

View file

@ -35,6 +35,7 @@ import { BackfillMissingStandardViewsCommand } from 'src/database/commands/upgra
import { SeedServerIdCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-seed-server-id.command';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
@ -87,6 +88,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
// 1.20 Commands
protected readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand,
protected readonly backfillPageLayoutsCommand: BackfillPageLayoutsCommand,
protected readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
) {
super(
workspaceRepository,
@ -133,6 +135,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
];
const commands_1200: VersionCommands = [
this.migrateRichTextToTextCommand,
this.backfillCommandMenuItemsCommand,
this.backfillPageLayoutsCommand,
];

View file

@ -335,20 +335,6 @@ export const fieldRawJsonMock = getMockFieldMetadataEntity({
updatedAt: new Date(),
});
export const fieldRichTextMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
id: 'fieldRichTextId',
name: 'fieldRichText',
type: FieldMetadataType.RICH_TEXT,
label: 'Field Rich Text',
isNullable: true,
defaultValue: null,
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
});
export const fieldActorMock = getMockFieldMetadataEntity({
workspaceId,
objectMetadataId,
@ -436,7 +422,6 @@ const FIELDS_MOCK = [
fieldPositionMock,
fieldAddressMock,
fieldRawJsonMock,
fieldRichTextMock,
fieldActorMock,
fieldArrayMock,
];

View file

@ -172,8 +172,6 @@ exports[`DataArgProcessorService failing inputs validation RELATION should throw
exports[`DataArgProcessorService failing inputs validation RELATION should throw for invalid input #5: "non-uuid" 1`] = `"Invalid UUID value 'non-uuid' for field "manyToOneRelationFieldId""`;
exports[`DataArgProcessorService failing inputs validation RICH_TEXT should throw for invalid input #1: "test" 1`] = `"richTextField RICH_TEXT-typed field does not support write operations"`;
exports[`DataArgProcessorService failing inputs validation RICH_TEXT_V2 should throw for invalid input #1: "not-a-rich-text" 1`] = `"Invalid rich text v2 value 'not-a-rich-text' for field "richTextV2Field" - Should be an object"`;
exports[`DataArgProcessorService failing inputs validation RICH_TEXT_V2 should throw for invalid input #2: 1 1`] = `"Invalid rich text v2 value 1 for field "richTextV2Field" - Should be an object"`;

View file

@ -96,7 +96,6 @@ export const failingInputsByFieldMetadataType: {
{ input: { booleanField: 'string' } },
{ input: { booleanField: 1 } },
],
[FieldMetadataType.RICH_TEXT]: [{ input: { richTextField: 'test' } }],
[FieldMetadataType.ADDRESS]: [
{ input: { addressField: 'not-an-address' } },
{ input: { addressField: 1 } },

View file

@ -139,11 +139,6 @@ export const fieldMetadataConfigByFieldName: Record<
type: FieldMetadataType.RICH_TEXT_V2,
isNullable: true,
},
richTextField: {
name: 'richTextField',
type: FieldMetadataType.RICH_TEXT,
isNullable: true,
},
position: {
name: 'position',
type: FieldMetadataType.POSITION,

View file

@ -312,7 +312,6 @@ export class DataArgProcessorService {
return transformLinksValue(validatedValue);
}
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.TS_VECTOR:
throw new CommonQueryRunnerException(
`${key} ${fieldMetadata.type}-typed field does not support write operations`,

View file

@ -140,10 +140,6 @@ exports[`FilterArgProcessorService failing filter inputs validation RELATION sho
exports[`FilterArgProcessorService failing filter inputs validation RELATION should throw for invalid filter #3: {"manyToOneRelationFieldId":{}} 1`] = `"Filter for field "manyToOneRelationFieldId" must have exactly one operator"`;
exports[`FilterArgProcessorService failing filter inputs validation RICH_TEXT should throw for invalid filter #1: {"richTextField":{"invalidOperator":"test"}} 1`] = `"Operator "invalidOperator" is not valid for field "richTextField" of type RICH_TEXT - Allowed operators: eq, neq, gt, gte, lt, lte, in, is, like, ilike, startsWith, endsWith"`;
exports[`FilterArgProcessorService failing filter inputs validation RICH_TEXT should throw for invalid filter #2: {"richTextField":{}} 1`] = `"Filter for field "richTextField" must have exactly one operator"`;
exports[`FilterArgProcessorService failing filter inputs validation RICH_TEXT_V2 should throw for invalid filter #1: {"richTextV2Field":{"invalidOperator":"test"}} 1`] = `"Sub field "invalidOperator" not found for composite type: RICH_TEXT_V2"`;
exports[`FilterArgProcessorService failing filter inputs validation RICH_TEXT_V2 should throw for invalid filter #2: {"richTextV2Field":{"markdown":{"invalidOperator":"test"}}} 1`] = `"Operator "invalidOperator" is not valid for field "richTextV2Field.markdown" of type TEXT - Allowed operators: eq, neq, gt, gte, lt, lte, in, is, like, ilike, startsWith, endsWith"`;

View file

@ -11,10 +11,6 @@ export const failingFilterInputsByFieldMetadataType: {
{ filter: { textField: { eq: 'test', neq: 'test' } } },
{ filter: { textField: {} } },
],
[FieldMetadataType.RICH_TEXT]: [
{ filter: { richTextField: { invalidOperator: 'test' } } },
{ filter: { richTextField: {} } },
],
[FieldMetadataType.NUMBER]: [
{ filter: { numberField: { eq: 'not-a-number' } } },
{ filter: { numberField: { eq: {} } } },

View file

@ -21,13 +21,6 @@ export const successfulFilterInputsByFieldMetadataType: {
{ filter: { textField: { is: 'NOT_NULL' } } },
{ filter: { textField: { eq: null } } },
],
[FieldMetadataType.RICH_TEXT]: [
{ filter: { richTextField: { eq: 'test' } } },
{ filter: { richTextField: { like: '%test%' } } },
{ filter: { richTextField: { ilike: '%test%' } } },
{ filter: { richTextField: { is: 'NULL' } } },
{ filter: { richTextField: { is: 'NOT_NULL' } } },
],
[FieldMetadataType.NUMBER]: [
{ filter: { numberField: { eq: 1 } } },
{ filter: { numberField: { neq: 1 } } },

View file

@ -20,7 +20,6 @@ export const getOperatorsForFieldType = (
): FilterOperator[] => {
switch (fieldType) {
case FieldMetadataType.TEXT:
case FieldMetadataType.RICH_TEXT:
return STRING_FILTER_OPERATORS;
case FieldMetadataType.NUMBER:

View file

@ -74,7 +74,6 @@ export class TypeMapperService {
[FieldMetadataType.POSITION, PositionScalarType],
[FieldMetadataType.RAW_JSON, GraphQLJSON],
[FieldMetadataType.ARRAY, StringArrayScalarType],
[FieldMetadataType.RICH_TEXT, GraphQLString],
[FieldMetadataType.TS_VECTOR, TSVectorScalarType],
]);
@ -179,7 +178,6 @@ export class TypeMapperService {
[FieldMetadataType.POSITION, FloatFilterType],
[FieldMetadataType.FILES, RawJsonFilterType],
[FieldMetadataType.RAW_JSON, RawJsonFilterType],
[FieldMetadataType.RICH_TEXT, StringFilterType],
[FieldMetadataType.RICH_TEXT_V2, RichTextV2FilterType],
[FieldMetadataType.ARRAY, ArrayFilterType],
[FieldMetadataType.MULTI_SELECT, MultiSelectFilterType],
@ -209,7 +207,6 @@ export class TypeMapperService {
[FieldMetadataType.POSITION, OrderByDirectionType],
[FieldMetadataType.FILES, OrderByDirectionType],
[FieldMetadataType.RAW_JSON, OrderByDirectionType],
[FieldMetadataType.RICH_TEXT, OrderByDirectionType],
[FieldMetadataType.ARRAY, OrderByDirectionType],
[FieldMetadataType.TS_VECTOR, OrderByDirectionType], // TODO: Add TSVectorOrderByType
]);

View file

@ -295,10 +295,6 @@ describe('computeSchemaComponents', () => {
"format": "uuid",
"type": "string",
},
"fieldRichText": {
"description": "Default field metadata entity description",
"type": "string",
},
"fieldSelect": {
"description": "Default field metadata entity description",
"enum": [
@ -540,10 +536,6 @@ describe('computeSchemaComponents', () => {
"format": "uuid",
"type": "string",
},
"fieldRichText": {
"description": "Default field metadata entity description",
"type": "string",
},
"fieldSelect": {
"description": "Default field metadata entity description",
"enum": [
@ -796,10 +788,6 @@ describe('computeSchemaComponents', () => {
"format": "uuid",
"type": "string",
},
"fieldRichText": {
"description": "Default field metadata entity description",
"type": "string",
},
"fieldSelect": {
"description": "Default field metadata entity description",
"enum": [

View file

@ -124,7 +124,6 @@ export const generateRandomFieldValue = ({
return {};
}
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.RICH_TEXT_V2: {
return '';
}

View file

@ -28,7 +28,6 @@ export const generateFieldFilterZodSchema = (
.describe(`Filter by ${field.name} (UUID field)`);
case FieldMetadataType.TEXT:
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.RICH_TEXT_V2:
return z
.object({

View file

@ -37,7 +37,6 @@ const getFieldZodType = (field: FlatFieldMetadata): z.ZodTypeAny => {
return z.string().uuidv4();
case FieldMetadataType.TEXT:
case FieldMetadataType.RICH_TEXT:
return z.string();
case FieldMetadataType.DATE_TIME:

View file

@ -78,10 +78,6 @@ export class RecordInputTransformerService {
return value || null;
case FieldMetadataType.NUMBER:
return value === null ? null : Number(value);
case FieldMetadataType.RICH_TEXT:
throw new Error(
'Rich text is not supported, please use RICH_TEXT_V2 instead',
);
case FieldMetadataType.RICH_TEXT_V2:
return await transformRichTextV2Value(value);
case FieldMetadataType.LINKS:

View file

@ -61,8 +61,6 @@ type PositionFieldMetadata = FieldMetadataEntity<FieldMetadataType.POSITION>;
type RawJsonFieldMetadata = FieldMetadataEntity<FieldMetadataType.RAW_JSON>;
type RichTextFieldMetadata = FieldMetadataEntity<FieldMetadataType.RICH_TEXT>;
type ActorFieldMetadata = FieldMetadataEntity<FieldMetadataType.ACTOR>;
type ArrayFieldMetadata = FieldMetadataEntity<FieldMetadataType.ARRAY>;
@ -93,7 +91,6 @@ type RelationAssertions = [
Expect<HasAllProperties<MultiSelectFieldMetadata, NotDefinedRelationRecord>>,
Expect<HasAllProperties<PositionFieldMetadata, NotDefinedRelationRecord>>,
Expect<HasAllProperties<RawJsonFieldMetadata, NotDefinedRelationRecord>>,
Expect<HasAllProperties<RichTextFieldMetadata, NotDefinedRelationRecord>>,
Expect<HasAllProperties<ActorFieldMetadata, NotDefinedRelationRecord>>,
Expect<HasAllProperties<ArrayFieldMetadata, NotDefinedRelationRecord>>,
Expect<HasAllProperties<PhonesFieldMetadata, NotDefinedRelationRecord>>,
@ -124,7 +121,6 @@ type SettingsAssertions = [
Expect<HasAllProperties<MultiSelectFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<PositionFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<RawJsonFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<RichTextFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<ActorFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<UUIDFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<BooleanFieldMetadata, NotDefinedSettings>>,
@ -371,16 +367,6 @@ type DefaultValueAssertions = [
}
>
>,
Expect<
HasAllProperties<
RichTextFieldMetadata,
{
defaultValue: JsonbProperty<
FieldMetadataDefaultValueMapping[FieldMetadataType.RICH_TEXT]
>;
}
>
>,
Expect<
HasAllProperties<
ActorFieldMetadata,
@ -484,7 +470,6 @@ type OptionsAssertions = [
Expect<HasAllProperties<FullNameFieldMetadata, NotDefinedOptions>>,
Expect<HasAllProperties<PositionFieldMetadata, NotDefinedOptions>>,
Expect<HasAllProperties<RawJsonFieldMetadata, NotDefinedOptions>>,
Expect<HasAllProperties<RichTextFieldMetadata, NotDefinedOptions>>,
Expect<HasAllProperties<ActorFieldMetadata, NotDefinedOptions>>,
Expect<HasAllProperties<ArrayFieldMetadata, NotDefinedOptions>>,
Expect<HasAllProperties<PhonesFieldMetadata, NotDefinedOptions>>,

View file

@ -742,7 +742,7 @@ export const PET_FLAT_FIELDS_MOCK = {
bio: getFlatFieldMetadataMock({
id: 'fa0ec409-0f29-4b19-b304-31d1018a2344',
objectMetadataId: 'd34e0f07-1b8c-4de0-938e-599cf05e1f7f',
type: FieldMetadataType.RICH_TEXT,
type: FieldMetadataType.TEXT,
name: 'bio',
label: 'Bio',
defaultValue: null,

View file

@ -71,7 +71,6 @@ export class FlatFieldMetadataTypeValidatorService {
PHONES: DEFAULT_NO_VALIDATION,
POSITION: validatePositionFlatFieldMetadata,
RAW_JSON: DEFAULT_NO_VALIDATION,
RICH_TEXT: DEFAULT_NO_VALIDATION,
RICH_TEXT_V2: DEFAULT_NO_VALIDATION,
TEXT: DEFAULT_NO_VALIDATION,
TS_VECTOR: validateTsVectorFlatFieldMetadata,

View file

@ -105,7 +105,6 @@ const _assertion: Record<string, AbstractFlatFieldMetadata> = {
// JSON/Array types
rawJson: {} as FlatFieldMetadata<FieldMetadataType.RAW_JSON>,
array: {} as FlatFieldMetadata<FieldMetadataType.ARRAY>,
richText: {} as FlatFieldMetadata<FieldMetadataType.RICH_TEXT>,
richTextV2: {} as FlatFieldMetadata<FieldMetadataType.RICH_TEXT_V2>,
// Relation types

View file

@ -183,7 +183,6 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
case FieldMetadataType.POSITION:
case FieldMetadataType.ADDRESS:
case FieldMetadataType.RAW_JSON:
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.RICH_TEXT_V2:
case FieldMetadataType.ACTOR:
case FieldMetadataType.ARRAY: {

View file

@ -243,15 +243,6 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({
value: recordFieldValue,
});
}
case FieldMetadataType.RICH_TEXT: {
// TODO: Implement a better rich text filter once it becomes a composite field
// See this issue for more context: https://github.com/twentyhq/twenty/issues/7613#issuecomment-2408944585
// This should be tackled in Q4'24
return isMatchingStringFilter({
stringFilter: filterValue as StringFilter,
value: recordFieldValue,
});
}
case FieldMetadataType.RICH_TEXT_V2: {
return isMatchingRichTextV2Filter({
richTextV2Filter: filterValue as RichTextV2Filter,

View file

@ -49,8 +49,7 @@ const getFieldProperties = (field: FieldMetadataEntity): SchemaObject => {
case FieldMetadataType.UUID: {
return { type: 'string', format: 'uuid' };
}
case FieldMetadataType.TEXT:
case FieldMetadataType.RICH_TEXT: {
case FieldMetadataType.TEXT: {
return { type: 'string' };
}
case FieldMetadataType.DATE_TIME: {

View file

@ -68,8 +68,6 @@ const generateFieldMetadataTypeValue = (
return 'Tim Cook';
case FieldMetadataType.RAW_JSON:
return null;
case FieldMetadataType.RICH_TEXT:
return 'My rich text';
case FieldMetadataType.UUID:
return '123e4567-e89b-12d3-a456-426614174000';
default:

View file

@ -90,7 +90,7 @@ export const PET_CUSTOM_FIELD_SEEDS: FieldMetadataSeed[] = [
name: 'soundSwag',
},
{
type: FieldMetadataType.RICH_TEXT,
type: FieldMetadataType.TEXT,
label: 'Bio',
name: 'bio',
},

View file

@ -367,7 +367,7 @@ You help users manage their workspace data model by creating, updating, and orga
- **CURRENCY**: Monetary values
- **RATING**: Star ratings
- **RELATION**: Links to other objects
- **RICH_TEXT**: Formatted text content
- **RICH_TEXT_V2**: Formatted rich text content
## Best Practices

View file

@ -50,9 +50,9 @@ describe('getTsVectorColumnExpressionFromFields', () => {
);
});
it('should handle rich text fields', () => {
it('should handle text fields', () => {
const fields = [
{ name: 'body', type: FieldMetadataType.RICH_TEXT },
{ name: 'body', type: FieldMetadataType.TEXT },
] as FieldTypeAndNameMetadata[];
const result = getTsVectorColumnExpressionFromFields(fields);

View file

@ -6,7 +6,6 @@ const SEARCHABLE_FIELD_TYPES = [
FieldMetadataType.ADDRESS,
FieldMetadataType.LINKS,
FieldMetadataType.PHONES,
FieldMetadataType.RICH_TEXT,
FieldMetadataType.RICH_TEXT_V2,
FieldMetadataType.UUID,
] as const;

View file

@ -1,8 +1,11 @@
import { FieldMetadataType } from 'twenty-shared/types';
// 'RICH_TEXT' kept as a raw string for pre-upgrade workspaces that haven't
// yet run the 1.20 migrate-rich-text-to-text command.
export const isTextColumnType = (type: FieldMetadataType) => {
return (
type === FieldMetadataType.TEXT ||
type === FieldMetadataType.RICH_TEXT ||
type === FieldMetadataType.ARRAY
type === FieldMetadataType.ARRAY ||
(type as string) === 'RICH_TEXT'
);
};

View file

@ -53,7 +53,6 @@ export const TEST_OBJECT_GQL_FIELDS = `
}
rawJsonField
arrayField
richTextField
richTextV2Field {
blocknote
markdown

View file

@ -1,5 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Create input validation - RICH_TEXT Gql create input - failure RICH_TEXT - should fail with : {"richTextField":"test"} 1`] = `"richTextField RICH_TEXT-typed field does not support write operations"`;
exports[`Create input validation - RICH_TEXT Rest create input - failure RICH_TEXT - should fail with : {"richTextField":"test"} 1`] = `"["richTextField RICH_TEXT-typed field does not support write operations"]"`;

View file

@ -138,13 +138,6 @@ export const failingCreateInputByFieldMetadataType: {
},
},
],
[FieldMetadataType.RICH_TEXT]: [
{
input: {
richTextField: 'test',
},
},
],
[FieldMetadataType.ADDRESS]: [
{
input: {

View file

@ -8,7 +8,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
export const successfulCreateInputByFieldMetadataType: {
[K in Exclude<
FieldMetadataTypesToTestForCreateInputValidation,
FieldMetadataType.RICH_TEXT | FieldMetadataType.FILES // Done in files-field-sync.integration-spec.ts
FieldMetadataType.FILES // Done in files-field-sync.integration-spec.ts
>]: {
input: any;
validateInput: (record: Record<string, any>) => boolean;

View file

@ -1,71 +0,0 @@
import { failingCreateInputByFieldMetadataType } from 'test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant';
import { expectGqlCreateInputValidationError } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-gql-create-input-validation-error.util';
import { expectRestCreateInputValidationError } from 'test/integration/graphql/suites/inputs-validation/create-validation/utils/expect-rest-create-input-validation-error.util';
import { destroyManyObjectsMetadata } from 'test/integration/graphql/suites/inputs-validation/utils/destroy-many-objects-metadata';
import { setupTestObjectsWithAllFieldTypes } from 'test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util';
import { FieldMetadataType } from 'twenty-shared/types';
const FIELD_METADATA_TYPE = FieldMetadataType.RICH_TEXT;
const failingTestCases =
failingCreateInputByFieldMetadataType[FIELD_METADATA_TYPE];
describe(`Create input validation - ${FIELD_METADATA_TYPE}`, () => {
let objectMetadataId: string;
let objectMetadataSingularName: string;
let objectMetadataPluralName: string;
let targetObjectMetadata1Id: string;
let targetObjectMetadata2Id: string;
beforeAll(async () => {
const setupTest = await setupTestObjectsWithAllFieldTypes();
objectMetadataId = setupTest.objectMetadataId;
objectMetadataSingularName = setupTest.objectMetadataSingularName;
objectMetadataPluralName = setupTest.objectMetadataPluralName;
targetObjectMetadata1Id = setupTest.targetObjectMetadata1Id;
targetObjectMetadata2Id = setupTest.targetObjectMetadata2Id;
});
afterAll(async () => {
await destroyManyObjectsMetadata([
objectMetadataId,
targetObjectMetadata1Id,
targetObjectMetadata2Id,
]);
});
describe('Gql create input - failure', () => {
it.each(
failingTestCases.map((testCase) => ({
...testCase,
stringifiedInput: JSON.stringify(testCase.input),
})),
)(
`${FIELD_METADATA_TYPE} - should fail with : $stringifiedInput`,
async ({ input }) => {
await expectGqlCreateInputValidationError(
objectMetadataSingularName,
input,
);
},
);
});
describe('Rest create input - failure', () => {
it.each(
failingTestCases.map((testCase) => ({
...testCase,
stringifiedInput: JSON.stringify(testCase.input),
})),
)(
`${FIELD_METADATA_TYPE} - should fail with : $stringifiedInput`,
async ({ input }) => {
await expectRestCreateInputValidationError(
objectMetadataPluralName,
input,
);
},
);
});
});

View file

@ -2,7 +2,6 @@ import { type FieldMetadataType } from 'twenty-shared/types';
type FieldMetadataTypesNotTestedForFilterInputValidation =
| 'TS_VECTOR'
| 'RICH_TEXT'
| 'POSITION'
| 'ACTOR'
| 'NUMERIC'

View file

@ -37,12 +37,6 @@ export const getFieldMetadataCreationInputs = (
'ACTOR' | 'POSITION'
>]: FieldMetadataCreationInput | FieldMetadataCreationInput[];
} = {
[FieldMetadataType.RICH_TEXT]: {
name: 'richTextField',
label: 'richTextField',
type: FieldMetadataType.RICH_TEXT,
objectMetadataId,
},
[FieldMetadataType.RICH_TEXT_V2]: {
name: 'richTextV2Field',
label: 'richTextV2Field',

View file

@ -90,7 +90,6 @@ export type FieldMetadataDefaultValueMapping = {
[FieldMetadataType.SELECT]: string | null;
[FieldMetadataType.MULTI_SELECT]: string[] | null;
[FieldMetadataType.RAW_JSON]: object | null;
[FieldMetadataType.RICH_TEXT]: string | null;
[FieldMetadataType.RICH_TEXT_V2]: FieldMetadataDefaultValueRichTextV2 | null;
[FieldMetadataType.ACTOR]: FieldMetadataDefaultActor | null;
[FieldMetadataType.ARRAY]: string[] | null;

View file

@ -19,7 +19,6 @@ export enum FieldMetadataType {
RATING = 'RATING',
RAW_JSON = 'RAW_JSON',
RELATION = 'RELATION',
RICH_TEXT = 'RICH_TEXT',
RICH_TEXT_V2 = 'RICH_TEXT_V2',
SELECT = 'SELECT',
TEXT = 'TEXT',

View file

@ -2,13 +2,12 @@ import { FieldMetadataType } from '@/types';
import { isFieldMetadataTextKind } from '../isFieldMetadataTextKind';
describe('isFieldMetadataTextKind', () => {
it.each([
FieldMetadataType.TEXT,
FieldMetadataType.RICH_TEXT,
FieldMetadataType.RICH_TEXT_V2,
])('should return true for %s', (type) => {
expect(isFieldMetadataTextKind(type)).toBe(true);
});
it.each([FieldMetadataType.TEXT, FieldMetadataType.RICH_TEXT_V2])(
'should return true for %s',
(type) => {
expect(isFieldMetadataTextKind(type)).toBe(true);
},
);
it.each([FieldMetadataType.NUMBER, FieldMetadataType.SELECT])(
'should return false for %s',

View file

@ -2,7 +2,6 @@ import { FieldMetadataType } from '@/types';
const TEXT_FIELD_TYPES: FieldMetadataType[] = [
FieldMetadataType.TEXT,
FieldMetadataType.RICH_TEXT,
FieldMetadataType.RICH_TEXT_V2,
];

View file

@ -15,7 +15,6 @@ const getTypeFromFieldMetadataType = (
switch (fieldMetadataType) {
case FieldMetadataType.UUID:
case FieldMetadataType.TEXT:
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.ARRAY:
case FieldMetadataType.RATING:
return 'string';
@ -242,7 +241,6 @@ export const computeInputFields = (
break;
case FieldMetadataType.UUID:
case FieldMetadataType.TEXT:
case FieldMetadataType.RICH_TEXT:
case FieldMetadataType.DATE_TIME:
case FieldMetadataType.DATE:
case FieldMetadataType.BOOLEAN: