Add set custom object is soft deletable command (#6788)

## Context
Custom object were not automatically created as softDeletable, this has
been fixed in a recent PR.
This PR adds a command to backfill existing custom objects.

We also introduce a baseCommandRunner and ActiveWorkspacesCommandRunner
to put some boilerplate and simplify future commands.

## Test
```bash
yarn command:prod upgrade-0-24:set-custom-object-is-soft-deletable
[Nest] 75852  - 08/29/2024, 5:16:41 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Running command on 2 workspaces
query: UPDATE "metadata"."objectMetadata" SET "isSoftDeletable" = $1, "updatedAt" = CURRENT_TIMESTAMP WHERE ("workspaceId" IN ($2, $3) AND "isCustom" = $4 AND "isSoftDeletable" = $5) -- PARAMETERS: [true,"3b8e6458-5fc1-4e63-8563-008ccddaa6db","20202020-1c25-4d02-bf25-6aeccf7ea419",true,false]
[Nest] 75852  - 08/29/2024, 5:16:41 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Updated 1 entities
[Nest] 75852  - 08/29/2024, 5:16:41 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Command completed!
```

```bash
yarn command:prod upgrade-0-24:set-custom-object-is-soft-deletable -d
[Nest] 75424  - 08/29/2024, 5:16:14 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Running command on 2 workspaces
[Nest] 75424  - 08/29/2024, 5:16:14 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Dry run mode: No changes will be applied
query: SELECT "ObjectMetadataEntity"."id" AS "ObjectMetadataEntity_id" FROM "metadata"."objectMetadata" "ObjectMetadataEntity" WHERE (("ObjectMetadataEntity"."workspaceId" IN ($1, $2)) AND ("ObjectMetadataEntity"."isCustom" = $3) AND ("ObjectMetadataEntity"."isSoftDeletable" = $4)) -- PARAMETERS: ["3b8e6458-5fc1-4e63-8563-008ccddaa6db","20202020-1c25-4d02-bf25-6aeccf7ea419",true,false]
[Nest] 75424  - 08/29/2024, 5:16:14 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Dry run: 1 entities would be updated
[Nest] 75424  - 08/29/2024, 5:16:14 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Command completed!
```

```bash
yarn command:prod upgrade-0-24:set-custom-object-is-soft-deletable -w 20202020-1c25-4d02-bf25-6aeccf7ea419 -w 20202020-1c25-4d02-bf25-6aeccf7ea419
query: UPDATE "metadata"."objectMetadata" SET "isSoftDeletable" = $1, "updatedAt" = CURRENT_TIMESTAMP WHERE ("workspaceId" IN ($2, $3) AND "isCustom" = $4) -- PARAMETERS: [true,"20202020-1c25-4d02-bf25-6aeccf7ea419","20202020-1c25-4d02-bf25-6aeccf7ea419",true]
[Nest] 70588  - 08/29/2024, 5:11:31 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Updated 2 entities
[Nest] 70588  - 08/29/2024, 5:11:31 PM     LOG [SetCustomObjectIsSoftDeletableCommand] Command completed!
```

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Weiko
2024-08-31 17:49:12 +02:00
committed by GitHub
parent c5572f1b1e
commit 7df5f91dc5
8 changed files with 216 additions and 9 deletions

View File

@ -0,0 +1,92 @@
import { Logger } from '@nestjs/common';
import chalk from 'chalk';
import { Option } from 'nest-commander';
import { Repository } from 'typeorm';
import {
BaseCommandOptions,
BaseCommandRunner,
} from 'src/database/commands/base.command';
import {
Workspace,
WorkspaceActivationStatus,
} from 'src/engine/core-modules/workspace/workspace.entity';
export type ActiveWorkspacesCommandOptions = BaseCommandOptions & {
workspaceId?: string;
};
export abstract class ActiveWorkspacesCommandRunner extends BaseCommandRunner {
private workspaceIds: string[] = [];
protected readonly logger: Logger;
constructor(protected readonly workspaceRepository: Repository<Workspace>) {
super();
this.logger = new Logger(this.constructor.name);
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description:
'workspace id. Command runs on all active workspaces if not provided',
required: false,
})
parseWorkspaceId(val: string): string[] {
this.workspaceIds.push(val);
return this.workspaceIds;
}
protected async fetchActiveWorkspaceIds(): Promise<string[]> {
const activeWorkspaces = await this.workspaceRepository.find({
select: ['id'],
where: {
activationStatus: WorkspaceActivationStatus.ACTIVE,
},
});
return activeWorkspaces.map((workspace) => workspace.id);
}
protected logWorkspaceCount(activeWorkspaceIds: string[]): void {
if (!activeWorkspaceIds.length) {
this.logger.log(chalk.yellow('No workspace found'));
} else {
this.logger.log(
chalk.green(
`Running command on ${activeWorkspaceIds.length} workspaces`,
),
);
}
}
override async executeBaseCommand(
passedParams: string[],
options: BaseCommandOptions,
): Promise<void> {
const activeWorkspaceIds =
this.workspaceIds.length > 0
? this.workspaceIds
: await this.fetchActiveWorkspaceIds();
this.logWorkspaceCount(activeWorkspaceIds);
if (options.dryRun) {
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
}
await this.executeActiveWorkspacesCommand(
passedParams,
options,
activeWorkspaceIds,
);
}
protected abstract executeActiveWorkspacesCommand(
passedParams: string[],
options: BaseCommandOptions,
activeWorkspaceIds: string[],
): Promise<void>;
}