[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:
+6
-3
@@ -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} />;
|
||||
|
||||
+18
-12
@@ -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
|
||||
|
||||
+86
-79
@@ -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,
|
||||
|
||||
+2
@@ -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.` });
|
||||
|
||||
+2
@@ -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.` });
|
||||
|
||||
+2
@@ -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.` });
|
||||
|
||||
+2
@@ -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.` });
|
||||
|
||||
+2
@@ -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.` });
|
||||
|
||||
+2
@@ -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,
|
||||
|
||||
+61
@@ -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,
|
||||
};
|
||||
};
|
||||
+4
@@ -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.` });
|
||||
|
||||
+9
-2
@@ -1,12 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FlatPageLayoutWidgetTypeValidatorService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { WorkspaceFlatPageLayoutWidgetMapCacheService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/workspace-flat-page-layout-widget-map-cache.service';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([PageLayoutWidgetEntity])],
|
||||
providers: [WorkspaceFlatPageLayoutWidgetMapCacheService],
|
||||
exports: [WorkspaceFlatPageLayoutWidgetMapCacheService],
|
||||
providers: [
|
||||
WorkspaceFlatPageLayoutWidgetMapCacheService,
|
||||
FlatPageLayoutWidgetTypeValidatorService,
|
||||
],
|
||||
exports: [
|
||||
WorkspaceFlatPageLayoutWidgetMapCacheService,
|
||||
FlatPageLayoutWidgetTypeValidatorService,
|
||||
],
|
||||
})
|
||||
export class FlatPageLayoutWidgetModule {}
|
||||
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FlatEntityPropertiesUpdates } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-updates.type';
|
||||
import {
|
||||
type FlatPageLayoutWidgetTypeValidatorForCreation,
|
||||
type FlatPageLayoutWidgetTypeValidatorForUpdate,
|
||||
} from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-type-validator.type';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { rejectWidgetType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/reject-widget-type.util';
|
||||
import { validateGraphFlatPageLayoutWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-graph-flat-page-layout-widget-for-creation.util';
|
||||
import { validateGraphFlatPageLayoutWidgetForUpdate } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-graph-flat-page-layout-widget-for-update.util';
|
||||
import { validateIframeFlatPageLayoutWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-iframe-flat-page-layout-widget-for-creation.util';
|
||||
import { validateIframeFlatPageLayoutWidgetForUpdate } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-iframe-flat-page-layout-widget-for-update.util';
|
||||
import { validateStandaloneRichTextFlatPageLayoutWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-standalone-rich-text-flat-page-layout-widget-for-creation.util';
|
||||
import { validateStandaloneRichTextFlatPageLayoutWidgetForUpdate } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-standalone-rich-text-flat-page-layout-widget-for-update.util';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
|
||||
|
||||
export type GenericValidateFlatPageLayoutWidgetTypeSpecificitiesArgs =
|
||||
FlatEntityValidationArgs<'pageLayoutWidget'> & {
|
||||
updates?: FlatEntityPropertiesUpdates<'pageLayoutWidget'>;
|
||||
};
|
||||
|
||||
export type ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs =
|
||||
FlatEntityValidationArgs<'pageLayoutWidget'>;
|
||||
|
||||
export type ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs =
|
||||
FlatEntityValidationArgs<'pageLayoutWidget'> & {
|
||||
updates: FlatEntityPropertiesUpdates<'pageLayoutWidget'>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class FlatPageLayoutWidgetTypeValidatorService {
|
||||
constructor() {}
|
||||
|
||||
private readonly PAGE_LAYOUT_WIDGET_TYPE_VALIDATOR_FOR_CREATION_HASHMAP: FlatPageLayoutWidgetTypeValidatorForCreation =
|
||||
{
|
||||
VIEW: rejectWidgetType(
|
||||
WidgetType.VIEW,
|
||||
'Widget type VIEW is not supported yet.',
|
||||
msg`Widget type VIEW is not supported yet.`,
|
||||
),
|
||||
IFRAME: validateIframeFlatPageLayoutWidgetForCreation,
|
||||
FIELD: rejectWidgetType(
|
||||
WidgetType.FIELD,
|
||||
'Widget type FIELD is not supported yet.',
|
||||
msg`Widget type FIELD is not supported yet.`,
|
||||
),
|
||||
FIELDS: rejectWidgetType(
|
||||
WidgetType.FIELDS,
|
||||
'Widget type FIELDS is not supported yet.',
|
||||
msg`Widget type FIELDS is not supported yet.`,
|
||||
),
|
||||
GRAPH: validateGraphFlatPageLayoutWidgetForCreation,
|
||||
STANDALONE_RICH_TEXT:
|
||||
validateStandaloneRichTextFlatPageLayoutWidgetForCreation,
|
||||
TIMELINE: rejectWidgetType(
|
||||
WidgetType.TIMELINE,
|
||||
'Widget type TIMELINE is not supported yet.',
|
||||
msg`Widget type TIMELINE is not supported yet.`,
|
||||
),
|
||||
TASKS: rejectWidgetType(
|
||||
WidgetType.TASKS,
|
||||
'Widget type TASKS is not supported yet.',
|
||||
msg`Widget type TASKS is not supported yet.`,
|
||||
),
|
||||
NOTES: rejectWidgetType(
|
||||
WidgetType.NOTES,
|
||||
'Widget type NOTES is not supported yet.',
|
||||
msg`Widget type NOTES is not supported yet.`,
|
||||
),
|
||||
FILES: rejectWidgetType(
|
||||
WidgetType.FILES,
|
||||
'Widget type FILES is not supported yet.',
|
||||
msg`Widget type FILES is not supported yet.`,
|
||||
),
|
||||
EMAILS: rejectWidgetType(
|
||||
WidgetType.EMAILS,
|
||||
'Widget type EMAILS is not supported yet.',
|
||||
msg`Widget type EMAILS is not supported yet.`,
|
||||
),
|
||||
CALENDAR: rejectWidgetType(
|
||||
WidgetType.CALENDAR,
|
||||
'Widget type CALENDAR is not supported yet.',
|
||||
msg`Widget type CALENDAR is not supported yet.`,
|
||||
),
|
||||
FIELD_RICH_TEXT: rejectWidgetType(
|
||||
WidgetType.FIELD_RICH_TEXT,
|
||||
'Widget type FIELD_RICH_TEXT is not supported yet.',
|
||||
msg`Widget type FIELD_RICH_TEXT is not supported yet.`,
|
||||
),
|
||||
WORKFLOW: rejectWidgetType(
|
||||
WidgetType.WORKFLOW,
|
||||
'Widget type WORKFLOW is not supported yet.',
|
||||
msg`Widget type WORKFLOW is not supported yet.`,
|
||||
),
|
||||
WORKFLOW_VERSION: rejectWidgetType(
|
||||
WidgetType.WORKFLOW_VERSION,
|
||||
'Widget type WORKFLOW_VERSION is not supported yet.',
|
||||
msg`Widget type WORKFLOW_VERSION is not supported yet.`,
|
||||
),
|
||||
WORKFLOW_RUN: rejectWidgetType(
|
||||
WidgetType.WORKFLOW_RUN,
|
||||
'Widget type WORKFLOW_RUN is not supported yet.',
|
||||
msg`Widget type WORKFLOW_RUN is not supported yet.`,
|
||||
),
|
||||
};
|
||||
|
||||
private readonly PAGE_LAYOUT_WIDGET_TYPE_VALIDATOR_FOR_UPDATE_HASHMAP: FlatPageLayoutWidgetTypeValidatorForUpdate =
|
||||
{
|
||||
VIEW: rejectWidgetType(
|
||||
WidgetType.VIEW,
|
||||
'Widget type VIEW is not supported yet.',
|
||||
msg`Widget type VIEW is not supported yet.`,
|
||||
),
|
||||
IFRAME: validateIframeFlatPageLayoutWidgetForUpdate,
|
||||
FIELD: rejectWidgetType(
|
||||
WidgetType.FIELD,
|
||||
'Widget type FIELD is not supported yet.',
|
||||
msg`Widget type FIELD is not supported yet.`,
|
||||
),
|
||||
FIELDS: rejectWidgetType(
|
||||
WidgetType.FIELDS,
|
||||
'Widget type FIELDS is not supported yet.',
|
||||
msg`Widget type FIELDS is not supported yet.`,
|
||||
),
|
||||
GRAPH: validateGraphFlatPageLayoutWidgetForUpdate,
|
||||
STANDALONE_RICH_TEXT:
|
||||
validateStandaloneRichTextFlatPageLayoutWidgetForUpdate,
|
||||
TIMELINE: rejectWidgetType(
|
||||
WidgetType.TIMELINE,
|
||||
'Widget type TIMELINE is not supported yet.',
|
||||
msg`Widget type TIMELINE is not supported yet.`,
|
||||
),
|
||||
TASKS: rejectWidgetType(
|
||||
WidgetType.TASKS,
|
||||
'Widget type TASKS is not supported yet.',
|
||||
msg`Widget type TASKS is not supported yet.`,
|
||||
),
|
||||
NOTES: rejectWidgetType(
|
||||
WidgetType.NOTES,
|
||||
'Widget type NOTES is not supported yet.',
|
||||
msg`Widget type NOTES is not supported yet.`,
|
||||
),
|
||||
FILES: rejectWidgetType(
|
||||
WidgetType.FILES,
|
||||
'Widget type FILES is not supported yet.',
|
||||
msg`Widget type FILES is not supported yet.`,
|
||||
),
|
||||
EMAILS: rejectWidgetType(
|
||||
WidgetType.EMAILS,
|
||||
'Widget type EMAILS is not supported yet.',
|
||||
msg`Widget type EMAILS is not supported yet.`,
|
||||
),
|
||||
CALENDAR: rejectWidgetType(
|
||||
WidgetType.CALENDAR,
|
||||
'Widget type CALENDAR is not supported yet.',
|
||||
msg`Widget type CALENDAR is not supported yet.`,
|
||||
),
|
||||
FIELD_RICH_TEXT: rejectWidgetType(
|
||||
WidgetType.FIELD_RICH_TEXT,
|
||||
'Widget type FIELD_RICH_TEXT is not supported yet.',
|
||||
msg`Widget type FIELD_RICH_TEXT is not supported yet.`,
|
||||
),
|
||||
WORKFLOW: rejectWidgetType(
|
||||
WidgetType.WORKFLOW,
|
||||
'Widget type WORKFLOW is not supported yet.',
|
||||
msg`Widget type WORKFLOW is not supported yet.`,
|
||||
),
|
||||
WORKFLOW_VERSION: rejectWidgetType(
|
||||
WidgetType.WORKFLOW_VERSION,
|
||||
'Widget type WORKFLOW_VERSION is not supported yet.',
|
||||
msg`Widget type WORKFLOW_VERSION is not supported yet.`,
|
||||
),
|
||||
WORKFLOW_RUN: rejectWidgetType(
|
||||
WidgetType.WORKFLOW_RUN,
|
||||
'Widget type WORKFLOW_RUN is not supported yet.',
|
||||
msg`Widget type WORKFLOW_RUN is not supported yet.`,
|
||||
),
|
||||
};
|
||||
|
||||
public validateFlatPageLayoutWidgetTypeSpecificitiesForCreation(
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] {
|
||||
const { flatEntityToValidate } = args;
|
||||
const widgetType = flatEntityToValidate.type;
|
||||
const pageLayoutWidgetTypeValidator =
|
||||
this.PAGE_LAYOUT_WIDGET_TYPE_VALIDATOR_FOR_CREATION_HASHMAP[widgetType];
|
||||
|
||||
if (!isDefined(pageLayoutWidgetTypeValidator)) {
|
||||
return [
|
||||
{
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: `Unsupported page layout widget type ${widgetType}`,
|
||||
value: widgetType,
|
||||
userFriendlyMessage: msg`Unsupported page layout widget type ${widgetType}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return pageLayoutWidgetTypeValidator(args);
|
||||
}
|
||||
|
||||
public validateFlatPageLayoutWidgetTypeSpecificitiesForUpdate(
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] {
|
||||
const { flatEntityToValidate } = args;
|
||||
const widgetType = flatEntityToValidate.type;
|
||||
const pageLayoutWidgetTypeValidator =
|
||||
this.PAGE_LAYOUT_WIDGET_TYPE_VALIDATOR_FOR_UPDATE_HASHMAP[widgetType];
|
||||
|
||||
if (!isDefined(pageLayoutWidgetTypeValidator)) {
|
||||
return [
|
||||
{
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: `Unsupported page layout widget type ${widgetType}`,
|
||||
value: widgetType,
|
||||
userFriendlyMessage: msg`Unsupported page layout widget type ${widgetType}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return pageLayoutWidgetTypeValidator(args);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
type GenericValidateFlatPageLayoutWidgetTypeSpecificitiesArgs,
|
||||
type ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs,
|
||||
type ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs,
|
||||
} from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
|
||||
export type FlatPageLayoutWidgetTypeValidator = {
|
||||
[T in WidgetType]: (
|
||||
args: GenericValidateFlatPageLayoutWidgetTypeSpecificitiesArgs,
|
||||
) => FlatPageLayoutWidgetValidationError[];
|
||||
};
|
||||
|
||||
export type FlatPageLayoutWidgetTypeValidatorForCreation = {
|
||||
[T in WidgetType]: (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs,
|
||||
) => FlatPageLayoutWidgetValidationError[];
|
||||
};
|
||||
|
||||
export type FlatPageLayoutWidgetTypeValidatorForUpdate = {
|
||||
[T in WidgetType]: (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs,
|
||||
) => FlatPageLayoutWidgetValidationError[];
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
|
||||
import { type PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export type FlatPageLayoutWidgetValidationError = {
|
||||
code: PageLayoutWidgetExceptionCode;
|
||||
message: string;
|
||||
userFriendlyMessage?: MessageDescriptor;
|
||||
value?: unknown;
|
||||
};
|
||||
+6
-2
@@ -3,7 +3,7 @@ import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/create-page-layout-widget.input';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { validateWidgetConfigurationInput } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util';
|
||||
|
||||
export type FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs =
|
||||
{
|
||||
@@ -23,6 +23,10 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
|
||||
['pageLayoutTabId'],
|
||||
);
|
||||
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: createPageLayoutWidgetInput.configuration,
|
||||
});
|
||||
|
||||
const createdAt = new Date().toISOString();
|
||||
const pageLayoutWidgetId = v4();
|
||||
|
||||
@@ -35,7 +39,7 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
|
||||
deletedAt: null,
|
||||
universalIdentifier: pageLayoutWidgetId,
|
||||
title: createPageLayoutWidgetInput.title,
|
||||
type: createPageLayoutWidgetInput.type ?? WidgetType.VIEW,
|
||||
type: createPageLayoutWidgetInput.type,
|
||||
objectMetadataId: createPageLayoutWidgetInput.objectMetadataId ?? null,
|
||||
gridPosition: createPageLayoutWidgetInput.gridPosition,
|
||||
configuration: createPageLayoutWidgetInput.configuration,
|
||||
|
||||
+12
@@ -12,6 +12,7 @@ import {
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { validateWidgetConfigurationInput } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util';
|
||||
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
|
||||
|
||||
export type UpdatePageLayoutWidgetInputWithId = {
|
||||
@@ -47,6 +48,17 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro
|
||||
FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES,
|
||||
);
|
||||
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
updatedEditableFieldProperties,
|
||||
'configuration',
|
||||
)
|
||||
) {
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: updatedEditableFieldProperties.configuration,
|
||||
});
|
||||
}
|
||||
|
||||
return mergeUpdateInExistingRecord({
|
||||
existing: existingFlatPageLayoutWidgetToUpdate,
|
||||
properties: FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES,
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
|
||||
export const VALID_GRAPH_CONFIGURATION_TYPES = [
|
||||
WidgetConfigurationType.AGGREGATE_CHART,
|
||||
WidgetConfigurationType.BAR_CHART,
|
||||
WidgetConfigurationType.LINE_CHART,
|
||||
WidgetConfigurationType.PIE_CHART,
|
||||
WidgetConfigurationType.GAUGE_CHART,
|
||||
] as const;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { type GraphConfiguration } from './graph-configuration.type';
|
||||
|
||||
export type BaseGraphConfiguration = Pick<
|
||||
GraphConfiguration,
|
||||
'configurationType' | 'aggregateFieldMetadataId' | 'aggregateOperation'
|
||||
>;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { type AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/aggregate-chart-configuration.dto';
|
||||
import { type BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/bar-chart-configuration.dto';
|
||||
import { type GaugeChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/gauge-chart-configuration.dto';
|
||||
import { type LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/line-chart-configuration.dto';
|
||||
import { type PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/pie-chart-configuration.dto';
|
||||
|
||||
export type GraphConfiguration =
|
||||
| BarChartConfigurationDTO
|
||||
| LineChartConfigurationDTO
|
||||
| PieChartConfigurationDTO
|
||||
| AggregateChartConfigurationDTO
|
||||
| GaugeChartConfigurationDTO;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
|
||||
export type IframeConfiguration = {
|
||||
configurationType?: WidgetConfigurationType;
|
||||
url?: string;
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { type msg } from '@lingui/core/macro';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const rejectWidgetType = (
|
||||
widgetType: WidgetType,
|
||||
message: string,
|
||||
userFriendlyMessage: ReturnType<typeof msg>,
|
||||
) => {
|
||||
return (): FlatPageLayoutWidgetValidationError[] => {
|
||||
return [
|
||||
{
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message,
|
||||
value: widgetType,
|
||||
userFriendlyMessage,
|
||||
},
|
||||
];
|
||||
};
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/bar-chart-configuration.dto';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateBarChartConfiguration = (
|
||||
configuration: BarChartConfigurationDTO,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration.primaryAxisGroupByFieldMetadataId)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Primary axis group by field is required for bar chart widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Primary axis group by field is required for bar chart`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isDefined(configuration.layout)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Layout is required for bar chart widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Layout is required for bar chart`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type BaseGraphConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/base-graph-configuration.type';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateBaseGraphFields = (
|
||||
configuration: BaseGraphConfiguration,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration.aggregateFieldMetadataId)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Aggregate field metadata ID is required for graph widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Aggregate field is required for graph widget`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isDefined(configuration.aggregateOperation)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Aggregate operation is required for graph widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Aggregate operation is required for graph widget`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type GraphConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/graph-configuration.type';
|
||||
import { validateBarChartConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-bar-chart-configuration.util';
|
||||
import { validateLineChartConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-line-chart-configuration.util';
|
||||
import { validatePieChartConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-pie-chart-configuration.util';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
|
||||
export const validateGraphConfigurationByType = (
|
||||
configuration: GraphConfiguration,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const configurationType = configuration.configurationType;
|
||||
|
||||
switch (configurationType) {
|
||||
case WidgetConfigurationType.BAR_CHART:
|
||||
return validateBarChartConfiguration(configuration, widgetTitle);
|
||||
case WidgetConfigurationType.LINE_CHART:
|
||||
return validateLineChartConfiguration(configuration, widgetTitle);
|
||||
case WidgetConfigurationType.PIE_CHART:
|
||||
return validatePieChartConfiguration(configuration, widgetTitle);
|
||||
case WidgetConfigurationType.AGGREGATE_CHART:
|
||||
case WidgetConfigurationType.GAUGE_CHART:
|
||||
return [];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { VALID_GRAPH_CONFIGURATION_TYPES } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/constants/valid-graph-configuration-types.constant';
|
||||
import { type GraphConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/graph-configuration.type';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateGraphConfigurationType = (
|
||||
configuration: GraphConfiguration,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration.configurationType)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Configuration type is required for widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Configuration type is required`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
const isValidGraphConfigurationType =
|
||||
VALID_GRAPH_CONFIGURATION_TYPES.includes(configuration.configurationType);
|
||||
|
||||
if (!isValidGraphConfigurationType) {
|
||||
const expectedConfigurationTypes = VALID_GRAPH_CONFIGURATION_TYPES.map(
|
||||
(type) => type.toString(),
|
||||
).join(', ');
|
||||
|
||||
const configurationTypeString = configuration.configurationType.toString();
|
||||
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Invalid configuration type for graph widget "${widgetTitle}". Expected one of ${expectedConfigurationTypes}, got ${configurationTypeString}`,
|
||||
userFriendlyMessage: msg`Invalid configuration type for graph widget`,
|
||||
value: configuration.configurationType,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type GraphConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/graph-configuration.type';
|
||||
import { validateBaseGraphFields } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-base-graph-fields.util';
|
||||
import { validateGraphConfigurationByType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-graph-configuration-by-type.util';
|
||||
import { validateGraphConfigurationType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-graph-configuration-type.util';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateGraphFlatPageLayoutWidgetForCreation = (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const { flatEntityToValidate } = args;
|
||||
const { configuration, title } = flatEntityToValidate;
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Configuration is required for graph widget "${title}"`,
|
||||
userFriendlyMessage: msg`Configuration is required for graph widget`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
const graphConfiguration = configuration as GraphConfiguration;
|
||||
|
||||
const configurationTypeErrors = validateGraphConfigurationType(
|
||||
graphConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...configurationTypeErrors);
|
||||
|
||||
if (configurationTypeErrors.length > 0) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const baseFieldErrors = validateBaseGraphFields(graphConfiguration, title);
|
||||
|
||||
errors.push(...baseFieldErrors);
|
||||
|
||||
const typeSpecificErrors = validateGraphConfigurationByType(
|
||||
graphConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...typeSpecificErrors);
|
||||
|
||||
return errors;
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type GraphConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/graph-configuration.type';
|
||||
import { validateBaseGraphFields } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-base-graph-fields.util';
|
||||
import { validateGraphConfigurationByType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-graph-configuration-by-type.util';
|
||||
import { validateGraphConfigurationType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-graph-configuration-type.util';
|
||||
|
||||
export const validateGraphFlatPageLayoutWidgetForUpdate = (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const { flatEntityToValidate } = args;
|
||||
const { configuration, title } = flatEntityToValidate;
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const graphConfiguration = configuration as GraphConfiguration;
|
||||
|
||||
const configurationTypeErrors = validateGraphConfigurationType(
|
||||
graphConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...configurationTypeErrors);
|
||||
|
||||
if (configurationTypeErrors.length > 0) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const baseFieldErrors = validateBaseGraphFields(graphConfiguration, title);
|
||||
|
||||
errors.push(...baseFieldErrors);
|
||||
|
||||
const typeSpecificErrors = validateGraphConfigurationByType(
|
||||
graphConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...typeSpecificErrors);
|
||||
|
||||
return errors;
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type IframeConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/iframe-configuration.type';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateIframeConfigurationType = (
|
||||
configuration: IframeConfiguration,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration.configurationType)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Configuration type is required for widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Configuration type is required`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (configuration.configurationType !== WidgetConfigurationType.IFRAME) {
|
||||
const configurationTypeString = configuration.configurationType.toString();
|
||||
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Invalid configuration type for iframe widget "${widgetTitle}". Expected IFRAME, got ${configurationTypeString}`,
|
||||
userFriendlyMessage: msg`Invalid configuration type for iframe widget`,
|
||||
value: configuration.configurationType,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type IframeConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/iframe-configuration.type';
|
||||
import { validateIframeConfigurationType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-iframe-configuration-type.util';
|
||||
import { validateIframeUrl } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-iframe-url.util';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateIframeFlatPageLayoutWidgetForCreation = (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const { flatEntityToValidate } = args;
|
||||
const { configuration, title } = flatEntityToValidate;
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Configuration is required for iframe widget "${title}"`,
|
||||
userFriendlyMessage: msg`Configuration is required for iframe widget`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
const iframeConfiguration = configuration as IframeConfiguration;
|
||||
|
||||
const configurationTypeErrors = validateIframeConfigurationType(
|
||||
iframeConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...configurationTypeErrors);
|
||||
|
||||
const urlErrors = validateIframeUrl(iframeConfiguration, title);
|
||||
|
||||
errors.push(...urlErrors);
|
||||
|
||||
return errors;
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type IframeConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/iframe-configuration.type';
|
||||
import { validateIframeConfigurationType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-iframe-configuration-type.util';
|
||||
import { validateIframeUrl } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-iframe-url.util';
|
||||
|
||||
export const validateIframeFlatPageLayoutWidgetForUpdate = (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const { flatEntityToValidate } = args;
|
||||
const { configuration, title } = flatEntityToValidate;
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const iframeConfiguration = configuration as IframeConfiguration;
|
||||
|
||||
const configurationTypeErrors = validateIframeConfigurationType(
|
||||
iframeConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...configurationTypeErrors);
|
||||
|
||||
const urlErrors = validateIframeUrl(iframeConfiguration, title);
|
||||
|
||||
errors.push(...urlErrors);
|
||||
|
||||
return errors;
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type IframeConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/types/iframe-configuration.type';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateIframeUrl = (
|
||||
configuration: IframeConfiguration,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (isDefined(configuration.url) && typeof configuration.url !== 'string') {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`URL must be a string for widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`URL must be a string`,
|
||||
value: configuration.url,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/line-chart-configuration.dto';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateLineChartConfiguration = (
|
||||
configuration: LineChartConfigurationDTO,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration.primaryAxisGroupByFieldMetadataId)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Primary axis group by field is required for line chart widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Primary axis group by field is required for line chart`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/pie-chart-configuration.dto';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validatePieChartConfiguration = (
|
||||
configuration: PieChartConfigurationDTO,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration.groupByFieldMetadataId)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Group by field is required for pie chart widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Group by field is required for pie chart`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/standalone-rich-text-configuration.dto';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateStandaloneRichTextBody = (
|
||||
configuration: StandaloneRichTextConfigurationDTO,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration.body)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Body is required for standalone rich text widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Body is required for standalone rich text widget`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof configuration.body !== 'object' ||
|
||||
Array.isArray(configuration.body)
|
||||
) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Body must be an object for widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Body must be an object`,
|
||||
value: configuration.body,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { type StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/standalone-rich-text-configuration.dto';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateStandaloneRichTextConfigurationType = (
|
||||
configuration: StandaloneRichTextConfigurationDTO,
|
||||
widgetTitle: string,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration.configurationType)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Configuration type is required for widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Configuration type is required`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (
|
||||
configuration.configurationType !==
|
||||
WidgetConfigurationType.STANDALONE_RICH_TEXT
|
||||
) {
|
||||
const expectedConfigurationType =
|
||||
WidgetConfigurationType.STANDALONE_RICH_TEXT;
|
||||
|
||||
const configurationType = configuration.configurationType;
|
||||
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Invalid configuration type for standalone rich text widget "${widgetTitle}". Expected ${expectedConfigurationType}, got ${configurationType}`,
|
||||
userFriendlyMessage: msg`Invalid configuration type for standalone rich text widget`,
|
||||
value: configuration.configurationType,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { validateStandaloneRichTextBody } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-standalone-rich-text-body.util';
|
||||
import { validateStandaloneRichTextConfigurationType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-standalone-rich-text-configuration-type.util';
|
||||
import { type StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/standalone-rich-text-configuration.dto';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
|
||||
export const validateStandaloneRichTextFlatPageLayoutWidgetForCreation = (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const { flatEntityToValidate } = args;
|
||||
const { configuration, title } = flatEntityToValidate;
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Configuration is required for standalone rich text widget "${title}"`,
|
||||
userFriendlyMessage: msg`Configuration is required for standalone rich text widget`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
const standaloneRichTextConfiguration =
|
||||
configuration as StandaloneRichTextConfigurationDTO;
|
||||
|
||||
const configurationTypeErrors = validateStandaloneRichTextConfigurationType(
|
||||
standaloneRichTextConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...configurationTypeErrors);
|
||||
|
||||
const bodyErrors = validateStandaloneRichTextBody(
|
||||
standaloneRichTextConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...bodyErrors);
|
||||
|
||||
return errors;
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { validateStandaloneRichTextBody } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-standalone-rich-text-body.util';
|
||||
import { validateStandaloneRichTextConfigurationType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-standalone-rich-text-configuration-type.util';
|
||||
import { type StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/standalone-rich-text-configuration.dto';
|
||||
|
||||
export const validateStandaloneRichTextFlatPageLayoutWidgetForUpdate = (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const { flatEntityToValidate } = args;
|
||||
const { configuration, title } = flatEntityToValidate;
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(configuration)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const standaloneRichTextConfiguration =
|
||||
configuration as StandaloneRichTextConfigurationDTO;
|
||||
|
||||
const configurationTypeErrors = validateStandaloneRichTextConfigurationType(
|
||||
standaloneRichTextConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...configurationTypeErrors);
|
||||
|
||||
const bodyErrors = validateStandaloneRichTextBody(
|
||||
standaloneRichTextConfiguration,
|
||||
title,
|
||||
);
|
||||
|
||||
errors.push(...bodyErrors);
|
||||
|
||||
return errors;
|
||||
};
|
||||
-1
@@ -31,7 +31,6 @@ export class CreatePageLayoutWidgetInput {
|
||||
|
||||
@Field(() => WidgetType, { nullable: false })
|
||||
@IsEnum(WidgetType)
|
||||
@IsOptional()
|
||||
type: WidgetType;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
|
||||
+1
-144
@@ -3,8 +3,6 @@ import { Injectable } from '@nestjs/common';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
|
||||
@@ -20,16 +18,13 @@ import {
|
||||
import { CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/create-page-layout-widget.input';
|
||||
import { UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/update-page-layout-widget.input';
|
||||
import { type PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/page-layout-widget.dto';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import {
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
PageLayoutWidgetExceptionMessageKey,
|
||||
generatePageLayoutWidgetExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
|
||||
import { fromFlatPageLayoutWidgetToPageLayoutWidgetDto } from 'src/engine/metadata-modules/page-layout-widget/utils/from-flat-page-layout-widget-to-page-layout-widget-dto.util';
|
||||
import { validateAndTransformWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-and-transform-widget-configuration.util';
|
||||
import { validateWidgetGridPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-grid-position.util';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
@@ -43,7 +38,6 @@ type WidgetMigrationOperations = {
|
||||
@Injectable()
|
||||
export class PageLayoutWidgetService {
|
||||
constructor(
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
@@ -63,56 +57,6 @@ export class PageLayoutWidgetService {
|
||||
return flatPageLayoutWidgetMaps;
|
||||
}
|
||||
|
||||
private async validateWidgetConfigurationOrThrow({
|
||||
type,
|
||||
configuration,
|
||||
workspaceId,
|
||||
titleForError,
|
||||
}: {
|
||||
type: WidgetType;
|
||||
configuration: AllPageLayoutWidgetConfiguration;
|
||||
workspaceId: string;
|
||||
titleForError: string;
|
||||
}): Promise<AllPageLayoutWidgetConfiguration> {
|
||||
const isDashboardV2Enabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_DASHBOARD_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
let validatedConfig: AllPageLayoutWidgetConfiguration | null = null;
|
||||
|
||||
try {
|
||||
validatedConfig = await validateAndTransformWidgetConfiguration({
|
||||
type,
|
||||
configuration,
|
||||
isDashboardV2Enabled,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_CONFIGURATION,
|
||||
titleForError,
|
||||
type,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(validatedConfig)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_CONFIGURATION,
|
||||
titleForError,
|
||||
type,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return validatedConfig;
|
||||
}
|
||||
|
||||
private async validateAndRunWidgetMigration({
|
||||
workspaceId,
|
||||
operations,
|
||||
@@ -189,18 +133,6 @@ export class PageLayoutWidgetService {
|
||||
createPageLayoutWidgetInput: CreatePageLayoutWidgetInput,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
this.validateCreateInput(createPageLayoutWidgetInput);
|
||||
|
||||
validateWidgetGridPosition(
|
||||
createPageLayoutWidgetInput.gridPosition,
|
||||
createPageLayoutWidgetInput.title,
|
||||
);
|
||||
|
||||
const validatedConfig = await this.getValidatedConfigurationForCreate(
|
||||
createPageLayoutWidgetInput,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
@@ -208,10 +140,7 @@ export class PageLayoutWidgetService {
|
||||
|
||||
const flatPageLayoutWidgetToCreate =
|
||||
fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate({
|
||||
createPageLayoutWidgetInput: {
|
||||
...createPageLayoutWidgetInput,
|
||||
configuration: validatedConfig,
|
||||
},
|
||||
createPageLayoutWidgetInput,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
});
|
||||
@@ -237,47 +166,6 @@ export class PageLayoutWidgetService {
|
||||
);
|
||||
}
|
||||
|
||||
private validateCreateInput(input: CreatePageLayoutWidgetInput): void {
|
||||
if (!isDefined(input.title)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.TITLE_REQUIRED,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(input.pageLayoutTabId)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_TAB_ID_REQUIRED,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(input.gridPosition)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.GRID_POSITION_REQUIRED,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async getValidatedConfigurationForCreate(
|
||||
input: CreatePageLayoutWidgetInput,
|
||||
workspaceId: string,
|
||||
): Promise<AllPageLayoutWidgetConfiguration> {
|
||||
return await this.validateWidgetConfigurationOrThrow({
|
||||
type: input.type,
|
||||
configuration: input.configuration,
|
||||
workspaceId,
|
||||
titleForError: input.title,
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
@@ -297,21 +185,10 @@ export class PageLayoutWidgetService {
|
||||
validateWidgetGridPosition(updateData.gridPosition, titleForValidation);
|
||||
}
|
||||
|
||||
const validatedConfig = await this.getValidatedConfigurationForUpdate(
|
||||
updateData,
|
||||
existingWidget,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const updatePageLayoutWidgetInput: UpdatePageLayoutWidgetInputWithId = {
|
||||
id,
|
||||
update: {
|
||||
...updateData,
|
||||
...(isDefined(validatedConfig)
|
||||
? {
|
||||
configuration: validatedConfig,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -361,26 +238,6 @@ export class PageLayoutWidgetService {
|
||||
return existingWidget;
|
||||
}
|
||||
|
||||
private async getValidatedConfigurationForUpdate(
|
||||
updateData: UpdatePageLayoutWidgetInput,
|
||||
existingWidget: FlatPageLayoutWidget,
|
||||
workspaceId: string,
|
||||
): Promise<AllPageLayoutWidgetConfiguration | undefined> {
|
||||
if (!isDefined(updateData.configuration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const typeForValidation = updateData.type ?? existingWidget.type;
|
||||
const titleForError = updateData.title ?? existingWidget.title;
|
||||
|
||||
return await this.validateWidgetConfigurationOrThrow({
|
||||
type: typeForValidation,
|
||||
configuration: updateData.configuration,
|
||||
workspaceId,
|
||||
titleForError,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<PageLayoutWidgetDTO> {
|
||||
const existingFlatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
-378
@@ -1,378 +0,0 @@
|
||||
import {
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_INVALID_SUBFIELDS,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
TEST_GAUGE_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_IFRAME_CONFIG,
|
||||
TEST_LINE_CHART_CONFIG,
|
||||
TEST_NUMBER_CHART_CONFIG,
|
||||
TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
TEST_PIE_CHART_CONFIG,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
} from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { validateAndTransformWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-and-transform-widget-configuration.util';
|
||||
|
||||
jest.mock(
|
||||
'src/engine/core-modules/record-transformer/utils/transform-rich-text-v2.util',
|
||||
() => ({
|
||||
transformRichTextV2Value: jest.fn((value) =>
|
||||
Promise.resolve({
|
||||
blocknote: value.blocknote ?? null,
|
||||
markdown: value.markdown ?? null,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
describe('validateAndTransformWidgetConfiguration', () => {
|
||||
describe('IFRAME widget', () => {
|
||||
it('should validate and transform valid iframe configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_IFRAME_CONFIG);
|
||||
});
|
||||
|
||||
it('should throw error for invalid URL', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/url must be a URL address/);
|
||||
});
|
||||
|
||||
it('should throw error for empty URL', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/url must be a URL address/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('STANDALONE_RICH_TEXT widget', () => {
|
||||
it('should validate and transform valid standalone rich text configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_STANDALONE_RICH_TEXT_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal standalone rich text configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for missing body', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/body/);
|
||||
});
|
||||
|
||||
it('should throw error when body is wrong type', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should strip invalid subfields from body', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_INVALID_SUBFIELDS,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect((result as any).body.blocknote).toBeDefined();
|
||||
expect((result as any).body.markdown).toBe('valid');
|
||||
expect((result as any).body.invalidField).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GRAPH widget', () => {
|
||||
describe('NUMBER graph', () => {
|
||||
it('should validate full number graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_NUMBER_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_NUMBER_CHART_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal number graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_NUMBER_CHART_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for partial number graph configuration with missing required fields', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/aggregateFieldMetadataId.*aggregateOperation/);
|
||||
});
|
||||
|
||||
it('should throw error for invalid UUID', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/aggregateFieldMetadataId must be a UUID/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('VERTICAL_BAR graph', () => {
|
||||
it('should validate full vertical bar graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal vertical bar graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for partial vertical bar graph configuration with missing required fields', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/primaryAxisGroupByFieldMetadataId/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HORIZONTAL_BAR graph', () => {
|
||||
it('should validate full horizontal bar graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal horizontal bar graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for partial horizontal bar graph configuration with missing required fields', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/primaryAxisGroupByFieldMetadataId/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null for unsupported graph type', async () => {
|
||||
const configuration = {
|
||||
graphType: 'UNSUPPORTED',
|
||||
viewId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
};
|
||||
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: configuration,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for missing graph type', async () => {
|
||||
const configuration = {
|
||||
viewId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
};
|
||||
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: configuration,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should throw error for null configuration', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: null,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should throw error for undefined configuration', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: undefined,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should throw error for non-object configuration', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: 'string',
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should return null for unsupported widget type', async () => {
|
||||
const configuration = { someField: 'value' };
|
||||
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: 'UNSUPPORTED' as WidgetType,
|
||||
configuration: configuration,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error messages', () => {
|
||||
it('should include validation details in error message', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/aggregateFieldMetadataId must be a UUID/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Feature flags', () => {
|
||||
it('should throw error for GAUGE chart type when IS_DASHBOARD_V2_ENABLED is false', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_GAUGE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/IS_DASHBOARD_V2_ENABLED feature flag/);
|
||||
});
|
||||
|
||||
it('should not throw error for GAUGE chart type when IS_DASHBOARD_V2_ENABLED is true', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_GAUGE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: true,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw error for PIE chart type regardless of IS_DASHBOARD_V2_ENABLED', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_PIE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_PIE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: true,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw error for LINE chart type regardless of IS_DASHBOARD_V2_ENABLED', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_LINE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_LINE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: true,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
import {
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
TEST_GAUGE_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_IFRAME_CONFIG,
|
||||
TEST_LINE_CHART_CONFIG,
|
||||
TEST_NUMBER_CHART_CONFIG,
|
||||
TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
TEST_PIE_CHART_CONFIG,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
} from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { validateWidgetConfigurationInput } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util';
|
||||
|
||||
describe('validateWidgetConfigurationInput', () => {
|
||||
describe('IFRAME widget', () => {
|
||||
it('should not throw for valid iframe configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for invalid URL', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: {
|
||||
...INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
},
|
||||
}),
|
||||
).toThrow(/url must be a URL address/);
|
||||
});
|
||||
|
||||
it('should throw error for empty URL', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: {
|
||||
...INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
},
|
||||
}),
|
||||
).toThrow(/url must be a URL address/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('STANDALONE_RICH_TEXT widget', () => {
|
||||
it('should not throw for valid standalone rich text configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw for minimal standalone rich text configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for missing body', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: {
|
||||
...INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
},
|
||||
}),
|
||||
).toThrow(/body/);
|
||||
});
|
||||
|
||||
it('should throw error when body is wrong type', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: {
|
||||
...INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
},
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GRAPH widget', () => {
|
||||
describe('AGGREGATE_CHART graph', () => {
|
||||
it('should not throw for full aggregate chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_NUMBER_CHART_CONFIG,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw for minimal aggregate chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for partial aggregate chart configuration with missing required fields', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
}),
|
||||
).toThrow(/aggregateFieldMetadataId.*aggregateOperation/);
|
||||
});
|
||||
|
||||
it('should throw error for invalid UUID', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
}),
|
||||
).toThrow(/aggregateFieldMetadataId must be a UUID/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BAR_CHART graph', () => {
|
||||
describe('VERTICAL layout', () => {
|
||||
it('should not throw for full vertical bar chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw for minimal vertical bar chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for partial vertical bar chart configuration with missing required fields', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
}),
|
||||
).toThrow(/primaryAxisGroupByFieldMetadataId/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HORIZONTAL layout', () => {
|
||||
it('should not throw for full horizontal bar chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw for minimal horizontal bar chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for partial horizontal bar chart configuration with missing required fields', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration:
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
}),
|
||||
).toThrow(/primaryAxisGroupByFieldMetadataId/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PIE_CHART graph', () => {
|
||||
it('should not throw for pie chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_PIE_CHART_CONFIG,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('LINE_CHART graph', () => {
|
||||
it('should not throw for line chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_LINE_CHART_CONFIG,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GAUGE_CHART graph', () => {
|
||||
it('should not throw for gauge chart configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: TEST_GAUGE_CHART_CONFIG,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should throw error for null configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: null,
|
||||
}),
|
||||
).toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should throw error for undefined configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: undefined,
|
||||
}),
|
||||
).toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should throw error for non-object configuration', () => {
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: 'string',
|
||||
}),
|
||||
).toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should throw error for missing configurationType', () => {
|
||||
const configuration = { someField: 'value' };
|
||||
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: configuration,
|
||||
}),
|
||||
).toThrow('Invalid configuration: missing configuration type');
|
||||
});
|
||||
|
||||
it('should throw error for unsupported configurationType', () => {
|
||||
const configuration = {
|
||||
configurationType: 'UNSUPPORTED_TYPE',
|
||||
someField: 'value',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: configuration,
|
||||
}),
|
||||
).toThrow(/Invalid configuration type: UNSUPPORTED_TYPE/);
|
||||
});
|
||||
});
|
||||
});
|
||||
+167
-130
@@ -1,6 +1,6 @@
|
||||
import { WIDGET_GRID_MAX_COLUMNS } from 'src/engine/metadata-modules/page-layout-widget/constants/widget-grid-max-columns.constant';
|
||||
import { WIDGET_GRID_MAX_ROWS } from 'src/engine/metadata-modules/page-layout-widget/constants/widget-grid-max-rows.constant';
|
||||
import { PageLayoutWidgetException } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { validateWidgetGridPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-grid-position.util';
|
||||
|
||||
describe('validateWidgetGridPosition', () => {
|
||||
@@ -12,176 +12,213 @@ describe('validateWidgetGridPosition', () => {
|
||||
};
|
||||
|
||||
describe('Valid grid positions', () => {
|
||||
it('should not throw for valid grid position', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(validGridPosition, 'Test Widget'),
|
||||
).not.toThrow();
|
||||
it('should return empty array for valid grid position', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
validGridPosition,
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not throw for widget at max column boundary', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: WIDGET_GRID_MAX_COLUMNS - 1,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).not.toThrow();
|
||||
it('should return empty array for widget at max column boundary', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: WIDGET_GRID_MAX_COLUMNS - 1,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not throw for widget at max row boundary', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 1,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).not.toThrow();
|
||||
it('should return empty array for widget at max row boundary', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 1,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not throw for widget spanning to column grid edge', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 8,
|
||||
rowSpan: 1,
|
||||
columnSpan: 4,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).not.toThrow();
|
||||
it('should return empty array for widget spanning to column grid edge', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 8,
|
||||
rowSpan: 1,
|
||||
columnSpan: 4,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not throw for widget spanning to row grid edge', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 5,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).not.toThrow();
|
||||
it('should return empty array for widget spanning to row grid edge', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 5,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid row positions', () => {
|
||||
it('should throw for row exceeding max rows', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{ ...validGridPosition, row: WIDGET_GRID_MAX_ROWS },
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(PageLayoutWidgetException);
|
||||
it('should return error for row exceeding max rows', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
{ ...validGridPosition, row: WIDGET_GRID_MAX_ROWS },
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(errors[0].code).toBe(
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when widget extends beyond grid height', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 2,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
it('should return error when widget extends beyond grid height', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 2,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
errors.some((error) =>
|
||||
error.message.includes('extends beyond grid height'),
|
||||
),
|
||||
).toThrow(/extends beyond grid height/);
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid column positions', () => {
|
||||
it('should throw for column exceeding max columns', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{ ...validGridPosition, column: WIDGET_GRID_MAX_COLUMNS },
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(PageLayoutWidgetException);
|
||||
it('should return error for column exceeding max columns', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
{ ...validGridPosition, column: WIDGET_GRID_MAX_COLUMNS },
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(errors[0].code).toBe(
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Widget extending beyond grid', () => {
|
||||
it('should throw when widget extends beyond grid width', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 10,
|
||||
rowSpan: 1,
|
||||
columnSpan: 3,
|
||||
},
|
||||
'Test Widget',
|
||||
it('should return error when widget extends beyond grid width', () => {
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 10,
|
||||
rowSpan: 1,
|
||||
columnSpan: 3,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
errors.some((error) =>
|
||||
error.message.includes('extends beyond grid width'),
|
||||
),
|
||||
).toThrow(/extends beyond grid width/);
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error messages', () => {
|
||||
it('should include max columns value in error', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 10,
|
||||
rowSpan: 1,
|
||||
columnSpan: 5,
|
||||
},
|
||||
'Test Widget',
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 10,
|
||||
rowSpan: 1,
|
||||
columnSpan: 5,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
errors.some((error) =>
|
||||
error.message.includes(WIDGET_GRID_MAX_COLUMNS.toString()),
|
||||
),
|
||||
).toThrow(new RegExp(WIDGET_GRID_MAX_COLUMNS.toString()));
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should include max rows value in error for row start', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS + 10,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS + 10,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
errors.some((error) =>
|
||||
error.message.includes(WIDGET_GRID_MAX_ROWS.toString()),
|
||||
),
|
||||
).toThrow(new RegExp(WIDGET_GRID_MAX_ROWS.toString()));
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should include max rows value in error for row extension', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 95,
|
||||
column: 0,
|
||||
rowSpan: 10,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: 95,
|
||||
column: 0,
|
||||
rowSpan: 10,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
errors.some((error) =>
|
||||
error.message.includes(WIDGET_GRID_MAX_ROWS.toString()),
|
||||
),
|
||||
).toThrow(new RegExp(WIDGET_GRID_MAX_ROWS.toString()));
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should include widget title in error message', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'My Custom Widget',
|
||||
),
|
||||
).toThrow(/My Custom Widget/);
|
||||
const errors = validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'My Custom Widget',
|
||||
);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
errors.some((error) => error.message.includes('My Custom Widget')),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validateSync, type ValidationError } from 'class-validator';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { transformRichTextV2Value } from 'src/engine/core-modules/record-transformer/utils/transform-rich-text-v2.util';
|
||||
import { AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/aggregate-chart-configuration.dto';
|
||||
import { BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/bar-chart-configuration.dto';
|
||||
import { GaugeChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/gauge-chart-configuration.dto';
|
||||
import { IframeConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/iframe-configuration.dto';
|
||||
import { LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/line-chart-configuration.dto';
|
||||
import { PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/pie-chart-configuration.dto';
|
||||
import { StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/standalone-rich-text-configuration.dto';
|
||||
import { BarChartGroupMode } from 'src/engine/metadata-modules/page-layout-widget/enums/bar-chart-group-mode.enum';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-type.enum';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
|
||||
|
||||
const formatValidationErrors = (errors: ValidationError[]): string => {
|
||||
return errors
|
||||
.map((err) => {
|
||||
const constraints = err.constraints
|
||||
? Object.values(err.constraints).join(', ')
|
||||
: 'Unknown error';
|
||||
|
||||
return `${err.property}: ${constraints}`;
|
||||
})
|
||||
.join('; ');
|
||||
};
|
||||
|
||||
const validateGraphConfiguration = ({
|
||||
configuration,
|
||||
isDashboardV2Enabled,
|
||||
}: {
|
||||
configuration: Record<string, unknown>;
|
||||
isDashboardV2Enabled: boolean;
|
||||
}): AllPageLayoutWidgetConfiguration | null => {
|
||||
const configurationType = configuration.configurationType as GraphType;
|
||||
|
||||
if (
|
||||
!configurationType ||
|
||||
!Object.values(GraphType).includes(configurationType)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (configurationType === GraphType.GAUGE_CHART && !isDashboardV2Enabled) {
|
||||
throw new Error(
|
||||
`Chart type ${configurationType} requires IS_DASHBOARD_V2_ENABLED feature flag`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (configurationType) {
|
||||
case GraphType.BAR_CHART: {
|
||||
const instance = plainToInstance(BarChartConfigurationDTO, configuration);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(instance.secondaryAxisGroupByFieldMetadataId) &&
|
||||
!isDefined(instance.groupMode)
|
||||
) {
|
||||
instance.groupMode = BarChartGroupMode.STACKED;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
case GraphType.LINE_CHART: {
|
||||
const instance = plainToInstance(
|
||||
LineChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(instance.secondaryAxisGroupByFieldMetadataId) &&
|
||||
!isDefined(instance.isStacked)
|
||||
) {
|
||||
instance.isStacked = true;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
case GraphType.PIE_CHART: {
|
||||
const instance = plainToInstance(PieChartConfigurationDTO, configuration);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
case GraphType.AGGREGATE_CHART: {
|
||||
const instance = plainToInstance(
|
||||
AggregateChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
case GraphType.GAUGE_CHART: {
|
||||
const instance = plainToInstance(
|
||||
GaugeChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const validateIframeConfiguration = (
|
||||
configuration: unknown,
|
||||
): AllPageLayoutWidgetConfiguration | null => {
|
||||
const instance = plainToInstance(IframeConfigurationDTO, configuration);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
const validateStandaloneRichTextConfiguration = async (
|
||||
configuration: unknown,
|
||||
): Promise<AllPageLayoutWidgetConfiguration | null> => {
|
||||
const instance = plainToInstance(
|
||||
StandaloneRichTextConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
if (instance.body) {
|
||||
instance.body = await transformRichTextV2Value(instance.body);
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
export const validateAndTransformWidgetConfiguration = async ({
|
||||
type,
|
||||
configuration,
|
||||
isDashboardV2Enabled,
|
||||
}: {
|
||||
type: WidgetType;
|
||||
configuration: unknown;
|
||||
isDashboardV2Enabled: boolean;
|
||||
}): Promise<AllPageLayoutWidgetConfiguration | null> => {
|
||||
if (!configuration || typeof configuration !== 'object') {
|
||||
throw new Error('Invalid configuration: not an object');
|
||||
}
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case WidgetType.GRAPH:
|
||||
return validateGraphConfiguration({
|
||||
configuration: configuration as Record<string, unknown>,
|
||||
isDashboardV2Enabled,
|
||||
});
|
||||
case WidgetType.IFRAME:
|
||||
return validateIframeConfiguration(configuration);
|
||||
case WidgetType.STANDALONE_RICH_TEXT:
|
||||
return await validateStandaloneRichTextConfiguration(configuration);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
if (Array.isArray(error)) {
|
||||
const errorMessage = formatValidationErrors(error);
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { type ClassConstructor } from 'class-transformer/types/interfaces';
|
||||
import { validateSync, type ValidationError } from 'class-validator';
|
||||
|
||||
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
|
||||
|
||||
export const validateWidgetConfigurationByDto = <
|
||||
T extends AllPageLayoutWidgetConfiguration,
|
||||
>(
|
||||
DtoClass: ClassConstructor<T>,
|
||||
configuration: unknown,
|
||||
): ValidationError[] => {
|
||||
const instance = plainToInstance(DtoClass, configuration);
|
||||
|
||||
return validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
};
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
import { isNotEmptyObject, type ValidationError } from 'class-validator';
|
||||
|
||||
import { AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/aggregate-chart-configuration.dto';
|
||||
import { BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/bar-chart-configuration.dto';
|
||||
import { GaugeChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/gauge-chart-configuration.dto';
|
||||
import { IframeConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/iframe-configuration.dto';
|
||||
import { LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/line-chart-configuration.dto';
|
||||
import { PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/pie-chart-configuration.dto';
|
||||
import { StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/standalone-rich-text-configuration.dto';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import {
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { validateWidgetConfigurationByDto } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-by-dto.util';
|
||||
|
||||
const formatValidationErrors = (
|
||||
errors: ValidationError[],
|
||||
parentProperty?: string,
|
||||
): string => {
|
||||
return errors
|
||||
.map((err) => {
|
||||
const propertyPath = parentProperty
|
||||
? `${parentProperty}.${err.property}`
|
||||
: err.property;
|
||||
|
||||
if (err.constraints) {
|
||||
const constraints = Object.values(err.constraints).join(', ');
|
||||
|
||||
return `${propertyPath}: ${constraints}`;
|
||||
}
|
||||
|
||||
if (err.children && err.children.length > 0) {
|
||||
return formatValidationErrors(err.children, propertyPath);
|
||||
}
|
||||
|
||||
return `${propertyPath}: Unknown error`;
|
||||
})
|
||||
.join('; ');
|
||||
};
|
||||
|
||||
export const validateWidgetConfigurationInput = ({
|
||||
configuration,
|
||||
}: {
|
||||
configuration: unknown;
|
||||
}): void => {
|
||||
if (!isNotEmptyObject(configuration)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
'Invalid configuration: not an object',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
const configurationRecord = configuration as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(
|
||||
configurationRecord,
|
||||
'configurationType',
|
||||
)
|
||||
) {
|
||||
throw new PageLayoutWidgetException(
|
||||
'Invalid configuration: missing configuration type',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
const configurationType =
|
||||
configurationRecord.configurationType as WidgetConfigurationType;
|
||||
|
||||
let errors: ValidationError[] = [];
|
||||
|
||||
switch (configurationType) {
|
||||
case WidgetConfigurationType.BAR_CHART:
|
||||
errors = validateWidgetConfigurationByDto(
|
||||
BarChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
break;
|
||||
case WidgetConfigurationType.LINE_CHART:
|
||||
errors = validateWidgetConfigurationByDto(
|
||||
LineChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
break;
|
||||
case WidgetConfigurationType.PIE_CHART:
|
||||
errors = validateWidgetConfigurationByDto(
|
||||
PieChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
break;
|
||||
case WidgetConfigurationType.AGGREGATE_CHART:
|
||||
errors = validateWidgetConfigurationByDto(
|
||||
AggregateChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
break;
|
||||
case WidgetConfigurationType.GAUGE_CHART:
|
||||
errors = validateWidgetConfigurationByDto(
|
||||
GaugeChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
break;
|
||||
case WidgetConfigurationType.IFRAME:
|
||||
errors = validateWidgetConfigurationByDto(
|
||||
IframeConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
break;
|
||||
case WidgetConfigurationType.STANDALONE_RICH_TEXT:
|
||||
errors = validateWidgetConfigurationByDto(
|
||||
StandaloneRichTextConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
break;
|
||||
case WidgetConfigurationType.VIEW:
|
||||
throw new PageLayoutWidgetException(
|
||||
'View configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.FIELD:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Field configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.FIELDS:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Fields configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.TIMELINE:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Timeline configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.TASKS:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Tasks configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.NOTES:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Notes configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.FILES:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Files configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.EMAILS:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Emails configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.CALENDAR:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Calendar configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.FIELD_RICH_TEXT:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Field rich text configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.WORKFLOW:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Workflow configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.WORKFLOW_VERSION:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Workflow version configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
case WidgetConfigurationType.WORKFLOW_RUN:
|
||||
throw new PageLayoutWidgetException(
|
||||
'Workflow run configuration is not supported yet',
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
default:
|
||||
throw new PageLayoutWidgetException(
|
||||
`Invalid configuration type: ${configurationType}`,
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new PageLayoutWidgetException(
|
||||
formatValidationErrors(errors),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
};
|
||||
+28
-18
@@ -1,11 +1,13 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { WIDGET_GRID_MAX_COLUMNS } from 'src/engine/metadata-modules/page-layout-widget/constants/widget-grid-max-columns.constant';
|
||||
import { WIDGET_GRID_MAX_ROWS } from 'src/engine/metadata-modules/page-layout-widget/constants/widget-grid-max-rows.constant';
|
||||
import {
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
PageLayoutWidgetExceptionMessageKey,
|
||||
generatePageLayoutWidgetExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
|
||||
type GridPosition = {
|
||||
row: number;
|
||||
@@ -17,54 +19,62 @@ type GridPosition = {
|
||||
export const validateWidgetGridPosition = (
|
||||
gridPosition: GridPosition,
|
||||
widgetTitle: string,
|
||||
): void => {
|
||||
): FlatEntityValidationError[] => {
|
||||
const errors: FlatEntityValidationError[] = [];
|
||||
|
||||
const { row, column, rowSpan, columnSpan } = gridPosition;
|
||||
|
||||
if (column >= WIDGET_GRID_MAX_COLUMNS) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION,
|
||||
widgetTitle,
|
||||
undefined,
|
||||
`column ${column} exceeds grid width (max column is ${WIDGET_GRID_MAX_COLUMNS - 1})`,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
userFriendlyMessage: msg`Widget extends beyond grid width`,
|
||||
});
|
||||
}
|
||||
|
||||
if (column + columnSpan > WIDGET_GRID_MAX_COLUMNS) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION,
|
||||
widgetTitle,
|
||||
undefined,
|
||||
`widget extends beyond grid width (column ${column} + columnSpan ${columnSpan} > ${WIDGET_GRID_MAX_COLUMNS})`,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
userFriendlyMessage: msg`Widget extends beyond grid width`,
|
||||
});
|
||||
}
|
||||
|
||||
if (row >= WIDGET_GRID_MAX_ROWS) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION,
|
||||
widgetTitle,
|
||||
undefined,
|
||||
`row ${row} exceeds maximum allowed rows (${WIDGET_GRID_MAX_ROWS})`,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
userFriendlyMessage: msg`Widget row exceeds grid height`,
|
||||
});
|
||||
}
|
||||
|
||||
if (row + rowSpan > WIDGET_GRID_MAX_ROWS) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION,
|
||||
widgetTitle,
|
||||
undefined,
|
||||
`widget extends beyond grid height (row ${row} + rowSpan ${rowSpan} > ${WIDGET_GRID_MAX_ROWS})`,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
userFriendlyMessage: msg`Widget extends beyond grid height`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
+17
-20
@@ -1,8 +1,9 @@
|
||||
import { isDefined } from 'class-validator';
|
||||
import { type DataSource } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { validateAndTransformWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-and-transform-widget-configuration.util';
|
||||
import { validateWidgetConfigurationInput } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util';
|
||||
import { getPageLayoutWidgetDataSeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds.util';
|
||||
|
||||
export const seedPageLayoutWidgets = async ({
|
||||
@@ -26,26 +27,22 @@ export const seedPageLayoutWidgets = async ({
|
||||
isDashboardV2Enabled,
|
||||
);
|
||||
|
||||
const pageLayoutWidgets = await Promise.all(
|
||||
widgetSeeds.map(async (widget) => {
|
||||
const validatedConfiguration = widget.configuration
|
||||
? await validateAndTransformWidgetConfiguration({
|
||||
type: widget.type,
|
||||
configuration: widget.configuration,
|
||||
isDashboardV2Enabled,
|
||||
})
|
||||
: null;
|
||||
const pageLayoutWidgets = widgetSeeds.map((widget) => {
|
||||
if (isDefined(widget.configuration)) {
|
||||
validateWidgetConfigurationInput({
|
||||
configuration: widget.configuration,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...widget,
|
||||
workspaceId,
|
||||
gridPosition: widget.gridPosition,
|
||||
configuration: validatedConfiguration,
|
||||
universalIdentifier: v4(),
|
||||
applicationId: workspaceCustomApplicationId,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return {
|
||||
...widget,
|
||||
workspaceId,
|
||||
gridPosition: widget.gridPosition,
|
||||
configuration: widget.configuration,
|
||||
universalIdentifier: v4(),
|
||||
applicationId: workspaceCustomApplicationId,
|
||||
};
|
||||
});
|
||||
|
||||
if (pageLayoutWidgets.length > 0) {
|
||||
await dataSource
|
||||
|
||||
+3
@@ -81,6 +81,7 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
|
||||
flatEntityUpdates,
|
||||
buildOptions,
|
||||
workspaceId,
|
||||
additionalCacheDataMaps,
|
||||
}: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.index
|
||||
>): FlatEntityValidationReturnType<typeof ALL_METADATA_NAME.index, 'update'> {
|
||||
@@ -113,6 +114,7 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
|
||||
workspaceId,
|
||||
flatEntityToValidate: flatEntity,
|
||||
remainingFlatEntityMapsToValidate: createEmptyFlatEntityMaps(),
|
||||
additionalCacheDataMaps,
|
||||
});
|
||||
|
||||
if (deletionValidationResult.errors.length > 0) {
|
||||
@@ -152,6 +154,7 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt
|
||||
flatIndexMaps: tempOptimisticFlatIndexMaps,
|
||||
},
|
||||
remainingFlatEntityMapsToValidate: createEmptyFlatEntityMaps(),
|
||||
additionalCacheDataMaps,
|
||||
});
|
||||
|
||||
if (creationValidationResult.errors.length > 0) {
|
||||
|
||||
+14
-10
@@ -19,14 +19,16 @@ export class WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService extends W
|
||||
super(ALL_METADATA_NAME.pageLayoutWidget);
|
||||
}
|
||||
|
||||
protected validateFlatEntityCreation(
|
||||
protected async validateFlatEntityCreation(
|
||||
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.pageLayoutWidget>,
|
||||
): FlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.pageLayoutWidget,
|
||||
'create'
|
||||
): Promise<
|
||||
FlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.pageLayoutWidget,
|
||||
'create'
|
||||
>
|
||||
> {
|
||||
const validationResult =
|
||||
this.flatPageLayoutWidgetValidatorService.validateFlatPageLayoutWidgetCreation(
|
||||
await this.flatPageLayoutWidgetValidatorService.validateFlatPageLayoutWidgetCreation(
|
||||
args,
|
||||
);
|
||||
|
||||
@@ -77,16 +79,18 @@ export class WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService extends W
|
||||
};
|
||||
}
|
||||
|
||||
protected validateFlatEntityUpdate(
|
||||
protected async validateFlatEntityUpdate(
|
||||
args: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.pageLayoutWidget
|
||||
>,
|
||||
): FlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.pageLayoutWidget,
|
||||
'update'
|
||||
): Promise<
|
||||
FlatEntityValidationReturnType<
|
||||
typeof ALL_METADATA_NAME.pageLayoutWidget,
|
||||
'update'
|
||||
>
|
||||
> {
|
||||
const validationResult =
|
||||
this.flatPageLayoutWidgetValidatorService.validateFlatPageLayoutWidgetUpdate(
|
||||
await this.flatPageLayoutWidgetValidatorService.validateFlatPageLayoutWidgetUpdate(
|
||||
args,
|
||||
);
|
||||
|
||||
|
||||
+4
@@ -57,6 +57,7 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
|
||||
dependencyOptimisticFlatEntityMaps: inputDependencyOptimisticFlatEntityMaps,
|
||||
from: fromFlatEntityMaps,
|
||||
to: toFlatEntityMaps,
|
||||
additionalCacheDataMaps,
|
||||
workspaceId,
|
||||
}: ValidateAndBuildArgs<T>): ValidateAndBuildReturnType<T> {
|
||||
this.logger.time(`EntityBuilder ${this.metadataName}`, 'validateAndBuild');
|
||||
@@ -125,6 +126,7 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
|
||||
});
|
||||
|
||||
const validationResult = await this.validateFlatEntityCreation({
|
||||
additionalCacheDataMaps,
|
||||
flatEntityToValidate: flatEntityToCreate,
|
||||
workspaceId,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
@@ -193,6 +195,7 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
|
||||
remainingFlatEntityMapsToValidate: remainingFlatEntityMapsToDelete,
|
||||
buildOptions,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
additionalCacheDataMaps,
|
||||
});
|
||||
|
||||
if (validationResult.status === 'fail') {
|
||||
@@ -237,6 +240,7 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
workspaceId,
|
||||
buildOptions,
|
||||
additionalCacheDataMaps,
|
||||
});
|
||||
|
||||
if (validationResult.status === 'fail') {
|
||||
|
||||
+2
@@ -3,9 +3,11 @@ import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
import { type MetadataFlatEntityAndRelatedFlatEntityMapsForValidation } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-and-related-flat-entity-maps-for-validation.type';
|
||||
import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { type WorkspaceMigrationBuilderAdditionalCacheDataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-builder-additional-cache-data-maps.type';
|
||||
import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-builder-options.type';
|
||||
|
||||
export type FlatEntityValidationArgs<T extends AllMetadataName> = {
|
||||
additionalCacheDataMaps: WorkspaceMigrationBuilderAdditionalCacheDataMaps;
|
||||
flatEntityToValidate: MetadataFlatEntity<T>;
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: MetadataFlatEntityAndRelatedFlatEntityMapsForValidation<T>;
|
||||
workspaceId: string;
|
||||
|
||||
+4
@@ -36,6 +36,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
},
|
||||
workspaceId,
|
||||
buildOptions,
|
||||
additionalCacheDataMaps,
|
||||
}: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.fieldMetadata
|
||||
>): FailedFlatEntityValidation<'fieldMetadata', 'update'> {
|
||||
@@ -184,6 +185,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
buildOptions,
|
||||
remainingFlatEntityMapsToValidate: createEmptyFlatEntityMaps(),
|
||||
workspaceId,
|
||||
additionalCacheDataMaps,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -285,6 +287,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
workspaceId,
|
||||
buildOptions,
|
||||
remainingFlatEntityMapsToValidate,
|
||||
additionalCacheDataMaps,
|
||||
}: FlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.fieldMetadata
|
||||
>): FailedFlatEntityValidation<'fieldMetadata', 'create'> {
|
||||
@@ -372,6 +375,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
buildOptions,
|
||||
workspaceId,
|
||||
remainingFlatEntityMapsToValidate,
|
||||
additionalCacheDataMaps,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
+162
-18
@@ -4,10 +4,20 @@ import { msg, t } from '@lingui/core/macro';
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { FlatPageLayoutWidgetTypeValidatorService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { PageLayoutTabExceptionCode } from 'src/engine/metadata-modules/page-layout-tab/exceptions/page-layout-tab.exception';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-type.enum';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
import { AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
|
||||
import { GridPosition } from 'src/engine/metadata-modules/page-layout-widget/types/grid-position.type';
|
||||
import { validateWidgetGridPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-grid-position.util';
|
||||
import {
|
||||
FailedFlatEntityValidation,
|
||||
FlatEntityValidationError,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/utils/get-flat-entity-validation-error.util';
|
||||
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
|
||||
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
|
||||
@@ -15,19 +25,26 @@ import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/w
|
||||
|
||||
@Injectable()
|
||||
export class FlatPageLayoutWidgetValidatorService {
|
||||
constructor() {}
|
||||
constructor(
|
||||
private readonly flatPageLayoutWidgetTypeValidatorService: FlatPageLayoutWidgetTypeValidatorService,
|
||||
) {}
|
||||
|
||||
public validateFlatPageLayoutWidgetUpdate({
|
||||
public async validateFlatPageLayoutWidgetUpdate({
|
||||
flatEntityId,
|
||||
flatEntityUpdates,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatPageLayoutWidgetMaps: optimisticFlatPageLayoutWidgetMaps,
|
||||
},
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
additionalCacheDataMaps: { featureFlagsMap },
|
||||
workspaceId,
|
||||
buildOptions,
|
||||
}: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.pageLayoutWidget
|
||||
>): FailedFlatEntityValidation<'pageLayoutWidget', 'update'> {
|
||||
>): Promise<FailedFlatEntityValidation<'pageLayoutWidget', 'update'>> {
|
||||
const isDashboardV2Enabled =
|
||||
featureFlagsMap[FeatureFlagKey.IS_DASHBOARD_V2_ENABLED] ?? false;
|
||||
|
||||
const existingFlatPageLayoutWidget =
|
||||
optimisticFlatPageLayoutWidgetMaps.byId[flatEntityId];
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatPageLayoutWidgetMaps
|
||||
.byId[flatEntityId];
|
||||
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
@@ -61,6 +78,38 @@ export class FlatPageLayoutWidgetValidatorService {
|
||||
pageLayoutTabId: updatedFlatPageLayoutWidget.pageLayoutTabId,
|
||||
};
|
||||
|
||||
const gridPositionErrors = this.validateGridPosition({
|
||||
gridPosition: updatedFlatPageLayoutWidget.gridPosition,
|
||||
widgetTitle: updatedFlatPageLayoutWidget.title,
|
||||
});
|
||||
|
||||
validationResult.errors.push(...gridPositionErrors);
|
||||
|
||||
const featureFlagErrors = this.validateFeatureFlags({
|
||||
type: updatedFlatPageLayoutWidget.type,
|
||||
configuration: updatedFlatPageLayoutWidget.configuration,
|
||||
widgetTitle: updatedFlatPageLayoutWidget.title,
|
||||
isDashboardV2Enabled,
|
||||
});
|
||||
|
||||
validationResult.errors.push(...featureFlagErrors);
|
||||
|
||||
const typeSpecificityErrors =
|
||||
this.flatPageLayoutWidgetTypeValidatorService.validateFlatPageLayoutWidgetTypeSpecificitiesForUpdate(
|
||||
{
|
||||
flatEntityToValidate: updatedFlatPageLayoutWidget,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
updates: flatEntityUpdates,
|
||||
additionalCacheDataMaps: { featureFlagsMap },
|
||||
workspaceId,
|
||||
buildOptions,
|
||||
remainingFlatEntityMapsToValidate:
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatPageLayoutWidgetMaps,
|
||||
},
|
||||
);
|
||||
|
||||
validationResult.errors.push(...typeSpecificityErrors);
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
@@ -100,15 +149,19 @@ export class FlatPageLayoutWidgetValidatorService {
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatPageLayoutWidgetCreation({
|
||||
public async validateFlatPageLayoutWidgetCreation({
|
||||
flatEntityToValidate: flatPageLayoutWidgetToValidate,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps: optimisticFlatPageLayoutWidgetMaps,
|
||||
},
|
||||
additionalCacheDataMaps: { featureFlagsMap },
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
workspaceId,
|
||||
buildOptions,
|
||||
remainingFlatEntityMapsToValidate,
|
||||
}: FlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.pageLayoutWidget
|
||||
>): FailedFlatEntityValidation<'pageLayoutWidget', 'create'> {
|
||||
>): Promise<FailedFlatEntityValidation<'pageLayoutWidget', 'create'>> {
|
||||
const isDashboardV2Enabled =
|
||||
featureFlagsMap[FeatureFlagKey.IS_DASHBOARD_V2_ENABLED] ?? false;
|
||||
|
||||
const validationResult = getEmptyFlatEntityValidationError({
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatPageLayoutWidgetToValidate.id,
|
||||
@@ -120,9 +173,8 @@ export class FlatPageLayoutWidgetValidatorService {
|
||||
});
|
||||
|
||||
const existingFlatPageLayoutWidget =
|
||||
optimisticFlatPageLayoutWidgetMaps.byId[
|
||||
flatPageLayoutWidgetToValidate.id
|
||||
];
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatPageLayoutWidgetMaps
|
||||
.byId[flatPageLayoutWidgetToValidate.id];
|
||||
|
||||
if (isDefined(existingFlatPageLayoutWidget)) {
|
||||
const flatPageLayoutWidgetId = flatPageLayoutWidgetToValidate.id;
|
||||
@@ -136,7 +188,8 @@ export class FlatPageLayoutWidgetValidatorService {
|
||||
|
||||
const referencedPageLayoutTab = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatPageLayoutWidgetToValidate.pageLayoutTabId,
|
||||
flatEntityMaps: flatPageLayoutTabMaps,
|
||||
flatEntityMaps:
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(referencedPageLayoutTab)) {
|
||||
@@ -147,6 +200,97 @@ export class FlatPageLayoutWidgetValidatorService {
|
||||
});
|
||||
}
|
||||
|
||||
const gridPositionErrors = this.validateGridPosition({
|
||||
gridPosition: flatPageLayoutWidgetToValidate.gridPosition,
|
||||
widgetTitle: flatPageLayoutWidgetToValidate.title,
|
||||
});
|
||||
|
||||
validationResult.errors.push(...gridPositionErrors);
|
||||
|
||||
const featureFlagErrors = this.validateFeatureFlags({
|
||||
type: flatPageLayoutWidgetToValidate.type,
|
||||
configuration: flatPageLayoutWidgetToValidate.configuration,
|
||||
widgetTitle: flatPageLayoutWidgetToValidate.title,
|
||||
isDashboardV2Enabled,
|
||||
});
|
||||
|
||||
validationResult.errors.push(...featureFlagErrors);
|
||||
|
||||
const typeSpecificityErrors =
|
||||
this.flatPageLayoutWidgetTypeValidatorService.validateFlatPageLayoutWidgetTypeSpecificitiesForCreation(
|
||||
{
|
||||
flatEntityToValidate: flatPageLayoutWidgetToValidate,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
additionalCacheDataMaps: { featureFlagsMap },
|
||||
workspaceId,
|
||||
buildOptions,
|
||||
remainingFlatEntityMapsToValidate,
|
||||
},
|
||||
);
|
||||
|
||||
validationResult.errors.push(...typeSpecificityErrors);
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
private validateGridPosition({
|
||||
gridPosition,
|
||||
widgetTitle,
|
||||
}: {
|
||||
gridPosition: GridPosition | undefined;
|
||||
widgetTitle: string;
|
||||
}): FlatEntityValidationError[] {
|
||||
if (!isDefined(gridPosition)) {
|
||||
return [
|
||||
{
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Grid position is required`,
|
||||
userFriendlyMessage: msg`Grid position is required`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return validateWidgetGridPosition(gridPosition, widgetTitle);
|
||||
}
|
||||
|
||||
private validateFeatureFlags({
|
||||
type,
|
||||
configuration,
|
||||
widgetTitle,
|
||||
isDashboardV2Enabled,
|
||||
}: {
|
||||
type: WidgetType | undefined;
|
||||
configuration: AllPageLayoutWidgetConfiguration | null | undefined;
|
||||
widgetTitle: string;
|
||||
isDashboardV2Enabled: boolean;
|
||||
}): FlatEntityValidationError[] {
|
||||
if (!isDefined(type) || !isDefined(configuration)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (type !== WidgetType.GRAPH) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const graphConfiguration = configuration as unknown as {
|
||||
configurationType?: GraphType;
|
||||
};
|
||||
|
||||
if (
|
||||
graphConfiguration.configurationType === GraphType.GAUGE_CHART &&
|
||||
!isDashboardV2Enabled
|
||||
) {
|
||||
const chartType = graphConfiguration.configurationType;
|
||||
|
||||
return [
|
||||
{
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Invalid configuration for widget "${widgetTitle}": Chart type ${chartType} requires IS_DASHBOARD_V2_ENABLED feature flag`,
|
||||
userFriendlyMessage: msg`This chart type requires the Dashboard V2 feature to be enabled`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
|
||||
import { FlatPageLayoutWidgetTypeValidatorService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { FlatAgentValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service';
|
||||
import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
|
||||
import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
|
||||
@@ -39,6 +40,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
|
||||
FlatDatabaseEventTriggerValidatorService,
|
||||
FlatCronTriggerValidatorService,
|
||||
FlatFieldMetadataTypeValidatorService,
|
||||
FlatPageLayoutWidgetTypeValidatorService,
|
||||
FlatRouteTriggerValidatorService,
|
||||
FlatRoleValidatorService,
|
||||
FlatRoleTargetValidatorService,
|
||||
|
||||
+65
-71
@@ -1,4 +1,11 @@
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { type AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/aggregate-chart-configuration.dto';
|
||||
import { type BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/bar-chart-configuration.dto';
|
||||
import { type GaugeChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/gauge-chart-configuration.dto';
|
||||
import { type IframeConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/iframe-configuration.dto';
|
||||
import { type LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/line-chart-configuration.dto';
|
||||
import { type PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/pie-chart-configuration.dto';
|
||||
import { type StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/standalone-rich-text-configuration.dto';
|
||||
import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout-widget/enums/axis-name-display.enum';
|
||||
import { BarChartLayout } from 'src/engine/metadata-modules/page-layout-widget/enums/bar-chart-layout.enum';
|
||||
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
|
||||
@@ -9,26 +16,28 @@ export const TEST_FIELD_METADATA_ID_2 = '20202020-2222-4222-a222-222222222222';
|
||||
export const TEST_FIELD_METADATA_ID_3 = '20202020-3333-4333-a333-333333333333';
|
||||
export const TEST_FIELD_METADATA_ID_4 = '20202020-4444-4444-a444-444444444444';
|
||||
|
||||
export const TEST_IFRAME_CONFIG = {
|
||||
export const TEST_IFRAME_CONFIG: IframeConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
url: 'https://example.com/dashboard',
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const TEST_STANDALONE_RICH_TEXT_CONFIG = {
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
body: {
|
||||
blocknote:
|
||||
'{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Hello world"}]}]}',
|
||||
markdown: '# Hello world',
|
||||
},
|
||||
} as const;
|
||||
export const TEST_STANDALONE_RICH_TEXT_CONFIG: StandaloneRichTextConfigurationDTO =
|
||||
{
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
body: {
|
||||
blocknote:
|
||||
'{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Hello world"}]}]}',
|
||||
markdown: '# Hello world',
|
||||
},
|
||||
};
|
||||
|
||||
export const TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL = {
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
body: {
|
||||
markdown: 'Simple text',
|
||||
},
|
||||
} as const;
|
||||
export const TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL: StandaloneRichTextConfigurationDTO =
|
||||
{
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
body: {
|
||||
markdown: 'Simple text',
|
||||
},
|
||||
};
|
||||
|
||||
export const INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY = {};
|
||||
|
||||
@@ -45,12 +54,12 @@ export const INVALID_STANDALONE_RICH_TEXT_CONFIG_INVALID_SUBFIELDS = {
|
||||
},
|
||||
};
|
||||
|
||||
export const TEST_IFRAME_CONFIG_ALTERNATIVE = {
|
||||
export const TEST_IFRAME_CONFIG_ALTERNATIVE: IframeConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
url: 'https://app.twenty.com/analytics',
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const TEST_NUMBER_CHART_CONFIG = {
|
||||
export const TEST_NUMBER_CHART_CONFIG: AggregateChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
@@ -60,14 +69,15 @@ export const TEST_NUMBER_CHART_CONFIG = {
|
||||
displayDataLabel: true,
|
||||
};
|
||||
|
||||
export const TEST_NUMBER_CHART_CONFIG_MINIMAL = {
|
||||
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
displayDataLabel: false,
|
||||
};
|
||||
export const TEST_NUMBER_CHART_CONFIG_MINIMAL: AggregateChartConfigurationDTO =
|
||||
{
|
||||
configurationType: WidgetConfigurationType.AGGREGATE_CHART,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
displayDataLabel: false,
|
||||
};
|
||||
|
||||
export const TEST_VERTICAL_BAR_CHART_CONFIG = {
|
||||
export const TEST_VERTICAL_BAR_CHART_CONFIG: BarChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.BAR_CHART,
|
||||
layout: BarChartLayout.VERTICAL,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
@@ -83,18 +93,19 @@ export const TEST_VERTICAL_BAR_CHART_CONFIG = {
|
||||
rangeMax: 100000,
|
||||
};
|
||||
|
||||
export const TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL = {
|
||||
configurationType: WidgetConfigurationType.BAR_CHART,
|
||||
layout: BarChartLayout.VERTICAL,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.VALUE_DESC,
|
||||
displayDataLabel: false,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
};
|
||||
export const TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL: BarChartConfigurationDTO =
|
||||
{
|
||||
configurationType: WidgetConfigurationType.BAR_CHART,
|
||||
layout: BarChartLayout.VERTICAL,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.VALUE_DESC,
|
||||
displayDataLabel: false,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
};
|
||||
|
||||
export const TEST_HORIZONTAL_BAR_CHART_CONFIG = {
|
||||
export const TEST_HORIZONTAL_BAR_CHART_CONFIG: BarChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.BAR_CHART,
|
||||
layout: BarChartLayout.HORIZONTAL,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
@@ -110,18 +121,19 @@ export const TEST_HORIZONTAL_BAR_CHART_CONFIG = {
|
||||
rangeMax: 100000,
|
||||
};
|
||||
|
||||
export const TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL = {
|
||||
configurationType: WidgetConfigurationType.BAR_CHART,
|
||||
layout: BarChartLayout.HORIZONTAL,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.VALUE_DESC,
|
||||
displayDataLabel: false,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
};
|
||||
export const TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL: BarChartConfigurationDTO =
|
||||
{
|
||||
configurationType: WidgetConfigurationType.BAR_CHART,
|
||||
layout: BarChartLayout.HORIZONTAL,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.VALUE_DESC,
|
||||
displayDataLabel: false,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
};
|
||||
|
||||
export const TEST_LINE_CHART_CONFIG = {
|
||||
export const TEST_LINE_CHART_CONFIG: LineChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.LINE_CHART,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.AVG,
|
||||
@@ -138,7 +150,7 @@ export const TEST_LINE_CHART_CONFIG = {
|
||||
rangeMax: 100,
|
||||
};
|
||||
|
||||
export const TEST_LINE_CHART_CONFIG_MINIMAL = {
|
||||
export const TEST_LINE_CHART_CONFIG_MINIMAL: LineChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.LINE_CHART,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.MAX,
|
||||
@@ -148,7 +160,7 @@ export const TEST_LINE_CHART_CONFIG_MINIMAL = {
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
};
|
||||
|
||||
export const TEST_PIE_CHART_CONFIG = {
|
||||
export const TEST_PIE_CHART_CONFIG: PieChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.PIE_CHART,
|
||||
groupByFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
@@ -159,18 +171,9 @@ export const TEST_PIE_CHART_CONFIG = {
|
||||
showCenterMetric: true,
|
||||
color: 'yellow',
|
||||
description: 'Distribution by category',
|
||||
filter: {
|
||||
and: [
|
||||
{
|
||||
field: 'status',
|
||||
operator: 'eq',
|
||||
value: 'active',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const TEST_PIE_CHART_CONFIG_MINIMAL = {
|
||||
export const TEST_PIE_CHART_CONFIG_MINIMAL: PieChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.PIE_CHART,
|
||||
groupByFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
@@ -179,24 +182,15 @@ export const TEST_PIE_CHART_CONFIG_MINIMAL = {
|
||||
displayDataLabel: false,
|
||||
};
|
||||
|
||||
export const TEST_GAUGE_CHART_CONFIG = {
|
||||
export const TEST_GAUGE_CHART_CONFIG: GaugeChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.GAUGE_CHART,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
description: 'Completion percentage',
|
||||
displayDataLabel: true,
|
||||
filter: {
|
||||
or: [
|
||||
{
|
||||
field: 'completed',
|
||||
operator: 'eq',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const TEST_GAUGE_CHART_CONFIG_MINIMAL = {
|
||||
export const TEST_GAUGE_CHART_CONFIG_MINIMAL: GaugeChartConfigurationDTO = {
|
||||
configurationType: WidgetConfigurationType.GAUGE_CHART,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.COUNT_TRUE,
|
||||
|
||||
+127
-3
@@ -1,6 +1,86 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Page layout widget creation should fail when gridPosition has invalid values 1`] = `
|
||||
exports[`Page layout widget creation should fail AGGREGATE_CHART widget configuration validation failures when AGGREGATE_CHART configuration has invalid UUID 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "aggregateFieldMetadataId: aggregateFieldMetadataId must be a UUID",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail AGGREGATE_CHART widget configuration validation failures when AGGREGATE_CHART configuration has missing required fields 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "aggregateFieldMetadataId: aggregateFieldMetadataId should not be empty, aggregateFieldMetadataId must be a UUID; aggregateOperation: aggregateOperation should not be empty, aggregateOperation must be one of the following values: MIN, MAX, AVG, SUM, COUNT, COUNT_UNIQUE_VALUES, COUNT_EMPTY, COUNT_NOT_EMPTY, COUNT_TRUE, COUNT_FALSE, PERCENTAGE_EMPTY, PERCENTAGE_NOT_EMPTY",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail BAR_CHART widget configuration validation failures when HORIZONTAL BAR_CHART configuration is missing group by field 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "primaryAxisGroupByFieldMetadataId: primaryAxisGroupByFieldMetadataId should not be empty, primaryAxisGroupByFieldMetadataId must be a UUID; layout: layout should not be empty, layout must be one of the following values: VERTICAL, HORIZONTAL",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail BAR_CHART widget configuration validation failures when VERTICAL BAR_CHART configuration is missing group by field 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "primaryAxisGroupByFieldMetadataId: primaryAxisGroupByFieldMetadataId should not be empty, primaryAxisGroupByFieldMetadataId must be a UUID; layout: layout should not be empty, layout must be one of the following values: VERTICAL, HORIZONTAL",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail Edge case configuration validation failures when configuration has missing configurationType 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "Invalid configuration: missing configuration type",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail Edge case configuration validation failures when configuration has unsupported configurationType 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "Invalid configuration type: UNSUPPORTED_TYPE",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail Edge case configuration validation failures when configuration is null 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"http": {
|
||||
"status": 400,
|
||||
},
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
},
|
||||
"message": "Expected non-nullable type "JSON!" not to be null.",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail General validation failures when gridPosition has invalid values 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
@@ -11,7 +91,7 @@ exports[`Page layout widget creation should fail when gridPosition has invalid v
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail when pageLayoutTabId references non-existent tab 1`] = `
|
||||
exports[`Page layout widget creation should fail General validation failures when pageLayoutTabId references non-existent tab 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
@@ -48,7 +128,7 @@ exports[`Page layout widget creation should fail when pageLayoutTabId references
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail when title is missing 1`] = `
|
||||
exports[`Page layout widget creation should fail General validation failures when title is missing 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
@@ -61,3 +141,47 @@ exports[`Page layout widget creation should fail when title is missing 1`] = `
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail IFRAME widget configuration validation failures when IFRAME configuration has empty URL 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "url: url must be a URL address",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail IFRAME widget configuration validation failures when IFRAME configuration has invalid URL 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "url: url must be a URL address",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail STANDALONE_RICH_TEXT widget configuration validation failures when STANDALONE_RICH_TEXT configuration has missing body 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "body: body should not be empty",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should fail STANDALONE_RICH_TEXT widget configuration validation failures when STANDALONE_RICH_TEXT configuration has wrong body type 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "body: nested property body must be either object or array",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
+121
@@ -1,5 +1,126 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update AGGREGATE_CHART widget configuration validation failures when updating to AGGREGATE_CHART with invalid UUID 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "aggregateFieldMetadataId: aggregateFieldMetadataId must be a UUID",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update AGGREGATE_CHART widget configuration validation failures when updating to AGGREGATE_CHART with missing required fields 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "aggregateFieldMetadataId: aggregateFieldMetadataId should not be empty, aggregateFieldMetadataId must be a UUID; aggregateOperation: aggregateOperation should not be empty, aggregateOperation must be one of the following values: MIN, MAX, AVG, SUM, COUNT, COUNT_UNIQUE_VALUES, COUNT_EMPTY, COUNT_NOT_EMPTY, COUNT_TRUE, COUNT_FALSE, PERCENTAGE_EMPTY, PERCENTAGE_NOT_EMPTY",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update BAR_CHART widget configuration validation failures when updating to BAR_CHART with missing group by field (horizontal) 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "primaryAxisGroupByFieldMetadataId: primaryAxisGroupByFieldMetadataId should not be empty, primaryAxisGroupByFieldMetadataId must be a UUID; layout: layout should not be empty, layout must be one of the following values: VERTICAL, HORIZONTAL",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update BAR_CHART widget configuration validation failures when updating to BAR_CHART with missing group by field (vertical) 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "primaryAxisGroupByFieldMetadataId: primaryAxisGroupByFieldMetadataId should not be empty, primaryAxisGroupByFieldMetadataId must be a UUID; layout: layout should not be empty, layout must be one of the following values: VERTICAL, HORIZONTAL",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update Edge case configuration validation failures when updating configuration to null 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "Invalid configuration: not an object",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update Edge case configuration validation failures when updating configuration with missing configurationType 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "Invalid configuration: missing configuration type",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update Edge case configuration validation failures when updating configuration with unsupported configurationType 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "Invalid configuration type: UNSUPPORTED_TYPE",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update IFRAME widget configuration validation failures when updating IFRAME configuration with empty URL 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "url: url must be a URL address",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update IFRAME widget configuration validation failures when updating IFRAME configuration with invalid URL 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "url: url must be a URL address",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update STANDALONE_RICH_TEXT widget configuration validation failures when updating to STANDALONE_RICH_TEXT with missing body 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "body: body should not be empty",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail Widget configuration validation failures on update STANDALONE_RICH_TEXT widget configuration validation failures when updating to STANDALONE_RICH_TEXT with wrong body type 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "BAD_USER_INPUT",
|
||||
"userFriendlyMessage": "Invalid page layout widget data.",
|
||||
},
|
||||
"message": "body: nested property body must be either object or array",
|
||||
"name": "UserInputError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should fail when updating a non-existent page layout widget 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
|
||||
+547
-9
@@ -1,6 +1,251 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget 1`] = `
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with AGGREGATE_CHART full configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "COUNT",
|
||||
"configurationType": "AGGREGATE_CHART",
|
||||
"description": "Count of all records",
|
||||
"displayDataLabel": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"format": "0,0",
|
||||
"label": "Total Records",
|
||||
"prefix": null,
|
||||
"suffix": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Number Chart Widget",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with AGGREGATE_CHART minimal configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"configurationType": "AGGREGATE_CHART",
|
||||
"description": null,
|
||||
"displayDataLabel": false,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"format": null,
|
||||
"label": null,
|
||||
"prefix": null,
|
||||
"suffix": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Number Chart Widget Minimal",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with GAUGE_CHART full configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"color": null,
|
||||
"configurationType": "GAUGE_CHART",
|
||||
"description": "Completion percentage",
|
||||
"displayDataLabel": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Gauge Chart Widget",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with GAUGE_CHART minimal configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "COUNT_TRUE",
|
||||
"color": null,
|
||||
"configurationType": "GAUGE_CHART",
|
||||
"description": null,
|
||||
"displayDataLabel": false,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Gauge Chart Widget Minimal",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with HORIZONTAL BAR_CHART full configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": "blue",
|
||||
"configurationType": "BAR_CHART",
|
||||
"description": "Horizontal revenue breakdown",
|
||||
"displayDataLabel": true,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "HORIZONTAL",
|
||||
"omitNullValues": true,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "FIELD_ASC",
|
||||
"rangeMax": 100000,
|
||||
"rangeMin": 0,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": null,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Horizontal Bar Chart Widget",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with HORIZONTAL BAR_CHART minimal configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "COUNT",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": null,
|
||||
"configurationType": "BAR_CHART",
|
||||
"description": null,
|
||||
"displayDataLabel": false,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "HORIZONTAL",
|
||||
"omitNullValues": null,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "VALUE_DESC",
|
||||
"rangeMax": null,
|
||||
"rangeMin": null,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": null,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Horizontal Bar Chart Widget Minimal",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with IFRAME configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"configurationType": "IFRAME",
|
||||
"url": "https://example.com/dashboard",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Iframe Widget",
|
||||
"type": "IFRAME",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with IFRAME minimal configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"configurationType": "IFRAME",
|
||||
@@ -17,31 +262,324 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Test Widget",
|
||||
"title": "Iframe Widget Minimal",
|
||||
"type": "IFRAME",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with specific type 1`] = `
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with LINE_CHART full configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"configurationType": "IFRAME",
|
||||
"url": "https://example.com",
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "AVG",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": "cyan",
|
||||
"configurationType": "LINE_CHART",
|
||||
"description": "Trend over time",
|
||||
"displayDataLabel": true,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"isCumulative": null,
|
||||
"isStacked": null,
|
||||
"omitNullValues": false,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "FIELD_ASC",
|
||||
"rangeMax": 100,
|
||||
"rangeMin": -100,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": "FIELD_DESC",
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 2,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 2,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Iframe Widget",
|
||||
"type": "IFRAME",
|
||||
"title": "Line Chart Widget",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with LINE_CHART minimal configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "MAX",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": null,
|
||||
"configurationType": "LINE_CHART",
|
||||
"description": null,
|
||||
"displayDataLabel": false,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"isCumulative": null,
|
||||
"isStacked": null,
|
||||
"omitNullValues": null,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "VALUE_ASC",
|
||||
"rangeMax": null,
|
||||
"rangeMin": null,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": null,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Line Chart Widget Minimal",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with PIE_CHART full configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"color": "yellow",
|
||||
"configurationType": "PIE_CHART",
|
||||
"dateGranularity": "DAY",
|
||||
"description": "Distribution by category",
|
||||
"displayDataLabel": true,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"orderBy": "VALUE_DESC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Pie Chart Widget",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with PIE_CHART minimal configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "COUNT",
|
||||
"color": null,
|
||||
"configurationType": "PIE_CHART",
|
||||
"dateGranularity": "DAY",
|
||||
"description": null,
|
||||
"displayDataLabel": false,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"orderBy": "FIELD_ASC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Pie Chart Widget Minimal",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with STANDALONE_RICH_TEXT configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"body": {
|
||||
"blocknote": "{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Hello world"}]}]}",
|
||||
"markdown": "# Hello world",
|
||||
},
|
||||
"configurationType": "STANDALONE_RICH_TEXT",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Rich Text Widget",
|
||||
"type": "STANDALONE_RICH_TEXT",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with STANDALONE_RICH_TEXT minimal configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"body": {
|
||||
"blocknote": null,
|
||||
"markdown": "Simple text",
|
||||
},
|
||||
"configurationType": "STANDALONE_RICH_TEXT",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Rich Text Widget Minimal",
|
||||
"type": "STANDALONE_RICH_TEXT",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with VERTICAL BAR_CHART full configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": "red",
|
||||
"configurationType": "BAR_CHART",
|
||||
"description": "Monthly revenue breakdown",
|
||||
"displayDataLabel": true,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "VERTICAL",
|
||||
"omitNullValues": true,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "FIELD_ASC",
|
||||
"rangeMax": 100000,
|
||||
"rangeMin": 0,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": null,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Vertical Bar Chart Widget",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget creation should succeed should create a page layout widget with VERTICAL BAR_CHART minimal configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "COUNT",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": null,
|
||||
"configurationType": "BAR_CHART",
|
||||
"description": null,
|
||||
"displayDataLabel": false,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "VERTICAL",
|
||||
"omitNullValues": null,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "VALUE_DESC",
|
||||
"rangeMax": null,
|
||||
"rangeMin": null,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": null,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Vertical Bar Chart Widget Minimal",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
+305
-3
@@ -46,11 +46,313 @@ exports[`Page layout widget update should succeed should update page layout widg
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget type 1`] = `
|
||||
exports[`Page layout widget update should succeed should update page layout widget to AGGREGATE_CHART configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"configurationType": "IFRAME",
|
||||
"url": null,
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "COUNT",
|
||||
"configurationType": "AGGREGATE_CHART",
|
||||
"description": "Count of all records",
|
||||
"displayDataLabel": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"format": "0,0",
|
||||
"label": "Total Records",
|
||||
"prefix": null,
|
||||
"suffix": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Original Widget Title",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget to GAUGE_CHART configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"color": null,
|
||||
"configurationType": "GAUGE_CHART",
|
||||
"description": "Completion percentage",
|
||||
"displayDataLabel": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Original Widget Title",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget to HORIZONTAL BAR_CHART configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": "blue",
|
||||
"configurationType": "BAR_CHART",
|
||||
"description": "Horizontal revenue breakdown",
|
||||
"displayDataLabel": true,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "HORIZONTAL",
|
||||
"omitNullValues": true,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "FIELD_ASC",
|
||||
"rangeMax": 100000,
|
||||
"rangeMin": 0,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": null,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Original Widget Title",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget to IFRAME configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"configurationType": "IFRAME",
|
||||
"url": "https://app.twenty.com/analytics",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Original Widget Title",
|
||||
"type": "IFRAME",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget to LINE_CHART configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "AVG",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": "cyan",
|
||||
"configurationType": "LINE_CHART",
|
||||
"description": "Trend over time",
|
||||
"displayDataLabel": true,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"isCumulative": null,
|
||||
"isStacked": null,
|
||||
"omitNullValues": false,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "FIELD_ASC",
|
||||
"rangeMax": 100,
|
||||
"rangeMin": -100,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": "FIELD_DESC",
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Original Widget Title",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget to PIE_CHART configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"color": "yellow",
|
||||
"configurationType": "PIE_CHART",
|
||||
"dateGranularity": "DAY",
|
||||
"description": "Distribution by category",
|
||||
"displayDataLabel": true,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"orderBy": "VALUE_DESC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Original Widget Title",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget to STANDALONE_RICH_TEXT configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"body": {
|
||||
"blocknote": "{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Hello world"}]}]}",
|
||||
"markdown": "# Hello world",
|
||||
},
|
||||
"configurationType": "STANDALONE_RICH_TEXT",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Original Widget Title",
|
||||
"type": "STANDALONE_RICH_TEXT",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget to VERTICAL BAR_CHART configuration 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"axisNameDisplay": "NONE",
|
||||
"color": "red",
|
||||
"configurationType": "BAR_CHART",
|
||||
"description": "Monthly revenue breakdown",
|
||||
"displayDataLabel": true,
|
||||
"displayLegend": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "VERTICAL",
|
||||
"omitNullValues": true,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
"primaryAxisGroupBySubFieldName": null,
|
||||
"primaryAxisManualSortOrder": null,
|
||||
"primaryAxisOrderBy": "FIELD_ASC",
|
||||
"rangeMax": 100000,
|
||||
"rangeMin": 0,
|
||||
"secondaryAxisGroupByDateGranularity": "DAY",
|
||||
"secondaryAxisGroupByFieldMetadataId": null,
|
||||
"secondaryAxisGroupBySubFieldName": null,
|
||||
"secondaryAxisManualSortOrder": null,
|
||||
"secondaryAxisOrderBy": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Original Widget Title",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget update should succeed should update page layout widget type 1`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "COUNT",
|
||||
"configurationType": "AGGREGATE_CHART",
|
||||
"description": "Count of all records",
|
||||
"displayDataLabel": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"format": "0,0",
|
||||
"label": "Total Records",
|
||||
"prefix": null,
|
||||
"suffix": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
|
||||
+253
-46
@@ -1,5 +1,15 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { TEST_IFRAME_CONFIG } from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
import {
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
TEST_IFRAME_CONFIG,
|
||||
} from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
|
||||
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
|
||||
@@ -8,8 +18,16 @@ import { createOnePageLayout } from 'test/integration/metadata/suites/page-layou
|
||||
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
|
||||
|
||||
import { type CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/create-page-layout-widget.input';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
|
||||
const DEFAULT_GRID_POSITION = {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
};
|
||||
|
||||
describe('Page layout widget creation should fail', () => {
|
||||
let testPageLayoutId: string;
|
||||
let testPageLayoutTabId: string;
|
||||
@@ -44,62 +62,251 @@ describe('Page layout widget creation should fail', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('when title is missing', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
} as CreatePageLayoutWidgetInput,
|
||||
describe('General validation failures', () => {
|
||||
it('when title is missing', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
} as CreatePageLayoutWidgetInput,
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
it('when pageLayoutTabId references non-existent tab', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Non-Existent Tab',
|
||||
pageLayoutTabId: faker.string.uuid(),
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when gridPosition has invalid values', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Invalid Grid Position',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
gridPosition: {
|
||||
row: -1,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
it('when pageLayoutTabId references non-existent tab', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Non-Existent Tab',
|
||||
pageLayoutTabId: faker.string.uuid(),
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
describe('IFRAME widget configuration validation failures', () => {
|
||||
it('when IFRAME configuration has invalid URL', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Invalid URL',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: {
|
||||
...INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
},
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
it('when IFRAME configuration has empty URL', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Empty URL',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: {
|
||||
...INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
},
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
it('when gridPosition has invalid values', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Invalid Grid Position',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
gridPosition: {
|
||||
row: -1,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
describe('STANDALONE_RICH_TEXT widget configuration validation failures', () => {
|
||||
it('when STANDALONE_RICH_TEXT configuration has missing body', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Missing Body',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: {
|
||||
...INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
} as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
it('when STANDALONE_RICH_TEXT configuration has wrong body type', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Wrong Body Type',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: {
|
||||
...INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
} as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
describe('AGGREGATE_CHART widget configuration validation failures', () => {
|
||||
it('when AGGREGATE_CHART configuration has missing required fields', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Missing Fields',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.GRAPH,
|
||||
configuration:
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when AGGREGATE_CHART configuration has invalid UUID', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Bad UUID',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.GRAPH,
|
||||
configuration:
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
describe('BAR_CHART widget configuration validation failures', () => {
|
||||
it('when VERTICAL BAR_CHART configuration is missing group by field', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Vertical Bar Chart Missing Group By',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.GRAPH,
|
||||
configuration:
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when HORIZONTAL BAR_CHART configuration is missing group by field', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Horizontal Bar Chart Missing Group By',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.GRAPH,
|
||||
configuration:
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge case configuration validation failures', () => {
|
||||
it('when configuration is null', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Null Config',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration:
|
||||
null as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when configuration has missing configurationType', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Missing Config Type',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: {
|
||||
someField: 'value',
|
||||
} as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when configuration has unsupported configurationType', async () => {
|
||||
const { errors } = await createOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
title: 'Widget With Unsupported Config Type',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: {
|
||||
configurationType: 'UNSUPPORTED_TYPE',
|
||||
someField: 'value',
|
||||
} as unknown as CreatePageLayoutWidgetInput['configuration'],
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+250
@@ -1,5 +1,33 @@
|
||||
import {
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
TEST_IFRAME_CONFIG,
|
||||
} from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
|
||||
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
|
||||
import { createOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/create-one-page-layout-widget.util';
|
||||
import { destroyOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/destroy-one-page-layout-widget.util';
|
||||
import { updateOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/update-one-page-layout-widget.util';
|
||||
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
|
||||
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
|
||||
|
||||
import { type UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/update-page-layout-widget.input';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
|
||||
const DEFAULT_GRID_POSITION = {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
};
|
||||
|
||||
describe('Page layout widget update should fail', () => {
|
||||
it('when updating a non-existent page layout widget', async () => {
|
||||
@@ -13,4 +41,226 @@ describe('Page layout widget update should fail', () => {
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
describe('Widget configuration validation failures on update', () => {
|
||||
let testPageLayoutId: string;
|
||||
let testPageLayoutTabId: string;
|
||||
let testPageLayoutWidgetId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data: layoutData } = await createOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { name: 'Test Page Layout For Widget Update Failures' },
|
||||
});
|
||||
|
||||
testPageLayoutId = layoutData.createPageLayout.id;
|
||||
|
||||
const { data: tabData } = await createOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: 'Test Tab For Widget Update Failures',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
},
|
||||
});
|
||||
|
||||
testPageLayoutTabId = tabData.createPageLayoutTab.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await destroyOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: testPageLayoutTabId },
|
||||
});
|
||||
await destroyOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { id: testPageLayoutId },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const { data } = await createOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: 'Original Widget',
|
||||
pageLayoutTabId: testPageLayoutTabId,
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
});
|
||||
|
||||
testPageLayoutWidgetId = data.createPageLayoutWidget.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await destroyOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: { id: testPageLayoutWidgetId },
|
||||
});
|
||||
});
|
||||
|
||||
describe('IFRAME widget configuration validation failures', () => {
|
||||
it('when updating IFRAME configuration with invalid URL', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration: {
|
||||
...INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when updating IFRAME configuration with empty URL', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration: {
|
||||
...INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
describe('STANDALONE_RICH_TEXT widget configuration validation failures', () => {
|
||||
it('when updating to STANDALONE_RICH_TEXT with missing body', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration: {
|
||||
...INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
} as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when updating to STANDALONE_RICH_TEXT with wrong body type', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration: {
|
||||
...INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT,
|
||||
} as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
describe('AGGREGATE_CHART widget configuration validation failures', () => {
|
||||
it('when updating to AGGREGATE_CHART with missing required fields', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration:
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when updating to AGGREGATE_CHART with invalid UUID', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration:
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
describe('BAR_CHART widget configuration validation failures', () => {
|
||||
it('when updating to BAR_CHART with missing group by field (vertical)', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration:
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when updating to BAR_CHART with missing group by field (horizontal)', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration:
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge case configuration validation failures', () => {
|
||||
it('when updating configuration to null', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration:
|
||||
null as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when updating configuration with missing configurationType', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration: {
|
||||
someField: 'value',
|
||||
} as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
|
||||
it('when updating configuration with unsupported configurationType', async () => {
|
||||
const { errors } = await updateOnePageLayoutWidget({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
id: testPageLayoutWidgetId,
|
||||
configuration: {
|
||||
configurationType: 'UNSUPPORTED_TYPE',
|
||||
someField: 'value',
|
||||
} as unknown as UpdatePageLayoutWidgetInput['configuration'],
|
||||
},
|
||||
});
|
||||
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+207
-26
@@ -1,3 +1,20 @@
|
||||
import {
|
||||
TEST_GAUGE_CHART_CONFIG,
|
||||
TEST_GAUGE_CHART_CONFIG_MINIMAL,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_IFRAME_CONFIG,
|
||||
TEST_LINE_CHART_CONFIG,
|
||||
TEST_LINE_CHART_CONFIG_MINIMAL,
|
||||
TEST_NUMBER_CHART_CONFIG,
|
||||
TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
TEST_PIE_CHART_CONFIG,
|
||||
TEST_PIE_CHART_CONFIG_MINIMAL,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
} from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
|
||||
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
|
||||
import { createOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/create-one-page-layout-widget.util';
|
||||
@@ -28,41 +45,205 @@ type TestContext = {
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_GRID_POSITION = {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
};
|
||||
|
||||
const SUCCESSFUL_TEST_CASES: EachTestingContext<TestContext>[] = [
|
||||
// IFRAME widget tests
|
||||
{
|
||||
title: 'create a page layout widget',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Test Widget',
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: {
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
},
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'create a page layout widget with specific type',
|
||||
title: 'create a page layout widget with IFRAME configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Iframe Widget',
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'create a page layout widget with IFRAME minimal configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Iframe Widget Minimal',
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: {
|
||||
configurationType: WidgetConfigurationType.IFRAME,
|
||||
url: 'https://example.com',
|
||||
},
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 2,
|
||||
columnSpan: 2,
|
||||
},
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
// STANDALONE_RICH_TEXT widget tests
|
||||
{
|
||||
title:
|
||||
'create a page layout widget with STANDALONE_RICH_TEXT configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Rich Text Widget',
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'create a page layout widget with STANDALONE_RICH_TEXT minimal configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Rich Text Widget Minimal',
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
// AGGREGATE_CHART (number chart) widget tests
|
||||
{
|
||||
title:
|
||||
'create a page layout widget with AGGREGATE_CHART full configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Number Chart Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_NUMBER_CHART_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'create a page layout widget with AGGREGATE_CHART minimal configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Number Chart Widget Minimal',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
// BAR_CHART vertical widget tests
|
||||
{
|
||||
title:
|
||||
'create a page layout widget with VERTICAL BAR_CHART full configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Vertical Bar Chart Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'create a page layout widget with VERTICAL BAR_CHART minimal configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Vertical Bar Chart Widget Minimal',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
// BAR_CHART horizontal widget tests
|
||||
{
|
||||
title:
|
||||
'create a page layout widget with HORIZONTAL BAR_CHART full configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Horizontal Bar Chart Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'create a page layout widget with HORIZONTAL BAR_CHART minimal configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Horizontal Bar Chart Widget Minimal',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
// PIE_CHART widget tests
|
||||
{
|
||||
title: 'create a page layout widget with PIE_CHART full configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Pie Chart Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_PIE_CHART_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'create a page layout widget with PIE_CHART minimal configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Pie Chart Widget Minimal',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_PIE_CHART_CONFIG_MINIMAL,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
// LINE_CHART widget tests
|
||||
{
|
||||
title: 'create a page layout widget with LINE_CHART full configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Line Chart Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_LINE_CHART_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'create a page layout widget with LINE_CHART minimal configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Line Chart Widget Minimal',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_LINE_CHART_CONFIG_MINIMAL,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
// GAUGE_CHART widget tests
|
||||
{
|
||||
title: 'create a page layout widget with GAUGE_CHART full configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Gauge Chart Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_GAUGE_CHART_CONFIG,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'create a page layout widget with GAUGE_CHART minimal configuration',
|
||||
context: {
|
||||
input: {
|
||||
title: 'Gauge Chart Widget Minimal',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_GAUGE_CHART_CONFIG_MINIMAL,
|
||||
gridPosition: DEFAULT_GRID_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+85
@@ -1,3 +1,13 @@
|
||||
import {
|
||||
TEST_GAUGE_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_IFRAME_CONFIG_ALTERNATIVE,
|
||||
TEST_LINE_CHART_CONFIG,
|
||||
TEST_NUMBER_CHART_CONFIG,
|
||||
TEST_PIE_CHART_CONFIG,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
} from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
|
||||
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
|
||||
import { createOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/create-one-page-layout-widget.util';
|
||||
@@ -13,11 +23,13 @@ import {
|
||||
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
|
||||
|
||||
type TestContext = {
|
||||
input: {
|
||||
title?: string;
|
||||
type?: WidgetType;
|
||||
configuration?: AllPageLayoutWidgetConfiguration;
|
||||
gridPosition?: {
|
||||
row: number;
|
||||
column: number;
|
||||
@@ -41,6 +53,7 @@ const SUCCESSFUL_TEST_CASES: EachTestingContext<TestContext>[] = [
|
||||
context: {
|
||||
input: {
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_NUMBER_CHART_CONFIG,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -57,6 +70,78 @@ const SUCCESSFUL_TEST_CASES: EachTestingContext<TestContext>[] = [
|
||||
},
|
||||
},
|
||||
},
|
||||
// Configuration update tests
|
||||
{
|
||||
title: 'update page layout widget to IFRAME configuration',
|
||||
context: {
|
||||
input: {
|
||||
configuration: TEST_IFRAME_CONFIG_ALTERNATIVE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'update page layout widget to STANDALONE_RICH_TEXT configuration',
|
||||
context: {
|
||||
input: {
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'update page layout widget to AGGREGATE_CHART configuration',
|
||||
context: {
|
||||
input: {
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_NUMBER_CHART_CONFIG,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'update page layout widget to VERTICAL BAR_CHART configuration',
|
||||
context: {
|
||||
input: {
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'update page layout widget to HORIZONTAL BAR_CHART configuration',
|
||||
context: {
|
||||
input: {
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'update page layout widget to PIE_CHART configuration',
|
||||
context: {
|
||||
input: {
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_PIE_CHART_CONFIG,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'update page layout widget to LINE_CHART configuration',
|
||||
context: {
|
||||
input: {
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_LINE_CHART_CONFIG,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'update page layout widget to GAUGE_CHART configuration',
|
||||
context: {
|
||||
input: {
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_GAUGE_CHART_CONFIG,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Page layout widget update should succeed', () => {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum CrudOperationType {
|
||||
CREATE = 'CREATE',
|
||||
UPDATE = 'UPDATE',
|
||||
DELETE = 'DELETE',
|
||||
RESTORE = 'RESTORE',
|
||||
DESTROY = 'DESTROY',
|
||||
}
|
||||
@@ -50,6 +50,7 @@ export {
|
||||
export type { CompositeFieldSubFieldName } from './CompositeFieldSubFieldNameType';
|
||||
export type { ConfigVariableValue } from './ConfigVariableValue';
|
||||
export { ConnectedAccountProvider } from './ConnectedAccountProvider';
|
||||
export { CrudOperationType } from './CrudOperationType';
|
||||
export type { EnumFieldMetadataType } from './EnumFieldMetadataType';
|
||||
export type { ExcludeFunctions } from './ExcludeFunctions';
|
||||
export type { ExtractPropertiesThatEndsWithId } from './ExtractPropertiesThatEndsWithId';
|
||||
|
||||
Reference in New Issue
Block a user