From a16b0d233e7a9f80ade788e9aa6a98324fdd802f Mon Sep 17 00:00:00 2001 From: Weiko Date: Tue, 26 Nov 2024 10:40:17 +0100 Subject: [PATCH] add delete view fields without views command (#8728) ## Context We recently added a command to ensure uniqueness on the viewId column in the viewField table. This created some issues for some old workspaces that had viewFields with an empty viewId. This command should get rid of those and set the column as non-nullable. Also updating the onDelete action accordingly and set one missing for FavoriteFolder --- .../src/database/commands/base.command.ts | 9 ++ ...elete-view-fields-without-views.command.ts | 95 +++++++++++++++++++ ...0-33-enforce-unique-constraints.command.ts | 31 +++--- .../0-33/0-33-upgrade-version.command.ts | 7 ++ .../0-33/0-33-upgrade-version.module.ts | 2 + .../favorite-folder.workspace-entity.ts | 6 +- .../view-field.workspace-entity.ts | 8 +- .../standard-objects/view.workspace-entity.ts | 2 +- 8 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts diff --git a/packages/twenty-server/src/database/commands/base.command.ts b/packages/twenty-server/src/database/commands/base.command.ts index 6715b5c29..419a6f114 100644 --- a/packages/twenty-server/src/database/commands/base.command.ts +++ b/packages/twenty-server/src/database/commands/base.command.ts @@ -6,6 +6,7 @@ import { CommandRunner, Option } from 'nest-commander'; export type BaseCommandOptions = { workspaceId?: string; dryRun?: boolean; + verbose?: boolean; }; export abstract class BaseCommandRunner extends CommandRunner { @@ -25,6 +26,14 @@ export abstract class BaseCommandRunner extends CommandRunner { return true; } + @Option({ + flags: '--verbose', + description: 'Verbose output', + }) + parseVerbose() { + return true; + } + override async run( passedParams: string[], options: BaseCommandOptions, diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts new file mode 100644 index 000000000..790d829a9 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts @@ -0,0 +1,95 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command } from 'nest-commander'; +import { IsNull, Repository } from 'typeorm'; + +import { + ActiveWorkspacesCommandOptions, + ActiveWorkspacesCommandRunner, +} from 'src/database/commands/active-workspaces.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +interface DeleteViewFieldsWithoutViewsCommandOptions + extends ActiveWorkspacesCommandOptions {} + +@Command({ + name: 'upgrade-0.33:delete-view-fields-without-views', + description: 'Delete ViewFields that do not have a View', +}) +export class DeleteViewFieldsWithoutViewsCommand extends ActiveWorkspacesCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) { + super(workspaceRepository); + } + + async executeActiveWorkspacesCommand( + _passedParam: string[], + options: DeleteViewFieldsWithoutViewsCommandOptions, + workspaceIds: string[], + ): Promise { + this.logger.log( + 'Running command to delete ViewFields that do not have a View', + ); + + for (const workspaceId of workspaceIds) { + this.logger.log(`Running command for workspace ${workspaceId}`); + + try { + await this.deleteViewFieldsWithoutViewsForWorkspace( + workspaceId, + options, + ); + } catch (error) { + this.logger.log( + chalk.red( + `Running command on workspace ${workspaceId} failed with error: ${error}, ${error.stack}`, + ), + ); + continue; + } finally { + this.logger.log( + chalk.green(`Finished running command for workspace ${workspaceId}.`), + ); + } + } + + this.logger.log(chalk.green(`Command completed!`)); + } + + private async deleteViewFieldsWithoutViewsForWorkspace( + workspaceId: string, + options: DeleteViewFieldsWithoutViewsCommandOptions, + ): Promise { + const viewFieldRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'viewField', + false, + ); + + const viewFieldsWithoutViews = await viewFieldRepository.find({ + where: { + viewId: IsNull(), + }, + }); + + const viewFieldIds = viewFieldsWithoutViews.map((vf) => vf.id); + + if (!options.dryRun && viewFieldIds.length > 0) { + await viewFieldRepository.delete(viewFieldIds); + } + + if (options.verbose) { + this.logger.log( + chalk.yellow( + `Deleted ${viewFieldsWithoutViews.length} ViewFields that do not have a View`, + ), + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts index f85573e8a..a5a0acb85 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts @@ -17,7 +17,6 @@ interface EnforceUniqueConstraintsCommandOptions company?: boolean; viewField?: boolean; viewSort?: boolean; - verbose?: boolean; } @Command({ @@ -42,14 +41,6 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn return true; } - @Option({ - flags: '--verbose', - description: 'Verbose output', - }) - parseVerbose() { - return true; - } - @Option({ flags: '--company', description: 'Enforce unique constraints on company domainName', @@ -250,9 +241,16 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn .getRawMany(); for (const duplicate of duplicates) { - const { fieldMetadataId, viewId } = duplicate; + const { + viewField_fieldMetadataId: fieldMetadataId, + viewField_viewId: viewId, + } = duplicate; const viewFields = await viewFieldRepository.find({ - where: { fieldMetadataId, viewId, deletedAt: IsNull() }, + where: { + fieldMetadataId, + viewId, + deletedAt: IsNull(), + }, order: { createdAt: 'DESC' }, }); @@ -292,9 +290,16 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn .getRawMany(); for (const duplicate of duplicates) { - const { fieldMetadataId, viewId } = duplicate; + const { + viewSort_fieldMetadataId: fieldMetadataId, + viewSort_viewId: viewId, + } = duplicate; const viewSorts = await viewSortRepository.find({ - where: { fieldMetadataId, viewId, deletedAt: IsNull() }, + where: { + fieldMetadataId, + viewId, + deletedAt: IsNull(), + }, order: { createdAt: 'DESC' }, }); diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts index 9089d80ad..972d9a582 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts @@ -4,6 +4,7 @@ import { Command } from 'nest-commander'; import { Repository } from 'typeorm'; import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; +import { DeleteViewFieldsWithoutViewsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command'; import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command'; import { UpdateRichTextSearchVectorCommand } from 'src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -23,6 +24,7 @@ export class UpgradeTo0_33Command extends ActiveWorkspacesCommandRunner { protected readonly workspaceRepository: Repository, private readonly updateRichTextSearchVectorCommand: UpdateRichTextSearchVectorCommand, private readonly enforceUniqueConstraintsCommand: EnforceUniqueConstraintsCommand, + private readonly deleteViewFieldsWithoutViewsCommand: DeleteViewFieldsWithoutViewsCommand, private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, ) { super(workspaceRepository); @@ -33,6 +35,11 @@ export class UpgradeTo0_33Command extends ActiveWorkspacesCommandRunner { options: UpdateTo0_33CommandOptions, workspaceIds: string[], ): Promise { + await this.deleteViewFieldsWithoutViewsCommand.executeActiveWorkspacesCommand( + passedParam, + options, + workspaceIds, + ); await this.enforceUniqueConstraintsCommand.executeActiveWorkspacesCommand( passedParam, { diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts index 8707a7549..cd7c1f7c2 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { DeleteViewFieldsWithoutViewsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command'; import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command'; import { UpdateRichTextSearchVectorCommand } from 'src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression'; import { UpgradeTo0_33Command } from 'src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command'; @@ -26,6 +27,7 @@ import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manage UpgradeTo0_33Command, UpdateRichTextSearchVectorCommand, EnforceUniqueConstraintsCommand, + DeleteViewFieldsWithoutViewsCommand, ], }) export class UpgradeTo0_33CommandModule {} diff --git a/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts b/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts index dc2904587..0411d1cce 100644 --- a/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts +++ b/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts @@ -2,7 +2,10 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/i import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; @@ -53,6 +56,7 @@ export class FavoriteFolderWorkspaceEntity extends BaseWorkspaceEntity { icon: 'IconHeart', inverseSideFieldKey: 'favoriteFolder', inverseSideTarget: () => FavoriteWorkspaceEntity, + onDelete: RelationOnDeleteAction.SET_NULL, }) favorites: Relation; } diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts index 44e2f331e..9de31a8f0 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts @@ -1,3 +1,5 @@ +import { Relation } from 'typeorm'; + import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; @@ -5,7 +7,6 @@ import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-enti import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; @@ -77,9 +78,8 @@ export class ViewFieldWorkspaceEntity extends BaseWorkspaceEntity { inverseSideTarget: () => ViewWorkspaceEntity, inverseSideFieldKey: 'viewFields', }) - @WorkspaceIsNullable() - view?: ViewWorkspaceEntity | null; + view: Relation; @WorkspaceJoinColumn('view') - viewId: string | null; + viewId: string; } diff --git a/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts index 72d90b4da..67af0c9fa 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts @@ -111,7 +111,7 @@ export class ViewWorkspaceEntity extends BaseWorkspaceEntity { description: 'View Fields', icon: 'IconTag', inverseSideTarget: () => ViewFieldWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, + onDelete: RelationOnDeleteAction.CASCADE, }) @WorkspaceIsNullable() viewFields: Relation;