diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-clean-orphaned-kanban-aggregate-operation-field-metadata-id.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-clean-orphaned-kanban-aggregate-operation-field-metadata-id.command.ts new file mode 100644 index 0000000000..8014c08c38 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-clean-orphaned-kanban-aggregate-operation-field-metadata-id.command.ts @@ -0,0 +1,114 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { isDefined } from 'twenty-shared/utils'; +import { In, IsNull, Not, Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + type RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; +import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +@Command({ + name: 'upgrade:1-10:clean-orphaned-kanban-aggregate-operation-field-metadata-id', + description: + "Delete all kanbanAggregateOperationFieldMetadataId in views that don't map to a real fieldMetadata (this is because we want to introduce a FK later)", +}) +export class CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + @InjectRepository(ViewEntity) + private readonly viewRepository: Repository, + @InjectRepository(FieldMetadataEntity) + private readonly fieldMetadataRepository: Repository, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + workspaceId, + index, + total, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `[${index + 1}/${total}] Cleaning orphaned kanbanAggregateOperationFieldMetadataId for workspace ${workspaceId}`, + ); + + const existingFieldMetadataIds = await this.fieldMetadataRepository.find({ + where: { workspaceId }, + select: ['id'], + }); + + const existingFieldMetadataIdSet = new Set( + existingFieldMetadataIds.map((fm) => fm.id), + ); + + const viewsWithOrphanedFieldMetadata = await this.viewRepository.find({ + where: { + workspaceId, + kanbanAggregateOperationFieldMetadataId: Not(IsNull()), + }, + select: ['id', 'kanbanAggregateOperationFieldMetadataId', 'type'], + }); + + const orphanedViews = viewsWithOrphanedFieldMetadata.filter( + (view) => + isDefined(view.kanbanAggregateOperationFieldMetadataId) && + !existingFieldMetadataIdSet.has( + view.kanbanAggregateOperationFieldMetadataId, + ), + ); + + if (orphanedViews.length === 0) { + this.logger.log( + 'No orphaned kanbanAggregateOperationFieldMetadataId references found', + ); + + return; + } + + const kanbanViewsToDelete = orphanedViews.filter( + (view) => view.type === ViewType.KANBAN, + ); + const otherViewsToUpdate = orphanedViews.filter( + (view) => view.type !== ViewType.KANBAN, + ); + + let deletedCount = 0; + let updatedCount = 0; + + if (!options.dryRun) { + if (kanbanViewsToDelete.length > 0) { + const kanbanViewIds = kanbanViewsToDelete.map((view) => view.id); + + await this.viewRepository.delete({ id: In(kanbanViewIds) }); + deletedCount = kanbanViewsToDelete.length; + } + + if (otherViewsToUpdate.length > 0) { + const otherViewIds = otherViewsToUpdate.map((view) => view.id); + + await this.viewRepository.update( + { id: In(otherViewIds) }, + { kanbanAggregateOperationFieldMetadataId: null }, + ); + updatedCount = otherViewsToUpdate.length; + } + } else { + deletedCount = kanbanViewsToDelete.length; + updatedCount = otherViewsToUpdate.length; + } + + this.logger.log( + `${options.dryRun ? 'DRY RUN - Would have' : ''}Deleted ${deletedCount} KANBAN views and updated ${updatedCount} other views with orphaned kanbanAggregateOperationFieldMetadataId references`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module.ts index ce4bbbb574..f2e410e151 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AddWorkflowRunStopStatusesCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-add-workflow-run-stop-statuses.command'; +import { CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-clean-orphaned-kanban-aggregate-operation-field-metadata-id.command'; import { MigrateAttachmentAuthorToCreatedByCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-author-to-created-by.command'; import { MigrateAttachmentTypeToFileCategoryCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-type-to-file-category.command'; import { RegenerateSearchVectorsCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-regenerate-search-vectors.command'; @@ -9,6 +10,7 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module'; @Module({ @@ -18,6 +20,7 @@ import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-sc ObjectMetadataEntity, FieldMetadataEntity, IndexMetadataEntity, + ViewEntity, ]), WorkspaceSchemaManagerModule, ], @@ -26,12 +29,14 @@ import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-sc MigrateAttachmentTypeToFileCategoryCommand, RegenerateSearchVectorsCommand, AddWorkflowRunStopStatusesCommand, + CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand, ], exports: [ MigrateAttachmentAuthorToCreatedByCommand, MigrateAttachmentTypeToFileCategoryCommand, RegenerateSearchVectorsCommand, AddWorkflowRunStopStatusesCommand, + CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand, ], }) export class V1_10_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index b4f2816b31..157c9309cf 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -20,6 +20,7 @@ import { FixSchemaArrayTypeCommand } from 'src/database/commands/upgrade-version import { FixUpdateStandardFieldsIsLabelSyncedWithName } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command'; import { MigrateWorkflowRunStatesCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command'; import { AddWorkflowRunStopStatusesCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-add-workflow-run-stop-statuses.command'; +import { CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-clean-orphaned-kanban-aggregate-operation-field-metadata-id.command'; import { MigrateAttachmentAuthorToCreatedByCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-author-to-created-by.command'; import { MigrateAttachmentTypeToFileCategoryCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-type-to-file-category.command'; import { RegenerateSearchVectorsCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-regenerate-search-vectors.command'; @@ -105,6 +106,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly migrateAttachmentTypeToFileCategoryCommand: MigrateAttachmentTypeToFileCategoryCommand, protected readonly regenerateSearchVectorsCommand: RegenerateSearchVectorsCommand, protected readonly addWorkflowRunStopStatusesCommand: AddWorkflowRunStopStatusesCommand, + protected readonly cleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand: CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand, ) { super( workspaceRepository, @@ -213,6 +215,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { beforeSyncMetadata: [ this.regenerateSearchVectorsCommand, this.addWorkflowRunStopStatusesCommand, + this.cleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand, ], afterSyncMetadata: [ this.migrateAttachmentAuthorToCreatedByCommand,