From e722303a2fb6a677b5c8ae9a5d3fd24cace27d3c Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:24:24 +0200 Subject: [PATCH] Migration builder v2 handle `RELATION` through `fields` actions (#13076) # Introduction In this PR we've mainly refactor the typing to be extending existing entities. Also handling relations through the field abstraction layer rather than a dedicated one. We reverted midway We then still need to: - Handle indexing - Uniqueness - Add strong coverage and avoid static inline snapshoting as right now + building a coherent testing set - Deprecate the `standardId` in favor of a `uniqueIdentifier` on each `objectMetadata` and `fieldMetadata` - Rename types `input` to `flattened` - Handle custom or non custom edit edge cases. ( e.g cannot delete a standard field or object ) ## Notes Right I preferred including too many information ( whole object and field input ) in the action context, we might wanna evict redundant information in the future when implementing the runners --- .../types/from-to.type.ts | 4 + .../workspace-migration-action-common-v2.ts | 12 + .../types/workspace-migration-action-v2.ts | 106 ----- .../workspace-migration-field-action-v2.ts | 35 ++ .../types/workspace-migration-field-input.ts | 35 ++ .../workspace-migration-index-action-v2.ts | 11 + .../workspace-migration-object-action-v2.ts | 33 ++ .../types/workspace-migration-object-input.ts | 42 +- ...orkspace-migration-uniqueness-action-v2.ts | 11 + .../types/workspace-migration-v2.ts | 8 +- .../workspace-migration-v2-builder.spec.ts | 374 ++++++++++++--- ...ace-migration-v2-relations-builder.spec.ts | 424 ++++++++++++++++++ ...leted-created-updated-field-matrix.util.ts | 40 ++ ...-created-updated-matrix-dispatcher.util.ts | 2 +- ...et-workspace-migration-v2-field-actions.ts | 30 +- ...t-workspace-migration-v2-object-actions.ts | 12 +- ...on-field-metadata-input-comparator.util.ts | 101 +++++ ...n-object-metadata-input-comparator.util.ts | 70 +++ .../workspace-migration-builder-v2.service.ts | 39 +- ...pace-migration-v2-field-actions-builder.ts | 218 ++------- ...ace-migration-v2-object-actions-builder.ts | 121 +---- 21 files changed, 1206 insertions(+), 522 deletions(-) create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/from-to.type.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-common-v2.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-action-v2.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-index-action-v2.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-action-v2.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-uniqueness-action-v2.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-v2-relations-builder.spec.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-field-metadata-input-comparator.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-object-metadata-input-comparator.util.ts diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/from-to.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/from-to.type.ts new file mode 100644 index 000000000..a4890702b --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/from-to.type.ts @@ -0,0 +1,4 @@ +export type FromTo = { + from: T; + to: T; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-common-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-common-v2.ts new file mode 100644 index 000000000..ef5ed4e82 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-common-v2.ts @@ -0,0 +1,12 @@ +import { WorkspaceMigrationFieldActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-action-v2'; +import { WorkspaceMigrationIndexActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-index-action-v2'; +import { WorkspaceMigrationV2ObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-action-v2'; +import { WorkspaceMigrationUniquenessActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-uniqueness-action-v2'; + +export type WorkspaceMigrationActionV2 = + | WorkspaceMigrationV2ObjectAction + | WorkspaceMigrationFieldActionV2 + | WorkspaceMigrationUniquenessActionV2 + | WorkspaceMigrationIndexActionV2; + +export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type']; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2.ts deleted file mode 100644 index b8d312fbe..000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; - -type UniqueIdentifierRecord = { - [P in `${TTarget}UniqueIdentifier`]: string; -}; - -type ObjectMetadataUniqueIdentifier = UniqueIdentifierRecord<'objectMetadata'>; - -type FieldMetadataUniqueIdentifier = UniqueIdentifierRecord<'fieldMetadata'>; - -export type FromTo = { - from: T; - to: T; -}; - -type ObjectActionCommon = ObjectMetadataUniqueIdentifier; -export type CreateObjectAction = { - type: 'create_object'; - object: ObjectMetadataEntity; -} & ObjectActionCommon; - -export type UpdateObjectAction = { - type: 'update_object'; - updates: (FromTo> & { property: string })[]; -} & ObjectActionCommon; - -export type DeleteObjectAction = { - type: 'delete_object'; -} & ObjectActionCommon; - -export type WorkspaceMigrationV2ObjectAction = - | CreateObjectAction - | UpdateObjectAction - | DeleteObjectAction; - -type FieldActionCommon = { - field: Partial; -} & ObjectMetadataUniqueIdentifier & - FieldMetadataUniqueIdentifier; -export type CreateFieldAction = { - type: 'create_field'; -} & FieldActionCommon; - -export type UpdateFieldAction = { - type: 'update_field'; -} & FieldActionCommon; - -export type DeleteFieldAction = { - type: 'delete_field'; -} & Omit; - -export type WorkspaceMigrationFieldActionV2 = - | CreateFieldAction - | UpdateFieldAction - | DeleteFieldAction; - -export interface CreateRelationAction { - type: 'create_relation'; -} - -export interface UpdateRelationAction { - type: 'update_relation'; -} - -export interface DeleteRelationAction { - type: 'delete_relation'; -} - -export type WorkspaceMigrationRelationActionV2 = - | CreateRelationAction - | UpdateRelationAction - | DeleteRelationAction; - -export interface CreateIndexAction { - type: 'create_index'; -} - -export interface DeleteIndexAction { - type: 'delete_index'; -} - -export type WorkspaceMigrationIndexActionV2 = - | CreateIndexAction - | DeleteIndexAction; - -export interface AddUniquenessConstraintAction { - type: 'add_uniqueness_constraint'; -} - -export interface RemoveUniquenessConstraintAction { - type: 'remove_uniqueness_constraint'; -} - -export type WorkspaceMigrationUniquenessActionV2 = - | RemoveUniquenessConstraintAction - | AddUniquenessConstraintAction; - -export type WorkspaceMigrationActionV2 = - | WorkspaceMigrationRelationActionV2 - | WorkspaceMigrationV2ObjectAction - | WorkspaceMigrationFieldActionV2 - | WorkspaceMigrationUniquenessActionV2 - | WorkspaceMigrationIndexActionV2; - -export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type']; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-action-v2.ts new file mode 100644 index 000000000..1191b6c28 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-action-v2.ts @@ -0,0 +1,35 @@ +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { + FieldMetadataEntityEditableProperties, + WorkspaceMigrationFieldInput, +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input'; +import { WorkspaceMigrationObjectWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; + +export type FieldAndObjectMetadataWorkspaceMigrationInput = { + fieldMetadataInput: WorkspaceMigrationFieldInput; + objectMetadataInput: WorkspaceMigrationObjectWithoutFields; +}; +export type CreateFieldAction = { + type: 'create_field'; +} & FieldAndObjectMetadataWorkspaceMigrationInput; + +export type UpdateFieldAction = { + type: 'update_field'; + updates: Partial< + { + [P in FieldMetadataEntityEditableProperties]: { + property: P; + } & FromTo; + }[FieldMetadataEntityEditableProperties] + >[]; +} & FieldAndObjectMetadataWorkspaceMigrationInput; + +export type DeleteFieldAction = { + type: 'delete_field'; +} & FieldAndObjectMetadataWorkspaceMigrationInput; + +export type WorkspaceMigrationFieldActionV2 = + | CreateFieldAction + | UpdateFieldAction + | DeleteFieldAction; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input.ts new file mode 100644 index 000000000..6759512da --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input.ts @@ -0,0 +1,35 @@ +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; + +export const fieldMetadataEntityEditableProperties = [ + 'defaultValue', + 'description', + 'icon', + 'isActive', + 'isLabelSyncedWithName', + 'isUnique', // unsure + 'label', + 'name', + 'options', + // TODO update once we reactivate the relation edition + // 'relationTargetFieldMetadata', + // 'relationTargetFieldMetadataId', + // 'relationTargetObjectMetadata', + // 'relationTargetObjectMetadataId', + // 'settings', + /// + 'standardOverrides', +] as const satisfies (keyof FieldMetadataEntity)[]; +export type FieldMetadataEntityEditableProperties = + (typeof fieldMetadataEntityEditableProperties)[number]; + +// TODO could describe required minimum keys +export type WorkspaceMigrationFieldInput = Partial< + Omit +> & { + uniqueIdentifier: string; +}; + +export const fieldMetadataPropertiesToStringify = [ + 'defaultValue', + 'standardOverrides', +] as const satisfies FieldMetadataEntityEditableProperties[]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-index-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-index-action-v2.ts new file mode 100644 index 000000000..9f364e154 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-index-action-v2.ts @@ -0,0 +1,11 @@ +export type CreateIndexAction = { + type: 'create_index'; +}; + +export type DeleteIndexAction = { + type: 'delete_index'; +}; + +export type WorkspaceMigrationIndexActionV2 = + | CreateIndexAction + | DeleteIndexAction; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-action-v2.ts new file mode 100644 index 000000000..9226f6eae --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-action-v2.ts @@ -0,0 +1,33 @@ +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { + ObjectMetadataEntityEditableProperties, + WorkspaceMigrationObjectWithoutFields, +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; + +type ObjectActionCommon = { + objectMetadataInput: WorkspaceMigrationObjectWithoutFields; +}; +export type CreateObjectAction = { + type: 'create_object'; +} & ObjectActionCommon; + +export type UpdateObjectAction = { + type: 'update_object'; + updates: Partial< + { + [P in ObjectMetadataEntityEditableProperties]: { + property: P; + } & FromTo; + }[ObjectMetadataEntityEditableProperties] + >[]; +} & ObjectActionCommon; + +export type DeleteObjectAction = { + type: 'delete_object'; +} & ObjectActionCommon; + +export type WorkspaceMigrationV2ObjectAction = + | CreateObjectAction + | UpdateObjectAction + | DeleteObjectAction; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input.ts index cee695b80..ff408c156 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input.ts @@ -1,22 +1,28 @@ -import { FieldMetadataType } from 'twenty-shared/types'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { WorkspaceMigrationFieldInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input'; -export type WorkspaceMigrationObjectFieldInput = { +export const objectMetadataEntityEditableProperties = [ + 'description', + 'icon', + 'isActive', + 'isLabelSyncedWithName', + 'labelPlural', + 'labelSingular', + 'namePlural', + 'nameSingular', + 'standardOverrides', // Only if standard +] as const satisfies (keyof ObjectMetadataEntity)[]; +export type ObjectMetadataEntityEditableProperties = + (typeof objectMetadataEntityEditableProperties)[number]; + +export type WorkspaceMigrationObjectInput = Partial< + Omit +> & { uniqueIdentifier: string; - name: string; - label: string; - defaultValue: unknown; - type: FieldMetadataType; - description?: string; - // TODO this should extend FieldMetadataEntity + fieldInputs: WorkspaceMigrationFieldInput[]; }; -export type WorkspaceMigrationObjectInput = { - uniqueIdentifier: string; - nameSingular: string; - namePlural: string; - labelSingular: string; - labelPlural: string; - description?: string; - fields: WorkspaceMigrationObjectFieldInput[]; - // TODO this should extend ObjectMetadataEntity -}; +export type WorkspaceMigrationObjectWithoutFields = Omit< + WorkspaceMigrationObjectInput, + 'fieldInputs' +>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-uniqueness-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-uniqueness-action-v2.ts new file mode 100644 index 000000000..2a412bc02 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-uniqueness-action-v2.ts @@ -0,0 +1,11 @@ +export type AddUniquenessConstraintAction = { + type: 'add_uniqueness_constraint'; +}; + +export type RemoveUniquenessConstraintAction = { + type: 'remove_uniqueness_constraint'; +}; + +export type WorkspaceMigrationUniquenessActionV2 = + | RemoveUniquenessConstraintAction + | AddUniquenessConstraintAction; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-v2.ts index f3ba2c365..6a6d3cfd1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-v2.ts @@ -1,12 +1,12 @@ -import { WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2'; +import { WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-common-v2'; -export interface WorkspaceMigrationV2< +export type WorkspaceMigrationV2< TActions extends WorkspaceMigrationActionV2 = WorkspaceMigrationActionV2, -> { +> = { // formatVersion: 1; // createdAt: string; // name: string; // description?: string; actions: TActions[]; // objectActions: TActions[] // could be cool ? -} +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-v2-builder.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-v2-builder.spec.ts index 843bbdfee..b61b433cf 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-v2-builder.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-v2-builder.spec.ts @@ -13,7 +13,7 @@ describe('WorkspaceMigrationBuilderV2Service', () => { labelSingular: 'Contact', labelPlural: 'Contacts', description: 'A contact', - fields: [ + fieldInputs: [ { uniqueIdentifier: '20202020-e89b-12d3-a456-426614174000', name: 'firstName', @@ -41,7 +41,24 @@ describe('WorkspaceMigrationBuilderV2Service', () => { { "actions": [ { - "objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175000", + "objectMetadataInput": { + "description": "A contact", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "First Name", + "name": "firstName", + "type": "FULL_NAME", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614174000", + }, + ], + "labelPlural": "Contacts", + "labelSingular": "Contact", + "namePlural": "Contacts", + "nameSingular": "Person", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614175000", + }, "type": "update_object", "updates": [ { @@ -64,7 +81,7 @@ describe('WorkspaceMigrationBuilderV2Service', () => { labelSingular: 'Company', labelPlural: 'Companies', description: 'A company', - fields: [ + fieldInputs: [ { uniqueIdentifier: '20202020-e89b-12d3-a456-426614174001', name: 'name', @@ -82,9 +99,9 @@ describe('WorkspaceMigrationBuilderV2Service', () => { { "actions": [ { - "object": { + "objectMetadataInput": { "description": "A company", - "fields": [ + "fieldInputs": [ { "defaultValue": "", "description": "", @@ -100,11 +117,10 @@ describe('WorkspaceMigrationBuilderV2Service', () => { "nameSingular": "Company", "uniqueIdentifier": "20202020-e89b-12d3-a456-426614175001", }, - "objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175001", "type": "create_object", }, { - "field": { + "fieldMetadataInput": { "defaultValue": "", "description": "", "label": "Name", @@ -112,8 +128,24 @@ describe('WorkspaceMigrationBuilderV2Service', () => { "type": "ADDRESS", "uniqueIdentifier": "20202020-e89b-12d3-a456-426614174001", }, - "fieldMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614174001", - "objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175001", + "objectMetadataInput": { + "description": "A company", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "Name", + "name": "name", + "type": "ADDRESS", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614174001", + }, + ], + "labelPlural": "Companies", + "labelSingular": "Company", + "namePlural": "Companies", + "nameSingular": "Company", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614175001", + }, "type": "create_field", }, ], @@ -128,7 +160,24 @@ describe('WorkspaceMigrationBuilderV2Service', () => { { "actions": [ { - "objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175000", + "objectMetadataInput": { + "description": "A contact", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "First Name", + "name": "firstName", + "type": "FULL_NAME", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614174000", + }, + ], + "labelPlural": "Contacts", + "labelSingular": "Contact", + "namePlural": "Contacts", + "nameSingular": "Contact", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614175000", + }, "type": "delete_object", }, ], @@ -140,8 +189,8 @@ describe('WorkspaceMigrationBuilderV2Service', () => { const objectToUpdate: WorkspaceMigrationObjectInput = { ...baseObject, nameSingular: 'Person', - fields: [ - ...baseObject.fields, + fieldInputs: [ + ...baseObject.fieldInputs, { defaultValue: '', label: 'New field', @@ -163,7 +212,7 @@ describe('WorkspaceMigrationBuilderV2Service', () => { labelSingular: 'Company', labelPlural: 'Companies', description: 'A company', - fields: [ + fieldInputs: [ { uniqueIdentifier: '20202020-1016-4f09-bad6-e75681f385f4', name: 'name', @@ -184,9 +233,9 @@ describe('WorkspaceMigrationBuilderV2Service', () => { { "actions": [ { - "object": { + "objectMetadataInput": { "description": "A company", - "fields": [ + "fieldInputs": [ { "defaultValue": "", "description": "", @@ -202,28 +251,56 @@ describe('WorkspaceMigrationBuilderV2Service', () => { "nameSingular": "Company", "uniqueIdentifier": "20202020-1218-4fc0-b32d-fc4f005c4bab", }, - "objectMetadataUniqueIdentifier": "20202020-1218-4fc0-b32d-fc4f005c4bab", "type": "create_object", }, { - "field": { - "defaultValue": "", - "description": "", - "label": "Name", - "name": "name", - "type": "ADDRESS", - "uniqueIdentifier": "20202020-1016-4f09-bad6-e75681f385f4", + "objectMetadataInput": { + "description": "A contact", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "First Name", + "name": "firstName", + "type": "FULL_NAME", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614174000", + }, + ], + "labelPlural": "Contacts", + "labelSingular": "Contact", + "namePlural": "Contacts", + "nameSingular": "Contact", + "uniqueIdentifier": "20202020-59ef-4a14-a509-0a02acb248d5", }, - "fieldMetadataUniqueIdentifier": "20202020-1016-4f09-bad6-e75681f385f4", - "objectMetadataUniqueIdentifier": "20202020-1218-4fc0-b32d-fc4f005c4bab", - "type": "create_field", - }, - { - "objectMetadataUniqueIdentifier": "20202020-59ef-4a14-a509-0a02acb248d5", "type": "delete_object", }, { - "objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175000", + "objectMetadataInput": { + "description": "A contact", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "First Name", + "name": "firstName", + "type": "FULL_NAME", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614174000", + }, + { + "defaultValue": "", + "description": "new field description", + "label": "New field", + "name": "newField", + "type": "NUMBER", + "uniqueIdentifier": "20202020-3ad3-4fec-9c46-8dc9158980e3", + }, + ], + "labelPlural": "Contacts", + "labelSingular": "Contact", + "namePlural": "Contacts", + "nameSingular": "Person", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614175000", + }, "type": "update_object", "updates": [ { @@ -234,7 +311,36 @@ describe('WorkspaceMigrationBuilderV2Service', () => { ], }, { - "field": { + "fieldMetadataInput": { + "defaultValue": "", + "description": "", + "label": "Name", + "name": "name", + "type": "ADDRESS", + "uniqueIdentifier": "20202020-1016-4f09-bad6-e75681f385f4", + }, + "objectMetadataInput": { + "description": "A company", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "Name", + "name": "name", + "type": "ADDRESS", + "uniqueIdentifier": "20202020-1016-4f09-bad6-e75681f385f4", + }, + ], + "labelPlural": "Companies", + "labelSingular": "Company", + "namePlural": "Companies", + "nameSingular": "Company", + "uniqueIdentifier": "20202020-1218-4fc0-b32d-fc4f005c4bab", + }, + "type": "create_field", + }, + { + "fieldMetadataInput": { "defaultValue": "", "description": "new field description", "label": "New field", @@ -242,8 +348,32 @@ describe('WorkspaceMigrationBuilderV2Service', () => { "type": "NUMBER", "uniqueIdentifier": "20202020-3ad3-4fec-9c46-8dc9158980e3", }, - "fieldMetadataUniqueIdentifier": "20202020-3ad3-4fec-9c46-8dc9158980e3", - "objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175000", + "objectMetadataInput": { + "description": "A contact", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "First Name", + "name": "firstName", + "type": "FULL_NAME", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614174000", + }, + { + "defaultValue": "", + "description": "new field description", + "label": "New field", + "name": "newField", + "type": "NUMBER", + "uniqueIdentifier": "20202020-3ad3-4fec-9c46-8dc9158980e3", + }, + ], + "labelPlural": "Contacts", + "labelSingular": "Contact", + "namePlural": "Contacts", + "nameSingular": "Person", + "uniqueIdentifier": "20202020-e89b-12d3-a456-426614175000", + }, "type": "create_field", }, ], @@ -259,7 +389,7 @@ describe('WorkspaceMigrationBuilderV2Service', () => { labelSingular: 'Duplicate', labelPlural: 'Duplicates', description: 'First object', - fields: [ + fieldInputs: [ { uniqueIdentifier: 'field-1', name: 'fieldA', @@ -277,7 +407,7 @@ describe('WorkspaceMigrationBuilderV2Service', () => { labelSingular: 'Duplicate', labelPlural: 'Duplicates', description: 'Second object', - fields: [ + fieldInputs: [ { uniqueIdentifier: 'field-2', name: 'fieldB', @@ -291,33 +421,159 @@ describe('WorkspaceMigrationBuilderV2Service', () => { const result = service.build({ from: [], to: [objectA, objectB] }); - expect(result.actions).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - type: 'create_object', - objectMetadataUniqueIdentifier: 'id-1', - }), - expect.objectContaining({ - type: 'create_object', - objectMetadataUniqueIdentifier: 'id-2', - }), - ]), - ); + expect(result.actions).toMatchInlineSnapshot(` +[ + { + "objectMetadataInput": { + "description": "First object", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "Field A", + "name": "fieldA", + "type": "FULL_NAME", + "uniqueIdentifier": "field-1", + }, + ], + "labelPlural": "Duplicates", + "labelSingular": "Duplicate", + "namePlural": "Duplicates", + "nameSingular": "Duplicate", + "uniqueIdentifier": "id-1", + }, + "type": "create_object", + }, + { + "objectMetadataInput": { + "description": "Second object", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "Field B", + "name": "fieldB", + "type": "ADDRESS", + "uniqueIdentifier": "field-2", + }, + ], + "labelPlural": "Duplicates", + "labelSingular": "Duplicate", + "namePlural": "Duplicates", + "nameSingular": "Duplicate", + "uniqueIdentifier": "id-2", + }, + "type": "create_object", + }, + { + "fieldMetadataInput": { + "defaultValue": "", + "description": "", + "label": "Field A", + "name": "fieldA", + "type": "FULL_NAME", + "uniqueIdentifier": "field-1", + }, + "objectMetadataInput": { + "description": "First object", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "Field A", + "name": "fieldA", + "type": "FULL_NAME", + "uniqueIdentifier": "field-1", + }, + ], + "labelPlural": "Duplicates", + "labelSingular": "Duplicate", + "namePlural": "Duplicates", + "nameSingular": "Duplicate", + "uniqueIdentifier": "id-1", + }, + "type": "create_field", + }, + { + "fieldMetadataInput": { + "defaultValue": "", + "description": "", + "label": "Field B", + "name": "fieldB", + "type": "ADDRESS", + "uniqueIdentifier": "field-2", + }, + "objectMetadataInput": { + "description": "Second object", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "Field B", + "name": "fieldB", + "type": "ADDRESS", + "uniqueIdentifier": "field-2", + }, + ], + "labelPlural": "Duplicates", + "labelSingular": "Duplicate", + "namePlural": "Duplicates", + "nameSingular": "Duplicate", + "uniqueIdentifier": "id-2", + }, + "type": "create_field", + }, +] +`); const deleteResult = service.build({ from: [objectA, objectB], to: [] }); - expect(deleteResult.actions).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - type: 'delete_object', - objectMetadataUniqueIdentifier: 'id-1', - }), - expect.objectContaining({ - type: 'delete_object', - objectMetadataUniqueIdentifier: 'id-2', - }), - ]), - ); + expect(deleteResult.actions).toMatchInlineSnapshot(` +[ + { + "objectMetadataInput": { + "description": "First object", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "Field A", + "name": "fieldA", + "type": "FULL_NAME", + "uniqueIdentifier": "field-1", + }, + ], + "labelPlural": "Duplicates", + "labelSingular": "Duplicate", + "namePlural": "Duplicates", + "nameSingular": "Duplicate", + "uniqueIdentifier": "id-1", + }, + "type": "delete_object", + }, + { + "objectMetadataInput": { + "description": "Second object", + "fieldInputs": [ + { + "defaultValue": "", + "description": "", + "label": "Field B", + "name": "fieldB", + "type": "ADDRESS", + "uniqueIdentifier": "field-2", + }, + ], + "labelPlural": "Duplicates", + "labelSingular": "Duplicate", + "namePlural": "Duplicates", + "nameSingular": "Duplicate", + "uniqueIdentifier": "id-2", + }, + "type": "delete_object", + }, +] +`); }); it('should emit no actions when from and to are deeply equal', () => { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-v2-relations-builder.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-v2-relations-builder.spec.ts new file mode 100644 index 000000000..162768b94 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-v2-relations-builder.spec.ts @@ -0,0 +1,424 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface'; +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { WorkspaceMigrationFieldInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input'; +import { WorkspaceMigrationObjectInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; +import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; + +describe('Workspace migration builder relations tests suite', () => { + let service: WorkspaceMigrationBuilderV2Service; + + beforeEach(() => { + service = new WorkspaceMigrationBuilderV2Service(); + }); + + const createMockObject = ( + identifier: string, + fields: Partial[] = [], + ): WorkspaceMigrationObjectInput => ({ + uniqueIdentifier: identifier, + fieldInputs: fields.map((field) => ({ + type: FieldMetadataType.TEXT, + name: 'defaultName', + label: 'Default Label', + isCustom: true, + isActive: true, + isNullable: true, + uniqueIdentifier: 'default-id', + ...field, + })), + }); + + describe('buildWorkspaceMigrationV2RelationActions', () => { + it('should create relation actions for created fields', () => { + const fromObjects: WorkspaceMigrationObjectInput[] = []; + const toObjects: WorkspaceMigrationObjectInput[] = [ + createMockObject('company', [ + { + type: FieldMetadataType.RELATION, + name: 'employees', + label: 'Employees', + uniqueIdentifier: 'employees', + relationTargetFieldMetadataId: 'field-2', + relationTargetObjectMetadataId: 'obj-2', + }, + ]), + ]; + + const result = service.build({ from: fromObjects, to: toObjects }); + + expect(result).toMatchInlineSnapshot(` +{ + "actions": [ + { + "objectMetadataInput": { + "fieldInputs": [ + { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Employees", + "name": "employees", + "relationTargetFieldMetadataId": "field-2", + "relationTargetObjectMetadataId": "obj-2", + "type": "RELATION", + "uniqueIdentifier": "employees", + }, + ], + "uniqueIdentifier": "company", + }, + "type": "create_object", + }, + { + "fieldMetadataInput": { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Employees", + "name": "employees", + "relationTargetFieldMetadataId": "field-2", + "relationTargetObjectMetadataId": "obj-2", + "type": "RELATION", + "uniqueIdentifier": "employees", + }, + "objectMetadataInput": { + "fieldInputs": [ + { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Employees", + "name": "employees", + "relationTargetFieldMetadataId": "field-2", + "relationTargetObjectMetadataId": "obj-2", + "type": "RELATION", + "uniqueIdentifier": "employees", + }, + ], + "uniqueIdentifier": "company", + }, + "type": "create_field", + }, + ], +} +`); + }); + + it('should create delete actions for deleted fields', () => { + const fromObjects: WorkspaceMigrationObjectInput[] = [ + createMockObject('company', [ + { + type: FieldMetadataType.RELATION, + name: 'employees', + label: 'Employees', + uniqueIdentifier: 'employees', + relationTargetFieldMetadataId: 'field-2', + relationTargetObjectMetadataId: 'obj-2', + }, + ]), + ]; + const toObjects: WorkspaceMigrationObjectInput[] = [ + createMockObject('company'), + ]; + + const result = service.build({ from: fromObjects, to: toObjects }); + + expect(result).toMatchInlineSnapshot(` +{ + "actions": [ + { + "fieldMetadataInput": { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Employees", + "name": "employees", + "relationTargetFieldMetadataId": "field-2", + "relationTargetObjectMetadataId": "obj-2", + "type": "RELATION", + "uniqueIdentifier": "employees", + }, + "objectMetadataInput": { + "fieldInputs": [], + "uniqueIdentifier": "company", + }, + "type": "delete_field", + }, + ], +} +`); + }); + + it('should handle multiple relation changes across different objects', () => { + const fromObjects: WorkspaceMigrationObjectInput[] = [ + createMockObject('company', [ + { + type: FieldMetadataType.RELATION, + name: 'oldRelation', + label: 'Old Relation', + uniqueIdentifier: 'old-relation', + relationTargetFieldMetadataId: 'field-1', + relationTargetObjectMetadataId: 'obj-1', + }, + ]), + ]; + const toObjects: WorkspaceMigrationObjectInput[] = [ + createMockObject('company', [ + { + type: FieldMetadataType.RELATION, + name: 'newRelation', + label: 'New Relation', + uniqueIdentifier: 'new-relation', + relationTargetFieldMetadataId: 'field-2', + relationTargetObjectMetadataId: 'obj-2', + }, + ]), + createMockObject('person', [ + { + type: FieldMetadataType.RELATION, + name: 'manager', + label: 'Manager', + uniqueIdentifier: 'manager', + relationTargetFieldMetadataId: 'field-3', + relationTargetObjectMetadataId: 'obj-3', + }, + ]), + ]; + + const result = service.build({ from: fromObjects, to: toObjects }); + + expect(result).toMatchInlineSnapshot(` +{ + "actions": [ + { + "objectMetadataInput": { + "fieldInputs": [ + { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Manager", + "name": "manager", + "relationTargetFieldMetadataId": "field-3", + "relationTargetObjectMetadataId": "obj-3", + "type": "RELATION", + "uniqueIdentifier": "manager", + }, + ], + "uniqueIdentifier": "person", + }, + "type": "create_object", + }, + { + "fieldMetadataInput": { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Manager", + "name": "manager", + "relationTargetFieldMetadataId": "field-3", + "relationTargetObjectMetadataId": "obj-3", + "type": "RELATION", + "uniqueIdentifier": "manager", + }, + "objectMetadataInput": { + "fieldInputs": [ + { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Manager", + "name": "manager", + "relationTargetFieldMetadataId": "field-3", + "relationTargetObjectMetadataId": "obj-3", + "type": "RELATION", + "uniqueIdentifier": "manager", + }, + ], + "uniqueIdentifier": "person", + }, + "type": "create_field", + }, + { + "fieldMetadataInput": { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "New Relation", + "name": "newRelation", + "relationTargetFieldMetadataId": "field-2", + "relationTargetObjectMetadataId": "obj-2", + "type": "RELATION", + "uniqueIdentifier": "new-relation", + }, + "objectMetadataInput": { + "fieldInputs": [ + { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "New Relation", + "name": "newRelation", + "relationTargetFieldMetadataId": "field-2", + "relationTargetObjectMetadataId": "obj-2", + "type": "RELATION", + "uniqueIdentifier": "new-relation", + }, + ], + "uniqueIdentifier": "company", + }, + "type": "create_field", + }, + { + "fieldMetadataInput": { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Old Relation", + "name": "oldRelation", + "relationTargetFieldMetadataId": "field-1", + "relationTargetObjectMetadataId": "obj-1", + "type": "RELATION", + "uniqueIdentifier": "old-relation", + }, + "objectMetadataInput": { + "fieldInputs": [ + { + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "New Relation", + "name": "newRelation", + "relationTargetFieldMetadataId": "field-2", + "relationTargetObjectMetadataId": "obj-2", + "type": "RELATION", + "uniqueIdentifier": "new-relation", + }, + ], + "uniqueIdentifier": "company", + }, + "type": "delete_field", + }, + ], +} +`); + }); + + it('should handle empty objects', () => { + const result = service.build({ from: [], to: [] }); + + expect(result).toMatchInlineSnapshot(` +{ + "actions": [], +} +`); + }); + + it('should handle objects with no relation changes', () => { + const objects = [ + createMockObject('company', [ + { + type: FieldMetadataType.TEXT, + name: 'name', + label: 'Name', + uniqueIdentifier: 'name', + }, + ]), + ]; + + const result = service.build({ from: objects, to: objects }); + + expect(result).toMatchInlineSnapshot(` +{ + "actions": [], +} +`); + }); + + it('should handle relation field updates', () => { + const baseField = { + type: FieldMetadataType.RELATION, + name: 'employees', + label: 'Employees', + uniqueIdentifier: 'employees', + isCustom: true, + isActive: true, + isNullable: true, + description: 'Company employees', + }; + + const fromObjects: WorkspaceMigrationObjectInput[] = [ + createMockObject('company', [ + { + ...baseField, + relationTargetFieldMetadataId: 'field-1', + relationTargetObjectMetadataId: 'obj-1', + settings: { + relationType: RelationType.ONE_TO_MANY, + onDelete: RelationOnDeleteAction.CASCADE, + } as FieldMetadataSettings, + }, + ]), + ]; + + const toObjects: WorkspaceMigrationObjectInput[] = [ + { + ...fromObjects[0], + fieldInputs: [ + { + ...baseField, + name: 'updatedName', + }, + ], + }, + ]; + + const result = service.build({ from: fromObjects, to: toObjects }); + + expect(result).toMatchInlineSnapshot(` +{ + "actions": [ + { + "fieldMetadataInput": { + "description": "Company employees", + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Employees", + "name": "updatedName", + "type": "RELATION", + "uniqueIdentifier": "employees", + }, + "objectMetadataInput": { + "fieldInputs": [ + { + "description": "Company employees", + "isActive": true, + "isCustom": true, + "isNullable": true, + "label": "Employees", + "name": "updatedName", + "type": "RELATION", + "uniqueIdentifier": "employees", + }, + ], + "uniqueIdentifier": "company", + }, + "type": "update_field", + "updates": [ + { + "from": "employees", + "property": "name", + "to": "updatedName", + }, + ], + }, + ], +} +`); + }); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts new file mode 100644 index 000000000..593472a6d --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts @@ -0,0 +1,40 @@ +import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { WorkspaceMigrationFieldInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input'; +import { + WorkspaceMigrationObjectInput, + WorkspaceMigrationObjectWithoutFields, +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; +import { + CustomDeletedCreatedUpdatedMatrix, + deletedCreatedUpdatedMatrixDispatcher, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/deleted-created-updated-matrix-dispatcher.util'; + +export type UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = { + objectMetadataInput: WorkspaceMigrationObjectWithoutFields; +} & CustomDeletedCreatedUpdatedMatrix< + 'fieldMetadata', + WorkspaceMigrationFieldInput +>; + +export const computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = ( + updatedObjectMetadata: FromTo[], +): UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix[] => { + const matrixAccumulator: UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix[] = + []; + + for (const { from, to } of updatedObjectMetadata) { + const fieldMetadataMatrix = deletedCreatedUpdatedMatrixDispatcher({ + from: from.fieldInputs, + to: to.fieldInputs, + }); + + matrixAccumulator.push({ + objectMetadataInput: to, + createdFieldMetadata: fieldMetadataMatrix.created, + deletedFieldMetadata: fieldMetadataMatrix.deleted, + updatedFieldMetadata: fieldMetadataMatrix.updated, + }); + } + + return matrixAccumulator; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/deleted-created-updated-matrix-dispatcher.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/deleted-created-updated-matrix-dispatcher.util.ts index 6f1c9837d..68667909c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/deleted-created-updated-matrix-dispatcher.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/deleted-created-updated-matrix-dispatcher.util.ts @@ -1,4 +1,4 @@ -import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2'; +import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; export type DeletedCreatedUpdatedMatrix = { created: T[]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts index 3607155ae..1bc919f53 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts @@ -1,29 +1,23 @@ -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { CreateFieldAction, DeleteFieldAction, -} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2'; -import { WorkspaceMigrationObjectFieldInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; + FieldAndObjectMetadataWorkspaceMigrationInput, +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-action-v2'; -type FieldInputAndObjectUniqueIdentifier = { - field: WorkspaceMigrationObjectFieldInput; - objectMetadataUniqueIdentifier: string; -}; export const getWorkspaceMigrationV2FieldCreateAction = ({ - field, - objectMetadataUniqueIdentifier, -}: FieldInputAndObjectUniqueIdentifier): CreateFieldAction => ({ + fieldMetadataInput, + objectMetadataInput, +}: FieldAndObjectMetadataWorkspaceMigrationInput): CreateFieldAction => ({ type: 'create_field', - field: field as unknown as FieldMetadataEntity, // TODO prastoin - fieldMetadataUniqueIdentifier: field.uniqueIdentifier, - objectMetadataUniqueIdentifier, + fieldMetadataInput, + objectMetadataInput, }); export const getWorkspaceMigrationV2FieldDeleteAction = ({ - field, - objectMetadataUniqueIdentifier, -}: FieldInputAndObjectUniqueIdentifier): DeleteFieldAction => ({ + fieldMetadataInput, + objectMetadataInput, +}: FieldAndObjectMetadataWorkspaceMigrationInput): DeleteFieldAction => ({ type: 'delete_field', - fieldMetadataUniqueIdentifier: field.uniqueIdentifier, - objectMetadataUniqueIdentifier, + fieldMetadataInput, + objectMetadataInput, }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts index b0be2dc31..dd3668fef 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts @@ -1,21 +1,19 @@ -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { CreateObjectAction, DeleteObjectAction, -} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2'; +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-action-v2'; import { WorkspaceMigrationObjectInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; export const getWorkspaceMigrationV2ObjectCreateAction = ( - input: WorkspaceMigrationObjectInput, + objectMetadataInput: WorkspaceMigrationObjectInput, ): CreateObjectAction => ({ type: 'create_object', - objectMetadataUniqueIdentifier: input.uniqueIdentifier, - object: input as unknown as ObjectMetadataEntity, // TODO prastoin + objectMetadataInput, }); export const getWorkspaceMigrationV2ObjectDeleteAction = ( - input: WorkspaceMigrationObjectInput, + objectMetadataInput: WorkspaceMigrationObjectInput, ): DeleteObjectAction => ({ type: 'delete_object', - objectMetadataUniqueIdentifier: input.uniqueIdentifier, + objectMetadataInput, }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-field-metadata-input-comparator.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-field-metadata-input-comparator.util.ts new file mode 100644 index 000000000..3988fb434 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-field-metadata-input-comparator.util.ts @@ -0,0 +1,101 @@ +import diff from 'microdiff'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-action-v2'; +import { + FieldMetadataEntityEditableProperties, + WorkspaceMigrationFieldInput, + fieldMetadataEntityEditableProperties, + fieldMetadataPropertiesToStringify, +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-input'; +import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; + +const shouldNotOverrideDefaultValue = (type: FieldMetadataType) => { + return [ + FieldMetadataType.BOOLEAN, + FieldMetadataType.SELECT, + FieldMetadataType.MULTI_SELECT, + FieldMetadataType.CURRENCY, + FieldMetadataType.PHONES, + FieldMetadataType.ADDRESS, + ].includes(type); +}; + +const compareTwoWorkspaceMigrationFieldInput = ({ + from, + to, +}: FromTo) => { + const compareFieldMetadataOptions = { + shouldIgnoreProperty: ( + property: string, + fieldMetadata: WorkspaceMigrationFieldInput, + ) => { + if ( + !fieldMetadataEntityEditableProperties.includes( + property as FieldMetadataEntityEditableProperties, + ) + ) { + return true; + } + + if ( + property === 'defaultValue' && + isDefined(fieldMetadata.type) && + shouldNotOverrideDefaultValue(fieldMetadata.type) + ) { + return true; + } + + return false; + }, + propertiesToStringify: fieldMetadataPropertiesToStringify, + }; + const fromCompare = transformMetadataForComparison( + from, + compareFieldMetadataOptions, + ); + const toCompare = transformMetadataForComparison( + to, + compareFieldMetadataOptions, + ); + + const fieldMetadataDifference = diff(fromCompare, toCompare); + + return fieldMetadataDifference; +}; + +type GetWorkspaceMigrationUpdateFieldActionArgs = + FromTo; +export const compareFieldMetadataInputAndGetUpdateFieldActions = ({ + from, + to, +}: GetWorkspaceMigrationUpdateFieldActionArgs) => { + const fieldMetadataDifferences = compareTwoWorkspaceMigrationFieldInput({ + from, + to, + }); + + return fieldMetadataDifferences.flatMap( + (difference) => { + switch (difference.type) { + case 'CHANGE': { + const { oldValue, path, value } = difference; + + return { + from: oldValue, + to: value, + property: path[0] as FieldMetadataEntityEditableProperties, + }; + } + case 'CREATE': + case 'REMOVE': + default: { + // Should never occurs, we should only provide null never undefined and so on + return []; + } + } + }, + ); +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-object-metadata-input-comparator.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-object-metadata-input-comparator.util.ts new file mode 100644 index 000000000..7383768ef --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-object-metadata-input-comparator.util.ts @@ -0,0 +1,70 @@ +import omit from 'lodash.omit'; +import diff from 'microdiff'; +import { assertUnreachable } from 'twenty-shared/utils'; + +import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; +import { UpdateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-action-v2'; +import { + ObjectMetadataEntityEditableProperties, + WorkspaceMigrationObjectInput, + objectMetadataEntityEditableProperties, +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; +import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; + +type ObjectWorkspaceMigrationUpdate = FromTo; + +export const compareTwoWorkspaceMigrationObjectInput = ({ + from, + to, +}: ObjectWorkspaceMigrationUpdate) => { + const fromCompare = transformMetadataForComparison(from, {}); + const toCompare = transformMetadataForComparison(to, {}); + const objectMetadataDifference = diff(fromCompare, omit(toCompare, 'fields')); + + return objectMetadataDifference.flatMap< + UpdateObjectAction['updates'][number] + >((difference) => { + switch (difference.type) { + case 'CHANGE': { + if ( + difference.oldValue === null && + (difference.value === null || difference.value === undefined) + ) { + return []; + } + const property = difference.path[0]; + + // TODO investigate why it would be a number, in case of array I guess ? + if (typeof property === 'number') { + return []; + } + + // Could be handled directly from the diff we do above + if ( + !objectMetadataEntityEditableProperties.includes( + property as ObjectMetadataEntityEditableProperties, + ) + ) { + return []; + } + + return { + property: property as ObjectMetadataEntityEditableProperties, + from: difference.oldValue, + to: difference.value, + }; + } + case 'CREATE': + case 'REMOVE': { + // Should never occurs ? should throw ? + return []; + } + default: { + assertUnreachable( + difference, + `Unexpected difference type: ${difference['type']}`, + ); + } + } + }); +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts index dac47dc1a..b2feef6bd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts @@ -1,28 +1,19 @@ import { Injectable } from '@nestjs/common'; +import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/from-to.type'; import { WorkspaceMigrationObjectInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-v2'; -import { - DeletedCreatedUpdatedMatrix, - deletedCreatedUpdatedMatrixDispatcher, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/deleted-created-updated-matrix-dispatcher.util'; +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 { deletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/deleted-created-updated-matrix-dispatcher.util'; +import { getWorkspaceMigrationV2FieldCreateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions'; import { buildWorkspaceMigrationV2FieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder'; import { buildWorkspaceMigrationV2ObjectActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder'; - -type WorkspaceMigrationBuilderV2ServiceArgs = { - from: WorkspaceMigrationObjectInput[]; - to: WorkspaceMigrationObjectInput[]; -}; - -export type UniqueIdentifierWorkspaceMigrationObjectInputMapDispatcher = - DeletedCreatedUpdatedMatrix; - @Injectable() export class WorkspaceMigrationBuilderV2Service { constructor() {} build( - objectMetadataFromToInputs: WorkspaceMigrationBuilderV2ServiceArgs, + objectMetadataFromToInputs: FromTo, ): WorkspaceMigrationV2 { const { created: createdObjectMetadata, @@ -37,12 +28,30 @@ export class WorkspaceMigrationBuilderV2Service { updatedObjectMetadata, }); + const createdObjectWorkspaceMigrationCreateFieldActions = + createdObjectMetadata.flatMap((objectMetadataInput) => + objectMetadataInput.fieldInputs.map((fieldMetadataInput) => + getWorkspaceMigrationV2FieldCreateAction({ + fieldMetadataInput, + objectMetadataInput, + }), + ), + ); + + const updatedObjectMetadataFieldAndRelationDeletedCreatedUpdatedMatrix = + computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix( + updatedObjectMetadata, + ); + const fieldWorkspaceMigrationActions = - buildWorkspaceMigrationV2FieldActions({ updatedObjectMetadata }); + buildWorkspaceMigrationV2FieldActions( + updatedObjectMetadataFieldAndRelationDeletedCreatedUpdatedMatrix, + ); return { actions: [ ...objectWorkspaceMigrationActions, + ...createdObjectWorkspaceMigrationCreateFieldActions, ...fieldWorkspaceMigrationActions, ], }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts index e107841e6..e6b591c8a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts @@ -1,207 +1,57 @@ -import diff from 'microdiff'; -import { FieldMetadataType } from 'twenty-shared/types'; - import { - FromTo, + UpdateFieldAction, WorkspaceMigrationFieldActionV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2'; -import { WorkspaceMigrationObjectFieldInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-field-action-v2'; +import { 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'; -import { UniqueIdentifierWorkspaceMigrationObjectInputMapDispatcher } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; -import { CreatedDeletedUpdatedObjectMetadataInputMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder'; -import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; - -import { - CustomDeletedCreatedUpdatedMatrix, - deletedCreatedUpdatedMatrixDispatcher, -} from './utils/deleted-created-updated-matrix-dispatcher.util'; - -// Start TODO prastoin refactor and strictly type -const commonFieldPropertiesToIgnore = [ - 'id', - 'createdAt', - 'updatedAt', - 'objectMetadataId', - 'isActive', - 'options', - 'settings', - 'joinColumn', - 'gate', - 'asExpression', - 'generatedType', - 'isLabelSyncedWithName', - // uniqueIdentifier ? -]; - -const shouldNotOverrideDefaultValue = (type: FieldMetadataType) => { - return [ - FieldMetadataType.BOOLEAN, - FieldMetadataType.SELECT, - FieldMetadataType.MULTI_SELECT, - FieldMetadataType.CURRENCY, - FieldMetadataType.PHONES, - FieldMetadataType.ADDRESS, - ].includes(type); -}; - -const fieldPropertiesToStringify = ['defaultValue'] as const; -/// End - -export const compareTwoWorkspaceMigrationFieldInput = ({ - from, - to, -}: FromTo) => { - const compareFieldMetadataOptions = { - shouldIgnoreProperty: ( - property: string, - fieldMetadata: WorkspaceMigrationObjectFieldInput, - ) => { - if ( - property === 'defaultValue' && - shouldNotOverrideDefaultValue(fieldMetadata.type) - ) { - return true; - } - - if (commonFieldPropertiesToIgnore.includes(property)) { - return true; - } - - return false; - }, - propertiesToStringify: fieldPropertiesToStringify, - }; - const fromCompare = transformMetadataForComparison( - from, - compareFieldMetadataOptions, - ); - const toCompare = transformMetadataForComparison( - to, - compareFieldMetadataOptions, - ); - - const fieldMetadataDifference = diff(fromCompare, toCompare); - - return fieldMetadataDifference; -}; - -type BuildWorkspaceMigrationV2FieldActionFromUpdatedFieldMetadataArgs = - FromTo & { - objectMetadataUniqueIdentifier: string; - }; -// Still in wip -const buildWorkspaceMigrationV2FieldActionFromUpdatedFieldMetadata = ({ - objectMetadataUniqueIdentifier, - from, - to, -}: BuildWorkspaceMigrationV2FieldActionFromUpdatedFieldMetadataArgs) => { - const fieldMetadataDifferences = compareTwoWorkspaceMigrationFieldInput({ - from, - to, - }); - - return fieldMetadataDifferences.flatMap( - (difference) => { - switch (difference.type) { - case 'CREATE': { - return { - type: 'create_field', - field: difference.value, - fieldMetadataUniqueIdentifier: 'TODO', - objectMetadataUniqueIdentifier, - }; - } - case 'CHANGE': { - // TODO prastoin - return []; - } - case 'REMOVE': { - return { - type: 'delete_field', - fieldMetadataUniqueIdentifier: difference.oldValue.uniqueIdentifier, - objectMetadataUniqueIdentifier, - }; - } - default: { - return []; - } - } - }, - ); -}; - -type DeletedCreatedUpdatedFieldInputMatrix = { - objectMetadataUniqueIdentifier: string; -} & CustomDeletedCreatedUpdatedMatrix< - 'fieldMetadata', - WorkspaceMigrationObjectFieldInput ->; - -const updatedFieldMetadataMatriceMapDispatcher = ( - updatedObjectMetadata: UniqueIdentifierWorkspaceMigrationObjectInputMapDispatcher['updated'], -): DeletedCreatedUpdatedFieldInputMatrix[] => { - const matriceAccumulator: DeletedCreatedUpdatedFieldInputMatrix[] = []; - - for (const { from, to } of updatedObjectMetadata) { - const matrixResult = deletedCreatedUpdatedMatrixDispatcher({ - from: from.fields, - to: to.fields, - }); - - matriceAccumulator.push({ - objectMetadataUniqueIdentifier: from.uniqueIdentifier, - createdFieldMetadata: matrixResult.created, - deletedFieldMetadata: matrixResult.deleted, - updatedFieldMetadata: matrixResult.updated, - }); - } - - return matriceAccumulator; -}; - -type BuildWorkspaceMigrationV2FieldActionsArgs = Pick< - CreatedDeletedUpdatedObjectMetadataInputMatrix, - 'updatedObjectMetadata' ->; -export const buildWorkspaceMigrationV2FieldActions = ({ - updatedObjectMetadata, -}: BuildWorkspaceMigrationV2FieldActionsArgs): WorkspaceMigrationFieldActionV2[] => { - const objectMetadataDeletedCreatedUpdatedFields = - updatedFieldMetadataMatriceMapDispatcher(updatedObjectMetadata); +import { compareFieldMetadataInputAndGetUpdateFieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-field-metadata-input-comparator.util'; +export const buildWorkspaceMigrationV2FieldActions = ( + objectMetadataDeletedCreatedUpdatedFields: UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix[], +): WorkspaceMigrationFieldActionV2[] => { let allUpdatedObjectMetadataFieldActions: WorkspaceMigrationFieldActionV2[] = []; for (const { createdFieldMetadata, deletedFieldMetadata, - objectMetadataUniqueIdentifier, updatedFieldMetadata, + objectMetadataInput, } of objectMetadataDeletedCreatedUpdatedFields) { - const updateFieldAction = - updatedFieldMetadata.flatMap( - ({ from, to }) => - buildWorkspaceMigrationV2FieldActionFromUpdatedFieldMetadata({ - from, - to, - objectMetadataUniqueIdentifier: objectMetadataUniqueIdentifier, - }), - ); + const updateFieldActions = updatedFieldMetadata.flatMap( + ({ from, to }) => { + const updates = compareFieldMetadataInputAndGetUpdateFieldActions({ + from, + to, + }); - const createFieldAction = createdFieldMetadata.map((field) => + if (updates.length === 0) { + return []; + } + + return { + type: 'update_field', + fieldMetadataInput: to, + objectMetadataInput, + updates, + }; + }, + ); + + const createFieldAction = createdFieldMetadata.map((fieldMetadataInput) => getWorkspaceMigrationV2FieldCreateAction({ - field, - objectMetadataUniqueIdentifier, + fieldMetadataInput, + objectMetadataInput, }), ); - const deleteFieldAction = deletedFieldMetadata.map((field) => + const deleteFieldAction = deletedFieldMetadata.map((fieldMetadataInput) => getWorkspaceMigrationV2FieldDeleteAction({ - field, - objectMetadataUniqueIdentifier, + fieldMetadataInput, + objectMetadataInput, }), ); @@ -209,7 +59,7 @@ export const buildWorkspaceMigrationV2FieldActions = ({ allUpdatedObjectMetadataFieldActions.concat([ ...createFieldAction, ...deleteFieldAction, - ...updateFieldAction, + ...updateFieldActions, ]); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts index 5fdbdbeb4..f83990d8a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts @@ -1,101 +1,14 @@ -import omit from 'lodash.omit'; -import diff from 'microdiff'; -import { assertUnreachable } from 'twenty-shared/utils'; - -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { - FromTo, UpdateObjectAction, - WorkspaceMigrationActionV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2'; + WorkspaceMigrationV2ObjectAction, +} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-action-v2'; import { WorkspaceMigrationObjectInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input'; import { CustomDeletedCreatedUpdatedMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/deleted-created-updated-matrix-dispatcher.util'; -import { getWorkspaceMigrationV2FieldCreateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions'; import { getWorkspaceMigrationV2ObjectCreateAction, getWorkspaceMigrationV2ObjectDeleteAction, } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions'; -import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; - -// Start TODO prastoin refactor and strictly type -const objectPropertiesToIgnore = [ - 'id', - 'createdAt', - 'updatedAt', - 'labelIdentifierFieldMetadataId', - 'imageIdentifierFieldMetadataId', - 'isActive', - 'fields', -]; - -// Not the same for standard and custom -const allowedObjectProps: (keyof Partial)[] = [ - 'nameSingular', - 'namePlural', - 'labelSingular', - 'labelPlural', - 'description', -]; -/// End - -type ObjectWorkspaceMigrationUpdate = FromTo; - -const compareTwoWorkspaceMigrationObjectInput = ({ - from, - to, -}: ObjectWorkspaceMigrationUpdate) => { - const fromCompare = transformMetadataForComparison(from, { - shouldIgnoreProperty: (property) => - objectPropertiesToIgnore.includes(property), - }); - const toCompare = transformMetadataForComparison(to, { - shouldIgnoreProperty: (property) => - objectPropertiesToIgnore.includes(property), - }); - const objectMetadataDifference = diff(fromCompare, omit(toCompare, 'fields')); - - return objectMetadataDifference.flatMap< - UpdateObjectAction['updates'][number] - >((difference) => { - switch (difference.type) { - case 'CHANGE': { - if ( - difference.oldValue === null && - (difference.value === null || difference.value === undefined) - ) { - return []; - } - const property = difference.path[0]; - - // TODO investigate why it would be a number, in case of array I guess ? - if (typeof property === 'number') { - return []; - } - - // Could be handled directly from the diff we do above - if ( - !allowedObjectProps.includes(property as keyof ObjectMetadataEntity) - ) { - return []; - } - - return { - property, - from: difference.oldValue, - to: difference.value, - }; - } - case 'CREATE': - case 'REMOVE': { - // Should never occurs ? should throw ? - return []; - } - default: { - assertUnreachable(difference, 'TODO'); - } - } - }); -}; +import { compareTwoWorkspaceMigrationObjectInput } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/workspace-migration-object-metadata-input-comparator.util'; export type CreatedDeletedUpdatedObjectMetadataInputMatrix = CustomDeletedCreatedUpdatedMatrix< @@ -106,44 +19,32 @@ export const buildWorkspaceMigrationV2ObjectActions = ({ createdObjectMetadata, deletedObjectMetadata, updatedObjectMetadata, -}: CreatedDeletedUpdatedObjectMetadataInputMatrix): WorkspaceMigrationActionV2[] => { - const createdObjectActions = createdObjectMetadata.flatMap( - (objectMetadata) => { - const createObjectAction = - getWorkspaceMigrationV2ObjectCreateAction(objectMetadata); - const createFieldActions = objectMetadata.fields.map((field) => - getWorkspaceMigrationV2FieldCreateAction({ - field, - objectMetadataUniqueIdentifier: objectMetadata.uniqueIdentifier, - }), - ); - - return [createObjectAction, ...createFieldActions]; - }, +}: CreatedDeletedUpdatedObjectMetadataInputMatrix): WorkspaceMigrationV2ObjectAction[] => { + const createdObjectActions = createdObjectMetadata.map( + getWorkspaceMigrationV2ObjectCreateAction, ); const deletedObjectActions = deletedObjectMetadata.map( getWorkspaceMigrationV2ObjectDeleteAction, ); - const updatedObjectActions = updatedObjectMetadata - .map(({ from, to }) => { + const updatedObjectActions = + updatedObjectMetadata.flatMap(({ from, to }) => { const objectUpdatedProperties = compareTwoWorkspaceMigrationObjectInput({ from, to, }); if (objectUpdatedProperties.length === 0) { - return null; + return []; } return { - objectMetadataUniqueIdentifier: from.uniqueIdentifier, type: 'update_object', + objectMetadataInput: to, updates: objectUpdatedProperties, }; - }) - .filter((action): action is UpdateObjectAction => action !== null); + }); return [ ...createdObjectActions,