diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts new file mode 100644 index 000000000..db115bcd4 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts @@ -0,0 +1,117 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { + ActiveWorkspacesCommandOptions, + ActiveWorkspacesCommandRunner, +} from 'src/database/commands/active-workspaces.command'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { SearchService } from 'src/engine/metadata-modules/search/search.service'; +import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; +import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; +import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity'; +import { SEARCH_FIELDS_FOR_TASKS } from 'src/modules/task/standard-objects/task.workspace-entity'; + +@Command({ + name: 'upgrade-0.43:migrate-search-vector-on-note-and-task-entities', + description: 'Migrate search vector on note and task entities', +}) +export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveWorkspacesCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + @InjectRepository(FeatureFlag, 'core') + protected readonly featureFlagRepository: Repository, + @InjectRepository(ObjectMetadataEntity, 'metadata') + protected readonly objectMetadataRepository: Repository, + private readonly searchService: SearchService, + private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, + private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, + ) { + super(workspaceRepository); + } + + async executeActiveWorkspacesCommand( + _passedParam: string[], + options: ActiveWorkspacesCommandOptions, + workspaceIds: string[], + ): Promise { + this.logger.log( + 'Running command to migrate search vector on note and task entities', + ); + + for (const [index, workspaceId] of workspaceIds.entries()) { + await this.processWorkspace(workspaceId, index, workspaceIds.length); + } + + this.logger.log(chalk.green('Command completed!')); + } + + async processWorkspace( + workspaceId: string, + index: number, + total: number, + ): Promise { + try { + this.logger.log( + `Running command for workspace ${workspaceId} ${index + 1}/${total}`, + ); + + await this.featureFlagRepository.findOneOrFail({ + where: { + workspaceId, + key: FeatureFlagKey.IsRichTextV2Enabled, + value: true, + }, + }); + + const noteObjectMetadata = + await this.objectMetadataRepository.findOneOrFail({ + select: ['id'], + where: { + workspaceId, + nameSingular: 'note', + }, + }); + + await this.searchService.updateSearchVector( + noteObjectMetadata.id, + SEARCH_FIELDS_FOR_NOTES, + workspaceId, + ); + + const taskObjectMetadata = + await this.objectMetadataRepository.findOneOrFail({ + select: ['id'], + where: { + workspaceId, + nameSingular: 'task', + }, + }); + + await this.searchService.updateSearchVector( + taskObjectMetadata.id, + SEARCH_FIELDS_FOR_TASKS, + workspaceId, + ); + + await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( + workspaceId, + ); + + await this.workspaceMetadataVersionService.incrementMetadataVersion( + workspaceId, + ); + } catch (error) { + this.logger.log( + chalk.red(`Error in workspace ${workspaceId} - ${error.message}`), + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module.ts index 9ba25d5d5..6621ec39d 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module.ts @@ -2,25 +2,33 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command'; +import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command'; import { UpgradeTo0_43Command } from 'src/database/commands/upgrade-version/0-43/0-43-upgrade-version.command'; +import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; 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 { SearchModule } from 'src/engine/metadata-modules/search/search.module'; 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 { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; @Module({ imports: [ - TypeOrmModule.forFeature([Workspace], 'core'), + TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'), TypeOrmModule.forFeature( [ObjectMetadataEntity, FieldMetadataEntity], 'metadata', ), + SearchModule, WorkspaceMigrationRunnerModule, WorkspaceMigrationModule, WorkspaceMetadataVersionModule, ], - providers: [UpgradeTo0_43Command, AddTasksAssignedToMeViewCommand], + providers: [ + UpgradeTo0_43Command, + AddTasksAssignedToMeViewCommand, + MigrateSearchVectorOnNoteAndTaskEntitiesCommand, + ], }) export class UpgradeTo0_43CommandModule {} diff --git a/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts b/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts index 079e9c15b..42b260ca8 100644 --- a/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts +++ b/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts @@ -31,9 +31,11 @@ import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/not import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; const TITLE_FIELD_NAME = 'title'; +const BODY_V2_FIELD_NAME = 'bodyV2'; export const SEARCH_FIELDS_FOR_NOTES: FieldTypeAndNameMetadata[] = [ { name: TITLE_FIELD_NAME, type: FieldMetadataType.TEXT }, + { name: BODY_V2_FIELD_NAME, type: FieldMetadataType.RICH_TEXT_V2 }, ]; @WorkspaceEntity({ diff --git a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts index a5b433a07..fc9476452 100644 --- a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts +++ b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts @@ -34,8 +34,11 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta const TITLE_FIELD_NAME = 'title'; -export const SEARCH_FIELDS_FOR_TASK: FieldTypeAndNameMetadata[] = [ +const BODY_V2_FIELD_NAME = 'bodyV2'; + +export const SEARCH_FIELDS_FOR_TASKS: FieldTypeAndNameMetadata[] = [ { name: TITLE_FIELD_NAME, type: FieldMetadataType.TEXT }, + { name: BODY_V2_FIELD_NAME, type: FieldMetadataType.RICH_TEXT_V2 }, ]; @WorkspaceEntity({ @@ -205,7 +208,9 @@ export class TaskWorkspaceEntity extends BaseWorkspaceEntity { description: SEARCH_VECTOR_FIELD.description, icon: 'IconUser', generatedType: 'STORED', - asExpression: getTsVectorColumnExpressionFromFields(SEARCH_FIELDS_FOR_TASK), + asExpression: getTsVectorColumnExpressionFromFields( + SEARCH_FIELDS_FOR_TASKS, + ), }) @WorkspaceIsNullable() @WorkspaceIsSystem()