[PAGE LAYOUTS] Add widgets validation (#16635)

- Add widget validation
- Remove 'None' option for primary axis group by
- Fix error message parsing by passing the operation type in
`useMetadataErrorHandler`
This commit is contained in:
Raphaël Bosi
2026-01-07 16:00:13 +01:00
committed by GitHub
parent 701a713042
commit 4faed25624
71 changed files with 4004 additions and 1220 deletions
@@ -1,6 +1,5 @@
import { type ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
import { classifyMetadataError } from '@/metadata-error-handler/utils/classify-metadata-error.util';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@@ -8,9 +7,19 @@ 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`,
@@ -35,85 +44,83 @@ export const useMetadataErrorHandler = () => {
viewFilterGroup: t`view filter group`,
} as const satisfies Record<AllMetadataName, string>;
const handleMetadataError = useCallback(
(
error: ApolloError,
options: {
primaryMetadataName: AllMetadataName;
},
) => {
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,
}),
);
});
}
if (
targetErrors.length === 0 &&
relatedFailingMetadataNames.length > 0
) {
const relatedEntityNames = relatedFailingMetadataNames
.map((metadataName) => TRANSLATED_METADATA_NAME[metadataName])
.join(', ');
enqueueErrorSnackBar({
message: t`Failed to create ${translatedMetadataName}. Related ${relatedEntityNames} validation failed. Please check your configuration and try again.`,
});
}
if (
targetErrors.length === 0 &&
relatedFailingMetadataNames.length === 0
) {
enqueueErrorSnackBar({
message: t`Failed to create ${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;
}
}
const handleMetadataError = (
error: ApolloError,
options: {
primaryMetadataName: AllMetadataName;
operationType: CrudOperationType;
},
[enqueueErrorSnackBar, TRANSLATED_METADATA_NAME],
);
) => {
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,