Morph relation field v2 fixes (#14216)
Fixes, depending on the contextual current operation in order to retrieve relation target and all morph related fields to be delete: - DELETE Field, - DELETE Object, - UPDATE Field, that affects all others related fields
This commit is contained in:
+30
-13
@@ -83,10 +83,14 @@ export class FieldMetadataServiceV2 {
|
||||
const flatObjectMetadataMapsWithImpactedObject =
|
||||
getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: flatFieldMetadatasToDelete.map(
|
||||
(flatFieldMetadataToDelete) =>
|
||||
flatFieldMetadataToDelete.objectMetadataId,
|
||||
),
|
||||
objectMetadataIds: [
|
||||
...new Set(
|
||||
flatFieldMetadatasToDelete.map(
|
||||
(flatFieldMetadataToDelete) =>
|
||||
flatFieldMetadataToDelete.objectMetadataId,
|
||||
),
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete.reduce(
|
||||
@@ -145,20 +149,33 @@ export class FieldMetadataServiceV2 {
|
||||
throw inputTranspilationResult.error;
|
||||
}
|
||||
|
||||
const optimisticiallyUpdatedFlatFieldMetadata =
|
||||
const optimisticallyUpdatedFlatFieldMetadatas =
|
||||
inputTranspilationResult.result;
|
||||
|
||||
const objectMetadataIdWithRelatedObjectMetadataIds = [
|
||||
...new Set(
|
||||
optimisticallyUpdatedFlatFieldMetadatas.flatMap(
|
||||
({ objectMetadataId, relationTargetObjectMetadataId }) =>
|
||||
isDefined(relationTargetObjectMetadataId)
|
||||
? [objectMetadataId, relationTargetObjectMetadataId]
|
||||
: [objectMetadataId],
|
||||
),
|
||||
),
|
||||
];
|
||||
const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: [
|
||||
optimisticiallyUpdatedFlatFieldMetadata.objectMetadataId,
|
||||
],
|
||||
objectMetadataIds: objectMetadataIdWithRelatedObjectMetadataIds,
|
||||
});
|
||||
|
||||
const toFlatObjectMetadataMaps =
|
||||
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: fromFlatObjectMetadataMaps,
|
||||
flatFieldMetadata: optimisticiallyUpdatedFlatFieldMetadata,
|
||||
});
|
||||
optimisticallyUpdatedFlatFieldMetadatas.reduce(
|
||||
(flatObjectMetadataMaps, flatFieldMetadata) =>
|
||||
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadata,
|
||||
}),
|
||||
fromFlatObjectMetadataMaps,
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
@@ -182,7 +199,7 @@ export class FieldMetadataServiceV2 {
|
||||
|
||||
return this.fieldMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
id: optimisticiallyUpdatedFlatFieldMetadata.id,
|
||||
id: optimisticallyUpdatedFlatFieldMetadatas[0].id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
+2
-2
@@ -133,7 +133,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
validationResult.errors.push(
|
||||
...validateFlatFieldMetadataName(updatedFlatFieldMetadata.name),
|
||||
...validateFlatFieldMetadataNameAvailability({
|
||||
name: updatedFlatFieldMetadata.name,
|
||||
flatFieldMetadata: updatedFlatFieldMetadata,
|
||||
flatObjectMetadata: flatObjectMetadata,
|
||||
}),
|
||||
);
|
||||
@@ -298,7 +298,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
|
||||
validationResult.errors.push(
|
||||
...validateFlatFieldMetadataNameAvailability({
|
||||
name: flatFieldMetadataToValidate.name,
|
||||
flatFieldMetadata: flatFieldMetadataToValidate,
|
||||
flatObjectMetadata: parentFlatObjectMetadata,
|
||||
}),
|
||||
);
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { findFlatFieldMetadatasRelatedToMorphRelationOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-flat-field-metadatas-related-to-morph-relation-or-throw.util';
|
||||
import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.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';
|
||||
|
||||
export const computeFlatFieldMetadataRelatedFlatFieldMetadata = ({
|
||||
flatFieldMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
}: {
|
||||
flatFieldMetadata: FlatFieldMetadata;
|
||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
}): FlatFieldMetadata[] => {
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
flatFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
)
|
||||
) {
|
||||
return [
|
||||
findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow({
|
||||
flatFieldMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
flatFieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
return findFlatFieldMetadatasRelatedToMorphRelationOrThrow({
|
||||
flatFieldMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
+4
@@ -62,6 +62,10 @@ export const findFlatFieldMetadatasRelatedToMorphRelationOrThrow = ({
|
||||
flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
if (flatFieldMetadata.id === morphRelationFlatFieldMetadata.id) {
|
||||
return [relationTargetFlatFieldMetadata];
|
||||
}
|
||||
|
||||
return [flatFieldMetadata, relationTargetFlatFieldMetadata];
|
||||
},
|
||||
);
|
||||
|
||||
+5
-29
@@ -1,4 +1,3 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
@@ -10,9 +9,7 @@ import {
|
||||
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 { findFlatFieldMetadatasRelatedToMorphRelationOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-flat-field-metadatas-related-to-morph-relation-or-throw.util';
|
||||
import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util';
|
||||
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.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';
|
||||
|
||||
@@ -43,32 +40,11 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
flatFieldMetadataToDelete,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
return findFlatFieldMetadatasRelatedToMorphRelationOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
const relatedFlatFieldMetadataToDelete =
|
||||
computeFlatFieldMetadataRelatedFlatFieldMetadata({
|
||||
flatFieldMetadata: flatFieldMetadataToDelete,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
flatFieldMetadataToDelete,
|
||||
FieldMetadataType.RELATION,
|
||||
)
|
||||
) {
|
||||
const relationTargetFlatFieldMetadata =
|
||||
findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadata: flatFieldMetadataToDelete,
|
||||
});
|
||||
|
||||
return [flatFieldMetadataToDelete, relationTargetFlatFieldMetadata];
|
||||
}
|
||||
|
||||
return [flatFieldMetadataToDelete];
|
||||
return [flatFieldMetadataToDelete, ...relatedFlatFieldMetadataToDelete];
|
||||
};
|
||||
|
||||
+65
-26
@@ -13,11 +13,36 @@ import { FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-m
|
||||
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
|
||||
import { type FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import {} from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
|
||||
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.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';
|
||||
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
|
||||
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
|
||||
|
||||
const applyUpdatesToFlatFieldMetadata = ({
|
||||
updatedEditableFieldProperties,
|
||||
flatFieldMetadata,
|
||||
}: {
|
||||
updatedEditableFieldProperties: SanitizedUpdateFieldInput;
|
||||
flatFieldMetadata: FlatFieldMetadata;
|
||||
}) => {
|
||||
return fieldMetadataEditableProperties.reduce((acc, property) => {
|
||||
let newValue = updatedEditableFieldProperties[property];
|
||||
|
||||
if (property === 'options' && isDefined(newValue)) {
|
||||
newValue = updatedEditableFieldProperties[property]?.map((option) => ({
|
||||
id: v4(),
|
||||
...option,
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
...(newValue !== undefined ? { [property]: newValue } : {}),
|
||||
};
|
||||
}, flatFieldMetadata);
|
||||
};
|
||||
|
||||
const fieldMetadataEditableProperties =
|
||||
FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE.filter(
|
||||
(
|
||||
@@ -28,6 +53,13 @@ const fieldMetadataEditableProperties =
|
||||
> => property !== 'standardOverrides',
|
||||
);
|
||||
|
||||
type SanitizedUpdateFieldInput = ReturnType<
|
||||
typeof extractAndSanitizeObjectStringFields<
|
||||
UpdateFieldInput,
|
||||
(typeof fieldMetadataEditableProperties)[number][]
|
||||
>
|
||||
>;
|
||||
|
||||
type FromUpdateFieldInputToFlatFieldMetadataArgs = {
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
updateFieldInput: UpdateFieldInput;
|
||||
@@ -35,7 +67,9 @@ type FromUpdateFieldInputToFlatFieldMetadataArgs = {
|
||||
export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
existingFlatObjectMetadataMaps,
|
||||
updateFieldInput: rawUpdateFieldInput,
|
||||
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult<FlatFieldMetadata> => {
|
||||
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult<
|
||||
FlatFieldMetadata[]
|
||||
> => {
|
||||
const updateFieldInputInformalProperties =
|
||||
extractAndSanitizeObjectStringFields(rawUpdateFieldInput, [
|
||||
'objectMetadataId',
|
||||
@@ -46,13 +80,13 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
fieldMetadataEditableProperties,
|
||||
);
|
||||
|
||||
const relatedFlatFieldMetadata =
|
||||
const existingFlatFieldMetadataToUpdate =
|
||||
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
|
||||
fieldMetadataId: updateFieldInputInformalProperties.id,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(relatedFlatFieldMetadata)) {
|
||||
if (!isDefined(existingFlatFieldMetadataToUpdate)) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: {
|
||||
@@ -65,7 +99,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
existingFlatObjectMetadataMaps.byId[
|
||||
relatedFlatFieldMetadata.objectMetadataId
|
||||
existingFlatFieldMetadataToUpdate.objectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
|
||||
@@ -79,7 +113,12 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
};
|
||||
}
|
||||
|
||||
if (flatObjectMetadataWithFlatFieldMaps.isRemote) {
|
||||
const flatObjectMetadata =
|
||||
fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata(
|
||||
flatObjectMetadataWithFlatFieldMaps,
|
||||
);
|
||||
|
||||
if (flatObjectMetadata.isRemote) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: {
|
||||
@@ -89,7 +128,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
};
|
||||
}
|
||||
|
||||
if (isStandardMetadata(relatedFlatFieldMetadata)) {
|
||||
if (isStandardMetadata(existingFlatFieldMetadataToUpdate)) {
|
||||
const invalidUpdatedProperties = Object.keys(
|
||||
updatedEditableFieldProperties,
|
||||
).filter((property) =>
|
||||
@@ -125,35 +164,35 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
}, relatedFlatFieldMetadata);
|
||||
}, existingFlatFieldMetadataToUpdate);
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
result: updatedStandardFlatFieldMetadata,
|
||||
result: [updatedStandardFlatFieldMetadata],
|
||||
};
|
||||
}
|
||||
|
||||
const updatedFlatFieldMetadata = fieldMetadataEditableProperties.reduce(
|
||||
(acc, property) => {
|
||||
let newValue = updatedEditableFieldProperties[property];
|
||||
const relatedFlatFieldMetadatasToUpdate =
|
||||
computeFlatFieldMetadataRelatedFlatFieldMetadata({
|
||||
flatFieldMetadata: existingFlatFieldMetadataToUpdate,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
if (property === 'options' && isDefined(newValue)) {
|
||||
newValue = updatedEditableFieldProperties[property]?.map((option) => ({
|
||||
id: v4(),
|
||||
...option,
|
||||
}));
|
||||
}
|
||||
const flatFieldMetadatasToUpdate = [
|
||||
existingFlatFieldMetadataToUpdate,
|
||||
...relatedFlatFieldMetadatasToUpdate,
|
||||
];
|
||||
|
||||
return {
|
||||
...acc,
|
||||
...(newValue !== undefined ? { [property]: newValue } : {}),
|
||||
};
|
||||
},
|
||||
relatedFlatFieldMetadata,
|
||||
);
|
||||
const optimisticiallyUpdatedFlatFieldMetadatas =
|
||||
flatFieldMetadatasToUpdate.map((flatFieldMetadata) => {
|
||||
return applyUpdatesToFlatFieldMetadata({
|
||||
flatFieldMetadata,
|
||||
updatedEditableFieldProperties,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
result: updatedFlatFieldMetadata,
|
||||
result: optimisticiallyUpdatedFlatFieldMetadatas,
|
||||
};
|
||||
};
|
||||
|
||||
+17
-11
@@ -6,6 +6,8 @@ import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-me
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
|
||||
|
||||
@@ -32,39 +34,43 @@ const getReservedCompositeFieldNames = (
|
||||
});
|
||||
};
|
||||
|
||||
// Should implement Morph relation nameObjectId col availability
|
||||
export const validateFlatFieldMetadataNameAvailability = ({
|
||||
name,
|
||||
flatFieldMetadata,
|
||||
flatObjectMetadata,
|
||||
}: {
|
||||
name: string;
|
||||
flatFieldMetadata: FlatFieldMetadata;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
}): FlatFieldMetadataValidationError[] => {
|
||||
const errors: FlatFieldMetadataValidationError[] = [];
|
||||
const reservedCompositeFieldsNames =
|
||||
getReservedCompositeFieldNames(flatObjectMetadata);
|
||||
|
||||
const flatFieldMetadataName = flatFieldMetadata.name;
|
||||
|
||||
if (
|
||||
!isMorphOrRelationFlatFieldMetadata(flatFieldMetadata) &&
|
||||
flatObjectMetadata.flatFieldMetadatas.some(
|
||||
(field) =>
|
||||
field.name === name ||
|
||||
field.name === flatFieldMetadataName ||
|
||||
(isMorphOrRelationFieldMetadataType(field.type) &&
|
||||
`${field.name}Id` === name),
|
||||
`${field.name}Id` === flatFieldMetadataName),
|
||||
)
|
||||
) {
|
||||
errors.push({
|
||||
code: FieldMetadataExceptionCode.NOT_AVAILABLE,
|
||||
value: name,
|
||||
message: `Name "${name}" is not available as it is already used by another field`,
|
||||
userFriendlyMessage: t`Name "${name}" is not available as it is already used by another field`,
|
||||
value: flatFieldMetadataName,
|
||||
message: `Name "${flatFieldMetadataName}" is not available as it is already used by another field`,
|
||||
userFriendlyMessage: t`Name "${flatFieldMetadataName}" is not available as it is already used by another field`,
|
||||
});
|
||||
}
|
||||
|
||||
if (reservedCompositeFieldsNames.includes(name)) {
|
||||
if (reservedCompositeFieldsNames.includes(flatFieldMetadataName)) {
|
||||
errors.push({
|
||||
code: FieldMetadataExceptionCode.RESERVED_KEYWORD,
|
||||
message: `Name "${name}" is reserved composite field name`,
|
||||
value: name,
|
||||
userFriendlyMessage: t`Name "${name}" is not available`,
|
||||
message: `Name "${flatFieldMetadataName}" is reserved composite field name`,
|
||||
value: flatFieldMetadataName,
|
||||
userFriendlyMessage: t`Name "${flatFieldMetadataName}" is not available`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+6
-21
@@ -1,12 +1,10 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util';
|
||||
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
@@ -48,26 +46,13 @@ export const fromDeleteObjectInputToFlatFieldMetadatasToDelete = ({
|
||||
const flatFieldMetadatasToDelete =
|
||||
flatObjectMetadataToDelete.flatFieldMetadatas.flatMap(
|
||||
(flatFieldMetadata) => {
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
const relatedFlatFieldMetadata =
|
||||
computeFlatFieldMetadataRelatedFlatFieldMetadata({
|
||||
flatFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
) ||
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
flatFieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
const relationTargetFlatFieldMetadata =
|
||||
findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadata,
|
||||
});
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
return [flatFieldMetadata, relationTargetFlatFieldMetadata];
|
||||
}
|
||||
|
||||
return flatFieldMetadata;
|
||||
return [flatFieldMetadata, ...relatedFlatFieldMetadata];
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import {
|
||||
eachTestingContextFilter,
|
||||
type EachTestingContext,
|
||||
} from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||
import { type RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto';
|
||||
import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
|
||||
describe('createOne FieldMetadataService morph relation fields', () => {
|
||||
let createdObjectMetadataPersonId: string;
|
||||
let createdObjectMetadataOpportunityId: string;
|
||||
let createdObjectMetadataCompanyId: string;
|
||||
let createdFieldMetadataId: string | undefined = undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
expectToFail: false,
|
||||
featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
value: true,
|
||||
workspaceId: SEED_APPLE_WORKSPACE_ID,
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataPersonId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'personForMorphRelationSecond',
|
||||
namePlural: 'peopleForMorphRelationSecond',
|
||||
labelSingular: 'Person For Morph Relation',
|
||||
labelPlural: 'People For Morph Relation',
|
||||
icon: 'IconPerson',
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataPersonId = objectMetadataPersonId;
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataCompanyId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'companyForMorphRelationSecond',
|
||||
namePlural: 'companiesForMorphRelationSecond',
|
||||
labelSingular: 'Company For Morph Relation',
|
||||
labelPlural: 'Companies For Morph Relation',
|
||||
icon: 'IconCompany',
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataCompanyId = objectMetadataCompanyId;
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataOpportunityId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'opportunityForMorphRelationSecond',
|
||||
namePlural: 'opportunitiesForMorphRelationSecond',
|
||||
labelSingular: 'Opportunity For Morph Relation',
|
||||
labelPlural: 'Opportunities For Morph Relation',
|
||||
icon: 'IconOpportunity',
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataOpportunityId = objectMetadataOpportunityId;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Promise.all(
|
||||
[
|
||||
createdObjectMetadataPersonId,
|
||||
createdObjectMetadataOpportunityId,
|
||||
createdObjectMetadataCompanyId,
|
||||
].map(
|
||||
async (objectMetadataId) =>
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: objectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
[
|
||||
createdObjectMetadataPersonId,
|
||||
createdObjectMetadataOpportunityId,
|
||||
createdObjectMetadataCompanyId,
|
||||
].map(
|
||||
async (objectMetadataId) =>
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: objectMetadataId },
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await updateFeatureFlag({
|
||||
expectToFail: false,
|
||||
featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
value: false,
|
||||
workspaceId: SEED_APPLE_WORKSPACE_ID,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!isDefined(createdFieldMetadataId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: {
|
||||
isActive: false,
|
||||
},
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
await deleteOneFieldMetadata({
|
||||
input: {
|
||||
idToDelete: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = undefined;
|
||||
});
|
||||
|
||||
type EachTestingContextArray = EachTestingContext<
|
||||
(args: {
|
||||
createdObjectMetadataPersonId: string;
|
||||
createdObjectMetadataOpportunityId: string;
|
||||
createdObjectMetadataCompanyId: string;
|
||||
}) => Omit<
|
||||
CreateFieldInput,
|
||||
'morphRelationsCreationPayload' | 'workspaceId'
|
||||
> &
|
||||
Required<Pick<CreateFieldInput, 'morphRelationsCreationPayload'>>
|
||||
>[];
|
||||
|
||||
const eachTestingContextArray: EachTestingContextArray = [
|
||||
{
|
||||
title: 'should create a MORPH_RELATION field type MANY_TO_ONE',
|
||||
context: ({
|
||||
createdObjectMetadataCompanyId,
|
||||
createdObjectMetadataOpportunityId,
|
||||
createdObjectMetadataPersonId,
|
||||
}) => ({
|
||||
label: 'field label',
|
||||
name: 'fieldName',
|
||||
objectMetadataId: createdObjectMetadataCompanyId,
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
morphRelationsCreationPayload: [
|
||||
{
|
||||
targetFieldIcon: 'Icon123',
|
||||
targetFieldLabel: 'toto',
|
||||
targetObjectMetadataId: createdObjectMetadataOpportunityId,
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
{
|
||||
targetFieldIcon: 'Icon123',
|
||||
targetFieldLabel: 'tata',
|
||||
targetObjectMetadataId: createdObjectMetadataPersonId,
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'should create a MORPH_RELATION field type ONE_TO_MANY',
|
||||
context: ({
|
||||
createdObjectMetadataCompanyId,
|
||||
createdObjectMetadataOpportunityId,
|
||||
createdObjectMetadataPersonId,
|
||||
}) => ({
|
||||
label: 'field label',
|
||||
name: 'fieldName',
|
||||
objectMetadataId: createdObjectMetadataCompanyId,
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
morphRelationsCreationPayload: [
|
||||
{
|
||||
targetFieldIcon: 'Icon123',
|
||||
targetFieldLabel: 'toto',
|
||||
targetObjectMetadataId: createdObjectMetadataOpportunityId,
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
{
|
||||
targetFieldIcon: 'Icon123',
|
||||
targetFieldLabel: 'tata',
|
||||
targetObjectMetadataId: createdObjectMetadataPersonId,
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
it.each(eachTestingContextFilter(eachTestingContextArray))(
|
||||
'$title',
|
||||
async ({ context }) => {
|
||||
const contextPayload = context({
|
||||
createdObjectMetadataOpportunityId,
|
||||
createdObjectMetadataPersonId,
|
||||
createdObjectMetadataCompanyId,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { createOneField: rawCreateOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
input: contextPayload,
|
||||
expectToFail: false,
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
settings
|
||||
object {
|
||||
id
|
||||
nameSingular
|
||||
}
|
||||
morphRelations {
|
||||
type
|
||||
targetFieldMetadata {
|
||||
id
|
||||
}
|
||||
targetObjectMetadata {
|
||||
id
|
||||
}
|
||||
sourceFieldMetadata {
|
||||
id
|
||||
}
|
||||
sourceObjectMetadata {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
const createOneField = rawCreateOneField as FieldMetadataDTO & {
|
||||
morphRelations: RelationDTO[];
|
||||
};
|
||||
|
||||
createdFieldMetadataId = createOneField.id;
|
||||
|
||||
const expectedMorphRelations =
|
||||
contextPayload.morphRelationsCreationPayload.map(
|
||||
({ targetObjectMetadataId, type }) => ({
|
||||
sourceFieldMetadata: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
sourceObjectMetadata: {
|
||||
id: contextPayload.objectMetadataId,
|
||||
},
|
||||
targetFieldMetadata: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
targetObjectMetadata: {
|
||||
id: targetObjectMetadataId,
|
||||
},
|
||||
type,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(createOneField.morphRelations).toMatchObject(
|
||||
expectedMorphRelations,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
|
||||
describe('updateOne FieldMetadataService morph relation fields v2', () => {
|
||||
let createdObjectMetadataPersonId: string;
|
||||
let createdObjectMetadataOpportunityId: string;
|
||||
let createdObjectMetadataCompanyId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
expectToFail: false,
|
||||
featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
value: true,
|
||||
workspaceId: SEED_APPLE_WORKSPACE_ID,
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataPersonId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'personForMorphRelationSecond',
|
||||
namePlural: 'peopleForMorphRelationSecond',
|
||||
labelSingular: 'Person For Morph Relation',
|
||||
labelPlural: 'People For Morph Relation',
|
||||
icon: 'IconPerson',
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataPersonId = objectMetadataPersonId;
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataCompanyId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'companyForMorphRelationSecond',
|
||||
namePlural: 'companiesForMorphRelationSecond',
|
||||
labelSingular: 'Company For Morph Relation',
|
||||
labelPlural: 'Companies For Morph Relation',
|
||||
icon: 'IconCompany',
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataCompanyId = objectMetadataCompanyId;
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataOpportunityId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'opportunityForMorphRelationSecond',
|
||||
namePlural: 'opportunitiesForMorphRelationSecond',
|
||||
labelSingular: 'Opportunity For Morph Relation',
|
||||
labelPlural: 'Opportunities For Morph Relation',
|
||||
icon: 'IconOpportunity',
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataOpportunityId = objectMetadataOpportunityId;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Promise.all(
|
||||
[
|
||||
createdObjectMetadataPersonId,
|
||||
createdObjectMetadataOpportunityId,
|
||||
createdObjectMetadataCompanyId,
|
||||
].map(
|
||||
async (objectMetadataId) =>
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: objectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
[
|
||||
createdObjectMetadataPersonId,
|
||||
createdObjectMetadataOpportunityId,
|
||||
createdObjectMetadataCompanyId,
|
||||
].map(
|
||||
async (objectMetadataId) =>
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: objectMetadataId },
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await updateFeatureFlag({
|
||||
expectToFail: false,
|
||||
featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
value: false,
|
||||
workspaceId: SEED_APPLE_WORKSPACE_ID,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {
|
||||
data: { createOneField: rawCreateOneField },
|
||||
} = await createOneFieldMetadata({
|
||||
input: {
|
||||
label: 'field label',
|
||||
name: 'fieldName',
|
||||
objectMetadataId: createdObjectMetadataCompanyId,
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
morphRelationsCreationPayload: [
|
||||
{
|
||||
targetFieldIcon: 'Icon123',
|
||||
targetFieldLabel: 'toto',
|
||||
targetObjectMetadataId: createdObjectMetadataOpportunityId,
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
{
|
||||
targetFieldIcon: 'Icon123',
|
||||
targetFieldLabel: 'tata',
|
||||
targetObjectMetadataId: createdObjectMetadataPersonId,
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
],
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = rawCreateOneField.id;
|
||||
});
|
||||
|
||||
it('It should update all morph related flat field metadata allowing its deletion', async () => {
|
||||
const input = {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: {
|
||||
isActive: false,
|
||||
label: 'new label',
|
||||
description: 'new description',
|
||||
},
|
||||
};
|
||||
const {
|
||||
data: { updateOneField },
|
||||
} = await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input,
|
||||
gqlFields: `
|
||||
id
|
||||
isActive
|
||||
description
|
||||
name
|
||||
label
|
||||
`,
|
||||
});
|
||||
|
||||
expect(updateOneField).toMatchObject(input.updatePayload);
|
||||
|
||||
await deleteOneFieldMetadata({
|
||||
input: {
|
||||
idToDelete: createdFieldMetadataId,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user