-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RICH_TEXT_V2 upgrade command #9704
Closed
eliasylonen
wants to merge
12
commits into
twentyhq:main
from
eliasylonen:feat/7613-rich-text-upgrade-command
+607
−4
Closed
Changes from 8 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
88506dc
Add RICH_TEXT upgrade command
ad-elias bb4d570
Update field metadata type
ad-elias 075d796
Fix upgrade command
ad-elias f305878
Add RICH_TEXT_V2 field metadata type
ad-elias b66a415
Merge branch 'main' into feat/7613-rich-text-upgrade-command
ad-elias d6c70b9
Copy data to v2 columns
ad-elias 6e9226d
Remove try catch
ad-elias d1174e5
Merge composite field definition from feat/7613-composite-rich-text
ad-elias ba2ec76
Add FixBodyV2ViewFieldPositionCommand
ad-elias d7480d1
Remove try catch, error message is not specific enough without a stac…
ad-elias 3ed97d4
Fix typo
ad-elias cdb6afd
Merge branch 'main' into feat/7613-rich-text-upgrade-command
ad-elias File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
200 changes: 200 additions & 0 deletions
200
...server/src/database/commands/upgrade-version/0-41/0-41-migrate-rich-text-field.command.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
|
||
import { ServerBlockNoteEditor } from '@blocknote/server-util'; | ||
import chalk from 'chalk'; | ||
import { Command } from 'nest-commander'; | ||
import { FieldMetadataType } from 'twenty-shared'; | ||
import { Repository } from 'typeorm'; | ||
|
||
import { | ||
ActiveWorkspacesCommandOptions, | ||
ActiveWorkspacesCommandRunner, | ||
} from 'src/database/commands/active-workspaces.command'; | ||
import { isCommandLogger } from 'src/database/commands/logger'; | ||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; | ||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; | ||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; | ||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; | ||
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; | ||
import { | ||
WorkspaceMigrationColumnActionType, | ||
WorkspaceMigrationColumnCreate, | ||
WorkspaceMigrationTableAction, | ||
WorkspaceMigrationTableActionType, | ||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; | ||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; | ||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; | ||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; | ||
import { computeTableName } from 'src/engine/utils/compute-table-name.util'; | ||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; | ||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; | ||
import { isDefined } from 'src/utils/is-defined'; | ||
|
||
@Command({ | ||
name: 'upgrade-0.41:migrate-rich-text-field', | ||
description: 'Migrate RICH_TEXT fields to new composite structure', | ||
}) | ||
export class MigrateRichTextFieldCommand extends ActiveWorkspacesCommandRunner { | ||
constructor( | ||
@InjectRepository(Workspace, 'core') | ||
protected readonly workspaceRepository: Repository<Workspace>, | ||
@InjectRepository(FieldMetadataEntity, 'metadata') | ||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>, | ||
@InjectRepository(ObjectMetadataEntity, 'metadata') | ||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, | ||
private readonly workspaceDataSourceService: WorkspaceDataSourceService, | ||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, | ||
private readonly workspaceMigrationService: WorkspaceMigrationService, | ||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, | ||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, | ||
) { | ||
super(workspaceRepository); | ||
} | ||
|
||
async executeActiveWorkspacesCommand( | ||
_passedParam: string[], | ||
options: ActiveWorkspacesCommandOptions, | ||
workspaceIds: string[], | ||
): Promise<void> { | ||
this.logger.log( | ||
'Running command to migrate RICH_TEXT fields to new composite structure', | ||
); | ||
|
||
if (isCommandLogger(this.logger)) { | ||
this.logger.setVerbose(options.verbose ?? false); | ||
} | ||
|
||
let workspaceIterator = 1; | ||
|
||
for (const workspaceId of workspaceIds) { | ||
this.logger.log( | ||
`Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`, | ||
); | ||
|
||
const richTextFields = await this.fieldMetadataRepository.find({ | ||
where: { | ||
workspaceId, | ||
type: FieldMetadataType.RICH_TEXT, | ||
}, | ||
}); | ||
|
||
if (!richTextFields.length) { | ||
this.logger.log('No RICH_TEXT fields found in this workspace'); | ||
workspaceIterator++; | ||
continue; | ||
} | ||
|
||
this.logger.log(`Found ${richTextFields.length} RICH_TEXT fields`); | ||
|
||
for (const richTextField of richTextFields) { | ||
const newRichTextField: Partial<FieldMetadataEntity> = { | ||
...richTextField, | ||
name: `${richTextField.name}V2`, | ||
id: undefined, | ||
type: FieldMetadataType.RICH_TEXT_V2, | ||
defaultValue: null, | ||
}; | ||
|
||
await this.fieldMetadataRepository.insert(newRichTextField); | ||
|
||
const objectMetadata = await this.objectMetadataRepository.findOne({ | ||
where: { id: richTextField.objectMetadataId }, | ||
}); | ||
|
||
if (!isDefined(objectMetadata)) { | ||
this.logger.log( | ||
`Object metadata not found for rich text field ${richTextField.name} in workspace ${workspaceId}`, | ||
); | ||
continue; | ||
} | ||
|
||
await this.workspaceMigrationService.createCustomMigration( | ||
generateMigrationName( | ||
`migrate-rich-text-field-${objectMetadata.nameSingular}-${richTextField.name}`, | ||
), | ||
workspaceId, | ||
[ | ||
{ | ||
name: computeObjectTargetTable(objectMetadata), | ||
action: WorkspaceMigrationTableActionType.ALTER, | ||
columns: [ | ||
{ | ||
action: WorkspaceMigrationColumnActionType.CREATE, | ||
columnName: `${richTextField.name}V2Blocknote`, | ||
columnType: 'text', | ||
isNullable: true, | ||
defaultValue: null, | ||
} satisfies WorkspaceMigrationColumnCreate, | ||
{ | ||
action: WorkspaceMigrationColumnActionType.CREATE, | ||
columnName: `${richTextField.name}V2Markdown`, | ||
columnType: 'text', | ||
isNullable: true, | ||
defaultValue: null, | ||
} satisfies WorkspaceMigrationColumnCreate, | ||
], | ||
} satisfies WorkspaceMigrationTableAction, | ||
], | ||
); | ||
} | ||
|
||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( | ||
workspaceId, | ||
); | ||
|
||
await this.workspaceMetadataVersionService.incrementMetadataVersion( | ||
workspaceId, | ||
); | ||
|
||
const serverBlockNoteEditor = ServerBlockNoteEditor.create(); | ||
|
||
for (const richTextField of richTextFields) { | ||
const objectMetadata = await this.objectMetadataRepository.findOne({ | ||
where: { id: richTextField.objectMetadataId }, | ||
}); | ||
|
||
if (!isDefined(objectMetadata)) { | ||
this.logger.log( | ||
`Object metadata not found for rich text field ${richTextField.name} in workspace ${workspaceId}`, | ||
); | ||
continue; | ||
} | ||
|
||
const schemaName = | ||
this.workspaceDataSourceService.getSchemaName(workspaceId); | ||
|
||
const workspaceDataSource = | ||
await this.twentyORMGlobalManager.getDataSourceForWorkspace( | ||
workspaceId, | ||
); | ||
|
||
const rows = await workspaceDataSource.query( | ||
`SELECT id, "${richTextField.name}" FROM "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}"`, | ||
); | ||
|
||
this.logger.log(`Generating markdown for ${rows.length} records`); | ||
|
||
for (const row of rows) { | ||
const blocknoteFieldValue = row[richTextField.name]; | ||
const markdownFieldValue = blocknoteFieldValue | ||
? await serverBlockNoteEditor.blocksToMarkdownLossy( | ||
JSON.parse(blocknoteFieldValue), | ||
) | ||
: null; | ||
|
||
await workspaceDataSource.query( | ||
`UPDATE "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" SET "${richTextField.name}V2Blocknote" = $1, "${richTextField.name}V2Markdown" = $2 WHERE id = $3`, | ||
[blocknoteFieldValue, markdownFieldValue, row.id], | ||
); | ||
} | ||
} | ||
|
||
workspaceIterator++; | ||
this.logger.log( | ||
chalk.green(`Command completed for workspace ${workspaceId}`), | ||
); | ||
} | ||
|
||
this.logger.log(chalk.green('Command completed!')); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
.../twenty-server/src/database/commands/upgrade-version/0-41/0-41-upgrade-version.command.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
|
||
import { Command } from 'nest-commander'; | ||
import { Repository } from 'typeorm'; | ||
|
||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; | ||
import { BaseCommandOptions } from 'src/database/commands/base.command'; | ||
import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-41/0-41-migrate-rich-text-field.command'; | ||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; | ||
|
||
@Command({ | ||
name: 'upgrade-0.41', | ||
description: 'Upgrade to 0.41', | ||
}) | ||
export class UpgradeTo0_41Command extends ActiveWorkspacesCommandRunner { | ||
constructor( | ||
@InjectRepository(Workspace, 'core') | ||
protected readonly workspaceRepository: Repository<Workspace>, | ||
private readonly migrateRichTextFieldCommand: MigrateRichTextFieldCommand, | ||
) { | ||
super(workspaceRepository); | ||
} | ||
|
||
async executeActiveWorkspacesCommand( | ||
passedParam: string[], | ||
options: BaseCommandOptions, | ||
workspaceIds: string[], | ||
): Promise<void> { | ||
Comment on lines
+26
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: passedParam is unused in this implementation but passed through to migrateRichTextFieldCommand. Consider documenting the expected parameters or removing if not needed. |
||
this.logger.log('Running command to upgrade to 0.41'); | ||
|
||
await this.migrateRichTextFieldCommand.executeActiveWorkspacesCommand( | ||
passedParam, | ||
options, | ||
workspaceIds, | ||
); | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
...s/twenty-server/src/database/commands/upgrade-version/0-41/0-41-upgrade-version.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { TypeOrmModule } from '@nestjs/typeorm'; | ||
|
||
import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-41/0-41-migrate-rich-text-field.command'; | ||
import { UpgradeTo0_41Command } from 'src/database/commands/upgrade-version/0-41/0-41-upgrade-version.command'; | ||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; | ||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; | ||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; | ||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; | ||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; | ||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; | ||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; | ||
|
||
@Module({ | ||
imports: [ | ||
TypeOrmModule.forFeature([Workspace], 'core'), | ||
TypeOrmModule.forFeature( | ||
[ObjectMetadataEntity, FieldMetadataEntity], | ||
'metadata', | ||
), | ||
WorkspaceMigrationRunnerModule, | ||
WorkspaceMigrationModule, | ||
WorkspaceMetadataVersionModule, | ||
WorkspaceDataSourceModule, | ||
], | ||
providers: [UpgradeTo0_41Command, MigrateRichTextFieldCommand], | ||
}) | ||
export class UpgradeTo0_41CommandModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
...src/engine/metadata-modules/field-metadata/composite-types/rich-text-v2.composite-type.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: No error handling for JSON.parse() - could throw exception and halt migration if blocknoteFieldValue contains invalid JSON