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
This commit is contained in:
@ -0,0 +1,4 @@
|
||||
export type FromTo<T> = {
|
||||
from: T;
|
||||
to: T;
|
||||
};
|
||||
@ -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'];
|
||||
@ -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<TTarget extends string> = {
|
||||
[P in `${TTarget}UniqueIdentifier`]: string;
|
||||
};
|
||||
|
||||
type ObjectMetadataUniqueIdentifier = UniqueIdentifierRecord<'objectMetadata'>;
|
||||
|
||||
type FieldMetadataUniqueIdentifier = UniqueIdentifierRecord<'fieldMetadata'>;
|
||||
|
||||
export type FromTo<T> = {
|
||||
from: T;
|
||||
to: T;
|
||||
};
|
||||
|
||||
type ObjectActionCommon = ObjectMetadataUniqueIdentifier;
|
||||
export type CreateObjectAction = {
|
||||
type: 'create_object';
|
||||
object: ObjectMetadataEntity;
|
||||
} & ObjectActionCommon;
|
||||
|
||||
export type UpdateObjectAction = {
|
||||
type: 'update_object';
|
||||
updates: (FromTo<Partial<ObjectMetadataEntity>> & { property: string })[];
|
||||
} & ObjectActionCommon;
|
||||
|
||||
export type DeleteObjectAction = {
|
||||
type: 'delete_object';
|
||||
} & ObjectActionCommon;
|
||||
|
||||
export type WorkspaceMigrationV2ObjectAction =
|
||||
| CreateObjectAction
|
||||
| UpdateObjectAction
|
||||
| DeleteObjectAction;
|
||||
|
||||
type FieldActionCommon = {
|
||||
field: Partial<FieldMetadataEntity>;
|
||||
} & ObjectMetadataUniqueIdentifier &
|
||||
FieldMetadataUniqueIdentifier;
|
||||
export type CreateFieldAction = {
|
||||
type: 'create_field';
|
||||
} & FieldActionCommon;
|
||||
|
||||
export type UpdateFieldAction = {
|
||||
type: 'update_field';
|
||||
} & FieldActionCommon;
|
||||
|
||||
export type DeleteFieldAction = {
|
||||
type: 'delete_field';
|
||||
} & Omit<FieldActionCommon, 'field'>;
|
||||
|
||||
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'];
|
||||
@ -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<FieldMetadataEntity[P]>;
|
||||
}[FieldMetadataEntityEditableProperties]
|
||||
>[];
|
||||
} & FieldAndObjectMetadataWorkspaceMigrationInput;
|
||||
|
||||
export type DeleteFieldAction = {
|
||||
type: 'delete_field';
|
||||
} & FieldAndObjectMetadataWorkspaceMigrationInput;
|
||||
|
||||
export type WorkspaceMigrationFieldActionV2 =
|
||||
| CreateFieldAction
|
||||
| UpdateFieldAction
|
||||
| DeleteFieldAction;
|
||||
@ -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<FieldMetadataEntity, 'object'>
|
||||
> & {
|
||||
uniqueIdentifier: string;
|
||||
};
|
||||
|
||||
export const fieldMetadataPropertiesToStringify = [
|
||||
'defaultValue',
|
||||
'standardOverrides',
|
||||
] as const satisfies FieldMetadataEntityEditableProperties[];
|
||||
@ -0,0 +1,11 @@
|
||||
export type CreateIndexAction = {
|
||||
type: 'create_index';
|
||||
};
|
||||
|
||||
export type DeleteIndexAction = {
|
||||
type: 'delete_index';
|
||||
};
|
||||
|
||||
export type WorkspaceMigrationIndexActionV2 =
|
||||
| CreateIndexAction
|
||||
| DeleteIndexAction;
|
||||
@ -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<ObjectMetadataEntity[P]>;
|
||||
}[ObjectMetadataEntityEditableProperties]
|
||||
>[];
|
||||
} & ObjectActionCommon;
|
||||
|
||||
export type DeleteObjectAction = {
|
||||
type: 'delete_object';
|
||||
} & ObjectActionCommon;
|
||||
|
||||
export type WorkspaceMigrationV2ObjectAction =
|
||||
| CreateObjectAction
|
||||
| UpdateObjectAction
|
||||
| DeleteObjectAction;
|
||||
@ -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<ObjectMetadataEntity, 'fields'>
|
||||
> & {
|
||||
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'
|
||||
>;
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
export type AddUniquenessConstraintAction = {
|
||||
type: 'add_uniqueness_constraint';
|
||||
};
|
||||
|
||||
export type RemoveUniquenessConstraintAction = {
|
||||
type: 'remove_uniqueness_constraint';
|
||||
};
|
||||
|
||||
export type WorkspaceMigrationUniquenessActionV2 =
|
||||
| RemoveUniquenessConstraintAction
|
||||
| AddUniquenessConstraintAction;
|
||||
@ -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 ?
|
||||
}
|
||||
};
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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<WorkspaceMigrationFieldInput>[] = [],
|
||||
): 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<FieldMetadataType.RELATION>,
|
||||
},
|
||||
]),
|
||||
];
|
||||
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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<WorkspaceMigrationObjectInput>[],
|
||||
): 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;
|
||||
};
|
||||
@ -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<T> = {
|
||||
created: T[];
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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<WorkspaceMigrationFieldInput>) => {
|
||||
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<WorkspaceMigrationFieldInput>;
|
||||
export const compareFieldMetadataInputAndGetUpdateFieldActions = ({
|
||||
from,
|
||||
to,
|
||||
}: GetWorkspaceMigrationUpdateFieldActionArgs) => {
|
||||
const fieldMetadataDifferences = compareTwoWorkspaceMigrationFieldInput({
|
||||
from,
|
||||
to,
|
||||
});
|
||||
|
||||
return fieldMetadataDifferences.flatMap<UpdateFieldAction['updates'][number]>(
|
||||
(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 [];
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
@ -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<WorkspaceMigrationObjectInput>;
|
||||
|
||||
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']}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -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<WorkspaceMigrationObjectInput>;
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationBuilderV2Service {
|
||||
constructor() {}
|
||||
|
||||
build(
|
||||
objectMetadataFromToInputs: WorkspaceMigrationBuilderV2ServiceArgs,
|
||||
objectMetadataFromToInputs: FromTo<WorkspaceMigrationObjectInput[]>,
|
||||
): 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,
|
||||
],
|
||||
};
|
||||
|
||||
@ -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<WorkspaceMigrationObjectFieldInput>) => {
|
||||
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<WorkspaceMigrationObjectFieldInput> & {
|
||||
objectMetadataUniqueIdentifier: string;
|
||||
};
|
||||
// Still in wip
|
||||
const buildWorkspaceMigrationV2FieldActionFromUpdatedFieldMetadata = ({
|
||||
objectMetadataUniqueIdentifier,
|
||||
from,
|
||||
to,
|
||||
}: BuildWorkspaceMigrationV2FieldActionFromUpdatedFieldMetadataArgs) => {
|
||||
const fieldMetadataDifferences = compareTwoWorkspaceMigrationFieldInput({
|
||||
from,
|
||||
to,
|
||||
});
|
||||
|
||||
return fieldMetadataDifferences.flatMap<WorkspaceMigrationFieldActionV2>(
|
||||
(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<WorkspaceMigrationFieldActionV2>(
|
||||
({ from, to }) =>
|
||||
buildWorkspaceMigrationV2FieldActionFromUpdatedFieldMetadata({
|
||||
from,
|
||||
to,
|
||||
objectMetadataUniqueIdentifier: objectMetadataUniqueIdentifier,
|
||||
}),
|
||||
);
|
||||
const updateFieldActions = updatedFieldMetadata.flatMap<UpdateFieldAction>(
|
||||
({ 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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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<ObjectMetadataEntity>)[] = [
|
||||
'nameSingular',
|
||||
'namePlural',
|
||||
'labelSingular',
|
||||
'labelPlural',
|
||||
'description',
|
||||
];
|
||||
/// End
|
||||
|
||||
type ObjectWorkspaceMigrationUpdate = FromTo<WorkspaceMigrationObjectInput>;
|
||||
|
||||
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<UpdateObjectAction | null>(({ from, to }) => {
|
||||
const updatedObjectActions =
|
||||
updatedObjectMetadata.flatMap<UpdateObjectAction>(({ 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,
|
||||
|
||||
Reference in New Issue
Block a user