mirror of
https://github.com/twentyhq/twenty.git
synced 2024-09-11 15:35:36 +03:00
feat: workspace:health nullable fix (#3882)
This commit is contained in:
parent
2ba9a209e8
commit
d3fe1b9e31
3
packages/twenty-server/.gitignore
vendored
3
packages/twenty-server/.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
dist/*
|
dist/*
|
||||||
.local-storage
|
.local-storage
|
||||||
|
logs/**/*
|
||||||
|
38
packages/twenty-server/src/commands/command-logger.ts
Normal file
38
packages/twenty-server/src/commands/command-logger.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
|
||||||
|
import { kebabCase } from 'src/utils/kebab-case';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CommandLogger {
|
||||||
|
constructor(private readonly className: string) {}
|
||||||
|
|
||||||
|
async writeLog(
|
||||||
|
fileName: string,
|
||||||
|
data: unknown,
|
||||||
|
append: boolean = false,
|
||||||
|
): Promise<void> {
|
||||||
|
const path = `./logs/${kebabCase(this.className)}`;
|
||||||
|
|
||||||
|
if (existsSync(path) === false) {
|
||||||
|
await fs.mkdir(path, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.writeFile(
|
||||||
|
`${path}/${fileName}.json`,
|
||||||
|
JSON.stringify(data, null, 2),
|
||||||
|
{
|
||||||
|
flag: append ? 'a' : 'w',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
`Error writing to file ${path}/${fileName}.json: ${err?.message}`,
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 { 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';
|
||||||
|
import { CommandLogger } from 'src/commands/command-logger';
|
||||||
|
|
||||||
interface WorkspaceHealthCommandOptions {
|
interface WorkspaceHealthCommandOptions {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
verbose?: boolean;
|
verbose?: boolean;
|
||||||
mode?: WorkspaceHealthMode;
|
mode?: WorkspaceHealthMode;
|
||||||
fix?: WorkspaceHealthFixKind;
|
fix?: WorkspaceHealthFixKind;
|
||||||
|
dryRun?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command({
|
@Command({
|
||||||
@ -18,6 +20,10 @@ interface WorkspaceHealthCommandOptions {
|
|||||||
description: 'Check health of the given workspace.',
|
description: 'Check health of the given workspace.',
|
||||||
})
|
})
|
||||||
export class WorkspaceHealthCommand extends CommandRunner {
|
export class WorkspaceHealthCommand extends CommandRunner {
|
||||||
|
private readonly commandLogger = new CommandLogger(
|
||||||
|
WorkspaceHealthCommand.name,
|
||||||
|
);
|
||||||
|
|
||||||
constructor(private readonly workspaceHealthService: WorkspaceHealthService) {
|
constructor(private readonly workspaceHealthService: WorkspaceHealthService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -48,11 +54,29 @@ export class WorkspaceHealthCommand extends CommandRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.fix) {
|
if (options.fix) {
|
||||||
await this.workspaceHealthService.fixIssues(
|
console.log(chalk.yellow('Fixing issues'));
|
||||||
|
|
||||||
|
const workspaceMigrations = await this.workspaceHealthService.fixIssues(
|
||||||
options.workspaceId,
|
options.workspaceId,
|
||||||
issues,
|
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;
|
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',
|
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_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_FOREIGN_KEY_NOT_VALID = 'RELATION_FOREIGN_KEY_NOT_VALID',
|
||||||
RELATION_NULLABILITY_CONFLICT = 'RELATION_NULLABILITY_CONFLICT',
|
|
||||||
RELATION_FOREIGN_KEY_CONFLICT = 'RELATION_FOREIGN_KEY_CONFLICT',
|
RELATION_FOREIGN_KEY_CONFLICT = 'RELATION_FOREIGN_KEY_CONFLICT',
|
||||||
RELATION_TYPE_NOT_VALID = 'RELATION_TYPE_NOT_VALID',
|
RELATION_TYPE_NOT_VALID = 'RELATION_TYPE_NOT_VALID',
|
||||||
}
|
}
|
||||||
@ -83,7 +82,6 @@ export interface WorkspaceHealthRelationIssue<
|
|||||||
T,
|
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_FOREIGN_KEY_CONFLICT
|
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_CONFLICT
|
||||||
| WorkspaceHealthIssueType.RELATION_TYPE_NOT_VALID
|
| 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 (
|
if (
|
||||||
relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE &&
|
relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE &&
|
||||||
!relationColumn.isUnique
|
!relationColumn.isUnique
|
||||||
|
@ -5,29 +5,69 @@ import { EntityManager } from 'typeorm';
|
|||||||
import {
|
import {
|
||||||
WorkspaceHealthColumnIssue,
|
WorkspaceHealthColumnIssue,
|
||||||
WorkspaceHealthIssueType,
|
WorkspaceHealthIssueType,
|
||||||
WorkspaceHealthRelationIssue,
|
|
||||||
} from 'src/workspace/workspace-health/interfaces/workspace-health-issue.interface';
|
} 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 { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||||
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.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 =
|
type WorkspaceHealthNullableIssue =
|
||||||
| WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT>
|
WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT>;
|
||||||
| WorkspaceHealthRelationIssue<WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT>;
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceFixNullableService {
|
export class WorkspaceFixNullableService {
|
||||||
constructor() {}
|
constructor(
|
||||||
|
private readonly workspaceMigrationFieldFactory: WorkspaceMigrationFieldFactory,
|
||||||
|
) {}
|
||||||
|
|
||||||
async fix(
|
async fix(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
manager: EntityManager,
|
manager: EntityManager,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
objectMetadataCollection: ObjectMetadataEntity[],
|
objectMetadataCollection: ObjectMetadataEntity[],
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
issues: WorkspaceHealthNullableIssue[],
|
issues: WorkspaceHealthNullableIssue[],
|
||||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
// TODO: Implement nullable fix
|
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||||
return [];
|
|
||||||
|
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 = (
|
export const isWorkspaceHealthNullableIssue = (
|
||||||
type: WorkspaceHealthIssueType,
|
type: WorkspaceHealthIssueType,
|
||||||
): type is
|
): type is WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT => {
|
||||||
| WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT
|
return type === WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT;
|
||||||
| WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT => {
|
|
||||||
return type === WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT ||
|
|
||||||
type === WorkspaceHealthIssueType.RELATION_NULLABILITY_CONFLICT
|
|
||||||
? true
|
|
||||||
: false;
|
|
||||||
};
|
};
|
||||||
|
@ -121,8 +121,16 @@ export class WorkspaceHealthService {
|
|||||||
async fixIssues(
|
async fixIssues(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
issues: WorkspaceHealthIssue[],
|
issues: WorkspaceHealthIssue[],
|
||||||
type: WorkspaceHealthFixKind,
|
options: {
|
||||||
): Promise<void> {
|
type: WorkspaceHealthFixKind;
|
||||||
|
applyChanges?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
|
let workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||||
|
|
||||||
|
// Set default options
|
||||||
|
options.applyChanges ??= true;
|
||||||
|
|
||||||
const queryRunner = this.metadataDataSource.createQueryRunner();
|
const queryRunner = this.metadataDataSource.createQueryRunner();
|
||||||
|
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
@ -137,16 +145,25 @@ export class WorkspaceHealthService {
|
|||||||
const objectMetadataCollection =
|
const objectMetadataCollection =
|
||||||
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
||||||
|
|
||||||
const workspaceMigrations = await this.workspaceFixService.fix(
|
workspaceMigrations = await this.workspaceFixService.fix(
|
||||||
manager,
|
manager,
|
||||||
objectMetadataCollection,
|
objectMetadataCollection,
|
||||||
type,
|
options.type,
|
||||||
issues,
|
issues,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save workspace migrations into the database
|
// Save workspace migrations into the database
|
||||||
await workspaceMigrationRepository.save(workspaceMigrations);
|
await workspaceMigrationRepository.save(workspaceMigrations);
|
||||||
|
|
||||||
|
if (!options.applyChanges) {
|
||||||
|
// Rollback transactions
|
||||||
|
await queryRunner.rollbackTransaction();
|
||||||
|
|
||||||
|
await queryRunner.release();
|
||||||
|
|
||||||
|
return workspaceMigrations;
|
||||||
|
}
|
||||||
|
|
||||||
// Commit the transaction
|
// Commit the transaction
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
@ -160,5 +177,7 @@ export class WorkspaceHealthService {
|
|||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release();
|
await queryRunner.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return workspaceMigrations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ export class WorkspaceMigrationRunnerService {
|
|||||||
|
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Error executing migration', error);
|
||||||
await queryRunner.rollbackTransaction();
|
await queryRunner.rollbackTransaction();
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||||
|
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||||
|
import { CommandLogger } from 'src/commands/command-logger';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SyncWorkspaceLoggerService {
|
||||||
|
private readonly commandLogger = new CommandLogger(
|
||||||
|
SyncWorkspaceLoggerService.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
async saveLogs(
|
||||||
|
storage: WorkspaceSyncStorage,
|
||||||
|
workspaceMigrations: WorkspaceMigrationEntity[],
|
||||||
|
) {
|
||||||
|
// Save workspace migrations
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'workspace-migrations',
|
||||||
|
workspaceMigrations,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save object metadata create collection
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'object-metadata-create-collection',
|
||||||
|
storage.objectMetadataCreateCollection,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save object metadata update collection
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'object-metadata-update-collection',
|
||||||
|
storage.objectMetadataUpdateCollection,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save object metadata delete collection
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'object-metadata-delete-collection',
|
||||||
|
storage.objectMetadataDeleteCollection,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save field metadata create collection
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'field-metadata-create-collection',
|
||||||
|
storage.fieldMetadataCreateCollection,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save field metadata update collection
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'field-metadata-update-collection',
|
||||||
|
storage.fieldMetadataUpdateCollection,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save field metadata delete collection
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'field-metadata-delete-collection',
|
||||||
|
storage.fieldMetadataDeleteCollection,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save relation metadata create collection
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'relation-metadata-create-collection',
|
||||||
|
storage.relationMetadataCreateCollection,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save relation metadata delete collection
|
||||||
|
await this.commandLogger.writeLog(
|
||||||
|
'relation-metadata-delete-collection',
|
||||||
|
storage.relationMetadataDeleteCollection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ import { Command, CommandRunner, Option } from 'nest-commander';
|
|||||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||||
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 { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service';
|
||||||
|
|
||||||
// TODO: implement dry-run
|
// TODO: implement dry-run
|
||||||
interface RunWorkspaceMigrationsOptions {
|
interface RunWorkspaceMigrationsOptions {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -17,6 +19,7 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly dataSourceService: DataSourceService,
|
||||||
|
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -31,13 +34,21 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner {
|
|||||||
options.workspaceId,
|
options.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.workspaceSyncMetadataService.syncStandardObjectsAndFieldsMetadata(
|
const { storage, workspaceMigrations } =
|
||||||
{
|
await this.workspaceSyncMetadataService.syncStandardObjectsAndFieldsMetadata(
|
||||||
workspaceId: options.workspaceId,
|
{
|
||||||
dataSourceId: dataSourceMetadata.id,
|
workspaceId: options.workspaceId,
|
||||||
},
|
dataSourceId: dataSourceMetadata.id,
|
||||||
{ dryRun: options.dryRun },
|
},
|
||||||
);
|
{ applyChanges: !options.dryRun },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.dryRun) {
|
||||||
|
await this.syncWorkspaceLoggerService.saveLogs(
|
||||||
|
storage,
|
||||||
|
workspaceMigrations,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Option({
|
@Option({
|
||||||
|
@ -5,8 +5,10 @@ import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metada
|
|||||||
|
|
||||||
import { SyncWorkspaceMetadataCommand } from './sync-workspace-metadata.command';
|
import { SyncWorkspaceMetadataCommand } from './sync-workspace-metadata.command';
|
||||||
|
|
||||||
|
import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [WorkspaceSyncMetadataModule, DataSourceModule],
|
imports: [WorkspaceSyncMetadataModule, DataSourceModule],
|
||||||
providers: [SyncWorkspaceMetadataCommand],
|
providers: [SyncWorkspaceMetadataCommand, SyncWorkspaceLoggerService],
|
||||||
})
|
})
|
||||||
export class WorkspaceSyncMetadataCommandsModule {}
|
export class WorkspaceSyncMetadataCommandsModule {}
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { existsSync } from 'fs';
|
|
||||||
import fs from 'fs/promises';
|
|
||||||
|
|
||||||
import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
|
|
||||||
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class WorkspaceLogsService {
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
async saveLogs(
|
|
||||||
storage: WorkspaceSyncStorage,
|
|
||||||
workspaceMigrations: WorkspaceMigrationEntity[],
|
|
||||||
) {
|
|
||||||
// Check if `logs` folder exists
|
|
||||||
if (existsSync('./logs') === false) {
|
|
||||||
await fs.mkdir('./logs', { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save workspace migrations
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/workspace-migrations.json',
|
|
||||||
JSON.stringify(workspaceMigrations, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save object metadata create collection
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/object-metadata-create-collection.json',
|
|
||||||
JSON.stringify(storage.objectMetadataCreateCollection, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save object metadata update collection
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/object-metadata-update-collection.json',
|
|
||||||
JSON.stringify(storage.objectMetadataUpdateCollection, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save object metadata delete collection
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/object-metadata-delete-collection.json',
|
|
||||||
JSON.stringify(storage.objectMetadataDeleteCollection, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save field metadata create collection
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/field-metadata-create-collection.json',
|
|
||||||
JSON.stringify(storage.fieldMetadataCreateCollection, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save field metadata update collection
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/field-metadata-update-collection.json',
|
|
||||||
JSON.stringify(storage.fieldMetadataUpdateCollection, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save field metadata delete collection
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/field-metadata-delete-collection.json',
|
|
||||||
JSON.stringify(storage.fieldMetadataDeleteCollection, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save relation metadata create collection
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/relation-metadata-create-collection.json',
|
|
||||||
JSON.stringify(storage.relationMetadataCreateCollection, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save relation metadata delete collection
|
|
||||||
await fs.writeFile(
|
|
||||||
'./logs/relation-metadata-delete-collection.json',
|
|
||||||
JSON.stringify(storage.relationMetadataDeleteCollection, null, 2),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,7 +13,6 @@ import { workspaceSyncMetadataComparators } from 'src/workspace/workspace-sync-m
|
|||||||
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 { 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 { WorkspaceMigrationBuilderModule } from 'src/workspace/workspace-migration-builder/workspace-migration-builder.module';
|
import { WorkspaceMigrationBuilderModule } from 'src/workspace/workspace-migration-builder/workspace-migration-builder.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@ -38,7 +37,6 @@ import { WorkspaceMigrationBuilderModule } from 'src/workspace/workspace-migrati
|
|||||||
WorkspaceSyncObjectMetadataService,
|
WorkspaceSyncObjectMetadataService,
|
||||||
WorkspaceSyncRelationMetadataService,
|
WorkspaceSyncRelationMetadataService,
|
||||||
WorkspaceSyncMetadataService,
|
WorkspaceSyncMetadataService,
|
||||||
WorkspaceLogsService,
|
|
||||||
],
|
],
|
||||||
exports: [WorkspaceSyncMetadataService],
|
exports: [WorkspaceSyncMetadataService],
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,6 @@ import { FeatureFlagFactory } from 'src/workspace/workspace-sync-metadata/factor
|
|||||||
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 { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
|
import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||||
import { WorkspaceLogsService } from 'src/workspace/workspace-sync-metadata/services/workspace-logs.service';
|
|
||||||
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -24,7 +23,6 @@ export class WorkspaceSyncMetadataService {
|
|||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
private readonly workspaceSyncObjectMetadataService: WorkspaceSyncObjectMetadataService,
|
private readonly workspaceSyncObjectMetadataService: WorkspaceSyncObjectMetadataService,
|
||||||
private readonly workspaceSyncRelationMetadataService: WorkspaceSyncRelationMetadataService,
|
private readonly workspaceSyncRelationMetadataService: WorkspaceSyncRelationMetadataService,
|
||||||
private readonly workspaceLogsService: WorkspaceLogsService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,18 +35,23 @@ export class WorkspaceSyncMetadataService {
|
|||||||
*/
|
*/
|
||||||
public async syncStandardObjectsAndFieldsMetadata(
|
public async syncStandardObjectsAndFieldsMetadata(
|
||||||
context: WorkspaceSyncContext,
|
context: WorkspaceSyncContext,
|
||||||
options?: { dryRun?: boolean },
|
options: { applyChanges?: boolean } = { applyChanges: true },
|
||||||
) {
|
): Promise<{
|
||||||
this.logger.log('Syncing standard objects and fields metadata');
|
workspaceMigrations: WorkspaceMigrationEntity[];
|
||||||
|
storage: WorkspaceSyncStorage;
|
||||||
|
}> {
|
||||||
|
let workspaceMigrations: WorkspaceMigrationEntity[] = [];
|
||||||
|
const storage = new WorkspaceSyncStorage();
|
||||||
const queryRunner = this.metadataDataSource.createQueryRunner();
|
const queryRunner = this.metadataDataSource.createQueryRunner();
|
||||||
|
|
||||||
|
this.logger.log('Syncing standard objects and fields metadata');
|
||||||
|
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
const manager = queryRunner.manager;
|
const manager = queryRunner.manager;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storage = new WorkspaceSyncStorage();
|
|
||||||
const workspaceMigrationRepository = manager.getRepository(
|
const workspaceMigrationRepository = manager.getRepository(
|
||||||
WorkspaceMigrationEntity,
|
WorkspaceMigrationEntity,
|
||||||
);
|
);
|
||||||
@ -76,20 +79,23 @@ export class WorkspaceSyncMetadataService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Save workspace migrations into the database
|
// Save workspace migrations into the database
|
||||||
const workspaceMigrations = await workspaceMigrationRepository.save([
|
workspaceMigrations = await workspaceMigrationRepository.save([
|
||||||
...workspaceObjectMigrations,
|
...workspaceObjectMigrations,
|
||||||
...workspaceRelationMigrations,
|
...workspaceRelationMigrations,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// If we're running a dry run, rollback the transaction and do not execute migrations
|
// If we're running a dry run, rollback the transaction and do not execute migrations
|
||||||
if (options?.dryRun) {
|
if (!options.applyChanges) {
|
||||||
this.logger.log('Running in dry run mode, rolling back transaction');
|
this.logger.log('Running in dry run mode, rolling back transaction');
|
||||||
|
|
||||||
await queryRunner.rollbackTransaction();
|
await queryRunner.rollbackTransaction();
|
||||||
|
|
||||||
await this.workspaceLogsService.saveLogs(storage, workspaceMigrations);
|
await queryRunner.release();
|
||||||
|
|
||||||
return;
|
return {
|
||||||
|
workspaceMigrations,
|
||||||
|
storage,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
@ -104,5 +110,10 @@ export class WorkspaceSyncMetadataService {
|
|||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release();
|
await queryRunner.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
workspaceMigrations,
|
||||||
|
storage,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user