Extract field metadata build out of object metadata build (#14763)

# Introduction

Extracting the legacy fields build and dispatch out of the object one to
follow the generic flat entity build, also update caches entries for
object

## Main tasks
- flat field map cache
- flat field builder
- refactored the dispatch matrix to return flat entity maps instead of
flat entity arrays
- orchestrator aggregator
- removing legacy code
- making universal identifier aka standardId of standard field for
custom object deterministically dynamic
- perfs debug logs for v2

## TODO
- [x] Refactor the generic entity builder to be dependency flat maps in
order to main foreign keys list in flat parent
- [x] Refactor the flat object metadata to contain the array of related
fields and avoid costy find object fields
- [ ] Improve the create field handler to handle multiple field at once
- [ ] Refactor the dispatch to embbed the comparison
- [ ] Improve perf by extracting from elements out of existing
- [ ] Fix the labelIdentifierId validators on object before field
creation ( integ tests are in failing mode )

## Debug logs snippet

```ts
[EntityBuilder fieldMetadata] matrix computation: 0.027ms
[EntityBuilder fieldMetadata] creation validation: 0.001ms
[EntityBuilder fieldMetadata] deletion validation: 0.293ms
[EntityBuilder fieldMetadata] update validation: 0.006ms
[EntityBuilder fieldMetadata] entity processing: 0.363ms
[EntityBuilder fieldMetadata] validateAndBuild: 0.455ms
[EntityBuilder index] matrix computation: 0.005ms
[EntityBuilder index] creation validation: 0.001ms
[EntityBuilder index] deletion validation: 0.146ms
[EntityBuilder index] update validation: 0.004ms
[EntityBuilder index] entity processing: 0.199ms
[EntityBuilder index] validateAndBuild: 0.228ms
[Runner] Initial cache retrieval: 0.549ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_index executeForWorkspaceSchema: 11.665ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_index executeForMetadata: 12.864ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForWorkspaceSchema: 1.476ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForMetadata: 6.816ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForWorkspaceSchema: 0.062ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForMetadata: 0.889ms
[Runner] Transaction execution: 23.434ms
[Runner] Cache invalidation: 316.662ms
[Runner] Total execution: 340.767ms
```
As you can see cache invalidation is way to long, we could replace the
cache by the optimistic in the end
This commit is contained in:
Paul Rastoin
2025-10-02 18:07:34 +02:00
committed by GitHub
parent d4b83df3f5
commit 170f6d27c5
240 changed files with 11385 additions and 17744 deletions
@@ -111,7 +111,7 @@ export const PET_DATA_SEEDS: PetDataSeed[] = [
interestingFacts: [
'Worlds Best Sock Thief',
'Expert at Puppy Eyes',
'Fearless Except Around Bananas',
'Fearless Except Around Bananas',
'Signature Ear Flip',
],
extraData: JSON.stringify({
@@ -243,6 +243,7 @@ export class DevSeederDataService {
);
if (!objectMetadata) {
// TODO this continue is hacky, we should have a record seed config per workspace
continue;
}
@@ -1,5 +1,6 @@
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { ROCKET_CUSTOM_OBJECT_SEED } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-objects/constants/rocket-custom-object-seed.constant';
import { type FieldMetadataSeed } from 'src/engine/workspace-manager/dev-seeder/metadata/types/field-metadata-seed.type';
export const PET_CUSTOM_RELATION_FIELD_SEEDS: (FieldMetadataSeed & {
@@ -18,7 +19,10 @@ export const PET_CUSTOM_RELATION_FIELD_SEEDS: (FieldMetadataSeed & {
targetFieldIcon: 'IconRelationOneToMany',
},
],
targetObjectMetadataNames: ['surveyResult', 'rocket'],
targetObjectMetadataNames: [
'surveyResult',
ROCKET_CUSTOM_OBJECT_SEED.nameSingular,
],
},
{
type: FieldMetadataType.MORPH_RELATION,
@@ -33,6 +37,9 @@ export const PET_CUSTOM_RELATION_FIELD_SEEDS: (FieldMetadataSeed & {
targetFieldIcon: 'IconRelationOneToMany',
},
],
targetObjectMetadataNames: ['surveyResult', 'rocket'],
targetObjectMetadataNames: [
'surveyResult',
ROCKET_CUSTOM_OBJECT_SEED.nameSingular,
],
},
];
@@ -6,7 +6,8 @@ import { DataSource } from 'typeorm';
import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
import { FieldMetadataServiceV2 } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2';
import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
@@ -30,7 +31,8 @@ import { prefillCoreViews } from 'src/engine/workspace-manager/standard-objects-
export class DevSeederMetadataService {
constructor(
private readonly objectMetadataService: ObjectMetadataService,
private readonly fieldMetadataService: FieldMetadataService,
private readonly objectMetadataServiceV2: ObjectMetadataServiceV2,
private readonly fieldMetadataServiceV2: FieldMetadataServiceV2,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
@@ -61,7 +63,10 @@ export class DevSeederMetadataService {
{ objectName: 'person', seeds: PERSON_CUSTOM_FIELD_SEEDS },
],
relations: [
{ objectName: 'pet', seeds: PET_CUSTOM_RELATION_FIELD_SEEDS },
{
objectName: PET_CUSTOM_OBJECT_SEED.nameSingular,
seeds: PET_CUSTOM_RELATION_FIELD_SEEDS,
},
],
},
[SEED_YCOMBINATOR_WORKSPACE_ID]: {
@@ -131,9 +136,11 @@ export class DevSeederMetadataService {
workspaceId: string;
objectMetadataSeed: ObjectMetadataSeed;
}): Promise<void> {
await this.objectMetadataService.createOne({
...objectMetadataSeed,
dataSourceId,
await this.objectMetadataServiceV2.createOne({
createObjectInput: {
...objectMetadataSeed,
dataSourceId,
},
workspaceId,
});
}
@@ -152,19 +159,20 @@ export class DevSeederMetadataService {
where: { nameSingular: objectMetadataNameSingular },
});
if (!objectMetadata) {
if (!isDefined(objectMetadata)) {
throw new Error(
`Object metadata not found for: ${objectMetadataNameSingular}`,
);
}
const createFieldInputs = fieldMetadataSeeds.map((fieldMetadataSeed) => ({
...fieldMetadataSeed,
objectMetadataId: objectMetadata.id,
}));
await this.fieldMetadataService.createMany(
fieldMetadataSeeds.map((fieldMetadataSeed) => ({
...fieldMetadataSeed,
objectMetadataId: objectMetadata.id,
workspaceId,
})),
);
await this.fieldMetadataServiceV2.createMany({
createFieldInputs,
workspaceId,
});
}
private async seedCoreViews({
@@ -226,27 +234,27 @@ export class DevSeederMetadataService {
},
);
const relationFieldInputs = this.createFieldInputs({
const createFieldInputs = this.createFieldInputs({
relation,
objectMetadataMaps,
workspaceId,
});
await this.fieldMetadataService.createMany(relationFieldInputs);
await this.fieldMetadataServiceV2.createMany({
createFieldInputs,
workspaceId,
});
}
private createFieldInputs({
relation,
objectMetadataMaps,
workspaceId,
}: {
relation: {
objectName: string;
seeds: (FieldMetadataSeed & { targetObjectMetadataNames: string[] })[];
};
objectMetadataMaps: ObjectMetadataMaps;
workspaceId: string;
}): CreateFieldInput[] {
}): Omit<CreateFieldInput, 'workspaceId'>[] {
const objectMetadataId =
objectMetadataMaps.idByNameSingular[relation.objectName];
@@ -261,7 +269,6 @@ export class DevSeederMetadataService {
label: seed.label,
name: seed.name,
objectMetadataId,
workspaceId,
morphRelationsCreationPayload: seed.targetObjectMetadataNames.map(
(targetObjectMetadataName) => {
const targetObjectMetadataId =
@@ -13,9 +13,11 @@ export const fromWorkspaceMigrationBuilderExceptionToValidationResponseError = (
invalidServerlessFunction: 0,
invalidDatabaseEventTrigger: 0,
invalidCronTrigger: 0,
invalidFieldMetadata: 0,
totalErrors: 0,
},
errors: {
fieldMetadata: [],
index: [],
objectMetadata: [],
view: [],
@@ -1,4 +1,4 @@
import { Injectable, Logger } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
@@ -11,28 +11,26 @@ import {
WorkspaceMigrationOrchestratorFailedResult,
WorkspaceMigrationOrchestratorSuccessfulResult,
} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { aggregateOrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report.util';
import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/services/workspace-migration-v2-field-actions-builder.service';
import { WorkspaceMigrationV2IndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service';
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-actions-builder.service';
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
import { WorkspaceMigrationV2ViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
@Injectable()
export class WorkspaceMigrationBuildOrchestratorService {
private readonly logger = new Logger(
WorkspaceMigrationBuildOrchestratorService.name,
);
constructor(
private readonly workspaceMigrationBuilderV2Service: WorkspaceMigrationBuilderV2Service,
private readonly workspaceMigrationV2ObjectActionsBuilderService: WorkspaceMigrationV2ObjectActionsBuilderService,
private readonly workspaceMigrationV2IndexActionsBuilderService: WorkspaceMigrationV2IndexActionsBuilderService,
private readonly workspaceMigrationV2ViewActionsBuilderService: WorkspaceMigrationV2ViewActionsBuilderService,
private readonly workspaceMigrationV2ViewFieldActionsBuilderService: WorkspaceMigrationV2ViewFieldActionsBuilderService,
private readonly workspaceMigrationV2ServerlessFunctionActionsBuilderService: WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
private readonly workspaceMigrationV2DatabaseEventTriggerActionsBuilderService: WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService,
private readonly workspaceMigrationV2CronTriggerActionsBuilderService: WorkspaceMigrationV2CronTriggerActionsBuilderService,
private readonly workspaceMigrationV2FieldActionsBuilderService: WorkspaceMigrationV2FieldActionsBuilderService,
) {}
private setupOptimisticCache({
@@ -86,6 +84,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
serverlessFunction: [],
databaseEventTrigger: [],
cronTrigger: [],
fieldMetadata: [],
};
const optimisticAllFlatEntityMaps = this.setupOptimisticCache({
@@ -100,6 +99,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatServerlessFunctionMaps,
flatDatabaseEventTriggerMaps,
flatCronTriggerMaps,
flatFieldMetadataMaps,
} = fromToAllFlatEntityMaps;
if (isDefined(flatObjectMetadataMaps)) {
@@ -107,20 +107,57 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatObjectMetadataMaps;
const objectResult =
await this.workspaceMigrationBuilderV2Service.validateAndBuild({
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
buildOptions,
});
await this.workspaceMigrationV2ObjectActionsBuilderService.validateAndBuild(
{
buildOptions,
dependencyOptimisticFlatEntityMaps: undefined,
// TODO That's hacky also not reliable ?
// dependencyOptimisticFlatEntityMaps: {
// flatFieldMetadataMaps:
// flatFieldMetadataMaps?.to ?? EMPTY_FLAT_ENTITY_MAPS,
// },
from: fromFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
workspaceId,
},
);
optimisticAllFlatEntityMaps.flatObjectMetadataMaps =
objectResult.optimisticFlatObjectMetadataMaps;
objectResult.optimisticFlatEntityMaps;
if (objectResult.status === 'fail') {
orchestratorFailureReport.objectMetadata.push(...objectResult.errors);
} else {
orchestratorActionsReport.fieldMetadata = objectResult.fieldsActions;
orchestratorActionsReport.objectMetadata = objectResult.objectActions;
orchestratorActionsReport.objectMetadata = objectResult.actions;
}
}
if (isDefined(flatFieldMetadataMaps)) {
const { from: fromFlatFieldMetadataMaps, to: toFlatFieldMetadataMaps } =
flatFieldMetadataMaps;
const fieldResult =
await this.workspaceMigrationV2FieldActionsBuilderService.validateAndBuild(
{
from: fromFlatFieldMetadataMaps,
to: toFlatFieldMetadataMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {
flatObjectMetadataMaps:
optimisticAllFlatEntityMaps.flatObjectMetadataMaps,
},
workspaceId,
},
);
optimisticAllFlatEntityMaps.flatFieldMetadataMaps =
fieldResult.optimisticFlatEntityMaps;
optimisticAllFlatEntityMaps.flatObjectMetadataMaps =
fieldResult.dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps;
if (fieldResult.status === 'fail') {
orchestratorFailureReport.fieldMetadata.push(...fieldResult.errors);
} else {
orchestratorActionsReport.fieldMetadata = fieldResult.actions;
}
}
@@ -133,14 +170,19 @@ export class WorkspaceMigrationBuildOrchestratorService {
to: toFlatIndexMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {
flatFieldMetadataMaps:
optimisticAllFlatEntityMaps.flatFieldMetadataMaps,
flatObjectMetadataMaps:
optimisticAllFlatEntityMaps.flatObjectMetadataMaps,
},
workspaceId,
},
);
optimisticAllFlatEntityMaps.flatIndexMaps =
indexResult.optimisticFlatEntityMaps;
optimisticAllFlatEntityMaps.flatObjectMetadataMaps =
indexResult.dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps;
if (indexResult.status === 'fail') {
orchestratorFailureReport.index.push(...indexResult.errors);
@@ -161,6 +203,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
from: fromFlatViewMaps,
to: toFlatViewMaps,
buildOptions,
workspaceId,
},
);
@@ -184,15 +227,18 @@ export class WorkspaceMigrationBuildOrchestratorService {
to: toFlatViewFieldMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {
flatObjectMetadataMaps:
optimisticAllFlatEntityMaps.flatObjectMetadataMaps,
flatFieldMetadataMaps:
optimisticAllFlatEntityMaps.flatFieldMetadataMaps,
flatViewMaps: optimisticAllFlatEntityMaps.flatViewMaps,
},
workspaceId,
},
);
optimisticAllFlatEntityMaps.flatViewFieldMaps =
viewFieldResult.optimisticFlatEntityMaps;
optimisticAllFlatEntityMaps.flatViewMaps =
viewFieldResult.dependencyOptimisticFlatEntityMaps.flatViewMaps;
if (viewFieldResult.status === 'fail') {
orchestratorFailureReport.viewField.push(...viewFieldResult.errors);
@@ -213,7 +259,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
from: fromFlatServerlessFunctionMaps,
to: toFlatServerlessFunctionMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {} as AllFlatEntityMaps,
dependencyOptimisticFlatEntityMaps: undefined,
workspaceId,
},
);
@@ -242,7 +289,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
from: fromFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {} as AllFlatEntityMaps,
workspaceId,
dependencyOptimisticFlatEntityMaps: undefined,
},
);
@@ -269,7 +317,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
from: fromFlatCronTriggerMaps,
to: toFlatCronTriggerMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {} as AllFlatEntityMaps,
workspaceId,
dependencyOptimisticFlatEntityMaps: undefined,
},
);
@@ -292,9 +341,17 @@ export class WorkspaceMigrationBuildOrchestratorService {
};
}
const relatedFlatEntityMapsKeys = Object.keys(
fromToAllFlatEntityMaps,
) as (keyof AllFlatEntityMaps)[];
const relatedFlatEntityMapsKeys = [
...new Set([
...Object.keys(fromToAllFlatEntityMaps),
...Object.keys(dependencyAllFlatEntityMaps ?? {}),
]),
] as (keyof AllFlatEntityMaps)[];
const { aggregatedOrchestratorActionsReport } =
aggregateOrchestratorActionsReport({
orchestratorActionsReport,
});
return {
status: 'success',
@@ -302,42 +359,42 @@ export class WorkspaceMigrationBuildOrchestratorService {
relatedFlatEntityMapsKeys,
actions: [
// Object and fields and indexes
...orchestratorActionsReport.index.deleted,
...orchestratorActionsReport.fieldMetadata.deleted,
...orchestratorActionsReport.objectMetadata.deleted,
...orchestratorActionsReport.objectMetadata.created,
...orchestratorActionsReport.objectMetadata.updated,
...orchestratorActionsReport.fieldMetadata.created,
...orchestratorActionsReport.fieldMetadata.updated,
...orchestratorActionsReport.index.created,
...orchestratorActionsReport.index.updated,
...aggregatedOrchestratorActionsReport.index.deleted,
...aggregatedOrchestratorActionsReport.fieldMetadata.deleted,
...aggregatedOrchestratorActionsReport.objectMetadata.deleted,
...aggregatedOrchestratorActionsReport.objectMetadata.created,
...aggregatedOrchestratorActionsReport.objectMetadata.updated,
...aggregatedOrchestratorActionsReport.fieldMetadata.created,
...aggregatedOrchestratorActionsReport.fieldMetadata.updated,
...aggregatedOrchestratorActionsReport.index.created,
...aggregatedOrchestratorActionsReport.index.updated,
///
// Views
...orchestratorActionsReport.view.deleted,
...orchestratorActionsReport.view.created,
...orchestratorActionsReport.view.updated,
...orchestratorActionsReport.viewField.deleted,
...orchestratorActionsReport.viewField.created,
...orchestratorActionsReport.viewField.updated,
...aggregatedOrchestratorActionsReport.view.deleted,
...aggregatedOrchestratorActionsReport.view.created,
...aggregatedOrchestratorActionsReport.view.updated,
...aggregatedOrchestratorActionsReport.viewField.deleted,
...aggregatedOrchestratorActionsReport.viewField.created,
...aggregatedOrchestratorActionsReport.viewField.updated,
///
// Serverless functions
...orchestratorActionsReport.serverlessFunction.deleted,
...orchestratorActionsReport.serverlessFunction.created,
...orchestratorActionsReport.serverlessFunction.updated,
...aggregatedOrchestratorActionsReport.serverlessFunction.deleted,
...aggregatedOrchestratorActionsReport.serverlessFunction.created,
...aggregatedOrchestratorActionsReport.serverlessFunction.updated,
///
// Database event triggers
...orchestratorActionsReport.databaseEventTrigger.deleted,
...orchestratorActionsReport.databaseEventTrigger.created,
...orchestratorActionsReport.databaseEventTrigger.updated,
...aggregatedOrchestratorActionsReport.databaseEventTrigger.deleted,
...aggregatedOrchestratorActionsReport.databaseEventTrigger.created,
...aggregatedOrchestratorActionsReport.databaseEventTrigger.updated,
///
// Cron triggers
...orchestratorActionsReport.cronTrigger.deleted,
...orchestratorActionsReport.cronTrigger.created,
...orchestratorActionsReport.cronTrigger.updated,
...aggregatedOrchestratorActionsReport.cronTrigger.deleted,
...aggregatedOrchestratorActionsReport.cronTrigger.created,
...aggregatedOrchestratorActionsReport.cronTrigger.updated,
///
],
workspaceId,
@@ -11,7 +11,6 @@ import {
WorkspaceMigrationV2ExceptionCode,
} from 'src/engine/workspace-manager/workspace-migration.exception';
// TODO: replace build logic by orchestrator service
@Injectable()
export class WorkspaceMigrationValidateBuildAndRunService {
private readonly logger = new Logger(
@@ -4,13 +4,13 @@ import { type AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-module
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { type CreatedDeletedUpdatedActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { type WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-builder-options.type';
import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
export type WorkspaceMigrationOrchestratorBuildArgs = {
workspaceId: string;
buildOptions: WorkspaceMigrationV2BuilderOptions;
buildOptions: WorkspaceMigrationBuilderOptions;
fromToAllFlatEntityMaps: {
[P in keyof AllFlatEntityMaps]?: FromTo<AllFlatEntityMaps[P]>;
};
@@ -24,6 +24,7 @@ export type OrchestratorFailureReport = {
};
export type OrchestratorActionsReport = {
// TODO could improve this typing but making generic actions and using extract
[P in keyof AllFlatEntitiesByMetadataEngineName]: CreatedDeletedUpdatedActions<WorkspaceMigrationActionV2>;
} & {
fieldMetadata: CreatedDeletedUpdatedActions<WorkspaceMigrationActionV2>;
@@ -0,0 +1,423 @@
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
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 { EMPTY_ORCHESTRATOR_ACTIONS_REPORT } from 'src/engine/workspace-manager/workspace-migration-v2/constant/empty-orchestrator-actions-report.constant';
import { type OrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { aggregateOrchestratorActionsReportCreateObjectAndCreateFieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report-create-object-and-create-field-actions.util';
import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { type CreateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
type CreateAggregationTestCase = EachTestingContext<{
input: OrchestratorActionsReport;
expected: {
expectCreateFieldActionPerObjectMetadataId: Record<string, number>;
expectCreateObjectActionPerObjectMetadataId: Record<string, number>;
};
}>;
describe('aggregateOrchestratorActionsReportCreateObjectAndCreateFieldActions', () => {
const testCases: CreateAggregationTestCase[] = [
{
title:
'should aggregate single object with multiple fields into one object action',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [
{
type: 'create_object',
flatObjectMetadata: getFlatObjectMetadataMock({
universalIdentifier: 'object-1',
id: 'object-1',
nameSingular: 'user',
namePlural: 'users',
}),
flatFieldMetadatas: [],
} satisfies CreateObjectAction,
],
updated: [],
deleted: [],
},
fieldMetadata: {
created: [
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-1',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-1',
name: 'firstName',
}),
getFlatFieldMetadataMock({
universalIdentifier: 'field-2',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-2',
name: 'lastName',
}),
],
} satisfies CreateFieldAction,
],
updated: [],
deleted: [],
},
} satisfies OrchestratorActionsReport,
expected: {
expectCreateFieldActionPerObjectMetadataId: {},
expectCreateObjectActionPerObjectMetadataId: {
'object-1': 1,
},
},
},
},
{
title:
'should keep separate field actions when no matching object action exists',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [],
updated: [],
deleted: [],
},
fieldMetadata: {
created: [
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-1',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-1',
name: 'firstName',
}),
],
} satisfies CreateFieldAction,
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-2',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-2',
name: 'secondName',
}),
],
} satisfies CreateFieldAction,
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-3',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-3',
name: 'lastName',
}),
],
} satisfies CreateFieldAction,
],
updated: [],
deleted: [],
},
} satisfies OrchestratorActionsReport,
expected: {
expectCreateFieldActionPerObjectMetadataId: {
'object-1': 1,
},
expectCreateObjectActionPerObjectMetadataId: {},
},
},
},
{
title: 'should handle multiple objects with their respective fields',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [
{
type: 'create_object',
flatObjectMetadata: getFlatObjectMetadataMock({
universalIdentifier: 'object-1',
id: 'object-1',
nameSingular: 'user',
namePlural: 'users',
}),
flatFieldMetadatas: [],
} satisfies CreateObjectAction,
{
type: 'create_object',
flatObjectMetadata: getFlatObjectMetadataMock({
universalIdentifier: 'object-2',
id: 'object-2',
nameSingular: 'company',
namePlural: 'companies',
}),
flatFieldMetadatas: [],
} satisfies CreateObjectAction,
],
updated: [],
deleted: [],
},
fieldMetadata: {
created: [
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-1',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-1',
name: 'firstName',
}),
],
} satisfies CreateFieldAction,
{
type: 'create_field',
objectMetadataId: 'object-2',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-2',
objectMetadataId: 'object-2',
type: FieldMetadataType.TEXT,
id: 'field-2',
name: 'name',
}),
getFlatFieldMetadataMock({
universalIdentifier: 'field-3',
objectMetadataId: 'object-2',
type: FieldMetadataType.TEXT,
id: 'field-3',
name: 'industry',
}),
],
} satisfies CreateFieldAction,
],
updated: [],
deleted: [],
},
} satisfies OrchestratorActionsReport,
expected: {
expectCreateFieldActionPerObjectMetadataId: {},
expectCreateObjectActionPerObjectMetadataId: {
'object-1': 1,
'object-2': 1,
},
},
},
},
{
title:
'should handle mixed scenario with some fields merged and some kept separate',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [
{
type: 'create_object',
flatObjectMetadata: getFlatObjectMetadataMock({
universalIdentifier: 'object-1',
id: 'object-1',
nameSingular: 'user',
namePlural: 'users',
}),
flatFieldMetadatas: [],
} satisfies CreateObjectAction,
],
updated: [],
deleted: [],
},
fieldMetadata: {
created: [
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-1',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-1',
name: 'firstName',
}),
],
} satisfies CreateFieldAction,
{
type: 'create_field',
objectMetadataId: 'object-2',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-2',
objectMetadataId: 'object-2',
type: FieldMetadataType.TEXT,
id: 'field-2',
name: 'orphanField',
}),
],
} satisfies CreateFieldAction,
],
updated: [],
deleted: [],
},
} satisfies OrchestratorActionsReport,
expected: {
expectCreateFieldActionPerObjectMetadataId: {
'object-2': 1,
},
expectCreateObjectActionPerObjectMetadataId: {
'object-1': 1,
},
},
},
},
{
title:
'should aggregate multiple field actions for the same object when no create object action exists',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [],
updated: [],
deleted: [],
},
fieldMetadata: {
created: [
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-1',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-1',
name: 'firstName',
}),
],
} satisfies CreateFieldAction,
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-2',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-2',
name: 'lastName',
}),
],
} satisfies CreateFieldAction,
{
type: 'create_field',
objectMetadataId: 'object-1',
flatFieldMetadatas: [
getFlatFieldMetadataMock({
universalIdentifier: 'field-3',
objectMetadataId: 'object-1',
type: FieldMetadataType.TEXT,
id: 'field-3',
name: 'email',
}),
],
} satisfies CreateFieldAction,
],
updated: [],
deleted: [],
},
} satisfies OrchestratorActionsReport,
expected: {
expectCreateFieldActionPerObjectMetadataId: {
'object-1': 1,
},
expectCreateObjectActionPerObjectMetadataId: {},
},
},
},
{
title: 'should handle empty actions report',
context: {
input: EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
expected: {
expectCreateFieldActionPerObjectMetadataId: {},
expectCreateObjectActionPerObjectMetadataId: {},
},
},
},
];
test.each(eachTestingContextFilter(testCases))(
'$title',
({ context: { input, expected } }) => {
const result =
aggregateOrchestratorActionsReportCreateObjectAndCreateFieldActions({
orchestratorActionsReport: input,
});
const fieldActions = result.fieldMetadata.created as CreateFieldAction[];
const fieldActionCounts = fieldActions.reduce(
(acc, action) => {
acc[action.objectMetadataId] =
(acc[action.objectMetadataId] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
const objectActions = result.objectMetadata
.created as CreateObjectAction[];
const objectActionCounts = objectActions.reduce(
(acc, action) => {
acc[action.flatObjectMetadata.id] =
(acc[action.flatObjectMetadata.id] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
Object.entries(
expected.expectCreateFieldActionPerObjectMetadataId,
).forEach(([objectId, expectedCount]) => {
expect(fieldActionCounts[objectId]).toBe(expectedCount);
});
Object.entries(
expected.expectCreateObjectActionPerObjectMetadataId,
).forEach(([objectId, expectedCount]) => {
expect(objectActionCounts[objectId]).toBe(expectedCount);
});
// Check total counts
const expectedTotalFieldActions = Object.values(
expected.expectCreateFieldActionPerObjectMetadataId,
).reduce((sum, count) => sum + count, 0);
const expectedTotalObjectActions = Object.values(
expected.expectCreateObjectActionPerObjectMetadataId,
).reduce((sum, count) => sum + count, 0);
expect(fieldActions).toHaveLength(expectedTotalFieldActions);
expect(objectActions).toHaveLength(expectedTotalObjectActions);
},
);
});
@@ -0,0 +1,254 @@
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { EMPTY_ORCHESTRATOR_ACTIONS_REPORT } from 'src/engine/workspace-manager/workspace-migration-v2/constant/empty-orchestrator-actions-report.constant';
import { type OrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { aggregateOrchestratorActionsReportDeleteObjectAndDeleteFieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report-delete-object-and-delete-field.util';
import { type DeleteFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { type DeleteObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
type DeleteAggregationTestCase = EachTestingContext<{
input: OrchestratorActionsReport;
expected: {
expectDeleteFieldActionPerObjectMetadataId: Record<string, number>;
expectDeleteObjectActionPerObjectMetadataId: Record<string, number>;
};
}>;
describe('aggregateOrchestratorActionsReportDeleteObjectAndDeleteFieldActions', () => {
const testCases: DeleteAggregationTestCase[] = [
{
title: 'should remove field actions when parent object is being deleted',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [],
updated: [],
deleted: [
{
type: 'delete_object',
objectMetadataId: 'object-1',
} satisfies DeleteObjectAction,
],
},
fieldMetadata: {
created: [],
updated: [],
deleted: [
{
type: 'delete_field',
fieldMetadataId: 'field-1',
objectMetadataId: 'object-1',
} satisfies DeleteFieldAction,
{
type: 'delete_field',
fieldMetadataId: 'field-2',
objectMetadataId: 'object-1',
} satisfies DeleteFieldAction,
],
},
} satisfies OrchestratorActionsReport,
expected: {
expectDeleteFieldActionPerObjectMetadataId: {},
expectDeleteObjectActionPerObjectMetadataId: {
'object-1': 1,
},
},
},
},
{
title: 'should keep field actions when no parent object is being deleted',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [],
updated: [],
deleted: [],
},
fieldMetadata: {
created: [],
updated: [],
deleted: [
{
type: 'delete_field',
fieldMetadataId: 'field-1',
objectMetadataId: 'object-1',
} satisfies DeleteFieldAction,
],
},
} satisfies OrchestratorActionsReport,
expected: {
expectDeleteFieldActionPerObjectMetadataId: {
'object-1': 1,
},
expectDeleteObjectActionPerObjectMetadataId: {},
},
},
},
{
title:
'should handle mixed scenario with some fields removed and some kept',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [],
updated: [],
deleted: [
{
type: 'delete_object',
objectMetadataId: 'object-1',
} satisfies DeleteObjectAction,
],
},
fieldMetadata: {
created: [],
updated: [],
deleted: [
{
type: 'delete_field',
fieldMetadataId: 'field-1',
objectMetadataId: 'object-1',
} satisfies DeleteFieldAction,
{
type: 'delete_field',
fieldMetadataId: 'field-2',
objectMetadataId: 'object-2',
} satisfies DeleteFieldAction,
],
},
} satisfies OrchestratorActionsReport,
expected: {
expectDeleteFieldActionPerObjectMetadataId: {
'object-2': 1,
},
expectDeleteObjectActionPerObjectMetadataId: {
'object-1': 1,
},
},
},
},
{
title: 'should handle multiple objects with mixed field deletions',
context: {
input: {
...EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
objectMetadata: {
created: [],
updated: [],
deleted: [
{
type: 'delete_object',
objectMetadataId: 'object-1',
} satisfies DeleteObjectAction,
{
type: 'delete_object',
objectMetadataId: 'object-2',
} satisfies DeleteObjectAction,
],
},
fieldMetadata: {
created: [],
updated: [],
deleted: [
{
type: 'delete_field',
fieldMetadataId: 'field-1',
objectMetadataId: 'object-1',
} satisfies DeleteFieldAction,
{
type: 'delete_field',
fieldMetadataId: 'field-2',
objectMetadataId: 'object-2',
} satisfies DeleteFieldAction,
{
type: 'delete_field',
fieldMetadataId: 'field-3',
objectMetadataId: 'object-3',
} satisfies DeleteFieldAction,
],
},
} satisfies OrchestratorActionsReport,
expected: {
expectDeleteFieldActionPerObjectMetadataId: {
'object-3': 1,
},
expectDeleteObjectActionPerObjectMetadataId: {
'object-1': 1,
'object-2': 1,
},
},
},
},
{
title: 'should handle empty actions report',
context: {
input: EMPTY_ORCHESTRATOR_ACTIONS_REPORT,
expected: {
expectDeleteFieldActionPerObjectMetadataId: {},
expectDeleteObjectActionPerObjectMetadataId: {},
},
},
},
];
test.each(eachTestingContextFilter(testCases))(
'$title',
({ context: { input, expected } }) => {
const result =
aggregateOrchestratorActionsReportDeleteObjectAndDeleteFieldActions({
orchestratorActionsReport: input,
});
const fieldActions = result.fieldMetadata.deleted as DeleteFieldAction[];
const fieldActionCounts = fieldActions.reduce(
(acc, action) => {
acc[action.objectMetadataId] =
(acc[action.objectMetadataId] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
const objectActions = result.objectMetadata
.deleted as DeleteObjectAction[];
const objectActionCounts = objectActions.reduce(
(acc, action) => {
acc[action.objectMetadataId] =
(acc[action.objectMetadataId] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
Object.entries(
expected.expectDeleteFieldActionPerObjectMetadataId,
).forEach(([objectId, expectedCount]) => {
expect(fieldActionCounts[objectId]).toBe(expectedCount);
});
Object.entries(
expected.expectDeleteObjectActionPerObjectMetadataId,
).forEach(([objectId, expectedCount]) => {
expect(objectActionCounts[objectId]).toBe(expectedCount);
});
// Check total counts
const expectedTotalFieldActions = Object.values(
expected.expectDeleteFieldActionPerObjectMetadataId,
).reduce((sum, count) => sum + count, 0);
const expectedTotalObjectActions = Object.values(
expected.expectDeleteObjectActionPerObjectMetadataId,
).reduce((sum, count) => sum + count, 0);
expect(fieldActions).toHaveLength(expectedTotalFieldActions);
expect(objectActions).toHaveLength(expectedTotalObjectActions);
},
);
});
@@ -0,0 +1,111 @@
import { isDefined } from 'twenty-shared/utils';
import { type OrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { type CreateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
type AggregateOrchestratorActionsReportCreateObjectAndCreateFieldActionsArgs = {
orchestratorActionsReport: OrchestratorActionsReport;
};
type AggregatedActions = {
createdFieldActionByObjectMetadataId: Record<string, CreateFieldAction>;
createdObjectActionByObjectMetadataId: Record<string, CreateObjectAction>;
};
export const aggregateOrchestratorActionsReportCreateObjectAndCreateFieldActions =
({
orchestratorActionsReport,
}: AggregateOrchestratorActionsReportCreateObjectAndCreateFieldActionsArgs): OrchestratorActionsReport => {
const initialCreatedObjectActionByObjectMetadataId = (
orchestratorActionsReport.objectMetadata.created as CreateObjectAction[]
).reduce(
(acc, createObjectAction) => ({
...acc,
[createObjectAction.flatObjectMetadata.id]: createObjectAction,
}),
{},
);
const initialAccumulator: AggregatedActions = {
createdFieldActionByObjectMetadataId: {},
createdObjectActionByObjectMetadataId:
initialCreatedObjectActionByObjectMetadataId,
};
const {
createdFieldActionByObjectMetadataId,
createdObjectActionByObjectMetadataId,
} = (
orchestratorActionsReport.fieldMetadata.created as CreateFieldAction[]
).reduce<AggregatedActions>(
(
{
createdObjectActionByObjectMetadataId,
createdFieldActionByObjectMetadataId,
},
createFieldAction,
) => {
const existingCreateObjectAction =
createdObjectActionByObjectMetadataId[
createFieldAction.objectMetadataId
];
if (isDefined(existingCreateObjectAction)) {
return {
createdObjectActionByObjectMetadataId: {
...createdObjectActionByObjectMetadataId,
[createFieldAction.objectMetadataId]: {
...existingCreateObjectAction,
flatFieldMetadatas: [
...existingCreateObjectAction.flatFieldMetadatas,
...createFieldAction.flatFieldMetadatas,
],
},
},
createdFieldActionByObjectMetadataId,
};
}
const existingCreateFieldAction =
createdFieldActionByObjectMetadataId[
createFieldAction.objectMetadataId
];
if (isDefined(existingCreateFieldAction)) {
return {
createdFieldActionByObjectMetadataId: {
...createdFieldActionByObjectMetadataId,
[createFieldAction.objectMetadataId]: {
...existingCreateFieldAction,
flatFieldMetadatas: [
...existingCreateFieldAction.flatFieldMetadatas,
...createFieldAction.flatFieldMetadatas,
],
},
},
createdObjectActionByObjectMetadataId,
};
}
return {
createdFieldActionByObjectMetadataId: {
...createdFieldActionByObjectMetadataId,
[createFieldAction.objectMetadataId]: createFieldAction,
},
createdObjectActionByObjectMetadataId,
};
},
initialAccumulator,
);
return {
...orchestratorActionsReport,
fieldMetadata: {
...orchestratorActionsReport.fieldMetadata,
created: Object.values(createdFieldActionByObjectMetadataId),
},
objectMetadata: {
...orchestratorActionsReport.objectMetadata,
created: Object.values(createdObjectActionByObjectMetadataId),
},
};
};
@@ -0,0 +1,63 @@
import { isDefined } from 'twenty-shared/utils';
import { type OrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { type DeleteFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { type DeleteObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
type AggregateOrchestratorActionsReportDeleteObjectAndDeleteFieldActionsArgs = {
orchestratorActionsReport: OrchestratorActionsReport;
};
type AggregatedActions = {
deleteFieldActionByFieldMetadataId: Record<string, DeleteFieldAction>;
};
export const aggregateOrchestratorActionsReportDeleteObjectAndDeleteFieldActions =
({
orchestratorActionsReport,
}: AggregateOrchestratorActionsReportDeleteObjectAndDeleteFieldActionsArgs): OrchestratorActionsReport => {
const deleteObjectActionByObjectMetadataId = (
orchestratorActionsReport.objectMetadata.deleted as DeleteObjectAction[]
).reduce<Record<string, DeleteObjectAction>>(
(acc, deleteObjectAction) => ({
...acc,
[deleteObjectAction.objectMetadataId]: deleteObjectAction,
}),
{},
);
const initialAccumulator: AggregatedActions = {
deleteFieldActionByFieldMetadataId: {},
};
const { deleteFieldActionByFieldMetadataId } = (
orchestratorActionsReport.fieldMetadata.deleted as DeleteFieldAction[]
).reduce<AggregatedActions>(
({ deleteFieldActionByFieldMetadataId }, deleteFieldAction) => {
const fieldParentObjectDeleteObjectAction =
deleteObjectActionByObjectMetadataId[
deleteFieldAction.objectMetadataId
];
if (isDefined(fieldParentObjectDeleteObjectAction)) {
return {
deleteFieldActionByFieldMetadataId,
};
}
return {
deleteFieldActionByFieldMetadataId: {
...deleteFieldActionByFieldMetadataId,
[deleteFieldAction.fieldMetadataId]: deleteFieldAction,
},
};
},
initialAccumulator,
);
return {
...orchestratorActionsReport,
fieldMetadata: {
...orchestratorActionsReport.fieldMetadata,
deleted: Object.values(deleteFieldActionByFieldMetadataId),
},
};
};
@@ -0,0 +1,23 @@
import { type OrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { aggregateOrchestratorActionsReportCreateObjectAndCreateFieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report-create-object-and-create-field-actions.util';
import { aggregateOrchestratorActionsReportDeleteObjectAndDeleteFieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report-delete-object-and-delete-field.util';
type AggregateOrchestratorActionsReportArgs = {
orchestratorActionsReport: OrchestratorActionsReport;
};
export const aggregateOrchestratorActionsReport = ({
orchestratorActionsReport,
}: AggregateOrchestratorActionsReportArgs) => {
const aggregatedOrchestratorActionsReport = [
aggregateOrchestratorActionsReportCreateObjectAndCreateFieldActions,
aggregateOrchestratorActionsReportDeleteObjectAndDeleteFieldActions,
].reduce(
(currentOrchestratorActionsReport, aggregator) =>
aggregator({
orchestratorActionsReport: currentOrchestratorActionsReport,
}),
orchestratorActionsReport,
);
return { aggregatedOrchestratorActionsReport };
};
@@ -1,56 +0,0 @@
import { type FromTo } from 'twenty-shared/types';
export type DeletedCreatedUpdatedMatrix<T> = {
created: T[];
deleted: T[];
updated: FromTo<T>[];
};
export type CustomDeletedCreatedUpdatedMatrix<TLabel extends string, TInput> = {
[P in keyof DeletedCreatedUpdatedMatrix<TInput> as `${P}${Capitalize<TLabel>}`]: DeletedCreatedUpdatedMatrix<TInput>[P];
};
export type UniversalIdentifierItem = {
universalIdentifier: string;
};
export const deletedCreatedUpdatedMatrixDispatcher = <
T extends UniversalIdentifierItem,
>({
from,
to,
}: FromTo<T[]>): DeletedCreatedUpdatedMatrix<T> => {
const initialDispatcher: DeletedCreatedUpdatedMatrix<T> = {
created: [],
updated: [],
deleted: [],
};
const fromMap = new Map(from.map((obj) => [obj.universalIdentifier, obj]));
const toMap = new Map(to.map((obj) => [obj.universalIdentifier, obj]));
for (const [identifier, fromObj] of fromMap) {
if (!toMap.has(identifier)) {
initialDispatcher.deleted.push(fromObj);
}
}
for (const [identifier, toObj] of toMap) {
if (!fromMap.has(identifier)) {
initialDispatcher.created.push(toObj);
}
}
for (const [identifier, fromObj] of fromMap) {
const toObj = toMap.get(identifier);
if (toObj) {
initialDispatcher.updated.push({
from: fromObj,
to: toObj,
});
}
}
return initialDispatcher;
};
@@ -0,0 +1,83 @@
import { type FromTo } from 'twenty-shared/types';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { type AllFlatEntities } from 'src/engine/core-modules/common/types/all-flat-entities.type';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-builder-options.type';
export type DeletedCreatedUpdatedMatrix<T extends AllFlatEntities> = {
createdFlatEntityMaps: FlatEntityMaps<T>;
deletedFlatEntityMaps: FlatEntityMaps<T>;
updatedFlatEntityMaps: FromTo<FlatEntityMaps<T>>;
};
export type UniversalIdentifierItem = {
universalIdentifier: string;
};
export const flatEntityDeletedCreatedUpdatedMatrixDispatcher = <
T extends AllFlatEntities,
>({
from,
to,
buildOptions,
}: FromTo<T[]> & {
buildOptions: WorkspaceMigrationBuilderOptions;
}): DeletedCreatedUpdatedMatrix<T> => {
const initialDispatcher: DeletedCreatedUpdatedMatrix<T> = {
createdFlatEntityMaps: EMPTY_FLAT_ENTITY_MAPS,
deletedFlatEntityMaps: EMPTY_FLAT_ENTITY_MAPS,
updatedFlatEntityMaps: {
from: EMPTY_FLAT_ENTITY_MAPS,
to: EMPTY_FLAT_ENTITY_MAPS,
},
};
const fromMap = new Map(from.map((obj) => [obj.universalIdentifier, obj]));
const toMap = new Map(to.map((obj) => [obj.universalIdentifier, obj]));
if (buildOptions.inferDeletionFromMissingEntities) {
for (const [universalIdentifier, fromEntity] of fromMap) {
if (!toMap.has(universalIdentifier)) {
initialDispatcher.deletedFlatEntityMaps =
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: fromEntity,
flatEntityMaps: initialDispatcher.deletedFlatEntityMaps,
});
}
}
}
for (const [universalIdentifier, toFlatEntity] of toMap) {
if (!fromMap.has(universalIdentifier)) {
initialDispatcher.createdFlatEntityMaps =
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: toFlatEntity,
flatEntityMaps: initialDispatcher.createdFlatEntityMaps,
});
}
}
for (const [universalIdentifier, fromFlatEntity] of fromMap) {
const toFlatEntity = toMap.get(universalIdentifier);
// TODO Perf improvement compare directly here to avoid mapping the whole workspaces + avoid compare duplication
// Would not have FromTo anymore but an PropertyUpdates[]
if (toFlatEntity) {
initialDispatcher.updatedFlatEntityMaps.from =
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: fromFlatEntity,
flatEntityMaps: initialDispatcher.updatedFlatEntityMaps.from,
});
initialDispatcher.updatedFlatEntityMaps.to =
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: toFlatEntity,
flatEntityMaps: initialDispatcher.updatedFlatEntityMaps.to,
});
}
}
return initialDispatcher;
};
@@ -1,227 +0,0 @@
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';
import { COMPANY_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/company-flat-fields.mock';
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
import { getRelationTargetFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock';
import { OPPORTUNITY_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/opportunity-flat-fields.mock';
import { PET_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/pet-flat-fields.mock';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
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 { 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';
const relationTestCases: WorkspaceMigrationBuilderTestCase[] = [
{
title: 'It should build an create_field action for a RELATION field',
context: {
input: () => {
const targetRelationId = faker.string.uuid();
const sourceRelationId = faker.string.uuid();
const draftSourceRelationFlatField =
getRelationTargetFlatFieldMetadataMock({
id: sourceRelationId,
universalIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId: PET_FLAT_OBJECT_MOCK.id,
type: FieldMetadataType.RELATION,
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'whatever',
onDelete: undefined,
},
relationTargetFieldMetadataId: targetRelationId,
relationTargetObjectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
flatRelationTargetObjectMetadata:
fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(
ROCKET_FLAT_OBJECT_MOCK,
),
});
const targetRelationFlatField = getFlatFieldMetadataMock({
id: targetRelationId,
universalIdentifier: 'field-metadata-unique-identifier-2',
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
type: FieldMetadataType.RELATION,
settings: {
relationType: RelationType.ONE_TO_MANY,
joinColumnName: 'sourceWhatever',
onDelete: undefined,
},
relationTargetFieldMetadataId: sourceRelationId,
relationTargetObjectMetadataId: PET_FLAT_OBJECT_MOCK.id,
flatRelationTargetFieldMetadata: draftSourceRelationFlatField,
flatRelationTargetObjectMetadata:
fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(
PET_FLAT_OBJECT_MOCK,
),
});
const sourceRelationFlatField = getFlatFieldMetadataMock({
...draftSourceRelationFlatField,
flatRelationTargetFieldMetadata: removePropertiesFromRecord(
targetRelationFlatField as FlatFieldMetadata<FieldMetadataType.RELATION>,
[
'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,
};
},
},
},
{
title: 'It should build an update_field action for a RELATION field',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: getFlatFieldMetadataMock({
...PET_FLAT_FIELDS_MOCK.species,
description: 'new description',
label: 'new label',
}),
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
},
},
},
{
title:
'It should NOT build an update_field action for a field RELATION uncovered fields mutation',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: getFlatFieldMetadataMock({
...COMPANY_FLAT_FIELDS_MOCK.opportunities,
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'new-column-name',
onDelete: undefined,
},
name: 'newName',
relationTargetFieldMetadataId: faker.string.uuid(),
relationTargetObjectMetadataId: faker.string.uuid(),
}),
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
},
},
},
];
const basicCrudTestCases: WorkspaceMigrationBuilderTestCase[] = [
{
title: 'It should build an create_field action',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: getFlatFieldMetadataMock({
universalIdentifier: 'field-metadata-unique-identifier-1',
type: FieldMetadataType.TEXT,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
}),
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
},
},
},
{
title: 'It should build an update_field action for standard field',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadata: getFlatFieldMetadataMock({
...OPPORTUNITY_FLAT_FIELDS_MOCK.stage,
standardOverrides: {
description: 'new description',
icon: 'new icon',
},
}),
}),
},
},
},
{
title: 'It should build an update_field action for custom fields',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadata: getFlatFieldMetadataMock({
...PET_FLAT_FIELDS_MOCK.species,
description: 'new description',
icon: 'new icon',
}),
}),
},
},
},
{
title: 'It should build a delete_field action',
context: {
input: {
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,
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
},
},
},
];
export const WORKSPACE_MIGRATION_FIELD_BUILDER_TEST_CASES: WorkspaceMigrationBuilderTestCase[] =
[
...relationTestCases,
...basicCrudTestCases,
{
title:
'It should not infer any actions as from and to fields are identical',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
},
},
];
@@ -1,150 +0,0 @@
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
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 { 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 { OPPORTUNITY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/opportunity-flat-object.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 { STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/standard-relation-target-flat-object-metadata.mocks';
import { fromFlatObjectMetadatasToFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadatas-to-flat-object-metadata-maps.util';
import { type WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type';
// TODO prastoin implement validation exception tests cases coverage too
const DELETE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [
{
title:
'It should build a delete_object action with custom deactivated object',
context: {
input: {
fromFlatObjectMetadataMaps:
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadata: getFlatObjectMetadataMock({
...ROCKET_FLAT_OBJECT_MOCK,
isActive: false,
}),
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
toFlatObjectMetadataMaps: deleteObjectFromFlatObjectMetadataMapsOrThrow(
{
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
},
),
},
},
},
{
title:
'It should build a delete_object action with standard activated object',
context: {
input: {
buildOptions: {
inferDeletionFromMissingEntities: true,
isSystemBuild: true,
},
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps: deleteObjectFromFlatObjectMetadataMapsOrThrow(
{
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
objectMetadataId: COMPANY_FLAT_OBJECT_MOCK.id,
},
),
},
},
},
];
const CREATE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [
{
title: 'It should build a create_object action with custom object',
context: {
input: {
fromFlatObjectMetadataMaps:
fromFlatObjectMetadatasToFlatObjectMetadataMaps([
PET_FLAT_OBJECT_MOCK,
...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS,
]),
toFlatObjectMetadataMaps:
fromFlatObjectMetadatasToFlatObjectMetadataMaps([
PET_FLAT_OBJECT_MOCK,
ROCKET_FLAT_OBJECT_MOCK,
...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS,
]),
},
},
},
{
title: 'It should build a create_object action with standard object',
context: {
input: {
fromFlatObjectMetadataMaps:
fromFlatObjectMetadatasToFlatObjectMetadataMaps([
ROCKET_FLAT_OBJECT_MOCK,
...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS,
]),
toFlatObjectMetadataMaps:
fromFlatObjectMetadatasToFlatObjectMetadataMaps([
PET_FLAT_OBJECT_MOCK,
ROCKET_FLAT_OBJECT_MOCK,
...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS,
]),
},
},
},
];
const UPDATE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [
{
title: 'It should build an update_object for custom object',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...PET_FLAT_OBJECT_MOCK,
nameSingular: 'prastouin',
namePlural: 'prastoins',
isLabelSyncedWithName: false,
}),
}),
},
},
},
{
title: 'It should build an update_object for standard object',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...OPPORTUNITY_FLAT_OBJECT_MOCK,
standardOverrides: {
description: 'Updated description',
},
}),
}),
},
},
},
];
export const WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES: WorkspaceMigrationBuilderTestCase[] =
[
...DELETE_OBJECT_TEST_CASES,
...CREATE_OBJECT_TEST_CASES,
...UPDATE_OBJECT_TEST_CASES,
{
title: 'It should not infer any actions as from and to are identical',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
},
},
];
@@ -1,24 +0,0 @@
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';
export type CamelCasedWorkspaceMigrationActionsType =
ConvertActionTypeToCamelCase<WorkspaceMigrationActionTypeV2>;
export type ExpectedActionCounters = Partial<
Record<CamelCasedWorkspaceMigrationActionsType, number>
>;
type TestWorkspaceMigrationBuildArgs = Omit<
WorkspaceMigrationBuildArgs,
'workspaceId' | 'buildOptions'
> &
Partial<Pick<WorkspaceMigrationBuildArgs, 'buildOptions'>>;
export type WorkspaceMigrationBuilderTestCase = EachTestingContext<{
input:
| TestWorkspaceMigrationBuildArgs
| (() => TestWorkspaceMigrationBuildArgs);
}>;
@@ -1,105 +0,0 @@
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { eachTestingContextFilter } from 'twenty-shared/testing';
import { type FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
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 { 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_OBJECT_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case';
import { type WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/services/workspace-migration-v2-field-actions-builder.service';
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-actions-builder.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
``;
const allWorkspaceBuilderTestCases: {
label: string;
testCases: WorkspaceMigrationBuilderTestCase[];
}[] = [
{
label: 'object',
testCases: WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES,
},
{
label: 'field',
testCases: WORKSPACE_MIGRATION_FIELD_BUILDER_TEST_CASES,
},
];
describe.each(allWorkspaceBuilderTestCases)(
'Workspace migration builder $label actions test suite',
({ testCases }) => {
let service: WorkspaceMigrationBuilderV2Service;
let featureFlagService: FeatureFlagService;
let flatFieldMetadataTypeValidatorService: FlatFieldMetadataTypeValidatorService;
let flatFieldMetadataValidatorService: FlatFieldMetadataValidatorService;
let flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService;
let objectActionsBuilder: WorkspaceMigrationV2ObjectActionsBuilderService;
let fieldActionsBuilder: WorkspaceMigrationV2FieldActionsBuilderService;
beforeEach(() => {
featureFlagService = {
isFeatureEnabled: jest.fn().mockResolvedValue(true),
} as any;
flatFieldMetadataTypeValidatorService =
new FlatFieldMetadataTypeValidatorService(featureFlagService);
flatFieldMetadataValidatorService = new FlatFieldMetadataValidatorService(
flatFieldMetadataTypeValidatorService,
);
flatObjectMetadataValidatorService =
new FlatObjectMetadataValidatorService(
flatFieldMetadataValidatorService,
);
objectActionsBuilder =
new WorkspaceMigrationV2ObjectActionsBuilderService(
flatObjectMetadataValidatorService,
);
fieldActionsBuilder = new WorkspaceMigrationV2FieldActionsBuilderService(
flatFieldMetadataValidatorService,
);
service = new WorkspaceMigrationBuilderV2Service(
objectActionsBuilder,
fieldActionsBuilder,
);
});
it.each(eachTestingContextFilter(testCases))(
'$title',
async ({ context: { input } }) => {
const {
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
buildOptions = {
inferDeletionFromMissingEntities: true,
isSystemBuild: false,
},
} = typeof input === 'function' ? input() : input;
const validateAndBuildResult = await service.validateAndBuild({
buildOptions,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
});
expect(validateAndBuildResult.status).toBe('success');
if (validateAndBuildResult.status !== 'success') {
throw new Error('Should never occur');
}
expect(validateAndBuildResult.fieldsActions).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(
validateAndBuildResult.fieldsActions,
),
);
expect(validateAndBuildResult.objectActions).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(
validateAndBuildResult.objectActions,
),
);
},
);
},
);
@@ -12,31 +12,26 @@ import {
UpdateCronTriggerAction,
WorkspaceMigrationCronTriggerActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-cron-trigger-action-v2.type';
import {
CronTriggerRelatedFlatEntityMaps,
FlatCronTriggerValidatorService,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
@Injectable()
export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
'cronTrigger',
FlatCronTrigger,
WorkspaceMigrationCronTriggerActionV2,
CronTriggerRelatedFlatEntityMaps
undefined
> {
constructor(
private readonly flatCronTriggerValidatorService: FlatCronTriggerValidatorService,
) {
super();
super('cronTrigger');
}
protected async validateFlatEntityCreation({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatCronTriggerToValidate,
optimisticFlatEntityMaps: optimisticFlatCronTriggerMaps,
}: FlatEntityValidationArgs<
FlatCronTrigger,
CronTriggerRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityValidationArgs<FlatCronTrigger>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationCronTriggerActionV2,
FlatCronTrigger
@@ -47,7 +42,6 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp
{
flatCronTriggerToValidate,
optimisticFlatCronTriggerMaps,
dependencyOptimisticFlatEntityMaps,
},
);
@@ -64,17 +58,15 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp
type: 'create_cron_trigger',
cronTrigger: flatCronTriggerToValidate,
},
dependencyOptimisticFlatEntityMaps,
};
}
protected async validateFlatEntityDeletion({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatCronTriggerToValidate,
optimisticFlatEntityMaps: optimisticFlatCronTriggerMaps,
}: FlatEntityValidationArgs<
FlatCronTrigger,
CronTriggerRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityValidationArgs<FlatCronTrigger>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationCronTriggerActionV2,
FlatCronTrigger
@@ -84,7 +76,6 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp
this.flatCronTriggerValidatorService.validateFlatCronTriggerDeletion({
flatCronTriggerToValidate,
optimisticFlatCronTriggerMaps,
dependencyOptimisticFlatEntityMaps,
});
if (validationResult.errors.length > 0) {
@@ -100,17 +91,15 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp
type: 'delete_cron_trigger',
cronTriggerId: flatCronTriggerToValidate.id,
},
dependencyOptimisticFlatEntityMaps,
};
}
protected async validateFlatEntityUpdate({
dependencyOptimisticFlatEntityMaps,
flatEntityUpdate: { from: fromFlatCronTrigger, to: toFlatCronTrigger },
optimisticFlatEntityMaps: optimisticFlatCronTriggerMaps,
}: FlatEntityUpdateValidationArgs<
FlatCronTrigger,
CronTriggerRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityUpdateValidationArgs<FlatCronTrigger>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationCronTriggerActionV2,
FlatCronTrigger
@@ -130,7 +119,6 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp
this.flatCronTriggerValidatorService.validateFlatCronTriggerUpdate({
flatCronTriggerToValidate: toFlatCronTrigger,
optimisticFlatCronTriggerMaps,
dependencyOptimisticFlatEntityMaps,
});
if (validationResult.errors.length > 0) {
@@ -149,6 +137,7 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp
return {
status: 'success',
action: updateCronTriggerAction,
dependencyOptimisticFlatEntityMaps,
};
}
}
@@ -12,31 +12,26 @@ import {
UpdateDatabaseEventTriggerAction,
WorkspaceMigrationDatabaseEventTriggerActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-database-event-trigger-action-v2.type';
import {
DatabaseEventTriggerRelatedFlatEntityMaps,
FlatDatabaseEventTriggerValidatorService,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
@Injectable()
export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
'databaseEventTrigger',
FlatDatabaseEventTrigger,
WorkspaceMigrationDatabaseEventTriggerActionV2,
DatabaseEventTriggerRelatedFlatEntityMaps
undefined
> {
constructor(
private readonly flatDatabaseEventTriggerValidatorService: FlatDatabaseEventTriggerValidatorService,
) {
super();
super('databaseEventTrigger');
}
protected async validateFlatEntityCreation({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatDatabaseEventTriggerToValidate,
optimisticFlatEntityMaps: optimisticFlatDatabaseEventTriggerMaps,
}: FlatEntityValidationArgs<
FlatDatabaseEventTrigger,
DatabaseEventTriggerRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityValidationArgs<FlatDatabaseEventTrigger>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationDatabaseEventTriggerActionV2,
FlatDatabaseEventTrigger
@@ -47,7 +42,6 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten
{
flatDatabaseEventTriggerToValidate,
optimisticFlatDatabaseEventTriggerMaps,
dependencyOptimisticFlatEntityMaps,
},
);
@@ -64,17 +58,15 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten
type: 'create_database_event_trigger',
databaseEventTrigger: flatDatabaseEventTriggerToValidate,
},
dependencyOptimisticFlatEntityMaps,
};
}
protected async validateFlatEntityDeletion({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatDatabaseEventTriggerToValidate,
optimisticFlatEntityMaps: optimisticFlatDatabaseEventTriggerMaps,
}: FlatEntityValidationArgs<
FlatDatabaseEventTrigger,
DatabaseEventTriggerRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityValidationArgs<FlatDatabaseEventTrigger>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationDatabaseEventTriggerActionV2,
FlatDatabaseEventTrigger
@@ -85,7 +77,6 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten
{
flatDatabaseEventTriggerToValidate,
optimisticFlatDatabaseEventTriggerMaps,
dependencyOptimisticFlatEntityMaps,
},
);
@@ -102,20 +93,18 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten
type: 'delete_database_event_trigger',
databaseEventTriggerId: flatDatabaseEventTriggerToValidate.id,
},
dependencyOptimisticFlatEntityMaps,
};
}
protected async validateFlatEntityUpdate({
dependencyOptimisticFlatEntityMaps,
flatEntityUpdate: {
from: fromFlatDatabaseEventTrigger,
to: toFlatDatabaseEventTrigger,
},
optimisticFlatEntityMaps: optimisticFlatDatabaseEventTriggerMaps,
}: FlatEntityUpdateValidationArgs<
FlatDatabaseEventTrigger,
DatabaseEventTriggerRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityUpdateValidationArgs<FlatDatabaseEventTrigger>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationDatabaseEventTriggerActionV2,
FlatDatabaseEventTrigger
@@ -137,7 +126,6 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten
{
flatDatabaseEventTriggerToValidate: toFlatDatabaseEventTrigger,
optimisticFlatDatabaseEventTriggerMaps,
dependencyOptimisticFlatEntityMaps,
},
);
@@ -157,6 +145,7 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten
return {
status: 'success',
action: updateDatabaseEventTriggerAction,
dependencyOptimisticFlatEntityMaps,
};
}
}
@@ -1,255 +1,217 @@
import { Injectable } from '@nestjs/common';
import { FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { compareTwoFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.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 { computeMorphOrRelationTargetFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/compute-morph-or-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';
FlatEntityUpdateValidationArgs,
FlatEntityValidationArgs,
FlatEntityValidationReturnType,
WorkspaceEntityMigrationBuilderV2Service,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import {
UpdateFieldAction,
WorkspaceMigrationFieldActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { FlatFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
type BuildWorkspaceMigrationV2FieldActionsArgs = FromTo<
FlatObjectMetadataMaps,
export type FieldMetadataRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps'
> & {
buildOptions: WorkspaceMigrationV2BuilderOptions;
objectMetadataDeletedCreatedUpdatedFields: UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix[];
};
type ValidateFlatObjectMetadataFlatFieldMetadataMatrixArgs = FromTo<
FlatObjectMetadataMaps,
'flatObjectMetadataMaps'
> &
UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix & {
buildOptions: WorkspaceMigrationV2BuilderOptions;
};
>;
@Injectable()
export class WorkspaceMigrationV2FieldActionsBuilderService {
export class WorkspaceMigrationV2FieldActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
'fieldMetadata',
FlatFieldMetadata,
WorkspaceMigrationFieldActionV2,
FieldMetadataRelatedFlatEntityMaps
> {
constructor(
private readonly flatFieldMetadataValidatorService: FlatFieldMetadataValidatorService,
) {}
private async validateFlatObjectMetadataFlatFieldMetadataMatrix({
createdFlatFieldMetadatas,
deletedFlatFieldMetadatas,
flatObjectMetadata,
updatedFlatFieldMetadatas,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
buildOptions,
}: ValidateFlatObjectMetadataFlatFieldMetadataMatrixArgs): Promise<
ValidateAndBuildMetadataResult<WorkspaceMigrationFieldActionV2>
> {
const validateAndBuildResult: ValidateAndBuildMetadataResult<WorkspaceMigrationFieldActionV2> =
{
failed: [],
created: [],
deleted: [],
updated: [],
optimisticFlatObjectMetadataMaps: structuredClone(
fromFlatObjectMetadataMaps,
),
};
for (const {
from: fromFlatFieldMetadata,
to: toFlatFieldMetadata,
} of updatedFlatFieldMetadatas) {
const updates = compareTwoFlatFieldMetadata({
fromFlatFieldMetadata,
toFlatFieldMetadata,
});
if (updates.length === 0) {
continue;
}
const {
workspaceId,
id: fieldMetadataId,
objectMetadataId,
} = toFlatFieldMetadata;
const validationErrors =
await this.flatFieldMetadataValidatorService.validateFlatFieldMetadataUpdate(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
flatFieldMetadataToValidate: toFlatFieldMetadata,
workspaceId,
otherFlatObjectMetadataMapsToValidate: undefined,
},
);
if (validationErrors.errors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: toFlatFieldMetadata,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
});
validateAndBuildResult.updated.push({
type: 'update_field',
fieldMetadataId,
objectMetadataId,
workspaceId,
updates,
});
}
for (const flatFieldMetadataToCreate of createdFlatFieldMetadatas) {
const relationTargetFlatObjectMetadataMaps =
isMorphOrRelationFlatFieldMetadata(flatFieldMetadataToCreate)
? computeMorphOrRelationTargetFlatObjectMetadataMaps({
flatFieldMetadata: flatFieldMetadataToCreate,
flatObjectMetadataMaps: toFlatObjectMetadataMaps,
})
: undefined;
const validationErrors =
await this.flatFieldMetadataValidatorService.validateFlatFieldMetadataCreation(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
flatFieldMetadataToValidate: flatFieldMetadataToCreate,
workspaceId: flatFieldMetadataToCreate.workspaceId,
otherFlatObjectMetadataMapsToValidate:
relationTargetFlatObjectMetadataMaps,
},
);
if (validationErrors.errors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: flatFieldMetadataToCreate,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
});
const createFieldAction = getWorkspaceMigrationV2FieldCreateAction({
flatFieldMetadata: flatFieldMetadataToCreate,
flatObjectMetadata,
});
validateAndBuildResult.created.push(createFieldAction);
}
for (const flatFieldMetadataToDelete of buildOptions.inferDeletionFromMissingEntities
? deletedFlatFieldMetadatas
: []) {
const validationErrors =
this.flatFieldMetadataValidatorService.validateFlatFieldMetadataDeletion(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
flatFieldMetadataToDelete,
},
);
if (validationErrors.errors.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.deleted.push(deleteFieldAction);
}
return validateAndBuildResult;
private readonly flatFieldValidatorService: FlatFieldMetadataValidatorService,
) {
super('fieldMetadata');
}
public async validateAndBuildFieldActions({
buildOptions,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
objectMetadataDeletedCreatedUpdatedFields,
}: BuildWorkspaceMigrationV2FieldActionsArgs): Promise<
ValidateAndBuildMetadataResult<WorkspaceMigrationFieldActionV2>
protected async validateFlatEntityCreation({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatFieldMetadataToValidate,
optimisticFlatEntityMaps: optimisticFlatFieldMetadataMaps,
workspaceId,
remainingFlatEntityMapsToValidate,
}: FlatEntityValidationArgs<
FlatFieldMetadata,
FieldMetadataRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationFieldActionV2,
FlatFieldMetadata,
FieldMetadataRelatedFlatEntityMaps
>
> {
let validateAndBuildResult: ValidateAndBuildMetadataResult<WorkspaceMigrationFieldActionV2> =
{
failed: [],
created: [],
deleted: [],
updated: [],
optimisticFlatObjectMetadataMaps: structuredClone(
fromFlatObjectMetadataMaps,
),
};
const validationResult =
await this.flatFieldValidatorService.validateFlatFieldMetadataCreation({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
workspaceId,
remainingFlatEntityMapsToValidate,
});
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,
],
created: [
...validateAndBuildResult.created,
...flatObjectMetadataFlatFieldMatrixValidateAndBuildResult.created,
],
deleted: [
...validateAndBuildResult.deleted,
...flatObjectMetadataFlatFieldMatrixValidateAndBuildResult.deleted,
],
updated: [
...validateAndBuildResult.updated,
...flatObjectMetadataFlatFieldMatrixValidateAndBuildResult.updated,
],
optimisticFlatObjectMetadataMaps:
flatObjectMetadataFlatFieldMatrixValidateAndBuildResult.optimisticFlatObjectMetadataMaps,
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return validateAndBuildResult;
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatFieldMetadataToValidate.objectMetadataId,
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
});
const updatedFlatObjectMetadataMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: {
...flatObjectMetadata,
fieldMetadataIds: [
...flatObjectMetadata.fieldMetadataIds,
flatFieldMetadataToValidate.id,
],
},
flatEntityMaps:
dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
});
return {
status: 'success',
action: {
type: 'create_field',
objectMetadataId: flatFieldMetadataToValidate.objectMetadataId,
flatFieldMetadatas: [flatFieldMetadataToValidate],
},
dependencyOptimisticFlatEntityMaps: {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
},
};
}
protected async validateFlatEntityDeletion({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatFieldMetadataToValidate,
optimisticFlatEntityMaps: optimisticFlatFieldMetadataMaps,
workspaceId,
}: FlatEntityValidationArgs<
FlatFieldMetadata,
FieldMetadataRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationFieldActionV2,
FlatFieldMetadata,
FieldMetadataRelatedFlatEntityMaps
>
> {
const validationResult =
this.flatFieldValidatorService.validateFlatFieldMetadataDeletion({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
workspaceId,
});
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatFieldMetadataToValidate.objectMetadataId,
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
});
const updatedFlatObjectMetadataMaps = isDefined(flatObjectMetadata)
? replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: {
...flatObjectMetadata,
fieldMetadataIds: flatObjectMetadata.fieldMetadataIds.filter(
(id) => id !== flatFieldMetadataToValidate.id,
),
},
flatEntityMaps:
dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
})
: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps;
return {
status: 'success',
action: {
type: 'delete_field',
fieldMetadataId: flatFieldMetadataToValidate.id,
objectMetadataId: flatFieldMetadataToValidate.objectMetadataId,
},
dependencyOptimisticFlatEntityMaps: {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
},
};
}
protected async validateFlatEntityUpdate({
dependencyOptimisticFlatEntityMaps,
flatEntityUpdate: { from: fromFlatFieldMetadata, to: toFlatFieldMetadata },
optimisticFlatEntityMaps: optimisticFlatFieldMetadataMaps,
workspaceId,
}: FlatEntityUpdateValidationArgs<
FlatFieldMetadata,
FieldMetadataRelatedFlatEntityMaps
>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationFieldActionV2,
FlatFieldMetadata,
FieldMetadataRelatedFlatEntityMaps
>
| undefined
> {
const flatFieldMetadataUpdatedProperties = compareTwoFlatFieldMetadata({
fromFlatFieldMetadata,
toFlatFieldMetadata,
});
if (flatFieldMetadataUpdatedProperties.length === 0) {
return undefined;
}
const validationResult =
await this.flatFieldValidatorService.validateFlatFieldMetadataUpdate({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate: toFlatFieldMetadata,
optimisticFlatFieldMetadataMaps,
workspaceId,
});
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const updateFieldAction: UpdateFieldAction = {
type: 'update_field',
fieldMetadataId: toFlatFieldMetadata.id,
objectMetadataId: toFlatFieldMetadata.objectMetadataId,
updates: flatFieldMetadataUpdatedProperties,
};
return {
status: 'success',
action: updateFieldAction,
dependencyOptimisticFlatEntityMaps,
};
}
}
@@ -1,7 +1,12 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { compareTwoFlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/utils/compare-two-flat-index-metadata.util';
import {
@@ -15,10 +20,11 @@ import { FlatIndexValidatorService } from 'src/engine/workspace-manager/workspac
export type IndexRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps'
'flatFieldMetadataMaps' | 'flatObjectMetadataMaps'
>;
@Injectable()
export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
'index',
FlatIndexMetadata,
WorkspaceMigrationIndexActionV2,
IndexRelatedFlatEntityMaps
@@ -26,7 +32,7 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
constructor(
private readonly flatIndexValidatorService: FlatIndexValidatorService,
) {
super();
super('index');
}
protected async validateFlatEntityCreation({
@@ -39,7 +45,8 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationIndexActionV2,
FlatIndexMetadata
FlatIndexMetadata,
IndexRelatedFlatEntityMaps
>
> {
const validationResult =
@@ -56,12 +63,34 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
};
}
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatIndexToValidate.objectMetadataId,
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
});
const updatedFlatObjectMetadataMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: {
...flatObjectMetadata,
indexMetadataIds: [
...flatObjectMetadata.indexMetadataIds,
flatIndexToValidate.id,
],
},
flatEntityMaps:
dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
});
return {
status: 'success',
action: {
type: 'create_index',
flatIndexMetadata: flatIndexToValidate,
},
dependencyOptimisticFlatEntityMaps: {
...dependencyOptimisticFlatEntityMaps,
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
},
};
}
@@ -75,7 +104,8 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationIndexActionV2,
FlatIndexMetadata
FlatIndexMetadata,
IndexRelatedFlatEntityMaps
>
> {
const validationResult =
@@ -92,12 +122,34 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
};
}
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatIndexToValidate.objectMetadataId,
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
});
const updatedFlatObjectMetadataMaps = isDefined(flatObjectMetadata)
? replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: {
...flatObjectMetadata,
indexMetadataIds: flatObjectMetadata.indexMetadataIds.filter(
(id) => id !== flatIndexToValidate.id,
),
},
flatEntityMaps:
dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
})
: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps;
return {
status: 'success',
action: {
type: 'delete_index',
flatIndexMetadataId: flatIndexToValidate.id,
},
dependencyOptimisticFlatEntityMaps: {
...dependencyOptimisticFlatEntityMaps,
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
},
};
}
@@ -111,7 +163,8 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationIndexActionV2,
FlatIndexMetadata
FlatIndexMetadata,
IndexRelatedFlatEntityMaps
>
| undefined
> {
@@ -167,6 +220,7 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
flatIndexMetadata: toFlatIndex,
},
],
dependencyOptimisticFlatEntityMaps,
};
}
}
@@ -1,186 +1,173 @@
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 { 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 {
FlatEntityUpdateValidationArgs,
FlatEntityValidationArgs,
FlatEntityValidationReturnType,
WorkspaceEntityMigrationBuilderV2Service,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
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';
import { FlatObjectMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service';
// export type ObjectMetadataRelatedFlatEntityMaps = Pick<
// AllFlatEntityMaps,
// 'flatFieldMetadataMaps'
// >;
export type CreatedDeletedUpdatedObjectMetadataInputMatrix = FromTo<
FlatObjectMetadataMaps,
'FlatObjectMetadataMaps'
> &
CustomDeletedCreatedUpdatedMatrix<
'flatObjectMetadatas',
FlatObjectMetadata
> & {
buildOptions: WorkspaceMigrationV2BuilderOptions;
fromFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
// TODO find a solution in order to handle label indentifier validation
export type ObjectMetadataRelatedFlatEntityMaps = undefined;
@Injectable()
export class WorkspaceMigrationV2ObjectActionsBuilderService {
export class WorkspaceMigrationV2ObjectActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
'objectMetadata',
FlatObjectMetadata,
WorkspaceMigrationObjectActionV2,
ObjectMetadataRelatedFlatEntityMaps
> {
constructor(
private readonly flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService,
) {}
private readonly flatObjectValidatorService: FlatObjectMetadataValidatorService,
) {
super('objectMetadata');
}
public async validateAndBuildObjectActions({
createdFlatObjectMetadatas,
deletedFlatObjectMetadatas,
updatedFlatObjectMetadatas,
protected async validateFlatEntityCreation({
flatEntityToValidate: flatObjectMetadataToValidate,
optimisticFlatEntityMaps: optimisticFlatObjectMetadataMaps,
buildOptions,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
}: CreatedDeletedUpdatedObjectMetadataInputMatrix): Promise<
ValidateAndBuildMetadataResult<WorkspaceMigrationObjectActionV2>
dependencyOptimisticFlatEntityMaps,
}: FlatEntityValidationArgs<
FlatObjectMetadata,
ObjectMetadataRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationObjectActionV2,
FlatObjectMetadata,
ObjectMetadataRelatedFlatEntityMaps
>
> {
const validateAndBuildResult: ValidateAndBuildMetadataResult<WorkspaceMigrationObjectActionV2> =
{
failed: [],
created: [],
deleted: [],
updated: [],
optimisticFlatObjectMetadataMaps: structuredClone(
fromFlatObjectMetadataMaps,
),
};
for (const flatObjectMetadataToCreate of createdFlatObjectMetadatas) {
const validationErrors =
await this.flatObjectMetadataValidatorService.validateFlatObjectMetadataCreation(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate: flatObjectMetadataToCreate,
otherFlatObjectMetadataMapsToValidate: toFlatObjectMetadataMaps,
},
);
if (
validationErrors.fieldValidationResults.length > 0 ||
validationErrors.objectValidationResult.errors.length > 0
) {
validateAndBuildResult.failed.push(
validationErrors.objectValidationResult,
...validationErrors.fieldValidationResults,
);
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,
const validationResult =
await this.flatObjectValidatorService.validateFlatObjectMetadataCreation({
buildOptions,
flatObjectMetadataToValidate,
optimisticFlatObjectMetadataMaps,
dependencyOptimisticFlatEntityMaps,
});
validateAndBuildResult.created.push(createObjectAction);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
for (const flatObjectMetadataToDelete of buildOptions.inferDeletionFromMissingEntities
? deletedFlatObjectMetadatas
: []) {
const validationErrors =
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataDeletion(
{
buildOptions,
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
objectMetadataToDeleteId: flatObjectMetadataToDelete.id,
},
);
return {
status: 'success',
action: {
type: 'create_object',
flatFieldMetadatas: [],
flatObjectMetadata: flatObjectMetadataToValidate,
},
dependencyOptimisticFlatEntityMaps,
};
}
if (validationErrors.errors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
protected async validateFlatEntityDeletion({
buildOptions,
flatEntityToValidate: flatObjectMetadataToValidate,
optimisticFlatEntityMaps: optimisticFlatObjectMetadataMaps,
dependencyOptimisticFlatEntityMaps,
}: FlatEntityValidationArgs<
FlatObjectMetadata,
ObjectMetadataRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationObjectActionV2,
FlatObjectMetadata,
ObjectMetadataRelatedFlatEntityMaps
>
> {
const validationResult =
this.flatObjectValidatorService.validateFlatObjectMetadataDeletion({
buildOptions,
flatObjectMetadataToValidate,
optimisticFlatObjectMetadataMaps,
dependencyOptimisticFlatEntityMaps,
});
validateAndBuildResult.optimisticFlatObjectMetadataMaps =
deleteObjectFromFlatObjectMetadataMapsOrThrow({
objectMetadataId: flatObjectMetadataToDelete.id,
flatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
});
const deleteObjectAction = getWorkspaceMigrationV2ObjectDeleteAction(
flatObjectMetadataToDelete,
);
validateAndBuildResult.deleted.push(deleteObjectAction);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
for (const {
return {
status: 'success',
action: {
type: 'delete_object',
objectMetadataId: flatObjectMetadataToValidate.id,
},
dependencyOptimisticFlatEntityMaps,
};
}
protected async validateFlatEntityUpdate({
buildOptions,
flatEntityUpdate: {
from: fromFlatObjectMetadata,
to: toFlatObjectMetadata,
} of updatedFlatObjectMetadatas) {
const objectUpdatedProperties = compareTwoFlatObjectMetadata({
from: fromFlatObjectMetadata,
to: toFlatObjectMetadata,
});
},
optimisticFlatEntityMaps: optimisticFlatObjectMetadataMaps,
dependencyOptimisticFlatEntityMaps,
}: FlatEntityUpdateValidationArgs<
FlatObjectMetadata,
ObjectMetadataRelatedFlatEntityMaps
>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationObjectActionV2,
FlatObjectMetadata,
ObjectMetadataRelatedFlatEntityMaps
>
| undefined
> {
const objectMetadataUpdatedProperties = compareTwoFlatObjectMetadata({
fromFlatObjectMetadata,
toFlatObjectMetadata,
});
if (objectUpdatedProperties.length === 0) {
continue;
}
const validationErrors =
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataUpdate(
{
existingFlatObjectMetadataMaps:
validateAndBuildResult.optimisticFlatObjectMetadataMaps,
updatedFlatObjectMetadata: toFlatObjectMetadata,
},
);
if (validationErrors.errors.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.updated.push(updateObjectAction);
if (objectMetadataUpdatedProperties.length === 0) {
return undefined;
}
return validateAndBuildResult;
const validationResult =
this.flatObjectValidatorService.validateFlatObjectMetadataUpdate({
buildOptions,
flatObjectMetadataToValidate: toFlatObjectMetadata,
optimisticFlatObjectMetadataMaps,
dependencyOptimisticFlatEntityMaps,
});
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const updateObjectAction: UpdateObjectAction = {
type: 'update_object',
objectMetadataId: toFlatObjectMetadata.id,
updates: objectMetadataUpdatedProperties,
};
return {
status: 'success',
action: updateObjectAction,
dependencyOptimisticFlatEntityMaps,
};
}
}
@@ -12,31 +12,26 @@ import {
UpdateServerlessFunctionAction,
WorkspaceMigrationServerlessFunctionActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-serverless-function-action-v2.type';
import {
FlatServerlessFunctionValidatorService,
ServerlessFunctionRelatedFlatEntityMaps,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service';
import { FlatServerlessFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service';
@Injectable()
export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
'serverlessFunction',
FlatServerlessFunction,
WorkspaceMigrationServerlessFunctionActionV2,
ServerlessFunctionRelatedFlatEntityMaps
undefined
> {
constructor(
private readonly flatServerlessFunctionValidatorService: FlatServerlessFunctionValidatorService,
) {
super();
super('serverlessFunction');
}
protected async validateFlatEntityCreation({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatServerlessFunctionToValidate,
optimisticFlatEntityMaps: optimisticFlatServerlessFunctionMaps,
}: FlatEntityValidationArgs<
FlatServerlessFunction,
ServerlessFunctionRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityValidationArgs<FlatServerlessFunction>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationServerlessFunctionActionV2,
FlatServerlessFunction
@@ -47,7 +42,6 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends
{
flatServerlessFunctionToValidate,
optimisticFlatServerlessFunctionMaps,
dependencyOptimisticFlatEntityMaps,
},
);
@@ -64,17 +58,15 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends
type: 'create_serverless_function',
serverlessFunction: flatServerlessFunctionToValidate,
},
dependencyOptimisticFlatEntityMaps,
};
}
protected async validateFlatEntityDeletion({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatServerlessFunctionToValidate,
optimisticFlatEntityMaps: optimisticFlatServerlessFunctionMaps,
}: FlatEntityValidationArgs<
FlatServerlessFunction,
ServerlessFunctionRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityValidationArgs<FlatServerlessFunction>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationServerlessFunctionActionV2,
FlatServerlessFunction
@@ -85,7 +77,6 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends
{
flatServerlessFunctionToValidate,
optimisticFlatServerlessFunctionMaps,
dependencyOptimisticFlatEntityMaps,
},
);
@@ -102,20 +93,18 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends
type: 'delete_serverless_function',
serverlessFunctionId: flatServerlessFunctionToValidate.id,
},
dependencyOptimisticFlatEntityMaps,
};
}
protected async validateFlatEntityUpdate({
dependencyOptimisticFlatEntityMaps,
flatEntityUpdate: {
from: fromFlatServerlessFunction,
to: toFlatServerlessFunction,
},
optimisticFlatEntityMaps: optimisticFlatServerlessFunctionMaps,
}: FlatEntityUpdateValidationArgs<
FlatServerlessFunction,
ServerlessFunctionRelatedFlatEntityMaps
>): Promise<
dependencyOptimisticFlatEntityMaps,
}: FlatEntityUpdateValidationArgs<FlatServerlessFunction>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationServerlessFunctionActionV2,
FlatServerlessFunction
@@ -137,7 +126,6 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends
{
flatServerlessFunctionToValidate: toFlatServerlessFunction,
optimisticFlatServerlessFunctionMaps,
dependencyOptimisticFlatEntityMaps,
},
);
@@ -158,6 +146,7 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends
return {
status: 'success',
action: updateServerlessFunctionAction,
dependencyOptimisticFlatEntityMaps,
};
}
}
@@ -2,5 +2,5 @@ import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all
export type ViewFieldRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps' | 'flatViewMaps'
'flatViewMaps' | 'flatFieldMetadataMaps'
>;
@@ -1,5 +1,10 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
import { compareTwoFlatViewField } from 'src/engine/core-modules/view/flat-view/utils/compare-two-flat-view-field.util';
import { ViewFieldRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/view-field-related-flat-entity-maps.type';
@@ -17,6 +22,7 @@ import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/work
@Injectable()
export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
'viewField',
FlatViewField,
WorkspaceMigrationViewFieldActionV2,
ViewFieldRelatedFlatEntityMaps
@@ -24,7 +30,7 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
constructor(
private readonly flatViewFieldValidatorService: FlatViewFieldValidatorService,
) {
super();
super('viewField');
}
protected async validateFlatEntityCreation({
@@ -37,7 +43,8 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationViewFieldActionV2,
FlatViewField
FlatViewField,
ViewFieldRelatedFlatEntityMaps
>
> {
const validationResult =
@@ -54,12 +61,29 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
};
}
const flatView = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatViewFieldToValidate.viewId,
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatViewMaps,
});
const updatedFlatViewFields = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: {
...flatView,
viewFieldIds: [...flatView.viewFieldIds, flatViewFieldToValidate.id],
},
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatViewMaps,
});
return {
status: 'success',
action: {
type: 'create_view_field',
viewField: flatViewFieldToValidate,
},
dependencyOptimisticFlatEntityMaps: {
...dependencyOptimisticFlatEntityMaps,
flatViewMaps: updatedFlatViewFields,
},
};
}
@@ -73,7 +97,8 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationViewFieldActionV2,
FlatViewField
FlatViewField,
ViewFieldRelatedFlatEntityMaps
>
> {
const validationResult =
@@ -90,12 +115,33 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
};
}
const flatView = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatViewFieldToValidate.viewId,
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatViewMaps,
});
const updatedFlatViewFields = isDefined(flatView)
? replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: {
...flatView,
viewFieldIds: flatView.viewFieldIds.filter(
(id) => id !== flatViewFieldToValidate.id,
),
},
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatViewMaps,
})
: dependencyOptimisticFlatEntityMaps.flatViewMaps;
return {
status: 'success',
action: {
type: 'delete_view_field',
viewFieldId: flatViewFieldToValidate.id,
},
dependencyOptimisticFlatEntityMaps: {
...dependencyOptimisticFlatEntityMaps,
flatViewMaps: updatedFlatViewFields,
},
};
}
@@ -109,7 +155,8 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationViewFieldActionV2,
FlatViewField
FlatViewField,
ViewFieldRelatedFlatEntityMaps
>
| undefined
> {
@@ -145,6 +192,7 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
return {
status: 'success',
action: updateViewFieldAction,
dependencyOptimisticFlatEntityMaps,
};
}
}
@@ -18,10 +18,10 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
export type ViewRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps'
// 'flatFieldMetadataMaps' TODO implement once with extract field from object diffing
>;
@Injectable()
export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
'view',
FlatView,
WorkspaceMigrationViewActionV2,
ViewRelatedFlatEntityMaps
@@ -29,7 +29,7 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
constructor(
private readonly flatViewValidatorService: FlatViewValidatorService,
) {
super();
super('view');
}
protected async validateFlatEntityCreation({
@@ -37,7 +37,11 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
flatEntityToValidate: flatViewToValidate,
optimisticFlatEntityMaps: optimisticFlatViewMaps,
}: FlatEntityValidationArgs<FlatView, ViewRelatedFlatEntityMaps>): Promise<
FlatEntityValidationReturnType<WorkspaceMigrationViewActionV2, FlatView>
FlatEntityValidationReturnType<
WorkspaceMigrationViewActionV2,
FlatView,
ViewRelatedFlatEntityMaps
>
> {
const validationResult =
await this.flatViewValidatorService.validateFlatViewCreation({
@@ -59,6 +63,7 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
type: 'create_view',
view: flatViewToValidate,
},
dependencyOptimisticFlatEntityMaps,
};
}
@@ -67,7 +72,11 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
flatEntityToValidate: flatViewToValidate,
optimisticFlatEntityMaps: optimisticFlatViewMaps,
}: FlatEntityValidationArgs<FlatView, ViewRelatedFlatEntityMaps>): Promise<
FlatEntityValidationReturnType<WorkspaceMigrationViewActionV2, FlatView>
FlatEntityValidationReturnType<
WorkspaceMigrationViewActionV2,
FlatView,
ViewRelatedFlatEntityMaps
>
> {
const validationResult =
this.flatViewValidatorService.validateFlatViewDeletion({
@@ -89,6 +98,7 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
type: 'delete_view',
viewId: flatViewToValidate.id,
},
dependencyOptimisticFlatEntityMaps,
};
}
@@ -100,7 +110,11 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
FlatView,
ViewRelatedFlatEntityMaps
>): Promise<
| FlatEntityValidationReturnType<WorkspaceMigrationViewActionV2, FlatView>
| FlatEntityValidationReturnType<
WorkspaceMigrationViewActionV2,
FlatView,
ViewRelatedFlatEntityMaps
>
| undefined
> {
const viewUpdatedProperties = compareTwoFlatView({
@@ -135,6 +149,7 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
return {
status: 'success',
action: updateViewAction,
dependencyOptimisticFlatEntityMaps,
};
}
}
@@ -1,21 +1,29 @@
import { Inject } from '@nestjs/common';
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
import { AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type';
import { type AllFlatEntities } from 'src/engine/core-modules/common/types/all-flat-entities.type';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { LoggerService } from 'src/engine/core-modules/logger/logger.service';
import {
type DeletedCreatedUpdatedMatrix,
deletedCreatedUpdatedMatrixDispatcher,
} from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
flatEntityDeletedCreatedUpdatedMatrixDispatcher,
} from 'src/engine/workspace-manager/workspace-migration-v2/utils/flat-entity-deleted-created-updated-matrix-dispatcher.util';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { type WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-builder-options.type';
// TODO extract types in dedicated files
export type CreatedDeletedUpdatedActions<
TActions extends WorkspaceMigrationActionV2,
> = {
@@ -26,91 +34,140 @@ export type CreatedDeletedUpdatedActions<
export type SuccessfulEntityMigrationBuildResult<
TActions extends WorkspaceMigrationActionV2,
TFlatEntity extends FlatEntity,
TFlatEntity extends AllFlatEntities,
TRelatedFlatEntityMaps extends
| Partial<AllFlatEntityMaps>
| undefined = undefined,
> = {
status: 'success';
actions: CreatedDeletedUpdatedActions<TActions>;
optimisticFlatEntityMaps: FlatEntityMaps<TFlatEntity>;
dependencyOptimisticFlatEntityMaps: TRelatedFlatEntityMaps;
};
export type FailedEntityMigrationBuildResult<TFlatEntity extends FlatEntity> = {
export type FailedEntityMigrationBuildResult<
TFlatEntity extends AllFlatEntities,
TRelatedFlatEntityMaps extends
| Partial<AllFlatEntityMaps>
| undefined = undefined,
> = {
status: 'fail';
errors: FailedFlatEntityValidation<TFlatEntity>[];
optimisticFlatEntityMaps: FlatEntityMaps<TFlatEntity>;
dependencyOptimisticFlatEntityMaps: TRelatedFlatEntityMaps;
};
export type ValidateAndBuildArgs<
T extends FlatEntity,
TRelatedFlatEntityMaps extends Partial<AllFlatEntityMaps>,
T extends AllFlatEntities,
TRelatedFlatEntityMaps extends Partial<AllFlatEntityMaps> | undefined,
> = {
buildOptions: WorkspaceMigrationV2BuilderOptions;
buildOptions: WorkspaceMigrationBuilderOptions;
dependencyOptimisticFlatEntityMaps: TRelatedFlatEntityMaps;
workspaceId: string;
} & FromTo<FlatEntityMaps<T>>;
export type ValidateAndBuildReturnType<
TActions extends WorkspaceMigrationActionV2,
TFlatEntity extends FlatEntity,
TFlatEntity extends AllFlatEntities,
TRelatedFlatEntityMaps extends
| Partial<AllFlatEntityMaps>
| undefined = undefined,
> =
| SuccessfulEntityMigrationBuildResult<TActions, TFlatEntity>
| FailedEntityMigrationBuildResult<TFlatEntity>;
| SuccessfulEntityMigrationBuildResult<
TActions,
TFlatEntity,
TRelatedFlatEntityMaps
>
| FailedEntityMigrationBuildResult<TFlatEntity, TRelatedFlatEntityMaps>;
export type ValidateAndBuildActionsArgs<
T extends FlatEntity,
T extends AllFlatEntities,
TRelatedFlatEntityMaps extends Partial<AllFlatEntityMaps>,
> = ValidateAndBuildArgs<T, TRelatedFlatEntityMaps> &
DeletedCreatedUpdatedMatrix<T>;
export type ValidateAndBuilActionsReturnType<
TFlatEntity extends FlatEntity,
TFlatEntity extends AllFlatEntities,
TActions extends WorkspaceMigrationActionV2,
> = {
failed: FailedFlatEntityValidation<TFlatEntity>[];
} & CreatedDeletedUpdatedActions<TActions>;
export type FlatEntityValidationArgs<
TFlatEntity extends FlatEntity,
TRelatedFlatEntityMaps extends Partial<AllFlatEntityMaps>,
TFlatEntity extends AllFlatEntities,
TRelatedFlatEntityMaps extends
| Partial<AllFlatEntityMaps>
| undefined = undefined,
> = {
flatEntityToValidate: TFlatEntity;
optimisticFlatEntityMaps: FlatEntityMaps<TFlatEntity>;
dependencyOptimisticFlatEntityMaps: TRelatedFlatEntityMaps;
workspaceId: string;
remainingFlatEntityMapsToValidate: FlatEntityMaps<TFlatEntity>;
buildOptions: WorkspaceMigrationBuilderOptions;
};
export type FlatEntityUpdateValidationArgs<
TFlatEntity extends FlatEntity,
TRelatedFlatEntityMaps extends Partial<AllFlatEntityMaps>,
TFlatEntity extends AllFlatEntities,
TRelatedFlatEntityMaps extends
| Partial<AllFlatEntityMaps>
| undefined = undefined,
> = Omit<
FlatEntityValidationArgs<TFlatEntity, TRelatedFlatEntityMaps>,
'flatEntityToValidate'
'flatEntityToValidate' | 'remainingFlatEntityMapsToValidate'
> & {
flatEntityUpdate: FromTo<TFlatEntity>;
remainingFlatEntityMapsToValidate: FromTo<FlatEntityMaps<TFlatEntity>>;
};
export type FlatEntityValidationReturnType<
TActions extends WorkspaceMigrationActionV2,
TFlatEntity extends FlatEntity,
TFlatEntity extends AllFlatEntities,
TRelatedFlatEntityMaps extends
| Partial<AllFlatEntityMaps>
| undefined = undefined,
> =
| {
status: 'success';
action: TActions | TActions[];
dependencyOptimisticFlatEntityMaps: TRelatedFlatEntityMaps;
}
| ({
status: 'fail';
} & FailedFlatEntityValidation<TFlatEntity>);
// TODO prastoin refactor the builder to building a selection of flatEntityMaps => simplify
export abstract class WorkspaceEntityMigrationBuilderV2Service<
T extends keyof AllFlatEntitiesByMetadataEngineName, // TODO refactor the whole generic to be inferred from this one only
TFlatEntity extends AllFlatEntities,
TActions extends WorkspaceMigrationActionV2,
TRelatedFlatEntityMaps extends Partial<AllFlatEntityMaps>,
TRelatedFlatEntityMaps extends
| Partial<AllFlatEntityMaps>
| undefined = undefined,
> {
@Inject(LoggerService)
protected readonly logger: LoggerService;
private metadataName: T;
constructor(metadataName: T) {
this.metadataName = metadataName;
}
public async validateAndBuild({
buildOptions,
dependencyOptimisticFlatEntityMaps,
from: fromFlatEntityMaps,
to: toFlatEntityMaps,
workspaceId,
}: ValidateAndBuildArgs<TFlatEntity, TRelatedFlatEntityMaps>): Promise<
ValidateAndBuildReturnType<TActions, TFlatEntity>
ValidateAndBuildReturnType<TActions, TFlatEntity, TRelatedFlatEntityMaps>
> {
this.logger.time(`EntityBuilder ${this.metadataName}`, 'validateAndBuild');
this.logger.time(
`EntityBuilder ${this.metadataName}`,
'matrix computation',
);
const fromFlatEntities = Object.values(fromFlatEntityMaps.byId).filter(
isDefined,
);
@@ -118,14 +175,23 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
isDefined,
);
const { created, deleted, updated } = deletedCreatedUpdatedMatrixDispatcher(
{
from: fromFlatEntities,
to: toFlatEntities,
},
);
let optimisticFlatEntityMaps = structuredClone(fromFlatEntityMaps);
const {
createdFlatEntityMaps,
deletedFlatEntityMaps,
updatedFlatEntityMaps,
} = flatEntityDeletedCreatedUpdatedMatrixDispatcher({
from: fromFlatEntities,
to: toFlatEntities,
buildOptions,
});
this.logger.timeEnd(
`EntityBuilder ${this.metadataName}`,
'matrix computation',
);
this.logger.time(`EntityBuilder ${this.metadataName}`, 'entity processing');
let optimisticFlatEntityMaps = structuredClone(fromFlatEntityMaps);
const validateAndBuildResult: ValidateAndBuilActionsReturnType<
TFlatEntity,
TActions
@@ -136,11 +202,38 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
updated: [],
};
for (const flatEntityToCreate of created) {
let remainingFlatEntityMapsToCreate = structuredClone(
createdFlatEntityMaps,
);
this.logger.time(
`EntityBuilder ${this.metadataName}`,
'creation validation',
);
for (const flatEntityToCreateId in createdFlatEntityMaps.byId) {
const flatEntityToCreate =
createdFlatEntityMaps.byId[flatEntityToCreateId];
if (!isDefined(flatEntityToCreate)) {
throw new FlatEntityMapsException(
'Could not find flat entity to create in maps should never occur',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
remainingFlatEntityMapsToCreate =
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatEntityToCreateId,
flatEntityMaps: remainingFlatEntityMapsToCreate,
});
const validationResult = await this.validateFlatEntityCreation({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatEntityToCreate,
optimisticFlatEntityMaps,
workspaceId,
remainingFlatEntityMapsToValidate: remainingFlatEntityMapsToCreate,
buildOptions,
});
if (validationResult.status === 'fail') {
@@ -152,6 +245,8 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
flatEntity: flatEntityToCreate,
flatEntityMaps: optimisticFlatEntityMaps,
});
dependencyOptimisticFlatEntityMaps =
validationResult.dependencyOptimisticFlatEntityMaps;
validateAndBuildResult.created.push(
...(Array.isArray(validationResult.action)
@@ -160,13 +255,45 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
);
}
for (const flatEntityToDelete of buildOptions.inferDeletionFromMissingEntities
? deleted
: []) {
this.logger.timeEnd(
`EntityBuilder ${this.metadataName}`,
'creation validation',
);
this.logger.time(
`EntityBuilder ${this.metadataName}`,
'deletion validation',
);
let remainingFlatEntityMapsToDelete = structuredClone(
deletedFlatEntityMaps,
);
for (const flatEntityToDeleteId in buildOptions.inferDeletionFromMissingEntities
? deletedFlatEntityMaps.byId
: {}) {
const flatEntityToDelete =
deletedFlatEntityMaps.byId[flatEntityToDeleteId];
if (!isDefined(flatEntityToDelete)) {
throw new FlatEntityMapsException(
'Could not find flat entity to delete in maps should never occur',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
remainingFlatEntityMapsToDelete =
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatEntityToDeleteId,
flatEntityMaps: remainingFlatEntityMapsToDelete,
});
const validationResult = await this.validateFlatEntityDeletion({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatEntityToDelete,
optimisticFlatEntityMaps: optimisticFlatEntityMaps,
workspaceId,
remainingFlatEntityMapsToValidate: remainingFlatEntityMapsToDelete,
buildOptions,
});
if (validationResult.status === 'fail') {
@@ -178,6 +305,8 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
entityToDeleteId: flatEntityToDelete.id,
flatEntityMaps: optimisticFlatEntityMaps,
});
dependencyOptimisticFlatEntityMaps =
validationResult.dependencyOptimisticFlatEntityMaps;
validateAndBuildResult.deleted.push(
...(Array.isArray(validationResult.action)
@@ -186,11 +315,53 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
);
}
for (const flatEntityUpdate of updated) {
this.logger.timeEnd(
`EntityBuilder ${this.metadataName}`,
'deletion validation',
);
this.logger.time(`EntityBuilder ${this.metadataName}`, 'update validation');
let remainingFlatEntityMapsToUpdate = structuredClone(
updatedFlatEntityMaps,
);
for (const flatEntityToUpdateId in updatedFlatEntityMaps.to.byId) {
const flatEntityToUpdateFrom =
updatedFlatEntityMaps.from.byId[flatEntityToUpdateId];
const flatEntityToUpdateTo =
updatedFlatEntityMaps.to.byId[flatEntityToUpdateId];
if (
!isDefined(flatEntityToUpdateTo) ||
!isDefined(flatEntityToUpdateFrom)
) {
throw new FlatEntityMapsException(
'Could not find flat entity to update in maps should never occur',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
remainingFlatEntityMapsToUpdate.from =
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatEntityToUpdateId,
flatEntityMaps: remainingFlatEntityMapsToUpdate.from,
});
remainingFlatEntityMapsToUpdate.to =
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatEntityToUpdateId,
flatEntityMaps: remainingFlatEntityMapsToUpdate.to,
});
const validationResult = await this.validateFlatEntityUpdate({
dependencyOptimisticFlatEntityMaps,
flatEntityUpdate,
flatEntityUpdate: {
from: flatEntityToUpdateFrom,
to: flatEntityToUpdateTo,
},
optimisticFlatEntityMaps: optimisticFlatEntityMaps,
workspaceId,
remainingFlatEntityMapsToValidate: remainingFlatEntityMapsToUpdate,
buildOptions,
});
if (validationResult === undefined) {
@@ -203,9 +374,11 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
}
optimisticFlatEntityMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatEntityUpdate.to,
flatEntity: flatEntityToUpdateTo,
flatEntityMaps: optimisticFlatEntityMaps,
});
dependencyOptimisticFlatEntityMaps =
validationResult.dependencyOptimisticFlatEntityMaps;
validateAndBuildResult.updated.push(
...(Array.isArray(validationResult.action)
@@ -214,14 +387,29 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
);
}
this.logger.timeEnd(
`EntityBuilder ${this.metadataName}`,
'update validation',
);
this.logger.timeEnd(
`EntityBuilder ${this.metadataName}`,
'entity processing',
);
if (validateAndBuildResult.failed.length > 0) {
return {
status: 'fail',
errors: validateAndBuildResult.failed,
optimisticFlatEntityMaps,
dependencyOptimisticFlatEntityMaps,
};
}
this.logger.timeEnd(
`EntityBuilder ${this.metadataName}`,
'validateAndBuild',
);
return {
status: 'success',
actions: {
@@ -230,18 +418,38 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
updated: validateAndBuildResult.updated,
},
optimisticFlatEntityMaps,
dependencyOptimisticFlatEntityMaps,
};
}
protected abstract validateFlatEntityCreation(
args: FlatEntityValidationArgs<TFlatEntity, TRelatedFlatEntityMaps>,
): Promise<FlatEntityValidationReturnType<TActions, TFlatEntity>>;
): Promise<
FlatEntityValidationReturnType<
TActions,
TFlatEntity,
TRelatedFlatEntityMaps
>
>;
protected abstract validateFlatEntityDeletion(
args: FlatEntityValidationArgs<TFlatEntity, TRelatedFlatEntityMaps>,
): Promise<FlatEntityValidationReturnType<TActions, TFlatEntity>>;
): Promise<
FlatEntityValidationReturnType<
TActions,
TFlatEntity,
TRelatedFlatEntityMaps
>
>;
protected abstract validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<TFlatEntity, TRelatedFlatEntityMaps>,
): Promise<FlatEntityValidationReturnType<TActions, TFlatEntity> | undefined>;
): Promise<
| FlatEntityValidationReturnType<
TActions,
TFlatEntity,
TRelatedFlatEntityMaps
>
| undefined
>;
}
@@ -1,132 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type FromTo } from 'twenty-shared/types';
import { FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.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 { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/services/workspace-migration-v2-field-actions-builder.service';
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-actions-builder.service';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { CreatedDeletedUpdatedActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { WorkspaceMigrationFieldActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { WorkspaceMigrationObjectActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-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';
export type WorkspaceMigrationV2BuilderOptions = {
inferDeletionFromMissingEntities: boolean;
isSystemBuild: boolean;
};
export type SuccessfulWorkspaceMigrationBuildResult = {
status: 'success';
objectActions: CreatedDeletedUpdatedActions<WorkspaceMigrationObjectActionV2>;
fieldsActions: CreatedDeletedUpdatedActions<WorkspaceMigrationFieldActionV2>;
optimisticFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export type FailedWorkspaceMigrationBuildResult = {
status: 'fail';
optimisticFlatObjectMetadataMaps: FlatObjectMetadataMaps;
errors: FailedFlatEntityValidation<FlatEntity>[];
};
export type WorkspaceMigrationBuildArgs = {
buildOptions: WorkspaceMigrationV2BuilderOptions;
} & FromTo<FlatObjectMetadataMaps, 'FlatObjectMetadataMaps'>;
// TODO deprecate this file
@Injectable()
export class WorkspaceMigrationBuilderV2Service {
constructor(
private readonly workspaceMigrationV2ObjectActionsBuilderService: WorkspaceMigrationV2ObjectActionsBuilderService,
private readonly workspaceMigrationV2FieldActionsBuilderService: WorkspaceMigrationV2FieldActionsBuilderService,
) {}
public async validateAndBuild({
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
buildOptions,
}: WorkspaceMigrationBuildArgs): Promise<
| FailedWorkspaceMigrationBuildResult
| SuccessfulWorkspaceMigrationBuildResult
> {
const fromFlatObjectMetadatas =
fromFlatObjectMetadataMapsToFlatObjectMetadatas(
fromFlatObjectMetadataMaps,
);
const toFlatObjectMetadatas =
fromFlatObjectMetadataMapsToFlatObjectMetadatas(toFlatObjectMetadataMaps);
// Dispatch
const {
created: createdFlatObjectMetadatas,
deleted: deletedFlatObjectMetadatas,
updated: updatedFlatObjectMetadatas,
} = deletedCreatedUpdatedMatrixDispatcher({
from: fromFlatObjectMetadatas,
to: toFlatObjectMetadatas,
});
// Should be handled separately from objects
const objectMetadataDeletedCreatedUpdatedFields =
computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix(
updatedFlatObjectMetadatas,
);
///
// Validate and build
const objectActionsValidateAndBuildResult =
await this.workspaceMigrationV2ObjectActionsBuilderService.validateAndBuildObjectActions(
{
fromFlatObjectMetadataMaps,
createdFlatObjectMetadatas,
deletedFlatObjectMetadatas,
toFlatObjectMetadataMaps,
updatedFlatObjectMetadatas,
buildOptions,
},
);
const fieldActionsValidateAndBuildResult =
await this.workspaceMigrationV2FieldActionsBuilderService.validateAndBuildFieldActions(
{
buildOptions,
fromFlatObjectMetadataMaps:
objectActionsValidateAndBuildResult.optimisticFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
objectMetadataDeletedCreatedUpdatedFields,
},
);
const allValidateAndBuildResultFailures = [
...objectActionsValidateAndBuildResult.failed,
...fieldActionsValidateAndBuildResult.failed,
];
if (allValidateAndBuildResultFailures.length > 0) {
return {
status: 'fail',
errors: allValidateAndBuildResultFailures,
optimisticFlatObjectMetadataMaps:
fieldActionsValidateAndBuildResult.optimisticFlatObjectMetadataMaps,
};
}
return {
status: 'success',
fieldsActions: {
created: fieldActionsValidateAndBuildResult.created,
deleted: fieldActionsValidateAndBuildResult.deleted,
updated: fieldActionsValidateAndBuildResult.updated,
},
objectActions: {
created: objectActionsValidateAndBuildResult.created,
deleted: objectActionsValidateAndBuildResult.deleted,
updated: objectActionsValidateAndBuildResult.updated,
},
optimisticFlatObjectMetadataMaps:
fieldActionsValidateAndBuildResult.optimisticFlatObjectMetadataMaps,
};
}
}
@@ -1,14 +0,0 @@
import { type FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-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: FailedFlatEntityValidation<FlatEntity>[];
created: T[];
deleted: T[];
updated: T[];
optimisticFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
@@ -0,0 +1,4 @@
export type WorkspaceMigrationBuilderOptions = {
inferDeletionFromMissingEntities: boolean;
isSystemBuild: boolean;
};
@@ -4,12 +4,12 @@ import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migr
export type CreateFieldAction = {
type: 'create_field';
flatFieldMetadata: FlatFieldMetadata;
objectMetadataId: string;
flatFieldMetadatas: FlatFieldMetadata[];
};
export type UpdateFieldAction = {
type: 'update_field';
workspaceId: string;
fieldMetadataId: string;
objectMetadataId: string;
updates: Array<
@@ -1,15 +1,12 @@
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-properties-to-compare.type';
import {
type FlatObjectMetadata,
type FlatObjectMetadataWithoutFields,
} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
export type CreateObjectAction = {
type: 'create_object';
flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields;
createFieldActions: CreateFieldAction[];
flatObjectMetadata: FlatObjectMetadata;
flatFieldMetadatas: FlatFieldMetadata[];
};
export type UpdateObjectAction = {
@@ -1,35 +0,0 @@
import { type FromTo } from 'twenty-shared/types';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import {
type CustomDeletedCreatedUpdatedMatrix,
deletedCreatedUpdatedMatrixDispatcher,
} from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
export type UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = {
flatObjectMetadata: FlatObjectMetadata;
} & CustomDeletedCreatedUpdatedMatrix<'flatFieldMetadatas', FlatFieldMetadata>;
export const computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = (
updatedObjectMetadata: FromTo<FlatObjectMetadata>[],
): UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix[] => {
const matrixAccumulator: UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix[] =
[];
for (const { from, to } of updatedObjectMetadata) {
const fieldMetadataMatrix = deletedCreatedUpdatedMatrixDispatcher({
from: from.flatFieldMetadatas,
to: to.flatFieldMetadatas,
});
matrixAccumulator.push({
flatObjectMetadata: to,
createdFlatFieldMetadatas: fieldMetadataMatrix.created,
deletedFlatFieldMetadatas: fieldMetadataMatrix.deleted,
updatedFlatFieldMetadatas: fieldMetadataMatrix.updated,
});
}
return matrixAccumulator;
};
@@ -1,9 +0,0 @@
import {
type FlatObjectMetadata,
type FlatObjectMetadataWithoutFields,
} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const fromFlatObjectMetadataToFlatObjectMetadataWithoutFields = ({
flatFieldMetadatas: _flatFieldMetadatas,
...rest
}: FlatObjectMetadata): FlatObjectMetadataWithoutFields => rest;
@@ -1,12 +0,0 @@
import { type FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
export const fromObjectMetadataEntityToFlatObjectMetadataWithoutFields = (
objectMetadataEntity: ObjectMetadataEntity,
): FlatObjectMetadataWithoutFields => {
return fromObjectMetadataEntityToFlatObjectMetadata({
...objectMetadataEntity,
fields: [],
});
};
@@ -1,27 +0,0 @@
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import {
type CreateFieldAction,
type DeleteFieldAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
type FlatFieldMetadataAndFlatObjectMetadata = {
flatFieldMetadata: FlatFieldMetadata;
flatObjectMetadata: FlatObjectMetadata;
};
export const getWorkspaceMigrationV2FieldCreateAction = ({
flatFieldMetadata,
}: FlatFieldMetadataAndFlatObjectMetadata): CreateFieldAction => ({
type: 'create_field',
flatFieldMetadata,
});
export const getWorkspaceMigrationV2FieldDeleteAction = ({
flatFieldMetadata,
flatObjectMetadata,
}: FlatFieldMetadataAndFlatObjectMetadata): DeleteFieldAction => ({
type: 'delete_field',
fieldMetadataId: flatFieldMetadata.id,
objectMetadataId: flatObjectMetadata.id,
});
@@ -1,27 +0,0 @@
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import {
type CreateObjectAction,
type DeleteObjectAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
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';
export const getWorkspaceMigrationV2ObjectCreateAction = ({
createFieldActions,
flatObjectMetadata,
}: {
flatObjectMetadata: FlatObjectMetadata;
createFieldActions: CreateFieldAction[];
}): CreateObjectAction => ({
type: 'create_object',
flatObjectMetadataWithoutFields:
fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(flatObjectMetadata),
createFieldActions,
});
export const getWorkspaceMigrationV2ObjectDeleteAction = (
flatObjectMetadata: FlatObjectMetadata,
): DeleteObjectAction => ({
type: 'delete_object',
objectMetadataId: flatObjectMetadata.id,
});
@@ -3,21 +3,14 @@ import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { CronTriggerExceptionCode } from 'src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception';
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export type CronTriggerRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatCronTriggerMaps'
>;
type CronTriggerValidationArgs = {
flatCronTriggerToValidate: FlatCronTrigger;
optimisticFlatCronTriggerMaps: FlatEntityMaps<FlatCronTrigger>;
dependencyOptimisticFlatEntityMaps: CronTriggerRelatedFlatEntityMaps;
};
// TODO: validate settings integrity
@Injectable()
@@ -3,21 +3,14 @@ import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { DatabaseEventTriggerExceptionCode } from 'src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception';
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export type DatabaseEventTriggerRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatDatabaseEventTriggerMaps'
>;
type DatabaseEventTriggerValidationArgs = {
flatDatabaseEventTriggerToValidate: FlatDatabaseEventTrigger;
optimisticFlatDatabaseEventTriggerMaps: FlatEntityMaps<FlatDatabaseEventTrigger>;
dependencyOptimisticFlatEntityMaps: DatabaseEventTriggerRelatedFlatEntityMaps;
};
// TODO: validate settings integrity
@Injectable()
@@ -0,0 +1,331 @@
import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { FLAT_FIELD_METADATA_RELATION_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-relation-properties-to-compare.constant';
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { FlatFieldMetadataRelationPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-relation-properties-to-compare.type';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { compareTwoFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
import { isFlatFieldMetadataNameSyncedWithLabel } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-name-synced-with-label.util';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { validateFlatFieldMetadataNameAvailability } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name-availability.util';
import { validateFlatFieldMetadataName } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name.util';
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export type ValidateOneFieldMetadataArgs<
T extends FieldMetadataType = FieldMetadataType,
> = {
dependencyOptimisticFlatEntityMaps: Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps'
>;
remainingFlatEntityMapsToValidate?: FlatEntityMaps<FlatFieldMetadata>;
flatFieldMetadataToValidate: FlatFieldMetadata<T>;
optimisticFlatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
workspaceId: string;
};
@Injectable()
export class FlatFieldMetadataValidatorService {
constructor(
private readonly flatFieldMetadataTypeValidatorService: FlatFieldMetadataTypeValidatorService,
) {}
async validateFlatFieldMetadataUpdate<T extends FieldMetadataType>({
dependencyOptimisticFlatEntityMaps: { flatObjectMetadataMaps },
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
workspaceId,
}: ValidateOneFieldMetadataArgs<T>): Promise<
FailedFlatEntityValidation<FlatFieldMetadata>
> {
const validationResult: FailedFlatEntityValidation<FlatFieldMetadata> = {
type: 'update_field',
errors: [],
flatEntityMinimalInformation: {
id: flatFieldMetadataToValidate.id,
name: flatFieldMetadataToValidate.name,
objectMetadataId: flatFieldMetadataToValidate.objectMetadataId,
},
};
const flatObjectMetadata =
flatObjectMetadataMaps.byId[flatFieldMetadataToValidate.objectMetadataId];
if (!isDefined(flatObjectMetadata)) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: 'field metadata to update object metadata not found',
userFriendlyMessage: t`Object related to field to update not found`,
});
return validationResult;
}
if (!isDefined(flatObjectMetadata.labelIdentifierFieldMetadataId)) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND,
message: 'Label identifier field metadata id does not exist',
userFriendlyMessage: t`Object related to updated field does not have a label identifier`,
});
}
const existingFlatFieldMetadataToUpdate =
optimisticFlatFieldMetadataMaps.byId[flatFieldMetadataToValidate.id];
if (!isDefined(existingFlatFieldMetadataToUpdate)) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
message: 'field metadata to update not found',
userFriendlyMessage: t`Field to update not found`,
});
return validationResult;
}
const updates = compareTwoFlatFieldMetadata({
fromFlatFieldMetadata: existingFlatFieldMetadataToUpdate,
toFlatFieldMetadata: flatFieldMetadataToValidate,
});
if (isMorphOrRelationFlatFieldMetadata(flatFieldMetadataToValidate)) {
const relationNonEditableUpdatedProperties = updates.flatMap(
({ property }) =>
!FLAT_FIELD_METADATA_RELATION_PROPERTIES_TO_COMPARE.includes(
property as FlatFieldMetadataRelationPropertiesToCompare,
)
? property
: [],
);
if (relationNonEditableUpdatedProperties.length > 0) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
message: `Forbidden updated properties for relation field metadata: ${relationNonEditableUpdatedProperties.join(', ')}`,
userFriendlyMessage: t`Forbidden updated properties for relation field metadata`,
});
}
}
if (updates.some((update) => update.property === 'name')) {
validationResult.errors.push(
...validateFlatFieldMetadataName(flatFieldMetadataToValidate.name),
...validateFlatFieldMetadataNameAvailability({
flatFieldMetadata: flatFieldMetadataToValidate,
flatFieldMetadataMaps: optimisticFlatFieldMetadataMaps,
flatObjectMetadata,
}),
);
}
if (
flatFieldMetadataToValidate.isLabelSyncedWithName &&
!isFlatFieldMetadataNameSyncedWithLabel(flatFieldMetadataToValidate)
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
message: `Name is not synced with label.`,
userFriendlyMessage: t`Updated field name is not synced with label`,
});
}
const fieldMetadataTypeValidationErrors =
await this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities(
{
dependencyOptimisticFlatEntityMaps: { flatObjectMetadataMaps },
optimisticFlatFieldMetadataMaps,
flatFieldMetadataToValidate,
workspaceId,
},
);
if (fieldMetadataTypeValidationErrors.length > 0) {
validationResult.errors.push(...fieldMetadataTypeValidationErrors);
}
return validationResult;
}
validateFlatFieldMetadataDeletion({
dependencyOptimisticFlatEntityMaps: { flatObjectMetadataMaps },
flatFieldMetadataToValidate: { id: flatFieldMetadataToDeleteId },
optimisticFlatFieldMetadataMaps,
}: ValidateOneFieldMetadataArgs): FailedFlatEntityValidation<FlatFieldMetadata> {
const validationResult: FailedFlatEntityValidation<FlatFieldMetadata> = {
type: 'delete_field',
errors: [],
flatEntityMinimalInformation: {
id: flatFieldMetadataToDeleteId,
},
};
const flatFieldMetadataToDelete =
optimisticFlatFieldMetadataMaps.byId[flatFieldMetadataToDeleteId];
if (!isDefined(flatFieldMetadataToDelete)) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
message: 'Field metadata to delete not found',
userFriendlyMessage: t`Field metadata to delete not found`,
});
return validationResult;
}
validationResult.flatEntityMinimalInformation = {
name: flatFieldMetadataToDelete.name,
objectMetadataId: flatFieldMetadataToDelete.objectMetadataId,
};
const relatedFlatObjectMetadata =
flatObjectMetadataMaps.byId[flatFieldMetadataToDelete.objectMetadataId];
if (
isDefined(relatedFlatObjectMetadata) &&
relatedFlatObjectMetadata.labelIdentifierFieldMetadataId ===
flatFieldMetadataToDelete.id
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
message:
'Cannot delete, please update the label identifier field first',
userFriendlyMessage: t`Cannot delete, please update the label identifier field first`,
});
}
const relationTargetObjectMetadataHasBeenDeleted =
isMorphOrRelationFlatFieldMetadata(flatFieldMetadataToDelete) &&
!isDefined(
flatObjectMetadataMaps.byId[
flatFieldMetadataToDelete.relationTargetObjectMetadataId
],
);
const parentObjectMetadataHasBeenDeleted = !isDefined(
flatObjectMetadataMaps.byId[flatFieldMetadataToDelete.objectMetadataId],
);
if (
isStandardMetadata(flatFieldMetadataToDelete) &&
!relationTargetObjectMetadataHasBeenDeleted &&
!parentObjectMetadataHasBeenDeleted
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message: "Standard Fields can't be deleted",
userFriendlyMessage: t`Standard fields cannot be deleted`,
});
}
if (
flatFieldMetadataToDelete.isActive &&
!relationTargetObjectMetadataHasBeenDeleted &&
!parentObjectMetadataHasBeenDeleted
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message: "Active fields can't be deleted",
userFriendlyMessage: t`Active fields cannot be deleted`,
});
}
return validationResult;
}
async validateFlatFieldMetadataCreation<
T extends FieldMetadataType = FieldMetadataType,
>({
dependencyOptimisticFlatEntityMaps: { flatObjectMetadataMaps },
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
workspaceId,
remainingFlatEntityMapsToValidate,
}: ValidateOneFieldMetadataArgs<T>): Promise<
FailedFlatEntityValidation<FlatFieldMetadata>
> {
const validationResult: FailedFlatEntityValidation<FlatFieldMetadata> = {
errors: [],
flatEntityMinimalInformation: {
id: flatFieldMetadataToValidate.id,
name: flatFieldMetadataToValidate.name,
objectMetadataId: flatFieldMetadataToValidate.objectMetadataId,
},
type: 'create_field',
};
const parentFlatObjectMetadata =
flatObjectMetadataMaps.byId[flatFieldMetadataToValidate.objectMetadataId];
if (!isDefined(parentFlatObjectMetadata)) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: 'Object metadata not found',
userFriendlyMessage: t`Field to create related object not found`,
});
} else {
if (
isDefined(
optimisticFlatFieldMetadataMaps.byId[flatFieldMetadataToValidate.id],
)
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS,
message: 'Field with same id already exists in object',
userFriendlyMessage: t`Field already exists`,
});
}
if (parentFlatObjectMetadata.isRemote === true) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
message: 'Remote objects are read-only',
userFriendlyMessage: t`Remote objects are not production ready yet`,
});
}
validationResult.errors.push(
...validateFlatFieldMetadataNameAvailability({
flatFieldMetadata: flatFieldMetadataToValidate,
flatFieldMetadataMaps: optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
flatObjectMetadata: parentFlatObjectMetadata,
}),
);
}
if (
flatFieldMetadataToValidate.isLabelSyncedWithName &&
!isFlatFieldMetadataNameSyncedWithLabel(flatFieldMetadataToValidate)
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL,
message: `Name is not synced with label`,
userFriendlyMessage: t`Field name is not synced with field label`,
value: flatFieldMetadataToValidate.label,
});
}
validationResult.errors.push(
...validateFlatFieldMetadataName(flatFieldMetadataToValidate.name),
);
validationResult.errors.push(
...(await this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities(
{
dependencyOptimisticFlatEntityMaps: { flatObjectMetadataMaps },
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
workspaceId,
remainingFlatEntityMapsToValidate,
},
)),
);
return validationResult;
}
}
@@ -8,7 +8,6 @@ import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { IndexExceptionCode } from 'src/engine/metadata-modules/flat-index-metadata/exceptions/index-exception-code';
import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util';
import { IndexRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
@@ -49,7 +48,10 @@ export class FlatIndexValidatorService {
public validateFlatIndexCreation({
flatIndexToValidate,
optimisticFlatIndexMaps,
dependencyOptimisticFlatEntityMaps: { flatObjectMetadataMaps },
dependencyOptimisticFlatEntityMaps: {
flatFieldMetadataMaps,
flatObjectMetadataMaps,
},
}: {
flatIndexToValidate: FlatIndexMetadata;
optimisticFlatIndexMaps: FlatEntityMaps<FlatIndexMetadata>;
@@ -116,11 +118,10 @@ export class FlatIndexValidatorService {
});
} else {
flatIndexToValidate.flatIndexFieldMetadatas.forEach((flatIndexField) => {
const relatedFlatField =
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
fieldMetadataId: flatIndexField.fieldMetadataId,
flatObjectMetadataMaps,
});
const relatedFlatField = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatIndexField.fieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
});
if (!isDefined(relatedFlatField)) {
validationResult.errors.push({
@@ -0,0 +1,253 @@
import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { areFlatObjectMetadataNamesSyncedWithLabels } from 'src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util';
import { validateFlatObjectMetadataLabel } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-label.util';
import { validateFlatObjectMetadataNames } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name.util';
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
import { doesOtherObjectWithSameNameExists } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util';
import { ObjectMetadataRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-actions-builder.service';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-builder-options.type';
export type ValidateOneObjectMetadataArgs = {
flatObjectMetadataToValidate: FlatObjectMetadata;
optimisticFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
buildOptions: WorkspaceMigrationBuilderOptions;
dependencyOptimisticFlatEntityMaps: ObjectMetadataRelatedFlatEntityMaps;
};
@Injectable()
export class FlatObjectMetadataValidatorService {
public validateFlatObjectMetadataUpdate({
optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate: updatedFlatObjectMetadata,
// dependencyOptimisticFlatEntityMaps: { flatFieldMetadataMaps },
}: ValidateOneObjectMetadataArgs): FailedFlatEntityValidation<FlatObjectMetadata> {
const validationResult: FailedFlatEntityValidation<FlatObjectMetadata> = {
type: 'update_object',
errors: [],
flatEntityMinimalInformation: {
id: updatedFlatObjectMetadata.id,
},
};
const existingFlatObjectMetadata =
optimisticFlatObjectMetadataMaps.byId[updatedFlatObjectMetadata.id];
if (!isDefined(existingFlatObjectMetadata)) {
validationResult.errors.push({
code: ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: t`Object to update not found`,
userFriendlyMessage: t`Object to update not found`,
});
return validationResult;
}
validationResult.flatEntityMinimalInformation = {
id: existingFlatObjectMetadata.id,
namePlural: existingFlatObjectMetadata.namePlural,
nameSingular: existingFlatObjectMetadata.nameSingular,
};
validationResult.errors.push(
...this.validateFlatObjectMetadataNameAndLabels({
optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate: updatedFlatObjectMetadata,
}),
);
// const { objectFlatFieldMetadatas } =
// findObjectFieldsInFlatFieldMetadataMapsOrThrow({
// flatFieldMetadataMaps,
// flatObjectMetadata: existingFlatObjectMetadata,
// });
// validationResult.errors.push(
// ...validateFlatObjectMetadataIdentifiers({
// flatObjectMetadata: existingFlatObjectMetadata,
// objectFlatFieldMetadatas,
// }),
// );
return validationResult;
}
public validateFlatObjectMetadataDeletion({
flatObjectMetadataToValidate: { id: objectMetadataToDeleteId },
optimisticFlatObjectMetadataMaps,
buildOptions,
}: ValidateOneObjectMetadataArgs): FailedFlatEntityValidation<FlatObjectMetadata> {
const validationResult: FailedFlatEntityValidation<FlatObjectMetadata> = {
type: 'delete_object',
errors: [],
flatEntityMinimalInformation: {
id: objectMetadataToDeleteId,
},
};
const flatObjectMetadataToDelete =
optimisticFlatObjectMetadataMaps.byId[objectMetadataToDeleteId];
if (!isDefined(flatObjectMetadataToDelete)) {
validationResult.errors.push({
code: ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: t`Object to delete not found`,
userFriendlyMessage: t`Object to delete not found`,
});
} else {
validationResult.flatEntityMinimalInformation = {
id: flatObjectMetadataToDelete.id,
namePlural: flatObjectMetadataToDelete.namePlural,
nameSingular: flatObjectMetadataToDelete.nameSingular,
};
if (flatObjectMetadataToDelete.isRemote) {
validationResult.errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
message: t`Remote objects are not supported yet`,
userFriendlyMessage: t`Remote objects are not supported yet`,
});
}
if (
!buildOptions.isSystemBuild &&
isStandardMetadata(flatObjectMetadataToDelete)
) {
validationResult.errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
message: t`Standard objects cannot be deleted`,
userFriendlyMessage: t`Standard objects cannot be deleted`,
});
}
if (!buildOptions.isSystemBuild && flatObjectMetadataToDelete.isActive) {
validationResult.errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
message: t`Active objects cannot be deleted`,
userFriendlyMessage: t`Active objects cannot be deleted`,
});
}
}
return validationResult;
}
public async validateFlatObjectMetadataCreation({
optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate,
// dependencyOptimisticFlatEntityMaps: { flatFieldMetadataMaps },
}: ValidateOneObjectMetadataArgs): Promise<
FailedFlatEntityValidation<FlatObjectMetadata>
> {
const objectValidationResult: FailedFlatEntityValidation<FlatObjectMetadata> =
{
type: 'create_object',
errors: [],
flatEntityMinimalInformation: {
id: flatObjectMetadataToValidate.id,
namePlural: flatObjectMetadataToValidate.namePlural,
nameSingular: flatObjectMetadataToValidate.nameSingular,
},
};
if (
isDefined(
optimisticFlatObjectMetadataMaps.byId[flatObjectMetadataToValidate.id],
)
) {
objectValidationResult.errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
message: t`Object with same id already exists`,
userFriendlyMessage: t`Object with same id already exists`,
});
}
if (flatObjectMetadataToValidate.isRemote) {
objectValidationResult.errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
message: t`Remote objects are not supported yet`,
userFriendlyMessage: t`Remote objects are not supported yet`,
});
}
// const { objectFlatFieldMetadatas } =
// findObjectFieldsInFlatFieldMetadataMapsOrThrow({
// flatFieldMetadataMaps,
// flatObjectMetadata: flatObjectMetadataToValidate,
// });
// objectValidationResult.errors.push(
// ...validateFlatObjectMetadataIdentifiers({
// flatObjectMetadata: flatObjectMetadataToValidate,
// objectFlatFieldMetadatas,
// }),
// );
objectValidationResult.errors.push(
...this.validateFlatObjectMetadataNameAndLabels({
optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate,
}),
);
return objectValidationResult;
}
private validateFlatObjectMetadataNameAndLabels({
optimisticFlatObjectMetadataMaps,
flatObjectMetadataToValidate,
}: {
flatObjectMetadataToValidate: FlatObjectMetadata;
optimisticFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
}): FlatObjectMetadataValidationError[] {
const errors: FlatObjectMetadataValidationError[] = [];
errors.push(
...validateFlatObjectMetadataNames({
namePlural: flatObjectMetadataToValidate.namePlural,
nameSingular: flatObjectMetadataToValidate.nameSingular,
}),
);
errors.push(
...validateFlatObjectMetadataLabel({
labelPlural: flatObjectMetadataToValidate.labelPlural,
labelSingular: flatObjectMetadataToValidate.labelSingular,
}),
);
if (
flatObjectMetadataToValidate.isLabelSyncedWithName &&
!areFlatObjectMetadataNamesSyncedWithLabels(flatObjectMetadataToValidate)
) {
errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
message: t`Names are not synced with labels`,
userFriendlyMessage: t`Names are not synced with labels`,
});
}
if (
doesOtherObjectWithSameNameExists({
objectMetadataNamePlural: flatObjectMetadataToValidate.namePlural,
objectMetadataNameSingular: flatObjectMetadataToValidate.nameSingular,
objectMetadataMaps: optimisticFlatObjectMetadataMaps,
existingObjectMetadataId: flatObjectMetadataToValidate.id,
})
) {
errors.push({
code: ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS,
message: 'Object already exists',
userFriendlyMessage: t`Object already exists`,
});
}
return errors;
}
}
@@ -3,21 +3,14 @@ import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { ServerlessFunctionExceptionCode } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export type ServerlessFunctionRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatServerlessFunctionMaps'
>;
type ServerlessFunctionValidationArgs = {
flatServerlessFunctionToValidate: FlatServerlessFunction;
optimisticFlatServerlessFunctionMaps: FlatEntityMaps<FlatServerlessFunction>;
dependencyOptimisticFlatEntityMaps: ServerlessFunctionRelatedFlatEntityMaps;
};
@Injectable()
export class FlatServerlessFunctionValidatorService {
@@ -3,10 +3,10 @@ import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { ViewExceptionCode } from 'src/engine/core-modules/view/exceptions/view.exception';
import { FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type';
import { FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { ViewFieldRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/view-field-related-flat-entity-maps.type';
@@ -86,8 +86,8 @@ export class FlatViewFieldValidatorService {
flatViewFieldToValidate,
optimisticFlatViewFieldMaps,
dependencyOptimisticFlatEntityMaps: {
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
flatViewMaps: optimisticFlatViewMaps,
flatFieldMetadataMaps,
},
}: {
flatViewFieldToValidate: FlatViewField;
@@ -96,9 +96,10 @@ export class FlatViewFieldValidatorService {
}): Promise<FailedFlatEntityValidation<FlatViewField>> {
const errors = [];
if (
isDefined(optimisticFlatViewFieldMaps.byId[flatViewFieldToValidate.id])
) {
const existingFlatViewField =
optimisticFlatViewFieldMaps.byId[flatViewFieldToValidate.id];
if (isDefined(existingFlatViewField)) {
const flatViewFieldId = flatViewFieldToValidate.id;
errors.push({
@@ -108,11 +109,21 @@ export class FlatViewFieldValidatorService {
});
}
const relatedFlatFieldMetadata =
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
fieldMetadataId: flatViewFieldToValidate.fieldMetadataId,
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
const relatedFlatView =
optimisticFlatViewMaps.byId[flatViewFieldToValidate.viewId];
if (!isDefined(relatedFlatView)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View not found`,
userFriendlyMessage: t`View not found`,
});
}
const relatedFlatFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatViewFieldToValidate.fieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
});
if (!isDefined(relatedFlatFieldMetadata)) {
errors.push({
@@ -122,17 +133,6 @@ export class FlatViewFieldValidatorService {
});
}
const optimisticFlatView =
optimisticFlatViewMaps.byId[flatViewFieldToValidate.viewId];
if (!isDefined(optimisticFlatView)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View not found`,
userFriendlyMessage: t`View not found`,
});
}
const allFlatViewFields = Object.values(
optimisticFlatViewFieldMaps.byId,
).filter(isDefined);
@@ -1,19 +1,39 @@
import { Module } from '@nestjs/common';
import { FlatFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
import { FlatObjectMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
import { FlatIndexValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-index-metadata-validator.service';
import { FlatServerlessFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service';
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service';
import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service';
@Module({
imports: [FeatureFlagModule],
providers: [
FlatViewValidatorService,
FlatViewFieldValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
FlatObjectMetadataValidatorService,
FlatServerlessFunctionValidatorService,
FlatDatabaseEventTriggerValidatorService,
FlatCronTriggerValidatorService,
FlatFieldMetadataTypeValidatorService,
],
exports: [
FlatViewValidatorService,
FlatViewFieldValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
FlatObjectMetadataValidatorService,
FlatServerlessFunctionValidatorService,
FlatDatabaseEventTriggerValidatorService,
FlatCronTriggerValidatorService,
FlatFieldMetadataTypeValidatorService,
],
})
export class WorkspaceMigrationBuilderValidatorsModule {}
@@ -2,8 +2,6 @@ import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
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 { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/services/workspace-migration-v2-field-actions-builder.service';
@@ -12,40 +10,27 @@ import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/work
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
import { WorkspaceMigrationV2ViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
import { FlatServerlessFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service';
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service';
import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service';
import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/workspace-migration-builder-validators.module';
@Module({
imports: [FeatureFlagModule, WorkspaceMigrationBuilderValidatorsModule],
providers: [
WorkspaceMigrationBuilderV2Service,
FlatFieldMetadataValidatorService,
FlatFieldMetadataTypeValidatorService,
WorkspaceMigrationV2ObjectActionsBuilderService,
FlatObjectMetadataValidatorService,
WorkspaceMigrationV2FieldActionsBuilderService,
WorkspaceMigrationV2ViewActionsBuilderService,
WorkspaceMigrationV2ViewFieldActionsBuilderService,
WorkspaceMigrationV2IndexActionsBuilderService,
WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService,
WorkspaceMigrationV2FieldActionsBuilderService,
WorkspaceMigrationV2CronTriggerActionsBuilderService,
FlatViewValidatorService,
FlatViewFieldValidatorService,
FlatServerlessFunctionValidatorService,
FlatDatabaseEventTriggerValidatorService,
FlatCronTriggerValidatorService,
],
exports: [
WorkspaceMigrationBuilderV2Service,
WorkspaceMigrationV2ViewActionsBuilderService,
WorkspaceMigrationV2IndexActionsBuilderService,
WorkspaceMigrationV2ObjectActionsBuilderService,
WorkspaceMigrationV2ViewFieldActionsBuilderService,
WorkspaceMigrationV2FieldActionsBuilderService,
WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService,
WorkspaceMigrationV2CronTriggerActionsBuilderService,
@@ -9,10 +9,10 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
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 { 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 { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { convertOnDeleteActionToOnDelete } from 'src/engine/workspace-manager/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util';
@@ -40,15 +40,19 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateFieldAction>): Partial<AllFlatEntityMaps> {
const { flatObjectMetadataMaps } = allFlatEntityMaps;
const updatedFlatObjectMetadataMaps =
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: action.flatFieldMetadata,
flatObjectMetadataMaps,
});
const { flatFieldMetadataMaps: existingFlatFieldMetadataMaps } =
allFlatEntityMaps;
const updatedFlatFieldMetadataMaps = action.flatFieldMetadatas.reduce(
(flatFieldMetadataMaps, flatFieldMetadata) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatFieldMetadata,
flatEntityMaps: flatFieldMetadataMaps,
}),
existingFlatFieldMetadataMaps,
);
return {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
flatFieldMetadataMaps: updatedFlatFieldMetadataMaps,
};
}
@@ -61,9 +65,11 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
FieldMetadataEntity,
);
const { flatFieldMetadata } = action;
const { flatFieldMetadatas } = action;
await fieldMetadataRepository.insert(flatFieldMetadata);
for (const flatFieldMetadata of flatFieldMetadatas) {
await fieldMetadataRepository.insert(flatFieldMetadata);
}
}
async executeForWorkspaceSchema(
@@ -75,76 +81,83 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
allFlatEntityMaps: { flatObjectMetadataMaps },
workspaceId,
} = context;
const { flatFieldMetadata } = action;
const { flatFieldMetadatas } = action;
const flatObjectMetadataWithFlatFieldMaps =
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId: flatFieldMetadata.objectMetadataId,
// TODO prastoin improve doing batchs
for (const flatFieldMetadata of flatFieldMetadatas) {
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
flatEntityId: flatFieldMetadata.objectMetadataId,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata,
});
const enumOperations = collectEnumOperationsForField({
flatFieldMetadata: flatFieldMetadata,
tableName,
operation: EnumOperation.CREATE,
});
const enumOperations = collectEnumOperationsForField({
flatFieldMetadata,
tableName,
operation: EnumOperation.CREATE,
});
const columnDefinitions = generateColumnDefinitions({
flatFieldMetadata: flatFieldMetadata,
flatObjectMetadataWithoutFields: flatObjectMetadataWithFlatFieldMaps,
});
const columnDefinitions = generateColumnDefinitions({
flatFieldMetadata,
flatObjectMetadata,
});
await executeBatchEnumOperations({
enumOperations,
queryRunner,
schemaName,
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
});
await executeBatchEnumOperations({
enumOperations,
queryRunner,
schemaName,
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
});
await this.workspaceSchemaManagerService.columnManager.addColumns({
queryRunner,
schemaName,
tableName,
columnDefinitions,
});
await this.workspaceSchemaManagerService.columnManager.addColumns({
queryRunner,
schemaName,
tableName,
columnDefinitions,
});
if (
isMorphOrRelationFlatFieldMetadata(flatFieldMetadata) &&
flatFieldMetadata.settings.relationType === RelationType.MANY_TO_ONE
) {
const referencedTableName = computeObjectTargetTable(
flatFieldMetadata.flatRelationTargetObjectMetadata,
);
if (
isMorphOrRelationFlatFieldMetadata(flatFieldMetadata) &&
flatFieldMetadata.settings.relationType === RelationType.MANY_TO_ONE
) {
const targetFlatObjectMetadata =
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatFieldMetadata.relationTargetObjectMetadataId,
flatEntityMaps: flatObjectMetadataMaps,
});
const referencedTableName = computeObjectTargetTable(
targetFlatObjectMetadata,
);
const joinColumnName = flatFieldMetadata.settings.joinColumnName;
const joinColumnName = flatFieldMetadata.settings.joinColumnName;
if (!isDefined(joinColumnName)) {
throw new Error(
'Join column name is not defined in a MANY_TO_ONE relation',
if (!isDefined(joinColumnName)) {
throw new Error(
'Join column name is not defined in a MANY_TO_ONE relation',
);
}
await this.workspaceSchemaManagerService.foreignKeyManager.createForeignKey(
{
queryRunner,
schemaName,
foreignKey: {
tableName,
columnName: joinColumnName,
referencedTableName,
referencedColumnName: 'id',
onDelete:
convertOnDeleteActionToOnDelete(
flatFieldMetadata.settings.onDelete,
) ?? 'CASCADE',
},
},
);
}
await this.workspaceSchemaManagerService.foreignKeyManager.createForeignKey(
{
queryRunner,
schemaName,
foreignKey: {
tableName,
columnName: joinColumnName,
referencedTableName,
referencedColumnName: 'id',
onDelete:
convertOnDeleteActionToOnDelete(
flatFieldMetadata.settings.onDelete,
) ?? 'CASCADE',
},
},
);
}
}
}
@@ -8,11 +8,9 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-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 { 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';
@@ -38,18 +36,17 @@ export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerAct
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<DeleteFieldAction>): Partial<AllFlatEntityMaps> {
const { flatObjectMetadataMaps } = allFlatEntityMaps;
const { fieldMetadataId, objectMetadataId } = action;
const { flatFieldMetadataMaps } = allFlatEntityMaps;
const { fieldMetadataId } = action;
const updatedFlatObjectMetadataMaps =
deleteFieldFromFlatObjectMetadataMapsOrThrow({
fieldMetadataId,
flatObjectMetadataMaps,
objectMetadataId,
const updatedFlatFieldMetadataMaps =
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: fieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
});
return {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
flatFieldMetadataMaps: updatedFlatFieldMetadataMaps,
};
}
@@ -75,37 +72,29 @@ export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerAct
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
allFlatEntityMaps: { flatObjectMetadataMaps, flatFieldMetadataMaps },
workspaceId,
} = context;
const { objectMetadataId, fieldMetadataId } = action;
const flatObjectMetadataWithFlatFieldMaps =
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
});
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
flatEntityId: objectMetadataId,
});
const fieldMetadata = findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
fieldMetadataId,
const fieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityId: fieldMetadataId,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
flatObjectMetadata,
});
const flatObjectMetadata =
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
});
const columnDefinitions = generateColumnDefinitions({
flatFieldMetadata: fieldMetadata,
flatObjectMetadataWithoutFields: flatObjectMetadata,
flatObjectMetadata: flatObjectMetadata,
});
const columnNamesToDrop = columnDefinitions.map((def) => def.name);
@@ -10,22 +10,19 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type';
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';
import { FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { 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 { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.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 { findFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps.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 { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-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 { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
@@ -71,15 +68,15 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<UpdateFieldAction>): Partial<AllFlatEntityMaps> {
const { flatObjectMetadataMaps } = allFlatEntityMaps;
const { fieldMetadataId, objectMetadataId } = action;
const { flatFieldMetadataMaps } = allFlatEntityMaps;
const { fieldMetadataId } = action;
const existingFlatFieldMetadata =
findFlatFieldMetadataInFlatObjectMetadataMaps({
fieldMetadataId,
objectMetadataId,
flatObjectMetadataMaps,
});
const existingFlatFieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow(
{
flatEntityId: fieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
},
);
if (!isDefined(existingFlatFieldMetadata)) {
throw new WorkspaceMigrationRunnerException(
@@ -93,14 +90,14 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
...fromWorkspaceMigrationUpdateActionToPartialEntity(action),
};
const updatedFlatObjectMetadataMaps =
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: updatedFlatFieldMetadata,
flatObjectMetadataMaps,
const updatedFlatFieldMetadataMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: updatedFlatFieldMetadata,
flatEntityMaps: flatFieldMetadataMaps,
});
return {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
flatFieldMetadataMaps: updatedFlatFieldMetadataMaps,
};
}
@@ -127,28 +124,25 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
allFlatEntityMaps: { flatObjectMetadataMaps, flatFieldMetadataMaps },
workspaceId,
} = context;
const { objectMetadataId, fieldMetadataId, updates } = action;
const flatObjectMetadataWithFlatFieldMaps =
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
});
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: objectMetadataId,
flatEntityMaps: flatObjectMetadataMaps,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
flatObjectMetadata,
});
const currentFlatFieldMetadata =
findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
fieldMetadataId,
});
const currentFlatFieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: fieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
});
let optimisticFlatFieldMetadata = structuredClone(currentFlatFieldMetadata);
@@ -181,10 +175,7 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
queryRunner,
schemaName,
tableName,
flatObjectMetadata:
fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata(
flatObjectMetadataWithFlatFieldMaps,
),
flatObjectMetadata,
flatFieldMetadata: optimisticFlatFieldMetadata,
update,
});
@@ -378,7 +369,7 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
const enumColumnDefinitions = generateColumnDefinitions({
flatFieldMetadata: flatFieldMetadata,
flatObjectMetadataWithoutFields: flatObjectMetadata,
flatObjectMetadata: flatObjectMetadata,
});
for (const enumColumnDefinition of enumColumnDefinitions) {
@@ -17,9 +17,8 @@ import {
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.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 { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
@@ -91,17 +90,16 @@ export class CreateIndexActionHandlerService extends WorkspaceMigrationRunnerAct
context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
): Promise<void> {
const {
allFlatEntityMaps: { flatObjectMetadataMaps },
allFlatEntityMaps: { flatObjectMetadataMaps, flatFieldMetadataMaps },
action: { flatIndexMetadata },
queryRunner,
workspaceId,
} = context;
const flatObjectMetadata =
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId: flatIndexMetadata.objectMetadataId,
});
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
flatEntityId: flatIndexMetadata.objectMetadataId,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata,
@@ -109,11 +107,10 @@ export class CreateIndexActionHandlerService extends WorkspaceMigrationRunnerAct
const quotedColumns = flatIndexMetadata.flatIndexFieldMetadatas.map(
({ fieldMetadataId }) => {
const flatFieldMetadata =
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
fieldMetadataId,
flatObjectMetadataMaps,
});
const flatFieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: fieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
});
if (!isDefined(flatFieldMetadata)) {
throw new FlatEntityMapsException(
@@ -6,11 +6,11 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
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 { 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 { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-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 CreateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
@@ -37,24 +37,26 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateObjectAction>): Partial<AllFlatEntityMaps> {
const { flatObjectMetadataMaps } = allFlatEntityMaps;
const { flatObjectMetadataWithoutFields, createFieldActions } = action;
const { flatObjectMetadataMaps, flatFieldMetadataMaps } = allFlatEntityMaps;
const { flatObjectMetadata, flatFieldMetadatas } = action;
const flatFieldMetadatas = createFieldActions.map(
(createFieldAction) => createFieldAction.flatFieldMetadata,
const updatedFlatFieldMetadatas = flatFieldMetadatas.reduce(
(flatFieldMaps, flatFieldMetadata) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatFieldMetadata,
flatEntityMaps: flatFieldMaps,
}),
flatFieldMetadataMaps,
);
const updatedFlatObjectMetadataMaps =
addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({
flatObjectMetadata: {
...flatObjectMetadataWithoutFields,
flatFieldMetadatas,
},
flatObjectMetadataMaps,
});
const updatedFlatObjectMetadataMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatObjectMetadata,
flatEntityMaps: flatObjectMetadataMaps,
});
return {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
flatFieldMetadataMaps: updatedFlatFieldMetadatas,
};
}
@@ -62,7 +64,7 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
context: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>,
): Promise<void> {
const { action, queryRunner } = context;
const { flatObjectMetadataWithoutFields, createFieldActions } = action;
const { flatObjectMetadata, flatFieldMetadatas } = action;
const objectMetadataRepository =
queryRunner.manager.getRepository<ObjectMetadataEntity>(
@@ -74,13 +76,13 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
const lastDataSourceMetadata = await dataSourceRepository.findOneOrFail({
where: {
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
workspaceId: flatObjectMetadata.workspaceId,
},
order: { createdAt: 'DESC' },
});
await objectMetadataRepository.insert({
...flatObjectMetadataWithoutFields,
...flatObjectMetadata,
dataSourceId: lastDataSourceMetadata.id,
targetTableName: 'DEPRECATED',
});
@@ -90,9 +92,7 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
FieldMetadataEntity,
);
for (const createFieldAction of createFieldActions) {
const { flatFieldMetadata } = createFieldAction;
for (const flatFieldMetadata of flatFieldMetadatas) {
await fieldMetadataRepository.save(flatFieldMetadata);
}
}
@@ -101,21 +101,17 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
context: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { flatObjectMetadataWithoutFields, createFieldActions } = action;
const { flatObjectMetadata, flatFieldMetadatas } = action;
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata: flatObjectMetadataWithoutFields,
flatObjectMetadata,
});
const flatFieldMetadatas = createFieldActions.map(
(createFieldAction) => createFieldAction.flatFieldMetadata,
);
const columnDefinitions = flatFieldMetadatas.flatMap((flatFieldMetadata) =>
generateColumnDefinitions({
flatFieldMetadata,
flatObjectMetadataWithoutFields,
flatObjectMetadata,
}),
);
@@ -6,11 +6,11 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findObjectFieldsInFlatFieldMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
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 { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-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 { 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';
@@ -40,9 +40,9 @@ export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerAc
const { objectMetadataId } = action;
const updatedFlatObjectMetadataMaps =
deleteObjectFromFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
entityToDeleteId: objectMetadataId,
});
return {
@@ -70,20 +70,19 @@ export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerAc
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
allFlatEntityMaps: { flatObjectMetadataMaps, flatFieldMetadataMaps },
workspaceId,
} = context;
const { objectMetadataId } = action;
const flatObjectMetadataWithFlatFieldMaps =
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
});
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
flatEntityId: objectMetadataId,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
flatObjectMetadata,
});
await this.workspaceSchemaManagerService.tableManager.dropTable({
@@ -91,15 +90,15 @@ export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerAc
schemaName,
tableName,
});
const enumOrCompositeFlatFieldMetadatas = Object.values(
flatObjectMetadataWithFlatFieldMaps.fieldsById,
)
.filter((field): field is FlatFieldMetadata => field != null)
.filter(
(field) =>
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
);
const { objectFlatFieldMetadatas } =
findObjectFieldsInFlatFieldMetadataMapsOrThrow({
flatFieldMetadataMaps,
flatObjectMetadata,
});
const enumOrCompositeFlatFieldMetadatas = objectFlatFieldMetadatas.filter(
(field) =>
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
);
const enumOperations = collectEnumOperationsForObject({
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
@@ -6,12 +6,11 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { findObjectFieldsInFlatFieldMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
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 { 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 { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-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';
@@ -43,9 +42,9 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
const { objectMetadataId } = action;
const existingFlatObjectMetadata =
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
objectMetadataId,
flatObjectMetadataMaps,
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: objectMetadataId,
flatEntityMaps: flatObjectMetadataMaps,
});
const updatedFlatObjectMetadata = {
@@ -54,9 +53,9 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
};
const updatedFlatObjectMetadataMaps =
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadata: updatedFlatObjectMetadata,
flatObjectMetadataMaps,
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: updatedFlatObjectMetadata,
flatEntityMaps: flatObjectMetadataMaps,
});
return {
@@ -86,21 +85,20 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
allFlatEntityMaps: { flatObjectMetadataMaps, flatFieldMetadataMaps },
workspaceId,
} = context;
const { objectMetadataId, updates } = action;
const flatObjectMetadataWithFlatFieldMaps =
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
});
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
flatEntityId: objectMetadataId,
});
const { schemaName, tableName: currentTableName } =
getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
flatObjectMetadata: flatObjectMetadata,
});
for (const update of updates) {
@@ -109,7 +107,7 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
}
const updatedObjectMetadata = {
...flatObjectMetadataWithFlatFieldMaps,
...flatObjectMetadata,
[update.property]: update.to,
};
@@ -123,14 +121,16 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
newTableName,
});
const enumOrCompositeFlatFieldMetadatas = Object.values(
flatObjectMetadataWithFlatFieldMaps.fieldsById,
)
.filter((field): field is FlatFieldMetadata => field != null)
.filter(
(field) =>
isEnumFlatFieldMetadata(field) ||
isCompositeFlatFieldMetadata(field),
const { objectFlatFieldMetadatas } =
findObjectFieldsInFlatFieldMetadataMapsOrThrow({
flatFieldMetadataMaps,
flatObjectMetadata: updatedObjectMetadata,
});
const enumOrCompositeFlatFieldMetadatas =
objectFlatFieldMetadatas.filter(
(flatField) =>
isEnumFlatFieldMetadata(flatField) ||
isCompositeFlatFieldMetadata(flatField),
);
const enumOperations = collectEnumOperationsForObject({
@@ -1,5 +1,6 @@
import { SetMetadata } from '@nestjs/common';
import { Inject, SetMetadata } from '@nestjs/common';
import { LoggerService } from 'src/engine/core-modules/logger/logger.service';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import {
type ExtractAction,
@@ -32,16 +33,19 @@ export abstract class BaseWorkspaceMigrationRunnerActionHandlerService<
TActionType extends WorkspaceMigrationActionTypeV2,
> implements WorkspaceMigrationRunnerActionHandlerService<TActionType>
{
public actionType: TActionType;
@Inject(LoggerService)
protected readonly logger: LoggerService;
executeForMetadata(
// eslint-disable-next-line unused-imports/no-unused-vars
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
_context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
): Promise<void> {
return Promise.resolve();
}
executeForWorkspaceSchema(
// eslint-disable-next-line unused-imports/no-unused-vars
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
_context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
): Promise<void> {
return Promise.resolve();
}
@@ -55,8 +59,7 @@ export abstract class BaseWorkspaceMigrationRunnerActionHandlerService<
}
rollbackForMetadata(
// eslint-disable-next-line unused-imports/no-unused-vars
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
_context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
): Promise<void> {
return Promise.resolve();
}
@@ -65,8 +68,14 @@ export abstract class BaseWorkspaceMigrationRunnerActionHandlerService<
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
): Promise<Partial<AllFlatEntityMaps>> {
await Promise.all([
this.executeForMetadata(context),
this.executeForWorkspaceSchema(context),
this.asyncMethodPerformanceMetricWrapper({
label: 'executeForMetadata',
method: async () => this.executeForMetadata(context),
}),
this.asyncMethodPerformanceMetricWrapper({
label: 'executeForWorkspaceSchema',
method: async () => this.executeForWorkspaceSchema(context),
}),
]);
return this.optimisticallyApplyActionOnAllFlatEntityMaps({
@@ -80,6 +89,24 @@ export abstract class BaseWorkspaceMigrationRunnerActionHandlerService<
): Promise<void> {
await this.rollbackForMetadata(context);
}
private async asyncMethodPerformanceMetricWrapper({
label,
method,
}: {
label: string;
method: () => Promise<void>;
}): Promise<void> {
this.logger.time(
'BaseWorkspaceMigrationRunnerActionHandlerService',
`${this.actionType} ${label}`,
);
await method();
this.logger.timeEnd(
'BaseWorkspaceMigrationRunnerActionHandlerService',
`${this.actionType} ${label}`,
);
}
}
export function WorkspaceMigrationRunnerActionHandler<
@@ -87,7 +114,9 @@ export function WorkspaceMigrationRunnerActionHandler<
>(
actionType: TActionType,
): typeof BaseWorkspaceMigrationRunnerActionHandlerService<TActionType> {
abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService<TActionType> {}
abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService<TActionType> {
actionType = actionType;
}
SetMetadata(
WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY,
@@ -3,12 +3,15 @@ import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { LoggerService } from 'src/engine/core-modules/logger/logger.service';
import {
WorkspaceQueryRunnerException,
WorkspaceQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
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 { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service';
@@ -19,6 +22,9 @@ export class WorkspaceMigrationRunnerV2Service {
@InjectDataSource()
private readonly coreDataSource: DataSource,
private readonly workspaceMigrationRunnerActionHandlerRegistry: WorkspaceMigrationRunnerActionHandlerRegistryService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly logger: LoggerService,
) {}
run = async ({
@@ -26,6 +32,9 @@ export class WorkspaceMigrationRunnerV2Service {
workspaceId,
relatedFlatEntityMapsKeys,
}: WorkspaceMigrationV2): Promise<AllFlatEntityMaps> => {
this.logger.time('Runner', 'Total execution');
this.logger.time('Runner', 'Initial cache retrieval');
const queryRunner = this.coreDataSource.createQueryRunner();
let allFlatEntityMaps =
@@ -36,6 +45,9 @@ export class WorkspaceMigrationRunnerV2Service {
},
);
this.logger.timeEnd('Runner', 'Initial cache retrieval');
this.logger.time('Runner', 'Transaction execution');
await queryRunner.connect();
await queryRunner.startTransaction();
@@ -60,10 +72,8 @@ export class WorkspaceMigrationRunnerV2Service {
) as (keyof AllFlatEntityMaps)[];
flatEntityMapsToInvalidate = [
...new Set([
...optimisticallyUpdatedFlatEntityMapsKeys,
...flatEntityMapsToInvalidate,
]),
...optimisticallyUpdatedFlatEntityMapsKeys,
...flatEntityMapsToInvalidate,
];
allFlatEntityMaps = {
@@ -74,11 +84,44 @@ export class WorkspaceMigrationRunnerV2Service {
await queryRunner.commitTransaction();
this.logger.timeEnd('Runner', 'Transaction execution');
this.logger.time('Runner', 'Cache invalidation');
const flatEntitiesCacheToInvalidate = [
...new Set([
...flatEntityMapsToInvalidate,
...(relatedFlatEntityMapsKeys ?? []),
]),
];
if (
flatEntitiesCacheToInvalidate.includes('flatObjectMetadataMaps') ||
flatEntitiesCacheToInvalidate.includes('flatFieldMetadataMaps')
) {
// Temporarily invalidation old cache too until it's deprecated
await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache(
{
workspaceId,
},
);
}
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
workspaceId,
flatEntities: flatEntityMapsToInvalidate,
flatEntities: [
...new Set([
...flatEntityMapsToInvalidate,
...(relatedFlatEntityMapsKeys ?? []),
]),
],
});
this.logger.timeEnd('Runner', 'Cache invalidation');
this.logger.timeEnd('Runner', 'Total execution');
return allFlatEntityMaps;
} catch (error) {
if (queryRunner.isTransactionActive) {
@@ -42,7 +42,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: enumField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toHaveLength(1);
@@ -85,7 +85,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: multiSelectField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toHaveLength(1);
@@ -115,7 +115,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: relationField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
// Relations without join columns must return empty array
@@ -136,7 +136,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: relationField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toHaveLength(1);
@@ -166,7 +166,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: addressField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toHaveLength(8);
@@ -206,7 +206,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: currencyField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toHaveLength(2);
@@ -245,7 +245,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: textField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toStrictEqual([
@@ -271,7 +271,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: booleanField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toStrictEqual([
@@ -298,7 +298,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: textField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toStrictEqual([
@@ -323,7 +323,7 @@ describe('Generate Column Definitions', () => {
const columns = generateColumnDefinitions({
flatFieldMetadata: uuidField,
flatObjectMetadataWithoutFields: mockObjectMetadata,
flatObjectMetadata: mockObjectMetadata,
});
expect(columns).toStrictEqual([
@@ -13,7 +13,7 @@ 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 { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { type FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
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';
@@ -27,15 +27,15 @@ import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-mana
export const generateCompositeColumnDefinition = ({
compositeProperty,
parentFieldMetadata,
flatObjectMetadataWithoutFields,
flatObjectMetadata,
}: {
compositeProperty: CompositeProperty;
parentFieldMetadata: FlatFieldMetadata<CompositeFieldMetadataType>;
flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields;
flatObjectMetadata: FlatObjectMetadata;
}): WorkspaceSchemaColumnDefinition => {
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
flatObjectMetadata: flatObjectMetadataWithoutFields,
workspaceId: flatObjectMetadata.workspaceId,
flatObjectMetadata,
});
if (
@@ -125,11 +125,15 @@ const generateRelationColumnDefinition = (
};
};
const generateColumnDefinition = (
flatFieldMetadata: FlatFieldMetadata,
tableName: string,
schemaName: string,
): WorkspaceSchemaColumnDefinition => {
const generateColumnDefinition = ({
flatFieldMetadata,
schemaName,
tableName,
}: {
flatFieldMetadata: FlatFieldMetadata;
tableName: string;
schemaName: string;
}): WorkspaceSchemaColumnDefinition => {
const columnName = computeColumnName(flatFieldMetadata.name);
const columnType = fieldMetadataTypeToColumnType(
flatFieldMetadata.type,
@@ -159,14 +163,14 @@ const generateColumnDefinition = (
export const generateColumnDefinitions = ({
flatFieldMetadata,
flatObjectMetadataWithoutFields,
flatObjectMetadata,
}: {
flatFieldMetadata: FlatFieldMetadata;
flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields;
flatObjectMetadata: FlatObjectMetadata;
}): WorkspaceSchemaColumnDefinition[] => {
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
flatObjectMetadata: flatObjectMetadataWithoutFields,
workspaceId: flatObjectMetadata.workspaceId,
flatObjectMetadata,
});
if (isCompositeFlatFieldMetadata(flatFieldMetadata)) {
@@ -176,7 +180,7 @@ export const generateColumnDefinitions = ({
generateCompositeColumnDefinition({
compositeProperty: property,
parentFieldMetadata: flatFieldMetadata,
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
flatObjectMetadata,
}),
);
}
@@ -193,5 +197,7 @@ export const generateColumnDefinitions = ({
return relationColumn ? [relationColumn] : [];
}
return [generateColumnDefinition(flatFieldMetadata, tableName, schemaName)];
return [
generateColumnDefinition({ flatFieldMetadata, tableName, schemaName }),
];
};
@@ -1,7 +1,4 @@
import {
type FlatObjectMetadata,
type FlatObjectMetadataWithoutFields,
} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatObjectMetadata } 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';
@@ -15,7 +12,7 @@ export const getWorkspaceSchemaContextForMigration = ({
flatObjectMetadata,
}: {
workspaceId: string;
flatObjectMetadata: FlatObjectMetadata | FlatObjectMetadataWithoutFields;
flatObjectMetadata: FlatObjectMetadata;
}): WorkspaceSchemaContextForMigration => {
return {
schemaName: getWorkspaceSchemaName(workspaceId),
@@ -6,6 +6,8 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-mod
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
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 { 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';
@@ -15,6 +17,8 @@ import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/
FeatureFlagModule,
TypeORMModule,
DataSourceModule,
WorkspaceMetadataVersionModule,
WorkspacePermissionsCacheModule,
WorkspaceMetadataCacheModule,
WorkspaceSchemaMigrationRunnerActionHandlersModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -40,6 +41,7 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
TypeOrmModule.forFeature([Workspace, FeatureFlag]),
WorkspaceMetadataVersionModule,
AgentRoleModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
],
providers: [
...workspaceSyncMetadataFactories,
@@ -5,6 +5,7 @@ import { DataSource, QueryFailedError } from 'typeorm';
import { type WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import {
WorkspaceMigrationEntity,
@@ -40,6 +41,7 @@ export class WorkspaceSyncMetadataService {
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
private readonly workspaceSyncRoleService: WorkspaceSyncRoleService,
private readonly workspaceSyncAgentService: WorkspaceSyncAgentService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
/**
@@ -260,6 +262,10 @@ export class WorkspaceSyncMetadataService {
await this.workspaceMetadataVersionService.incrementMetadataVersion(
context.workspaceId,
);
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
workspaceId: context.workspaceId,
flatEntities: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
});
}
return {