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
71 changed files with 165 additions and 368 deletions
@@ -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}`,
);
}
}
@@ -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 {}
@@ -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,
];