feat: workspace:health nullable fix (#3882)
This commit is contained in:
@ -5,12 +5,14 @@ import { WorkspaceHealthMode } from 'src/workspace/workspace-health/interfaces/w
|
||||
import { WorkspaceHealthFixKind } from 'src/workspace/workspace-health/interfaces/workspace-health-fix-kind.interface';
|
||||
|
||||
import { WorkspaceHealthService } from 'src/workspace/workspace-health/workspace-health.service';
|
||||
import { CommandLogger } from 'src/commands/command-logger';
|
||||
|
||||
interface WorkspaceHealthCommandOptions {
|
||||
workspaceId: string;
|
||||
verbose?: boolean;
|
||||
mode?: WorkspaceHealthMode;
|
||||
fix?: WorkspaceHealthFixKind;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
@Command({
|
||||
@ -18,6 +20,10 @@ interface WorkspaceHealthCommandOptions {
|
||||
description: 'Check health of the given workspace.',
|
||||
})
|
||||
export class WorkspaceHealthCommand extends CommandRunner {
|
||||
private readonly commandLogger = new CommandLogger(
|
||||
WorkspaceHealthCommand.name,
|
||||
);
|
||||
|
||||
constructor(private readonly workspaceHealthService: WorkspaceHealthService) {
|
||||
super();
|
||||
}
|
||||
@ -48,11 +54,29 @@ export class WorkspaceHealthCommand extends CommandRunner {
|
||||
}
|
||||
|
||||
if (options.fix) {
|
||||
await this.workspaceHealthService.fixIssues(
|
||||
console.log(chalk.yellow('Fixing issues'));
|
||||
|
||||
const workspaceMigrations = await this.workspaceHealthService.fixIssues(
|
||||
options.workspaceId,
|
||||
issues,
|
||||
options.fix,
|
||||
{
|
||||
type: options.fix,
|
||||
applyChanges: !options.dryRun,
|
||||
},
|
||||
);
|
||||
|
||||
if (options.dryRun) {
|
||||
await this.commandLogger.writeLog(
|
||||
`workspace-health-${options.fix}-migrations`,
|
||||
workspaceMigrations,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Fixed ${workspaceMigrations.length}/${issues.length} issues`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,4 +124,13 @@ export class WorkspaceHealthCommand extends CommandRunner {
|
||||
|
||||
return value as WorkspaceHealthMode;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-d, --dry-run',
|
||||
description: 'Dry run without applying changes',
|
||||
required: false,
|
||||
})
|
||||
dryRun(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,6 @@ export enum WorkspaceHealthIssueType {
|
||||
COLUMN_OPTIONS_NOT_VALID = 'COLUMN_OPTIONS_NOT_VALID',
|
||||
RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID = 'RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID',
|
||||
RELATION_FOREIGN_KEY_NOT_VALID = 'RELATION_FOREIGN_KEY_NOT_VALID',
|
||||
RELATION_NULLABILITY_CONFLICT = 'RELATION_NULLABILITY_CONFLICT',
|
||||
RELATION_FOREIGN_KEY_CONFLICT = 'RELATION_FOREIGN_KEY_CONFLICT',
|
||||
RELATION_TYPE_NOT_VALID = 'RELATION_TYPE_NOT_VALID',
|
||||
}
|
||||
@ -83,7 +82,6 @@ export interface WorkspaceHealthRelationIssue<
|
||||
T,
|
||||
| WorkspaceHealthIssueType.RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID
|
||||
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_NOT_VALID
|
||||
| WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT
|
||||
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_CONFLICT
|
||||
| WorkspaceHealthIssueType.RELATION_TYPE_NOT_VALID
|
||||
>;
|
||||
|
||||
@ -188,16 +188,6 @@ export class RelationMetadataHealthService {
|
||||
});
|
||||
}
|
||||
|
||||
if (relationColumn.isNullable !== relationFieldMetadata.isNullable) {
|
||||
issues.push({
|
||||
type: WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT,
|
||||
fromFieldMetadata,
|
||||
toFieldMetadata,
|
||||
relationMetadata,
|
||||
message: `Relation ${relationMetadata.id} foreign key is not properly set`,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE &&
|
||||
!relationColumn.isUnique
|
||||
|
||||
@ -5,29 +5,69 @@ import { EntityManager } from 'typeorm';
|
||||
import {
|
||||
WorkspaceHealthColumnIssue,
|
||||
WorkspaceHealthIssueType,
|
||||
WorkspaceHealthRelationIssue,
|
||||
} from 'src/workspace/workspace-health/interfaces/workspace-health-issue.interface';
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/workspace/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
|
||||
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceMigrationFieldFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-field.factory';
|
||||
|
||||
type WorkspaceHealthNullableIssue =
|
||||
| WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT>
|
||||
| WorkspaceHealthRelationIssue<WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT>;
|
||||
WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT>;
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceFixNullableService {
|
||||
constructor() {}
|
||||
constructor(
|
||||
private readonly workspaceMigrationFieldFactory: WorkspaceMigrationFieldFactory,
|
||||
) {}
|
||||
|
||||
async fix(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
manager: EntityManager,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
issues: WorkspaceHealthNullableIssue[],
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
// TODO: Implement nullable fix
|
||||
return [];
|
||||
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
for (const issue of issues) {
|
||||
switch (issue.type) {
|
||||
case WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT: {
|
||||
const columnNullabilityWorkspaceMigrations =
|
||||
await this.fixColumnNullabilityIssues(
|
||||
objectMetadataCollection,
|
||||
issues.filter(
|
||||
(issue) =>
|
||||
issue.type ===
|
||||
WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT,
|
||||
) as WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT>[],
|
||||
);
|
||||
|
||||
workspaceMigrations.push(...columnNullabilityWorkspaceMigrations);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return workspaceMigrations;
|
||||
}
|
||||
|
||||
private async fixColumnNullabilityIssues(
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
issues: WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT>[],
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
const fieldMetadataUpdateCollection = issues.map((issue) => {
|
||||
return {
|
||||
current: {
|
||||
...issue.fieldMetadata,
|
||||
isNullable: issue.columnStructure?.isNullable ?? false,
|
||||
},
|
||||
altered: issue.fieldMetadata,
|
||||
};
|
||||
});
|
||||
|
||||
return this.workspaceMigrationFieldFactory.create(
|
||||
objectMetadataCollection,
|
||||
fieldMetadataUpdateCollection,
|
||||
WorkspaceMigrationBuilderAction.UPDATE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,11 +2,6 @@ import { WorkspaceHealthIssueType } from 'src/workspace/workspace-health/interfa
|
||||
|
||||
export const isWorkspaceHealthNullableIssue = (
|
||||
type: WorkspaceHealthIssueType,
|
||||
): type is
|
||||
| WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT
|
||||
| WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT => {
|
||||
return type === WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT ||
|
||||
type === WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT
|
||||
? true
|
||||
: false;
|
||||
): type is WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT => {
|
||||
return type === WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT;
|
||||
};
|
||||
|
||||
@ -121,8 +121,16 @@ export class WorkspaceHealthService {
|
||||
async fixIssues(
|
||||
workspaceId: string,
|
||||
issues: WorkspaceHealthIssue[],
|
||||
type: WorkspaceHealthFixKind,
|
||||
): Promise<void> {
|
||||
options: {
|
||||
type: WorkspaceHealthFixKind;
|
||||
applyChanges?: boolean;
|
||||
},
|
||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||
let workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
// Set default options
|
||||
options.applyChanges ??= true;
|
||||
|
||||
const queryRunner = this.metadataDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
@ -137,16 +145,25 @@ export class WorkspaceHealthService {
|
||||
const objectMetadataCollection =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
||||
|
||||
const workspaceMigrations = await this.workspaceFixService.fix(
|
||||
workspaceMigrations = await this.workspaceFixService.fix(
|
||||
manager,
|
||||
objectMetadataCollection,
|
||||
type,
|
||||
options.type,
|
||||
issues,
|
||||
);
|
||||
|
||||
// Save workspace migrations into the database
|
||||
await workspaceMigrationRepository.save(workspaceMigrations);
|
||||
|
||||
if (!options.applyChanges) {
|
||||
// Rollback transactions
|
||||
await queryRunner.rollbackTransaction();
|
||||
|
||||
await queryRunner.release();
|
||||
|
||||
return workspaceMigrations;
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
@ -160,5 +177,7 @@ export class WorkspaceHealthService {
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
|
||||
return workspaceMigrations;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user