[POC] Workspace migration builder v2 (#13026)
# Introduction In this PR we've initialized the `workspace-migration-v2` folder. Focusing on the builder in the first place. From now it contains: - Basic temporary types ( `fieldMetadataEntity` and `ObjectMetadataEntity` ) - Object actions builder ( create, delete, update ) - Fields actions builder ( create, delete ) ( update coming in a following PR ) We will still have to handle specific conditions such as: - Index creation - Uniqueness addition removal - Relation We also need to determine when we want to compute and transpile the object no field `uniqueIdentifier` We're aiming to merge this first in order to avoid accumulating code in this PR --------- Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@ -16,6 +16,7 @@ import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-
|
|||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
import { DevSeederModule } from 'src/engine/workspace-manager/dev-seeder/dev-seeder.module';
|
import { DevSeederModule } from 'src/engine/workspace-manager/dev-seeder/dev-seeder.module';
|
||||||
import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
|
import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
|
||||||
|
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
|
||||||
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
|
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
|
||||||
|
|
||||||
import { WorkspaceManagerService } from './workspace-manager.service';
|
import { WorkspaceManagerService } from './workspace-manager.service';
|
||||||
@ -24,6 +25,7 @@ import { WorkspaceManagerService } from './workspace-manager.service';
|
|||||||
imports: [
|
imports: [
|
||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
WorkspaceMigrationModule,
|
WorkspaceMigrationModule,
|
||||||
|
WorkspaceMigrationV2Module,
|
||||||
ObjectMetadataModule,
|
ObjectMetadataModule,
|
||||||
DevSeederModule,
|
DevSeederModule,
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
|
|||||||
@ -0,0 +1,106 @@
|
|||||||
|
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,22 @@
|
|||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
export type WorkspaceMigrationObjectFieldInput = {
|
||||||
|
uniqueIdentifier: string;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
defaultValue: unknown;
|
||||||
|
type: FieldMetadataType;
|
||||||
|
description?: string;
|
||||||
|
// TODO this should extend FieldMetadataEntity
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceMigrationObjectInput = {
|
||||||
|
uniqueIdentifier: string;
|
||||||
|
nameSingular: string;
|
||||||
|
namePlural: string;
|
||||||
|
labelSingular: string;
|
||||||
|
labelPlural: string;
|
||||||
|
description?: string;
|
||||||
|
fields: WorkspaceMigrationObjectFieldInput[];
|
||||||
|
// TODO this should extend ObjectMetadataEntity
|
||||||
|
};
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2';
|
||||||
|
|
||||||
|
export interface WorkspaceMigrationV2<
|
||||||
|
TActions extends WorkspaceMigrationActionV2 = WorkspaceMigrationActionV2,
|
||||||
|
> {
|
||||||
|
// formatVersion: 1;
|
||||||
|
// createdAt: string;
|
||||||
|
// name: string;
|
||||||
|
// description?: string;
|
||||||
|
actions: TActions[];
|
||||||
|
// objectActions: TActions[] // could be cool ?
|
||||||
|
}
|
||||||
@ -0,0 +1,329 @@
|
|||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
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('WorkspaceMigrationBuilderV2Service', () => {
|
||||||
|
let service: WorkspaceMigrationBuilderV2Service;
|
||||||
|
|
||||||
|
const baseObject: WorkspaceMigrationObjectInput = {
|
||||||
|
uniqueIdentifier: '20202020-e89b-12d3-a456-426614175000',
|
||||||
|
nameSingular: 'Contact',
|
||||||
|
namePlural: 'Contacts',
|
||||||
|
labelSingular: 'Contact',
|
||||||
|
labelPlural: 'Contacts',
|
||||||
|
description: 'A contact',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
uniqueIdentifier: '20202020-e89b-12d3-a456-426614174000',
|
||||||
|
name: 'firstName',
|
||||||
|
label: 'First Name',
|
||||||
|
type: FieldMetadataType.FULL_NAME,
|
||||||
|
defaultValue: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new WorkspaceMigrationBuilderV2Service();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a migration when nameSingular changes', () => {
|
||||||
|
const from: WorkspaceMigrationObjectInput = baseObject;
|
||||||
|
const to: WorkspaceMigrationObjectInput = {
|
||||||
|
...from,
|
||||||
|
nameSingular: 'Person',
|
||||||
|
};
|
||||||
|
const result = service.build({ from: [from], to: [to] });
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175000",
|
||||||
|
"type": "update_object",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"from": "Contact",
|
||||||
|
"property": "nameSingular",
|
||||||
|
"to": "Person",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a migration when creating a new object', () => {
|
||||||
|
const newObject: WorkspaceMigrationObjectInput = {
|
||||||
|
uniqueIdentifier: '20202020-e89b-12d3-a456-426614175001',
|
||||||
|
nameSingular: 'Company',
|
||||||
|
namePlural: 'Companies',
|
||||||
|
labelSingular: 'Company',
|
||||||
|
labelPlural: 'Companies',
|
||||||
|
description: 'A company',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
uniqueIdentifier: '20202020-e89b-12d3-a456-426614174001',
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
type: FieldMetadataType.ADDRESS,
|
||||||
|
defaultValue: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = service.build({ from: [], to: [newObject] });
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"description": "A company",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
"objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175001",
|
||||||
|
"type": "create_object",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": {
|
||||||
|
"defaultValue": "",
|
||||||
|
"description": "",
|
||||||
|
"label": "Name",
|
||||||
|
"name": "name",
|
||||||
|
"type": "ADDRESS",
|
||||||
|
"uniqueIdentifier": "20202020-e89b-12d3-a456-426614174001",
|
||||||
|
},
|
||||||
|
"fieldMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614174001",
|
||||||
|
"objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175001",
|
||||||
|
"type": "create_field",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a migration when deleting an object', () => {
|
||||||
|
const result = service.build({ from: [baseObject], to: [] });
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175000",
|
||||||
|
"type": "delete_object",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple operations in a single migration', () => {
|
||||||
|
const objectToUpdate: WorkspaceMigrationObjectInput = {
|
||||||
|
...baseObject,
|
||||||
|
nameSingular: 'Person',
|
||||||
|
fields: [
|
||||||
|
...baseObject.fields,
|
||||||
|
{
|
||||||
|
defaultValue: '',
|
||||||
|
label: 'New field',
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
name: 'newField',
|
||||||
|
uniqueIdentifier: '20202020-3ad3-4fec-9c46-8dc9158980e3',
|
||||||
|
description: 'new field description',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const objectToDelete = {
|
||||||
|
...baseObject,
|
||||||
|
uniqueIdentifier: '20202020-59ef-4a14-a509-0a02acb248d5',
|
||||||
|
};
|
||||||
|
const objectToCreate: WorkspaceMigrationObjectInput = {
|
||||||
|
uniqueIdentifier: '20202020-1218-4fc0-b32d-fc4f005c4bab',
|
||||||
|
nameSingular: 'Company',
|
||||||
|
namePlural: 'Companies',
|
||||||
|
labelSingular: 'Company',
|
||||||
|
labelPlural: 'Companies',
|
||||||
|
description: 'A company',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
uniqueIdentifier: '20202020-1016-4f09-bad6-e75681f385f4',
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
type: FieldMetadataType.ADDRESS,
|
||||||
|
defaultValue: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = service.build({
|
||||||
|
from: [baseObject, objectToDelete],
|
||||||
|
to: [objectToUpdate, objectToCreate],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"description": "A company",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
"objectMetadataUniqueIdentifier": "20202020-1218-4fc0-b32d-fc4f005c4bab",
|
||||||
|
"type": "create_object",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": {
|
||||||
|
"defaultValue": "",
|
||||||
|
"description": "",
|
||||||
|
"label": "Name",
|
||||||
|
"name": "name",
|
||||||
|
"type": "ADDRESS",
|
||||||
|
"uniqueIdentifier": "20202020-1016-4f09-bad6-e75681f385f4",
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"type": "update_object",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"from": "Contact",
|
||||||
|
"property": "nameSingular",
|
||||||
|
"to": "Person",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": {
|
||||||
|
"defaultValue": "",
|
||||||
|
"description": "new field description",
|
||||||
|
"label": "New field",
|
||||||
|
"name": "newField",
|
||||||
|
"type": "NUMBER",
|
||||||
|
"uniqueIdentifier": "20202020-3ad3-4fec-9c46-8dc9158980e3",
|
||||||
|
},
|
||||||
|
"fieldMetadataUniqueIdentifier": "20202020-3ad3-4fec-9c46-8dc9158980e3",
|
||||||
|
"objectMetadataUniqueIdentifier": "20202020-e89b-12d3-a456-426614175000",
|
||||||
|
"type": "create_field",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should treat objects with the same name but different IDs as distinct', () => {
|
||||||
|
const objectA: WorkspaceMigrationObjectInput = {
|
||||||
|
uniqueIdentifier: 'id-1',
|
||||||
|
nameSingular: 'Duplicate',
|
||||||
|
namePlural: 'Duplicates',
|
||||||
|
labelSingular: 'Duplicate',
|
||||||
|
labelPlural: 'Duplicates',
|
||||||
|
description: 'First object',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
uniqueIdentifier: 'field-1',
|
||||||
|
name: 'fieldA',
|
||||||
|
label: 'Field A',
|
||||||
|
type: FieldMetadataType.FULL_NAME,
|
||||||
|
defaultValue: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const objectB: WorkspaceMigrationObjectInput = {
|
||||||
|
uniqueIdentifier: 'id-2',
|
||||||
|
nameSingular: 'Duplicate',
|
||||||
|
namePlural: 'Duplicates',
|
||||||
|
labelSingular: 'Duplicate',
|
||||||
|
labelPlural: 'Duplicates',
|
||||||
|
description: 'Second object',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
uniqueIdentifier: 'field-2',
|
||||||
|
name: 'fieldB',
|
||||||
|
label: 'Field B',
|
||||||
|
type: FieldMetadataType.ADDRESS,
|
||||||
|
defaultValue: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
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',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
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',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit no actions when from and to are deeply equal', () => {
|
||||||
|
const obj: WorkspaceMigrationObjectInput = { ...baseObject };
|
||||||
|
const result = service.build({ from: [obj], to: [obj] });
|
||||||
|
|
||||||
|
expect(result.actions).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import { FromTo } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-action-v2';
|
||||||
|
|
||||||
|
export type DeletedCreatedUpdatedMatrix<T> = {
|
||||||
|
created: T[];
|
||||||
|
deleted: T[];
|
||||||
|
updated: FromTo<T>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CustomDeletedCreatedUpdatedMatrix<TLabel extends string, TInput> = {
|
||||||
|
[P in keyof DeletedCreatedUpdatedMatrix<TInput> as `${P}${Capitalize<TLabel>}`]: DeletedCreatedUpdatedMatrix<TInput>[P];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UniqueIdentifierItem = {
|
||||||
|
uniqueIdentifier: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deletedCreatedUpdatedMatrixDispatcher = <
|
||||||
|
T extends UniqueIdentifierItem,
|
||||||
|
>({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
}: FromTo<T[]>): DeletedCreatedUpdatedMatrix<T> => {
|
||||||
|
const initialDispatcher: DeletedCreatedUpdatedMatrix<T> = {
|
||||||
|
created: [],
|
||||||
|
updated: [],
|
||||||
|
deleted: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromMap = new Map(from.map((obj) => [obj.uniqueIdentifier, obj]));
|
||||||
|
const toMap = new Map(to.map((obj) => [obj.uniqueIdentifier, obj]));
|
||||||
|
|
||||||
|
for (const [identifier, fromObj] of fromMap) {
|
||||||
|
if (!toMap.has(identifier)) {
|
||||||
|
initialDispatcher.deleted.push(fromObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [identifier, toObj] of toMap) {
|
||||||
|
if (!fromMap.has(identifier)) {
|
||||||
|
initialDispatcher.created.push(toObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [identifier, fromObj] of fromMap) {
|
||||||
|
const toObj = toMap.get(identifier);
|
||||||
|
|
||||||
|
if (toObj) {
|
||||||
|
initialDispatcher.updated.push({
|
||||||
|
from: fromObj,
|
||||||
|
to: toObj,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialDispatcher;
|
||||||
|
};
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
type FieldInputAndObjectUniqueIdentifier = {
|
||||||
|
field: WorkspaceMigrationObjectFieldInput;
|
||||||
|
objectMetadataUniqueIdentifier: string;
|
||||||
|
};
|
||||||
|
export const getWorkspaceMigrationV2FieldCreateAction = ({
|
||||||
|
field,
|
||||||
|
objectMetadataUniqueIdentifier,
|
||||||
|
}: FieldInputAndObjectUniqueIdentifier): CreateFieldAction => ({
|
||||||
|
type: 'create_field',
|
||||||
|
field: field as unknown as FieldMetadataEntity, // TODO prastoin
|
||||||
|
fieldMetadataUniqueIdentifier: field.uniqueIdentifier,
|
||||||
|
objectMetadataUniqueIdentifier,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getWorkspaceMigrationV2FieldDeleteAction = ({
|
||||||
|
field,
|
||||||
|
objectMetadataUniqueIdentifier,
|
||||||
|
}: FieldInputAndObjectUniqueIdentifier): DeleteFieldAction => ({
|
||||||
|
type: 'delete_field',
|
||||||
|
fieldMetadataUniqueIdentifier: field.uniqueIdentifier,
|
||||||
|
objectMetadataUniqueIdentifier,
|
||||||
|
});
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
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';
|
||||||
|
import { WorkspaceMigrationObjectInput } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-object-input';
|
||||||
|
|
||||||
|
export const getWorkspaceMigrationV2ObjectCreateAction = (
|
||||||
|
input: WorkspaceMigrationObjectInput,
|
||||||
|
): CreateObjectAction => ({
|
||||||
|
type: 'create_object',
|
||||||
|
objectMetadataUniqueIdentifier: input.uniqueIdentifier,
|
||||||
|
object: input as unknown as ObjectMetadataEntity, // TODO prastoin
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getWorkspaceMigrationV2ObjectDeleteAction = (
|
||||||
|
input: WorkspaceMigrationObjectInput,
|
||||||
|
): DeleteObjectAction => ({
|
||||||
|
type: 'delete_object',
|
||||||
|
objectMetadataUniqueIdentifier: input.uniqueIdentifier,
|
||||||
|
});
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [FeatureFlagModule],
|
||||||
|
providers: [WorkspaceMigrationBuilderV2Service],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class WorkspaceMigrationBuilderV2Module {}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
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 { 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,
|
||||||
|
): WorkspaceMigrationV2 {
|
||||||
|
const {
|
||||||
|
created: createdObjectMetadata,
|
||||||
|
deleted: deletedObjectMetadata,
|
||||||
|
updated: updatedObjectMetadata,
|
||||||
|
} = deletedCreatedUpdatedMatrixDispatcher(objectMetadataFromToInputs);
|
||||||
|
|
||||||
|
const objectWorkspaceMigrationActions =
|
||||||
|
buildWorkspaceMigrationV2ObjectActions({
|
||||||
|
createdObjectMetadata,
|
||||||
|
deletedObjectMetadata,
|
||||||
|
updatedObjectMetadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldWorkspaceMigrationActions =
|
||||||
|
buildWorkspaceMigrationV2FieldActions({ updatedObjectMetadata });
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions: [
|
||||||
|
...objectWorkspaceMigrationActions,
|
||||||
|
...fieldWorkspaceMigrationActions,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,217 @@
|
|||||||
|
import diff from 'microdiff';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FromTo,
|
||||||
|
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';
|
||||||
|
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);
|
||||||
|
|
||||||
|
let allUpdatedObjectMetadataFieldActions: WorkspaceMigrationFieldActionV2[] =
|
||||||
|
[];
|
||||||
|
|
||||||
|
for (const {
|
||||||
|
createdFieldMetadata,
|
||||||
|
deletedFieldMetadata,
|
||||||
|
objectMetadataUniqueIdentifier,
|
||||||
|
updatedFieldMetadata,
|
||||||
|
} of objectMetadataDeletedCreatedUpdatedFields) {
|
||||||
|
const updateFieldAction =
|
||||||
|
updatedFieldMetadata.flatMap<WorkspaceMigrationFieldActionV2>(
|
||||||
|
({ from, to }) =>
|
||||||
|
buildWorkspaceMigrationV2FieldActionFromUpdatedFieldMetadata({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
objectMetadataUniqueIdentifier: objectMetadataUniqueIdentifier,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const createFieldAction = createdFieldMetadata.map((field) =>
|
||||||
|
getWorkspaceMigrationV2FieldCreateAction({
|
||||||
|
field,
|
||||||
|
objectMetadataUniqueIdentifier,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteFieldAction = deletedFieldMetadata.map((field) =>
|
||||||
|
getWorkspaceMigrationV2FieldDeleteAction({
|
||||||
|
field,
|
||||||
|
objectMetadataUniqueIdentifier,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
allUpdatedObjectMetadataFieldActions =
|
||||||
|
allUpdatedObjectMetadataFieldActions.concat([
|
||||||
|
...createFieldAction,
|
||||||
|
...deleteFieldAction,
|
||||||
|
...updateFieldAction,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allUpdatedObjectMetadataFieldActions;
|
||||||
|
};
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
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';
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreatedDeletedUpdatedObjectMetadataInputMatrix =
|
||||||
|
CustomDeletedCreatedUpdatedMatrix<
|
||||||
|
'objectMetadata',
|
||||||
|
WorkspaceMigrationObjectInput
|
||||||
|
>;
|
||||||
|
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];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const deletedObjectActions = deletedObjectMetadata.map(
|
||||||
|
getWorkspaceMigrationV2ObjectDeleteAction,
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedObjectActions = updatedObjectMetadata
|
||||||
|
.map<UpdateObjectAction | null>(({ from, to }) => {
|
||||||
|
const objectUpdatedProperties = compareTwoWorkspaceMigrationObjectInput({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (objectUpdatedProperties.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
objectMetadataUniqueIdentifier: from.uniqueIdentifier,
|
||||||
|
type: 'update_object',
|
||||||
|
updates: objectUpdatedProperties,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((action): action is UpdateObjectAction => action !== null);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...createdObjectActions,
|
||||||
|
...deletedObjectActions,
|
||||||
|
...updatedObjectActions,
|
||||||
|
];
|
||||||
|
};
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceMetadataMigrationRunnerService {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { WorkspaceMetadataMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner.service';
|
||||||
|
import { WorkspaceSchemaMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [FeatureFlagModule],
|
||||||
|
providers: [
|
||||||
|
WorkspaceMetadataMigrationRunnerService,
|
||||||
|
WorkspaceSchemaMigrationRunnerService,
|
||||||
|
],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class WorkspaceMigrationRunnerV2Module {}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceSchemaMigrationRunnerService {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module';
|
||||||
|
import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
WorkspaceMigrationBuilderV2Module,
|
||||||
|
WorkspaceMigrationRunnerV2Module,
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class WorkspaceMigrationV2Module {}
|
||||||
Reference in New Issue
Block a user