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
@@ -11,6 +11,8 @@ import {
import { type FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import {
@@ -28,8 +30,8 @@ import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
import { RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto';
import {
type UpdateFieldInput,
UpdateOneFieldMetadataInput,
type UpdateFieldInput,
} from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
@@ -38,6 +40,7 @@ import {
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { BeforeUpdateOneField } from 'src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
import { FieldMetadataServiceV2 } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2';
import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util';
import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util';
import { fromObjectMetadataEntityToObjectMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util';
@@ -57,6 +60,8 @@ export class FieldMetadataResolver {
constructor(
private readonly fieldMetadataService: FieldMetadataService,
private readonly beforeUpdateOneField: BeforeUpdateOneField<UpdateFieldInput>,
private readonly featureFlagService: FeatureFlagService,
private readonly fieldMetadataServiceV2: FieldMetadataServiceV2,
) {}
@UseGuards(SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL))
@@ -103,10 +108,27 @@ export class FieldMetadataResolver {
@Args('input') input: DeleteOneFieldInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
if (!workspaceId) {
if (!isDefined(workspaceId)) {
throw new ForbiddenError('Could not retrieve workspace ID');
}
try {
const isWorkspaceMigrationV2Enabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
workspaceId,
);
if (isWorkspaceMigrationV2Enabled) {
return await this.fieldMetadataServiceV2.deleteOneField({
deleteOneFieldInput: input,
workspaceId,
});
}
} catch (error) {
fieldMetadataGraphqlApiExceptionHandler(error);
}
const fieldMetadata =
await this.fieldMetadataService.findOneWithinWorkspace(workspaceId, {
where: {
@@ -114,7 +136,7 @@ export class FieldMetadataResolver {
},
});
if (!fieldMetadata) {
if (!isDefined(fieldMetadata)) {
throw new ValidationError('Field does not exist');
}
@@ -1,13 +1,16 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import {
isDefined,
trimAndRemoveDuplicatedWhitespacesFromString,
} from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
import { MultipleMetadataValidationErrors } from 'src/engine/core-modules/error/multiple-metadata-validation-errors';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
FieldMetadataException,
@@ -16,20 +19,21 @@ import {
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { type FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util';
import { fromCreateFieldInputToFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util';
import { fromDeleteFieldInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util';
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
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 { addFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps.util';
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util';
import { extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/extract-flat-object-metadata-maps-out-of-flat-object-metadata-maps-or-throw.util';
import { extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/extract-flat-object-metadata-maps-out-of-flat-object-metadata-maps.util';
import { fromFlatObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-maps-to-flat-object-metadatas.util';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
@Injectable()
export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEntity> {
export class FieldMetadataServiceV2 {
constructor(
@InjectRepository(FieldMetadataEntity, 'core')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
@@ -37,14 +41,19 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
private readonly workspaceMigrationBuilderV2: WorkspaceMigrationBuilderV2Service,
private readonly flatFieldMetadataValidatorService: FlatFieldMetadataValidatorService,
private readonly workspaceMigrationRunnerV2Service: WorkspaceMigrationRunnerV2Service,
) {
super(fieldMetadataRepository);
}
) {}
override async createOne(
fieldMetadataInput: CreateFieldInput,
): Promise<FieldMetadataEntity> {
const [createdFieldMetadata] = await this.createMany([fieldMetadataInput]);
async createOne({
fieldMetadataInput,
workspaceId,
}: {
fieldMetadataInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
}): Promise<FieldMetadataEntity> {
const [createdFieldMetadata] = await this.createMany({
fieldMetadataInputs: [fieldMetadataInput],
workspaceId,
});
if (!isDefined(createdFieldMetadata)) {
throw new FieldMetadataException(
@@ -56,6 +65,76 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
return createdFieldMetadata;
}
public async deleteOneField({
deleteOneFieldInput,
workspaceId,
}: {
deleteOneFieldInput: DeleteOneFieldInput;
workspaceId: string;
}): Promise<void> {
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
{
workspaceId,
},
);
const flatFieldMetadatasToDelete =
fromDeleteFieldInputToFlatFieldMetadatasToDelete({
deleteOneFieldInput,
existingFlatObjectMetadataMaps,
});
const validationErrors = flatFieldMetadatasToDelete.flatMap(
(flatFieldMetadataToDelete) =>
this.flatFieldMetadataValidatorService.validateFlatFieldMetadataDeletion(
{
existingFlatObjectMetadataMaps,
flatFieldMetadataToDelete,
},
),
);
if (validationErrors.length > 0) {
throw new MultipleMetadataValidationErrors(
validationErrors,
validationErrors.length > 1
? 'Multiple validation errors occurred while deleting field'
: 'A validation error occurred while deleting field',
);
}
const flatObjectMetadataMapsWithImpactedObject =
extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
objectMetadataIds: flatFieldMetadatasToDelete.map(
(flatFieldMetadataToDelete) =>
flatFieldMetadataToDelete.objectMetadataId,
),
});
const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete.reduce(
(flatObjectMetadataMaps, flatFieldMetadataToDelete) =>
deleteFieldFromFlatObjectMetadataMapsOrThrow({
fieldMetadataId: flatFieldMetadataToDelete.id,
flatObjectMetadataMaps,
objectMetadataId: flatFieldMetadataToDelete.objectMetadataId,
}),
flatObjectMetadataMapsWithImpactedObject,
);
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
fromFlatObjectMetadataMaps: flatObjectMetadataMapsWithImpactedObject,
toFlatObjectMetadataMaps,
inferDeletionFromMissingObjectFieldIndex: true,
workspaceId,
});
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
return;
}
private computeOtherFlatObjectMetadataMapsToValidate({
flatFieldMetadataToCreate,
flatFieldMetadatasToCreate,
@@ -107,15 +186,17 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
});
}
async createMany(
fieldMetadataInputs: CreateFieldInput[],
): Promise<FieldMetadataEntity[]> {
async createMany({
fieldMetadataInputs,
workspaceId,
}: {
fieldMetadataInputs: Omit<CreateFieldInput, 'workspaceId'>[];
workspaceId: string;
}): Promise<FieldMetadataEntity[]> {
if (!fieldMetadataInputs.length) {
return [];
}
const workspaceId = fieldMetadataInputs[0].workspaceId;
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
{ workspaceId },
@@ -125,8 +206,9 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
await Promise.all(
fieldMetadataInputs.map(
async (fieldMetadataInput) =>
await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({
await fromCreateFieldInputToFlatFieldMetadatasToCreate({
existingFlatObjectMetadataMaps,
workspaceId,
rawCreateFieldInput: fieldMetadataInput,
}),
),
@@ -192,33 +274,38 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
);
try {
const fromImpactedFlatObjectMetadataMaps =
const fromFlatObjectMetadataMaps =
extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
objectMetadataIds: impactedObjectMetadataIds,
});
const toImpactedFlatObjectMetadataMaps =
const toFlatObjectMetadataMaps =
extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
objectMetadataIds: impactedObjectMetadataIds,
});
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
objectMetadataFromToInputs: {
from: fromFlatObjectMetadataMapsToFlatObjectMetadatas(
fromImpactedFlatObjectMetadataMaps,
),
to: fromFlatObjectMetadataMapsToFlatObjectMetadatas(
toImpactedFlatObjectMetadataMaps,
),
},
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
inferDeletionFromMissingObjectFieldIndex: false,
workspaceId,
});
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
// TODO refactor once the runner has been refactored to return created entities
return [];
// In the best of the world could consume runner returned value instead of searching in db here
return this.fieldMetadataRepository.find({
where: {
name: In(
fieldMetadataInputs.map((flatFieldMetadata) =>
trimAndRemoveDuplicatedWhitespacesFromString(
flatFieldMetadata.name,
),
),
),
workspaceId,
},
});
} catch {
// TODO prastoin We should pass the internal error here
throw new FieldMetadataException(
@@ -7,10 +7,10 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
DataSource,
type FindOneOptions,
In,
type QueryRunner,
Repository,
type FindOneOptions,
type QueryRunner,
} from 'typeorm';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
@@ -58,9 +58,9 @@ import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/wor
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import {
WorkspaceMigrationColumnActionType,
WorkspaceMigrationTableActionType,
type WorkspaceMigrationColumnDrop,
type WorkspaceMigrationTableAction,
WorkspaceMigrationTableActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
@@ -111,7 +111,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
);
if (isWorkspaceMigrationV2Enabled) {
return this.fieldMetadataServiceV2.createOne(fieldMetadataInput);
return this.fieldMetadataServiceV2.createOne({
fieldMetadataInput,
workspaceId: fieldMetadataInput.workspaceId,
});
}
const [createdFieldMetadata] = await this.createMany([fieldMetadataInput]);
@@ -589,7 +592,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
);
if (isWorkspaceMigrationV2Enabled) {
return this.fieldMetadataServiceV2.createMany(fieldMetadataInputs);
return this.fieldMetadataServiceV2.createMany({
fieldMetadataInputs,
workspaceId,
});
}
const { objectMetadataMaps } =
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { type FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -38,6 +39,65 @@ export class FlatFieldMetadataValidatorService {
private readonly flatFieldMetadataTypeValidatorService: FlatFieldMetadataTypeValidatorService,
) {}
validateFlatFieldMetadataDeletion({
existingFlatObjectMetadataMaps,
flatFieldMetadataToDelete,
}: {
flatFieldMetadataToDelete: FlatFieldMetadata;
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
}): FailedFlatFieldMetadataValidationExceptions[] {
const errors: FailedFlatFieldMetadataValidationExceptions[] = [];
const flatObjectMetadataWithFieldMaps =
existingFlatObjectMetadataMaps.byId[
flatFieldMetadataToDelete.objectMetadataId
];
if (!isDefined(flatObjectMetadataWithFieldMaps)) {
errors.push(
new FieldMetadataException(
'field to delete object metadata not found',
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
),
);
} else {
if (
flatObjectMetadataWithFieldMaps.labelIdentifierFieldMetadataId ===
flatFieldMetadataToDelete.id
) {
errors.push(
new FieldMetadataException(
'Cannot delete, please update the label identifier field first',
FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
{
userFriendlyMessage: t`Cannot delete, please update the label identifier field first`,
},
),
);
}
}
if (!flatFieldMetadataToDelete.isCustom) {
errors.push(
new FieldMetadataException(
"Standard Fields can't be deleted",
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
),
);
}
if (flatFieldMetadataToDelete.isActive) {
errors.push(
new FieldMetadataException(
"Active fields can't be deleted",
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
),
);
}
return errors;
}
async validateOneFlatFieldMetadata<
T extends FieldMetadataType = FieldMetadataType,
>({
@@ -21,12 +21,14 @@ import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-fi
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
type FromCreateFieldInputToFlatObjectMetadata = {
rawCreateFieldInput: CreateFieldInput;
rawCreateFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
workspaceId: string;
};
export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({
export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
rawCreateFieldInput,
workspaceId,
existingFlatObjectMetadataMaps,
}: FromCreateFieldInputToFlatObjectMetadata): Promise<FlatFieldMetadata[]> => {
if (rawCreateFieldInput.isRemoteCreation) {
@@ -57,11 +59,13 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({
const fieldMetadataId = v4();
const commonFlatFieldMetadata = getDefaultFlatFieldMetadata({
createFieldInput,
workspaceId,
fieldMetadataId,
});
switch (createFieldInput.type) {
case FieldMetadataType.MORPH_RELATION: {
// TODO prastoin
throw new UserInputError(
'Morph relation feature is not migrated to workspace migration v2 yet',
);
@@ -71,6 +75,7 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({
existingFlatObjectMetadataMaps,
sourceParentFlatObjectMetadata: parentFlatObjectMetadata,
createFieldInput,
workspaceId,
});
}
case FieldMetadataType.RATING: {
@@ -0,0 +1,86 @@
import { FieldMetadataType } from 'twenty-shared/types';
import {
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util';
type FromDeleteFieldInputToFlatFieldMetadatasToDeleteArgs = {
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
deleteOneFieldInput: DeleteOneFieldInput;
};
export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
existingFlatObjectMetadataMaps,
deleteOneFieldInput: rawDeleteOneInput,
}: FromDeleteFieldInputToFlatFieldMetadatasToDeleteArgs): FlatFieldMetadata[] => {
const { id: fieldMetadataToDeleteId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawDeleteOneInput,
['id'],
);
const flatFieldMetadataToDelete =
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
fieldMetadataId: fieldMetadataToDeleteId,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
});
if (!isDefined(flatFieldMetadataToDelete)) {
throw new FieldMetadataException(
'Field to delete not found',
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
);
}
if (
isFlatFieldMetadataEntityOfType(
flatFieldMetadataToDelete,
FieldMetadataType.RELATION,
)
) {
const { relationTargetFieldMetadataId, relationTargetObjectMetadataId } =
flatFieldMetadataToDelete;
const relatedFlatObjectMetadata =
existingFlatObjectMetadataMaps.byId[relationTargetObjectMetadataId];
if (!isDefined(relatedFlatObjectMetadata)) {
throw new FieldMetadataException(
`Deleted field metadata relation object metadata target not found`,
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
const relatedFlatFieldMetadata =
relatedFlatObjectMetadata.fieldsById[relationTargetFieldMetadataId];
if (!isDefined(relatedFlatFieldMetadata)) {
throw new FieldMetadataException(
`Deleted field metadata relation field metadata target not found`,
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
);
}
return [flatFieldMetadataToDelete, relatedFlatFieldMetadata];
}
if (
isFlatFieldMetadataEntityOfType(
flatFieldMetadataToDelete,
FieldMetadataType.MORPH_RELATION,
)
) {
// TODO prastoin
return [flatFieldMetadataToDelete];
}
return [flatFieldMetadataToDelete];
};
@@ -39,14 +39,16 @@ const computeFieldMetadataRelationSettingsForRelationType = ({
};
type FromRelationCreateFieldInputToFlatFieldMetadataArgs = {
createFieldInput: CreateFieldInput;
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
sourceParentFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
};
export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
existingFlatObjectMetadataMaps,
sourceParentFlatObjectMetadata,
createFieldInput,
workspaceId,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FlatFieldMetadata[]
> => {
@@ -85,6 +87,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
> = {
...getDefaultFlatFieldMetadata({
createFieldInput,
workspaceId,
fieldMetadataId: sourceRelationTargetFieldMetadataId,
}),
icon: createFieldInput.icon ?? 'IconRelationOneToMany',
@@ -105,7 +108,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
)}Id`,
objectMetadataId: targetParentFlatObjectMetadata.id,
type: FieldMetadataType.RELATION,
workspaceId: createFieldInput.workspaceId,
workspaceId,
};
const targetFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
@@ -119,6 +122,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
{
...getDefaultFlatFieldMetadata({
createFieldInput: targetCreateFieldInput,
workspaceId,
fieldMetadataId: targetRelationTargetFieldMetadataId,
}),
type: FieldMetadataType.RELATION,
@@ -6,11 +6,13 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m
type GetDefaultFlatFieldMetadataArgs = {
fieldMetadataId: string;
createFieldInput: CreateFieldInput;
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
};
export const getDefaultFlatFieldMetadata = ({
createFieldInput,
fieldMetadataId,
workspaceId,
}: GetDefaultFlatFieldMetadataArgs) => {
const { defaultValue, settings } = extractAndSanitizeObjectStringFields(
createFieldInput,
@@ -40,7 +42,7 @@ export const getDefaultFlatFieldMetadata = ({
standardOverrides: null,
type: createFieldInput.type,
uniqueIdentifier: fieldMetadataId,
workspaceId: createFieldInput.workspaceId,
workspaceId,
flatRelationTargetFieldMetadata: null,
flatRelationTargetObjectMetadata: null,
options: null,
@@ -0,0 +1,6 @@
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
export const EMPTY_FLAT_OBJECT_METADATA_MAPS = {
byId: {},
idByNameSingular: {},
} as const satisfies FlatObjectMetadataMaps;
@@ -1,5 +1,6 @@
import { isDefined } from 'twenty-shared/utils';
import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
@@ -17,11 +18,6 @@ export const extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow =
flatObjectMetadataMaps: sourceFlatObjectMetadataMaps,
objectMetadataIds,
}: ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs): FlatObjectMetadataMaps => {
const emptyFlatObjectMetadataMaps: FlatObjectMetadataMaps = {
byId: {},
idByNameSingular: {},
};
return objectMetadataIds.reduce(
(flatObjectMetadataMaps, objectMetadataId) => {
const flatObjectMetadataWithFlatFieldMaps =
@@ -41,6 +37,6 @@ export const extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow =
},
);
},
emptyFlatObjectMetadataMaps,
EMPTY_FLAT_OBJECT_METADATA_MAPS,
);
};
@@ -0,0 +1,32 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
export type FindFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldIdArgs = {
fieldMetadataId: string;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
};
// TODO add tests
export const findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId = ({
flatObjectMetadataMaps,
fieldMetadataId,
}: FindFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldIdArgs):
| FlatFieldMetadata
| undefined => {
const flatObjectMetadataWithFlatFieldMapsArray = Object.values(
flatObjectMetadataMaps.byId,
).filter(isDefined);
const matchingFlatObjectMetadataWithFlatFieldMaps =
flatObjectMetadataWithFlatFieldMapsArray.find(
(flatObjectMetadataWithFlatFieldMaps) =>
isDefined(
flatObjectMetadataWithFlatFieldMaps.fieldsById[fieldMetadataId],
),
);
return matchingFlatObjectMetadataWithFlatFieldMaps?.fieldsById[
fieldMetadataId
];
};
@@ -8,7 +8,6 @@ export const fromFlatObjectMetadataToFlatObjectMetadataWithFlatFieldMaps = (
const emptyFlatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps =
{
...flatObjectMetadata,
flatIndexMetadatas: [],
flatFieldMetadatas: [],
fieldIdByJoinColumnName: {},
fieldIdByName: {},
@@ -1,21 +1,16 @@
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant';
import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const fromFlatObjectMetadatasToFlatObjectMetadataMaps = (
flatObjectMetadatas: FlatObjectMetadata[],
) => {
const emptyFlatObjectMetadataMaps: FlatObjectMetadataMaps = {
byId: {},
idByNameSingular: {},
};
return flatObjectMetadatas.reduce(
(flatObjectMetadataMaps, flatObjectMetadata) =>
addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({
flatObjectMetadata,
flatObjectMetadataMaps,
}),
emptyFlatObjectMetadataMaps,
EMPTY_FLAT_OBJECT_METADATA_MAPS,
);
};
@@ -4,17 +4,12 @@ import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { Repository } from 'typeorm';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { fromCreateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util';
import { fromFlatObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-maps-to-flat-object-metadatas.util';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
import { ObjectMetadataEntity } from './object-metadata.entity';
import { type CreateObjectInput } from './dtos/create-object.input';
@Injectable()
export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataEntity> {
constructor(
@@ -27,43 +22,43 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
super(objectMetadataRepository);
}
override async createOne(
objectMetadataInput: CreateObjectInput,
): Promise<ObjectMetadataEntity> {
const { flatObjectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
{
workspaceId: objectMetadataInput.workspaceId,
},
);
// override async createOne(
// objectMetadataInput: CreateObjectInput,
// ): Promise<ObjectMetadataEntity> {
// const { flatObjectMetadataMaps } =
// await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
// {
// workspaceId: objectMetadataInput.workspaceId,
// },
// );
const createdRawFlatObjectMetadata =
fromCreateObjectInputToFlatObjectMetadata(objectMetadataInput);
const existingFlatObjectMetadatas: FlatObjectMetadataMaps = {
byId: {},
idByNameSingular: {},
};
// @ts-expect-error TODO implement validateFlatObjectMetadata
const createdFlatObjectMetadata = validateFlatObjectMetadata({
existing:
// Here we assume that EVERYTHING is in cache and up to date, this is very critical, also race condition prone :thinking:
fromFlatObjectMetadataMapsToFlatObjectMetadatas(flatObjectMetadataMaps),
toValidate: [createdRawFlatObjectMetadata],
});
// const createdRawFlatObjectMetadata =
// fromCreateObjectInputToFlatObjectMetadata(objectMetadataInput);
// const existingFlatObjectMetadatas: FlatObjectMetadataMaps = {
// byId: {},
// idByNameSingular: {},
// };
// // @ts-expect-error TODO implement validateFlatObjectMetadata
// const createdFlatObjectMetadata = validateFlatObjectMetadata({
// existing:
// // Here we assume that EVERYTHING is in cache and up to date, this is very critical, also race condition prone :thinking:
// fromFlatObjectMetadataMapsToFlatObjectMetadatas(flatObjectMetadataMaps),
// toValidate: [createdRawFlatObjectMetadata],
// });
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
objectMetadataFromToInputs: {
from: fromFlatObjectMetadataMapsToFlatObjectMetadatas(
existingFlatObjectMetadatas,
),
to: [createdFlatObjectMetadata],
},
inferDeletionFromMissingObjectFieldIndex: false,
workspaceId: objectMetadataInput.workspaceId,
});
// const workspaceMigration = this.workspaceMigrationBuilderV2.build({
// objectMetadataFromToInputs: {
// from: fromFlatObjectMetadataMapsToFlatObjectMetadatas(
// existingFlatObjectMetadatas,
// ),
// to: [createdFlatObjectMetadata],
// },
// inferDeletionFromMissingObjectFieldIndex: false,
// workspaceId: objectMetadataInput.workspaceId,
// });
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
// await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
return createdFlatObjectMetadata; // TODO retrieve from cache
}
// return createdFlatObjectMetadata; // TODO retrieve from cache
// }
}
@@ -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();