Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add set custom object is soft deletable command #6788

Merged
merged 8 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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>;
}
46 changes: 46 additions & 0 deletions packages/twenty-server/src/database/commands/base.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Logger } from '@nestjs/common';

import chalk from 'chalk';
import { CommandRunner, Option } from 'nest-commander';

export type BaseCommandOptions = {
workspaceId?: string;
dryRun?: boolean;
};

export abstract class BaseCommandRunner extends CommandRunner {
protected readonly logger: Logger;

constructor() {
super();
this.logger = new Logger(this.constructor.name);
}

@Option({
flags: '-d, --dry-run',
description: 'Simulate the command without making actual changes',
required: false,
})
parseDryRun(): boolean {
return true;
}

override async run(
passedParams: string[],
options: BaseCommandOptions,
): Promise<void> {
try {
await this.executeBaseCommand(passedParams, options);
} catch (error) {
this.logger.error(chalk.red(`Command failed`));
throw error;
} finally {
this.logger.log(chalk.blue('Command completed!'));
}
}

protected abstract executeBaseCommand(
passedParams: string[],
options: BaseCommandOptions,
): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { InjectRepository } from '@nestjs/typeorm';

import { Command } from 'nest-commander';
import { In, Repository } from 'typeorm';

import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';

type SetCustomObjectIsSoftDeletableCommandOptions =
ActiveWorkspacesCommandOptions;

@Command({
name: 'upgrade-0.24:set-custom-object-is-soft-deletable',
description: 'Set custom object is soft deletable',
})
export class SetCustomObjectIsSoftDeletableCommand extends ActiveWorkspacesCommandRunner {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love it!

constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
) {
super(workspaceRepository);
}

async executeActiveWorkspacesCommand(
_passedParam: string[],
options: SetCustomObjectIsSoftDeletableCommandOptions,
workspaceIds: string[],
): Promise<void> {
const updateCriteria = {
workspaceId: In(workspaceIds),
isCustom: true,
isSoftDeletable: false,
};

if (options.dryRun) {
const objectsToUpdate = await this.objectMetadataRepository.find({
select: ['id'],
where: updateCriteria,
});

this.logger.log(
`Dry run: ${objectsToUpdate.length} objects would be updated`,
);

return;
}

const result = await this.objectMetadataRepository.update(updateCriteria, {
isSoftDeletable: true,
});

this.logger.log(`Updated ${result.affected} objects`);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Command, CommandRunner, Option } from 'nest-commander';

import { SetCustomObjectIsSoftDeletableCommand } from 'src/database/commands/upgrade-version/0-24/0-24-set-custom-object-is-soft-deletable.command';
import { SetMessageDirectionCommand } from 'src/database/commands/upgrade-version/0-24/0-24-set-message-direction.command';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';

Expand All @@ -15,6 +16,7 @@ export class UpgradeTo0_24Command extends CommandRunner {
constructor(
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
private readonly setMessagesDirectionCommand: SetMessageDirectionCommand,
private readonly setCustomObjectIsSoftDeletableCommand: SetCustomObjectIsSoftDeletableCommand,
) {
super();
}
Expand All @@ -30,13 +32,14 @@ export class UpgradeTo0_24Command extends CommandRunner {
}

async run(
_passedParam: string[],
passedParam: string[],
options: UpdateTo0_24CommandOptions,
): Promise<void> {
await this.syncWorkspaceMetadataCommand.run(_passedParam, {
await this.syncWorkspaceMetadataCommand.run(passedParam, {
...options,
force: true,
});
await this.setMessagesDirectionCommand.run(_passedParam, options);
await this.setMessagesDirectionCommand.run(passedParam, options);
await this.setCustomObjectIsSoftDeletableCommand.run(passedParam, options);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { SetCustomObjectIsSoftDeletableCommand } from 'src/database/commands/upgrade-version/0-24/0-24-set-custom-object-is-soft-deletable.command';
import { SetMessageDirectionCommand } from 'src/database/commands/upgrade-version/0-24/0-24-set-message-direction.command';
import { UpgradeTo0_24Command } from 'src/database/commands/upgrade-version/0-24/0-24-upgrade-version.command';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
Expand Down Expand Up @@ -33,6 +34,10 @@ import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manage
),
TypeORMModule,
],
providers: [UpgradeTo0_24Command, SetMessageDirectionCommand],
providers: [
UpgradeTo0_24Command,
SetMessageDirectionCommand,
SetCustomObjectIsSoftDeletableCommand,
],
})
export class UpgradeTo0_24CommandModule {}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TypeOrmModuleOptions } from '@nestjs/typeorm';

import { DataSource, DataSourceOptions } from 'typeorm';
import { config } from 'dotenv';
import { DataSource, DataSourceOptions } from 'typeorm';
config();

export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export class GraphqlQuerySelectedFieldsRelationParser {
fieldValue: any,
result: { select: Record<string, any>; relations: Record<string, any> },
): void {
result.relations[fieldKey] = true;

if (!fieldValue || typeof fieldValue !== 'object') {
return;
}

result.relations[fieldKey] = true;

const referencedObjectMetadata = getRelationObjectMetadata(
fieldMetadata,
this.objectMetadataMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ export class GraphqlQueryRunnerService {
const objectMetadata = objectMetadataMap[objectMetadataItem.nameSingular];

if (!objectMetadata) {
throw new Error(
`Object metadata for ${objectMetadataItem.nameSingular} not found`,
throw new GraphqlQueryRunnerException(
`Object metadata not found for ${objectMetadataItem.nameSingular}`,
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}

Expand Down
Loading