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:
Paul Rastoin
2025-10-28 10:50:41 +01:00
committed by GitHub
parent 08e1fc8bca
commit 503a5029da
56 changed files with 2073 additions and 1136 deletions
@@ -1,15 +1,15 @@
import { useMutation } from '@apollo/client';
import {
type CreateObjectInput,
type CreateOneObjectMetadataItemMutation,
type CreateOneObjectMetadataItemMutationVariables,
useCreateOneObjectMetadataItemMutation,
} from '~/generated-metadata/graphql';
import { CREATE_ONE_OBJECT_METADATA_ITEM } from '../graphql/mutations';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
export const useCreateOneObjectMetadataItem = () => {
@@ -18,27 +18,52 @@ export const useCreateOneObjectMetadataItem = () => {
const { refreshCoreViewsByObjectMetadataId } =
useRefreshCoreViewsByObjectMetadataId();
const [mutate] = useMutation<
CreateOneObjectMetadataItemMutation,
CreateOneObjectMetadataItemMutationVariables
>(CREATE_ONE_OBJECT_METADATA_ITEM);
const [createOneObjectMetadataItemMutation] =
useCreateOneObjectMetadataItemMutation();
const createOneObjectMetadataItem = async (input: CreateObjectInput) => {
const createdObjectMetadata = await mutate({
variables: {
input: { object: input },
},
});
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
await refreshObjectMetadataItems();
const createOneObjectMetadataItem = async (
input: CreateObjectInput,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof createOneObjectMetadataItemMutation>>
>
> => {
try {
const createdObjectMetadata = await createOneObjectMetadataItemMutation({
variables: {
input: { object: input },
},
});
if (isDefined(createdObjectMetadata.data?.createOneObject?.id)) {
await refreshCoreViewsByObjectMetadataId(
createdObjectMetadata.data.createOneObject.id,
);
await refreshObjectMetadataItems();
if (isDefined(createdObjectMetadata.data?.createOneObject?.id)) {
await refreshCoreViewsByObjectMetadataId(
createdObjectMetadata.data.createOneObject.id,
);
}
return {
status: 'successful',
response: createdObjectMetadata,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'objectMetadata',
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
return createdObjectMetadata;
};
return {