Feat/workspace health core fix (#3863)

* feat: add deletion support on sync metadata command

* fix: remove debug

* feat: wip workspace health command add --fix option

fix: remove test

* feat: core of --fix option for workspace-health
This commit is contained in:
Jérémy M
2024-02-07 18:27:35 +01:00
committed by GitHub
parent 850eab8f8f
commit 6e3a8e3461
20 changed files with 380 additions and 103 deletions

View File

@ -1,6 +1,3 @@
export function generateMigrationName( export function generateMigrationName(name?: string): string {
name?: string, return `${new Date().getTime()}${name ? `-${name}` : ''}`;
addMilliseconds: number = 0,
): string {
return `${new Date().getTime() + addMilliseconds}${name ? `-${name}` : ''}`;
} }

View File

@ -2,6 +2,7 @@ import { Command, CommandRunner, Option } from 'nest-commander';
import chalk from 'chalk'; import chalk from 'chalk';
import { WorkspaceHealthMode } from 'src/workspace/workspace-health/interfaces/workspace-health-options.interface'; import { WorkspaceHealthMode } from 'src/workspace/workspace-health/interfaces/workspace-health-options.interface';
import { WorkspaceHealthFixKind } from 'src/workspace/workspace-health/interfaces/workspace-health-fix-kind.interface';
import { WorkspaceHealthService } from 'src/workspace/workspace-health/workspace-health.service'; import { WorkspaceHealthService } from 'src/workspace/workspace-health/workspace-health.service';
@ -9,6 +10,7 @@ interface WorkspaceHealthCommandOptions {
workspaceId: string; workspaceId: string;
verbose?: boolean; verbose?: boolean;
mode?: WorkspaceHealthMode; mode?: WorkspaceHealthMode;
fix?: WorkspaceHealthFixKind;
} }
@Command({ @Command({
@ -44,6 +46,14 @@ export class WorkspaceHealthCommand extends CommandRunner {
console.groupEnd(); console.groupEnd();
} }
} }
if (options.fix) {
await this.workspaceHealthService.fixIssues(
options.workspaceId,
issues,
options.fix,
);
}
} }
@Option({ @Option({
@ -55,6 +65,19 @@ export class WorkspaceHealthCommand extends CommandRunner {
return value; return value;
} }
@Option({
flags: '-f, --fix [kind]',
description: 'fix issues',
required: false,
})
fix(value: string): WorkspaceHealthFixKind {
if (!Object.values(WorkspaceHealthFixKind).includes(value as any)) {
throw new Error(`Invalid fix kind ${value}`);
}
return value as WorkspaceHealthFixKind;
}
@Option({ @Option({
flags: '-v, --verbose', flags: '-v, --verbose',
description: 'Detailed output', description: 'Detailed output',
@ -71,6 +94,10 @@ export class WorkspaceHealthCommand extends CommandRunner {
defaultValue: WorkspaceHealthMode.All, defaultValue: WorkspaceHealthMode.All,
}) })
parseMode(value: string): WorkspaceHealthMode { parseMode(value: string): WorkspaceHealthMode {
if (!Object.values(WorkspaceHealthMode).includes(value as any)) {
throw new Error(`Invalid mode ${value}`);
}
return value as WorkspaceHealthMode; return value as WorkspaceHealthMode;
} }
} }

View File

@ -0,0 +1,6 @@
export enum WorkspaceHealthFixKind {
Nullable = 'nullable',
Type = 'type',
DefaultValue = 'default-value',
TargetColumnMap = 'target-column-map',
}

View File

@ -31,19 +31,31 @@ export enum WorkspaceHealthIssueType {
RELATION_TYPE_NOT_VALID = 'RELATION_TYPE_NOT_VALID', RELATION_TYPE_NOT_VALID = 'RELATION_TYPE_NOT_VALID',
} }
export interface WorkspaceHealthTableIssue { type ConditionalType<
type: T extends WorkspaceHealthIssueType | null,
U,
> = T extends WorkspaceHealthIssueType ? T : U;
export interface WorkspaceHealthTableIssue<
T extends WorkspaceHealthIssueType | null = null,
> {
type: ConditionalType<
T,
| WorkspaceHealthIssueType.MISSING_TABLE | WorkspaceHealthIssueType.MISSING_TABLE
| WorkspaceHealthIssueType.TABLE_NAME_SHOULD_BE_CUSTOM | WorkspaceHealthIssueType.TABLE_NAME_SHOULD_BE_CUSTOM
| WorkspaceHealthIssueType.TABLE_TARGET_TABLE_NAME_NOT_VALID | WorkspaceHealthIssueType.TABLE_TARGET_TABLE_NAME_NOT_VALID
| WorkspaceHealthIssueType.TABLE_DATA_SOURCE_ID_NOT_VALID | WorkspaceHealthIssueType.TABLE_DATA_SOURCE_ID_NOT_VALID
| WorkspaceHealthIssueType.TABLE_NAME_NOT_VALID; | WorkspaceHealthIssueType.TABLE_NAME_NOT_VALID
>;
objectMetadata: ObjectMetadataEntity; objectMetadata: ObjectMetadataEntity;
message: string; message: string;
} }
export interface WorkspaceHealthColumnIssue { export interface WorkspaceHealthColumnIssue<
type: T extends WorkspaceHealthIssueType | null = null,
> {
type: ConditionalType<
T,
| WorkspaceHealthIssueType.MISSING_COLUMN | WorkspaceHealthIssueType.MISSING_COLUMN
| WorkspaceHealthIssueType.MISSING_INDEX | WorkspaceHealthIssueType.MISSING_INDEX
| WorkspaceHealthIssueType.MISSING_FOREIGN_KEY | WorkspaceHealthIssueType.MISSING_FOREIGN_KEY
@ -57,19 +69,24 @@ export interface WorkspaceHealthColumnIssue {
| WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT | WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT
| WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_CONFLICT | WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_CONFLICT
| WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_NOT_VALID | WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_NOT_VALID
| WorkspaceHealthIssueType.COLUMN_OPTIONS_NOT_VALID; | WorkspaceHealthIssueType.COLUMN_OPTIONS_NOT_VALID
>;
fieldMetadata: FieldMetadataEntity; fieldMetadata: FieldMetadataEntity;
columnStructure?: WorkspaceTableStructure; columnStructure?: WorkspaceTableStructure;
message: string; message: string;
} }
export interface WorkspaceHealthRelationIssue { export interface WorkspaceHealthRelationIssue<
type: T extends WorkspaceHealthIssueType | null = null,
> {
type: ConditionalType<
T,
| WorkspaceHealthIssueType.RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID | WorkspaceHealthIssueType.RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_NOT_VALID | WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_NOT_VALID
| WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT | WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_CONFLICT | WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_CONFLICT
| WorkspaceHealthIssueType.RELATION_TYPE_NOT_VALID; | WorkspaceHealthIssueType.RELATION_TYPE_NOT_VALID
>;
fromFieldMetadata: FieldMetadataEntity | undefined; fromFieldMetadata: FieldMetadataEntity | undefined;
toFieldMetadata: FieldMetadataEntity | undefined; toFieldMetadata: FieldMetadataEntity | undefined;
relationMetadata: RelationMetadataEntity; relationMetadata: RelationMetadataEntity;

View File

@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import {
WorkspaceHealthColumnIssue,
WorkspaceHealthIssueType,
WorkspaceHealthRelationIssue,
} from 'src/workspace/workspace-health/interfaces/workspace-health-issue.interface';
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
type WorkspaceHealthNullableIssue =
| WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT>
| WorkspaceHealthRelationIssue<WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT>;
@Injectable()
export class WorkspaceFixNullableService {
constructor() {}
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 [];
}
}

View File

@ -0,0 +1,41 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceHealthFixKind } from 'src/workspace/workspace-health/interfaces/workspace-health-fix-kind.interface';
import { WorkspaceHealthIssue } from 'src/workspace/workspace-health/interfaces/workspace-health-issue.interface';
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { isWorkspaceHealthNullableIssue } from 'src/workspace/workspace-health/utils/is-workspace-health-issue-type.util';
import { WorkspaceFixNullableService } from './workspace-fix-nullable.service';
@Injectable()
export class WorkspaceFixService {
constructor(
private readonly workspaceFixNullableService: WorkspaceFixNullableService,
) {}
async fix(
manager: EntityManager,
objectMetadataCollection: ObjectMetadataEntity[],
type: WorkspaceHealthFixKind,
issues: WorkspaceHealthIssue[],
): Promise<Partial<WorkspaceMigrationEntity>[]> {
const services = {
[WorkspaceHealthFixKind.Nullable]: {
service: this.workspaceFixNullableService,
issues: issues.filter((issue) =>
isWorkspaceHealthNullableIssue(issue.type),
),
},
};
return services[type].service.fix(
manager,
objectMetadataCollection,
services[type].issues,
);
}
}

View File

@ -0,0 +1,12 @@
import { WorkspaceHealthIssueType } from 'src/workspace/workspace-health/interfaces/workspace-health-issue.interface';
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;
};

View File

@ -9,6 +9,11 @@ import { FieldMetadataHealthService } from 'src/workspace/workspace-health/servi
import { ObjectMetadataHealthService } from 'src/workspace/workspace-health/services/object-metadata-health.service'; import { ObjectMetadataHealthService } from 'src/workspace/workspace-health/services/object-metadata-health.service';
import { RelationMetadataHealthService } from 'src/workspace/workspace-health/services/relation-metadata.health.service'; import { RelationMetadataHealthService } from 'src/workspace/workspace-health/services/relation-metadata.health.service';
import { WorkspaceHealthService } from 'src/workspace/workspace-health/workspace-health.service'; import { WorkspaceHealthService } from 'src/workspace/workspace-health/workspace-health.service';
import { WorkspaceMigrationBuilderModule } from 'src/workspace/workspace-migration-builder/workspace-migration-builder.module';
import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.module';
import { WorkspaceFixService } from './services/workspace-fix.service';
import { WorkspaceFixNullableService } from './services/workspace-fix-nullable.service';
@Module({ @Module({
imports: [ imports: [
@ -16,6 +21,8 @@ import { WorkspaceHealthService } from 'src/workspace/workspace-health/workspace
TypeORMModule, TypeORMModule,
ObjectMetadataModule, ObjectMetadataModule,
WorkspaceDataSourceModule, WorkspaceDataSourceModule,
WorkspaceMigrationRunnerModule,
WorkspaceMigrationBuilderModule,
], ],
providers: [ providers: [
WorkspaceHealthService, WorkspaceHealthService,
@ -23,6 +30,8 @@ import { WorkspaceHealthService } from 'src/workspace/workspace-health/workspace
ObjectMetadataHealthService, ObjectMetadataHealthService,
FieldMetadataHealthService, FieldMetadataHealthService,
RelationMetadataHealthService, RelationMetadataHealthService,
WorkspaceFixNullableService,
WorkspaceFixService,
], ],
exports: [WorkspaceHealthService], exports: [WorkspaceHealthService],
}) })

View File

@ -1,10 +1,14 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { WorkspaceHealthIssue } from 'src/workspace/workspace-health/interfaces/workspace-health-issue.interface'; import { WorkspaceHealthIssue } from 'src/workspace/workspace-health/interfaces/workspace-health-issue.interface';
import { import {
WorkspaceHealthMode, WorkspaceHealthMode,
WorkspaceHealthOptions, WorkspaceHealthOptions,
} from 'src/workspace/workspace-health/interfaces/workspace-health-options.interface'; } from 'src/workspace/workspace-health/interfaces/workspace-health-options.interface';
import { WorkspaceHealthFixKind } from 'src/workspace/workspace-health/interfaces/workspace-health-fix-kind.interface';
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/metadata/data-source/data-source.service'; import { DataSourceService } from 'src/metadata/data-source/data-source.service';
@ -15,10 +19,15 @@ import { FieldMetadataHealthService } from 'src/workspace/workspace-health/servi
import { RelationMetadataHealthService } from 'src/workspace/workspace-health/services/relation-metadata.health.service'; import { RelationMetadataHealthService } from 'src/workspace/workspace-health/services/relation-metadata.health.service';
import { DatabaseStructureService } from 'src/workspace/workspace-health/services/database-structure.service'; import { DatabaseStructureService } from 'src/workspace/workspace-health/services/database-structure.service';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util'; import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
import { WorkspaceFixService } from 'src/workspace/workspace-health/services/workspace-fix.service';
@Injectable() @Injectable()
export class WorkspaceHealthService { export class WorkspaceHealthService {
constructor( constructor(
@InjectDataSource('metadata')
private readonly metadataDataSource: DataSource,
private readonly dataSourceService: DataSourceService, private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService, private readonly typeORMService: TypeORMService,
private readonly objectMetadataService: ObjectMetadataService, private readonly objectMetadataService: ObjectMetadataService,
@ -27,6 +36,8 @@ export class WorkspaceHealthService {
private readonly objectMetadataHealthService: ObjectMetadataHealthService, private readonly objectMetadataHealthService: ObjectMetadataHealthService,
private readonly fieldMetadataHealthService: FieldMetadataHealthService, private readonly fieldMetadataHealthService: FieldMetadataHealthService,
private readonly relationMetadataHealthService: RelationMetadataHealthService, private readonly relationMetadataHealthService: RelationMetadataHealthService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceFixService: WorkspaceFixService,
) {} ) {}
async healthCheck( async healthCheck(
@ -106,4 +117,48 @@ export class WorkspaceHealthService {
return issues; return issues;
} }
async fixIssues(
workspaceId: string,
issues: WorkspaceHealthIssue[],
type: WorkspaceHealthFixKind,
): Promise<void> {
const queryRunner = this.metadataDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
const manager = queryRunner.manager;
try {
const workspaceMigrationRepository = manager.getRepository(
WorkspaceMigrationEntity,
);
const objectMetadataCollection =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
const workspaceMigrations = await this.workspaceFixService.fix(
manager,
objectMetadataCollection,
type,
issues,
);
// Save workspace migrations into the database
await workspaceMigrationRepository.save(workspaceMigrations);
// Commit the transaction
await queryRunner.commitTransaction();
// Apply pending migrations
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
workspaceId,
);
} catch (error) {
await queryRunner.rollbackTransaction();
console.error('Fix of issues failed with:', error);
} finally {
await queryRunner.release();
}
}
} }

View File

@ -5,6 +5,7 @@ import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metada
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module'; import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module'; import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/workspace-sync-metadata.module'; import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/workspace-sync-metadata.module';
import { WorkspaceHealthModule } from 'src/workspace/workspace-health/workspace-health.module';
import { WorkspaceManagerService } from './workspace-manager.service'; import { WorkspaceManagerService } from './workspace-manager.service';
@ -15,6 +16,7 @@ import { WorkspaceManagerService } from './workspace-manager.service';
ObjectMetadataModule, ObjectMetadataModule,
DataSourceModule, DataSourceModule,
WorkspaceSyncMetadataModule, WorkspaceSyncMetadataModule,
WorkspaceHealthModule,
], ],
exports: [WorkspaceManagerService], exports: [WorkspaceManagerService],
providers: [WorkspaceManagerService], providers: [WorkspaceManagerService],

