[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
@@ -21,9 +21,12 @@ export const SaveDashboardSingleRecordAction = () => {
const { closeCommandMenu } = useCommandMenu();
const handleClick = async () => {
await savePageLayout();
closeCommandMenu();
setIsPageLayoutInEditMode(false);
const result = await savePageLayout();
if (result.status === 'successful') {
closeCommandMenu();
setIsPageLayoutInEditMode(false);
}
};
return <Action onClick={handleClick} />;
@@ -101,6 +101,13 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
const { getIcon } = useIcons();
const isSecondaryAxisGroupBy =
fieldMetadataIdKey === 'secondaryAxisGroupByFieldMetadataId';
const selectableItemIdArray = isSecondaryAxisGroupBy
? ['none', ...availableFieldMetadataItems.map((item) => item.id)]
: availableFieldMetadataItems.map((item) => item.id);
if (!isDefined(sourceObjectMetadataItem)) {
return null;
}
@@ -227,19 +234,18 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
<SelectableList
selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={[
'none',
...availableFieldMetadataItems.map((item) => item.id),
]}
selectableItemIdArray={selectableItemIdArray}
>
<SelectableListItem itemId="none" onEnter={handleSelectNone}>
<MenuItemSelect
text={t`None`}
selected={!isDefined(currentGroupByFieldMetadataId)}
focused={selectedItemId === 'none'}
onClick={handleSelectNone}
/>
</SelectableListItem>
{isSecondaryAxisGroupBy && (
<SelectableListItem itemId="none" onEnter={handleSelectNone}>
<MenuItemSelect
text={t`None`}
selected={!isDefined(currentGroupByFieldMetadataId)}
focused={selectedItemId === 'none'}
onClick={handleSelectNone}
/>
</SelectableListItem>
)}
{availableFieldMetadataItems.map((fieldMetadataItem) => (
<SelectableListItem
@@ -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,
@@ -10,6 +10,7 @@ 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 { CrudOperationType } from 'twenty-shared/types';
export const useCreateOneFieldMetadataItem = () => {
const { refreshObjectMetadataItems } =
@@ -52,6 +53,7 @@ export const useCreateOneFieldMetadataItem = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'fieldMetadata',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -10,6 +10,7 @@ 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 { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const useCreateOneObjectMetadataItem = () => {
@@ -54,6 +55,7 @@ export const useCreateOneObjectMetadataItem = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'objectMetadata',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -13,6 +13,7 @@ import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
export const useDeleteOneFieldMetadataItem = () => {
const [deleteOneFieldMetadataItemMutation] =
@@ -80,6 +81,7 @@ export const useDeleteOneFieldMetadataItem = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'fieldMetadata',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -7,6 +7,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useRefreshAllCoreViews } from '@/views/hooks/useRefreshAllCoreViews';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
export const useDeleteOneObjectMetadataItem = () => {
const [deleteOneObjectMetadataItemMutation] =
@@ -45,6 +46,7 @@ export const useDeleteOneObjectMetadataItem = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'objectMetadata',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -10,6 +10,7 @@ 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 { CrudOperationType } from 'twenty-shared/types';
export const useUpdateOneFieldMetadataItem = () => {
const { refreshObjectMetadataItems } =
@@ -67,6 +68,7 @@ export const useUpdateOneFieldMetadataItem = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'fieldMetadata',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -10,6 +10,7 @@ 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 { CrudOperationType } from 'twenty-shared/types';
// TODO: Slice the Apollo store synchronously in the update function instead of subscribing, so we can use update after read in the same function call
export const useUpdateOneObjectMetadataItem = () => {
@@ -55,6 +56,7 @@ export const useUpdateOneObjectMetadataItem = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'objectMetadata',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -1,4 +1,5 @@
import { usePageLayoutDraftState } from '@/page-layout/hooks/usePageLayoutDraftState';
import { useUpdatePageLayoutWithTabsAndWidgets } from '@/page-layout/hooks/useUpdatePageLayoutWithTabsAndWidgets';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
@@ -10,7 +11,6 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useUpdatePageLayoutWithTabsAndWidgetsMutation } from '~/generated/graphql';
export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
@@ -30,8 +30,8 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
const { pageLayoutDraft } = usePageLayoutDraftState(pageLayoutId);
const [updatePageLayoutWithTabsAndWidgets] =
useUpdatePageLayoutWithTabsAndWidgetsMutation();
const { updatePageLayoutWithTabsAndWidgets } =
useUpdatePageLayoutWithTabsAndWidgets();
const savePageLayout = useRecoilCallback(
({ set }) =>
@@ -39,25 +39,28 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
const updateInput =
convertPageLayoutDraftToUpdateInput(pageLayoutDraft);
const { data } = await updatePageLayoutWithTabsAndWidgets({
variables: {
id: pageLayoutId,
input: updateInput,
},
});
const result = await updatePageLayoutWithTabsAndWidgets(
pageLayoutId,
updateInput,
);
const updatedPageLayout = data?.updatePageLayoutWithTabsAndWidgets;
if (result.status === 'successful') {
const updatedPageLayout =
result.response.data?.updatePageLayoutWithTabsAndWidgets;
if (isDefined(updatedPageLayout)) {
const pageLayoutToPersist: PageLayout =
transformPageLayout(updatedPageLayout);
if (isDefined(updatedPageLayout)) {
const pageLayoutToPersist: PageLayout =
transformPageLayout(updatedPageLayout);
set(pageLayoutPersistedCallbackState, pageLayoutToPersist);
set(
pageLayoutCurrentLayoutsCallbackState,
convertPageLayoutToTabLayouts(pageLayoutToPersist),
);
set(pageLayoutPersistedCallbackState, pageLayoutToPersist);
set(
pageLayoutCurrentLayoutsCallbackState,
convertPageLayoutToTabLayouts(pageLayoutToPersist),
);
}
}
return result;
},
[
pageLayoutCurrentLayoutsCallbackState,
@@ -0,0 +1,61 @@
import {
type UpdatePageLayoutWithTabsInput,
useUpdatePageLayoutWithTabsAndWidgetsMutation,
} from '~/generated/graphql';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
export const useUpdatePageLayoutWithTabsAndWidgets = () => {
const [updatePageLayoutWithTabsAndWidgetsMutation] =
useUpdatePageLayoutWithTabsAndWidgetsMutation();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const updatePageLayoutWithTabsAndWidgets = async (
id: string,
input: UpdatePageLayoutWithTabsInput,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof updatePageLayoutWithTabsAndWidgetsMutation>>
>
> => {
try {
const updatedPageLayout =
await updatePageLayoutWithTabsAndWidgetsMutation({
variables: {
id,
input,
},
});
return {
status: 'successful',
response: updatedPageLayout,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'pageLayout',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
};
return {
updatePageLayoutWithTabsAndWidgets,
};
};
@@ -12,6 +12,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ApolloError, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import {
type CreateOneServerlessFunctionItemMutation,
type CreateOneServerlessFunctionItemMutationVariables,
@@ -72,6 +73,7 @@ export const usePersistServerlessFunction = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -114,6 +116,7 @@ export const usePersistServerlessFunction = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -157,6 +160,7 @@ export const usePersistServerlessFunction = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -6,6 +6,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useViewsSideEffectsOnViewGroups } from '@/views/hooks/useViewsSideEffectsOnViewGroups';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import {
@@ -72,6 +73,7 @@ export const usePersistView = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'view',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -110,6 +112,7 @@ export const usePersistView = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'view',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -143,6 +146,7 @@ export const usePersistView = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'view',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -6,6 +6,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useTriggerViewFieldOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type CreateManyCoreViewFieldsMutationVariables,
@@ -71,6 +72,7 @@ export const usePersistViewField = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewField',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -132,6 +134,7 @@ export const usePersistViewField = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewField',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -193,6 +196,7 @@ export const usePersistViewField = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewField',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -244,6 +248,7 @@ export const usePersistViewField = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewField',
operationType: CrudOperationType.DESTROY,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -6,6 +6,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useTriggerViewFilterOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type CreateCoreViewFilterMutationVariables,
@@ -71,6 +72,7 @@ export const usePersistViewFilterRecords = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewFilter',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -132,6 +134,7 @@ export const usePersistViewFilterRecords = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewFilter',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -193,6 +196,7 @@ export const usePersistViewFilterRecords = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewFilter',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -244,6 +248,7 @@ export const usePersistViewFilterRecords = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewFilter',
operationType: CrudOperationType.DESTROY,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
@@ -6,6 +6,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type UpdateCoreViewGroupMutationVariables,
@@ -63,6 +64,7 @@ export const usePersistViewGroupRecords = () => {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewGroup',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });