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 6715b5c293b..419a6f114b2 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 00000000000..790d829a904 --- /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 f85573e8a69..a5a0acb850c 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 9089d80adbf..972d9a582ba 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 8707a754908..cd7c1f7c2c3 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 dc290458794..0411d1ccee7 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 44e2f331e8a..9de31a8f020 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 72d90b4dabf..67af0c9fa21 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;