Handle input transpilation morph relation v2 (#14124)
# Introduction From `morphRelationCreationPayload` to `flatFieldMetadatas` close https://github.com/twentyhq/core-team-issues/issues/1406 ## Unit testing I'm not a fan of covering things this way, where we could have some strong integration testing tests in the first place But I wanted to freeze the optimistic path computation, also covered the exception because it was cheap, doesn't mean I won't cover them through integration tests later anw
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
||||
import { type RelationCreationPayload } from 'twenty-shared/types';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'class-validator';
|
||||
|
||||
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import {
|
||||
type FailedFieldInputTranspilation,
|
||||
type SuccessfulFieldInputTranspilation,
|
||||
type FieldInputTranspilationResult,
|
||||
} from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
|
||||
import { validateRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
type RelationCreationPayloadAndObjectMetadata = {
|
||||
relationCreationPayload: RelationCreationPayload;
|
||||
targetFlatObjectMetadata: FlatObjectMetadata;
|
||||
};
|
||||
|
||||
type ValidateMorphRelationCreationPayloadUtilArgs = {
|
||||
morphRelationCreationPayload: RelationCreationPayload[];
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
};
|
||||
export const validateMorphRelationCreationPayload = async ({
|
||||
existingFlatObjectMetadataMaps,
|
||||
morphRelationCreationPayload,
|
||||
}: ValidateMorphRelationCreationPayloadUtilArgs): Promise<
|
||||
FieldInputTranspilationResult<RelationCreationPayloadAndObjectMetadata[]>
|
||||
> => {
|
||||
const allRelationType = [
|
||||
...new Set(
|
||||
morphRelationCreationPayload.map(
|
||||
(relationCreationPayload) => relationCreationPayload.type,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
if (allRelationType.length > 1) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: {
|
||||
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||
message: 'Morph relation relations must have the same relation type',
|
||||
userFriendlyMessage: t`Morph relation relations must have the same relation type`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const allRelatedObjectMetadataIds = morphRelationCreationPayload.map(
|
||||
(relationCreationPayload) => relationCreationPayload.targetObjectMetadataId,
|
||||
);
|
||||
const allRelatedObjectMetadataIdsSet = [
|
||||
...new Set(allRelatedObjectMetadataIds),
|
||||
];
|
||||
|
||||
if (
|
||||
allRelatedObjectMetadataIds.length !== allRelatedObjectMetadataIdsSet.length
|
||||
) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: {
|
||||
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||
message:
|
||||
'Morph relation relations must have only relation to the same object metadata',
|
||||
userFriendlyMessage: t`Morph relation relations must have only relation to the same object metadata`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const relationCreationPayloadReport: {
|
||||
success: SuccessfulFieldInputTranspilation<RelationCreationPayloadAndObjectMetadata>[];
|
||||
failed: FailedFieldInputTranspilation[];
|
||||
} = {
|
||||
success: [],
|
||||
failed: [],
|
||||
};
|
||||
|
||||
for (const rawRelationCreationPayload of morphRelationCreationPayload) {
|
||||
const relationValidationResult = await validateRelationCreationPayload({
|
||||
existingFlatObjectMetadataMaps,
|
||||
relationCreationPayload: rawRelationCreationPayload,
|
||||
});
|
||||
|
||||
if (relationValidationResult.status === 'fail') {
|
||||
relationCreationPayloadReport.failed.push(relationValidationResult);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { relationCreationPayload, targetFlatObjectMetadata } =
|
||||
relationValidationResult.result;
|
||||
|
||||
relationCreationPayloadReport.success.push({
|
||||
status: 'success',
|
||||
result: {
|
||||
relationCreationPayload,
|
||||
targetFlatObjectMetadata,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (relationCreationPayloadReport.failed.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: {
|
||||
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||
message: 'Morph relation input transpilation failed',
|
||||
userFriendlyMessage: t`Invalid morph relation input`,
|
||||
value: relationCreationPayloadReport.failed
|
||||
.map((failedTranspilation) => failedTranspilation.error.value)
|
||||
.filter(isDefined),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
result: relationCreationPayloadReport.success.map(
|
||||
(relationCreationPayloadValidation) =>
|
||||
relationCreationPayloadValidation.result,
|
||||
),
|
||||
};
|
||||
};
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { type RelationCreationPayload } from 'twenty-shared/types';
|
||||
import {
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload-or-throw.util';
|
||||
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
|
||||
|
||||
type ValidateRelationCreationPayloadUtilArgs = {
|
||||
relationCreationPayload: RelationCreationPayload;
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
};
|
||||
export const validateRelationCreationPayload = async ({
|
||||
existingFlatObjectMetadataMaps,
|
||||
relationCreationPayload: rawRelationCreationPayload,
|
||||
}: ValidateRelationCreationPayloadUtilArgs): Promise<
|
||||
FieldInputTranspilationResult<{
|
||||
relationCreationPayload: RelationCreationPayload;
|
||||
targetFlatObjectMetadata: FlatObjectMetadata;
|
||||
}>
|
||||
> => {
|
||||
const relationCreationPayload =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawRelationCreationPayload,
|
||||
['targetFieldIcon', 'targetFieldLabel', 'targetObjectMetadataId', 'type'],
|
||||
);
|
||||
|
||||
try {
|
||||
await validateRelationCreationPayloadOrThrow(relationCreationPayload);
|
||||
} catch (error) {
|
||||
if (error instanceof FieldMetadataException) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: {
|
||||
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||
message: `Relation creation payload is invalid ${JSON.stringify(relationCreationPayload)}`,
|
||||
userFriendlyMessage: t`Invalid relation creation payload`,
|
||||
value: relationCreationPayload,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const targetFlatObjectMetadataWithFlatFieldMaps =
|
||||
existingFlatObjectMetadataMaps.byId[
|
||||
relationCreationPayload.targetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(targetFlatObjectMetadataWithFlatFieldMaps)) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: {
|
||||
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||
message: `Object metadata relation target not found for relation creation payload`,
|
||||
userFriendlyMessage: t`Object targeted by field to create not found`,
|
||||
value: relationCreationPayload,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
result: {
|
||||
relationCreationPayload,
|
||||
targetFlatObjectMetadata:
|
||||
fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata(
|
||||
targetFlatObjectMetadataWithFlatFieldMaps,
|
||||
),
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user