Add workspace migration orchestrator + refactor builders + view/viewField action builders (#14383)

This commit is contained in:
Weiko
2025-09-10 16:55:30 +02:00
committed by GitHub
parent fcf3dc4429
commit 61f1a883a0
78 changed files with 2557 additions and 468 deletions
@@ -0,0 +1,6 @@
export class WorkspaceMigrationOrchestratorException extends Error {
constructor(message = 'Workspace migration orchestrator failed') {
super(message);
this.name = 'WorkspaceMigrationOrchestratorException';
}
}
@@ -0,0 +1,189 @@
import { Injectable, Logger } from '@nestjs/common';
import { isDefined } from 'class-validator';
import { ViewCacheService } from 'src/engine/core-modules/view/cache/services/view-cache.service';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceMigrationOrchestratorException } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-orchestrator-exception';
import {
WorkspaceMigrationOrchestratorBuildArgs,
WorkspaceMigrationOrchestratorEntityMaps,
WorkspaceMigrationOrchestratorFailedResult,
WorkspaceMigrationOrchestratorOptimisticEntityMaps,
} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
import { WorkspaceViewFieldMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-view-field-migration-builder-v2.service';
import { WorkspaceViewMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-view-migration-builder-v2.service';
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service';
import {
WorkspaceMigrationV2Exception,
WorkspaceMigrationV2ExceptionCode,
} from 'src/engine/workspace-manager/workspace-migration.exception';
@Injectable()
export class WorkspaceMigrationBuildOrchestratorService {
private readonly logger = new Logger(
WorkspaceMigrationBuildOrchestratorService.name,
);
constructor(
private readonly workspaceMigrationBuilderV2Service: WorkspaceMigrationBuilderV2Service,
private readonly workspaceViewMigrationBuilderV2Service: WorkspaceViewMigrationBuilderV2Service,
private readonly workspaceViewFieldMigrationBuilderV2Service: WorkspaceViewFieldMigrationBuilderV2Service,
private readonly migrationRunner: WorkspaceMigrationRunnerV2Service,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
private readonly viewCacheService: ViewCacheService,
) {}
public async buildWorkspaceMigrations({
workspaceId,
buildOptions,
entityMaps,
}: WorkspaceMigrationOrchestratorBuildArgs): Promise<
WorkspaceMigrationOrchestratorFailedResult | undefined
> {
try {
const allObjectAndFieldActions = [];
const allViewActions = [];
const allViewFieldActions = [];
const allFailures = [];
const optimisticEntityMaps = await this.prepareOptimisticEntityMaps({
entityMaps,
workspaceId,
});
if (entityMaps.object) {
const objectResult =
await this.workspaceMigrationBuilderV2Service.validateAndBuild({
fromFlatObjectMetadataMaps:
entityMaps.object.fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps:
entityMaps.object.toFlatObjectMetadataMaps,
workspaceId,
buildOptions,
});
if (objectResult.status === 'fail') {
allFailures.push(...objectResult.errors);
} else {
optimisticEntityMaps.object = objectResult.optimisticFlatEntityMaps;
allObjectAndFieldActions.push(
...objectResult.workspaceMigration.actions,
);
}
}
if (entityMaps.view) {
const viewResult =
await this.workspaceViewMigrationBuilderV2Service.validateAndBuild({
fromFlatViewMaps: entityMaps.view.fromFlatViewMaps,
toFlatViewMaps: entityMaps.view.toFlatViewMaps,
workspaceId,
buildOptions,
dependencyOptimisticEntityMaps: {
object: optimisticEntityMaps.object,
},
});
if (viewResult.status === 'fail') {
allFailures.push(...viewResult.errors);
} else {
optimisticEntityMaps.view = viewResult.optimisticFlatEntityMaps;
allViewActions.push(...viewResult.workspaceMigration.actions);
}
}
if (entityMaps.viewField) {
const viewFieldResult =
await this.workspaceViewFieldMigrationBuilderV2Service.validateAndBuild(
{
fromFlatViewFieldMaps: entityMaps.viewField.fromFlatViewFieldMaps,
toFlatViewFieldMaps: entityMaps.viewField.toFlatViewFieldMaps,
workspaceId,
buildOptions,
dependencyOptimisticEntityMaps: {
object: optimisticEntityMaps.object,
view: optimisticEntityMaps.view,
},
},
);
if (viewFieldResult.status === 'fail') {
allFailures.push(...viewFieldResult.errors);
} else {
optimisticEntityMaps.viewField =
viewFieldResult.optimisticFlatEntityMaps;
allViewFieldActions.push(
...viewFieldResult.workspaceMigration.actions,
);
}
}
if (allFailures.length > 0) {
return {
status: 'fail',
errors: allFailures,
};
}
// TODO: return workspace migrations
return;
} catch (error) {
this.logger.error(error);
throw new WorkspaceMigrationV2Exception(
WorkspaceMigrationV2ExceptionCode.BUILDER_INTERNAL_SERVER_ERROR,
error.message,
);
}
}
// TODO: move to dedicated service
private async prepareOptimisticEntityMaps({
entityMaps,
workspaceId,
}: {
entityMaps: WorkspaceMigrationOrchestratorEntityMaps;
workspaceId: string;
}): Promise<WorkspaceMigrationOrchestratorOptimisticEntityMaps> {
const optimisticObjectMaps =
entityMaps.object?.fromFlatObjectMetadataMaps ??
(
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
{ workspaceId },
)
).flatObjectMetadataMaps;
if (!isDefined(optimisticObjectMaps)) {
throw new WorkspaceMigrationOrchestratorException(
'Object metadata maps not found',
);
}
const optimisticViewMaps =
entityMaps.view?.fromFlatViewMaps ??
(
await this.viewCacheService.getExistingOrRecomputeFlatViewMaps({
workspaceId,
})
).flatViewMaps;
if (!isDefined(optimisticViewMaps)) {
throw new WorkspaceMigrationOrchestratorException('View maps not found');
}
// For ViewFields, if not provided, start with empty maps since ViewFields are optional
const optimisticViewFieldMaps = entityMaps.viewField
?.fromFlatViewFieldMaps ?? {
byId: {},
idByUniversalIdentifier: {},
};
return {
object: optimisticObjectMaps,
view: optimisticViewMaps,
viewField: optimisticViewFieldMaps,
};
}
}
@@ -11,6 +11,7 @@ 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(
@@ -1,6 +0,0 @@
import { type ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
export type FlatViewField = Pick<
ViewFieldEntity,
'fieldMetadataId' | 'position' | 'isVisible' | 'size'
>;
@@ -1,9 +0,0 @@
import { type ViewEntity } from 'src/engine/core-modules/view/entities/view.entity';
import { type FlatViewField } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-view-field.type';
export type FlatView = Pick<
ViewEntity,
'name' | 'objectMetadataId' | 'type' | 'isCustom' | 'key' | 'icon'
> & {
viewFields: FlatViewField[];
};
@@ -0,0 +1,41 @@
import { type FromTo } from 'twenty-shared/types';
import { type FailedFlatViewFieldValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-field-validation.type';
import { type FailedFlatViewValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-validation.type';
import { type FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type';
import { type FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type';
import { type FailedFlatFieldMetadataValidation } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { type FailedFlatObjectMetadataValidation } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
import { type WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
export type WorkspaceMigrationOrchestratorEntityMaps = {
object?: FromTo<FlatObjectMetadataMaps, 'FlatObjectMetadataMaps'>;
field?: FromTo<FlatObjectMetadataMaps, 'FlatObjectMetadataMaps'>; // Fields are part of object maps for now
view?: FromTo<FlatViewMaps, 'FlatViewMaps'>;
viewField?: FromTo<FlatViewFieldMaps, 'FlatViewFieldMaps'>;
};
type UnwrapFromTo<T> = T extends FromTo<infer U, infer _Tag> ? U : T;
export type WorkspaceMigrationOrchestratorOptimisticEntityMaps = {
[K in keyof WorkspaceMigrationOrchestratorEntityMaps]?: UnwrapFromTo<
WorkspaceMigrationOrchestratorEntityMaps[K]
>;
};
export type WorkspaceMigrationOrchestratorBuildArgs = {
workspaceId: string;
buildOptions: WorkspaceMigrationV2BuilderOptions;
entityMaps: WorkspaceMigrationOrchestratorEntityMaps;
};
export type WorkspaceMigrationOrchestratorFailedResult = {
status: 'fail';
errors: (
| FailedFlatObjectMetadataValidation
| FailedFlatFieldMetadataValidation
| FailedFlatViewValidation
| FailedFlatViewFieldValidation
)[];
};
@@ -1033,85 +1033,6 @@ exports[`Workspace migration builder object actions test suite It should build a
},
"type": "create_object",
},
{
"type": "create_view",
"view": {
"icon": "IconList",
"isCustom": false,
"key": "INDEX",
"name": "All {objectLabelPlural}",
"objectMetadataId": Any<String>,
"type": "TABLE",
"viewFields": [
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 0,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 1,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 2,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 3,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 4,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 5,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 6,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 7,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 8,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 9,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 10,
"size": 180,
},
],
},
},
]
`;
@@ -2419,181 +2340,6 @@ exports[`Workspace migration builder object actions test suite It should build a
},
"type": "create_object",
},
{
"type": "create_view",
"view": {
"icon": "IconList",
"isCustom": false,
"key": "INDEX",
"name": "All {objectLabelPlural}",
"objectMetadataId": Any<String>,
"type": "TABLE",
"viewFields": [
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 0,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 1,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 2,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 3,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 4,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 5,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 6,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 7,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 8,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 9,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 10,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 11,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 12,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 13,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 14,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 15,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 16,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 17,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 18,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 19,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 20,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 21,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 22,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 23,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 24,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 25,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 26,
"size": 180,
},
],
},
},
]
`;
@@ -3246,85 +2992,6 @@ exports[`Workspace migration builder object actions test suite It should build a
},
"type": "create_index",
},
{
"type": "create_view",
"view": {
"icon": "IconList",
"isCustom": false,
"key": "INDEX",
"name": "All {objectLabelPlural}",
"objectMetadataId": Any<String>,
"type": "TABLE",
"viewFields": [
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 0,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 1,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 2,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 3,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 4,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 5,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 6,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 7,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 8,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 9,
"size": 180,
},
{
"fieldMetadataId": Any<String>,
"isVisible": true,
"position": 10,
"size": 180,
},
],
},
},
]
`;
@@ -44,7 +44,7 @@ const DELETE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [
context: {
input: {
buildOptions: {
inferDeletionFromMissingObjectFieldIndex: true,
inferDeletionFromMissingEntities: true,
isSystemBuild: true,
},
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
@@ -81,7 +81,6 @@ const CREATE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [
},
expectedActionsTypeCounter: {
createObject: 1,
createView: 1,
},
},
},
@@ -103,7 +102,6 @@ const CREATE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [
},
expectedActionsTypeCounter: {
createObject: 1,
createView: 1,
},
},
},
@@ -114,7 +112,6 @@ const CREATE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [
expectedActionsTypeCounter: {
createIndex: 1,
createObject: 1,
createView: 1,
},
input: {
fromFlatObjectMetadataMaps:
@@ -54,7 +54,6 @@ const expectedActionsTypeCounterChecker = ({
deleteObject: 0,
updateField: 0,
updateObject: 0,
createView: 0,
};
const actualActionsTypeCounter = workspaceMigration.actions.reduce(
(acc, action) => {
@@ -125,7 +124,7 @@ describe.each(allWorkspaceBuilderTestCases)(
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
buildOptions = {
inferDeletionFromMissingObjectFieldIndex: true,
inferDeletionFromMissingEntities: true,
isSystemBuild: false,
},
} = typeof input === 'function' ? input() : input;
@@ -155,7 +155,7 @@ export class WorkspaceMigrationV2FieldActionsBuilderService {
validateAndBuildResult.created.push(createFieldAction);
}
for (const flatFieldMetadataToDelete of buildOptions.inferDeletionFromMissingObjectFieldIndex
for (const flatFieldMetadataToDelete of buildOptions.inferDeletionFromMissingEntities
? deletedFlatFieldMetadatas
: []) {
const validationErrors =
@@ -103,7 +103,7 @@ export class WorkspaceMigrationV2ObjectActionsBuilderService {
validateAndBuildResult.created.push(createObjectAction);
}
for (const flatObjectMetadataToDelete of buildOptions.inferDeletionFromMissingObjectFieldIndex
for (const flatObjectMetadataToDelete of buildOptions.inferDeletionFromMissingEntities
? deletedFlatObjectMetadatas
: []) {
const validationErrors =
@@ -0,0 +1,178 @@
import { Injectable } from '@nestjs/common';
import { FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
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 { FailedFlatViewFieldValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-field-validation.type';
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 { compareTwoFlatViewField } from 'src/engine/core-modules/view/flat-view/utils/compare-two-flat-view-field.util';
import { WorkspaceMigrationOrchestratorOptimisticEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { type CustomDeletedCreatedUpdatedMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
import { WorkspaceViewFieldMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-view-field-migration-builder-v2.service';
import { WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
import {
UpdateViewFieldAction,
WorkspaceMigrationViewFieldActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
import {
getWorkspaceMigrationV2ViewFieldCreateAction,
getWorkspaceMigrationV2ViewFieldDeleteAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-view-field-action';
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service';
export type CreatedDeletedUpdatedViewFieldInputMatrix = FromTo<
FlatViewFieldMaps,
'FlatViewFieldMaps'
> &
CustomDeletedCreatedUpdatedMatrix<'flatViewField', FlatViewField> & {
buildOptions: WorkspaceViewFieldMigrationV2BuilderOptions;
fromFlatViewFieldMaps: FlatViewFieldMaps;
dependencyOptimisticEntityMaps: WorkspaceMigrationOrchestratorOptimisticEntityMaps;
};
type ValidateAndBuildViewFieldResult<T extends WorkspaceMigrationActionV2> = {
failed: FailedFlatViewFieldValidation[];
created: T[];
deleted: T[];
updated: T[];
optimisticFlatViewFieldMaps: FlatViewFieldMaps;
};
@Injectable()
export class WorkspaceMigrationV2ViewFieldActionsBuilderService {
constructor(
private readonly flatViewFieldValidatorService: FlatViewFieldValidatorService,
) {}
public async validateAndBuildViewFieldActions({
createdFlatViewField,
deletedFlatViewField,
updatedFlatViewField,
buildOptions,
fromFlatViewFieldMaps,
dependencyOptimisticEntityMaps,
}: CreatedDeletedUpdatedViewFieldInputMatrix): Promise<
ValidateAndBuildViewFieldResult<WorkspaceMigrationViewFieldActionV2>
> {
const validateAndBuildResult: ValidateAndBuildViewFieldResult<WorkspaceMigrationViewFieldActionV2> =
{
failed: [],
created: [],
deleted: [],
updated: [],
optimisticFlatViewFieldMaps: structuredClone(fromFlatViewFieldMaps),
};
if (
!isDefined(dependencyOptimisticEntityMaps.object) ||
!isDefined(dependencyOptimisticEntityMaps.view)
) {
throw new Error('Dependency optimistic entity maps are not defined');
}
for (const flatViewFieldToCreate of createdFlatViewField) {
const validationErrors =
await this.flatViewFieldValidatorService.validateFlatViewFieldCreation({
_existingFlatViewFieldMaps:
validateAndBuildResult.optimisticFlatViewFieldMaps,
flatViewFieldToValidate: flatViewFieldToCreate,
optimisticFlatObjectMetadataMaps:
dependencyOptimisticEntityMaps.object,
optimisticFlatViewMaps: dependencyOptimisticEntityMaps.view,
});
if (validationErrors.viewFieldLevelErrors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatViewFieldMaps =
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewFieldToCreate,
flatEntityMaps: validateAndBuildResult.optimisticFlatViewFieldMaps,
});
const createViewFieldAction =
getWorkspaceMigrationV2ViewFieldCreateAction({
flatViewField: flatViewFieldToCreate,
});
validateAndBuildResult.created.push(createViewFieldAction);
}
for (const flatViewFieldToDelete of buildOptions.inferDeletionFromMissingEntities
? deletedFlatViewField
: []) {
const validationErrors =
this.flatViewFieldValidatorService.validateFlatViewFieldDeletion({
existingFlatViewFieldMaps:
validateAndBuildResult.optimisticFlatViewFieldMaps,
viewFieldIdToDelete: flatViewFieldToDelete.id,
optimisticFlatViewMaps: dependencyOptimisticEntityMaps.view,
});
if (validationErrors.viewFieldLevelErrors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatViewFieldMaps =
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatViewFieldToDelete.id,
flatEntityMaps: validateAndBuildResult.optimisticFlatViewFieldMaps,
});
const deleteViewFieldAction =
getWorkspaceMigrationV2ViewFieldDeleteAction(flatViewFieldToDelete);
validateAndBuildResult.deleted.push(deleteViewFieldAction);
}
for (const {
from: fromFlatViewField,
to: toFlatViewField,
} of updatedFlatViewField) {
const viewFieldUpdatedProperties = compareTwoFlatViewField({
fromFlatViewField,
toFlatViewField,
});
if (viewFieldUpdatedProperties.length === 0) {
continue;
}
const validationErrors =
this.flatViewFieldValidatorService.validateFlatViewFieldUpdate({
existingFlatViewFieldMaps:
validateAndBuildResult.optimisticFlatViewFieldMaps,
updatedFlatViewField: toFlatViewField,
optimisticFlatViewMaps: dependencyOptimisticEntityMaps.view,
});
if (validationErrors.viewFieldLevelErrors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatViewFieldMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: toFlatViewField,
flatEntityMaps: validateAndBuildResult.optimisticFlatViewFieldMaps,
});
const updateViewFieldAction: UpdateViewFieldAction = {
type: 'update_view_field',
viewFieldId: toFlatViewField.id,
updates: viewFieldUpdatedProperties,
};
validateAndBuildResult.updated.push(updateViewFieldAction);
}
return validateAndBuildResult;
}
}
@@ -0,0 +1,165 @@
import { Injectable } from '@nestjs/common';
import { FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
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 { FailedFlatViewValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-validation.type';
import { FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type';
import { FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
import { compareTwoFlatView } from 'src/engine/core-modules/view/flat-view/utils/compare-two-flat-view.util';
import { WorkspaceMigrationOrchestratorOptimisticEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { type CustomDeletedCreatedUpdatedMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
import { WorkspaceViewMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-view-migration-builder-v2.service';
import { WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
import {
UpdateViewAction,
WorkspaceMigrationViewActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
import {
getWorkspaceMigrationV2ViewCreateAction,
getWorkspaceMigrationV2ViewDeleteAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-view-action';
import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service';
export type CreatedDeletedUpdatedViewInputMatrix = FromTo<
FlatViewMaps,
'FlatViewMaps'
> &
CustomDeletedCreatedUpdatedMatrix<'flatView', FlatView> & {
buildOptions: WorkspaceViewMigrationV2BuilderOptions;
fromFlatViewMaps: FlatViewMaps;
dependencyOptimisticEntityMaps: WorkspaceMigrationOrchestratorOptimisticEntityMaps;
};
type ValidateAndBuildViewResult<T extends WorkspaceMigrationActionV2> = {
failed: FailedFlatViewValidation[];
created: T[];
deleted: T[];
updated: T[];
optimisticFlatViewMaps: FlatViewMaps;
};
@Injectable()
export class WorkspaceMigrationV2ViewActionsBuilderService {
constructor(
private readonly flatViewValidatorService: FlatViewValidatorService,
) {}
public async validateAndBuildViewActions({
createdFlatView,
deletedFlatView,
updatedFlatView,
buildOptions,
fromFlatViewMaps,
dependencyOptimisticEntityMaps,
}: CreatedDeletedUpdatedViewInputMatrix): Promise<
ValidateAndBuildViewResult<WorkspaceMigrationViewActionV2>
> {
const validateAndBuildResult: ValidateAndBuildViewResult<WorkspaceMigrationViewActionV2> =
{
failed: [],
created: [],
deleted: [],
updated: [],
optimisticFlatViewMaps: structuredClone(fromFlatViewMaps),
};
if (!isDefined(dependencyOptimisticEntityMaps.object)) {
throw new Error('Dependency optimistic entity maps are not defined');
}
for (const flatViewToCreate of createdFlatView) {
const validationErrors =
await this.flatViewValidatorService.validateFlatViewCreation({
_existingFlatViewMaps: validateAndBuildResult.optimisticFlatViewMaps,
flatViewToValidate: flatViewToCreate,
optimisticFlatObjectMetadataMaps:
dependencyOptimisticEntityMaps.object,
});
if (validationErrors.viewLevelErrors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatViewMaps =
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewToCreate,
flatEntityMaps: validateAndBuildResult.optimisticFlatViewMaps,
});
const createViewAction = getWorkspaceMigrationV2ViewCreateAction({
flatView: flatViewToCreate,
});
validateAndBuildResult.created.push(createViewAction);
}
for (const flatViewToDelete of buildOptions.inferDeletionFromMissingEntities
? deletedFlatView
: []) {
const validationErrors =
this.flatViewValidatorService.validateFlatViewDeletion({
existingFlatViewMaps: validateAndBuildResult.optimisticFlatViewMaps,
viewIdToDelete: flatViewToDelete.id,
});
if (validationErrors.viewLevelErrors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatViewMaps =
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatViewToDelete.id,
flatEntityMaps: validateAndBuildResult.optimisticFlatViewMaps,
});
const deleteViewAction =
getWorkspaceMigrationV2ViewDeleteAction(flatViewToDelete);
validateAndBuildResult.deleted.push(deleteViewAction);
}
for (const { from: fromFlatView, to: toFlatView } of updatedFlatView) {
const viewUpdatedProperties = compareTwoFlatView({
fromFlatView,
toFlatView,
});
if (viewUpdatedProperties.length === 0) {
continue;
}
const validationErrors =
this.flatViewValidatorService.validateFlatViewUpdate({
existingFlatViewMaps: validateAndBuildResult.optimisticFlatViewMaps,
updatedFlatView: toFlatView,
});
if (validationErrors.viewLevelErrors.length > 0) {
validateAndBuildResult.failed.push(validationErrors);
continue;
}
validateAndBuildResult.optimisticFlatViewMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: toFlatView,
flatEntityMaps: validateAndBuildResult.optimisticFlatViewMaps,
});
const updateViewAction: UpdateViewAction = {
type: 'update_view',
viewId: toFlatView.id,
updates: viewUpdatedProperties,
};
validateAndBuildResult.updated.push(updateViewAction);
}
return validateAndBuildResult;
}
}
@@ -1,41 +0,0 @@
import { ViewKey } from 'src/engine/core-modules/view/enums/view-key.enum';
import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import {
type CreateViewAction,
type WorkspaceMigrationViewActionV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
type BuildWorkspaceMigrationViewActionsArgs = {
createdFlatObjectMetadatas: FlatObjectMetadata[];
};
const getCreateViewAction = (
flatObjectMetadata: FlatObjectMetadata,
): CreateViewAction => {
return {
type: 'create_view',
view: {
objectMetadataId: flatObjectMetadata.id,
type: ViewType.TABLE,
name: `All {objectLabelPlural}`,
key: ViewKey.INDEX,
isCustom: false,
icon: 'IconList',
viewFields: flatObjectMetadata.flatFieldMetadatas
.filter((field) => field.name !== 'id' && field.name !== 'deletedAt')
.map((field, index) => ({
fieldMetadataId: field.id,
position: index,
isVisible: true,
size: 180,
})),
},
};
};
export const buildWorkspaceMigrationViewActions = ({
createdFlatObjectMetadatas,
}: BuildWorkspaceMigrationViewActionsArgs): WorkspaceMigrationViewActionV2[] => {
return createdFlatObjectMetadatas.map(getCreateViewAction);
};
@@ -0,0 +1,102 @@
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
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 { deletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
export type SuccessfulEntityMigrationBuildResult<TFlatEntityMaps> = {
status: 'success';
workspaceMigration: WorkspaceMigrationV2;
optimisticFlatEntityMaps: TFlatEntityMaps;
};
export type FailedEntityMigrationBuildResult<TFailedValidation> = {
status: 'fail';
errors: TFailedValidation[];
};
export abstract class WorkspaceEntityMigrationBuilderV2Service<
TFlatEntityMaps extends FlatEntityMaps<TFlatEntity>,
TFlatEntity extends FlatEntity,
TFailedValidation,
TActionType extends WorkspaceMigrationActionV2,
TBuildArgs extends { workspaceId: string } & FromTo<TFlatEntityMaps, string>,
> {
public async validateAndBuild(
args: TBuildArgs,
): Promise<
| SuccessfulEntityMigrationBuildResult<TFlatEntityMaps>
| FailedEntityMigrationBuildResult<TFailedValidation>
> {
const { from: fromEntityMaps, to: toEntityMaps } =
this.extractFromToEntityMaps(args);
const fromEntities = Object.values(fromEntityMaps.byId).filter(isDefined);
const toEntities = Object.values(toEntityMaps.byId).filter(isDefined);
const { created, deleted, updated } = deletedCreatedUpdatedMatrixDispatcher(
{
from: fromEntities,
to: toEntities,
},
);
const result = await this.validateAndBuildActions({
...args,
created,
updated,
deleted,
});
if (result.failed.length > 0) {
return {
status: 'fail',
errors: result.failed,
};
}
return {
status: 'success',
workspaceMigration: {
workspaceId: args.workspaceId,
actions: [...result.deleted, ...result.created, ...result.updated],
},
optimisticFlatEntityMaps: result.optimisticMaps,
};
}
// TODO: Refactor, this is too hacky
private extractFromToEntityMaps(args: TBuildArgs): {
from: TFlatEntityMaps;
to: TFlatEntityMaps;
} {
const fromKey = Object.keys(args).find((key) =>
key.startsWith('from'),
) as keyof TBuildArgs;
const toKey = Object.keys(args).find((key) =>
key.startsWith('to'),
) as keyof TBuildArgs;
return {
from: args[fromKey] as TFlatEntityMaps,
to: args[toKey] as TFlatEntityMaps,
};
}
protected abstract validateAndBuildActions(
args: TBuildArgs & {
created: TFlatEntity[];
updated: { from: TFlatEntity; to: TFlatEntity }[];
deleted: TFlatEntity[];
},
): Promise<{
failed: TFailedValidation[];
created: TActionType[];
deleted: TActionType[];
updated: TActionType[];
optimisticMaps: TFlatEntityMaps;
}>;
}
@@ -10,20 +10,20 @@ import { deletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-mana
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 { buildWorkspaceMigrationIndexActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder';
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 { buildWorkspaceMigrationViewActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder';
import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
import { computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util';
import { computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util';
import { getWorkspaceMigrationV2CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions';
export type WorkspaceMigrationV2BuilderOptions = {
inferDeletionFromMissingObjectFieldIndex: boolean;
inferDeletionFromMissingEntities: boolean;
isSystemBuild: boolean;
};
type SuccessfulWorkspaceMigrationBuildResult = {
status: 'success';
workspaceMigration: WorkspaceMigrationV2;
optimisticFlatEntityMaps: FlatObjectMetadataMaps;
};
export type FailedWorkspaceMigrationBuildResult = {
@@ -71,11 +71,13 @@ export class WorkspaceMigrationBuilderV2Service {
to: toFlatObjectMetadatas,
});
// Should be handled separately from objects
const objectMetadataDeletedCreatedUpdatedIndex =
computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix(
updatedFlatObjectMetadatas,
);
// Should be handled separately from objects
const objectMetadataDeletedCreatedUpdatedFields =
computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix(
updatedFlatObjectMetadatas,
@@ -116,12 +118,7 @@ export class WorkspaceMigrationBuilderV2Service {
const indexWorkspaceMigrationActions = buildWorkspaceMigrationIndexActions({
objectMetadataDeletedCreatedUpdatedIndex,
inferDeletionFromMissingObjectFieldIndex:
buildOptions.inferDeletionFromMissingObjectFieldIndex,
});
// TODO: Remove this from here when we have a proper builder that handles views diffing independently
const viewWorkspaceMigrationActions = buildWorkspaceMigrationViewActions({
createdFlatObjectMetadatas, // should be flatViews, not objects.
buildOptions.inferDeletionFromMissingEntities,
});
const allValidateAndBuildResultFailures = [
@@ -141,17 +138,18 @@ export class WorkspaceMigrationBuilderV2Service {
workspaceMigration: {
workspaceId,
actions: [
...fieldActionsValidateAndBuildResult.deleted,
...fieldActionsValidateAndBuildResult.deleted, // Should be handled separately from objects
...objectActionsValidateAndBuildResult.deleted,
...objectActionsValidateAndBuildResult.created,
...objectActionsValidateAndBuildResult.updated,
...fieldActionsValidateAndBuildResult.created,
...fieldActionsValidateAndBuildResult.updated,
...fieldActionsValidateAndBuildResult.created, // Should be handled separately from objects
...fieldActionsValidateAndBuildResult.updated, // Should be handled separately from objects
...createdObjectMetadataCreateIndexActions,
...indexWorkspaceMigrationActions,
...viewWorkspaceMigrationActions,
...indexWorkspaceMigrationActions, // Should be handled separately from objects
],
},
optimisticFlatEntityMaps:
fieldActionsValidateAndBuildResult.optimisticFlatObjectMetadataMaps,
};
}
}
@@ -0,0 +1,79 @@
import { Injectable } from '@nestjs/common';
import { type FromTo } from 'twenty-shared/types';
import { FailedFlatViewFieldValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-field-validation.type';
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 { WorkspaceMigrationOrchestratorOptimisticEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
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 {
FailedEntityMigrationBuildResult,
WorkspaceEntityMigrationBuilderV2Service,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { WorkspaceMigrationViewFieldActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
export type WorkspaceViewFieldMigrationV2BuilderOptions = {
isSystemBuild: boolean;
inferDeletionFromMissingEntities: boolean;
};
export type FailedWorkspaceViewFieldMigrationBuildResult =
FailedEntityMigrationBuildResult<FailedFlatViewFieldValidation>;
export type WorkspaceViewFieldMigrationBuildArgs = {
workspaceId: string;
buildOptions: WorkspaceViewFieldMigrationV2BuilderOptions;
dependencyOptimisticEntityMaps: WorkspaceMigrationOrchestratorOptimisticEntityMaps;
} & FromTo<FlatViewFieldMaps, 'FlatViewFieldMaps'>;
@Injectable()
export class WorkspaceViewFieldMigrationBuilderV2Service extends WorkspaceEntityMigrationBuilderV2Service<
FlatViewFieldMaps,
FlatViewField,
FailedFlatViewFieldValidation,
WorkspaceMigrationViewFieldActionV2,
WorkspaceViewFieldMigrationBuildArgs
> {
constructor(
private readonly workspaceMigrationV2ViewFieldActionsBuilderService: WorkspaceMigrationV2ViewFieldActionsBuilderService,
) {
super();
}
protected async validateAndBuildActions(
args: WorkspaceViewFieldMigrationBuildArgs & {
created: FlatViewField[];
updated: { from: FlatViewField; to: FlatViewField }[];
deleted: FlatViewField[];
},
): Promise<{
failed: FailedFlatViewFieldValidation[];
created: WorkspaceMigrationViewFieldActionV2[];
deleted: WorkspaceMigrationViewFieldActionV2[];
updated: WorkspaceMigrationViewFieldActionV2[];
optimisticMaps: FlatViewFieldMaps;
}> {
const viewFieldActionsValidateAndBuildResult =
await this.workspaceMigrationV2ViewFieldActionsBuilderService.validateAndBuildViewFieldActions(
{
fromFlatViewFieldMaps: args.fromFlatViewFieldMaps,
toFlatViewFieldMaps: args.toFlatViewFieldMaps,
createdFlatViewField: args.created,
updatedFlatViewField: args.updated,
deletedFlatViewField: args.deleted,
buildOptions: args.buildOptions,
dependencyOptimisticEntityMaps: args.dependencyOptimisticEntityMaps,
},
);
return {
failed: viewFieldActionsValidateAndBuildResult.failed,
created: viewFieldActionsValidateAndBuildResult.created,
deleted: viewFieldActionsValidateAndBuildResult.deleted,
updated: viewFieldActionsValidateAndBuildResult.updated,
optimisticMaps:
viewFieldActionsValidateAndBuildResult.optimisticFlatViewFieldMaps,
};
}
}
@@ -0,0 +1,78 @@
import { Injectable } from '@nestjs/common';
import { type FromTo } from 'twenty-shared/types';
import { FailedFlatViewValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-validation.type';
import { FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type';
import { FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
import { WorkspaceMigrationOrchestratorOptimisticEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { WorkspaceMigrationV2ViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service';
import {
FailedEntityMigrationBuildResult,
WorkspaceEntityMigrationBuilderV2Service,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { WorkspaceMigrationViewActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
export type WorkspaceViewMigrationV2BuilderOptions = {
isSystemBuild: boolean;
inferDeletionFromMissingEntities: boolean;
};
export type FailedWorkspaceViewMigrationBuildResult =
FailedEntityMigrationBuildResult<FailedFlatViewValidation>;
export type WorkspaceViewMigrationBuildArgs = {
workspaceId: string;
buildOptions: WorkspaceViewMigrationV2BuilderOptions;
dependencyOptimisticEntityMaps: WorkspaceMigrationOrchestratorOptimisticEntityMaps;
} & FromTo<FlatViewMaps, 'FlatViewMaps'>;
@Injectable()
export class WorkspaceViewMigrationBuilderV2Service extends WorkspaceEntityMigrationBuilderV2Service<
FlatViewMaps,
FlatView,
FailedFlatViewValidation,
WorkspaceMigrationViewActionV2,
WorkspaceViewMigrationBuildArgs
> {
constructor(
private readonly workspaceMigrationV2ViewActionsBuilderService: WorkspaceMigrationV2ViewActionsBuilderService,
) {
super();
}
protected async validateAndBuildActions(
args: WorkspaceViewMigrationBuildArgs & {
created: FlatView[];
updated: { from: FlatView; to: FlatView }[];
deleted: FlatView[];
},
): Promise<{
failed: FailedFlatViewValidation[];
created: WorkspaceMigrationViewActionV2[];
deleted: WorkspaceMigrationViewActionV2[];
updated: WorkspaceMigrationViewActionV2[];
optimisticMaps: FlatViewMaps;
}> {
const viewActionsValidateAndBuildResult =
await this.workspaceMigrationV2ViewActionsBuilderService.validateAndBuildViewActions(
{
fromFlatViewMaps: args.fromFlatViewMaps,
toFlatViewMaps: args.toFlatViewMaps,
createdFlatView: args.created,
updatedFlatView: args.updated,
deletedFlatView: args.deleted,
buildOptions: args.buildOptions,
dependencyOptimisticEntityMaps: args.dependencyOptimisticEntityMaps,
},
);
return {
failed: viewActionsValidateAndBuildResult.failed,
created: viewActionsValidateAndBuildResult.created,
deleted: viewActionsValidateAndBuildResult.deleted,
updated: viewActionsValidateAndBuildResult.updated,
optimisticMaps: viewActionsValidateAndBuildResult.optimisticFlatViewMaps,
};
}
}
@@ -0,0 +1,11 @@
import { type FailedFlatViewValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-validation.type';
import { type FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
export type ValidateAndBuildViewResult<T extends WorkspaceMigrationActionV2> = {
failed: FailedFlatViewValidation[];
created: T[];
deleted: T[];
updated: T[];
optimisticFlatViewMaps: FlatViewMaps;
};
@@ -2,12 +2,14 @@ import { type WorkspaceMigrationFieldActionV2 } from 'src/engine/workspace-manag
import { type WorkspaceMigrationIndexActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
import { type WorkspaceMigrationObjectActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
import { type WorkspaceMigrationViewActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
import { type WorkspaceMigrationViewFieldActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
export type WorkspaceMigrationActionV2 =
| WorkspaceMigrationObjectActionV2
| WorkspaceMigrationFieldActionV2
| WorkspaceMigrationIndexActionV2
| WorkspaceMigrationViewActionV2;
| WorkspaceMigrationViewActionV2
| WorkspaceMigrationViewFieldActionV2;
export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type'];
@@ -1,11 +1,37 @@
import { type FlatView } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-view.type';
import { type FromTo } from 'twenty-shared/types';
import { type ViewEntity } from 'src/engine/core-modules/view/entities/view.entity';
import { type FlatViewPropertiesToCompare } from 'src/engine/core-modules/view/flat-view/types/flat-view-properties-to-compare.type';
import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
export type FlatViewPropertyUpdate<P extends FlatViewPropertiesToCompare> = {
property: P;
} & FromTo<ViewEntity[P]>;
export type CreateViewAction = {
type: 'create_view';
view: FlatView;
};
export type WorkspaceMigrationViewActionV2 = CreateViewAction;
export type UpdateViewAction = {
type: 'update_view';
viewId: string;
updates: Array<
{
[P in FlatViewPropertiesToCompare]: FlatViewPropertyUpdate<P>;
}[FlatViewPropertiesToCompare]
>;
};
export type DeleteViewAction = {
type: 'delete_view';
viewId: string;
};
export type WorkspaceMigrationViewActionV2 =
| CreateViewAction
| UpdateViewAction
| DeleteViewAction;
export type WorkspaceMigrationViewActionTypeV2 =
WorkspaceMigrationViewActionV2['type'];
@@ -0,0 +1,39 @@
import { type FromTo } from 'twenty-shared/types';
import { type ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
import { type FlatViewFieldPropertiesToCompare } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-properties-to-compare.type';
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
export type FlatViewFieldPropertyUpdate<
P extends FlatViewFieldPropertiesToCompare,
> = {
property: P;
} & FromTo<ViewFieldEntity[P]>;
export type CreateViewFieldAction = {
type: 'create_view_field';
viewField: FlatViewField;
};
export type UpdateViewFieldAction = {
type: 'update_view_field';
viewFieldId: string;
updates: Array<
{
[P in FlatViewFieldPropertiesToCompare]: FlatViewFieldPropertyUpdate<P>;
}[FlatViewFieldPropertiesToCompare]
>;
};
export type DeleteViewFieldAction = {
type: 'delete_view_field';
viewFieldId: string;
};
export type WorkspaceMigrationViewFieldActionV2 =
| CreateViewFieldAction
| UpdateViewFieldAction
| DeleteViewFieldAction;
export type WorkspaceMigrationViewFieldActionTypeV2 =
WorkspaceMigrationViewFieldActionV2['type'];
@@ -0,0 +1,21 @@
import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
import {
type CreateViewAction,
type DeleteViewAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
export const getWorkspaceMigrationV2ViewCreateAction = ({
flatView,
}: {
flatView: FlatView;
}): CreateViewAction => ({
type: 'create_view',
view: flatView,
});
export const getWorkspaceMigrationV2ViewDeleteAction = (
flatView: FlatView,
): DeleteViewAction => ({
type: 'delete_view',
viewId: flatView.id,
});
@@ -0,0 +1,21 @@
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
import {
type CreateViewFieldAction,
type DeleteViewFieldAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
export const getWorkspaceMigrationV2ViewFieldCreateAction = ({
flatViewField,
}: {
flatViewField: FlatViewField;
}): CreateViewFieldAction => ({
type: 'create_view_field',
viewField: flatViewField,
});
export const getWorkspaceMigrationV2ViewFieldDeleteAction = (
flatViewField: FlatViewField,
): DeleteViewFieldAction => ({
type: 'delete_view_field',
viewFieldId: flatViewField.id,
});
@@ -0,0 +1,151 @@
import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { ViewExceptionCode } from 'src/engine/core-modules/view/exceptions/view.exception';
import { FailedFlatViewFieldValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-field-validation.type';
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 { FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.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';
@Injectable()
export class FlatViewFieldValidatorService {
constructor() {}
public validateFlatViewFieldUpdate({
existingFlatViewFieldMaps,
updatedFlatViewField,
optimisticFlatViewMaps,
}: {
existingFlatViewFieldMaps: FlatViewFieldMaps;
updatedFlatViewField: FlatViewField;
optimisticFlatViewMaps: FlatViewMaps;
}): FailedFlatViewFieldValidation {
const errors = [];
const optimisticFlatView =
optimisticFlatViewMaps.byId[updatedFlatViewField.viewId];
if (!isDefined(optimisticFlatView)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View not found`,
userFriendlyMessage: t`View not found`,
});
}
const existingFlatViewField =
existingFlatViewFieldMaps.byId[updatedFlatViewField.id];
if (!isDefined(existingFlatViewField)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View field not found`,
userFriendlyMessage: t`View field not found`,
});
}
return {
type: 'update_view_field',
viewFieldLevelErrors: errors,
failedViewFieldValidationMinimalInformation: {
id: updatedFlatViewField.id,
viewId: updatedFlatViewField.viewId,
fieldMetadataId: updatedFlatViewField.fieldMetadataId,
},
};
}
public validateFlatViewFieldDeletion({
existingFlatViewFieldMaps,
viewFieldIdToDelete,
optimisticFlatViewMaps,
}: {
existingFlatViewFieldMaps: FlatViewFieldMaps;
viewFieldIdToDelete: string;
optimisticFlatViewMaps: FlatViewMaps;
}): FailedFlatViewFieldValidation {
const errors = [];
const optimisticFlatView = optimisticFlatViewMaps.byId[viewFieldIdToDelete];
if (!isDefined(optimisticFlatView)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View not found`,
userFriendlyMessage: t`View not found`,
});
}
const existingFlatViewField =
existingFlatViewFieldMaps.byId[viewFieldIdToDelete];
if (!isDefined(existingFlatViewField)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View field not found`,
userFriendlyMessage: t`View field not found`,
});
}
return {
type: 'delete_view_field',
viewFieldLevelErrors: errors,
failedViewFieldValidationMinimalInformation: {
id: viewFieldIdToDelete,
},
};
}
public async validateFlatViewFieldCreation({
_existingFlatViewFieldMaps,
flatViewFieldToValidate,
optimisticFlatObjectMetadataMaps,
optimisticFlatViewMaps,
}: {
_existingFlatViewFieldMaps: FlatViewFieldMaps;
flatViewFieldToValidate: FlatViewField;
optimisticFlatObjectMetadataMaps: FlatObjectMetadataMaps;
optimisticFlatViewMaps: FlatViewMaps;
}): Promise<FailedFlatViewFieldValidation> {
const errors = [];
const relatedFlatFieldMetadata =
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
fieldMetadataId: flatViewFieldToValidate.fieldMetadataId,
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
});
if (!isDefined(relatedFlatFieldMetadata)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`Field metadata not found`,
userFriendlyMessage: t`Field metadata not found`,
});
}
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`,
});
}
return {
type: 'create_view_field',
viewFieldLevelErrors: errors,
failedViewFieldValidationMinimalInformation: {
id: flatViewFieldToValidate.id,
viewId: flatViewFieldToValidate.viewId,
fieldMetadataId: flatViewFieldToValidate.fieldMetadataId,
},
};
}
}
@@ -0,0 +1,104 @@
import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { ViewExceptionCode } from 'src/engine/core-modules/view/exceptions/view.exception';
import { FailedFlatViewValidation } from 'src/engine/core-modules/view/flat-view/types/failed-flat-view-validation.type';
import { FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type';
import { FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
@Injectable()
export class FlatViewValidatorService {
constructor() {}
public validateFlatViewUpdate({
existingFlatViewMaps,
updatedFlatView,
}: {
existingFlatViewMaps: FlatViewMaps;
updatedFlatView: FlatView;
}): FailedFlatViewValidation {
const errors = [];
const existingFlatView = existingFlatViewMaps.byId[updatedFlatView.id];
if (!isDefined(existingFlatView)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View not found`,
userFriendlyMessage: t`View not found`,
});
}
return {
type: 'update_view',
viewLevelErrors: errors,
failedViewValidationMinimalInformation: {
id: updatedFlatView.id,
},
};
}
public validateFlatViewDeletion({
existingFlatViewMaps,
viewIdToDelete,
}: {
existingFlatViewMaps: FlatViewMaps;
viewIdToDelete: string;
}): FailedFlatViewValidation {
const errors = [];
const existingFlatView = existingFlatViewMaps.byId[viewIdToDelete];
if (!isDefined(existingFlatView)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`View not found`,
userFriendlyMessage: t`View not found`,
});
}
return {
type: 'delete_view',
viewLevelErrors: errors,
failedViewValidationMinimalInformation: {
id: viewIdToDelete,
},
};
}
public async validateFlatViewCreation({
_existingFlatViewMaps,
flatViewToValidate,
optimisticFlatObjectMetadataMaps,
}: {
_existingFlatViewMaps: FlatViewMaps;
flatViewToValidate: FlatView;
optimisticFlatObjectMetadataMaps: FlatObjectMetadataMaps;
}): Promise<FailedFlatViewValidation> {
const optimisticFlatObjectMetadata =
optimisticFlatObjectMetadataMaps.byId[
flatViewToValidate.objectMetadataId
];
const errors = [];
if (!isDefined(optimisticFlatObjectMetadata)) {
errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`Object metadata not found`,
userFriendlyMessage: t`Object metadata not found`,
});
}
return {
type: 'create_view',
viewLevelErrors: errors,
failedViewValidationMinimalInformation: {
id: flatViewToValidate.id,
},
};
}
}
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
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({
providers: [FlatViewValidatorService, FlatViewFieldValidatorService],
exports: [FlatViewValidatorService, FlatViewFieldValidatorService],
})
export class WorkspaceMigrationBuilderValidatorsModule {}
@@ -6,10 +6,15 @@ import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/f
import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.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 { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-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 { WorkspaceViewFieldMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-view-field-migration-builder-v2.service';
import { WorkspaceViewMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-view-migration-builder-v2.service';
import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/workspace-migration-builder-validators.module';
@Module({
imports: [FeatureFlagModule],
imports: [FeatureFlagModule, WorkspaceMigrationBuilderValidatorsModule],
providers: [
WorkspaceMigrationBuilderV2Service,
FlatFieldMetadataValidatorService,
@@ -17,7 +22,15 @@ import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager
WorkspaceMigrationV2ObjectActionsBuilderService,
FlatObjectMetadataValidatorService,
WorkspaceMigrationV2FieldActionsBuilderService,
WorkspaceMigrationV2ViewActionsBuilderService,
WorkspaceViewMigrationBuilderV2Service,
WorkspaceViewFieldMigrationBuilderV2Service,
WorkspaceMigrationV2ViewFieldActionsBuilderService,
],
exports: [
WorkspaceMigrationBuilderV2Service,
WorkspaceViewMigrationBuilderV2Service,
WorkspaceViewFieldMigrationBuilderV2Service,
],
exports: [WorkspaceMigrationBuilderV2Service],
})
export class WorkspaceMigrationBuilderV2Module {}
@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
import { CreateViewFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_view_field',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateViewFieldAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { viewField } = action;
const viewFieldRepository =
queryRunner.manager.getRepository<ViewFieldEntity>(ViewFieldEntity);
await viewFieldRepository.save({
...viewField,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<CreateViewFieldAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
import { DeleteViewFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteViewFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_view_field',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteViewFieldAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { viewFieldId } = action;
const viewFieldRepository =
queryRunner.manager.getRepository<ViewFieldEntity>(ViewFieldEntity);
await viewFieldRepository.delete({
id: viewFieldId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<DeleteViewFieldAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,38 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity';
import { UpdateViewFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
@Injectable()
export class UpdateViewFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update_view_field',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<UpdateViewFieldAction>,
): Promise<void> {
const { action, queryRunner } = context;
const { viewFieldId } = action;
const viewFieldRepository =
queryRunner.manager.getRepository<ViewEntity>(ViewEntity);
await viewFieldRepository.update(
viewFieldId,
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<UpdateViewFieldAction>,
): Promise<void> {
return;
}
}
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity';
import { CreateViewAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@@ -21,25 +20,13 @@ export class CreateViewActionHandlerService extends WorkspaceMigrationRunnerActi
const { action, queryRunner, workspaceId } = context;
const { view } = action;
const { viewFields } = view;
const viewRepository =
queryRunner.manager.getRepository<ViewEntity>(ViewEntity);
const viewFieldRepository =
queryRunner.manager.getRepository<ViewFieldEntity>(ViewFieldEntity);
const createdView = await viewRepository.save({
await viewRepository.save({
...view,
workspaceId,
});
await viewFieldRepository.save(
viewFields.map((viewField) => ({
...viewField,
viewId: createdView.id,
workspaceId,
})),
);
}
async executeForWorkspaceSchema(
@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity';
import { DeleteViewAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteViewActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_view',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteViewAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { viewId } = action;
const viewRepository =
queryRunner.manager.getRepository<ViewEntity>(ViewEntity);
await viewRepository.delete({
id: viewId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<DeleteViewAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,38 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity';
import { UpdateViewAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
@Injectable()
export class UpdateViewActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update_view',
) {
constructor() {
super();
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<UpdateViewAction>,
): Promise<void> {
const { action, queryRunner } = context;
const { viewId } = action;
const viewRepository =
queryRunner.manager.getRepository<ViewEntity>(ViewEntity);
await viewRepository.update(
viewId,
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<UpdateViewAction>,
): Promise<void> {
return;
}
}
@@ -9,7 +9,12 @@ import { DeleteIndexActionHandlerService } from 'src/engine/workspace-manager/wo
import { CreateObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service';
import { DeleteObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service';
import { UpdateObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service';
import { CreateViewFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/create-view-field-action-handler.service';
import { DeleteViewFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/delete-view-field-action-handler.service';
import { UpdateViewFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/update-view-field-action-handler.service';
import { CreateViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/create-view-action-handler.service';
import { DeleteViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/delete-view-action-handler.service';
import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/update-view-action-handler.service';
@Module({
imports: [WorkspaceSchemaManagerModule],
@@ -26,6 +31,12 @@ import { CreateViewActionHandlerService } from 'src/engine/workspace-manager/wor
DeleteIndexActionHandlerService,
CreateViewActionHandlerService,
UpdateViewActionHandlerService,
DeleteViewActionHandlerService,
CreateViewFieldActionHandlerService,
UpdateViewFieldActionHandlerService,
DeleteViewFieldActionHandlerService,
],
})
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
@@ -135,6 +135,11 @@ export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = <
case 'create_index':
case 'delete_index':
case 'create_view':
case 'update_view':
case 'delete_view':
case 'create_view_field':
case 'update_view_field':
case 'delete_view_field':
return flatObjectMetadataMaps;
default: {
assertUnreachable(
@@ -1,8 +1,14 @@
import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { type UpdateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
import { type UpdateViewAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
import { type UpdateViewFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type';
export const fromWorkspaceMigrationUpdateActionToPartialEntity = <
T extends UpdateFieldAction | UpdateObjectAction,
T extends
| UpdateFieldAction
| UpdateObjectAction
| UpdateViewAction
| UpdateViewFieldAction,
>(
action: T,
) => {
@@ -1,5 +1,8 @@
import { Module } from '@nestjs/common';
import { ViewCacheModule } from 'src/engine/core-modules/view/cache/services/view-cache.module';
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
import { WorkspaceMigrationBuildOrchestratorService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module';
import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module';
@@ -8,8 +11,16 @@ import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/w
imports: [
WorkspaceMigrationBuilderV2Module,
WorkspaceMigrationRunnerV2Module,
WorkspaceMetadataCacheModule,
ViewCacheModule,
],
providers: [
WorkspaceMigrationValidateBuildAndRunService,
WorkspaceMigrationBuildOrchestratorService,
],
exports: [
WorkspaceMigrationValidateBuildAndRunService,
WorkspaceMigrationBuildOrchestratorService,
],
providers: [WorkspaceMigrationValidateBuildAndRunService],
exports: [WorkspaceMigrationValidateBuildAndRunService],
})
export class WorkspaceMigrationV2Module {}