View File

@ -0,0 +1,9 @@
import { WorkspaceMigrationObjectFactory } from './workspace-migration-object.factory';
import { WorkspaceMigrationFieldFactory } from './workspace-migration-field.factory';
import { WorkspaceMigrationRelationFactory } from './workspace-migration-relation.factory';
export const workspaceMigrationBuilderFactories = [
WorkspaceMigrationObjectFactory,
WorkspaceMigrationFieldFactory,
WorkspaceMigrationRelationFactory,
];

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationBuilderAction } from 'src/workspace/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { import {
FieldMetadataEntity, FieldMetadataEntity,
FieldMetadataType, FieldMetadataType,
@ -14,18 +16,38 @@ import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-tar
import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory'; import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory';
import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util'; import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util';
interface FieldMetadataUpdate {
current: FieldMetadataEntity;
altered: FieldMetadataEntity;
}
@Injectable() @Injectable()
export class FieldWorkspaceMigrationFactory { export class WorkspaceMigrationFieldFactory {
constructor( constructor(
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
) {} ) {}
async create( async create(
originalObjectMetadataCollection: ObjectMetadataEntity[], originalObjectMetadataCollection: ObjectMetadataEntity[],
createFieldMetadataCollection: FieldMetadataEntity[], fieldMetadataCollection: FieldMetadataEntity[],
deleteFieldMetadataCollection: FieldMetadataEntity[], action:
| WorkspaceMigrationBuilderAction.CREATE
| WorkspaceMigrationBuilderAction.DELETE,
): Promise<Partial<WorkspaceMigrationEntity>[]>;
async create(
originalObjectMetadataCollection: ObjectMetadataEntity[],
fieldMetadataUpdateCollection: FieldMetadataUpdate[],
action: WorkspaceMigrationBuilderAction.UPDATE,
): Promise<Partial<WorkspaceMigrationEntity>[]>;
async create(
originalObjectMetadataCollection: ObjectMetadataEntity[],
fieldMetadataCollectionOrFieldMetadataUpdateCollection:
| FieldMetadataEntity[]
| FieldMetadataUpdate[],
action: WorkspaceMigrationBuilderAction,
): Promise<Partial<WorkspaceMigrationEntity>[]> { ): Promise<Partial<WorkspaceMigrationEntity>[]> {
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
const originalObjectMetadataMap = originalObjectMetadataCollection.reduce( const originalObjectMetadataMap = originalObjectMetadataCollection.reduce(
(result, currentObject) => { (result, currentObject) => {
result[currentObject.id] = currentObject; result[currentObject.id] = currentObject;
@ -35,31 +57,25 @@ export class FieldWorkspaceMigrationFactory {
{} as Record<string, ObjectMetadataEntity>, {} as Record<string, ObjectMetadataEntity>,
); );
/** switch (action) {
* Create field migrations case WorkspaceMigrationBuilderAction.CREATE:
*/ return this.createFieldMigration(
if (createFieldMetadataCollection.length > 0) { originalObjectMetadataMap,
const createFieldWorkspaceMigrations = await this.createFieldMigration( fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataEntity[],
originalObjectMetadataMap, );
createFieldMetadataCollection, case WorkspaceMigrationBuilderAction.UPDATE:
); return this.updateFieldMigration(
originalObjectMetadataMap,
workspaceMigrations.push(...createFieldWorkspaceMigrations); fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataUpdate[],
);
case WorkspaceMigrationBuilderAction.DELETE:
return this.deleteFieldMigration(
originalObjectMetadataMap,
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataEntity[],
);
default:
return [];
} }
/**
* Delete field migrations
*/
if (deleteFieldMetadataCollection.length > 0) {
const deleteFieldWorkspaceMigrations = await this.deleteFieldMigration(
originalObjectMetadataMap,
deleteFieldMetadataCollection,
);
workspaceMigrations.push(...deleteFieldWorkspaceMigrations);
}
return workspaceMigrations;
} }
private async createFieldMigration( private async createFieldMigration(
@ -93,6 +109,42 @@ export class FieldWorkspaceMigrationFactory {
return workspaceMigrations; return workspaceMigrations;
} }
private async updateFieldMigration(
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
fieldMetadataUpdateCollection: FieldMetadataUpdate[],
): Promise<Partial<WorkspaceMigrationEntity>[]> {
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
for (const fieldMetadataUpdate of fieldMetadataUpdateCollection) {
const migrations: WorkspaceMigrationTableAction[] = [
{
name: computeObjectTargetTable(
originalObjectMetadataMap[
fieldMetadataUpdate.current.objectMetadataId
],
),
action: 'alter',
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.ALTER,
fieldMetadataUpdate.current,
fieldMetadataUpdate.altered,
),
},
];
workspaceMigrations.push({
workspaceId: fieldMetadataUpdate.current.workspaceId,
name: generateMigrationName(
`update-${fieldMetadataUpdate.altered.name}`,
),
isCustom: false,
migrations,
});
}
return workspaceMigrations;
}
private async deleteFieldMigration( private async deleteFieldMigration(
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>, originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
fieldMetadataCollection: FieldMetadataEntity[], fieldMetadataCollection: FieldMetadataEntity[],

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationBuilderAction } from 'src/workspace/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { import {
@ -12,40 +14,23 @@ import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/work
import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util'; import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util';
@Injectable() @Injectable()
export class ObjectWorkspaceMigrationFactory { export class WorkspaceMigrationObjectFactory {
constructor( constructor(
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
) {} ) {}
async create( async create(
createObjectMetadataCollection: ObjectMetadataEntity[], objectMetadataCollection: ObjectMetadataEntity[],
deleteObjectMetadataCollection: ObjectMetadataEntity[], action: WorkspaceMigrationBuilderAction,
): Promise<Partial<WorkspaceMigrationEntity>[]> { ): Promise<Partial<WorkspaceMigrationEntity>[]> {
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = []; switch (action) {
case WorkspaceMigrationBuilderAction.CREATE:
/** return this.createObjectMigration(objectMetadataCollection);
* Create object migrations case WorkspaceMigrationBuilderAction.DELETE:
*/ return this.deleteObjectMigration(objectMetadataCollection);
if (createObjectMetadataCollection.length > 0) { default:
const createObjectWorkspaceMigrations = await this.createObjectMigration( return [];
createObjectMetadataCollection,
);
workspaceMigrations.push(...createObjectWorkspaceMigrations);
} }
/**
* Delete object migrations
*/
if (deleteObjectMetadataCollection.length > 0) {
const deleteObjectWorkspaceMigrations = await this.deleteObjectMigration(
deleteObjectMetadataCollection,
);
workspaceMigrations.push(...deleteObjectWorkspaceMigrations);
}
return workspaceMigrations;
} }
private async createObjectMigration( private async createObjectMigration(

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationBuilderAction } from 'src/workspace/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { import {
WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnActionType,
@ -15,7 +17,7 @@ import { camelCase } from 'src/utils/camel-case';
import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util'; import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util';
@Injectable() @Injectable()
export class RelationWorkspaceMigrationFactory { export class WorkspaceMigrationRelationFactory {
constructor() {} constructor() {}
/** /**
@ -23,9 +25,9 @@ export class RelationWorkspaceMigrationFactory {
*/ */
async create( async create(
originalObjectMetadataCollection: ObjectMetadataEntity[], originalObjectMetadataCollection: ObjectMetadataEntity[],
createRelationMetadataCollection: RelationMetadataEntity[], relationMetadataCollection: RelationMetadataEntity[],
action: WorkspaceMigrationBuilderAction,
): Promise<Partial<WorkspaceMigrationEntity>[]> { ): Promise<Partial<WorkspaceMigrationEntity>[]> {
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
const originalObjectMetadataMap = originalObjectMetadataCollection.reduce( const originalObjectMetadataMap = originalObjectMetadataCollection.reduce(
(result, currentObject) => { (result, currentObject) => {
result[currentObject.id] = currentObject; result[currentObject.id] = currentObject;
@ -35,17 +37,15 @@ export class RelationWorkspaceMigrationFactory {
{} as Record<string, ObjectMetadataEntity>, {} as Record<string, ObjectMetadataEntity>,
); );
if (createRelationMetadataCollection.length > 0) { switch (action) {
const createRelationWorkspaceMigrations = case WorkspaceMigrationBuilderAction.CREATE:
await this.createRelationMigration( return this.createRelationMigration(
originalObjectMetadataMap, originalObjectMetadataMap,
createRelationMetadataCollection, relationMetadataCollection,
); );
default:
workspaceMigrations.push(...createRelationWorkspaceMigrations); return [];
} }
return workspaceMigrations;
} }
private async createRelationMigration( private async createRelationMigration(

View File

@ -0,0 +1,5 @@
export enum WorkspaceMigrationBuilderAction {
CREATE = 'create',
UPDATE = 'update',
DELETE = 'delete',
}

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
import { workspaceMigrationBuilderFactories } from './factories';
@Module({
imports: [WorkspaceMigrationModule],
providers: [...workspaceMigrationBuilderFactories],
exports: [...workspaceMigrationBuilderFactories],
})
export class WorkspaceMigrationBuilderModule {}

View File

@ -1,15 +1,9 @@
import { FeatureFlagFactory } from './feature-flags.factory'; import { FeatureFlagFactory } from './feature-flags.factory';
import { StandardObjectFactory } from './standard-object.factory'; import { StandardObjectFactory } from './standard-object.factory';
import { StandardRelationFactory } from './standard-relation.factory'; import { StandardRelationFactory } from './standard-relation.factory';
import { ObjectWorkspaceMigrationFactory } from './object-workspace-migration.factory';
import { FieldWorkspaceMigrationFactory } from './field-workspace-migration.factory';
import { RelationWorkspaceMigrationFactory } from './relation-workspace-migration.factory';
export const workspaceSyncMetadataFactories = [ export const workspaceSyncMetadataFactories = [
FeatureFlagFactory, FeatureFlagFactory,
StandardObjectFactory, StandardObjectFactory,
StandardRelationFactory, StandardRelationFactory,
ObjectWorkspaceMigrationFactory,
FieldWorkspaceMigrationFactory,
RelationWorkspaceMigrationFactory,
]; ];

View File

@ -5,6 +5,7 @@ import { EntityManager } from 'typeorm';
import { WorkspaceSyncContext } from 'src/workspace/workspace-sync-metadata/interfaces/workspace-sync-context.interface'; import { WorkspaceSyncContext } from 'src/workspace/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { ComparatorAction } from 'src/workspace/workspace-sync-metadata/interfaces/comparator.interface'; import { ComparatorAction } from 'src/workspace/workspace-sync-metadata/interfaces/comparator.interface';
import { FeatureFlagMap } from 'src/core/feature-flag/interfaces/feature-flag-map.interface'; import { FeatureFlagMap } from 'src/core/feature-flag/interfaces/feature-flag-map.interface';
import { WorkspaceMigrationBuilderAction } from 'src/workspace/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { mapObjectMetadataByUniqueIdentifier } from 'src/workspace/workspace-sync-metadata/utils/sync-metadata.util'; import { mapObjectMetadataByUniqueIdentifier } from 'src/workspace/workspace-sync-metadata/utils/sync-metadata.util';
@ -14,8 +15,8 @@ import { WorkspaceObjectComparator } from 'src/workspace/workspace-sync-metadata
import { WorkspaceFieldComparator } from 'src/workspace/workspace-sync-metadata/comparators/workspace-field.comparator'; import { WorkspaceFieldComparator } from 'src/workspace/workspace-sync-metadata/comparators/workspace-field.comparator';
import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service'; import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service';
import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage'; import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
import { ObjectWorkspaceMigrationFactory } from 'src/workspace/workspace-sync-metadata/factories/object-workspace-migration.factory'; import { WorkspaceMigrationObjectFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-object.factory';
import { FieldWorkspaceMigrationFactory } from 'src/workspace/workspace-sync-metadata/factories/field-workspace-migration.factory'; import { WorkspaceMigrationFieldFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-field.factory';
@Injectable() @Injectable()
export class WorkspaceSyncObjectMetadataService { export class WorkspaceSyncObjectMetadataService {
@ -26,8 +27,8 @@ export class WorkspaceSyncObjectMetadataService {
private readonly workspaceObjectComparator: WorkspaceObjectComparator, private readonly workspaceObjectComparator: WorkspaceObjectComparator,
private readonly workspaceFieldComparator: WorkspaceFieldComparator, private readonly workspaceFieldComparator: WorkspaceFieldComparator,
private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService, private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService,
private readonly objectWorkspaceMigrationFactory: ObjectWorkspaceMigrationFactory, private readonly workspaceMigrationObjectFactory: WorkspaceMigrationObjectFactory,
private readonly fieldWorkspaceMigrationFactory: FieldWorkspaceMigrationFactory, private readonly workspaceMigrationFieldFactory: WorkspaceMigrationFieldFactory,
) {} ) {}
async synchronize( async synchronize(
@ -140,21 +141,39 @@ export class WorkspaceSyncObjectMetadataService {
this.logger.log('Generating migrations'); this.logger.log('Generating migrations');
// Create migrations // Create migrations
const objectWorkspaceMigrations = const createObjectWorkspaceMigrations =
await this.objectWorkspaceMigrationFactory.create( await this.workspaceMigrationObjectFactory.create(
metadataObjectUpdaterResult.createdObjectMetadataCollection, metadataObjectUpdaterResult.createdObjectMetadataCollection,
storage.objectMetadataDeleteCollection, WorkspaceMigrationBuilderAction.CREATE,
); );
const fieldWorkspaceMigrations = const deleteObjectWorkspaceMigrations =
await this.fieldWorkspaceMigrationFactory.create( await this.workspaceMigrationObjectFactory.create(
storage.objectMetadataDeleteCollection,
WorkspaceMigrationBuilderAction.DELETE,
);
const createFieldWorkspaceMigrations =
await this.workspaceMigrationFieldFactory.create(
originalObjectMetadataCollection, originalObjectMetadataCollection,
metadataFieldUpdaterResult.createdFieldMetadataCollection, metadataFieldUpdaterResult.createdFieldMetadataCollection,
WorkspaceMigrationBuilderAction.CREATE,
);
const deleteFieldWorkspaceMigrations =
await this.workspaceMigrationFieldFactory.create(
originalObjectMetadataCollection,
storage.fieldMetadataDeleteCollection, storage.fieldMetadataDeleteCollection,
WorkspaceMigrationBuilderAction.DELETE,
); );
this.logger.log('Saving migrations'); this.logger.log('Saving migrations');
return [...objectWorkspaceMigrations, ...fieldWorkspaceMigrations]; return [
...createObjectWorkspaceMigrations,
...deleteObjectWorkspaceMigrations,
...createFieldWorkspaceMigrations,
...deleteFieldWorkspaceMigrations,
];
} }
} }

View File

@ -5,6 +5,7 @@ import { EntityManager } from 'typeorm';
import { WorkspaceSyncContext } from 'src/workspace/workspace-sync-metadata/interfaces/workspace-sync-context.interface'; import { WorkspaceSyncContext } from 'src/workspace/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { FeatureFlagMap } from 'src/core/feature-flag/interfaces/feature-flag-map.interface'; import { FeatureFlagMap } from 'src/core/feature-flag/interfaces/feature-flag-map.interface';
import { ComparatorAction } from 'src/workspace/workspace-sync-metadata/interfaces/comparator.interface'; import { ComparatorAction } from 'src/workspace/workspace-sync-metadata/interfaces/comparator.interface';
import { WorkspaceMigrationBuilderAction } from 'src/workspace/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity'; import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity';
@ -14,7 +15,7 @@ import { WorkspaceRelationComparator } from 'src/workspace/workspace-sync-metada
import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service'; import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service';
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity'; import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage'; import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
import { RelationWorkspaceMigrationFactory } from 'src/workspace/workspace-sync-metadata/factories/relation-workspace-migration.factory'; import { WorkspaceMigrationRelationFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-relation.factory';
@Injectable() @Injectable()
export class WorkspaceSyncRelationMetadataService { export class WorkspaceSyncRelationMetadataService {
@ -26,7 +27,7 @@ export class WorkspaceSyncRelationMetadataService {
private readonly standardRelationFactory: StandardRelationFactory, private readonly standardRelationFactory: StandardRelationFactory,
private readonly workspaceRelationComparator: WorkspaceRelationComparator, private readonly workspaceRelationComparator: WorkspaceRelationComparator,
private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService, private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService,
private readonly relationWorkspaceMigrationFactory: RelationWorkspaceMigrationFactory, private readonly workspaceMigrationRelationFactory: WorkspaceMigrationRelationFactory,
) {} ) {}
async synchronize( async synchronize(
@ -95,12 +96,13 @@ export class WorkspaceSyncRelationMetadataService {
); );
// Create migrations // Create migrations
const workspaceRelationMigrations = const createRelationWorkspaceMigrations =
await this.relationWorkspaceMigrationFactory.create( await this.workspaceMigrationRelationFactory.create(
originalObjectMetadataCollection, originalObjectMetadataCollection,
metadataRelationUpdaterResult.createdRelationMetadataCollection, metadataRelationUpdaterResult.createdRelationMetadataCollection,
WorkspaceMigrationBuilderAction.CREATE,
); );
return workspaceRelationMigrations; return createRelationWorkspaceMigrations;
} }
} }

View File

@ -6,7 +6,6 @@ import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity'; import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity'; import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.module'; import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.module';
import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metadata/workspace-sync-metadata.service'; import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metadata/workspace-sync-metadata.service';
import { workspaceSyncMetadataFactories } from 'src/workspace/workspace-sync-metadata/factories'; import { workspaceSyncMetadataFactories } from 'src/workspace/workspace-sync-metadata/factories';
@ -15,10 +14,11 @@ import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-me
import { WorkspaceSyncObjectMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service'; import { WorkspaceSyncObjectMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service';
import { WorkspaceSyncRelationMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service'; import { WorkspaceSyncRelationMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service';
import { WorkspaceLogsService } from 'src/workspace/workspace-sync-metadata/services/workspace-logs.service'; import { WorkspaceLogsService } from 'src/workspace/workspace-sync-metadata/services/workspace-logs.service';
import { WorkspaceMigrationBuilderModule } from 'src/workspace/workspace-migration-builder/workspace-migration-builder.module';
@Module({ @Module({
imports: [ imports: [
WorkspaceMigrationModule, WorkspaceMigrationBuilderModule,
WorkspaceMigrationRunnerModule, WorkspaceMigrationRunnerModule,
TypeOrmModule.forFeature( TypeOrmModule.forFeature(
[ [