FieldMetadataServiceV2.deleteOneField (#13744)

# Introduction
- Migrated the `deleteOneField` handler to new workspace migration v2
style
- Refactored the build to expect `flatObjectMetadataMaps` that he
instantly translate at the be beginning
Commented `ObjectMetadataServiceV2` as it's still not implemented
neither used
This commit is contained in:
Paul Rastoin
2025-08-08 15:11:47 +02:00
committed by GitHub
parent 33f8885ef4
commit 28e5186f23
22 changed files with 1751 additions and 569 deletions
@@ -3,19 +3,20 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { COMPANY_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/company-flat-fields.mock';
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
import { OPPORTUNITY_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/opportunity-flat-fields.mock';
import { PET_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/pet-flat-fields.mock';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util';
import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
import { COMPANY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/company-flat-object.mock';
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock';
import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock';
import { type WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type';
const basicObjectMetadataId = faker.string.uuid();
const basicFlatFieldMetadatas = Array.from({ length: 5 }, (_value, index) =>
getFlatFieldMetadataMock({
objectMetadataId: basicObjectMetadataId,
uniqueIdentifier: `field_${index}`,
type: FieldMetadataType.TEXT,
}),
);
// TODO prastoin test defaultValue and settings updates
// TODO prastoin test standard abstraction in TDD style
@@ -24,16 +25,14 @@ const relationTestCases: WorkspaceMigrationBuilderTestCase[] = [
title: 'It should build an create_field action for a RELATION field',
context: {
input: () => {
const objectMetadataId = faker.string.uuid();
const targetObjectMetadataId = faker.string.uuid();
const createdFlatRelationFieldMetadata = getFlatFieldMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId,
objectMetadataId: PET_FLAT_OBJECT_MOCK.id,
type: FieldMetadataType.RELATION,
relationTargetFieldMetadataId: faker.string.uuid(),
relationTargetObjectMetadataId: faker.string.uuid(),
flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({
objectMetadataId: targetObjectMetadataId,
objectMetadataId: COMPANY_FLAT_OBJECT_MOCK.id,
type: FieldMetadataType.RELATION,
uniqueIdentifier: 'field-metadata-unique-identifier-2',
}),
@@ -41,20 +40,14 @@ const relationTestCases: WorkspaceMigrationBuilderTestCase[] = [
uniqueIdentifier: 'object-metadata-unique-identifier-2',
}),
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
isLabelSyncedWithName: true,
flatFieldMetadatas: [],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
flatFieldMetadatas: [createdFlatRelationFieldMetadata],
},
],
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadata: createdFlatRelationFieldMetadata,
}),
};
},
expectedActionsTypeCounter: {
@@ -65,42 +58,17 @@ const relationTestCases: WorkspaceMigrationBuilderTestCase[] = [
{
title: 'It should build an update_field action for a RELATION field',
context: {
input: () => {
const objectMetadataId = faker.string.uuid();
const updatedFieldMetadata = getFlatFieldMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId,
type: FieldMetadataType.RELATION,
relationTargetFieldMetadataId: faker.string.uuid(),
relationTargetObjectMetadataId: faker.string.uuid(),
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
isLabelSyncedWithName: true,
flatFieldMetadatas: [
...basicFlatFieldMetadatas,
updatedFieldMetadata,
],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
flatFieldMetadatas: [
...basicFlatFieldMetadatas,
{
...updatedFieldMetadata,
isActive: false,
description: 'new description',
label: 'new label',
},
],
},
],
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: getFlatFieldMetadataMock({
...PET_FLAT_FIELDS_MOCK.species,
description: 'new description',
label: 'new label',
}),
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
},
expectedActionsTypeCounter: {
updateField: 1,
@@ -111,47 +79,23 @@ const relationTestCases: WorkspaceMigrationBuilderTestCase[] = [
title:
'It should NOT build an update_field action for a field RELATION uncovered fields mutation',
context: {
input: () => {
const objectMetadataId = faker.string.uuid();
const updatedFieldMetadata = getFlatFieldMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId,
type: FieldMetadataType.RELATION,
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'column-name',
onDelete: undefined,
},
relationTargetFieldMetadataId: faker.string.uuid(),
relationTargetObjectMetadataId: faker.string.uuid(),
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
isLabelSyncedWithName: true,
flatFieldMetadatas: [updatedFieldMetadata],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
flatFieldMetadatas: [
{
...updatedFieldMetadata,
settings: {
relationType: RelationType.ONE_TO_MANY,
isForeignKey: false,
joinColumnName: 'new-column-name',
onDelete: undefined,
},
relationTargetFieldMetadataId: faker.string.uuid(),
relationTargetObjectMetadataId: faker.string.uuid(),
},
],
},
],
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: getFlatFieldMetadataMock({
...COMPANY_FLAT_FIELDS_MOCK.opportunities,
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'new-column-name',
onDelete: undefined,
},
name: 'newName',
relationTargetFieldMetadataId: faker.string.uuid(),
relationTargetObjectMetadataId: faker.string.uuid(),
}),
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
},
},
},
@@ -161,29 +105,17 @@ const basicCrudTestCases: WorkspaceMigrationBuilderTestCase[] = [
{
title: 'It should build an create_field action',
context: {
input: () => {
const objectMetadataId = faker.string.uuid();
const flatFieldMetadata = getFlatFieldMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
type: FieldMetadataType.TEXT,
objectMetadataId,
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
isLabelSyncedWithName: true,
flatFieldMetadatas: [],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
flatFieldMetadatas: [flatFieldMetadata],
},
],
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: getFlatFieldMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
type: FieldMetadataType.TEXT,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
}),
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
},
expectedActionsTypeCounter: {
createField: 1,
@@ -193,37 +125,19 @@ const basicCrudTestCases: WorkspaceMigrationBuilderTestCase[] = [
{
title: 'It should build an update_field action',
context: {
input: () => {
const objectMetadataId = faker.string.uuid();
const flatFieldMetadata = getFlatFieldMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
type: FieldMetadataType.TEXT,
objectMetadataId,
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
flatFieldMetadatas: [...basicFlatFieldMetadatas, flatFieldMetadata],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
flatFieldMetadatas: [
...basicFlatFieldMetadatas,
{
...flatFieldMetadata,
description: 'new description',
name: 'new name',
isActive: false,
icon: 'new icon',
},
],
},
],
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadata: getFlatFieldMetadataMock({
...OPPORTUNITY_FLAT_FIELDS_MOCK.stage,
description: 'new description',
name: 'new name',
isActive: false,
icon: 'new icon',
}),
}),
},
expectedActionsTypeCounter: {
updateField: 1,
@@ -233,28 +147,13 @@ const basicCrudTestCases: WorkspaceMigrationBuilderTestCase[] = [
{
title: 'It should build a delete_field action',
context: {
input: () => {
const objectMetadataId = faker.string.uuid();
const flatFieldMetadata = getFlatFieldMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
type: FieldMetadataType.TEXT,
objectMetadataId,
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
flatFieldMetadatas: [...basicFlatFieldMetadatas, flatFieldMetadata],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
flatFieldMetadatas: basicFlatFieldMetadatas,
},
],
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps: deleteFieldFromFlatObjectMetadataMapsOrThrow({
fieldMetadataId: PET_FLAT_FIELDS_MOCK.species.id,
objectMetadataId: PET_FLAT_FIELDS_MOCK.species.objectMetadataId,
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
}),
},
expectedActionsTypeCounter: {
deleteField: 1,
@@ -271,24 +170,9 @@ export const WORKSPACE_MIGRATION_FIELD_BUILDER_TEST_CASES: WorkspaceMigrationBui
title:
'It should not infer any actions as from and to fields are identical',
context: {
input: () => {
const objectMetadataId = faker.string.uuid();
const flatFieldMetadata = getFlatFieldMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
type: FieldMetadataType.TEXT,
objectMetadataId,
});
const from = [
getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
flatFieldMetadatas: [flatFieldMetadata],
}),
];
return {
from,
to: from,
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
},
},
@@ -1,37 +1,30 @@
import { faker } from '@faker-js/faker';
import { getFlatIndexMetadataMock } from 'src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
import { NOTE_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/note-flat-object.mock';
import { type WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type';
// Should test more things such as flatFieldIndex diffing
const objectMetadataId = faker.string.uuid();
export const WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES: WorkspaceMigrationBuilderTestCase[] =
[
{
title: 'It should build an create_index action',
context: {
input: () => {
const flatIndexMetadata = getFlatIndexMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId,
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
isLabelSyncedWithName: true,
flatIndexMetadatas: [],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
flatIndexMetadatas: [flatIndexMetadata],
},
],
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...NOTE_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [
getFlatIndexMetadataMock({
objectMetadataId: NOTE_FLAT_OBJECT_MOCK.id,
uniqueIdentifier: 'field-metadata-unique-identifier-1',
}),
],
}),
}),
},
expectedActionsTypeCounter: {
createIndex: 1,
@@ -44,30 +37,37 @@ export const WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES: WorkspaceMigrationBui
context: {
input: () => {
const flatIndexMetadata = getFlatIndexMetadataMock({
objectMetadataId: NOTE_FLAT_OBJECT_MOCK.id,
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId,
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
isLabelSyncedWithName: true,
flatIndexMetadatas: [flatIndexMetadata],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
const fromFlatObjectMetadataMaps =
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...NOTE_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [flatIndexMetadata],
}),
});
const toFlatObjectMetadataMaps =
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...NOTE_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [
{
...flatIndexMetadata,
name: 'new index name',
isUnique: false,
indexWhereClause: 'new index where clause',
uniqueIdentifier: 'field-metadata-unique-identifier-1',
},
],
},
],
}),
});
return {
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
};
},
expectedActionsTypeCounter: {
@@ -79,26 +79,21 @@ export const WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES: WorkspaceMigrationBui
{
title: 'It should build a delete_index action',
context: {
input: () => {
const flatIndexMetadata = getFlatIndexMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId,
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
isLabelSyncedWithName: true,
flatIndexMetadatas: [flatIndexMetadata],
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
flatIndexMetadatas: [],
},
],
};
input: {
fromFlatObjectMetadataMaps:
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...NOTE_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [
getFlatIndexMetadataMock({
objectMetadataId: NOTE_FLAT_OBJECT_MOCK.id,
uniqueIdentifier: 'field-metadata-unique-identifier-1',
}),
],
}),
}),
toFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expectedActionsTypeCounter: {
deleteIndex: 1,
@@ -109,21 +104,9 @@ export const WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES: WorkspaceMigrationBui
title:
'It should not infer any actions as from and to indexes are identical',
context: {
input: () => {
const flatIndexMetadata = getFlatIndexMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId,
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'object-metadata-unique-identifier-1',
isLabelSyncedWithName: true,
flatIndexMetadatas: [flatIndexMetadata],
});
return {
from: [flatObjectMetadata],
to: [flatObjectMetadata],
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
},
},
@@ -1,9 +1,13 @@
import { faker } from '@faker-js/faker';
import { FieldMetadataType } from 'twenty-shared/types';
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
import { getFlatIndexMetadataMock } from 'src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock';
import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util';
import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
import { COMPANY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/company-flat-object.mock';
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock';
import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock';
import { fromFlatObjectMetadatasToFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadatas-to-flat-object-metadata-maps.util';
import { type WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type';
export const WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES: WorkspaceMigrationBuilderTestCase[] =
@@ -12,25 +16,18 @@ export const WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES: WorkspaceMigrationBu
title:
'It should build an update_object action with all object updated fields',
context: {
input: () => {
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'pomme',
nameSingular: 'toto',
namePlural: 'totos',
isLabelSyncedWithName: true,
});
return {
from: [flatObjectMetadata],
to: [
{
...flatObjectMetadata,
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...PET_FLAT_OBJECT_MOCK,
nameSingular: 'prastouin',
namePlural: 'prastoins',
isLabelSyncedWithName: false,
},
],
};
}),
}),
},
expectedActionsTypeCounter: {
updateObject: 1,
@@ -40,18 +37,16 @@ export const WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES: WorkspaceMigrationBu
{
title: 'It should build a create_object action',
context: {
input: () => {
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'pomme',
nameSingular: 'toto',
namePlural: 'totos',
isLabelSyncedWithName: true,
});
return {
from: [],
to: [flatObjectMetadata],
};
input: {
fromFlatObjectMetadataMaps:
fromFlatObjectMetadatasToFlatObjectMetadataMaps([
PET_FLAT_OBJECT_MOCK,
]),
toFlatObjectMetadataMaps:
fromFlatObjectMetadatasToFlatObjectMetadataMaps([
PET_FLAT_OBJECT_MOCK,
ROCKET_FLAT_OBJECT_MOCK,
]),
},
expectedActionsTypeCounter: {
createObject: 1,
@@ -60,37 +55,22 @@ export const WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES: WorkspaceMigrationBu
},
{
title:
'It should build a create_object and create_field and create_index actions for each of this fieldMetadata',
'It should build a create_object and create_index actions for each of this fieldMetadata',
context: {
input: () => {
const objectMetadataId = faker.string.uuid();
const flatFieldMetadatas = Array.from(
{ length: 5 },
(_value, index) =>
getFlatFieldMetadataMock({
type: FieldMetadataType.TEXT,
objectMetadataId,
uniqueIdentifier: `field_${index}`,
}),
);
const flatIndexMetadata = getFlatIndexMetadataMock({
uniqueIdentifier: 'field-metadata-unique-identifier-1',
objectMetadataId,
});
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'pomme',
nameSingular: 'toto',
namePlural: 'totos',
isLabelSyncedWithName: true,
id: objectMetadataId,
flatFieldMetadatas,
flatIndexMetadatas: [flatIndexMetadata],
});
return {
from: [],
to: [flatObjectMetadata],
};
input: {
fromFlatObjectMetadataMaps: EMPTY_FLAT_OBJECT_METADATA_MAPS,
toFlatObjectMetadataMaps:
fromFlatObjectMetadatasToFlatObjectMetadataMaps([
{
...ROCKET_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [
getFlatIndexMetadataMock({
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
uniqueIdentifier: 'field-metadata-unique-identifier-1',
}),
],
},
]),
},
expectedActionsTypeCounter: {
@@ -103,36 +83,26 @@ export const WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES: WorkspaceMigrationBu
{
title: 'It should build a delete_object action',
context: {
input: () => {
const flatObjectMetadata = getFlatObjectMetadataMock({
uniqueIdentifier: 'pomme',
nameSingular: 'toto',
namePlural: 'totos',
isLabelSyncedWithName: true,
});
return {
from: [flatObjectMetadata],
to: [],
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
deleteObjectFromFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
objectMetadataId: COMPANY_FLAT_OBJECT_MOCK.id,
}),
},
expectedActionsTypeCounter: {
deleteObject: 1,
deleteField: COMPANY_FLAT_OBJECT_MOCK.flatFieldMetadatas.length,
},
},
},
{
title: 'It should not infer any actions as from and to are identical',
context: {
input: () => {
const from = [
getFlatObjectMetadataMock({ uniqueIdentifier: 'pomme' }),
];
return {
from,
to: from,
};
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
},
},
@@ -2,11 +2,7 @@ import { type EachTestingContext } from 'twenty-shared/testing';
import { type ConvertActionTypeToCamelCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/convert-action-type-to-camel-case.type';
import { type WorkspaceMigrationActionTypeV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
import { type WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
type WorkspaceBuilderArgs = Parameters<
typeof WorkspaceMigrationBuilderV2Service.prototype.build
>[0]['objectMetadataFromToInputs'];
import { type WorkspaceMigrationBuildArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
export type CamelCasedWorkspaceMigrationActionsType =
ConvertActionTypeToCamelCase<WorkspaceMigrationActionTypeV2>;
@@ -16,6 +12,8 @@ export type ExpectedActionCounters = Partial<
>;
export type WorkspaceMigrationBuilderTestCase = EachTestingContext<{
input: WorkspaceBuilderArgs | (() => WorkspaceBuilderArgs);
input:
| Omit<WorkspaceMigrationBuildArgs, 'workspaceId'>
| (() => Omit<WorkspaceMigrationBuildArgs, 'workspaceId'>);
expectedActionsTypeCounter?: ExpectedActionCounters;
}>;
@@ -31,6 +31,7 @@ const allWorkspaceBuilderTestCases: {
},
];
// TODO prastoin add coverage to infer deletion from missing entities
const expectedActionsTypeCounterChecker = ({
expectedActionsTypeCounter,
workspaceMigration,
@@ -81,12 +82,15 @@ describe.each(allWorkspaceBuilderTestCases)(
it.each(eachTestingContextFilter(testCases))(
'$title',
({ context: { input, expectedActionsTypeCounter } }) => {
const { from, to } = typeof input === 'function' ? input() : input;
const {
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
inferDeletionFromMissingObjectFieldIndex,
} = typeof input === 'function' ? input() : input;
const workspaceMigration = service.build({
objectMetadataFromToInputs: {
from,
to,
},
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
inferDeletionFromMissingObjectFieldIndex,
workspaceId: '20202020-52cc-4c64-ad63-76c26fc3a1e1',
});
@@ -2,7 +2,8 @@ import { Injectable } from '@nestjs/common';
import { type FromTo } from 'twenty-shared/types';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { fromFlatObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-maps-to-flat-object-metadatas.util';
import { deletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
import { computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util';
@@ -12,24 +13,35 @@ import { getWorkspaceMigrationV2CreateIndexAction } from 'src/engine/workspace-m
import { buildWorkspaceMigrationV2FieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder';
import { buildWorkspaceMigrationIndexActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-index-actions-builder';
import { buildWorkspaceMigrationV2ObjectActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder';
export type WorkspaceMigrationBuildArgs = {
workspaceId: string;
inferDeletionFromMissingObjectFieldIndex?: boolean;
} & FromTo<FlatObjectMetadataMaps, 'FlatObjectMetadataMaps'>;
@Injectable()
export class WorkspaceMigrationBuilderV2Service {
constructor() {}
build({
objectMetadataFromToInputs,
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
workspaceId,
inferDeletionFromMissingObjectFieldIndex = true,
}: {
objectMetadataFromToInputs: FromTo<FlatObjectMetadata[]>;
workspaceId: string;
inferDeletionFromMissingObjectFieldIndex?: boolean;
}): WorkspaceMigrationV2 {
}: WorkspaceMigrationBuildArgs): WorkspaceMigrationV2 {
const fromFlatObjectMetadatas =
fromFlatObjectMetadataMapsToFlatObjectMetadatas(
fromFlatObjectMetadataMaps,
);
const toFlatObjectMetadatas =
fromFlatObjectMetadataMapsToFlatObjectMetadatas(toFlatObjectMetadataMaps);
const {
created: createdObjectMetadata,
deleted: deletedObjectMetadata,
updated: updatedObjectMetadata,
} = deletedCreatedUpdatedMatrixDispatcher(objectMetadataFromToInputs);
} = deletedCreatedUpdatedMatrixDispatcher({
from: fromFlatObjectMetadatas,
to: toFlatObjectMetadatas,
});
const objectWorkspaceMigrationActions =
buildWorkspaceMigrationV2ObjectActions({
@@ -3,6 +3,7 @@ import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
@@ -23,10 +24,12 @@ export class WorkspaceMigrationRunnerV2Service {
private readonly coreDataSource: DataSource,
) {}
run = async (workspaceMigration: WorkspaceMigrationV2) => {
run = async (
workspaceMigration: WorkspaceMigrationV2,
): Promise<FlatObjectMetadataMaps> => {
const queryRunner = this.coreDataSource.createQueryRunner();
const { flatObjectMetadataMaps } =
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
{
workspaceId: workspaceMigration.workspaceId,
@@ -36,7 +39,7 @@ export class WorkspaceMigrationRunnerV2Service {
await queryRunner.connect();
await queryRunner.startTransaction();
let optimisticFlatObjectMetadataMaps = structuredClone(
flatObjectMetadataMaps,
existingFlatObjectMetadataMaps,
);
try {
@@ -73,6 +76,8 @@ export class WorkspaceMigrationRunnerV2Service {
workspaceId,
},
);
return optimisticFlatObjectMetadataMaps;
} catch (error) {
if (queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction();