Workspace migration v2 builder embed field metadata validation (#13960)

# Introduction
Following https://github.com/twentyhq/twenty/pull/13934
Finalizing object migration
Also refactored existing to follow same for const loop pattern
This commit is contained in:
Paul Rastoin
2025-08-18 16:00:58 +02:00
committed by GitHub
parent 578a2f4e6b
commit 3d3191425e
24 changed files with 771 additions and 571 deletions
@@ -59,8 +59,6 @@ exports[`Workspace migration builder field actions test suite It should build an
"createdAt": Any<String>,
"defaultValue": null,
"description": "default flat field metadata description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetObjectMetadata": null,
"icon": "icon",
"id": Any<String>,
"isActive": true,
@@ -73,8 +71,8 @@ exports[`Workspace migration builder field actions test suite It should build an
"name": "flatFieldMetadataName",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": null,
"relationTargetObjectMetadataId": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": null,
"standardId": null,
"standardOverrides": null,
@@ -85,13 +83,11 @@ exports[`Workspace migration builder field actions test suite It should build an
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "default flat object metadata description",
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "icon",
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
@@ -100,15 +96,15 @@ exports[`Workspace migration builder field actions test suite It should build an
"isSearchable": true,
"isSystem": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "default flat object metadata label plural",
"labelSingular": "default flat object metadata label singular",
"namePlural": "defaultflatObjectMetadataNamePlural",
"nameSingular": "defaultflatObjectMetadataNameSingular",
"shortcut": "shortcut",
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "",
"uniqueIdentifier": "object-metadata-unique-identifier-2",
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d78ec657-74a4-4652-a350-1f44ff62970a",
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -136,6 +132,141 @@ exports[`Workspace migration builder field actions test suite It should build an
},
"type": "create_field",
},
{
"flatFieldMetadata": {
"createdAt": Any<String>,
"defaultValue": null,
"description": "default flat field metadata description",
"flatRelationTargetFieldMetadata": {
"createdAt": Any<String>,
"defaultValue": null,
"description": "default flat field metadata description",
"flatRelationTargetFieldMetadata": {
"createdAt": Any<String>,
"defaultValue": null,
"description": "default flat field metadata description",
"icon": "icon",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUnique": false,
"label": "flat field metadata label",
"name": "flatFieldMetadataName",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": null,
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "field-metadata-unique-identifier-2",
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d78ec657-74a4-4652-a350-1f44ff62970a",
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "icon",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUnique": false,
"label": "flat field metadata label",
"name": "flatFieldMetadataName",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": null,
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "field-metadata-unique-identifier-1",
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": null,
"duplicateCriteria": null,
"icon": "IconCat",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Pets",
"labelSingular": "Pet",
"namePlural": "pets",
"nameSingular": "pet",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d34e0f07-1b8c-4de0-938e-599cf05e1f7f",
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "icon",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUnique": false,
"label": "flat field metadata label",
"name": "flatFieldMetadataName",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": null,
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "field-metadata-unique-identifier-2",
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"type": "create_field",
},
]
`;
@@ -1,5 +1,6 @@
import { faker } from '@faker-js/faker';
import { FieldMetadataType } from 'twenty-shared/types';
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
@@ -11,11 +12,10 @@ import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/fla
import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-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 { COMPANY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/company-flat-object.mock';
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock';
import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock';
import { type WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type';
import { fromFlatObjectMetadataToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util';
// TODO prastoin test defaultValue and settings updates
// TODO prastoin test standard abstraction in TDD style
@@ -25,33 +25,58 @@ const relationTestCases: WorkspaceMigrationBuilderTestCase[] = [
title: 'It should build an create_field action for a RELATION field',
context: {
input: () => {
const createdFlatRelationFieldMetadata = getFlatFieldMetadataMock({
const targetRelationId = faker.string.uuid();
const sourceRelationId = faker.string.uuid();
const sourceRelationFlatField = getFlatFieldMetadataMock({
id: sourceRelationId,
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId: PET_FLAT_OBJECT_MOCK.id,
type: FieldMetadataType.RELATION,
relationTargetFieldMetadataId: faker.string.uuid(),
relationTargetObjectMetadataId: faker.string.uuid(),
flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({
objectMetadataId: COMPANY_FLAT_OBJECT_MOCK.id,
type: FieldMetadataType.RELATION,
uniqueIdentifier: 'field-metadata-unique-identifier-2',
}),
flatRelationTargetObjectMetadata: getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-2',
}),
relationTargetFieldMetadataId: targetRelationId,
relationTargetObjectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
flatRelationTargetFieldMetadata: undefined,
flatRelationTargetObjectMetadata:
fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(
ROCKET_FLAT_OBJECT_MOCK,
),
});
const targetRelationFlatField = getFlatFieldMetadataMock({
id: targetRelationId,
uniqueIdentifier: 'field-metadata-unique-identifier-2',
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
type: FieldMetadataType.RELATION,
relationTargetFieldMetadataId: sourceRelationId,
relationTargetObjectMetadataId: PET_FLAT_OBJECT_MOCK.id,
flatRelationTargetFieldMetadata: sourceRelationFlatField,
flatRelationTargetObjectMetadata:
fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(
PET_FLAT_OBJECT_MOCK,
),
});
sourceRelationFlatField.flatRelationTargetFieldMetadata =
removePropertiesFromRecord(targetRelationFlatField, [
'flatRelationTargetFieldMetadata',
'flatRelationTargetObjectMetadata',
]);
const toFlatObjectMetadataMaps = [
sourceRelationFlatField,
targetRelationFlatField,
].reduce((flatObjectMetadataMaps, flatFieldMetadata) => {
return addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata,
flatObjectMetadataMaps,
});
}, FLAT_OBJECT_METADATA_MAPS_MOCKS);
return {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadata: createdFlatRelationFieldMetadata,
}),
toFlatObjectMetadataMaps,
};
},
expectedActionsTypeCounter: {
createField: 1,
createField: 2,
},
},
},
@@ -168,7 +193,14 @@ const basicCrudTestCases: WorkspaceMigrationBuilderTestCase[] = [
title: 'It should build a delete_field action',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
fromFlatObjectMetadataMaps:
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: getFlatFieldMetadataMock({
...PET_FLAT_FIELDS_MOCK.species,
isActive: false,
}),
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
toFlatObjectMetadataMaps: deleteFieldFromFlatObjectMetadataMapsOrThrow({
fieldMetadataId: PET_FLAT_FIELDS_MOCK.species.id,
objectMetadataId: PET_FLAT_FIELDS_MOCK.species.objectMetadataId,
@@ -1,8 +1,8 @@
import { type EachTestingContext } from 'twenty-shared/testing';
import { type WorkspaceMigrationBuildArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
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 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
import { type WorkspaceMigrationBuildArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
export type CamelCasedWorkspaceMigrationActionsType =
ConvertActionTypeToCamelCase<WorkspaceMigrationActionTypeV2>;
@@ -6,7 +6,6 @@ import { type FeatureFlagService } from 'src/engine/core-modules/feature-flag/se
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service';
import { WorkspaceMigrationV2ObjectActionsBuilder } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder';
import { WORKSPACE_MIGRATION_FIELD_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-field-test-case';
import { WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-index-test-case';
import { WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case';
@@ -15,8 +14,10 @@ import {
type ExpectedActionCounters,
type WorkspaceMigrationBuilderTestCase,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-v2-field-actions-builder.service';
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-v2-object-actions-builder.service';
import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
const allWorkspaceBuilderTestCases: {
label: string;
@@ -83,7 +84,8 @@ describe.each(allWorkspaceBuilderTestCases)(
let flatFieldMetadataTypeValidatorService: FlatFieldMetadataTypeValidatorService;
let flatFieldMetadataValidatorService: FlatFieldMetadataValidatorService;
let flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService;
let objectActionsBuilder: WorkspaceMigrationV2ObjectActionsBuilder;
let objectActionsBuilder: WorkspaceMigrationV2ObjectActionsBuilderService;
let fieldActionsBuilder: WorkspaceMigrationV2FieldActionsBuilderService;
beforeEach(() => {
featureFlagService = {
@@ -102,10 +104,17 @@ describe.each(allWorkspaceBuilderTestCases)(
flatFieldMetadataValidatorService,
);
objectActionsBuilder = new WorkspaceMigrationV2ObjectActionsBuilder(
flatObjectMetadataValidatorService,
objectActionsBuilder =
new WorkspaceMigrationV2ObjectActionsBuilderService(
flatObjectMetadataValidatorService,
);
fieldActionsBuilder = new WorkspaceMigrationV2FieldActionsBuilderService(
flatFieldMetadataValidatorService,
);
service = new WorkspaceMigrationBuilderV2Service(
objectActionsBuilder,
fieldActionsBuilder,
);
service = new WorkspaceMigrationBuilderV2Service(objectActionsBuilder);
});
it.each(eachTestingContextFilter(testCases))(
@@ -1,6 +0,0 @@
import { type FailedAndSuccessfulMetadataValidateAndBuildRecord } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type';
export const EMPTY_FAILED_AND_SUCCESSFUL_METADATA_BUILD_RECORD = {
failed: [],
successful: [],
} as const satisfies FailedAndSuccessfulMetadataValidateAndBuildRecord;
@@ -4,8 +4,9 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
import { WorkspaceMigrationV2ObjectActionsBuilder } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-v2-field-actions-builder.service';
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-v2-object-actions-builder.service';
@Module({
imports: [FeatureFlagModule],
@@ -13,8 +14,9 @@ import { WorkspaceMigrationV2ObjectActionsBuilder } from 'src/engine/workspace-m
WorkspaceMigrationBuilderV2Service,
FlatFieldMetadataValidatorService,
FlatFieldMetadataTypeValidatorService,
WorkspaceMigrationV2ObjectActionsBuilder,
WorkspaceMigrationV2ObjectActionsBuilderService,
FlatObjectMetadataValidatorService,
WorkspaceMigrationV2FieldActionsBuilderService,
],
exports: [WorkspaceMigrationBuilderV2Service],
})
@@ -2,18 +2,19 @@ import { Injectable } from '@nestjs/common';
import { type FromTo } from 'twenty-shared/types';
import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
import { fromFlatObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-maps-to-flat-object-metadatas.util';
import { deletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
import { FailedMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-v2-field-actions-builder.service';
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-v2-object-actions-builder.service';
import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
import { computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util';
import { computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util';
import { getWorkspaceMigrationV2FieldDeleteAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions';
import { getWorkspaceMigrationV2CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions';
import { buildWorkspaceMigrationV2FieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder';
import { buildWorkspaceMigrationIndexActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-index-actions-builder';
import { WorkspaceMigrationV2ObjectActionsBuilder } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder';
export type WorkspaceMigrationV2BuilderOptions = {
inferDeletionFromMissingObjectFieldIndex: boolean;
@@ -23,12 +24,12 @@ export type WorkspaceMigrationV2BuilderOptions = {
export type WorkspaceMigrationBuildArgs = {
workspaceId: string;
buildOptions: WorkspaceMigrationV2BuilderOptions;
// Could from be optional ? we still have a race condition when if we recompute cache it could have been different than when we calculated the to
} & FromTo<FlatObjectMetadataMaps, 'FlatObjectMetadataMaps'>;
@Injectable()
export class WorkspaceMigrationBuilderV2Service {
constructor(
private readonly workspaceMigrationV2ObjectActionsBuilder: WorkspaceMigrationV2ObjectActionsBuilder,
private readonly workspaceMigrationV2ObjectActionsBuilderService: WorkspaceMigrationV2ObjectActionsBuilderService,
private readonly workspaceMigrationV2FieldActionsBuilderService: WorkspaceMigrationV2FieldActionsBuilderService,
) {}
public async validateAndBuild({
@@ -37,7 +38,13 @@ export class WorkspaceMigrationBuilderV2Service {
workspaceId,
buildOptions,
}: WorkspaceMigrationBuildArgs): Promise<
| FailedMetadataValidateAndBuild
| {
status: 'fail';
errors: (
| FailedFlatObjectMetadataValidationExceptions
| FailedFlatFieldMetadataValidationExceptions
)[];
}
| {
status: 'success';
workspaceMigration: WorkspaceMigrationV2;
@@ -52,9 +59,9 @@ export class WorkspaceMigrationBuilderV2Service {
// Dispatch
const {
created: createdFlatObjectMetadata,
deleted: deletedFlatObjectMetadata,
updated: updatedFlatObjectMetadata,
created: createdFlatObjectMetadatas,
deleted: deletedFlatObjectMetadatas,
updated: updatedFlatObjectMetadatas,
} = deletedCreatedUpdatedMatrixDispatcher({
from: fromFlatObjectMetadatas,
to: toFlatObjectMetadatas,
@@ -62,30 +69,40 @@ export class WorkspaceMigrationBuilderV2Service {
const objectMetadataDeletedCreatedUpdatedIndex =
computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix(
updatedFlatObjectMetadata,
updatedFlatObjectMetadatas,
);
const objectMetadataDeletedCreatedUpdatedFields =
computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix(
updatedFlatObjectMetadata,
updatedFlatObjectMetadatas,
);
///
// Validate and build
const objectWorkspaceMigrationActions =
await this.workspaceMigrationV2ObjectActionsBuilder.validateAndBuildObjectActions(
const objectActionsValidateAndBuildResult =
await this.workspaceMigrationV2ObjectActionsBuilderService.validateAndBuildObjectActions(
{
fromFlatObjectMetadataMaps,
createdFlatObjectMetadata,
deletedFlatObjectMetadata,
createdFlatObjectMetadatas,
deletedFlatObjectMetadatas,
toFlatObjectMetadataMaps,
updatedFlatObjectMetadata,
updatedFlatObjectMetadatas,
buildOptions,
},
);
const fieldActionsValidateAndBuildResult =
await this.workspaceMigrationV2FieldActionsBuilderService.validateAndBuildFieldActions(
{
buildOptions,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
objectMetadataDeletedCreatedUpdatedFields,
},
);
const createdObjectMetadataCreateIndexActions =
createdFlatObjectMetadata.flatMap((objectMetadata) =>
createdFlatObjectMetadatas.flatMap((objectMetadata) =>
objectMetadata.flatIndexMetadatas.map(
getWorkspaceMigrationV2CreateIndexAction,
),
@@ -93,7 +110,7 @@ export class WorkspaceMigrationBuilderV2Service {
const deletedObjectWorkspaceMigrationDeleteFieldActions =
buildOptions.inferDeletionFromMissingObjectFieldIndex
? deletedFlatObjectMetadata.flatMap((flatObjectMetadata) =>
? deletedFlatObjectMetadatas.flatMap((flatObjectMetadata) =>
flatObjectMetadata.flatFieldMetadatas.map((flatFieldMetadata) =>
getWorkspaceMigrationV2FieldDeleteAction({
flatFieldMetadata,
@@ -103,13 +120,6 @@ export class WorkspaceMigrationBuilderV2Service {
)
: [];
const fieldWorkspaceMigrationActions =
buildWorkspaceMigrationV2FieldActions({
inferDeletionFromMissingObjectFieldIndex:
buildOptions.inferDeletionFromMissingObjectFieldIndex,
objectMetadataDeletedCreatedUpdatedFields,
});
const indexWorkspaceMigrationActions = buildWorkspaceMigrationIndexActions({
objectMetadataDeletedCreatedUpdatedIndex,
inferDeletionFromMissingObjectFieldIndex:
@@ -117,12 +127,15 @@ export class WorkspaceMigrationBuilderV2Service {
});
///
if (objectWorkspaceMigrationActions.results.failed.length > 0) {
const allValidateAndBuildResultFailures = [
...objectActionsValidateAndBuildResult.failed,
...fieldActionsValidateAndBuildResult.failed,
];
if (allValidateAndBuildResultFailures.length > 0) {
return {
status: 'fail',
errors: objectWorkspaceMigrationActions.results.failed.flatMap(
(el) => el.errors,
),
errors: allValidateAndBuildResultFailures,
};
}
@@ -132,11 +145,9 @@ export class WorkspaceMigrationBuilderV2Service {
workspaceId,
actions: [
...deletedObjectWorkspaceMigrationDeleteFieldActions,
...objectWorkspaceMigrationActions.results.successful.map(
(el) => el.result,
), // TMP create util
...objectActionsValidateAndBuildResult.successful,
...createdObjectMetadataCreateIndexActions,
...fieldWorkspaceMigrationActions,
...fieldActionsValidateAndBuildResult.successful,
...indexWorkspaceMigrationActions,
],
},
@@ -0,0 +1,249 @@
import { Injectable } from '@nestjs/common';
import { FieldMetadataType, FromTo } from 'twenty-shared/types';
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { compareTwoFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-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 { computeRelationTargetFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/compute-relation-target-flat-object-metadata-maps.util';
import { WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { ValidateAndBuildMetadataResult } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/validate-and-build-metadata-result.type';
import { type WorkspaceMigrationFieldActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { type UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util';
import {
getWorkspaceMigrationV2FieldCreateAction,
getWorkspaceMigrationV2FieldDeleteAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions';
type BuildWorkspaceMigrationV2FieldActionsArgs = FromTo<
FlatObjectMetadataMaps,
'flatObjectMetadataMaps'
> & {
buildOptions: WorkspaceMigrationV2BuilderOptions;
objectMetadataDeletedCreatedUpdatedFields: UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix[];
};
type ValidateFlatObjectMetadataFlatFieldMetadataMatrixArgs = FromTo<
FlatObjectMetadataMaps,
'flatObjectMetadataMaps'
> &
UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix & {
buildOptions: WorkspaceMigrationV2BuilderOptions;
};
@Injectable()
export class WorkspaceMigrationV2FieldActionsBuilderService {
constructor(
private readonly flatFieldMetadataValidatorService: FlatFieldMetadataValidatorService,
) {}
private async validateFlatObjectMetadataFlatFieldMetadataMatrix({
createdFlatFieldMetadatas,
deletedFlatFieldMetadatas,
flatObjectMetadata,
updatedFlatFieldMetadatas,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
buildOptions,
}: ValidateFlatObjectMetadataFlatFieldMetadataMatrixArgs): Promise<
ValidateAndBuildMetadataResult<WorkspaceMigrationFieldActionV2>
> {
const validateAndBuildResult: ValidateAndBuildMetadataResult<WorkspaceMigrationFieldActionV2> =
{
failed: [],
successful: [],
optimisticFlatObjectMetadataMaps: structuredClone(
fromFlatObjectMetadataMaps,
),
};
for (const {
from: fromFlatFieldMetadata,
to: toFlatFieldMetadata,
} of updatedFlatFieldMetadatas) {
const updates = compareTwoFlatFieldMetadata({
from: fromFlatFieldMetadata,
to: toFlatFieldMetadata,
});
if (updates.length === 0) {
continue;
}
const {
workspaceId,
id: fieldMetadataId,
objectMetadataId,
} = toFlatFieldMetadata;
const validationErrors =
await this.flatFieldMetadataValidatorService.validateFlatFieldMetadataUpdate(
{
existingFlatObjectMetadataMaps: fromFlatObjectMetadataMaps,
flatFieldMetadataToValidate: toFlatFieldMetadata,
workspaceId,
otherFlatObjectMetadataMapsToValidate: undefined,
},
);
if (validationErrors.length > 0) {
validateAndBuildResult.failed.push(...validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: toFlatFieldMetadata,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
});
validateAndBuildResult.successful.push({
type: 'update_field',
fieldMetadataId,
objectMetadataId,
workspaceId,
updates,
});
}
for (const flatFieldMetadataToCreate of createdFlatFieldMetadatas) {
const relationTargetFlatObjectMetadataMaps =
isFlatFieldMetadataEntityOfType(
flatFieldMetadataToCreate,
FieldMetadataType.RELATION,
) ||
isFlatFieldMetadataEntityOfType(
flatFieldMetadataToCreate,
FieldMetadataType.MORPH_RELATION,
)
? computeRelationTargetFlatObjectMetadataMaps({
flatFieldMetadata: flatFieldMetadataToCreate,
flatObjectMetadataMaps: toFlatObjectMetadataMaps,
})
: undefined;
const validationErrors =
await this.flatFieldMetadataValidatorService.validateFlatFieldMetadataCreation(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
flatFieldMetadataToValidate: flatFieldMetadataToCreate,
workspaceId: flatFieldMetadataToCreate.workspaceId,
otherFlatObjectMetadataMapsToValidate:
relationTargetFlatObjectMetadataMaps,
},
);
if (validationErrors.length > 0) {
validateAndBuildResult.failed.push(...validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: flatFieldMetadataToCreate,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
});
const createFieldAction = getWorkspaceMigrationV2FieldCreateAction({
flatFieldMetadata: flatFieldMetadataToCreate,
flatObjectMetadata,
});
validateAndBuildResult.successful.push(createFieldAction);
}
for (const flatFieldMetadataToDelete of buildOptions.inferDeletionFromMissingObjectFieldIndex
? deletedFlatFieldMetadatas
: []) {
const validationErrors =
this.flatFieldMetadataValidatorService.validateFlatFieldMetadataDeletion(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
flatFieldMetadataToDelete,
},
);
if (validationErrors.length > 0) {
validateAndBuildResult.failed.push(...validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
deleteFieldFromFlatObjectMetadataMapsOrThrow({
fieldMetadataId: flatFieldMetadataToDelete.id,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
objectMetadataId: flatFieldMetadataToDelete.objectMetadataId,
});
const deleteFieldAction = getWorkspaceMigrationV2FieldDeleteAction({
flatFieldMetadata: flatFieldMetadataToDelete,
flatObjectMetadata,
});
validateAndBuildResult.successful.push(deleteFieldAction);
}
return validateAndBuildResult;
}
public async validateAndBuildFieldActions({
buildOptions,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
objectMetadataDeletedCreatedUpdatedFields,
}: BuildWorkspaceMigrationV2FieldActionsArgs): Promise<
ValidateAndBuildMetadataResult<WorkspaceMigrationFieldActionV2>
> {
let validateAndBuildResult: ValidateAndBuildMetadataResult<WorkspaceMigrationFieldActionV2> =
{
failed: [],
successful: [],
optimisticFlatObjectMetadataMaps: structuredClone(
fromFlatObjectMetadataMaps,
),
};
for (const {
createdFlatFieldMetadatas,
deletedFlatFieldMetadatas,
updatedFlatFieldMetadatas,
flatObjectMetadata,
} of objectMetadataDeletedCreatedUpdatedFields) {
const flatObjectMetadataFlatFieldMatrixValidateAndBuildResult =
await this.validateFlatObjectMetadataFlatFieldMetadataMatrix({
createdFlatFieldMetadatas,
deletedFlatFieldMetadatas,
flatObjectMetadata,
fromFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
updatedFlatFieldMetadatas,
toFlatObjectMetadataMaps,
buildOptions,
});
validateAndBuildResult = {
failed: [
...validateAndBuildResult.failed,
...flatObjectMetadataFlatFieldMatrixValidateAndBuildResult.failed,
],
successful: [
...validateAndBuildResult.successful,
...flatObjectMetadataFlatFieldMatrixValidateAndBuildResult.successful,
],
optimisticFlatObjectMetadataMaps:
flatObjectMetadataFlatFieldMatrixValidateAndBuildResult.optimisticFlatObjectMetadataMaps,
};
}
return validateAndBuildResult;
}
}
@@ -0,0 +1,178 @@
import { Injectable } from '@nestjs/common';
import { FromTo } from 'twenty-shared/types';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util';
import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-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 { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { compareTwoFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util';
import { type CustomDeletedCreatedUpdatedMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
import { WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { ValidateAndBuildMetadataResult } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/validate-and-build-metadata-result.type';
import {
UpdateObjectAction,
WorkspaceMigrationObjectActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
import { getWorkspaceMigrationV2FieldCreateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions';
import {
getWorkspaceMigrationV2ObjectCreateAction,
getWorkspaceMigrationV2ObjectDeleteAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions';
export type CreatedDeletedUpdatedObjectMetadataInputMatrix = FromTo<
FlatObjectMetadataMaps,
'FlatObjectMetadataMaps'
> &
CustomDeletedCreatedUpdatedMatrix<
'flatObjectMetadatas',
FlatObjectMetadata
> & {
buildOptions: WorkspaceMigrationV2BuilderOptions;
fromFlatObjectMetadataMaps: FlatObjectMetadataMaps; // should be renamed to from
};
@Injectable()
export class WorkspaceMigrationV2ObjectActionsBuilderService {
constructor(
private readonly flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService,
) {}
public async validateAndBuildObjectActions({
createdFlatObjectMetadatas,
deletedFlatObjectMetadatas,
updatedFlatObjectMetadatas,
buildOptions,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
}: CreatedDeletedUpdatedObjectMetadataInputMatrix): Promise<
ValidateAndBuildMetadataResult<WorkspaceMigrationObjectActionV2>
> {
const validateAndBuildResult: ValidateAndBuildMetadataResult<WorkspaceMigrationObjectActionV2> =
{
failed: [],
successful: [],
optimisticFlatObjectMetadataMaps: structuredClone(
fromFlatObjectMetadataMaps,
),
};
for (const flatObjectMetadataToCreate of createdFlatObjectMetadatas) {
const validationErrors =
await this.flatObjectMetadataValidatorService.validateFlatObjectMetadataCreation(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate: flatObjectMetadataToCreate,
otherFlatObjectMetadataMapsToValidate: toFlatObjectMetadataMaps,
},
);
if (validationErrors.length > 0) {
validateAndBuildResult.failed.push(...validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({
flatObjectMetadata: flatObjectMetadataToCreate,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
});
const createFieldActions =
flatObjectMetadataToCreate.flatFieldMetadatas.map((flatFieldMetadata) =>
getWorkspaceMigrationV2FieldCreateAction({
flatFieldMetadata,
flatObjectMetadata: flatObjectMetadataToCreate,
}),
);
const createObjectAction = getWorkspaceMigrationV2ObjectCreateAction({
flatObjectMetadata: flatObjectMetadataToCreate,
createFieldActions,
});
validateAndBuildResult.successful.push(createObjectAction);
}
for (const flatObjectMetadataToDelete of buildOptions.inferDeletionFromMissingObjectFieldIndex
? deletedFlatObjectMetadatas
: []) {
const validationErrors =
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataDeletion(
{
buildOptions,
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
objectMetadataToDeleteId: flatObjectMetadataToDelete.id,
},
);
if (validationErrors.length > 0) {
validateAndBuildResult.failed.push(...validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
deleteObjectFromFlatObjectMetadataMapsOrThrow({
objectMetadataId: flatObjectMetadataToDelete.id,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
});
const deleteObjectAction = getWorkspaceMigrationV2ObjectDeleteAction(
flatObjectMetadataToDelete,
);
validateAndBuildResult.successful.push(deleteObjectAction);
}
for (const {
from: fromFlatObjectMetadata,
to: toFlatObjectMetadata,
} of updatedFlatObjectMetadatas) {
const objectUpdatedProperties = compareTwoFlatObjectMetadata({
from: fromFlatObjectMetadata,
to: toFlatObjectMetadata,
});
if (objectUpdatedProperties.length === 0) {
continue;
}
const validationErrors =
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataUpdate(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
updatedFlatObjectMetadata: toFlatObjectMetadata,
},
);
if (validationErrors.length > 0) {
validateAndBuildResult.failed.push(...validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadata: toFlatObjectMetadata,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
});
const updateObjectAction: UpdateObjectAction = {
type: 'update_object',
objectMetadataId: toFlatObjectMetadata.id,
updates: objectUpdatedProperties,
};
validateAndBuildResult.successful.push(updateObjectAction);
}
return validateAndBuildResult;
}
}
@@ -1,10 +0,0 @@
import { type FailedMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type';
import { type SuccessfulMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/successful-metadata-validate-and-build.type';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
export type FailedAndSuccessfulMetadataValidateAndBuildRecord<
T extends WorkspaceMigrationActionV2 = WorkspaceMigrationActionV2,
> = {
failed: FailedMetadataValidateAndBuild[];
successful: SuccessfulMetadataValidateAndBuild<T>[];
};
@@ -1,10 +0,0 @@
import { type FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
export type FailedMetadataValidateAndBuild = {
status: 'fail';
errors: (
| FailedFlatFieldMetadataValidationExceptions
| FailedFlatObjectMetadataValidationExceptions
)[];
};
@@ -1,10 +0,0 @@
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { type FailedAndSuccessfulMetadataValidateAndBuildRecord } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
export type MetadataActionValidateAndBuildResult<
T extends WorkspaceMigrationActionV2,
> = {
results: FailedAndSuccessfulMetadataValidateAndBuildRecord<T>;
optimisticFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
@@ -1,7 +0,0 @@
import { type FailedMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type';
import { type SuccessfulMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/successful-metadata-validate-and-build.type';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
export type MetadataValidateAndBuildResult<
T extends WorkspaceMigrationActionV2 = WorkspaceMigrationActionV2,
> = FailedMetadataValidateAndBuild | SuccessfulMetadataValidateAndBuild<T>;
@@ -1,8 +0,0 @@
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
export type SuccessfulMetadataValidateAndBuild<
T extends WorkspaceMigrationActionV2 = WorkspaceMigrationActionV2,
> = {
status: 'success';
result: T;
};
@@ -0,0 +1,15 @@
import { type FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
export type ValidateAndBuildMetadataResult<
T extends WorkspaceMigrationActionV2,
> = {
failed: (
| FailedFlatFieldMetadataValidationExceptions
| FailedFlatObjectMetadataValidationExceptions
)[];
successful: T[];
optimisticFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
@@ -9,7 +9,7 @@ import {
export type UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = {
flatObjectMetadata: FlatObjectMetadata;
} & CustomDeletedCreatedUpdatedMatrix<'flatFieldMetadata', FlatFieldMetadata>;
} & CustomDeletedCreatedUpdatedMatrix<'flatFieldMetadatas', FlatFieldMetadata>;
export const computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = (
updatedObjectMetadata: FromTo<FlatObjectMetadata>[],
@@ -25,9 +25,9 @@ export const computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = (
matrixAccumulator.push({
flatObjectMetadata: to,
createdFlatFieldMetadata: fieldMetadataMatrix.created,
deletedFlatFieldMetadata: fieldMetadataMatrix.deleted,
updatedFlatFieldMetadata: fieldMetadataMatrix.updated,
createdFlatFieldMetadatas: fieldMetadataMatrix.created,
deletedFlatFieldMetadatas: fieldMetadataMatrix.deleted,
updatedFlatFieldMetadatas: fieldMetadataMatrix.updated,
});
}
@@ -1,19 +0,0 @@
import { EMPTY_FAILED_AND_SUCCESSFUL_METADATA_BUILD_RECORD } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/constants/empty-failed-and-successful-metadata-build-record.constant';
import { type FailedAndSuccessfulMetadataValidateAndBuildRecord } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type';
import { type MetadataValidateAndBuildResult } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-validate-and-build-result.type';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
export const fromMetadataBuildResultsToFailedAndSuccessfulMetadataBuildRecord =
<T extends WorkspaceMigrationActionV2>(
metadataBuildResults: MetadataValidateAndBuildResult<T>[],
): FailedAndSuccessfulMetadataValidateAndBuildRecord<T> =>
metadataBuildResults.reduce(
(acc, buildResult) =>
buildResult.status === 'fail'
? {
...acc,
failed: [...acc.failed, buildResult],
}
: { ...acc, successful: [...acc.successful, buildResult] },
EMPTY_FAILED_AND_SUCCESSFUL_METADATA_BUILD_RECORD,
);
@@ -1,75 +0,0 @@
import { compareTwoFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
import {
type UpdateFieldAction,
type WorkspaceMigrationFieldActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { type UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util';
import {
getWorkspaceMigrationV2FieldCreateAction,
getWorkspaceMigrationV2FieldDeleteAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions';
type BuildWorkspaceMigrationV2FieldActionsArgs = {
inferDeletionFromMissingObjectFieldIndex: boolean;
objectMetadataDeletedCreatedUpdatedFields: UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix[];
};
export const buildWorkspaceMigrationV2FieldActions = ({
inferDeletionFromMissingObjectFieldIndex,
objectMetadataDeletedCreatedUpdatedFields,
}: BuildWorkspaceMigrationV2FieldActionsArgs): WorkspaceMigrationFieldActionV2[] => {
let allUpdatedObjectMetadataFieldActions: WorkspaceMigrationFieldActionV2[] =
[];
for (const {
createdFlatFieldMetadata,
deletedFlatFieldMetadata,
updatedFlatFieldMetadata,
flatObjectMetadata,
} of objectMetadataDeletedCreatedUpdatedFields) {
const updateFieldActions =
updatedFlatFieldMetadata.flatMap<UpdateFieldAction>(({ from, to }) => {
const updates = compareTwoFlatFieldMetadata({
from,
to,
});
if (updates.length === 0) {
return [];
}
return {
type: 'update_field',
fieldMetadataId: to.id,
objectMetadataId: to.objectMetadataId,
workspaceId: to.workspaceId,
updates,
};
});
const createFieldAction = createdFlatFieldMetadata.map(
(flatFieldMetadata) =>
getWorkspaceMigrationV2FieldCreateAction({
flatFieldMetadata,
flatObjectMetadata,
}),
);
const deleteFieldAction = inferDeletionFromMissingObjectFieldIndex
? deletedFlatFieldMetadata.map((flatFieldMetadata) =>
getWorkspaceMigrationV2FieldDeleteAction({
flatFieldMetadata,
flatObjectMetadata,
}),
)
: [];
allUpdatedObjectMetadataFieldActions =
allUpdatedObjectMetadataFieldActions.concat([
...createFieldAction,
...deleteFieldAction,
...updateFieldActions,
]);
}
return allUpdatedObjectMetadataFieldActions;
};
@@ -1,202 +0,0 @@
import { Injectable } from '@nestjs/common';
import { FromTo } from 'twenty-shared/types';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util';
import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-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 { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { compareTwoFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util';
import { type CustomDeletedCreatedUpdatedMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
import { MetadataActionValidateAndBuildResult } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-action-validate-and-build-result.type';
import { MetadataValidateAndBuildResult } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-validate-and-build-result.type';
import {
UpdateObjectAction,
WorkspaceMigrationObjectActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
import { fromMetadataBuildResultsToFailedAndSuccessfulMetadataBuildRecord } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-metadata-build-results-to-failed-and-successful-metadata-build-record.util';
import { getWorkspaceMigrationV2FieldCreateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions';
import {
getWorkspaceMigrationV2ObjectCreateAction,
getWorkspaceMigrationV2ObjectDeleteAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions';
import { WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
export type CreatedDeletedUpdatedObjectMetadataInputMatrix = FromTo<
FlatObjectMetadataMaps,
'FlatObjectMetadataMaps'
> &
CustomDeletedCreatedUpdatedMatrix<
'flatObjectMetadata',
FlatObjectMetadata
> & {
buildOptions: WorkspaceMigrationV2BuilderOptions;
fromFlatObjectMetadataMaps: FlatObjectMetadataMaps; // should be renamed to from
};
@Injectable()
export class WorkspaceMigrationV2ObjectActionsBuilder {
constructor(
private readonly flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService,
) {}
public async validateAndBuildObjectActions({
createdFlatObjectMetadata,
deletedFlatObjectMetadata,
updatedFlatObjectMetadata,
buildOptions,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
}: CreatedDeletedUpdatedObjectMetadataInputMatrix): Promise<
MetadataActionValidateAndBuildResult<WorkspaceMigrationObjectActionV2>
> {
let optimisticFlatObjectMetadataMaps = structuredClone(
fromFlatObjectMetadataMaps,
);
const createdObjectBuildResults = await Promise.all(
createdFlatObjectMetadata.map<
Promise<
MetadataValidateAndBuildResult<WorkspaceMigrationObjectActionV2>
>
>(async (flatObjectMetadata) => {
const validationErrors =
await this.flatObjectMetadataValidatorService.validateFlatObjectMetadataCreation(
{
existingFlatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate: flatObjectMetadata,
otherFlatObjectMetadataMapsToValidate: toFlatObjectMetadataMaps,
},
);
if (validationErrors.length > 0) {
return {
status: 'fail',
errors: validationErrors,
};
}
optimisticFlatObjectMetadataMaps =
addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({
flatObjectMetadata: flatObjectMetadata,
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
});
const createFieldActions = flatObjectMetadata.flatFieldMetadatas.map(
(flatFieldMetadata) =>
getWorkspaceMigrationV2FieldCreateAction({
flatFieldMetadata,
flatObjectMetadata,
}),
);
const createObjectAction = getWorkspaceMigrationV2ObjectCreateAction({
flatObjectMetadata,
createFieldActions,
});
return {
status: 'success',
result: createObjectAction,
};
}),
);
const deletedObjectBuildResults =
buildOptions.inferDeletionFromMissingObjectFieldIndex
? deletedFlatObjectMetadata.map<
MetadataValidateAndBuildResult<WorkspaceMigrationObjectActionV2>
>((flatObjectMetadataToDelete) => {
const validationErrors =
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataDeletion(
{
buildOptions,
existingFlatObjectMetadataMaps:
optimisticFlatObjectMetadataMaps,
objectMetadataToDeleteId: flatObjectMetadataToDelete.id,
},
);
if (validationErrors.length > 0) {
return {
status: 'fail',
errors: validationErrors,
};
}
optimisticFlatObjectMetadataMaps =
deleteObjectFromFlatObjectMetadataMapsOrThrow({
objectMetadataId: flatObjectMetadataToDelete.id,
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
});
const deleteObjectAction =
getWorkspaceMigrationV2ObjectDeleteAction(
flatObjectMetadataToDelete,
);
return {
status: 'success',
result: deleteObjectAction,
};
})
: [];
const updatedObjectBuildResults = updatedFlatObjectMetadata.flatMap<
MetadataValidateAndBuildResult<WorkspaceMigrationObjectActionV2>
>(({ from: fromFlatObjectMetadata, to: toFlatObjectMetadata }) => {
const objectUpdatedProperties = compareTwoFlatObjectMetadata({
from: fromFlatObjectMetadata,
to: toFlatObjectMetadata,
});
if (objectUpdatedProperties.length === 0) {
return [];
}
const validationErrors =
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataUpdate(
{
existingFlatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
updatedFlatObjectMetadata: toFlatObjectMetadata,
},
);
if (validationErrors.length > 0) {
return {
status: 'fail',
errors: validationErrors,
};
}
optimisticFlatObjectMetadataMaps =
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadata: toFlatObjectMetadata,
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
});
const updateObjectAction: UpdateObjectAction = {
type: 'update_object',
objectMetadataId: toFlatObjectMetadata.id,
updates: objectUpdatedProperties,
};
return {
status: 'success',
result: updateObjectAction,
};
});
return {
optimisticFlatObjectMetadataMaps,
results: fromMetadataBuildResultsToFailedAndSuccessfulMetadataBuildRecord(
[
...createdObjectBuildResults,
...deletedObjectBuildResults,
...updatedObjectBuildResults,
],
),
};
}
}
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module';
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/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';
@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { MultipleMetadataValidationErrors } from 'src/engine/core-modules/error/multiple-metadata-validation-errors';
import {
WorkspaceMigrationBuildArgs,
WorkspaceMigrationBuilderV2Service,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
} 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 {
WorkspaceMigrationV2Exception,
@@ -13,6 +13,10 @@ import {
@Injectable()
export class WorkspaceMigrationValidateBuildAndRunService {
private readonly logger = new Logger(
WorkspaceMigrationValidateBuildAndRunService.name,
);
constructor(
private readonly workspaceMigrationBuilderV2Service: WorkspaceMigrationBuilderV2Service,
private readonly workspaceMigrationRunnerV2Service: WorkspaceMigrationRunnerV2Service,
@@ -25,6 +29,7 @@ export class WorkspaceMigrationValidateBuildAndRunService {
const validateAndBuildResult = await this.workspaceMigrationBuilderV2Service
.validateAndBuild(builderArgs)
.catch((error) => {
this.logger.error(error);
throw new WorkspaceMigrationV2Exception(
WorkspaceMigrationV2ExceptionCode.BUILDER_INTERNAL_SERVER_ERROR,
error.message,
@@ -41,6 +46,7 @@ export class WorkspaceMigrationValidateBuildAndRunService {
await this.workspaceMigrationRunnerV2Service
.run(validateAndBuildResult.workspaceMigration)
.catch((error) => {
this.logger.error(error);
throw new WorkspaceMigrationV2Exception(
WorkspaceMigrationV2ExceptionCode.RUNNER_INTERNAL_SERVER_ERROR,
error.message,