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
This commit is contained in:
@ -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,
|
||||
|
||||
@ -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<Workspace>,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async executeActiveWorkspacesCommand(
|
||||
_passedParam: string[],
|
||||
options: DeleteViewFieldsWithoutViewsCommandOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
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`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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' },
|
||||
});
|
||||
|
||||
|
||||
@ -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<Workspace>,
|
||||
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<void> {
|
||||
await this.deleteViewFieldsWithoutViewsCommand.executeActiveWorkspacesCommand(
|
||||
passedParam,
|
||||
options,
|
||||
workspaceIds,
|
||||
);
|
||||
await this.enforceUniqueConstraintsCommand.executeActiveWorkspacesCommand(
|
||||
passedParam,
|
||||
{
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -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<FavoriteWorkspaceEntity[]>;
|
||||
}
|
||||
|
||||
@ -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<ViewWorkspaceEntity>;
|
||||
|
||||
@WorkspaceJoinColumn('view')
|
||||
viewId: string | null;
|
||||
viewId: string;
|
||||
}
|
||||
|
||||
@ -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<ViewFieldWorkspaceEntity[]>;
|
||||
|
||||
Reference in New Issue
Block a user