Files
twenty/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
T
Weiko 6aca1dd013 Introducing view field group syncable entity (#17867)
## Context
Introduces a new viewFieldGroup entity that allows grouping view fields
into sections (e.g. "General", "Additional", "Other") within a view.

The page layout fields widget needs a way to organize fields into
sections. Today, views have no concept of field grouping. This PR
introduces the viewFieldGroup entity which sits between a view and its
viewFields, enabling section-based organization.

<img width="401" height="724" alt="Layout - V2 (customize visibility)"
src="https://github.com/user-attachments/assets/6376e2ab-44db-42bf-9d2c-758f56f6b548"
/>

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-02-11 22:02:58 +01:00

131 lines
4.3 KiB
TypeScript

import { type ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { classifyMetadataError } from '@/metadata-error-handler/utils/classify-metadata-error.util';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import {
type AllMetadataName,
WorkspaceMigrationV2ExceptionCode,
} from 'twenty-shared/metadata';
import { CrudOperationType } from 'twenty-shared/types';
export const useMetadataErrorHandler = () => {
const { enqueueErrorSnackBar } = useSnackBar();
const TRANSLATED_OPERATION_TYPE = {
[CrudOperationType.CREATE]: t`create`,
[CrudOperationType.UPDATE]: t`update`,
[CrudOperationType.DELETE]: t`delete`,
[CrudOperationType.RESTORE]: t`restore`,
[CrudOperationType.DESTROY]: t`destroy`,
} as const satisfies Record<CrudOperationType, string>;
const TRANSLATED_METADATA_NAME = {
objectMetadata: t`object`,
fieldMetadata: t`field`,
view: t`view`,
viewField: t`view field`,
viewFieldGroup: t`view field group`,
viewGroup: t`view group`,
viewFilter: t`view filter`,
index: t`index`,
logicFunction: t`logic function`,
role: t`role`,
roleTarget: t`role target`,
agent: t`agent`,
skill: t`skill`,
pageLayout: t`page layout`,
pageLayoutTab: t`page layout tab`,
pageLayoutWidget: t`page layout widget`,
rowLevelPermissionPredicate: t`row level permission predicate`,
rowLevelPermissionPredicateGroup: t`row level permission predicate group`,
viewFilterGroup: t`view filter group`,
commandMenuItem: t`command menu item`,
frontComponent: t`front component`,
navigationMenuItem: t`navigation menu item`,
webhook: t`webhook`,
} as const satisfies Record<AllMetadataName, string>;
const handleMetadataError = (
error: ApolloError,
options: {
primaryMetadataName: AllMetadataName;
operationType: CrudOperationType;
},
) => {
const classification = classifyMetadataError({
error,
primaryMetadataName: options.primaryMetadataName,
});
const translatedMetadataName =
TRANSLATED_METADATA_NAME[options.primaryMetadataName];
switch (classification.type) {
case 'v1':
enqueueErrorSnackBar({ apolloError: classification.error });
break;
case 'v2-validation': {
const { extensions, primaryMetadataName, relatedFailingMetadataNames } =
classification;
const targetErrors = extensions.errors[primaryMetadataName] ?? [];
if (targetErrors.length > 0) {
targetErrors.forEach((entityError) => {
entityError.errors.forEach((validationError) =>
enqueueErrorSnackBar({
message:
validationError.userFriendlyMessage ??
validationError.message,
}),
);
});
}
const translatedOperationType =
TRANSLATED_OPERATION_TYPE[options.operationType];
if (
targetErrors.length === 0 &&
relatedFailingMetadataNames.length > 0
) {
const relatedEntityNames = relatedFailingMetadataNames
.map((metadataName) => TRANSLATED_METADATA_NAME[metadataName])
.join(', ');
enqueueErrorSnackBar({
message: t`Failed to ${translatedOperationType} ${translatedMetadataName}. Related ${relatedEntityNames} validation failed. Please check your configuration and try again.`,
});
}
if (
targetErrors.length === 0 &&
relatedFailingMetadataNames.length === 0
) {
enqueueErrorSnackBar({
message: t`Failed to ${translatedOperationType} ${translatedMetadataName}. Please try again.`,
});
}
break;
}
case 'v2-internal': {
const { code } = classification;
const errorMessage =
code ===
WorkspaceMigrationV2ExceptionCode.BUILDER_INTERNAL_SERVER_ERROR
? t`An internal error occurred while validating your changes. Please contact support.`
: t`An internal error occurred while applying your changes. Please contact support and try again later.`;
enqueueErrorSnackBar({ message: errorMessage });
break;
}
}
};
return {
handleMetadataError,
};
};