Refactor schema migration runner service discovery (#13988)
## Context
This refactoring improves scalability in the workspace migration runner
in terms of actions. Instead of relying on a switch case and multiple
changes and services + duplicate things between metadata and workspace
schema, we now have a unique module for all kind of actions and a
registry pattern to register them and run them dynamically.
Example on how to add a new "create_role" action with the future design:
```typescript
// packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/role/services/create-role-action-handler.service.ts
export class CreateRoleActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_role',
) {
constructor(
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
private readonly roleRepository: Repository<Role>,
) {}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateRoleAction>,
): Promise<void> {
this.roleRepository.save({...}); // technically could use the queryRunner from context param directly instead of injecting the repository
}
async executeForWorkspaceSchema(
context: WorkspaceMigrationActionRunnerArgs<CreateRoleAction>,
): Promise<void> {
// this.workspaceSchemaManagerService.tableManager... -> Nothing to do in the workspace schema in the role case
}
}
```
```typescript
// packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts
@Module({
imports: [WorkspaceSchemaManagerModule, +TypeOrmModule.forFeature([Role]),
providers: [
...
+RoleCreateActionService
],
})
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
```
```typescript
export type WorkspaceMigrationAction=
| WorkspaceMigrationObjectAction
...
+| WorkspaceMigrationRoleAction;
export type CreateRoleAction = {
type = 'create_role',
role: RoleEntity
};
export type WorkspaceMigrationRoleAction = CreateRoleAction;
```
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@ import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadat
|
||||
import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util';
|
||||
import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
export class FieldMetadataServiceV2 {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ import {
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataServiceV2 {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
||||
WorkspaceMigrationBuildArgs,
|
||||
WorkspaceMigrationBuilderV2Service,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
|
||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
|
||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service';
|
||||
import {
|
||||
WorkspaceMigrationV2Exception,
|
||||
WorkspaceMigrationV2ExceptionCode,
|
||||
+5
@@ -8,3 +8,8 @@ export type WorkspaceMigrationActionV2 =
|
||||
| WorkspaceMigrationIndexActionV2;
|
||||
|
||||
export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type'];
|
||||
|
||||
export type ExtractAction<T extends WorkspaceMigrationActionTypeV2> = Extract<
|
||||
WorkspaceMigrationActionV2,
|
||||
{ type: T }
|
||||
>;
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util';
|
||||
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||
import {
|
||||
collectEnumOperationsForField,
|
||||
EnumOperation,
|
||||
executeBatchEnumOperations,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||
|
||||
@Injectable()
|
||||
export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'create_field',
|
||||
) {
|
||||
constructor(
|
||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner } = context;
|
||||
const fieldMetadataRepository =
|
||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
const { flatFieldMetadata } = action;
|
||||
|
||||
await fieldMetadataRepository.save(flatFieldMetadata);
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
context: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||
context;
|
||||
const { flatFieldMetadata } = action;
|
||||
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId: flatFieldMetadata.objectMetadataId,
|
||||
});
|
||||
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId,
|
||||
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||
});
|
||||
|
||||
const enumOperations = collectEnumOperationsForField({
|
||||
flatFieldMetadata: flatFieldMetadata,
|
||||
tableName,
|
||||
operation: EnumOperation.CREATE,
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
|
||||
const columnDefinitions = generateColumnDefinitions({
|
||||
flatFieldMetadata: flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithFlatFieldMaps,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.columnManager.addColumns({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnDefinitions,
|
||||
});
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { type DeleteFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util';
|
||||
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||
import {
|
||||
collectEnumOperationsForField,
|
||||
EnumOperation,
|
||||
executeBatchEnumOperations,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'delete_field',
|
||||
) {
|
||||
constructor(
|
||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>,
|
||||
) {
|
||||
const { action, queryRunner } = context;
|
||||
const fieldMetadataRepository =
|
||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
const { fieldMetadataId } = action;
|
||||
|
||||
await fieldMetadataRepository.delete({
|
||||
id: In([fieldMetadataId]),
|
||||
});
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
context: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>,
|
||||
) {
|
||||
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||
context;
|
||||
const { objectMetadataId, fieldMetadataId } = action;
|
||||
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const fieldMetadata = findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
fieldMetadataId,
|
||||
});
|
||||
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId,
|
||||
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||
});
|
||||
|
||||
const flatObjectMetadata =
|
||||
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const columnDefinitions = generateColumnDefinitions({
|
||||
flatFieldMetadata: fieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
||||
});
|
||||
const columnNamesToDrop = columnDefinitions.map((def) => def.name);
|
||||
|
||||
await this.workspaceSchemaManagerService.columnManager.dropColumns({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnNames: columnNamesToDrop,
|
||||
});
|
||||
|
||||
const enumOperations = collectEnumOperationsForField({
|
||||
flatFieldMetadata: fieldMetadata,
|
||||
tableName,
|
||||
operation: EnumOperation.DROP,
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
}
|
||||
}
|
||||
+70
-138
@@ -1,16 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type FromTo } from 'twenty-shared/types';
|
||||
import { type EnumFieldMetadataType, type FromTo } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { type FieldMetadataDefaultValueForAnyType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import {
|
||||
FieldMetadataDefaultOption,
|
||||
type FieldMetadataComplexOption,
|
||||
type FieldMetadataDefaultOption,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { EnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/enum-field-metadata-type.type';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { getCompositeTypeOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/get-composite-type-or-throw.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
@@ -19,151 +20,84 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m
|
||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { isRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-relation-flat-field-metadata.util';
|
||||
import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
||||
import {
|
||||
type CreateFieldAction,
|
||||
type DeleteFieldAction,
|
||||
type UpdateFieldAction,
|
||||
type WorkspaceMigrationFieldActionTypeV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
||||
WorkspaceMigrationRunnerException,
|
||||
WorkspaceMigrationRunnerExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import {
|
||||
WorkspaceSchemaMigrationException,
|
||||
WorkspaceSchemaMigrationExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util';
|
||||
|
||||
import {
|
||||
prepareFieldWorkspaceSchemaContext,
|
||||
prepareWorkspaceSchemaContext,
|
||||
} from './utils/workspace-schema-context.util';
|
||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util';
|
||||
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||
import {
|
||||
collectEnumOperationsForField,
|
||||
EnumOperation,
|
||||
executeBatchEnumOperations,
|
||||
} from './utils/workspace-schema-enum-operations.util';
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSchemaFieldActionRunnerService
|
||||
implements
|
||||
RunnerMethodForActionType<WorkspaceMigrationFieldActionTypeV2, 'schema'>
|
||||
{
|
||||
export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'update_field',
|
||||
) {
|
||||
constructor(
|
||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
runDeleteFieldSchemaMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
}: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>) => {
|
||||
const { objectMetadataId, fieldMetadataId } = action;
|
||||
const { schemaName, tableName, fieldMetadata } =
|
||||
prepareFieldWorkspaceSchemaContext({
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner } = context;
|
||||
const fieldMetadataRepository =
|
||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
const { fieldMetadataId } = action;
|
||||
|
||||
await fieldMetadataRepository.update(
|
||||
fieldMetadataId,
|
||||
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
|
||||
);
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||
context;
|
||||
const { objectMetadataId, fieldMetadataId, updates } = action;
|
||||
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId,
|
||||
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||
});
|
||||
|
||||
const flatObjectMetadata =
|
||||
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const currentFlatFieldMetadata =
|
||||
findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
fieldMetadataId,
|
||||
});
|
||||
|
||||
const flatObjectMetadata =
|
||||
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const columnDefinitions = generateColumnDefinitions({
|
||||
flatFieldMetadata: fieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
||||
});
|
||||
const columnNamesToDrop = columnDefinitions.map((def) => def.name);
|
||||
|
||||
await this.workspaceSchemaManagerService.columnManager.dropColumns({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnNames: columnNamesToDrop,
|
||||
});
|
||||
|
||||
const enumOperations = collectEnumOperationsForField({
|
||||
flatFieldMetadata: fieldMetadata,
|
||||
tableName,
|
||||
operation: EnumOperation.DROP,
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
runCreateFieldSchemaMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
}: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>) => {
|
||||
const { flatFieldMetadata } = action;
|
||||
const {
|
||||
schemaName,
|
||||
tableName,
|
||||
flatObjectMetadataWithFlatFieldMaps: flatObjectMetadata,
|
||||
} = prepareWorkspaceSchemaContext({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId: flatFieldMetadata.objectMetadataId,
|
||||
});
|
||||
|
||||
const enumOperations = collectEnumOperationsForField({
|
||||
flatFieldMetadata: flatFieldMetadata,
|
||||
tableName,
|
||||
operation: EnumOperation.CREATE,
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
|
||||
const columnDefinitions = generateColumnDefinitions({
|
||||
flatFieldMetadata: flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.columnManager.addColumns({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnDefinitions,
|
||||
});
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
runUpdateFieldSchemaMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
}: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>) => {
|
||||
const { objectMetadataId, fieldMetadataId, updates } = action;
|
||||
const {
|
||||
schemaName,
|
||||
tableName,
|
||||
flatObjectMetadataWithFlatFieldMaps: flatObjectMetadata,
|
||||
fieldMetadata: currentFlatFieldMetadata,
|
||||
} = prepareFieldWorkspaceSchemaContext({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
fieldMetadataId,
|
||||
});
|
||||
|
||||
let optimisticFlatFieldMetadata = structuredClone(currentFlatFieldMetadata);
|
||||
|
||||
for (const update of updates) {
|
||||
@@ -202,9 +136,7 @@ export class WorkspaceSchemaFieldActionRunnerService
|
||||
optimisticFlatFieldMetadata.options = update.to ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
private async handleFieldNameUpdate(
|
||||
queryRunner: QueryRunner,
|
||||
@@ -220,9 +152,9 @@ export class WorkspaceSchemaFieldActionRunnerService
|
||||
|
||||
for (const property of compositeType.properties) {
|
||||
if (isRelationFieldMetadataType(property.type)) {
|
||||
throw new WorkspaceSchemaMigrationException(
|
||||
throw new WorkspaceMigrationRunnerException(
|
||||
'Relation field metadata in composite type is not supported yet',
|
||||
WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED,
|
||||
WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -245,9 +177,9 @@ export class WorkspaceSchemaFieldActionRunnerService
|
||||
}
|
||||
} else {
|
||||
if (isRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
throw new WorkspaceSchemaMigrationException(
|
||||
throw new WorkspaceMigrationRunnerException(
|
||||
'Relation field metadata name update is not supported yet',
|
||||
WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED,
|
||||
WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED,
|
||||
);
|
||||
}
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
@@ -290,9 +222,9 @@ export class WorkspaceSchemaFieldActionRunnerService
|
||||
|
||||
for (const property of compositeType.properties) {
|
||||
if (isRelationFieldMetadataType(property.type)) {
|
||||
throw new WorkspaceSchemaMigrationException(
|
||||
throw new WorkspaceMigrationRunnerException(
|
||||
'Relation field metadata in composite type is not supported yet',
|
||||
WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED,
|
||||
WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED,
|
||||
);
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { type CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class CreateIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'create_index',
|
||||
) {
|
||||
async executeForMetadata(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
|
||||
): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
|
||||
): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { type DeleteIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'delete_index',
|
||||
) {
|
||||
async executeForMetadata(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
|
||||
): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
|
||||
): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { type CreateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util';
|
||||
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||
import {
|
||||
collectEnumOperationsForObject,
|
||||
EnumOperation,
|
||||
executeBatchEnumOperations,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||
|
||||
@Injectable()
|
||||
export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'create_object',
|
||||
) {
|
||||
constructor(
|
||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner } = context;
|
||||
const { flatObjectMetadataWithoutFields, createFieldActions } = action;
|
||||
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||
ObjectMetadataEntity,
|
||||
);
|
||||
|
||||
const dataSourceRepository =
|
||||
queryRunner.manager.getRepository<DataSourceEntity>(DataSourceEntity);
|
||||
|
||||
const lastDataSourceMetadata = await dataSourceRepository.findOneOrFail({
|
||||
where: {
|
||||
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
||||
},
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
|
||||
await objectMetadataRepository.save({
|
||||
...flatObjectMetadataWithoutFields,
|
||||
dataSourceId: lastDataSourceMetadata.id,
|
||||
targetTableName: 'DEPRECATED',
|
||||
});
|
||||
|
||||
const fieldMetadataRepository =
|
||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
for (const createFieldAction of createFieldActions) {
|
||||
const { flatFieldMetadata } = createFieldAction;
|
||||
|
||||
await fieldMetadataRepository.save(flatFieldMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
context: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, workspaceId } = context;
|
||||
const { flatObjectMetadataWithoutFields, createFieldActions } = action;
|
||||
|
||||
const flatObjectMetadata = flatObjectMetadataWithoutFields;
|
||||
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId,
|
||||
flatObjectMetadata: flatObjectMetadata,
|
||||
});
|
||||
|
||||
const columnDefinitions = createFieldActions.flatMap((createFieldAction) =>
|
||||
generateColumnDefinitions({
|
||||
flatFieldMetadata: createFieldAction.flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
||||
}),
|
||||
);
|
||||
|
||||
const enumOrCompositeFlatFieldMetadatas = createFieldActions
|
||||
.map((createFieldAction) => createFieldAction.flatFieldMetadata)
|
||||
.filter((field): field is FlatFieldMetadata => field != null)
|
||||
.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
||||
);
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||
tableName,
|
||||
operation: EnumOperation.CREATE,
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.tableManager.createTable({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnDefinitions,
|
||||
});
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { type DeleteObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||
import {
|
||||
collectEnumOperationsForObject,
|
||||
EnumOperation,
|
||||
executeBatchEnumOperations,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'delete_object',
|
||||
) {
|
||||
constructor(
|
||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner } = context;
|
||||
const { objectMetadataId } = action;
|
||||
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||
ObjectMetadataEntity,
|
||||
);
|
||||
|
||||
await objectMetadataRepository.delete(objectMetadataId);
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
context: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||
context;
|
||||
const { objectMetadataId } = action;
|
||||
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId,
|
||||
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.tableManager.dropTable({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
});
|
||||
|
||||
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
||||
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
||||
)
|
||||
.filter((field): field is FlatFieldMetadata => field != null)
|
||||
.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
||||
);
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||
tableName,
|
||||
operation: EnumOperation.DROP,
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { type UpdateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||
import {
|
||||
collectEnumOperationsForObject,
|
||||
EnumOperation,
|
||||
executeBatchEnumOperations,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'update_object',
|
||||
) {
|
||||
constructor(
|
||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner } = context;
|
||||
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||
ObjectMetadataEntity,
|
||||
);
|
||||
|
||||
await objectMetadataRepository.update(
|
||||
action.objectMetadataId,
|
||||
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
|
||||
);
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||
context;
|
||||
const { objectMetadataId, updates } = action;
|
||||
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const { schemaName, tableName: currentTableName } =
|
||||
getWorkspaceSchemaContextForMigration({
|
||||
workspaceId,
|
||||
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||
});
|
||||
|
||||
for (const update of updates) {
|
||||
if (update.property !== 'nameSingular') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updatedObjectMetadata = {
|
||||
...flatObjectMetadataWithFlatFieldMaps,
|
||||
[update.property]: update.to,
|
||||
};
|
||||
|
||||
const newTableName = computeObjectTargetTable(updatedObjectMetadata);
|
||||
|
||||
if (currentTableName !== newTableName) {
|
||||
await this.workspaceSchemaManagerService.tableManager.renameTable({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
oldTableName: currentTableName,
|
||||
newTableName,
|
||||
});
|
||||
|
||||
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
||||
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
||||
)
|
||||
.filter((field): field is FlatFieldMetadata => field != null)
|
||||
.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) ||
|
||||
isCompositeFlatFieldMetadata(field),
|
||||
);
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||
tableName: currentTableName,
|
||||
operation: EnumOperation.RENAME,
|
||||
options: {
|
||||
newTableName,
|
||||
},
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
|
||||
import { CreateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service';
|
||||
import { DeleteFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service';
|
||||
import { UpdateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service';
|
||||
import { CreateIndexActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service';
|
||||
import { DeleteIndexActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service';
|
||||
import { CreateObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service';
|
||||
import { DeleteObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service';
|
||||
import { UpdateObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service';
|
||||
|
||||
@Module({
|
||||
imports: [WorkspaceSchemaManagerModule],
|
||||
providers: [
|
||||
CreateFieldActionHandlerService,
|
||||
UpdateFieldActionHandlerService,
|
||||
DeleteFieldActionHandlerService,
|
||||
|
||||
CreateObjectActionHandlerService,
|
||||
UpdateObjectActionHandlerService,
|
||||
DeleteObjectActionHandlerService,
|
||||
|
||||
CreateIndexActionHandlerService,
|
||||
DeleteIndexActionHandlerService,
|
||||
],
|
||||
})
|
||||
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY =
|
||||
'workspace_migration_action_handler_metadata_key';
|
||||
+4
@@ -10,4 +10,8 @@ export class WorkspaceMigrationRunnerException extends CustomException<
|
||||
export const WorkspaceMigrationRunnerExceptionCode = appendCommonExceptionCode({
|
||||
FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND',
|
||||
OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND',
|
||||
ENUM_OPERATION_FAILED: 'ENUM_OPERATION_FAILED',
|
||||
UNSUPPORTED_COMPOSITE_COLUMN_TYPE: 'UNSUPPORTED_COMPOSITE_COLUMN_TYPE',
|
||||
NOT_SUPPORTED: 'NOT_SUPPORTED',
|
||||
INVALID_ACTION_TYPE: 'INVALID_ACTION_TYPE',
|
||||
} as const);
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
type ExtractAction,
|
||||
type WorkspaceMigrationActionTypeV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
||||
import { WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
export interface WorkspaceMigrationRunnerActionHandlerService<
|
||||
T extends WorkspaceMigrationActionTypeV2,
|
||||
> {
|
||||
execute(
|
||||
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class BaseWorkspaceMigrationRunnerActionHandlerService<
|
||||
T extends WorkspaceMigrationActionTypeV2,
|
||||
> implements WorkspaceMigrationRunnerActionHandlerService<T>
|
||||
{
|
||||
abstract executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
|
||||
): Promise<void>;
|
||||
|
||||
abstract executeForWorkspaceSchema(
|
||||
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
|
||||
): Promise<void>;
|
||||
|
||||
async execute(
|
||||
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
|
||||
): Promise<void> {
|
||||
await Promise.all([
|
||||
this.executeForMetadata(context),
|
||||
this.executeForWorkspaceSchema(context),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export function WorkspaceMigrationRunnerActionHandler<
|
||||
T extends WorkspaceMigrationActionTypeV2,
|
||||
>(actionType: T): typeof BaseWorkspaceMigrationRunnerActionHandlerService<T> {
|
||||
abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService<T> {}
|
||||
|
||||
SetMetadata(
|
||||
WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY,
|
||||
actionType,
|
||||
)(ActionHandlerService);
|
||||
|
||||
return ActionHandlerService;
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { DiscoveryService } from '@nestjs/core';
|
||||
|
||||
import { type WorkspaceMigrationRunnerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import {
|
||||
type WorkspaceMigrationActionTypeV2,
|
||||
type WorkspaceMigrationActionV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
||||
import { WorkspaceSchemaMigrationRunnerActionHandlersModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module';
|
||||
import { WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant';
|
||||
import {
|
||||
WorkspaceMigrationRunnerException,
|
||||
WorkspaceMigrationRunnerExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationRunnerActionHandlerRegistryService
|
||||
implements OnModuleInit
|
||||
{
|
||||
private readonly actionHandlers = new Map<
|
||||
WorkspaceMigrationActionTypeV2,
|
||||
WorkspaceMigrationRunnerActionHandlerService<WorkspaceMigrationActionTypeV2>
|
||||
>();
|
||||
|
||||
constructor(private readonly discoveryService: DiscoveryService) {}
|
||||
|
||||
async onModuleInit() {
|
||||
this.discoverAndRegisterActionHandlers();
|
||||
}
|
||||
|
||||
private discoverAndRegisterActionHandlers(): void {
|
||||
const providers = this.discoveryService.getProviders({
|
||||
include: [WorkspaceSchemaMigrationRunnerActionHandlersModule],
|
||||
});
|
||||
|
||||
providers.forEach((wrapper) => {
|
||||
const { instance, metatype } = wrapper;
|
||||
|
||||
if (!instance || !metatype) return;
|
||||
|
||||
const actionType = Reflect.getMetadata(
|
||||
WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY,
|
||||
metatype,
|
||||
);
|
||||
|
||||
if (actionType && instance.execute) {
|
||||
this.actionHandlers.set(actionType, instance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async executeActionHandler<T extends WorkspaceMigrationActionV2>(
|
||||
actionType: WorkspaceMigrationActionTypeV2,
|
||||
context: WorkspaceMigrationActionRunnerArgs<T>,
|
||||
): Promise<void> {
|
||||
const handler = this.actionHandlers.get(actionType);
|
||||
|
||||
if (!handler) {
|
||||
throw new WorkspaceMigrationRunnerException(
|
||||
`No migration runner action handler found for action: ${actionType}`,
|
||||
WorkspaceMigrationRunnerExceptionCode.INVALID_ACTION_TYPE,
|
||||
);
|
||||
}
|
||||
|
||||
await handler.execute(context);
|
||||
}
|
||||
}
|
||||
+11
-15
@@ -8,20 +8,18 @@ import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/works
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
|
||||
import { applyWorkspaceMigrationActionOnFlatObjectMetadataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps';
|
||||
import { WorkspaceMetadataMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service';
|
||||
import { WorkspaceSchemaMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service';
|
||||
import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service';
|
||||
import { applyWorkspaceMigrationActionOnFlatObjectMetadataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationRunnerV2Service {
|
||||
constructor(
|
||||
private readonly workspaceMetadataMigrationRunner: WorkspaceMetadataMigrationRunnerService,
|
||||
private readonly workspaceSchemaMigrationRunner: WorkspaceSchemaMigrationRunnerService,
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
@InjectDataSource('core')
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly workspaceMigrationRunnerActionHandlerRegistry: WorkspaceMigrationRunnerActionHandlerRegistryService,
|
||||
) {}
|
||||
|
||||
run = async (
|
||||
@@ -44,23 +42,21 @@ export class WorkspaceMigrationRunnerV2Service {
|
||||
|
||||
try {
|
||||
for (const action of workspaceMigration.actions) {
|
||||
await Promise.all([
|
||||
this.workspaceMetadataMigrationRunner.runWorkspaceMetadataMigration({
|
||||
await this.workspaceMigrationRunnerActionHandlerRegistry.executeActionHandler(
|
||||
action.type,
|
||||
{
|
||||
action,
|
||||
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
||||
queryRunner,
|
||||
action,
|
||||
}),
|
||||
this.workspaceSchemaMigrationRunner.runWorkspaceSchemaMigration({
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
||||
action,
|
||||
}),
|
||||
]);
|
||||
workspaceId: workspaceMigration.workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
optimisticFlatObjectMetadataMaps =
|
||||
applyWorkspaceMigrationActionOnFlatObjectMetadataMaps({
|
||||
action,
|
||||
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
||||
workspaceId: workspaceMigration.workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { type ConvertActionTypeToCamelCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/convert-action-type-to-camel-case.type';
|
||||
import {
|
||||
type WorkspaceMigrationActionTypeV2,
|
||||
type WorkspaceMigrationActionV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
export type RunnerMethodForActionType<
|
||||
TAction extends WorkspaceMigrationActionTypeV2,
|
||||
TRunner extends 'metadata' | 'schema',
|
||||
> = {
|
||||
[P in TAction as `run${Capitalize<ConvertActionTypeToCamelCase<P>>}${Capitalize<TRunner>}Migration`]: (
|
||||
arg: WorkspaceMigrationActionRunnerArgs<
|
||||
Extract<WorkspaceMigrationActionV2, { type: P }>
|
||||
>,
|
||||
) => Promise<void>;
|
||||
};
|
||||
+1
@@ -9,4 +9,5 @@ export type WorkspaceMigrationActionRunnerArgs<
|
||||
queryRunner: QueryRunner;
|
||||
action: T;
|
||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
||||
|
||||
export type WorkspaceMigrationRunnerArgs = {
|
||||
action: WorkspaceMigrationActionV2;
|
||||
queryRunner: QueryRunner;
|
||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
};
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util';
|
||||
|
||||
describe('Generate Column Definitions', () => {
|
||||
const mockObjectMetadata = getFlatObjectMetadataMock({
|
||||
+3
-3
@@ -3,13 +3,13 @@ import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { WorkspaceSchemaMigrationException } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
||||
import { WorkspaceMigrationRunnerException } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||
import {
|
||||
collectEnumOperationsForField,
|
||||
EnumOperation,
|
||||
type EnumOperationSpec,
|
||||
executeBatchEnumOperations,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-enum-operations.util';
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||
|
||||
describe('WorkspaceSchemaEnumOperations', () => {
|
||||
let mockSchemaManagerService: jest.Mocked<WorkspaceSchemaManagerService>;
|
||||
@@ -61,7 +61,7 @@ describe('WorkspaceSchemaEnumOperations', () => {
|
||||
schemaName: 'test_schema',
|
||||
workspaceSchemaManagerService: mockSchemaManagerService,
|
||||
}),
|
||||
).rejects.toThrow(WorkspaceSchemaMigrationException);
|
||||
).rejects.toThrow(WorkspaceMigrationRunnerException);
|
||||
|
||||
// All operations should be attempted in parallel despite failure
|
||||
expect(
|
||||
+8
-5
@@ -9,18 +9,21 @@ import { findFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metada
|
||||
import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util';
|
||||
import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type';
|
||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
||||
import {
|
||||
WorkspaceMigrationRunnerException,
|
||||
WorkspaceMigrationRunnerExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception';
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||
|
||||
export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = ({
|
||||
export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = <
|
||||
T extends WorkspaceMigrationActionV2,
|
||||
>({
|
||||
action,
|
||||
flatObjectMetadataMaps,
|
||||
}: Omit<
|
||||
WorkspaceMigrationRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerArgs<T>,
|
||||
'queryRunner'
|
||||
>): FlatObjectMetadataMaps => {
|
||||
switch (action.type) {
|
||||
+8
-9
@@ -18,11 +18,10 @@ import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/works
|
||||
import { type WorkspaceSchemaColumnDefinition } from 'src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-column-definition.type';
|
||||
import { computePostgresEnumName } from 'src/engine/workspace-manager/workspace-migration-runner/utils/compute-postgres-enum-name.util';
|
||||
import {
|
||||
WorkspaceSchemaMigrationException,
|
||||
WorkspaceSchemaMigrationExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
||||
|
||||
import { getWorkspaceSchemaContextForMigration } from './get-workspace-schema-context-for-migration.util';
|
||||
WorkspaceMigrationRunnerException,
|
||||
WorkspaceMigrationRunnerExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||
|
||||
export const generateCompositeColumnDefinition = ({
|
||||
compositeProperty,
|
||||
@@ -35,16 +34,16 @@ export const generateCompositeColumnDefinition = ({
|
||||
}): WorkspaceSchemaColumnDefinition => {
|
||||
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
||||
flatObjectMetadataWithoutFields,
|
||||
flatObjectMetadata: flatObjectMetadataWithoutFields,
|
||||
});
|
||||
|
||||
if (
|
||||
compositeProperty.type === FieldMetadataType.RELATION ||
|
||||
compositeProperty.type === FieldMetadataType.MORPH_RELATION
|
||||
) {
|
||||
throw new WorkspaceSchemaMigrationException(
|
||||
throw new WorkspaceMigrationRunnerException(
|
||||
`Relation type not supported for composite columns`,
|
||||
WorkspaceSchemaMigrationExceptionCode.UNSUPPORTED_COMPOSITE_COLUMN_TYPE,
|
||||
WorkspaceMigrationRunnerExceptionCode.UNSUPPORTED_COMPOSITE_COLUMN_TYPE,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -156,7 +155,7 @@ export const generateColumnDefinitions = ({
|
||||
}): WorkspaceSchemaColumnDefinition[] => {
|
||||
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
||||
flatObjectMetadata: flatObjectMetadataWithoutFields,
|
||||
});
|
||||
|
||||
if (isCompositeFlatFieldMetadata(flatFieldMetadata)) {
|
||||
+7
-4
@@ -1,4 +1,7 @@
|
||||
import { type FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
type FlatObjectMetadata,
|
||||
type FlatObjectMetadataWithoutFields,
|
||||
} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
|
||||
@@ -9,13 +12,13 @@ export type WorkspaceSchemaContextForMigration = {
|
||||
|
||||
export const getWorkspaceSchemaContextForMigration = ({
|
||||
workspaceId,
|
||||
flatObjectMetadataWithoutFields,
|
||||
flatObjectMetadata,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields;
|
||||
flatObjectMetadata: FlatObjectMetadata | FlatObjectMetadataWithoutFields;
|
||||
}): WorkspaceSchemaContextForMigration => {
|
||||
return {
|
||||
schemaName: getWorkspaceSchemaName(workspaceId),
|
||||
tableName: computeObjectTargetTable(flatObjectMetadataWithoutFields),
|
||||
tableName: computeObjectTargetTable(flatObjectMetadata),
|
||||
};
|
||||
};
|
||||
+5
-5
@@ -12,9 +12,9 @@ import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-
|
||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { computePostgresEnumName } from 'src/engine/workspace-manager/workspace-migration-runner/utils/compute-postgres-enum-name.util';
|
||||
import {
|
||||
WorkspaceSchemaMigrationException,
|
||||
WorkspaceSchemaMigrationExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
||||
WorkspaceMigrationRunnerException,
|
||||
WorkspaceMigrationRunnerExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||
|
||||
export interface CreateEnumOperationSpec {
|
||||
operation: EnumOperation.CREATE;
|
||||
@@ -246,9 +246,9 @@ export const executeBatchEnumOperations = async ({
|
||||
|
||||
await Promise.all(enumPromises);
|
||||
} catch (error) {
|
||||
throw new WorkspaceSchemaMigrationException(
|
||||
throw new WorkspaceMigrationRunnerException(
|
||||
`Failed to execute batch enum operations: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
WorkspaceSchemaMigrationExceptionCode.ENUM_OPERATION_FAILED,
|
||||
WorkspaceMigrationRunnerExceptionCode.ENUM_OPERATION_FAILED,
|
||||
);
|
||||
}
|
||||
};
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
type CreateFieldAction,
|
||||
type DeleteFieldAction,
|
||||
type UpdateFieldAction,
|
||||
type WorkspaceMigrationFieldActionTypeV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||
|
||||
export class WorkspaceMetadataFieldActionRunnerService
|
||||
implements
|
||||
RunnerMethodForActionType<WorkspaceMigrationFieldActionTypeV2, 'metadata'>
|
||||
{
|
||||
runDeleteFieldMetadataMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
}: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>) => {
|
||||
const fieldMetadataRepository =
|
||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
const { fieldMetadataId } = action;
|
||||
|
||||
await fieldMetadataRepository.delete({
|
||||
id: In([fieldMetadataId]),
|
||||
});
|
||||
};
|
||||
|
||||
runCreateFieldMetadataMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
}: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>) => {
|
||||
const fieldMetadataRepository =
|
||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
const { flatFieldMetadata } = action;
|
||||
|
||||
await fieldMetadataRepository.save(flatFieldMetadata);
|
||||
};
|
||||
runUpdateFieldMetadataMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
}: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>) => {
|
||||
const fieldMetadataRepository =
|
||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
const { fieldMetadataId } = action;
|
||||
|
||||
await fieldMetadataRepository.update(
|
||||
fieldMetadataId,
|
||||
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
|
||||
);
|
||||
};
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
type CreateIndexAction,
|
||||
type DeleteIndexAction,
|
||||
type WorkspaceMigrationIndexActionTypeV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
|
||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMetadataIndexActionRunnerService
|
||||
implements
|
||||
RunnerMethodForActionType<WorkspaceMigrationIndexActionTypeV2, 'metadata'>
|
||||
{
|
||||
runDeleteIndexMetadataMigration = async (
|
||||
_action: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
|
||||
) => {
|
||||
return;
|
||||
};
|
||||
runCreateIndexMetadataMigration = async (
|
||||
_action: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
|
||||
) => {
|
||||
return;
|
||||
};
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type';
|
||||
import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service';
|
||||
import { WorkspaceMetadataIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service';
|
||||
import { WorkspaceMetadataObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMetadataMigrationRunnerService {
|
||||
constructor(
|
||||
private readonly workspaceMetadataObjectMigrationRunnerService: WorkspaceMetadataObjectActionRunnerService,
|
||||
private readonly workspaceMetadataIndexMigrationRunnerService: WorkspaceMetadataIndexActionRunnerService,
|
||||
private readonly workspaceMetadataFieldMigrationRunnerService: WorkspaceMetadataFieldActionRunnerService,
|
||||
) {}
|
||||
|
||||
runWorkspaceMetadataMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
}: WorkspaceMigrationRunnerArgs) => {
|
||||
switch (action.type) {
|
||||
case 'delete_object': {
|
||||
await this.workspaceMetadataObjectMigrationRunnerService.runDeleteObjectMetadataMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'create_object': {
|
||||
await this.workspaceMetadataObjectMigrationRunnerService.runCreateObjectMetadataMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'update_object': {
|
||||
await this.workspaceMetadataObjectMigrationRunnerService.runUpdateObjectMetadataMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'create_field': {
|
||||
await this.workspaceMetadataFieldMigrationRunnerService.runCreateFieldMetadataMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'update_field': {
|
||||
await this.workspaceMetadataFieldMigrationRunnerService.runUpdateFieldMetadataMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'delete_field': {
|
||||
await this.workspaceMetadataFieldMigrationRunnerService.runDeleteFieldMetadataMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'create_index': {
|
||||
await this.workspaceMetadataIndexMigrationRunnerService.runCreateIndexMetadataMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'delete_index': {
|
||||
await this.workspaceMetadataIndexMigrationRunnerService.runDeleteIndexMetadataMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(
|
||||
action,
|
||||
'Should never occur, encountered an unsupported workspace migration action type',
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
type CreateObjectAction,
|
||||
type DeleteObjectAction,
|
||||
type UpdateObjectAction,
|
||||
type WorkspaceMigrationObjectActionTypeV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||
import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMetadataObjectActionRunnerService
|
||||
implements
|
||||
RunnerMethodForActionType<WorkspaceMigrationObjectActionTypeV2, 'metadata'>
|
||||
{
|
||||
constructor(
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceMetadataFieldActionRunnerService: WorkspaceMetadataFieldActionRunnerService,
|
||||
) {}
|
||||
|
||||
runDeleteObjectMetadataMigration = async ({
|
||||
action: { objectMetadataId },
|
||||
queryRunner,
|
||||
}: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>) => {
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||
ObjectMetadataEntity,
|
||||
);
|
||||
|
||||
await objectMetadataRepository.delete(objectMetadataId);
|
||||
};
|
||||
|
||||
runCreateObjectMetadataMigration = async ({
|
||||
action: { flatObjectMetadataWithoutFields, createFieldActions },
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
}: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>) => {
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||
ObjectMetadataEntity,
|
||||
);
|
||||
const lastDataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
flatObjectMetadataWithoutFields.workspaceId,
|
||||
);
|
||||
|
||||
await objectMetadataRepository.save({
|
||||
...flatObjectMetadataWithoutFields,
|
||||
dataSourceId: lastDataSourceMetadata.id,
|
||||
targetTableName: 'DEPRECATED',
|
||||
});
|
||||
|
||||
for (const createFieldAction of createFieldActions) {
|
||||
await this.workspaceMetadataFieldActionRunnerService.runCreateFieldMetadataMigration(
|
||||
{
|
||||
action: createFieldAction,
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
runUpdateObjectMetadataMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
}: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>) => {
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||
ObjectMetadataEntity,
|
||||
);
|
||||
|
||||
await objectMetadataRepository.update(
|
||||
action.objectMetadataId,
|
||||
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
|
||||
);
|
||||
};
|
||||
}
|
||||
+7
-19
@@ -1,4 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DiscoveryModule } from '@nestjs/core';
|
||||
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
@@ -6,16 +7,9 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
|
||||
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
|
||||
import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service';
|
||||
import { WorkspaceMetadataIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service';
|
||||
import { WorkspaceMetadataMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service';
|
||||
import { WorkspaceMetadataObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service';
|
||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
|
||||
import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service';
|
||||
import { WorkspaceSchemaIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service';
|
||||
import { WorkspaceSchemaMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service';
|
||||
import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service';
|
||||
import { WorkspaceSchemaMigrationRunnerActionHandlersModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module';
|
||||
import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service';
|
||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -25,18 +19,12 @@ import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-m
|
||||
WorkspaceMetadataVersionModule,
|
||||
WorkspacePermissionsCacheModule,
|
||||
WorkspaceMetadataCacheModule,
|
||||
WorkspaceSchemaManagerModule,
|
||||
WorkspaceSchemaMigrationRunnerActionHandlersModule,
|
||||
DiscoveryModule,
|
||||
],
|
||||
providers: [
|
||||
WorkspaceMetadataObjectActionRunnerService,
|
||||
WorkspaceMetadataIndexActionRunnerService,
|
||||
WorkspaceMetadataFieldActionRunnerService,
|
||||
WorkspaceSchemaIndexActionRunnerService,
|
||||
WorkspaceMetadataMigrationRunnerService,
|
||||
WorkspaceSchemaMigrationRunnerService,
|
||||
WorkspaceMigrationRunnerV2Service,
|
||||
WorkspaceSchemaObjectActionRunnerService,
|
||||
WorkspaceSchemaFieldActionRunnerService,
|
||||
WorkspaceMigrationRunnerActionHandlerRegistryService,
|
||||
],
|
||||
exports: [WorkspaceMigrationRunnerV2Service],
|
||||
})
|
||||
|
||||
-730
@@ -1,730 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service';
|
||||
|
||||
describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
let service: WorkspaceSchemaFieldActionRunnerService;
|
||||
let mockSchemaManagerService: jest.Mocked<WorkspaceSchemaManagerService>;
|
||||
let mockQueryRunner: jest.Mocked<QueryRunner>;
|
||||
|
||||
const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
const mockSchemaName = getWorkspaceSchemaName(mockWorkspaceId);
|
||||
const mockObjectMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea418';
|
||||
const mockFieldMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea417';
|
||||
|
||||
const createMockFlatObjectMetadataMaps = (
|
||||
objectMetadata: any,
|
||||
fieldMetadata: any,
|
||||
) => ({
|
||||
byId: {
|
||||
[mockObjectMetadataId]: {
|
||||
...objectMetadata,
|
||||
fieldsById: {
|
||||
[mockFieldMetadataId]: fieldMetadata,
|
||||
},
|
||||
},
|
||||
},
|
||||
idByNameSingular: {
|
||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockSchemaManagerService = {
|
||||
columnManager: {
|
||||
dropColumns: jest.fn(),
|
||||
addColumns: jest.fn(),
|
||||
renameColumn: jest.fn(),
|
||||
alterColumnDefault: jest.fn(),
|
||||
},
|
||||
enumManager: {
|
||||
createEnum: jest.fn(),
|
||||
dropEnum: jest.fn(),
|
||||
renameEnum: jest.fn(),
|
||||
alterEnumValues: jest.fn(),
|
||||
},
|
||||
tableManager: {
|
||||
createTable: jest.fn(),
|
||||
dropTable: jest.fn(),
|
||||
renameTable: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
|
||||
mockQueryRunner = {} as any;
|
||||
|
||||
service = new WorkspaceSchemaFieldActionRunnerService(
|
||||
mockSchemaManagerService,
|
||||
);
|
||||
});
|
||||
|
||||
describe('DELETE Field Migration', () => {
|
||||
it('should properly delete composite ADDRESS fields with all sub-columns', async () => {
|
||||
const addressField = getFlatFieldMetadataMock({
|
||||
id: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.ADDRESS,
|
||||
name: 'homeAddress',
|
||||
uniqueIdentifier: 'homeAddress',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'person',
|
||||
uniqueIdentifier: 'person',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
addressField,
|
||||
);
|
||||
|
||||
await service.runDeleteFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'delete_field',
|
||||
fieldMetadataId: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// All 8 ADDRESS composite columns must be dropped
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.dropColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
columnNames: [
|
||||
'homeAddressAddressStreet1',
|
||||
'homeAddressAddressStreet2',
|
||||
'homeAddressAddressCity',
|
||||
'homeAddressAddressPostcode',
|
||||
'homeAddressAddressState',
|
||||
'homeAddressAddressCountry',
|
||||
'homeAddressAddressLat',
|
||||
'homeAddressAddressLng',
|
||||
],
|
||||
});
|
||||
|
||||
// No enum operations should be performed for ADDRESS fields
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should properly delete SELECT fields with enum cleanup', async () => {
|
||||
const selectField = getFlatFieldMetadataMock({
|
||||
id: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'status',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'ACTIVE',
|
||||
label: 'Active',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'INACTIVE',
|
||||
label: 'Inactive',
|
||||
color: 'red',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
uniqueIdentifier: 'status',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'person',
|
||||
uniqueIdentifier: 'person',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
selectField,
|
||||
);
|
||||
|
||||
await service.runDeleteFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'delete_field',
|
||||
fieldMetadataId: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Column must be dropped first
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.dropColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
columnNames: ['status'],
|
||||
});
|
||||
|
||||
// Enum type must be properly cleaned up
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_person_status_enum',
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly delete RELATION fields with foreign key cleanup', async () => {
|
||||
const relationField = getFlatFieldMetadataMock({
|
||||
id: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'company',
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: 'companyId',
|
||||
},
|
||||
uniqueIdentifier: 'company',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'person',
|
||||
uniqueIdentifier: 'person',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
relationField,
|
||||
);
|
||||
|
||||
await service.runDeleteFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'delete_field',
|
||||
fieldMetadataId: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Foreign key column must be dropped
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.dropColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
columnNames: ['companyId'],
|
||||
});
|
||||
|
||||
// No enum operations for relation fields
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('CREATE Field Migration', () => {
|
||||
it('should create SELECT fields with proper enum-first ordering', async () => {
|
||||
const selectField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'priority',
|
||||
options: [
|
||||
{ id: '1', value: 'HIGH', label: 'High', color: 'red', position: 0 },
|
||||
{ id: '2', value: 'LOW', label: 'Low', color: 'blue', position: 1 },
|
||||
],
|
||||
uniqueIdentifier: 'priority',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'task',
|
||||
uniqueIdentifier: 'task',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
selectField,
|
||||
);
|
||||
|
||||
await service.runCreateFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'create_field',
|
||||
flatFieldMetadata: selectField,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Enum must be created BEFORE column that depends on it
|
||||
const enumCreateMock = mockSchemaManagerService.enumManager
|
||||
.createEnum as unknown as jest.Mock;
|
||||
const addColumnsMock = mockSchemaManagerService.columnManager
|
||||
.addColumns as unknown as jest.Mock;
|
||||
|
||||
const allCalls = [
|
||||
...enumCreateMock.mock.calls.map((call: unknown[]) => ({
|
||||
type: 'enum',
|
||||
call,
|
||||
})),
|
||||
...addColumnsMock.mock.calls.map((call: unknown[]) => ({
|
||||
type: 'column',
|
||||
call,
|
||||
})),
|
||||
];
|
||||
|
||||
expect(allCalls).toHaveLength(2);
|
||||
expect(allCalls[0].type).toBe('enum');
|
||||
expect(allCalls[1].type).toBe('column');
|
||||
|
||||
// Enum creation with correct parameters
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.createEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_task_priority_enum',
|
||||
values: ['HIGH', 'LOW'],
|
||||
});
|
||||
|
||||
// Column creation with enum reference
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.addColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_task',
|
||||
columnDefinitions: [
|
||||
{
|
||||
name: 'priority',
|
||||
type: `"${mockSchemaName}"."_task_priority_enum"`,
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create CURRENCY composite fields with proper column generation', async () => {
|
||||
const currencyField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
name: 'salary',
|
||||
defaultValue: {
|
||||
amountMicros: '5000000000',
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
uniqueIdentifier: 'salary',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'employee',
|
||||
uniqueIdentifier: 'employee',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
currencyField,
|
||||
);
|
||||
|
||||
await service.runCreateFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'create_field',
|
||||
flatFieldMetadata: currencyField,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// No enum operations for composite fields
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.createEnum,
|
||||
).not.toHaveBeenCalled();
|
||||
|
||||
// Both composite columns must be created
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.addColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_employee',
|
||||
columnDefinitions: [
|
||||
{
|
||||
name: 'salaryAmountMicros',
|
||||
type: 'numeric',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: '5000000000',
|
||||
isArray: false,
|
||||
},
|
||||
{
|
||||
name: 'salaryCurrencyCode',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: 'USD',
|
||||
isArray: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('UPDATE Field Migration', () => {
|
||||
it('should handle field name updates with composite field column renaming', async () => {
|
||||
const addressField = getFlatFieldMetadataMock({
|
||||
id: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.ADDRESS,
|
||||
name: 'oldAddress',
|
||||
uniqueIdentifier: 'oldAddress',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'company',
|
||||
uniqueIdentifier: 'company',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
addressField,
|
||||
);
|
||||
|
||||
await service.runUpdateFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'update_field',
|
||||
workspaceId: mockWorkspaceId,
|
||||
fieldMetadataId: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
updates: [
|
||||
{
|
||||
property: 'name',
|
||||
from: 'oldAddress',
|
||||
to: 'newAddress',
|
||||
},
|
||||
],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// All 8 ADDRESS composite columns must be renamed
|
||||
const expectedRenameCalls = [
|
||||
['oldAddressAddressStreet1', 'newAddressAddressStreet1'],
|
||||
['oldAddressAddressStreet2', 'newAddressAddressStreet2'],
|
||||
['oldAddressAddressCity', 'newAddressAddressCity'],
|
||||
['oldAddressAddressPostcode', 'newAddressAddressPostcode'],
|
||||
['oldAddressAddressState', 'newAddressAddressState'],
|
||||
['oldAddressAddressCountry', 'newAddressAddressCountry'],
|
||||
['oldAddressAddressLat', 'newAddressAddressLat'],
|
||||
['oldAddressAddressLng', 'newAddressAddressLng'],
|
||||
];
|
||||
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.renameColumn,
|
||||
).toHaveBeenCalledTimes(8);
|
||||
|
||||
expectedRenameCalls.forEach(([fromName, toName]) => {
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.renameColumn,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_company',
|
||||
oldColumnName: fromName,
|
||||
newColumnName: toName,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle enum field option updates with proper value mapping', async () => {
|
||||
const selectField = getFlatFieldMetadataMock({
|
||||
id: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'status',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'UPDATED_ACTIVE',
|
||||
label: 'Updated Active',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'UPDATED_INACTIVE',
|
||||
label: 'Updated Inactive',
|
||||
color: 'red',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
uniqueIdentifier: 'status',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'person',
|
||||
uniqueIdentifier: 'person',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
selectField,
|
||||
);
|
||||
|
||||
await service.runUpdateFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'update_field',
|
||||
workspaceId: mockWorkspaceId,
|
||||
fieldMetadataId: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
updates: [
|
||||
{
|
||||
property: 'options',
|
||||
from: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'ACTIVE',
|
||||
label: 'Active',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'INACTIVE',
|
||||
label: 'Inactive',
|
||||
color: 'red',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
to: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'UPDATED_ACTIVE',
|
||||
label: 'Updated Active',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'UPDATED_INACTIVE',
|
||||
label: 'Updated Inactive',
|
||||
color: 'red',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Enum values must be updated with proper mapping
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.alterEnumValues,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
enumValues: ['UPDATED_ACTIVE', 'UPDATED_INACTIVE'],
|
||||
columnDefinition: {
|
||||
name: 'status',
|
||||
type: `"${mockSchemaName}"."_person_status_enum"`,
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
},
|
||||
oldToNewEnumOptionMap: {
|
||||
ACTIVE: 'UPDATED_ACTIVE', // Keep original values
|
||||
INACTIVE: 'UPDATED_INACTIVE',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle default value updates for composite fields', async () => {
|
||||
const currencyField = getFlatFieldMetadataMock({
|
||||
id: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
name: 'price',
|
||||
defaultValue: {
|
||||
amountMicros: "'0'",
|
||||
currencyCode: "'USD'",
|
||||
},
|
||||
uniqueIdentifier: 'price',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'product',
|
||||
uniqueIdentifier: 'product',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
currencyField,
|
||||
);
|
||||
|
||||
await service.runUpdateFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'update_field',
|
||||
workspaceId: mockWorkspaceId,
|
||||
fieldMetadataId: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
updates: [
|
||||
{
|
||||
property: 'defaultValue',
|
||||
from: {
|
||||
amountMicros: "'0'",
|
||||
currencyCode: "'USD'",
|
||||
},
|
||||
to: {
|
||||
amountMicros: "'100000000'",
|
||||
currencyCode: "'EUR'",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Both composite column defaults must be updated
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.alterColumnDefault,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_product',
|
||||
columnName: 'priceAmountMicros',
|
||||
defaultValue: '100000000',
|
||||
});
|
||||
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.alterColumnDefault,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_product',
|
||||
columnName: 'priceCurrencyCode',
|
||||
defaultValue: 'EUR',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling and Edge Cases', () => {
|
||||
it('should handle fields with no join column settings gracefully', async () => {
|
||||
const relationFieldWithoutSettings = getFlatFieldMetadataMock({
|
||||
id: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'company',
|
||||
settings: undefined,
|
||||
uniqueIdentifier: 'company',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'person',
|
||||
uniqueIdentifier: 'person',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
relationFieldWithoutSettings,
|
||||
);
|
||||
|
||||
await service.runDeleteFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'delete_field',
|
||||
fieldMetadataId: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Should not attempt to drop columns for relations without join columns
|
||||
expect(
|
||||
mockSchemaManagerService.columnManager.dropColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
columnNames: [], // Empty array - no columns to drop
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle enum fields without options safely', async () => {
|
||||
const selectFieldWithoutOptions = getFlatFieldMetadataMock({
|
||||
id: mockFieldMetadataId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'emptyStatus',
|
||||
options: [],
|
||||
uniqueIdentifier: 'emptyStatus',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'test',
|
||||
uniqueIdentifier: 'test',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
selectFieldWithoutOptions,
|
||||
);
|
||||
|
||||
await service.runCreateFieldSchemaMigration({
|
||||
action: {
|
||||
type: 'create_field',
|
||||
flatFieldMetadata: selectFieldWithoutOptions,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Should still create enum even with empty values
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.createEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_test_emptyStatus_enum',
|
||||
values: [], // Empty enum values array
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
-873
@@ -1,873 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service';
|
||||
|
||||
describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
let service: WorkspaceSchemaObjectActionRunnerService;
|
||||
let mockSchemaManagerService: jest.Mocked<WorkspaceSchemaManagerService>;
|
||||
let mockQueryRunner: jest.Mocked<QueryRunner>;
|
||||
|
||||
const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
const mockObjectMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea418';
|
||||
const mockSchemaName = getWorkspaceSchemaName(mockWorkspaceId);
|
||||
|
||||
const createMockFlatObjectMetadataMaps = (
|
||||
objectMetadata: any,
|
||||
fields: any[] = [],
|
||||
) => {
|
||||
const fieldsById = fields.reduce((acc, field, index) => {
|
||||
const fieldId = `field-${index}`;
|
||||
|
||||
acc[fieldId] = { ...field, id: fieldId };
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
byId: {
|
||||
[mockObjectMetadataId]: {
|
||||
...objectMetadata,
|
||||
fieldsById,
|
||||
},
|
||||
},
|
||||
idByNameSingular: {
|
||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockSchemaManagerService = {
|
||||
columnManager: {
|
||||
dropColumns: jest.fn(),
|
||||
addColumns: jest.fn(),
|
||||
renameColumn: jest.fn(),
|
||||
alterColumnDefault: jest.fn(),
|
||||
},
|
||||
enumManager: {
|
||||
createEnum: jest.fn(),
|
||||
dropEnum: jest.fn(),
|
||||
renameEnum: jest.fn(),
|
||||
alterEnumValues: jest.fn(),
|
||||
},
|
||||
tableManager: {
|
||||
createTable: jest.fn(),
|
||||
dropTable: jest.fn(),
|
||||
renameTable: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
|
||||
mockQueryRunner = {} as any;
|
||||
|
||||
service = new WorkspaceSchemaObjectActionRunnerService(
|
||||
mockSchemaManagerService,
|
||||
);
|
||||
});
|
||||
|
||||
describe('DELETE Object Migration', () => {
|
||||
it('should properly delete objects with SELECT field enum cleanup', async () => {
|
||||
const selectField1 = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'status',
|
||||
uniqueIdentifier: 'status',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'ACTIVE',
|
||||
label: 'Active',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const selectField2 = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'priority',
|
||||
uniqueIdentifier: 'priority',
|
||||
options: [
|
||||
{ id: '1', value: 'HIGH', label: 'High', color: 'red', position: 0 },
|
||||
],
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'task',
|
||||
uniqueIdentifier: 'task',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
[selectField1, selectField2],
|
||||
);
|
||||
|
||||
await service.runDeleteObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'delete_object',
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Table must be dropped first
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.dropTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_task',
|
||||
});
|
||||
|
||||
// All enum types associated with the object must be cleaned up
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledTimes(2);
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_task_status_enum',
|
||||
});
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_task_priority_enum',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle object deletion with mixed field types', async () => {
|
||||
const textField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
uniqueIdentifier: 'description',
|
||||
});
|
||||
|
||||
const addressField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.ADDRESS,
|
||||
name: 'location',
|
||||
uniqueIdentifier: 'location',
|
||||
});
|
||||
|
||||
const multiSelectField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.MULTI_SELECT,
|
||||
name: 'tags',
|
||||
uniqueIdentifier: 'tags',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'URGENT',
|
||||
label: 'Urgent',
|
||||
color: 'red',
|
||||
position: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'project',
|
||||
uniqueIdentifier: 'project',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
[textField, addressField, multiSelectField],
|
||||
);
|
||||
|
||||
await service.runDeleteObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'delete_object',
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Table must be dropped
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.dropTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_project',
|
||||
});
|
||||
|
||||
// Only enum fields should trigger enum cleanup
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_project_tags_enum',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle object deletion with no enum fields', async () => {
|
||||
const textField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'name',
|
||||
uniqueIdentifier: 'name',
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'simpleObject',
|
||||
uniqueIdentifier: 'simpleObject',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
[textField],
|
||||
);
|
||||
|
||||
await service.runDeleteObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'delete_object',
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Table must be dropped
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.dropTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_simpleObject',
|
||||
});
|
||||
|
||||
// No enum cleanup should occur
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('CREATE Object Migration', () => {
|
||||
it('should create objects with mixed field types and proper column generation', async () => {
|
||||
const textFieldAction = {
|
||||
type: 'create_field' as const,
|
||||
flatFieldMetadata: getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'title',
|
||||
uniqueIdentifier: 'title',
|
||||
}),
|
||||
};
|
||||
|
||||
const selectFieldAction = {
|
||||
type: 'create_field' as const,
|
||||
flatFieldMetadata: getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'status',
|
||||
uniqueIdentifier: 'status',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'DRAFT',
|
||||
label: 'Draft',
|
||||
color: 'gray',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'PUBLISHED',
|
||||
label: 'Published',
|
||||
color: 'green',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
const currencyFieldAction = {
|
||||
type: 'create_field' as const,
|
||||
flatFieldMetadata: getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
name: 'price',
|
||||
uniqueIdentifier: 'price',
|
||||
}),
|
||||
};
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'article',
|
||||
uniqueIdentifier: 'article',
|
||||
});
|
||||
|
||||
await service.runCreateObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'create_object',
|
||||
flatObjectMetadataWithoutFields: objectMetadata,
|
||||
createFieldActions: [
|
||||
textFieldAction,
|
||||
selectFieldAction,
|
||||
currencyFieldAction,
|
||||
],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps: {
|
||||
byId: {
|
||||
[mockObjectMetadataId]: {
|
||||
...objectMetadata,
|
||||
fieldsById: {},
|
||||
fieldIdByJoinColumnName: {},
|
||||
fieldIdByName: {},
|
||||
},
|
||||
},
|
||||
idByNameSingular: {
|
||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Table must be created with all field columns and enums
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.createTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_article',
|
||||
columnDefinitions: [
|
||||
// TEXT field column
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
},
|
||||
// SELECT field column
|
||||
{
|
||||
name: 'status',
|
||||
type: `"${mockSchemaName}"."_article_status_enum"`,
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
},
|
||||
// CURRENCY field columns (2 composite columns)
|
||||
{
|
||||
name: 'priceAmountMicros',
|
||||
type: 'numeric',
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
},
|
||||
{
|
||||
name: 'priceCurrencyCode',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.createEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_article_status_enum',
|
||||
values: ['DRAFT', 'PUBLISHED'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle object creation with ADDRESS composite fields', async () => {
|
||||
const addressFieldAction = {
|
||||
type: 'create_field' as const,
|
||||
flatFieldMetadata: getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.ADDRESS,
|
||||
name: 'headquarters',
|
||||
isNullable: false, // Test non-nullable composite field
|
||||
uniqueIdentifier: 'headquarters',
|
||||
}),
|
||||
};
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'company',
|
||||
uniqueIdentifier: 'company',
|
||||
});
|
||||
|
||||
await service.runCreateObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'create_object',
|
||||
flatObjectMetadataWithoutFields: objectMetadata,
|
||||
createFieldActions: [addressFieldAction],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps: {
|
||||
byId: {
|
||||
[mockObjectMetadataId]: {
|
||||
...objectMetadata,
|
||||
fieldsById: {},
|
||||
fieldIdByJoinColumnName: {},
|
||||
fieldIdByName: {},
|
||||
},
|
||||
},
|
||||
idByNameSingular: {
|
||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Table must be created with all 8 ADDRESS composite columns
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.createTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_company',
|
||||
columnDefinitions: [
|
||||
{
|
||||
name: 'headquartersAddressStreet1',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
},
|
||||
{
|
||||
name: 'headquartersAddressStreet2',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
},
|
||||
{
|
||||
name: 'headquartersAddressCity',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
},
|
||||
{
|
||||
name: 'headquartersAddressPostcode',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
},
|
||||
{
|
||||
name: 'headquartersAddressState',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
},
|
||||
{
|
||||
name: 'headquartersAddressCountry',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
},
|
||||
{
|
||||
name: 'headquartersAddressLat',
|
||||
type: 'numeric',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
},
|
||||
{
|
||||
name: 'headquartersAddressLng',
|
||||
type: 'numeric',
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle object creation with no field actions', async () => {
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'emptyObject',
|
||||
uniqueIdentifier: 'emptyObject',
|
||||
});
|
||||
|
||||
await service.runCreateObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'create_object',
|
||||
flatObjectMetadataWithoutFields: objectMetadata,
|
||||
createFieldActions: [],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps: {
|
||||
byId: {
|
||||
[mockObjectMetadataId]: {
|
||||
...objectMetadata,
|
||||
fieldsById: {},
|
||||
fieldIdByJoinColumnName: {},
|
||||
fieldIdByName: {},
|
||||
},
|
||||
},
|
||||
idByNameSingular: {
|
||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Table must be created even with no columns
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.createTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_emptyObject',
|
||||
columnDefinitions: [], // Empty columns array
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('UPDATE Object Migration', () => {
|
||||
it('should handle object name changes with enum type updates', async () => {
|
||||
const selectField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'category',
|
||||
uniqueIdentifier: 'category',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'TECH',
|
||||
label: 'Technology',
|
||||
color: 'blue',
|
||||
position: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const multiSelectField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.MULTI_SELECT,
|
||||
name: 'tags',
|
||||
uniqueIdentifier: 'tags',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'FEATURED',
|
||||
label: 'Featured',
|
||||
color: 'yellow',
|
||||
position: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'blogPost',
|
||||
uniqueIdentifier: 'blogPost',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
[selectField, multiSelectField],
|
||||
);
|
||||
|
||||
await service.runUpdateObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'update_object',
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
updates: [
|
||||
{
|
||||
property: 'nameSingular',
|
||||
from: 'blogPost',
|
||||
to: 'article',
|
||||
},
|
||||
],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Table must be renamed
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.renameTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
oldTableName: '_blogPost',
|
||||
newTableName: '_article',
|
||||
});
|
||||
|
||||
// All enum types must be renamed to match new table name
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).toHaveBeenCalledTimes(2);
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
oldEnumName: '_blogPost_category_enum',
|
||||
newEnumName: '_article_category_enum',
|
||||
});
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
oldEnumName: '_blogPost_tags_enum',
|
||||
newEnumName: '_article_tags_enum',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle object name changes with no actual table rename needed', async () => {
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'person',
|
||||
uniqueIdentifier: 'person',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
[],
|
||||
);
|
||||
|
||||
await service.runUpdateObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'update_object',
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
updates: [
|
||||
{
|
||||
property: 'nameSingular',
|
||||
from: 'person',
|
||||
to: 'person', // Same name - no change needed
|
||||
},
|
||||
],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// No table operations should occur when names are the same
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.renameTable,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle object updates with complex field combinations', async () => {
|
||||
const textField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
uniqueIdentifier: 'description',
|
||||
});
|
||||
|
||||
const selectField = getFlatFieldMetadataMock({
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
type: FieldMetadataType.SELECT,
|
||||
name: 'status',
|
||||
uniqueIdentifier: 'status',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
value: 'ACTIVE',
|
||||
label: 'Active',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'oldEntity',
|
||||
uniqueIdentifier: 'oldEntity',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
||||
objectMetadata,
|
||||
[textField, selectField],
|
||||
);
|
||||
|
||||
await service.runUpdateObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'update_object',
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
updates: [
|
||||
{
|
||||
property: 'nameSingular',
|
||||
from: 'oldEntity',
|
||||
to: 'newEntity',
|
||||
},
|
||||
],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Table rename
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.renameTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
oldTableName: '_oldEntity',
|
||||
newTableName: '_newEntity',
|
||||
});
|
||||
|
||||
// Only SELECT field should trigger enum rename
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
oldEnumName: '_oldEntity_status_enum',
|
||||
newEnumName: '_newEntity_status_enum',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases and Error Conditions', () => {
|
||||
it('should handle objects with undefined fieldsById gracefully', async () => {
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'testObject',
|
||||
uniqueIdentifier: 'testObject',
|
||||
});
|
||||
|
||||
// Create maps with undefined fieldsById
|
||||
const flatObjectMetadataMaps = {
|
||||
byId: {
|
||||
[mockObjectMetadataId]: {
|
||||
...objectMetadata,
|
||||
fieldsById: undefined as unknown as Record<string, any>,
|
||||
fieldIdByJoinColumnName: {},
|
||||
fieldIdByName: {},
|
||||
},
|
||||
},
|
||||
idByNameSingular: {
|
||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
||||
},
|
||||
};
|
||||
|
||||
await service.runCreateObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'create_object',
|
||||
flatObjectMetadataWithoutFields: objectMetadata,
|
||||
createFieldActions: [],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Should still drop table
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.createTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_testObject',
|
||||
columnDefinitions: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty fieldsById object', async () => {
|
||||
const objectMetadata = getFlatObjectMetadataMock({
|
||||
id: mockObjectMetadataId,
|
||||
workspaceId: mockWorkspaceId,
|
||||
nameSingular: 'emptyFieldsObject',
|
||||
uniqueIdentifier: 'emptyFieldsObject',
|
||||
});
|
||||
|
||||
const flatObjectMetadataMaps = {
|
||||
byId: {
|
||||
[mockObjectMetadataId]: {
|
||||
...objectMetadata,
|
||||
fieldsById: {},
|
||||
fieldIdByJoinColumnName: {},
|
||||
fieldIdByName: {},
|
||||
},
|
||||
},
|
||||
idByNameSingular: {
|
||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
||||
},
|
||||
};
|
||||
|
||||
await service.runUpdateObjectSchemaMigration({
|
||||
action: {
|
||||
type: 'update_object',
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
updates: [
|
||||
{
|
||||
property: 'nameSingular',
|
||||
from: 'emptyFieldsObject',
|
||||
to: 'renamedEmptyFieldsObject',
|
||||
},
|
||||
],
|
||||
},
|
||||
queryRunner: mockQueryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
// Should rename table
|
||||
expect(
|
||||
mockSchemaManagerService.tableManager.renameTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: mockSchemaName,
|
||||
oldTableName: '_emptyFieldsObject',
|
||||
newTableName: '_renamedEmptyFieldsObject',
|
||||
});
|
||||
|
||||
// Should not perform enum operations
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class WorkspaceSchemaMigrationException extends CustomException<WorkspaceSchemaMigrationExceptionCode> {}
|
||||
|
||||
export enum WorkspaceSchemaMigrationExceptionCode {
|
||||
COMPOSITE_TYPE_NOT_FOUND = 'COMPOSITE_TYPE_NOT_FOUND',
|
||||
INVALID_FIELD_TYPE = 'INVALID_FIELD_TYPE',
|
||||
ENUM_OPERATION_FAILED = 'ENUM_OPERATION_FAILED',
|
||||
COLUMN_OPERATION_FAILED = 'COLUMN_OPERATION_FAILED',
|
||||
TABLE_OPERATION_FAILED = 'TABLE_OPERATION_FAILED',
|
||||
UNSUPPORTED_COMPOSITE_COLUMN_TYPE = 'UNSUPPORTED_COMPOSITE_COLUMN_TYPE',
|
||||
NOT_SUPPORTED = 'NOT_SUPPORTED',
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { type FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type';
|
||||
import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util';
|
||||
|
||||
import { getWorkspaceSchemaContextForMigration } from './get-workspace-schema-context-for-migration.util';
|
||||
|
||||
export interface WorkspaceSchemaContext {
|
||||
flatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps;
|
||||
schemaName: string;
|
||||
tableName: string;
|
||||
}
|
||||
|
||||
export const prepareWorkspaceSchemaContext = ({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
}: {
|
||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
objectMetadataId: string;
|
||||
}): WorkspaceSchemaContext => {
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId: flatObjectMetadataWithFlatFieldMaps.workspaceId,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithFlatFieldMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
flatObjectMetadataWithFlatFieldMaps,
|
||||
schemaName,
|
||||
tableName,
|
||||
};
|
||||
};
|
||||
|
||||
export const prepareFieldWorkspaceSchemaContext = ({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
fieldMetadataId,
|
||||
}: {
|
||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
objectMetadataId: string;
|
||||
fieldMetadataId: string;
|
||||
}): WorkspaceSchemaContext & { fieldMetadata: FlatFieldMetadata } => {
|
||||
const context = prepareWorkspaceSchemaContext({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
const fieldMetadata = findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
fieldMetadataId,
|
||||
});
|
||||
|
||||
return {
|
||||
...context,
|
||||
fieldMetadata,
|
||||
};
|
||||
};
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
type CreateIndexAction,
|
||||
type DeleteIndexAction,
|
||||
type WorkspaceMigrationIndexActionTypeV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
|
||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSchemaIndexActionRunnerService
|
||||
implements
|
||||
RunnerMethodForActionType<WorkspaceMigrationIndexActionTypeV2, 'schema'>
|
||||
{
|
||||
// TODO: Implement index schema operations
|
||||
runDeleteIndexSchemaMigration = async (
|
||||
_action: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
|
||||
) => {
|
||||
return;
|
||||
};
|
||||
|
||||
runCreateIndexSchemaMigration = async (
|
||||
_action: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
|
||||
) => {
|
||||
return;
|
||||
};
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type';
|
||||
import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service';
|
||||
import { WorkspaceSchemaIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service';
|
||||
import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSchemaMigrationRunnerService {
|
||||
constructor(
|
||||
private readonly workspaceSchemaObjectMigrationRunnerService: WorkspaceSchemaObjectActionRunnerService,
|
||||
private readonly workspaceSchemaIndexMigrationRunnerService: WorkspaceSchemaIndexActionRunnerService,
|
||||
private readonly workspaceSchemaFieldMigrationRunnerService: WorkspaceSchemaFieldActionRunnerService,
|
||||
) {}
|
||||
|
||||
runWorkspaceSchemaMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
}: WorkspaceMigrationRunnerArgs) => {
|
||||
switch (action.type) {
|
||||
case 'delete_object': {
|
||||
await this.workspaceSchemaObjectMigrationRunnerService.runDeleteObjectSchemaMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'create_object': {
|
||||
await this.workspaceSchemaObjectMigrationRunnerService.runCreateObjectSchemaMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'update_object': {
|
||||
await this.workspaceSchemaObjectMigrationRunnerService.runUpdateObjectSchemaMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'create_field': {
|
||||
await this.workspaceSchemaFieldMigrationRunnerService.runCreateFieldSchemaMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'update_field': {
|
||||
await this.workspaceSchemaFieldMigrationRunnerService.runUpdateFieldSchemaMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'delete_field': {
|
||||
await this.workspaceSchemaFieldMigrationRunnerService.runDeleteFieldSchemaMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'create_index': {
|
||||
await this.workspaceSchemaIndexMigrationRunnerService.runCreateIndexSchemaMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'delete_index': {
|
||||
await this.workspaceSchemaIndexMigrationRunnerService.runDeleteIndexSchemaMigration(
|
||||
{ action, queryRunner, flatObjectMetadataMaps },
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(
|
||||
action,
|
||||
'Should never occur, encountered an unsupported workspace migration action type',
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
-181
@@ -1,181 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import {
|
||||
type CreateObjectAction,
|
||||
type DeleteObjectAction,
|
||||
type UpdateObjectAction,
|
||||
type WorkspaceMigrationObjectActionTypeV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util';
|
||||
|
||||
import { prepareWorkspaceSchemaContext } from './utils/workspace-schema-context.util';
|
||||
import {
|
||||
collectEnumOperationsForObject,
|
||||
EnumOperation,
|
||||
executeBatchEnumOperations,
|
||||
} from './utils/workspace-schema-enum-operations.util';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSchemaObjectActionRunnerService
|
||||
implements
|
||||
RunnerMethodForActionType<WorkspaceMigrationObjectActionTypeV2, 'schema'>
|
||||
{
|
||||
constructor(
|
||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||
) {}
|
||||
|
||||
runDeleteObjectSchemaMigration = async ({
|
||||
action: { objectMetadataId },
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
}: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>) => {
|
||||
const { schemaName, tableName, flatObjectMetadataWithFlatFieldMaps } =
|
||||
prepareWorkspaceSchemaContext({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.tableManager.dropTable({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
});
|
||||
|
||||
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
||||
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
||||
)
|
||||
.filter((field): field is FlatFieldMetadata => field != null)
|
||||
.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
||||
);
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||
tableName,
|
||||
operation: EnumOperation.DROP,
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
};
|
||||
runCreateObjectSchemaMigration = async ({
|
||||
action: { flatObjectMetadataWithoutFields, createFieldActions },
|
||||
queryRunner,
|
||||
}: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>) => {
|
||||
const flatObjectMetadata = flatObjectMetadataWithoutFields;
|
||||
const schemaName = getWorkspaceSchemaName(flatObjectMetadata.workspaceId);
|
||||
const tableName = computeObjectTargetTable(flatObjectMetadata);
|
||||
|
||||
const columnDefinitions = createFieldActions.flatMap((createFieldAction) =>
|
||||
generateColumnDefinitions({
|
||||
flatFieldMetadata: createFieldAction.flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
||||
}),
|
||||
);
|
||||
|
||||
const enumOrCompositeFlatFieldMetadatas = createFieldActions
|
||||
.map((createFieldAction) => createFieldAction.flatFieldMetadata)
|
||||
.filter((field): field is FlatFieldMetadata => field != null)
|
||||
.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
||||
);
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||
tableName,
|
||||
operation: EnumOperation.CREATE,
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.tableManager.createTable({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnDefinitions,
|
||||
});
|
||||
};
|
||||
|
||||
runUpdateObjectSchemaMigration = async ({
|
||||
action,
|
||||
queryRunner,
|
||||
flatObjectMetadataMaps,
|
||||
}: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>) => {
|
||||
const { objectMetadataId, updates } = action;
|
||||
const {
|
||||
schemaName,
|
||||
tableName: currentTableName,
|
||||
flatObjectMetadataWithFlatFieldMaps,
|
||||
} = prepareWorkspaceSchemaContext({
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
for (const update of updates) {
|
||||
if (update.property !== 'nameSingular') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updatedObjectMetadata = {
|
||||
...flatObjectMetadataWithFlatFieldMaps,
|
||||
[update.property]: update.to,
|
||||
};
|
||||
|
||||
const newTableName = computeObjectTargetTable(updatedObjectMetadata);
|
||||
|
||||
if (currentTableName !== newTableName) {
|
||||
await this.workspaceSchemaManagerService.tableManager.renameTable({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
oldTableName: currentTableName,
|
||||
newTableName,
|
||||
});
|
||||
|
||||
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
||||
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
||||
)
|
||||
.filter((field): field is FlatFieldMetadata => field != null)
|
||||
.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) ||
|
||||
isCompositeFlatFieldMetadata(field),
|
||||
);
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||
tableName: currentTableName,
|
||||
operation: EnumOperation.RENAME,
|
||||
options: {
|
||||
newTableName,
|
||||
},
|
||||
});
|
||||
|
||||
await executeBatchEnumOperations({
|
||||
enumOperations,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
+3
-7
@@ -1,8 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.module';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module';
|
||||
import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -10,10 +10,6 @@ import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspa
|
||||
WorkspaceMigrationRunnerV2Module,
|
||||
],
|
||||
providers: [WorkspaceMigrationValidateBuildAndRunService],
|
||||
exports: [
|
||||
WorkspaceMigrationRunnerV2Module,
|
||||
WorkspaceMigrationBuilderV2Module,
|
||||
WorkspaceMigrationValidateBuildAndRunService,
|
||||
],
|
||||
exports: [WorkspaceMigrationValidateBuildAndRunService],
|
||||
})
|
||||
export class WorkspaceMigrationV2Module {}
|
||||
|
||||
Reference in New Issue
Block a user