Refactor twenty-front metadata api services for v2 (#15360)
# Introduction Please first review this PR initial base https://github.com/twentyhq/twenty/pull/15358 In a nutshell refactored the frontend fetchers to display v2 errors format smoothly Please note that the v2 now finished the whole validation and does fail fast anymore ( summary is hardcoded for the moment ) ```json [ { "extensions": { "code": "BAD_USER_INPUT", "errors": { "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ { "errors": [ { "code": "INVALID_FIELD_INPUT", "message": "Default value should be as quoted string", "value": "", }, { "code": "INVALID_FIELD_INPUT", "message": "Default value "" must be one of the option values", "value": "", }, ], "flatEntityMinimalInformation": { "id": Any<String>, "name": "testField", "objectMetadataId": Any<String>, }, "status": "fail", "type": "create_field", }, ], "index": [], "objectMetadata": [], "routeTrigger": [], "serverlessFunction": [], "view": [], "viewField": [], "viewFilter": [], "viewGroup": [], }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, "invalidIndex": 0, "invalidObjectMetadata": 0, "invalidRouteTrigger": 0, "invalidServerlessFunction": 0, "invalidView": 0, "invalidViewField": 0, "invalidViewFilter": 0, "invalidViewGroup": 0, "totalErrors": 0, }, "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, "message": "Multiple validation errors occurred while creating fields", "name": "GraphQLError", }, ] ``` ## What's done - `usePersistView` tool ( CRUD ) - renamed `usePersistViewX` tools accordingly ( no more records or core ) - Now catching a lot of before unhandled exceptions - refactored each services to handle their own exception handlers and return either the response or the error within a discriminated union record ## Result ### Primary entity error When performing an metadata operation on a given metadata, if validation errors occurs we will display each of them in a toast Here while creating an object metadata. <img width="700" height="327" alt="image" src="https://github.com/user-attachments/assets/0c33d13c-c66c-4749-af36-b253abd3449b" /> ### Related entity error Still while creating an object <img width="700" height="327" alt="image" src="https://github.com/user-attachments/assets/52607788-c4e9-470c-ac8c-23437345ee5c" /> ### Translated <img width="700" height="327" alt="image" src="https://github.com/user-attachments/assets/a7198c20-ae82-47a6-910c-761de9594672" /> ## Conclusion This PR is an extract of https://github.com/twentyhq/twenty/pull/15331 close https://github.com/twentyhq/core-team-issues/issues/1776 ## Notes - Not refactor around triggers services as they're not consumed directly by any frontend services
This commit is contained in:
+82
@@ -0,0 +1,82 @@
|
||||
import { type ApolloError } from '@apollo/client';
|
||||
import {
|
||||
type AllMetadataName,
|
||||
type MetadataValidationErrorResponse,
|
||||
WorkspaceMigrationV2ExceptionCode,
|
||||
} from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export type MetadataErrorClassification =
|
||||
| { type: 'v1'; error: ApolloError }
|
||||
| {
|
||||
type: 'v2-validation';
|
||||
extensions: MetadataValidationErrorResponse;
|
||||
primaryMetadataName: AllMetadataName;
|
||||
relatedFailingMetadataNames: AllMetadataName[];
|
||||
}
|
||||
| {
|
||||
type: 'v2-internal';
|
||||
code: WorkspaceMigrationV2ExceptionCode;
|
||||
message: string;
|
||||
};
|
||||
|
||||
const isMetadataValidationError = (
|
||||
extensions: Record<string, unknown>,
|
||||
): extensions is MetadataValidationErrorResponse =>
|
||||
extensions.code === 'METADATA_VALIDATION_FAILED';
|
||||
|
||||
const isMetadataInternalError = (
|
||||
extensions: Record<string, unknown>,
|
||||
): boolean => {
|
||||
return (
|
||||
isDefined(extensions) &&
|
||||
isDefined(extensions.subCode) &&
|
||||
(extensions.subCode ===
|
||||
WorkspaceMigrationV2ExceptionCode.BUILDER_INTERNAL_SERVER_ERROR ||
|
||||
extensions.subCode ===
|
||||
WorkspaceMigrationV2ExceptionCode.RUNNER_INTERNAL_SERVER_ERROR)
|
||||
);
|
||||
};
|
||||
|
||||
type ClassifyMetadataErrorArgs = {
|
||||
error: ApolloError;
|
||||
primaryMetadataName: AllMetadataName;
|
||||
};
|
||||
export const classifyMetadataError = ({
|
||||
error,
|
||||
primaryMetadataName,
|
||||
}: ClassifyMetadataErrorArgs): MetadataErrorClassification => {
|
||||
const extensions = error.graphQLErrors?.[0]?.extensions;
|
||||
|
||||
if (!isDefined(extensions)) {
|
||||
return { type: 'v1', error };
|
||||
}
|
||||
|
||||
if (isMetadataValidationError(extensions)) {
|
||||
const failingMetadataNames = Object.keys(extensions.errors) as [
|
||||
keyof MetadataValidationErrorResponse['errors'],
|
||||
];
|
||||
const relatedFailingMetadataNames = failingMetadataNames.filter(
|
||||
(metadataName) =>
|
||||
extensions.errors[metadataName].length > 0 &&
|
||||
metadataName !== primaryMetadataName,
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'v2-validation',
|
||||
extensions,
|
||||
primaryMetadataName,
|
||||
relatedFailingMetadataNames,
|
||||
};
|
||||
}
|
||||
|
||||
if (isMetadataInternalError(extensions)) {
|
||||
return {
|
||||
type: 'v2-internal',
|
||||
code: extensions.subCode as WorkspaceMigrationV2ExceptionCode,
|
||||
message: (extensions.userFriendlyMessage as string) || error.message,
|
||||
};
|
||||
}
|
||||
|
||||
return { type: 'v1', error };
|
||||
};
|
||||
Reference in New Issue
Block a user