From 113e19025f8fd45932a451f49e91f0df4a1fb058 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Tue, 26 Nov 2024 13:33:52 +0800 Subject: [PATCH 01/59] task: create new version selector button --- .../VersionSelectorV2/VersionItem.tsx | 2 + .../VersionSelectorV2/index.tsx | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx create mode 100644 src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx new file mode 100644 index 0000000000..7033519a77 --- /dev/null +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -0,0 +1,2 @@ +type VersionItemProps = {}; +export const VersionItem = ({}: VersionItemProps) => {}; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx new file mode 100644 index 0000000000..221c95d441 --- /dev/null +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -0,0 +1,53 @@ +import { useState, memo } from "react"; +import { Button, Menu, MenuItem, Box, Tooltip, Chip } from "@mui/material"; +import { KeyboardArrowDownRounded } from "@mui/icons-material"; +import { useLocation, useParams } from "react-router"; + +import { + useGetContentItemVersionsQuery, + useGetItemPublishingsQuery, +} from "../../../../../../../../../shell/services/instance"; + +type VersionSelectorProps = { + version: number; +}; +export const VersionSelector = memo(({ version }: VersionSelectorProps) => { + const { modelZUID, itemZUID } = useParams<{ + modelZUID: string; + itemZUID: string; + }>(); + const [anchorEl, setAnchorEl] = useState(null); + const { data: versions } = useGetContentItemVersionsQuery({ + modelZUID, + itemZUID, + }); + + return ( + + + + ); +}); +VersionSelector.displayName = "VersionSelector"; From f5ea2b4772cce01b5fab853788efa41282038f37 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Tue, 26 Nov 2024 15:13:01 +0800 Subject: [PATCH 02/59] task: create version item component --- .../VersionSelectorV2/VersionItem.tsx | 69 ++++++- .../VersionSelectorV2/index.tsx | 187 ++++++++++++++---- 2 files changed, 212 insertions(+), 44 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 7033519a77..33678a7e77 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,2 +1,67 @@ -type VersionItemProps = {}; -export const VersionItem = ({}: VersionItemProps) => {}; +import { Box, MenuItem, Stack, Typography } from "@mui/material"; +import { ScheduleRounded, LanguageRounded } from "@mui/icons-material"; + +import { ContentItem } from "../../../../../../../../../shell/services/types"; + +export type Version = { + itemZUID: string; + modelZUID: string; + itemVersionZUID: string; + itemVersion: number; + labels: any[]; + createdAt: string; + isPublished: boolean; + isScheduled: boolean; +}; +type VersionItemProps = { + data: Version; + isActive: boolean; + withBottomBorder: boolean; +}; +export const VersionItem = ({ + data, + isActive, + withBottomBorder, +}: VersionItemProps) => { + return ( + + + + + v{data?.itemVersion} + + {data?.isPublished && ( + + + + Published + + + )} + {data?.isScheduled && ( + + + + Scheduled + + + )} + + + {data?.createdAt} + + + + ); +}; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 221c95d441..7984e787f7 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -1,53 +1,156 @@ -import { useState, memo } from "react"; -import { Button, Menu, MenuItem, Box, Tooltip, Chip } from "@mui/material"; +import { useState, memo, useMemo } from "react"; +import { + Button, + Menu, + MenuItem, + Box, + Tooltip, + Chip, + Divider, +} from "@mui/material"; import { KeyboardArrowDownRounded } from "@mui/icons-material"; import { useLocation, useParams } from "react-router"; +import moment from "moment"; + +const formatDateTime = (dateTimeString: string) => { + if (!dateTimeString) return ""; + + const momentDate = moment(dateTimeString); + const now = moment(); + + if (momentDate.isSame(now, "day")) { + return `Today ${momentDate.format("h:mm A")}`; + } else if (momentDate.isSame(now.clone().subtract(1, "day"), "day")) { + return `Yesterday ${momentDate.format("h:mm A")}`; + } else if (momentDate.isSame(now.clone().add(1, "day"), "day")) { + return `Tomorrow ${momentDate.format("h:mm A")}`; + } else { + return momentDate.format("MMM D h:mm A"); + } +}; import { useGetContentItemVersionsQuery, useGetItemPublishingsQuery, } from "../../../../../../../../../shell/services/instance"; +import { VersionItem, Version } from "./VersionItem"; type VersionSelectorProps = { - version: number; + activeVersion: number; }; -export const VersionSelector = memo(({ version }: VersionSelectorProps) => { - const { modelZUID, itemZUID } = useParams<{ - modelZUID: string; - itemZUID: string; - }>(); - const [anchorEl, setAnchorEl] = useState(null); - const { data: versions } = useGetContentItemVersionsQuery({ - modelZUID, - itemZUID, - }); - - return ( - - - - ); -}); +export const VersionSelector = memo( + ({ activeVersion }: VersionSelectorProps) => { + const [anchorEl, setAnchorEl] = useState(null); + const { modelZUID, itemZUID } = useParams<{ + modelZUID: string; + itemZUID: string; + }>(); + const { data: itemPublishings } = useGetItemPublishingsQuery( + { + modelZUID, + itemZUID, + }, + { skip: !modelZUID || !itemZUID } + ); + const { data: versions } = useGetContentItemVersionsQuery( + { + modelZUID, + itemZUID, + }, + { skip: !modelZUID || !itemZUID } + ); + + const mappedVersions: Version[] = useMemo(() => { + if (!versions?.length) return []; + + const activeVersion = itemPublishings?.find( + (itemPublishing) => itemPublishing._active + ); + const scheduledVersion = itemPublishings?.find( + (item) => + !item._active && + moment.utc(item.publishAt).isAfter(moment.utc()) && + !item.unpublishAt + ); + + return versions.map((v) => ({ + itemZUID: v?.meta?.ZUID, + modelZUID: v?.meta?.contentModelZUID, + itemVersionZUID: v?.web?.versionZUID, + itemVersion: v?.meta?.version, + labels: [], + createdAt: formatDateTime(v?.web?.createdAt), + isPublished: activeVersion?.version === v?.meta?.version, + isScheduled: scheduledVersion?.version === v?.meta?.version, + })); + }, [versions, itemPublishings]); + + return ( + <> + + + + setAnchorEl(null)} + anchorOrigin={{ + vertical: "bottom", + horizontal: "right", + }} + transformOrigin={{ + vertical: -26, + horizontal: "right", + }} + anchorEl={anchorEl} + open={!!anchorEl} + slotProps={{ + paper: { + sx: { + maxHeight: 540, + overflow: "auto", + width: 379, + bgcolor: "grey.50", + }, + }, + }} + sx={{ + "& .MuiMenu-list": { + py: 0, + }, + }} + > + {mappedVersions?.map((version, index) => ( + + ))} + + + ); + } +); VersionSelector.displayName = "VersionSelector"; From 828cf06b1092215a20b5de827e60f26e6329290f Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Tue, 26 Nov 2024 15:28:32 +0800 Subject: [PATCH 03/59] task: move menuItem to parent --- .../VersionSelectorV2/VersionItem.tsx | 11 ++------- .../VersionSelectorV2/index.tsx | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 33678a7e77..ce8ee80baf 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -24,14 +24,7 @@ export const VersionItem = ({ withBottomBorder, }: VersionItemProps) => { return ( - + <> @@ -62,6 +55,6 @@ export const VersionItem = ({ {data?.createdAt} - + ); }; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 7984e787f7..a3f435e9e3 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -141,12 +141,25 @@ export const VersionSelector = memo( }} > {mappedVersions?.map((version, index) => ( - + sx={{ + borderColor: "border", + bgcolor: + activeVersion === version?.itemVersion + ? "background.paper" + : "transparent", + p: 2, + }} + divider={index + 1 < versions?.length} + > + + ))} From ed0e32b9e51de44f9e36b4e36a039dae20f73b6b Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 27 Nov 2024 11:54:47 +0800 Subject: [PATCH 04/59] task: wire load version --- .../VersionSelectorV2/index.tsx | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index a3f435e9e3..c155019f55 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -1,16 +1,9 @@ import { useState, memo, useMemo } from "react"; -import { - Button, - Menu, - MenuItem, - Box, - Tooltip, - Chip, - Divider, -} from "@mui/material"; +import { Button, Menu, MenuItem, Tooltip, Chip } from "@mui/material"; import { KeyboardArrowDownRounded } from "@mui/icons-material"; -import { useLocation, useParams } from "react-router"; +import { useParams } from "react-router"; import moment from "moment"; +import { useDispatch } from "react-redux"; const formatDateTime = (dateTimeString: string) => { if (!dateTimeString) return ""; @@ -40,6 +33,7 @@ type VersionSelectorProps = { }; export const VersionSelector = memo( ({ activeVersion }: VersionSelectorProps) => { + const dispatch = useDispatch(); const [anchorEl, setAnchorEl] = useState(null); const { modelZUID, itemZUID } = useParams<{ modelZUID: string; @@ -85,6 +79,19 @@ export const VersionSelector = memo( })); }, [versions, itemPublishings]); + const handleLoadVersion = (version: number) => { + const versionToLoad = versions?.find((v) => v?.meta?.version === version); + + if (!!versionToLoad) { + dispatch({ + type: "LOAD_ITEM_VERSION", + itemZUID, + data: versionToLoad, + }); + setAnchorEl(null); + } + }; + return ( <> handleLoadVersion(version?.itemVersion)} > Date: Wed, 27 Nov 2024 12:48:47 +0800 Subject: [PATCH 05/59] task: render labels --- .../VersionSelectorV2/VersionItem.tsx | 61 ++++++++++++++++--- .../VersionSelectorV2/index.tsx | 27 +++++++- 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index ce8ee80baf..e77627b4fb 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,7 +1,25 @@ -import { Box, MenuItem, Stack, Typography } from "@mui/material"; -import { ScheduleRounded, LanguageRounded } from "@mui/icons-material"; +import { Box, MenuItem, Stack, Typography, Chip } from "@mui/material"; +import { + ScheduleRounded, + LanguageRounded, + AddRounded, +} from "@mui/icons-material"; import { ContentItem } from "../../../../../../../../../shell/services/types"; +import { MouseEventHandler } from "react"; + +const chipColors = [ + "default", + "error", + "success", + "info", + "primary", + "secondary", + "warning", +]; +const generateRandomChipColor = () => { + return chipColors[Math.floor(Math.random() * chipColors.length)]; +}; export type Version = { itemZUID: string; @@ -16,13 +34,8 @@ export type Version = { type VersionItemProps = { data: Version; isActive: boolean; - withBottomBorder: boolean; }; -export const VersionItem = ({ - data, - isActive, - withBottomBorder, -}: VersionItemProps) => { +export const VersionItem = ({ data, isActive }: VersionItemProps) => { return ( <> @@ -55,6 +68,38 @@ export const VersionItem = ({ {data?.createdAt} + {!!data?.labels?.length && ( + + {data.labels.map((label) => ( + { + evt.stopPropagation(); + console.log("open add label dropdown"); + }} + label={label} + // @ts-expect-error + color={generateRandomChipColor()} + size="small" + /> + ))} + {isActive && ( + { + evt.stopPropagation(); + console.log("open add label dropdown"); + }} + // Note: onDelete needs to be here for the custom deleteIcon to be visible + onDelete={() => {}} + deleteIcon={} + /> + )} + + )} ); }; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index c155019f55..a49fde2c07 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -5,6 +5,27 @@ import { useParams } from "react-router"; import moment from "moment"; import { useDispatch } from "react-redux"; +const dummyLabels = [ + "Approved", + "Draft", + "In Review", + "For Publish", + "Blocked", + "Published", + "Scheduled", +]; +const generateDummyLabels = () => { + let count = Math.floor(Math.random() * dummyLabels.length) + 1; + const labels = []; + + while (count) { + labels.push(dummyLabels[Math.floor(Math.random() * dummyLabels.length)]); + count--; + } + + return labels; +}; + const formatDateTime = (dateTimeString: string) => { if (!dateTimeString) return ""; @@ -72,7 +93,8 @@ export const VersionSelector = memo( modelZUID: v?.meta?.contentModelZUID, itemVersionZUID: v?.web?.versionZUID, itemVersion: v?.meta?.version, - labels: [], + // TODO: Change with actual values + labels: generateDummyLabels(), createdAt: formatDateTime(v?.web?.createdAt), isPublished: activeVersion?.version === v?.meta?.version, isScheduled: scheduledVersion?.version === v?.meta?.version, @@ -150,9 +172,11 @@ export const VersionSelector = memo( {mappedVersions?.map((version, index) => ( ))} From 25413adc5b4723b2bd092f739437ce4fbe52e14d Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 28 Nov 2024 08:47:10 +0800 Subject: [PATCH 06/59] task: use new version selector --- .../app/views/ItemEdit/components/ItemEditHeader/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/index.tsx index 5f89c8971b..bcacacf378 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/index.tsx @@ -27,7 +27,8 @@ import { import { useSelector } from "react-redux"; import { AppState } from "../../../../../../../../shell/store/types"; import { ItemEditHeaderActions } from "./ItemEditHeaderActions"; -import { VersionSelector } from "./VersionSelector"; +// import { VersionSelector } from "./VersionSelector"; +import { VersionSelector } from "./VersionSelectorV2"; import { LanguageSelector } from "./LanguageSelector"; import { ContentBreadcrumbs } from "../../../../components/ContentBreadcrumbs"; import { MoreMenu } from "./MoreMenu"; @@ -229,7 +230,7 @@ export const ItemEditHeader = ({ - + From 4c57890706173c14711c8e492f8a3c0fced014f1 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Tue, 3 Dec 2024 12:36:18 +0800 Subject: [PATCH 07/59] task: create add label component --- .../VersionSelectorV2/VersionItem.tsx | 94 +++++++++++++++++-- .../VersionSelectorV2/index.tsx | 6 +- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index e77627b4fb..dbd214ae8a 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,12 +1,29 @@ -import { Box, MenuItem, Stack, Typography, Chip } from "@mui/material"; +import { useState } from "react"; +import { + Box, + MenuItem, + Stack, + Typography, + Chip, + TextField, + InputAdornment, + Divider, + ListItemIcon, + ListItemText, +} from "@mui/material"; import { ScheduleRounded, LanguageRounded, AddRounded, + SearchRounded, + EditRounded, } from "@mui/icons-material"; import { ContentItem } from "../../../../../../../../../shell/services/types"; -import { MouseEventHandler } from "react"; +import { + useGetItemWorkflowStatusQuery, + useGetWorkflowStatusLabelsQuery, +} from "../../../../../../../../../shell/services/instance"; const chipColors = [ "default", @@ -36,9 +53,20 @@ type VersionItemProps = { isActive: boolean; }; export const VersionItem = ({ data, isActive }: VersionItemProps) => { + // const { data: statusLabels } = useGetItemWorkflowStatusQuery() + const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); + const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); + const [filterKeyword, setFilterKeyword] = useState(""); + return ( <> - + v{data?.itemVersion} @@ -69,13 +97,23 @@ export const VersionItem = ({ data, isActive }: VersionItemProps) => { {!!data?.labels?.length && ( - + {data.labels.map((label) => ( { evt.stopPropagation(); - console.log("open add label dropdown"); + if (isActive) { + setIsAddNewLabelOpen((prev) => !prev); + } }} label={label} // @ts-expect-error @@ -91,7 +129,9 @@ export const VersionItem = ({ data, isActive }: VersionItemProps) => { size="small" onClick={(evt) => { evt.stopPropagation(); - console.log("open add label dropdown"); + if (isActive) { + setIsAddNewLabelOpen((prev) => !prev); + } }} // Note: onDelete needs to be here for the custom deleteIcon to be visible onDelete={() => {}} @@ -100,6 +140,48 @@ export const VersionItem = ({ data, isActive }: VersionItemProps) => { )} )} + {isAddNewLabelOpen && ( + evt.stopPropagation()} + borderTop={1} + borderColor="border" + width="100%" + > + setFilterKeyword(evt.currentTarget.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + placeholder="Search status" + size="small" + fullWidth + autoFocus + sx={{ + my: 1.5, + px: 1, + }} + /> + + + + + Edit Statuses + + + )} ); }; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index a49fde2c07..1ddb2d2a77 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -175,7 +175,7 @@ export const VersionSelector = memo( disableRipple sx={{ borderColor: "border", - p: 2, + p: 0, flexDirection: "column", "&.Mui-selected": { @@ -192,7 +192,9 @@ export const VersionSelector = memo( }} divider={index + 1 < versions?.length} selected={activeVersion === version?.itemVersion} - onClick={() => handleLoadVersion(version?.itemVersion)} + onClick={(evt) => { + handleLoadVersion(version?.itemVersion); + }} > Date: Tue, 3 Dec 2024 12:36:24 +0800 Subject: [PATCH 08/59] task: create rtk queries --- src/shell/services/instance.ts | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/shell/services/instance.ts b/src/shell/services/instance.ts index bcdf9fbe4c..159c5fe3e9 100644 --- a/src/shell/services/instance.ts +++ b/src/shell/services/instance.ts @@ -51,6 +51,8 @@ export const instanceApi = createApi({ "HeadTags", "ContentItems", "ItemPublishings", + "ItemWorkflowStatus", + "WorkflowStatusLabels", ], endpoints: (builder) => ({ // https://www.zesty.io/docs/instances/api-reference/content/models/items/publishings/#Get-All-Item-Publishings @@ -604,6 +606,55 @@ export const instanceApi = createApi({ query: () => `/web/stylesheets/variables/categories`, transformResponse: getResponseData, }), + getItemWorkflowStatus: builder.query< + any, + { modelZUID: string; itemZUID: string } + >({ + query: ({ modelZUID, itemZUID }) => + `/content/models/${modelZUID}/items/${itemZUID}/labels`, + transformResponse: getResponseData, + providesTags: (_, __, { itemZUID }) => [ + { type: "ItemWorkflowStatus", id: itemZUID }, + ], + }), + createItemWorkflowStatus: builder.mutation< + any, + { + modelZUID: string; + itemZUID: string; + itemVersionZUID: string; + itemVersion: number; + label_zuids: string[]; + } + >({ + query: (payload) => ({ + url: `/content/models/${payload.modelZUID}/items/${payload.itemZUID}/labels`, + method: "POST", + body: payload, + }), + invalidatesTags: ["ItemWorkflowStatus"], + }), + updateItemWorkflowStatus: builder.mutation< + any, + { + modelZUID: string; + itemZUID: string; + itemWorkflowZUID: string; + labelZUIDs: string[]; + } + >({ + query: ({ modelZUID, itemZUID, itemWorkflowZUID, labelZUIDs }) => ({ + url: `/content/models/${modelZUID}/items/${itemZUID}/labels/${itemWorkflowZUID}`, + method: "PUT", + body: labelZUIDs, + }), + invalidatesTags: ["ItemWorkflowStatus"], + }), + getWorkflowStatusLabels: builder.query({ + query: () => `/env/labels`, + transformResponse: getResponseData, + providesTags: ["WorkflowStatusLabels"], + }), }), }); @@ -653,4 +704,8 @@ export const { useUpdateContentItemsMutation, useCreateItemsPublishingMutation, useDeleteContentItemsMutation, + useGetItemWorkflowStatusQuery, + useCreateItemWorkflowStatusMutation, + useUpdateItemWorkflowStatusMutation, + useGetWorkflowStatusLabelsQuery, } = instanceApi; From 98662893e915917884ae7a0de7e58dc9419d4013 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 4 Dec 2024 13:19:00 +0800 Subject: [PATCH 09/59] task: virtualize list --- .../ItemEditHeader/VersionSelectorV2/Row.tsx | 63 +++++ .../VersionSelectorV2/VersionItem.tsx | 261 +++++++++--------- .../VersionSelectorV2/index.tsx | 86 +++--- 3 files changed, 248 insertions(+), 162 deletions(-) create mode 100644 src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx new file mode 100644 index 0000000000..ecd69d73c8 --- /dev/null +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx @@ -0,0 +1,63 @@ +import { useEffect, CSSProperties, useRef, memo } from "react"; +import { MenuItem } from "@mui/material"; +import { areEqual } from "react-window"; + +import { Version, VersionItem } from "./VersionItem"; + +type RowProps = { + index: number; + style: CSSProperties; + data: { + versions: Version[]; + activeVersion: number; + handleLoadVersion: (version: number) => void; + setRowHeight: (index: number, size: number) => void; + }; +}; +export const Row = memo(({ index, style, data }: RowProps) => { + const rowRef = useRef(null); + const version = data?.versions[index]; + + useEffect(() => { + if (rowRef.current) { + data?.setRowHeight(index, rowRef.current?.clientHeight); + } + }, [rowRef]); + + return ( + { + data?.handleLoadVersion(version?.itemVersion); + }} + > + + + ); +}, areEqual); diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index dbd214ae8a..ca72831762 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, forwardRef } from "react"; import { Box, MenuItem, @@ -24,6 +24,7 @@ import { useGetItemWorkflowStatusQuery, useGetWorkflowStatusLabelsQuery, } from "../../../../../../../../../shell/services/instance"; +import { ForwardedRef } from "react-chartjs-2/dist/types"; const chipColors = [ "default", @@ -52,136 +53,148 @@ type VersionItemProps = { data: Version; isActive: boolean; }; -export const VersionItem = ({ data, isActive }: VersionItemProps) => { - // const { data: statusLabels } = useGetItemWorkflowStatusQuery() - const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); - const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); - const [filterKeyword, setFilterKeyword] = useState(""); +export const VersionItem = forwardRef( + ({ data, isActive }: VersionItemProps, ref: ForwardedRef) => { + // const { data: statusLabels } = useGetItemWorkflowStatusQuery() + const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); + const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); + const [filterKeyword, setFilterKeyword] = useState(""); - return ( - <> - - - - v{data?.itemVersion} - - {data?.isPublished && ( - - - - Published - - - )} - {data?.isScheduled && ( - - - - Scheduled - - - )} - - - {data?.createdAt} - - - {!!data?.labels?.length && ( + return ( + - {data.labels.map((label) => ( - { - evt.stopPropagation(); - if (isActive) { - setIsAddNewLabelOpen((prev) => !prev); - } + + + v{data?.itemVersion} + + {data?.isPublished && ( + + + + Published + + + )} + {data?.isScheduled && ( + + + + Scheduled + + + )} + + + {data?.createdAt} + + + {!!data?.labels?.length && ( + + {data.labels.map((label) => ( + { + evt.stopPropagation(); + if (isActive) { + setIsAddNewLabelOpen((prev) => !prev); + } + }} + label={label} + // color={generateRandomChipColor()} + color="primary" + size="small" + /> + ))} + {isActive && ( + { + evt.stopPropagation(); + if (isActive) { + setIsAddNewLabelOpen((prev) => !prev); + } + }} + // Note: onDelete needs to be here for the custom deleteIcon to be visible + onDelete={() => {}} + deleteIcon={} + /> + )} + + )} + {isAddNewLabelOpen && ( + evt.stopPropagation()} + borderTop={1} + borderColor="border" + width="100%" + > + setFilterKeyword(evt.currentTarget.value)} + InputProps={{ + startAdornment: ( + + + + ), }} - label={label} - // @ts-expect-error - color={generateRandomChipColor()} + placeholder="Search status" size="small" - /> - ))} - {isActive && ( - { - evt.stopPropagation(); - if (isActive) { - setIsAddNewLabelOpen((prev) => !prev); - } + fullWidth + autoFocus + sx={{ + my: 1.5, + px: 1, }} - // Note: onDelete needs to be here for the custom deleteIcon to be visible - onDelete={() => {}} - deleteIcon={} /> - )} - - )} - {isAddNewLabelOpen && ( - evt.stopPropagation()} - borderTop={1} - borderColor="border" - width="100%" - > - setFilterKeyword(evt.currentTarget.value)} - InputProps={{ - startAdornment: ( - - - - ), - }} - placeholder="Search status" - size="small" - fullWidth - autoFocus - sx={{ - my: 1.5, - px: 1, - }} - /> - - - - - Edit Statuses - - - )} - - ); -}; + + + + + Edit Statuses + + + )} + + ); + } +); + +VersionItem.displayName = "VersionItem"; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 1ddb2d2a77..55de96f846 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -1,9 +1,13 @@ -import { useState, memo, useMemo } from "react"; -import { Button, Menu, MenuItem, Tooltip, Chip } from "@mui/material"; +import { useState, memo, useMemo, useRef } from "react"; +import { Button, Tooltip, Chip, MenuList, Popover } from "@mui/material"; import { KeyboardArrowDownRounded } from "@mui/icons-material"; import { useParams } from "react-router"; import moment from "moment"; import { useDispatch } from "react-redux"; +import { VariableSizeList } from "react-window"; +import AutoSizer from "react-virtualized-auto-sizer"; + +import { Row } from "./Row"; const dummyLabels = [ "Approved", @@ -55,6 +59,8 @@ type VersionSelectorProps = { export const VersionSelector = memo( ({ activeVersion }: VersionSelectorProps) => { const dispatch = useDispatch(); + const listRef = useRef(null); + const rowHeights = useRef(null); const [anchorEl, setAnchorEl] = useState(null); const { modelZUID, itemZUID } = useParams<{ modelZUID: string; @@ -114,6 +120,18 @@ export const VersionSelector = memo( } }; + const setRowHeight = (index: number, size: number) => { + console.log("setrowheight", size); + listRef.current?.resetAfterIndex(0); + rowHeights.current = { ...rowHeights?.current, [index]: size }; + }; + + const getRowHeight = (index: number) => { + console.log("getrowheight", rowHeights.current?.[index]); + + return rowHeights.current?.[index] || 90; + }; + return ( <> - setAnchorEl(null)} anchorOrigin={{ vertical: "bottom", @@ -156,6 +174,7 @@ export const VersionSelector = memo( slotProps={{ paper: { sx: { + height: "100%", maxHeight: 540, overflow: "auto", width: 379, @@ -165,45 +184,36 @@ export const VersionSelector = memo( }} sx={{ "& .MuiMenu-list": { + height: "100%", py: 0, }, }} > - {mappedVersions?.map((version, index) => ( - { - handleLoadVersion(version?.itemVersion); - }} - > - - - ))} - + + {({ height, width }) => { + return ( + + {Row} + + ); + }} + + ); } From 485d34a65701ecb82077bb70c8fe663180ad82ad Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 5 Dec 2024 07:23:16 +0800 Subject: [PATCH 10/59] task: dynamically change row height --- .../ItemEditHeader/VersionSelectorV2/Row.tsx | 5 +++++ .../ItemEditHeader/VersionSelectorV2/VersionItem.tsx | 10 ++++++++-- .../ItemEditHeader/VersionSelectorV2/index.tsx | 7 ++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx index ecd69d73c8..5ac15f095d 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx @@ -57,6 +57,11 @@ export const Row = memo(({ index, style, data }: RowProps) => { key={version?.itemVersionZUID} data={version} isActive={data?.activeVersion === version?.itemVersion} + onUpdateElementHeight={() => { + setTimeout(() => { + data?.setRowHeight(index, rowRef.current?.clientHeight); + }); + }} /> ); diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index ca72831762..b7fb55d440 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,4 +1,4 @@ -import { useState, forwardRef } from "react"; +import { useState, forwardRef, useEffect } from "react"; import { Box, MenuItem, @@ -52,9 +52,13 @@ export type Version = { type VersionItemProps = { data: Version; isActive: boolean; + onUpdateElementHeight: () => void; }; export const VersionItem = forwardRef( - ({ data, isActive }: VersionItemProps, ref: ForwardedRef) => { + ( + { data, isActive, onUpdateElementHeight }: VersionItemProps, + ref: ForwardedRef + ) => { // const { data: statusLabels } = useGetItemWorkflowStatusQuery() const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); @@ -123,6 +127,7 @@ export const VersionItem = forwardRef( evt.stopPropagation(); if (isActive) { setIsAddNewLabelOpen((prev) => !prev); + onUpdateElementHeight(); } }} label={label} @@ -141,6 +146,7 @@ export const VersionItem = forwardRef( evt.stopPropagation(); if (isActive) { setIsAddNewLabelOpen((prev) => !prev); + onUpdateElementHeight(); } }} // Note: onDelete needs to be here for the custom deleteIcon to be visible diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 55de96f846..574e013901 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -51,7 +51,7 @@ import { useGetContentItemVersionsQuery, useGetItemPublishingsQuery, } from "../../../../../../../../../shell/services/instance"; -import { VersionItem, Version } from "./VersionItem"; +import { Version } from "./VersionItem"; type VersionSelectorProps = { activeVersion: number; @@ -121,14 +121,11 @@ export const VersionSelector = memo( }; const setRowHeight = (index: number, size: number) => { - console.log("setrowheight", size); - listRef.current?.resetAfterIndex(0); rowHeights.current = { ...rowHeights?.current, [index]: size }; + listRef.current?.resetAfterIndex(index); }; const getRowHeight = (index: number) => { - console.log("getrowheight", rowHeights.current?.[index]); - return rowHeights.current?.[index] || 90; }; From 5160299d87c1e39f3bb039ddb0dfbcfbdfcb4a82 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 5 Dec 2024 11:23:35 +0800 Subject: [PATCH 11/59] task: prevent jank when opening add label form --- .../VersionSelectorV2/VersionItem.tsx | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index b7fb55d440..096fbbb4db 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,4 +1,4 @@ -import { useState, forwardRef, useEffect } from "react"; +import { useState, forwardRef, useEffect, useRef } from "react"; import { Box, MenuItem, @@ -17,6 +17,7 @@ import { AddRounded, SearchRounded, EditRounded, + Check, } from "@mui/icons-material"; import { ContentItem } from "../../../../../../../../../shell/services/types"; @@ -59,11 +60,26 @@ export const VersionItem = forwardRef( { data, isActive, onUpdateElementHeight }: VersionItemProps, ref: ForwardedRef ) => { + const addNewLabelRef = useRef(null); // const { data: statusLabels } = useGetItemWorkflowStatusQuery() - const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); + // const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); const [filterKeyword, setFilterKeyword] = useState(""); + const handleOpenAddNewLabel = (evt: any) => { + evt.stopPropagation(); + + if (isActive) { + setIsAddNewLabelOpen((prev) => !prev); + onUpdateElementHeight(); + + // HACK: Prevents the dropdowm elements from flickering due to delayed height adjustment + setTimeout(() => { + addNewLabelRef.current.style.visibility = "visible"; + }); + } + }; + return ( ( { - evt.stopPropagation(); - if (isActive) { - setIsAddNewLabelOpen((prev) => !prev); - onUpdateElementHeight(); - } - }} + onClick={handleOpenAddNewLabel} label={label} // color={generateRandomChipColor()} color="primary" @@ -142,13 +152,7 @@ export const VersionItem = forwardRef( label="Add Status" color="default" size="small" - onClick={(evt) => { - evt.stopPropagation(); - if (isActive) { - setIsAddNewLabelOpen((prev) => !prev); - onUpdateElementHeight(); - } - }} + onClick={handleOpenAddNewLabel} // Note: onDelete needs to be here for the custom deleteIcon to be visible onDelete={() => {}} deleteIcon={} @@ -158,10 +162,13 @@ export const VersionItem = forwardRef( )} {isAddNewLabelOpen && ( evt.stopPropagation()} borderTop={1} borderColor="border" width="100%" + // HACK: Prevents the dropdowm elements from flickering due to delayed height adjustment + visibility="hidden" > + + + {" "} + + Draft + + + + Draft is ready for editor to check before sending to legal + + Date: Thu, 5 Dec 2024 13:14:52 +0800 Subject: [PATCH 12/59] task: add label select --- .../VersionSelectorV2/VersionItem.tsx | 120 +++++++++++++++--- src/shell/hooks/useResizeObserver.ts | 35 +++++ 2 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 src/shell/hooks/useResizeObserver.ts diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 096fbbb4db..648d99b14d 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,4 +1,4 @@ -import { useState, forwardRef, useEffect, useRef } from "react"; +import { useState, forwardRef, useEffect, useRef, ForwardedRef } from "react"; import { Box, MenuItem, @@ -25,7 +25,52 @@ import { useGetItemWorkflowStatusQuery, useGetWorkflowStatusLabelsQuery, } from "../../../../../../../../../shell/services/instance"; -import { ForwardedRef } from "react-chartjs-2/dist/types"; +import { useResizeObserver } from "../../../../../../../../../shell/hooks/useResizeObserver"; + +const DUMMY_LABELS: any[] = [ + { + ZUID: "36-14b315-4pp20v3d", + name: "Approved", + description: "Approved", + color: "#12b76a", + allowPublish: false, + sort: 3, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-14b315-d24ft", + name: "Draft", + description: "Content item is only available to preview in stage", + color: "#0BA5EC", + allowPublish: false, + sort: 1, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:02Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-n33d5-23v13w", + name: "Needs Review", + description: "Ready for review", + color: "#ff5c08", + allowPublish: false, + sort: 2, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:08Z", + updatedAt: "2024-11-25T06:21:23Z", + }, +]; const chipColors = [ "default", @@ -61,6 +106,8 @@ export const VersionItem = forwardRef( ref: ForwardedRef ) => { const addNewLabelRef = useRef(null); + const searchRef = useRef(null); + const dimensions = useResizeObserver(ref); // const { data: statusLabels } = useGetItemWorkflowStatusQuery() // const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); @@ -76,6 +123,7 @@ export const VersionItem = forwardRef( // HACK: Prevents the dropdowm elements from flickering due to delayed height adjustment setTimeout(() => { addNewLabelRef.current.style.visibility = "visible"; + searchRef.current?.querySelector("input").focus(); }); } }; @@ -171,6 +219,7 @@ export const VersionItem = forwardRef( visibility="hidden" > setFilterKeyword(evt.currentTarget.value)} InputProps={{ @@ -183,31 +232,60 @@ export const VersionItem = forwardRef( placeholder="Search status" size="small" fullWidth - autoFocus sx={{ my: 1.5, px: 1, }} /> - - - {" "} - - Draft - - - ( + - Draft is ready for editor to check before sending to legal - - + + + + + + {label.name} + + + + + {label.description} + + + ))} | ForwardedRef +) => { + const [dimensions, setDimensions] = useState(null); + + const callback = useCallback((entries: ResizeObserverEntry[]) => { + const entry = entries[0]; + + setDimensions(entry.contentRect); + }, []); + + useEffect(() => { + const observer = new ResizeObserver(callback); + + if (ref && "current" in ref && ref.current) { + observer.observe(ref.current); + } + + return () => { + if (ref && "current" in ref && ref.current) { + observer.unobserve(ref.current); + } + }; + }, [ref, callback]); + + return dimensions; +}; From 18641fa7721716bb4e16616cad7d3a5d45f9c969 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 5 Dec 2024 14:44:30 +0800 Subject: [PATCH 13/59] task: calculate max list height dynamically --- .../ItemEditHeader/VersionSelectorV2/index.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 574e013901..d0ee02c7f4 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -62,6 +62,7 @@ export const VersionSelector = memo( const listRef = useRef(null); const rowHeights = useRef(null); const [anchorEl, setAnchorEl] = useState(null); + const [listHeight, setListHeight] = useState(0); const { modelZUID, itemZUID } = useParams<{ modelZUID: string; itemZUID: string; @@ -126,6 +127,14 @@ export const VersionSelector = memo( }; const getRowHeight = (index: number) => { + const totalHeight = +Object.values(rowHeights.current || {}).reduce( + (acc: number, curr: number) => acc + curr, + 0 + ); + + // List height needs to at most be 540px + setListHeight(totalHeight <= 540 ? totalHeight : 540); + return rowHeights.current?.[index] || 90; }; @@ -171,8 +180,7 @@ export const VersionSelector = memo( slotProps={{ paper: { sx: { - height: "100%", - maxHeight: 540, + height: listHeight, overflow: "auto", width: 379, bgcolor: "grey.50", @@ -180,8 +188,7 @@ export const VersionSelector = memo( }, }} sx={{ - "& .MuiMenu-list": { - height: "100%", + "& .MuiList-root": { py: 0, }, }} From 1ac802f390a2e2971ecb0290d1f076d2e6b2fa0e Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 5 Dec 2024 14:45:21 +0800 Subject: [PATCH 14/59] task: remove ununsed hook --- .../components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 648d99b14d..f83655fa64 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -25,7 +25,6 @@ import { useGetItemWorkflowStatusQuery, useGetWorkflowStatusLabelsQuery, } from "../../../../../../../../../shell/services/instance"; -import { useResizeObserver } from "../../../../../../../../../shell/hooks/useResizeObserver"; const DUMMY_LABELS: any[] = [ { @@ -107,7 +106,6 @@ export const VersionItem = forwardRef( ) => { const addNewLabelRef = useRef(null); const searchRef = useRef(null); - const dimensions = useResizeObserver(ref); // const { data: statusLabels } = useGetItemWorkflowStatusQuery() // const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); From 2c9ce2bb7ca65ba5bb0d12938d60cb9f6381aa41 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 5 Dec 2024 16:18:30 +0800 Subject: [PATCH 15/59] chore: cleanup --- .../VersionSelectorV2/VersionItem.tsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index f83655fa64..69c7d8acc3 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,4 +1,4 @@ -import { useState, forwardRef, useEffect, useRef, ForwardedRef } from "react"; +import { useState, forwardRef, useRef, ForwardedRef } from "react"; import { Box, MenuItem, @@ -7,7 +7,6 @@ import { Chip, TextField, InputAdornment, - Divider, ListItemIcon, ListItemText, } from "@mui/material"; @@ -71,19 +70,6 @@ const DUMMY_LABELS: any[] = [ }, ]; -const chipColors = [ - "default", - "error", - "success", - "info", - "primary", - "secondary", - "warning", -]; -const generateRandomChipColor = () => { - return chipColors[Math.floor(Math.random() * chipColors.length)]; -}; - export type Version = { itemZUID: string; modelZUID: string; From 7113141b8921d1a1bb8dabc625839366df957547 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Fri, 6 Dec 2024 15:24:36 +0800 Subject: [PATCH 16/59] task: map an item's active status labels --- .../VersionSelectorV2/VersionItem.tsx | 130 ++++++------------ .../VersionSelectorV2/index.tsx | 83 +++++++---- src/shell/services/instance.ts | 6 +- src/shell/services/types.ts | 28 ++++ 4 files changed, 130 insertions(+), 117 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 69c7d8acc3..ee23e04fb0 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -19,63 +19,15 @@ import { Check, } from "@mui/icons-material"; -import { ContentItem } from "../../../../../../../../../shell/services/types"; -import { - useGetItemWorkflowStatusQuery, - useGetWorkflowStatusLabelsQuery, -} from "../../../../../../../../../shell/services/instance"; - -const DUMMY_LABELS: any[] = [ - { - ZUID: "36-14b315-4pp20v3d", - name: "Approved", - description: "Approved", - color: "#12b76a", - allowPublish: false, - sort: 3, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-14b315-d24ft", - name: "Draft", - description: "Content item is only available to preview in stage", - color: "#0BA5EC", - allowPublish: false, - sort: 1, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:02Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-n33d5-23v13w", - name: "Needs Review", - description: "Ready for review", - color: "#ff5c08", - allowPublish: false, - sort: 2, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:08Z", - updatedAt: "2024-11-25T06:21:23Z", - }, -]; +import { useGetWorkflowStatusLabelsQuery } from "../../../../../../../../../shell/services/instance"; +import { WorkflowStatusLabel } from "../../../../../../../../../shell/services/types"; export type Version = { itemZUID: string; modelZUID: string; itemVersionZUID: string; itemVersion: number; - labels: any[]; + labels: WorkflowStatusLabel[]; createdAt: string; isPublished: boolean; isScheduled: boolean; @@ -92,8 +44,7 @@ export const VersionItem = forwardRef( ) => { const addNewLabelRef = useRef(null); const searchRef = useRef(null); - // const { data: statusLabels } = useGetItemWorkflowStatusQuery() - // const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); + const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); const [filterKeyword, setFilterKeyword] = useState(""); @@ -158,40 +109,41 @@ export const VersionItem = forwardRef( {data?.createdAt} - {!!data?.labels?.length && ( - - {data.labels.map((label) => ( - - ))} - {isActive && ( - {}} - deleteIcon={} - /> - )} - - )} + {/* {!!data?.labels?.length && ( */} + + {data.labels?.map((label) => ( + + ))} + {isActive && ( + {}} + deleteIcon={} + /> + )} + + {/* )} */} {isAddNewLabelOpen && ( - {DUMMY_LABELS?.map((label: any, index) => ( + {statusLabels?.map((label, index) => ( diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index d0ee02c7f4..768c047196 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -50,8 +50,11 @@ const formatDateTime = (dateTimeString: string) => { import { useGetContentItemVersionsQuery, useGetItemPublishingsQuery, + useGetItemWorkflowStatusQuery, + useGetWorkflowStatusLabelsQuery, } from "../../../../../../../../../shell/services/instance"; import { Version } from "./VersionItem"; +import { WorkflowStatusLabel } from "../../../../../../../../../shell/services/types"; type VersionSelectorProps = { activeVersion: number; @@ -67,20 +70,29 @@ export const VersionSelector = memo( modelZUID: string; itemZUID: string; }>(); - const { data: itemPublishings } = useGetItemPublishingsQuery( - { - modelZUID, - itemZUID, - }, - { skip: !modelZUID || !itemZUID } - ); - const { data: versions } = useGetContentItemVersionsQuery( - { - modelZUID, - itemZUID, - }, - { skip: !modelZUID || !itemZUID } - ); + const { data: statusLabels, isLoading: isLoadingStatusLabels } = + useGetWorkflowStatusLabelsQuery(); + const { data: itemWorkflowStatus, isLoading: isLoadingItemWorkflowStatus } = + useGetItemWorkflowStatusQuery( + { itemZUID, modelZUID }, + { skip: !itemZUID || !modelZUID } + ); + const { data: itemPublishings, isLoading: isLoadingItemPublishings } = + useGetItemPublishingsQuery( + { + modelZUID, + itemZUID, + }, + { skip: !modelZUID || !itemZUID } + ); + const { data: versions, isLoading: isLoadingVersions } = + useGetContentItemVersionsQuery( + { + modelZUID, + itemZUID, + }, + { skip: !modelZUID || !itemZUID } + ); const mappedVersions: Version[] = useMemo(() => { if (!versions?.length) return []; @@ -95,18 +107,31 @@ export const VersionSelector = memo( !item.unpublishAt ); - return versions.map((v) => ({ - itemZUID: v?.meta?.ZUID, - modelZUID: v?.meta?.contentModelZUID, - itemVersionZUID: v?.web?.versionZUID, - itemVersion: v?.meta?.version, - // TODO: Change with actual values - labels: generateDummyLabels(), - createdAt: formatDateTime(v?.web?.createdAt), - isPublished: activeVersion?.version === v?.meta?.version, - isScheduled: scheduledVersion?.version === v?.meta?.version, - })); - }, [versions, itemPublishings]); + return versions.map((v) => { + let labels: WorkflowStatusLabel[] = []; + + if (statusLabels?.length && itemWorkflowStatus?.length) { + const labelZUIDs = itemWorkflowStatus.find( + (status) => status.itemVersion === v.meta?.version + )?.labelZUIDs; + + labels = labelZUIDs?.map((labelZUID) => + statusLabels.find((statusLabel) => statusLabel.ZUID === labelZUID) + ); + } + + return { + itemZUID: v.meta?.ZUID, + modelZUID: v.meta?.contentModelZUID, + itemVersionZUID: v.web?.versionZUID, + itemVersion: v.meta?.version, + labels, + createdAt: formatDateTime(v.web?.createdAt), + isPublished: activeVersion?.version === v.meta?.version, + isScheduled: scheduledVersion?.version === v.meta?.version, + }; + }); + }, [versions, itemPublishings, itemWorkflowStatus, statusLabels]); const handleLoadVersion = (version: number) => { const versionToLoad = versions?.find((v) => v?.meta?.version === version); @@ -160,6 +185,12 @@ export const VersionSelector = memo( color="inherit" endIcon={} onClick={(e) => setAnchorEl(e.currentTarget)} + disabled={ + isLoadingVersions || + isLoadingStatusLabels || + isLoadingItemPublishings || + isLoadingItemWorkflowStatus + } > v{activeVersion} diff --git a/src/shell/services/instance.ts b/src/shell/services/instance.ts index 159c5fe3e9..3f029217e5 100644 --- a/src/shell/services/instance.ts +++ b/src/shell/services/instance.ts @@ -22,6 +22,8 @@ import { Language, Data, StyleCategory, + WorkflowStatusLabel, + ItemWorkflowStatus, } from "./types"; import { batchApiRequests } from "../../utility/batchApiRequests"; @@ -607,7 +609,7 @@ export const instanceApi = createApi({ transformResponse: getResponseData, }), getItemWorkflowStatus: builder.query< - any, + ItemWorkflowStatus[], { modelZUID: string; itemZUID: string } >({ query: ({ modelZUID, itemZUID }) => @@ -650,7 +652,7 @@ export const instanceApi = createApi({ }), invalidatesTags: ["ItemWorkflowStatus"], }), - getWorkflowStatusLabels: builder.query({ + getWorkflowStatusLabels: builder.query({ query: () => `/env/labels`, transformResponse: getResponseData, providesTags: ["WorkflowStatusLabels"], diff --git a/src/shell/services/types.ts b/src/shell/services/types.ts index b8658b0d48..62526b4c6a 100644 --- a/src/shell/services/types.ts +++ b/src/shell/services/types.ts @@ -578,3 +578,31 @@ export type CommentReply = { mentions?: Mention[]; updatedAt: string; }; + +export type WorkflowStatusLabel = { + ZUID: string; + name: string; + description: string; + color: string; + allowPublish: boolean; + sort: number; + addPermissionRoles: string[]; + removePermissionRoles: string[]; + createdByUserZUID: string; + updatedByUserZUID: string; + createdAt: string; + updatedAt: string; +}; + +export type ItemWorkflowStatus = { + ZUID: string; + itemZUID: string; + setZUID: string; + itemVersionZUID: string; + itemVersion: number; + labelZUIDs: string[]; + createdByUserZUID: string; + updatedByUserZUID: string; + createdAt: string; + updatedAt: string; +}; From 02eccc16864828f3c1094c34685a1ce1b25b5cb1 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Tue, 10 Dec 2024 08:37:40 +0800 Subject: [PATCH 17/59] chore: cleanup --- .../components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index ee23e04fb0..649a750b2b 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -109,7 +109,6 @@ export const VersionItem = forwardRef( {data?.createdAt} - {/* {!!data?.labels?.length && ( */} )} - {/* )} */} {isAddNewLabelOpen && ( Date: Tue, 10 Dec 2024 13:21:22 +0800 Subject: [PATCH 18/59] task: label mapping --- .../VersionSelectorV2/VersionItem.tsx | 147 ++++++++++++------ .../VersionSelectorV2/index.tsx | 23 +-- .../ItemEditHeader/VersionSelectorV2/mocks.ts | 143 +++++++++++++++++ 3 files changed, 257 insertions(+), 56 deletions(-) create mode 100644 src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/mocks.ts diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 649a750b2b..34201eba67 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -9,6 +9,7 @@ import { InputAdornment, ListItemIcon, ListItemText, + Tooltip, } from "@mui/material"; import { ScheduleRounded, @@ -18,10 +19,29 @@ import { EditRounded, Check, } from "@mui/icons-material"; +import { useSelector } from "react-redux"; import { useGetWorkflowStatusLabelsQuery } from "../../../../../../../../../shell/services/instance"; -import { WorkflowStatusLabel } from "../../../../../../../../../shell/services/types"; +import { + User, + WorkflowStatusLabel, +} from "../../../../../../../../../shell/services/types"; +import { WORKFLOW_LABELS as statusLabels } from "./mocks"; +import { AppState } from "../../../../../../../../../shell/store/types"; +import { useGetUsersRolesQuery } from "../../../../../../../../../shell/services/accounts"; +const BG_COLOR_MAPPING: Record = { + "#0ba5ec": "blue.100", + "#12b76a": "green.100", + "#f79009": "yellow.100", + "#4e5ba6": "deepPurple.100", + "#7a5af8": "purple.100", + "#ee46bc": "pink.100", + "#ff5c08": "deepOrange.100", + "#f04438": "red.100", + "#f63d68": "#ffe4e8", + "#667085": "grey.100", +} as const; export type Version = { itemZUID: string; modelZUID: string; @@ -42,12 +62,18 @@ export const VersionItem = forwardRef( { data, isActive, onUpdateElementHeight }: VersionItemProps, ref: ForwardedRef ) => { + const user: User = useSelector((state: AppState) => state.user); const addNewLabelRef = useRef(null); const searchRef = useRef(null); - const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); + const { data: statusLabelsxx } = useGetWorkflowStatusLabelsQuery(); + const { data: usersRoles } = useGetUsersRolesQuery(); const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); const [filterKeyword, setFilterKeyword] = useState(""); + const currentUserRoleZUID = usersRoles?.find( + (userWithRole) => userWithRole.ZUID === user.ZUID + )?.role?.ZUID; + const handleOpenAddNewLabel = (evt: any) => { evt.stopPropagation(); @@ -124,9 +150,18 @@ export const VersionItem = forwardRef( clickable onClick={handleOpenAddNewLabel} label={label.name} - // color={generateRandomChipColor()} - color="primary" size="small" + sx={{ + color: label.color, + bgcolor: BG_COLOR_MAPPING[label.color.toLowerCase()], + + "&:hover": { + bgcolor: BG_COLOR_MAPPING[label.color.toLowerCase()], + }, + "&:focus": { + bgcolor: BG_COLOR_MAPPING[label.color.toLowerCase()], + }, + }} /> ))} {isActive && ( @@ -171,55 +206,75 @@ export const VersionItem = forwardRef( px: 1, }} /> - {statusLabels?.map((label, index) => ( - - - - - + {statusLabels?.map((label, index) => { + let title = ""; + + if ( + label.addPermissionRoles?.length && + !label.addPermissionRoles.includes(currentUserRoleZUID) + ) { + title = "Do not have permission to add this status"; + } + + if ( + label.removePermissionRoles?.length && + !label.removePermissionRoles.includes(currentUserRoleZUID) + ) { + title = "Do not have permission to remove this status"; + } + + return ( + + + + + + + + {label.name} + + + - {label.name} + {label.description} - - - - {label.description} - - - ))} + + + ); + })} (); - const { data: statusLabels, isLoading: isLoadingStatusLabels } = + const { data: statusLabelsxx, isLoading: isLoadingStatusLabels } = useGetWorkflowStatusLabelsQuery(); const { data: itemWorkflowStatus, isLoading: isLoadingItemWorkflowStatus } = useGetItemWorkflowStatusQuery( @@ -108,17 +109,19 @@ export const VersionSelector = memo( ); return versions.map((v) => { - let labels: WorkflowStatusLabel[] = []; + // let labels: WorkflowStatusLabel[] = []; - if (statusLabels?.length && itemWorkflowStatus?.length) { - const labelZUIDs = itemWorkflowStatus.find( - (status) => status.itemVersion === v.meta?.version - )?.labelZUIDs; + // if (statusLabels?.length && itemWorkflowStatus?.length) { + // const labelZUIDs = itemWorkflowStatus.find( + // (status) => status.itemVersion === v.meta?.version + // )?.labelZUIDs; - labels = labelZUIDs?.map((labelZUID) => - statusLabels.find((statusLabel) => statusLabel.ZUID === labelZUID) - ); - } + // labels = labelZUIDs?.map((labelZUID) => + // statusLabels.find((statusLabel) => statusLabel.ZUID === labelZUID) + // ); + // } + + const labels = WORKFLOW_LABELS; return { itemZUID: v.meta?.ZUID, diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/mocks.ts b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/mocks.ts new file mode 100644 index 0000000000..418ae0ee84 --- /dev/null +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/mocks.ts @@ -0,0 +1,143 @@ +import { WorkflowStatusLabel } from "../../../../../../../../../shell/services/types"; +export const WORKFLOW_LABELS: WorkflowStatusLabel[] = [ + { + ZUID: "36-14b315-4pp20v3d", + name: "Approved", + description: "Approved", + color: "#12b76a", + allowPublish: false, + sort: 3, + addPermissionRoles: ["55-8094cbd789-42sw0c"], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-14b315-d24ft", + name: "Draft", + description: "Content item is only available to preview in stage", + color: "#0BA5EC", + allowPublish: false, + sort: 1, + addPermissionRoles: [], + removePermissionRoles: ["55-8094cbd789-42sw0c"], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:02Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-n33d5-23v13w", + name: "Needs Review", + description: "Ready for review", + color: "#ff5c08", + allowPublish: false, + sort: 2, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:08Z", + updatedAt: "2024-11-25T06:21:23Z", + }, + { + ZUID: "36-14b315-4pp20v3e", + name: "Yellow", + description: "Test label", + color: "#f79009", + allowPublish: false, + sort: 4, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-14b315-4pp20v3e", + name: "Deep Purple", + description: "Test label", + color: "#4e5ba6", + allowPublish: false, + sort: 4, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-14b315-4pp20v3e", + name: "Purple", + description: "Test label", + color: "#7a5af8", + allowPublish: false, + sort: 4, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-14b315-4pp20v3e", + name: "Pink", + description: "Test label", + color: "#ee46bc", + allowPublish: false, + sort: 4, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-14b315-4pp20v3e", + name: "Red", + description: "Test label", + color: "#f04438", + allowPublish: false, + sort: 4, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-14b315-4pp20v3e", + name: "Rose", + description: "Test label", + color: "#f63d68", + allowPublish: false, + sort: 4, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, + { + ZUID: "36-14b315-4pp20v3e", + name: "Grey", + description: "Test label", + color: "#667085", + allowPublish: false, + sort: 4, + addPermissionRoles: [], + removePermissionRoles: [], + createdByUserZUID: "55-8094cbd789-42sw0c", + updatedByUserZUID: "55-8094cbd789-42sw0c", + createdAt: "2024-11-19T17:18:15Z", + updatedAt: "2024-11-25T06:21:22Z", + }, +]; From f458535af278b072d898f9d482a6b5086c16eb57 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 11 Dec 2024 11:03:27 +0800 Subject: [PATCH 19/59] task: add/remove status --- .../VersionSelectorV2/NoResults.tsx | 37 ++++++ .../VersionSelectorV2/VersionItem.tsx | 125 +++++++++++++----- .../VersionSelectorV2/index.tsx | 24 ++-- 3 files changed, 142 insertions(+), 44 deletions(-) create mode 100644 src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/NoResults.tsx diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/NoResults.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/NoResults.tsx new file mode 100644 index 0000000000..b3bce959cf --- /dev/null +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/NoResults.tsx @@ -0,0 +1,37 @@ +import { Button, Box, Stack, Typography } from "@mui/material"; +import { Search } from "@mui/icons-material"; + +import noResults from "../../../../../../../../../../public/images/noSearchResults.jpg"; + +type NoResultsProps = { + query: string; + onSearchAgain: () => void; +}; +export const NoResults = ({ query, onSearchAgain }: NoResultsProps) => { + return ( + + No Search Results + + + Your search "{query}" could not find any results + + + Try adjusting your search. We suggest check all words are spelled + correctly or try using different keywords. + + + + + ); +}; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 34201eba67..ad7aed2e09 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,4 +1,4 @@ -import { useState, forwardRef, useRef, ForwardedRef } from "react"; +import { useState, forwardRef, useRef, ForwardedRef, useMemo } from "react"; import { Box, MenuItem, @@ -20,15 +20,17 @@ import { Check, } from "@mui/icons-material"; import { useSelector } from "react-redux"; +import { useDebounce } from "react-use"; import { useGetWorkflowStatusLabelsQuery } from "../../../../../../../../../shell/services/instance"; import { User, WorkflowStatusLabel, } from "../../../../../../../../../shell/services/types"; -import { WORKFLOW_LABELS as statusLabels } from "./mocks"; +// import { WORKFLOW_LABELS as statusLabels } from "./mocks"; import { AppState } from "../../../../../../../../../shell/store/types"; import { useGetUsersRolesQuery } from "../../../../../../../../../shell/services/accounts"; +import { NoResults } from "./NoResults"; const BG_COLOR_MAPPING: Record = { "#0ba5ec": "blue.100", @@ -65,15 +67,35 @@ export const VersionItem = forwardRef( const user: User = useSelector((state: AppState) => state.user); const addNewLabelRef = useRef(null); const searchRef = useRef(null); - const { data: statusLabelsxx } = useGetWorkflowStatusLabelsQuery(); + const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); const { data: usersRoles } = useGetUsersRolesQuery(); const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); const [filterKeyword, setFilterKeyword] = useState(""); + const [debouncedFilterKeyword, setDebouncedFilterKeyword] = useState(""); + const [activeLabels, setActiveLabels] = useState( + data?.labels?.map((label) => label.ZUID) + ); const currentUserRoleZUID = usersRoles?.find( (userWithRole) => userWithRole.ZUID === user.ZUID )?.role?.ZUID; + useDebounce(() => setDebouncedFilterKeyword(filterKeyword), 200, [ + filterKeyword, + ]); + + const filteredStatusLabels = useMemo(() => { + onUpdateElementHeight(); + + if (!debouncedFilterKeyword) return statusLabels; + + return statusLabels?.filter((label) => + label.name + ?.toLowerCase() + .includes(debouncedFilterKeyword?.toLowerCase()?.trim()) + ); + }, [statusLabels, debouncedFilterKeyword]); + const handleOpenAddNewLabel = (evt: any) => { evt.stopPropagation(); @@ -89,6 +111,14 @@ export const VersionItem = forwardRef( } }; + const handleToggleLabel = (ZUID: string) => { + if (activeLabels?.includes(ZUID)) { + setActiveLabels(activeLabels.filter((label) => label !== ZUID)); + } else { + setActiveLabels([...activeLabels, ZUID]); + } + }; + return ( - {data.labels?.map((label) => ( - { + const labelData = statusLabels?.find( + (status) => status.ZUID === labelZUID + ); - "&:hover": { - bgcolor: BG_COLOR_MAPPING[label.color.toLowerCase()], - }, - "&:focus": { - bgcolor: BG_COLOR_MAPPING[label.color.toLowerCase()], - }, - }} - /> - ))} + return ( + + ); + })} {isActive && ( - {statusLabels?.map((label, index) => { + {!filteredStatusLabels?.length && filterKeyword && ( + { + setFilterKeyword(""); + searchRef.current?.querySelector("input").focus(); + }} + /> + )} + {filteredStatusLabels?.map((label, index) => { let title = ""; + const canRemove = label.removePermissionRoles?.length + ? label.removePermissionRoles.includes(currentUserRoleZUID) + : true; + const canAdd = label.addPermissionRoles?.length + ? label.addPermissionRoles.includes(currentUserRoleZUID) + : true; - if ( - label.addPermissionRoles?.length && - !label.addPermissionRoles.includes(currentUserRoleZUID) - ) { + if (!canAdd) { title = "Do not have permission to add this status"; } - if ( - label.removePermissionRoles?.length && - !label.removePermissionRoles.includes(currentUserRoleZUID) - ) { + if (!canRemove) { title = "Do not have permission to remove this status"; } @@ -235,9 +280,25 @@ export const VersionItem = forwardRef( borderBottom: index + 1 < statusLabels?.length ? 1 : 0, borderColor: "border", }} + onClick={() => { + if ( + (activeLabels.includes(label.ZUID) && canRemove) || + (!activeLabels.includes(label.ZUID) && canAdd) + ) { + handleToggleLabel(label.ZUID); + } + }} > - + (); - const { data: statusLabelsxx, isLoading: isLoadingStatusLabels } = + const { data: statusLabels, isLoading: isLoadingStatusLabels } = useGetWorkflowStatusLabelsQuery(); const { data: itemWorkflowStatus, isLoading: isLoadingItemWorkflowStatus } = useGetItemWorkflowStatusQuery( @@ -109,19 +109,19 @@ export const VersionSelector = memo( ); return versions.map((v) => { - // let labels: WorkflowStatusLabel[] = []; + let labels: WorkflowStatusLabel[] = []; - // if (statusLabels?.length && itemWorkflowStatus?.length) { - // const labelZUIDs = itemWorkflowStatus.find( - // (status) => status.itemVersion === v.meta?.version - // )?.labelZUIDs; + if (statusLabels?.length && itemWorkflowStatus?.length) { + const labelZUIDs = itemWorkflowStatus.find( + (status) => status.itemVersion === v.meta?.version + )?.labelZUIDs; - // labels = labelZUIDs?.map((labelZUID) => - // statusLabels.find((statusLabel) => statusLabel.ZUID === labelZUID) - // ); - // } + labels = labelZUIDs?.map((labelZUID) => + statusLabels.find((statusLabel) => statusLabel.ZUID === labelZUID) + ); + } - const labels = WORKFLOW_LABELS; + // const labels = WORKFLOW_LABELS; return { itemZUID: v.meta?.ZUID, From c7c9b67f6272d1ec1165b0aa8425529aab7feb64 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 11 Dec 2024 11:35:33 +0800 Subject: [PATCH 20/59] task: wire edit statuses button --- .../ItemEditHeader/VersionSelectorV2/VersionItem.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index ad7aed2e09..045fcfec51 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -21,6 +21,7 @@ import { } from "@mui/icons-material"; import { useSelector } from "react-redux"; import { useDebounce } from "react-use"; +import { useHistory } from "react-router"; import { useGetWorkflowStatusLabelsQuery } from "../../../../../../../../../shell/services/instance"; import { @@ -64,6 +65,7 @@ export const VersionItem = forwardRef( { data, isActive, onUpdateElementHeight }: VersionItemProps, ref: ForwardedRef ) => { + const history = useHistory(); const user: User = useSelector((state: AppState) => state.user); const addNewLabelRef = useRef(null); const searchRef = useRef(null); @@ -344,6 +346,7 @@ export const VersionItem = forwardRef( borderColor: "border", height: 44, }} + onClick={() => history.push("/settings/user/workflows")} > From 40363e742b7bf6f8f97fb9ecd9b49b789c7433ae Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Wed, 11 Dec 2024 16:01:27 +0800 Subject: [PATCH 21/59] task: wire add/remove label --- .../VersionSelectorV2/VersionItem.tsx | 46 ++++++++++++++----- .../VersionSelectorV2/index.tsx | 8 +++- src/shell/services/instance.ts | 10 +++- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 045fcfec51..3c9de0e412 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -20,10 +20,13 @@ import { Check, } from "@mui/icons-material"; import { useSelector } from "react-redux"; -import { useDebounce } from "react-use"; -import { useHistory } from "react-router"; +import { useDebounce, useUnmount } from "react-use"; +import { useHistory, useParams } from "react-router"; -import { useGetWorkflowStatusLabelsQuery } from "../../../../../../../../../shell/services/instance"; +import { + useGetWorkflowStatusLabelsQuery, + useUpdateItemWorkflowStatusMutation, +} from "../../../../../../../../../shell/services/instance"; import { User, WorkflowStatusLabel, @@ -50,6 +53,7 @@ export type Version = { modelZUID: string; itemVersionZUID: string; itemVersion: number; + itemWorkflowZUID: string; labels: WorkflowStatusLabel[]; createdAt: string; isPublished: boolean; @@ -66,11 +70,16 @@ export const VersionItem = forwardRef( ref: ForwardedRef ) => { const history = useHistory(); + const { modelZUID, itemZUID } = useParams<{ + modelZUID: string; + itemZUID: string; + }>(); const user: User = useSelector((state: AppState) => state.user); const addNewLabelRef = useRef(null); const searchRef = useRef(null); const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); const { data: usersRoles } = useGetUsersRolesQuery(); + const [updateItemWorkflowStatus] = useUpdateItemWorkflowStatusMutation(); const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); const [filterKeyword, setFilterKeyword] = useState(""); const [debouncedFilterKeyword, setDebouncedFilterKeyword] = useState(""); @@ -86,6 +95,8 @@ export const VersionItem = forwardRef( filterKeyword, ]); + useUnmount(() => saveLabelChanges()); + const filteredStatusLabels = useMemo(() => { onUpdateElementHeight(); @@ -107,7 +118,9 @@ export const VersionItem = forwardRef( // HACK: Prevents the dropdowm elements from flickering due to delayed height adjustment setTimeout(() => { - addNewLabelRef.current.style.visibility = "visible"; + if (addNewLabelRef.current) { + addNewLabelRef.current.style.visibility = "visible"; + } searchRef.current?.querySelector("input").focus(); }); } @@ -121,6 +134,17 @@ export const VersionItem = forwardRef( } }; + const saveLabelChanges = () => { + if (activeLabels.length !== data?.labels?.length) { + updateItemWorkflowStatus({ + modelZUID, + itemZUID, + itemWorkflowZUID: data?.itemWorkflowZUID, + labelZUIDs: activeLabels, + }); + } + }; + return ( { let title = ""; - const canRemove = label.removePermissionRoles?.length - ? label.removePermissionRoles.includes(currentUserRoleZUID) - : true; - const canAdd = label.addPermissionRoles?.length - ? label.addPermissionRoles.includes(currentUserRoleZUID) - : true; + const canRemove = + label.removePermissionRoles?.includes(currentUserRoleZUID); + const canAdd = + label.addPermissionRoles?.includes(currentUserRoleZUID); - if (!canAdd) { + if (!canAdd && !activeLabels.includes(label.ZUID)) { title = "Do not have permission to add this status"; } - if (!canRemove) { + if (!canRemove && activeLabels.includes(label.ZUID)) { title = "Do not have permission to remove this status"; } diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index b9c3fd6e65..c9869a4e4f 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -110,11 +110,14 @@ export const VersionSelector = memo( return versions.map((v) => { let labels: WorkflowStatusLabel[] = []; + let itemWorkflowZUID = ""; if (statusLabels?.length && itemWorkflowStatus?.length) { - const labelZUIDs = itemWorkflowStatus.find( + const workflowStatusData = itemWorkflowStatus.find( (status) => status.itemVersion === v.meta?.version - )?.labelZUIDs; + ); + const labelZUIDs = workflowStatusData?.labelZUIDs; + itemWorkflowZUID = workflowStatusData?.ZUID; labels = labelZUIDs?.map((labelZUID) => statusLabels.find((statusLabel) => statusLabel.ZUID === labelZUID) @@ -128,6 +131,7 @@ export const VersionSelector = memo( modelZUID: v.meta?.contentModelZUID, itemVersionZUID: v.web?.versionZUID, itemVersion: v.meta?.version, + itemWorkflowZUID, labels, createdAt: formatDateTime(v.web?.createdAt), isPublished: activeVersion?.version === v.meta?.version, diff --git a/src/shell/services/instance.ts b/src/shell/services/instance.ts index d1902554fc..66ecd933dc 100644 --- a/src/shell/services/instance.ts +++ b/src/shell/services/instance.ts @@ -635,7 +635,11 @@ export const instanceApi = createApi({ query: (payload) => ({ url: `/content/models/${payload.modelZUID}/items/${payload.itemZUID}/labels`, method: "POST", - body: payload, + body: { + itemVersionZUID: payload.itemVersionZUID, + itemVersion: payload.itemVersion, + label_zuids: payload.label_zuids, + }, }), invalidatesTags: ["ItemWorkflowStatus"], }), @@ -651,7 +655,9 @@ export const instanceApi = createApi({ query: ({ modelZUID, itemZUID, itemWorkflowZUID, labelZUIDs }) => ({ url: `/content/models/${modelZUID}/items/${itemZUID}/labels/${itemWorkflowZUID}`, method: "PUT", - body: labelZUIDs, + body: { + labelZUIDs, + }, }), invalidatesTags: ["ItemWorkflowStatus"], }), From 05029aebc149c1e2be16e42995097188cddf31b6 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 12 Dec 2024 10:26:21 +0800 Subject: [PATCH 22/59] task: invalidate itemworkflowstatus on content item update --- src/shell/store/content.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/shell/store/content.js b/src/shell/store/content.js index 981d795473..1bb2b14044 100644 --- a/src/shell/store/content.js +++ b/src/shell/store/content.js @@ -516,7 +516,9 @@ export function saveItem({ }, } ).then(async (res) => { - dispatch(instanceApi.util.invalidateTags(["ContentNav"])); + dispatch( + instanceApi.util.invalidateTags(["ContentNav", "ItemWorkflowStatus"]) + ); dispatch( instanceApi.util.invalidateTags([{ type: "ItemVersions", itemZUID }]) ); @@ -537,7 +539,12 @@ export function saveItem({ `${CONFIG.URL_PREVIEW_PROTOCOL}${itemBlockPreviewUrl}` ) ).then(() => - dispatch(instanceApi.util.invalidateTags(["ContentItems"])) + dispatch( + instanceApi.util.invalidateTags([ + "ContentItems", + "ItemWorkflowStatus", + ]) + ) ); } } From 1bf08ab9fe535fbdb4d99d069e62cfb79646d1cd Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 12 Dec 2024 10:41:48 +0800 Subject: [PATCH 23/59] task: show most recent label on the header --- .../VersionSelectorV2/VersionItem.tsx | 2 +- .../VersionSelectorV2/index.tsx | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 3c9de0e412..89f27b4741 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -36,7 +36,7 @@ import { AppState } from "../../../../../../../../../shell/store/types"; import { useGetUsersRolesQuery } from "../../../../../../../../../shell/services/accounts"; import { NoResults } from "./NoResults"; -const BG_COLOR_MAPPING: Record = { +export const BG_COLOR_MAPPING: Record = { "#0ba5ec": "blue.100", "#12b76a": "green.100", "#f79009": "yellow.100", diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index c9869a4e4f..df0b5a8474 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -8,6 +8,7 @@ import { VariableSizeList } from "react-window"; import AutoSizer from "react-virtualized-auto-sizer"; import { Row } from "./Row"; +import { BG_COLOR_MAPPING } from "./VersionItem"; // import { WORKFLOW_LABELS as statusLabels, WORKFLOW_LABELS } from "./mocks"; const dummyLabels = [ @@ -140,6 +141,15 @@ export const VersionSelector = memo( }); }, [versions, itemPublishings, itemWorkflowStatus, statusLabels]); + const latestItemWorkflowLabel = useMemo(() => { + if (!mappedVersions?.length || !activeVersion) return null; + + return ( + mappedVersions?.find((version) => version.itemVersion === activeVersion) + ?.labels?.[0] || null + ); + }, [mappedVersions, activeVersion]); + const handleLoadVersion = (version: number) => { const versionToLoad = versions?.find((v) => v?.meta?.version === version); @@ -200,7 +210,33 @@ export const VersionSelector = memo( } > v{activeVersion} - + {!!latestItemWorkflowLabel && ( + + )} Date: Mon, 16 Dec 2024 08:40:53 +0800 Subject: [PATCH 24/59] task: change dropdown position --- .../ItemEditHeader/VersionSelectorV2/index.tsx | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index df0b5a8474..83fd8c539c 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -20,17 +20,6 @@ const dummyLabels = [ "Published", "Scheduled", ]; -const generateDummyLabels = () => { - let count = Math.floor(Math.random() * dummyLabels.length) + 1; - const labels = []; - - while (count) { - labels.push(dummyLabels[Math.floor(Math.random() * dummyLabels.length)]); - count--; - } - - return labels; -}; const formatDateTime = (dateTimeString: string) => { if (!dateTimeString) return ""; @@ -125,8 +114,6 @@ export const VersionSelector = memo( ); } - // const labels = WORKFLOW_LABELS; - return { itemZUID: v.meta?.ZUID, modelZUID: v.meta?.contentModelZUID, @@ -246,7 +233,7 @@ export const VersionSelector = memo( horizontal: "right", }} transformOrigin={{ - vertical: -26, + vertical: -8, horizontal: "right", }} anchorEl={anchorEl} From 0aef49b898a10e3d6baa69a8a1a603cd2fac0ee1 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Mon, 16 Dec 2024 11:22:29 +0800 Subject: [PATCH 25/59] task: set max width for chip --- .../components/ItemEditHeader/VersionSelectorV2/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 83fd8c539c..74d073578e 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -132,8 +132,9 @@ export const VersionSelector = memo( if (!mappedVersions?.length || !activeVersion) return null; return ( - mappedVersions?.find((version) => version.itemVersion === activeVersion) - ?.labels?.[0] || null + mappedVersions + ?.find((version) => version.itemVersion === activeVersion) + ?.labels?.slice(-1)?.[0] || null ); }, [mappedVersions, activeVersion]); @@ -204,6 +205,7 @@ export const VersionSelector = memo( sx={{ ml: 0.5, color: latestItemWorkflowLabel.color, + maxWidth: 144, bgcolor: BG_COLOR_MAPPING[ latestItemWorkflowLabel.color?.toLowerCase() From 7c4a6c00c26934adfdff31283f7a2ddbf412ec7e Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Mon, 16 Dec 2024 13:02:24 +0800 Subject: [PATCH 26/59] task: show counter if active label is more than 1 --- .../VersionSelectorV2/index.tsx | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 74d073578e..1e24e43653 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -128,14 +128,10 @@ export const VersionSelector = memo( }); }, [versions, itemPublishings, itemWorkflowStatus, statusLabels]); - const latestItemWorkflowLabel = useMemo(() => { - if (!mappedVersions?.length || !activeVersion) return null; - - return ( - mappedVersions - ?.find((version) => version.itemVersion === activeVersion) - ?.labels?.slice(-1)?.[0] || null - ); + const activeVersionLabels = useMemo(() => { + return mappedVersions?.find( + (version) => version.itemVersion === activeVersion + )?.labels; }, [mappedVersions, activeVersion]); const handleLoadVersion = (version: number) => { @@ -198,33 +194,46 @@ export const VersionSelector = memo( } > v{activeVersion} - {!!latestItemWorkflowLabel && ( - + + + "&:hover": { + bgcolor: + BG_COLOR_MAPPING[ + activeVersionLabels + .slice(-1)?.[0] + .color?.toLowerCase() + ], + }, + "&:focus": { + bgcolor: + BG_COLOR_MAPPING[ + activeVersionLabels + .slice(-1)?.[0] + .color?.toLowerCase() + ], + }, + }} + /> + {activeVersionLabels?.length > 1 && ( + + )} + )} From 75b35a990c2dcfaa744f50160be73a21a319d28e Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Mon, 16 Dec 2024 15:43:43 +0800 Subject: [PATCH 27/59] task: add publish check --- .../src/app/views/ItemCreate/ItemCreate.tsx | 88 ++++++++++++++++--- .../ItemEditHeader/ItemEditHeaderActions.tsx | 88 +++++++++++++++---- .../VersionSelectorV2/index.tsx | 10 --- 3 files changed, 144 insertions(+), 42 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemCreate/ItemCreate.tsx b/src/apps/content-editor/src/app/views/ItemCreate/ItemCreate.tsx index ec0c7d4347..a2c50161c0 100644 --- a/src/apps/content-editor/src/app/views/ItemCreate/ItemCreate.tsx +++ b/src/apps/content-editor/src/app/views/ItemCreate/ItemCreate.tsx @@ -27,6 +27,7 @@ import { useCreateItemPublishingMutation, useGetContentItemQuery, useGetContentModelFieldsQuery, + useGetWorkflowStatusLabelsQuery, } from "../../../../../../shell/services/instance"; import { Error } from "../../components/Editor/Field/FieldShell"; import { @@ -100,6 +101,10 @@ export const ItemCreate = () => { isSuccess: isSuccessNewModelFields, isLoading: isFetchingNewModelFields, } = useGetContentModelFieldsQuery(modelZUID); + const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); + const hasAllowPublishLabel = statusLabels?.some( + (label) => label.allowPublish + ); // on mount and update modelZUID, load item fields useEffect(() => { @@ -295,28 +300,85 @@ export const ItemCreate = () => { break; case "publishNow": - // Make an api call to publish now - handlePublish(res.data.ZUID); - setWillRedirect(true); + if (hasAllowPublishLabel) { + // New items will by default have no workflow labels, therefore as long as there's a workflow label + // that is used to allow publish permissions, new content items should never be allowed to be published + // from the create new content item page + dispatch( + notify({ + message: `Cannot Publish: "${item.web.metaTitle}". Does not have a status that allows publishing`, + kind: "error", + }) + ); + history.push( + `/${ + model?.type === "block" ? "blocks" : "content" + }/${modelZUID}/${res.data.ZUID}` + ); + } else { + // Make an api call to publish now + handlePublish(res.data.ZUID); + setWillRedirect(true); + } break; case "schedulePublish": - // Open schedule publish flyout and redirect to item once done - setIsScheduleDialogOpen(true); - setWillRedirect(true); + if (hasAllowPublishLabel) { + // New items will by default have no workflow labels, therefore as long as there's a workflow label + // that is used to allow publish permissions, new content items should never be allowed to be published + // from the create new content item page + dispatch( + notify({ + message: `Cannot Publish: "${item.web.metaTitle}". Does not have a status that allows publishing`, + kind: "error", + }) + ); + history.push( + `/${ + model?.type === "block" ? "blocks" : "content" + }/${modelZUID}/${res.data.ZUID}` + ); + } else { + // Open schedule publish flyout and redirect to item once done + setIsScheduleDialogOpen(true); + setWillRedirect(true); + } break; case "publishAddNew": - // Publish but stay on page - handlePublish(res.data.ZUID); - setWillRedirect(false); - + if (hasAllowPublishLabel) { + // New items will by default have no workflow labels, therefore as long as there's a workflow label + // that is used to allow publish permissions, new content items should never be allowed to be published + // from the create new content item page + dispatch( + notify({ + message: `Cannot Publish: "${item.web.metaTitle}". Does not have a status that allows publishing`, + kind: "error", + }) + ); + } else { + // Publish but stay on page + handlePublish(res.data.ZUID); + setWillRedirect(false); + } break; case "schedulePublishAddNew": - // Open schedule publish flyout but stay on page once done - setIsScheduleDialogOpen(true); - setWillRedirect(false); + if (hasAllowPublishLabel) { + // New items will by default have no workflow labels, therefore as long as there's a workflow label + // that is used to allow publish permissions, new content items should never be allowed to be published + // from the create new content item page + dispatch( + notify({ + message: `Cannot Publish: "${item.web.metaTitle}". Does not have a status that allows publishing`, + kind: "error", + }) + ); + } else { + // Open schedule publish flyout but stay on page once done + setIsScheduleDialogOpen(true); + setWillRedirect(false); + } break; default: diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/ItemEditHeaderActions.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/ItemEditHeaderActions.tsx index 07c43a29fd..5ebf7c081c 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/ItemEditHeaderActions.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/ItemEditHeaderActions.tsx @@ -14,9 +14,11 @@ import { useDeleteItemPublishingMutation, useGetAuditsQuery, useGetItemPublishingsQuery, + useGetItemWorkflowStatusQuery, + useGetWorkflowStatusLabelsQuery, } from "../../../../../../../../shell/services/instance"; import { useHistory, useParams } from "react-router"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { SaveRounded, @@ -40,6 +42,8 @@ import { usePermission } from "../../../../../../../../shell/hooks/use-permissio import { ContentItemWithDirtyAndPublishing } from "../../../../../../../../shell/services/types"; import { ConfirmPublishModal } from "./ConfirmPublishModal"; import { SchedulePublish } from "../../../../../../../../shell/components/SchedulePublish"; +import { notify } from "../../../../../../../../shell/store/notifications"; +import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query"; const ITEM_STATES = { dirty: "dirty", @@ -83,7 +87,7 @@ export const ItemEditHeaderActions = ({ const { data: itemAudit } = useGetAuditsQuery({ affectedZUID: itemZUID, }); - const [createPublishing, { isLoading: publishing }] = + const [createPublishing, { isLoading: publishing, error: publishError }] = useCreateItemPublishingMutation(); const [deleteItemPublishing, { isLoading: unpublishing }] = useDeleteItemPublishingMutation(); @@ -97,6 +101,12 @@ export const ItemEditHeaderActions = ({ const activePublishing = itemPublishings?.find( (itemPublishing) => itemPublishing._active ); + const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); + const { data: itemWorkflowStatus, isLoading: isLoadingItemWorkflowStatus } = + useGetItemWorkflowStatusQuery( + { itemZUID, modelZUID }, + { skip: !itemZUID || !modelZUID } + ); const saveShortcut = useMetaKey("s", () => { if (!canUpdate) return; @@ -127,27 +137,56 @@ export const ItemEditHeaderActions = ({ } })(); + const allowPublish = useMemo(() => { + const allowPublishLabelZUIDs = statusLabels?.reduce((acc, next) => { + if (next.allowPublish) { + return (acc = [...acc, next.ZUID]); + } + + return acc; + }, []); + + if (!allowPublishLabelZUIDs?.length) return true; + + const itemWorkflowLabelZUIDs = itemWorkflowStatus?.find( + (i) => i.itemVersion === item?.meta?.version + )?.labelZUIDs; + + return itemWorkflowLabelZUIDs?.some((labelZUID) => + allowPublishLabelZUIDs?.includes(labelZUID) + ); + }, [statusLabels, itemWorkflowStatus, item?.meta?.version]); + const handlePublish = async () => { - // If item is scheduled, delete the scheduled publishing first - if (itemState === ITEM_STATES.scheduled) { - await deleteItemPublishing({ + if (allowPublish) { + // If item is scheduled, delete the scheduled publishing first + if (itemState === ITEM_STATES.scheduled) { + await deleteItemPublishing({ + modelZUID, + itemZUID, + publishingZUID: item?.scheduling?.ZUID, + }); + } + createPublishing({ modelZUID, itemZUID, - publishingZUID: item?.scheduling?.ZUID, + body: { + version: item?.meta.version, + publishAt: "now", + unpublishAt: "never", + }, + }).then(() => { + // Retain non rtk-query fetch of item publishing for legacy code + dispatch(fetchItemPublishing(modelZUID, itemZUID)); }); + } else { + dispatch( + notify({ + message: `Cannot Publish: "${item.web.metaTitle}". Does not have a status that allows publishing`, + kind: "error", + }) + ); } - createPublishing({ - modelZUID, - itemZUID, - body: { - version: item?.meta.version, - publishAt: "now", - unpublishAt: "never", - }, - }).then(() => { - // Retain non rtk-query fetch of item publishing for legacy code - dispatch(fetchItemPublishing(modelZUID, itemZUID)); - }); }; const handleUnpublish = async () => { @@ -479,7 +518,18 @@ export const ItemEditHeaderActions = ({ setPublishAfterSave={setPublishAfterSave} setScheduleAfterSave={setScheduleAfterSave} setUnpublishDialogOpen={setUnpublishDialogOpen} - setScheduledPublishDialogOpen={setScheduledPublishDialogOpen} + setScheduledPublishDialogOpen={(open) => { + if (!allowPublish) { + dispatch( + notify({ + message: `Cannot Publish: "${item.web.metaTitle}". Does not have a status that allows publishing`, + kind: "error", + }) + ); + } else { + setScheduledPublishDialogOpen(open); + } + }} setPublishAfterUnschedule={() => { setScheduledPublishDialogOpen(true); setPublishAfterUnschedule(true); diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 1e24e43653..674a373d48 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -11,16 +11,6 @@ import { Row } from "./Row"; import { BG_COLOR_MAPPING } from "./VersionItem"; // import { WORKFLOW_LABELS as statusLabels, WORKFLOW_LABELS } from "./mocks"; -const dummyLabels = [ - "Approved", - "Draft", - "In Review", - "For Publish", - "Blocked", - "Published", - "Scheduled", -]; - const formatDateTime = (dateTimeString: string) => { if (!dateTimeString) return ""; From 38a56c874bf81f93b98507b2f26603964074c0ab Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Tue, 17 Dec 2024 12:29:06 +0800 Subject: [PATCH 28/59] task: simplify row calculation --- .../ItemEditHeader/VersionSelectorV2/Row.tsx | 17 +- .../VersionSelectorV2/VersionItem.tsx | 584 +++++++++--------- .../VersionSelectorV2/index.tsx | 2 - 3 files changed, 302 insertions(+), 301 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx index 5ac15f095d..c04ed1f34f 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx @@ -3,6 +3,7 @@ import { MenuItem } from "@mui/material"; import { areEqual } from "react-window"; import { Version, VersionItem } from "./VersionItem"; +import { useResizeObserver } from "../../../../../../../../../shell/hooks/useResizeObserver"; type RowProps = { index: number; @@ -17,12 +18,15 @@ type RowProps = { export const Row = memo(({ index, style, data }: RowProps) => { const rowRef = useRef(null); const version = data?.versions[index]; + const dimensions = useResizeObserver(rowRef); + + console.log("row rerendered", index); useEffect(() => { - if (rowRef.current) { - data?.setRowHeight(index, rowRef.current?.clientHeight); + if (!!dimensions) { + data?.setRowHeight(index, dimensions?.height); } - }, [rowRef]); + }, [dimensions]); return ( { key={version?.itemVersionZUID} data={version} isActive={data?.activeVersion === version?.itemVersion} - onUpdateElementHeight={() => { - setTimeout(() => { - data?.setRowHeight(index, rowRef.current?.clientHeight); - }); - }} /> ); }, areEqual); + +Row.displayName = "VersionRow"; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 89f27b4741..318ad938db 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -1,4 +1,11 @@ -import { useState, forwardRef, useRef, ForwardedRef, useMemo } from "react"; +import { + memo, + useState, + forwardRef, + useRef, + ForwardedRef, + useMemo, +} from "react"; import { Box, MenuItem, @@ -22,6 +29,7 @@ import { import { useSelector } from "react-redux"; import { useDebounce, useUnmount } from "react-use"; import { useHistory, useParams } from "react-router"; +import { areEqual } from "react-window"; import { useGetWorkflowStatusLabelsQuery, @@ -62,324 +70,318 @@ export type Version = { type VersionItemProps = { data: Version; isActive: boolean; - onUpdateElementHeight: () => void; }; -export const VersionItem = forwardRef( - ( - { data, isActive, onUpdateElementHeight }: VersionItemProps, - ref: ForwardedRef - ) => { - const history = useHistory(); - const { modelZUID, itemZUID } = useParams<{ - modelZUID: string; - itemZUID: string; - }>(); - const user: User = useSelector((state: AppState) => state.user); - const addNewLabelRef = useRef(null); - const searchRef = useRef(null); - const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); - const { data: usersRoles } = useGetUsersRolesQuery(); - const [updateItemWorkflowStatus] = useUpdateItemWorkflowStatusMutation(); - const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); - const [filterKeyword, setFilterKeyword] = useState(""); - const [debouncedFilterKeyword, setDebouncedFilterKeyword] = useState(""); - const [activeLabels, setActiveLabels] = useState( - data?.labels?.map((label) => label.ZUID) - ); - - const currentUserRoleZUID = usersRoles?.find( - (userWithRole) => userWithRole.ZUID === user.ZUID - )?.role?.ZUID; +export const VersionItem = memo( + forwardRef( + ( + { data, isActive }: VersionItemProps, + ref: ForwardedRef + ) => { + const history = useHistory(); + const { modelZUID, itemZUID } = useParams<{ + modelZUID: string; + itemZUID: string; + }>(); + const user: User = useSelector((state: AppState) => state.user); + const addNewLabelRef = useRef(null); + const searchRef = useRef(null); + const { data: statusLabels } = useGetWorkflowStatusLabelsQuery(); + const { data: usersRoles } = useGetUsersRolesQuery(); + const [updateItemWorkflowStatus] = useUpdateItemWorkflowStatusMutation(); + const [isAddNewLabelOpen, setIsAddNewLabelOpen] = useState(false); + const [filterKeyword, setFilterKeyword] = useState(""); + const [debouncedFilterKeyword, setDebouncedFilterKeyword] = useState(""); + const [activeLabels, setActiveLabels] = useState( + data?.labels?.map((label) => label.ZUID) + ); - useDebounce(() => setDebouncedFilterKeyword(filterKeyword), 200, [ - filterKeyword, - ]); + const currentUserRoleZUID = usersRoles?.find( + (userWithRole) => userWithRole.ZUID === user.ZUID + )?.role?.ZUID; - useUnmount(() => saveLabelChanges()); + useDebounce(() => setDebouncedFilterKeyword(filterKeyword), 200, [ + filterKeyword, + ]); - const filteredStatusLabels = useMemo(() => { - onUpdateElementHeight(); + useUnmount(() => saveLabelChanges()); - if (!debouncedFilterKeyword) return statusLabels; + const filteredStatusLabels = useMemo(() => { + if (!debouncedFilterKeyword) return statusLabels; - return statusLabels?.filter((label) => - label.name - ?.toLowerCase() - .includes(debouncedFilterKeyword?.toLowerCase()?.trim()) - ); - }, [statusLabels, debouncedFilterKeyword]); + return statusLabels?.filter((label) => + label.name + ?.toLowerCase() + .includes(debouncedFilterKeyword?.toLowerCase()?.trim()) + ); + }, [statusLabels, debouncedFilterKeyword]); - const handleOpenAddNewLabel = (evt: any) => { - evt.stopPropagation(); + const handleOpenAddNewLabel = (evt: any) => { + evt.stopPropagation(); - if (isActive) { - setIsAddNewLabelOpen((prev) => !prev); - onUpdateElementHeight(); + if (isActive) { + setIsAddNewLabelOpen((prev) => !prev); - // HACK: Prevents the dropdowm elements from flickering due to delayed height adjustment - setTimeout(() => { - if (addNewLabelRef.current) { - addNewLabelRef.current.style.visibility = "visible"; - } - searchRef.current?.querySelector("input").focus(); - }); - } - }; + setTimeout(() => { + searchRef.current?.querySelector("input").focus(); + }); + } + }; - const handleToggleLabel = (ZUID: string) => { - if (activeLabels?.includes(ZUID)) { - setActiveLabels(activeLabels.filter((label) => label !== ZUID)); - } else { - setActiveLabels([...activeLabels, ZUID]); - } - }; + const handleToggleLabel = (ZUID: string) => { + if (activeLabels?.includes(ZUID)) { + setActiveLabels(activeLabels.filter((label) => label !== ZUID)); + } else { + setActiveLabels([...activeLabels, ZUID]); + } + }; - const saveLabelChanges = () => { - if (activeLabels.length !== data?.labels?.length) { - updateItemWorkflowStatus({ - modelZUID, - itemZUID, - itemWorkflowZUID: data?.itemWorkflowZUID, - labelZUIDs: activeLabels, - }); - } - }; + const saveLabelChanges = () => { + if (activeLabels.length !== data?.labels?.length) { + updateItemWorkflowStatus({ + modelZUID, + itemZUID, + itemWorkflowZUID: data?.itemWorkflowZUID, + labelZUIDs: activeLabels, + }); + } + }; - return ( - - - - - v{data?.itemVersion} + return ( + + + + + v{data?.itemVersion} + + {data?.isPublished && ( + + + + Published + + + )} + {data?.isScheduled && ( + + + + Scheduled + + + )} + + + {data?.createdAt} - {data?.isPublished && ( - - - - Published - - - )} - {data?.isScheduled && ( - - - - Scheduled - - - )} - - {data?.createdAt} - - - - {activeLabels?.map((labelZUID) => { - const labelData = statusLabels?.find( - (status) => status.ZUID === labelZUID - ); + + {activeLabels?.map((labelZUID) => { + const labelData = statusLabels?.find( + (status) => status.ZUID === labelZUID + ); - return ( + return ( + + ); + })} + {isActive && ( {}} + deleteIcon={} + /> + )} + + {isAddNewLabelOpen && ( + evt.stopPropagation()} + borderTop={1} + borderColor="border" + width="100%" + overflow="hidden" + > + setFilterKeyword(evt.currentTarget.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + placeholder="Search status" size="small" + fullWidth sx={{ - color: labelData.color, - bgcolor: BG_COLOR_MAPPING[labelData.color.toLowerCase()], - - "&:hover": { - bgcolor: BG_COLOR_MAPPING[labelData.color.toLowerCase()], - }, - "&:focus": { - bgcolor: BG_COLOR_MAPPING[labelData.color.toLowerCase()], - }, + my: 1.5, + px: 1, }} /> - ); - })} - {isActive && ( - {}} - deleteIcon={} - /> - )} - - {isAddNewLabelOpen && ( - evt.stopPropagation()} - borderTop={1} - borderColor="border" - width="100%" - // HACK: Prevents the dropdowm elements from flickering due to delayed height adjustment - visibility="hidden" - > - setFilterKeyword(evt.currentTarget.value)} - InputProps={{ - startAdornment: ( - - - - ), - }} - placeholder="Search status" - size="small" - fullWidth - sx={{ - my: 1.5, - px: 1, - }} - /> - {!filteredStatusLabels?.length && filterKeyword && ( - { - setFilterKeyword(""); - searchRef.current?.querySelector("input").focus(); - }} - /> - )} - {filteredStatusLabels?.map((label, index) => { - let title = ""; - const canRemove = - label.removePermissionRoles?.includes(currentUserRoleZUID); - const canAdd = - label.addPermissionRoles?.includes(currentUserRoleZUID); + {!filteredStatusLabels?.length && filterKeyword && ( + { + setFilterKeyword(""); + searchRef.current?.querySelector("input").focus(); + }} + /> + )} + {filteredStatusLabels?.map((label, index) => { + let title = ""; + const canRemove = + label.removePermissionRoles?.includes(currentUserRoleZUID); + const canAdd = + label.addPermissionRoles?.includes(currentUserRoleZUID); - if (!canAdd && !activeLabels.includes(label.ZUID)) { - title = "Do not have permission to add this status"; - } + if (!canAdd && !activeLabels.includes(label.ZUID)) { + title = "Do not have permission to add this status"; + } - if (!canRemove && activeLabels.includes(label.ZUID)) { - title = "Do not have permission to remove this status"; - } + if (!canRemove && activeLabels.includes(label.ZUID)) { + title = "Do not have permission to remove this status"; + } - return ( - - { - if ( - (activeLabels.includes(label.ZUID) && canRemove) || - (!activeLabels.includes(label.ZUID) && canAdd) - ) { - handleToggleLabel(label.ZUID); - } - }} - > - - - - - - {label.name} - - - - + { + if ( + (activeLabels.includes(label.ZUID) && canRemove) || + (!activeLabels.includes(label.ZUID) && canAdd) + ) { + handleToggleLabel(label.ZUID); + } }} > - {label.description} - - - - ); - })} - history.push("/settings/user/workflows")} - > - - - - Edit Statuses - - - )} - - ); - } + + + + + + {label.name} + + + + + {label.description} + + + + ); + })} + history.push("/settings/user/workflows")} + > + + + + Edit Statuses + + + )} + + ); + } + ), + areEqual ); VersionItem.displayName = "VersionItem"; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index 674a373d48..bce7f06e5b 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -9,7 +9,6 @@ import AutoSizer from "react-virtualized-auto-sizer"; import { Row } from "./Row"; import { BG_COLOR_MAPPING } from "./VersionItem"; -// import { WORKFLOW_LABELS as statusLabels, WORKFLOW_LABELS } from "./mocks"; const formatDateTime = (dateTimeString: string) => { if (!dateTimeString) return ""; @@ -148,7 +147,6 @@ export const VersionSelector = memo( 0 ); - // List height needs to at most be 540px setListHeight(totalHeight <= 540 ? totalHeight : 540); return rowHeights.current?.[index] || 90; From 9bda0dc58e36b798ec2224cbce3859d0a25d3643 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Tue, 17 Dec 2024 14:15:17 +0800 Subject: [PATCH 29/59] task: optimize row height calculation --- .../ItemEditHeader/VersionSelectorV2/Row.tsx | 2 -- .../VersionSelectorV2/index.tsx | 32 +++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx index c04ed1f34f..6557202c09 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx @@ -20,8 +20,6 @@ export const Row = memo(({ index, style, data }: RowProps) => { const version = data?.versions[index]; const dimensions = useResizeObserver(rowRef); - console.log("row rerendered", index); - useEffect(() => { if (!!dimensions) { data?.setRowHeight(index, dimensions?.height); diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx index bce7f06e5b..9a8c2dff00 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/index.tsx @@ -1,4 +1,4 @@ -import { useState, memo, useMemo, useRef } from "react"; +import { useState, memo, useMemo, useRef, useEffect } from "react"; import { Button, Tooltip, Chip, MenuList, Popover } from "@mui/material"; import { KeyboardArrowDownRounded } from "@mui/icons-material"; import { useParams } from "react-router"; @@ -36,6 +36,10 @@ import { import { Version } from "./VersionItem"; import { WorkflowStatusLabel } from "../../../../../../../../../shell/services/types"; +export let ROW_HEIGHTS: Record = {}; +export const DEFAULT_ROW_HEIGHT = 66; +const DEFAULT_LIST_HEIGHT = 540; + type VersionSelectorProps = { activeVersion: number; }; @@ -45,7 +49,7 @@ export const VersionSelector = memo( const listRef = useRef(null); const rowHeights = useRef(null); const [anchorEl, setAnchorEl] = useState(null); - const [listHeight, setListHeight] = useState(0); + const [listHeight, setListHeight] = useState(DEFAULT_LIST_HEIGHT); const { modelZUID, itemZUID } = useParams<{ modelZUID: string; itemZUID: string; @@ -123,6 +127,10 @@ export const VersionSelector = memo( )?.labels; }, [mappedVersions, activeVersion]); + useEffect(() => { + ROW_HEIGHTS = {}; + }, []); + const handleLoadVersion = (version: number) => { const versionToLoad = versions?.find((v) => v?.meta?.version === version); @@ -137,19 +145,23 @@ export const VersionSelector = memo( }; const setRowHeight = (index: number, size: number) => { - rowHeights.current = { ...rowHeights?.current, [index]: size }; - listRef.current?.resetAfterIndex(index); + if (ROW_HEIGHTS[index] !== size) { + ROW_HEIGHTS = { ...ROW_HEIGHTS, [index]: size }; + listRef.current?.resetAfterIndex(index); + } }; const getRowHeight = (index: number) => { - const totalHeight = +Object.values(rowHeights.current || {}).reduce( - (acc: number, curr: number) => acc + curr, - 0 - ); + setTimeout(() => { + const totalHeight = +Object.values(ROW_HEIGHTS).reduce( + (acc: number, curr: number) => acc + curr, + 0 + ); - setListHeight(totalHeight <= 540 ? totalHeight : 540); + setListHeight(totalHeight < 540 ? totalHeight : 540); + }); - return rowHeights.current?.[index] || 90; + return ROW_HEIGHTS[index] || DEFAULT_ROW_HEIGHT; }; return ( From 3fee7d79f8ee41b76a3a90561babe3310f5298d2 Mon Sep 17 00:00:00 2001 From: "geodem.dev" <74890703+geodem127@users.noreply.github.com> Date: Thu, 19 Dec 2024 07:12:49 +0800 Subject: [PATCH 30/59] =?UTF-8?q?[feature/2132-workflow-status-labels]:=20?= =?UTF-8?q?Create=20workflows=20page=20|=20status=E2=80=A6=20(#3100)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … labels --- public/images/restricted-image.svg | 13 + .../schema/src/app/components/NoResults.tsx | 8 +- src/apps/settings/src/app/App.js | 11 +- .../src/app/components/Nav/SettingsNav.tsx | 21 + .../views/User/Workflows/RestrictedPage.tsx | 168 ++++++ .../Workflows/authorized/ActiveStatus.tsx | 94 ++++ .../authorized/DeactivatedStatus.tsx | 37 ++ .../User/Workflows/authorized/StatusLabel.tsx | 329 +++++++++++ .../forms-dialogs/DeactivationDialog.tsx | 114 ++++ .../forms-dialogs/StatusLabelForm.tsx | 521 ++++++++++++++++++ .../authorized/forms-dialogs/index.tsx | 130 +++++ .../views/User/Workflows/authorized/index.tsx | 257 +++++++++ .../src/app/views/User/Workflows/index.tsx | 42 ++ src/shell/services/instance.ts | 64 ++- src/shell/services/types.ts | 96 ++++ 15 files changed, 1899 insertions(+), 6 deletions(-) create mode 100644 public/images/restricted-image.svg create mode 100644 src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx create mode 100644 src/apps/settings/src/app/views/User/Workflows/authorized/ActiveStatus.tsx create mode 100644 src/apps/settings/src/app/views/User/Workflows/authorized/DeactivatedStatus.tsx create mode 100644 src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx create mode 100644 src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx create mode 100644 src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx create mode 100644 src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/index.tsx create mode 100644 src/apps/settings/src/app/views/User/Workflows/authorized/index.tsx create mode 100644 src/apps/settings/src/app/views/User/Workflows/index.tsx diff --git a/public/images/restricted-image.svg b/public/images/restricted-image.svg new file mode 100644 index 0000000000..94152cab0c --- /dev/null +++ b/public/images/restricted-image.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/apps/schema/src/app/components/NoResults.tsx b/src/apps/schema/src/app/components/NoResults.tsx index f7f2f55dfb..1fe04e1dca 100644 --- a/src/apps/schema/src/app/components/NoResults.tsx +++ b/src/apps/schema/src/app/components/NoResults.tsx @@ -38,7 +38,13 @@ export const NoResults = ({ type, searchTerm, onButtonClick, sx }: Props) => { }} > No search results - + {TEXT_CONFIG[type].header.replace("{searchTerm}", searchTerm)} diff --git a/src/apps/settings/src/app/App.js b/src/apps/settings/src/app/App.js index a678fdac2f..cd41902d58 100644 --- a/src/apps/settings/src/app/App.js +++ b/src/apps/settings/src/app/App.js @@ -24,7 +24,7 @@ import { fetchFontsInstalled, } from "shell/store/settings"; import { ResizableContainer } from "../../../../shell/components/ResizeableContainer"; - +import Workflows from "./views/User/Workflows"; // Makes sure that other apps using legacy theme does not get affected with the palette const customTheme = createTheme(legacyTheme, { palette: { @@ -138,7 +138,10 @@ export default connect((state) => ({ ({ )} /> + { const location = useLocation(); @@ -110,6 +118,9 @@ export const SettingsNav = () => { instance: instanceSettings?.filter((setting) => setting.label.toLowerCase().includes(keyword) ), + user: USER_SETTINGS_CAT?.filter((setting) => + setting.label.toLowerCase().includes(keyword) + ), meta: GLOBAL_META_CAT?.filter((setting) => setting.label.toLowerCase().includes(keyword) ), @@ -124,6 +135,7 @@ export const SettingsNav = () => { return { instance: instanceSettings, + user: USER_SETTINGS_CAT, meta: GLOBAL_META_CAT, styles: styleSettings, fonts: FONTS_CAT, @@ -141,6 +153,7 @@ export const SettingsNav = () => { > {keyword && !navItems.fonts?.length && + !navItems.user?.length && !navItems.instance?.length && !navItems.meta?.length && !navItems.styles?.length ? ( @@ -164,6 +177,14 @@ export const SettingsNav = () => { tree={navItems.instance} selected={location.pathname} /> + + } + tree={navItems.user} + selected={location.pathname} + /> + = ({ + name, + role, + email, + imageUrl = "", +}) => ( + theme.palette.border} + > + + + + {name} + + {`${role} • ${email}`} + + +); + +const RestrictedPage = () => { + const { isLoading, isError, data } = useGetUsersRolesQuery(); + + const profileList = data + ?.filter((profile) => AUTHORIZED_ROLES.includes(profile?.role?.name)) + .map((item, index) => ({ + id: item?.ZUID, + name: `${item?.firstName} ${item?.lastName}`, + role: item?.role?.name, + email: item?.email, + imageUrl: "", + sort: + (item?.role?.name && + ROLE_ORDER_MAPPING[ + item?.role?.name as keyof typeof ROLE_ORDER_MAPPING + ]) || + index + 2, + })) + .sort((a, b) => a.sort - b.sort); + + return ( + + + + Workflows + + + + + + + + You need permission to view and edit workflows + + + Contact the instance owner or administrators listed below to + upgrade your role to Admin or Owner. + + + + {!isLoading && + !isError && + profileList.length > 0 && + profileList.map((profile) => ( + + ))} + + + + + + + + + ); +}; + +export default RestrictedPage; diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/ActiveStatus.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/ActiveStatus.tsx new file mode 100644 index 0000000000..f1d2e115e9 --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/ActiveStatus.tsx @@ -0,0 +1,94 @@ +import { FC, useCallback, useEffect, useState } from "react"; +import { useDrop } from "react-dnd"; +import { Box } from "@mui/material"; +import { useUpdateWorkflowStatusLabelOrderMutation } from "../../../../../../../../shell/services/instance"; +import { useFormDialogContext } from "./forms-dialogs"; +import { StatusLabelSorting } from "."; +import { StatusLabel, StatusLabelLoader } from "./StatusLabel"; +import { UpdateSortingOrder } from "../../../../../../../../shell/services/types"; + +type ActiveStatusProps = { + labels: StatusLabelSorting[]; + isLoading: boolean; +}; + +const ActiveStatus: FC = ({ labels, isLoading = false }) => { + const { focusedLabel } = useFormDialogContext(); + const [statusLabels, setStatusLabels] = + useState(labels); + const [updateWorkflowStatusLabelOrder] = + useUpdateWorkflowStatusLabelOrderMutation(); + + const findCard = useCallback( + (id: string) => { + const label = statusLabels.find((c) => c.id === id); + return { label, index: statusLabels.indexOf(label) }; + }, + [statusLabels] + ); + + const moveCard = useCallback( + (id: string, atIndex: number) => { + const { label, index } = findCard(id); + + if (label) { + const updatedLabels = [...statusLabels]; + updatedLabels.splice(index, 1); + updatedLabels.splice(atIndex, 0, label); + + setStatusLabels(updatedLabels); + } + }, + [findCard, statusLabels] + ); + + const onReorder = useCallback(async () => { + const requestPayload: UpdateSortingOrder[] = statusLabels.map( + (item, index) => ({ + ZUID: item.id, + sort: index + 1, + }) + ); + + try { + await updateWorkflowStatusLabelOrder(requestPayload); + } catch (error) { + console.error("Error during reorder:", error); + } + }, [statusLabels, updateWorkflowStatusLabelOrder]); + + const [, drop] = useDrop(() => ({ + accept: "draggable", + })); + + useEffect(() => { + if (!isLoading) { + setStatusLabels(labels); + } + }, [labels, isLoading]); + + return ( + <> + {isLoading ? ( + + ) : ( + + {statusLabels.map((label, index) => ( + + ))} + + )} + + ); +}; + +export default ActiveStatus; diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/DeactivatedStatus.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/DeactivatedStatus.tsx new file mode 100644 index 0000000000..4c4a58ba1f --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/DeactivatedStatus.tsx @@ -0,0 +1,37 @@ +import { FC, useEffect, useState } from "react"; +import { Box, Collapse } from "@mui/material"; +import { StatusLabelSorting } from "."; +import { StatusLabel, StatusLabelLoader } from "./StatusLabel"; + +type DeactivatedStatusProps = { + labels: StatusLabelSorting[]; + isLoading: boolean; +}; + +const DeactivatedStatus: FC = ({ + labels, + isLoading, +}) => { + return ( + + {isLoading ? ( + + ) : ( + <> + {labels.map((label: StatusLabelSorting, index: number) => ( + + + + ))} + + )} + + ); +}; + +export default DeactivatedStatus; diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx new file mode 100644 index 0000000000..fe4f1c36dd --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx @@ -0,0 +1,329 @@ +import { MouseEvent, FC, ReactElement, useState, useCallback } from "react"; +import { useDrag, useDrop } from "react-dnd"; +import { + IconButton, + Menu, + MenuItem, + Fade, + Box, + Paper, + Collapse, + Typography, + Card, + CardActions, + CardContent, + ListItemIcon, + Skeleton, + alpha, +} from "@mui/material"; +import Brightness1Icon from "@mui/icons-material/Brightness1"; +import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; +import DriveFileRenameOutlineIcon from "@mui/icons-material/DriveFileRenameOutline"; +import PauseCircleOutlineRoundedIcon from "@mui/icons-material/PauseCircleOutlineRounded"; +import DragIndicatorRoundedIcon from "@mui/icons-material/DragIndicatorRounded"; +import { ClickAwayListener } from "@mui/base/ClickAwayListener"; +import { useFormDialogContext } from "./forms-dialogs"; +import { StatusLabel as StatusLabelTypes } from "../../../../../../../../shell/services/types"; + +export type StatusLabelProps = { + id: string; + isFiltered: boolean; + moveCard?: (id: string, to: number) => void; + findCard?: (id: string) => { index: number }; + isDeactivated?: boolean; + onReorder?: () => void; + isFocused?: boolean; + data: StatusLabelTypes; +}; + +export const StatusLabel: FC = ({ + id, + isFiltered, + moveCard, + findCard, + isDeactivated, + onReorder, + isFocused, + data, +}: StatusLabelProps) => { + const { setFocusedLabel, openStatusLabelForm } = useFormDialogContext(); + + const originalIndex = isDeactivated ? 0 : findCard?.(id)?.index; + + const [{ isDragging }, drag, preview] = useDrag( + () => ({ + type: "draggable", + item: { id, originalIndex }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + options: { + dropEffect: "copy", + }, + end: (item, monitor) => { + const { id: droppedId, originalIndex } = item; + const didDrop = monitor.didDrop(); + if (!didDrop) { + moveCard?.(droppedId, originalIndex); + } + }, + }), + [id, originalIndex, moveCard] + ); + + const [, drop] = useDrop( + () => ({ + accept: "draggable", + hover({ id: draggedId }: { id: string; originalIndex: number }) { + if (draggedId !== id) { + const { index: overIndex } = findCard?.(id); + moveCard?.(draggedId, overIndex); + } + }, + drop: () => { + onReorder?.(); + }, + }), + [findCard, moveCard, onReorder] + ); + + const withClickAwayListener = useCallback( + (component: ReactElement) => { + if (isFocused) { + return ( + setFocusedLabel("")}> + {component} + + ); + } + return component; + }, + [isFocused, setFocusedLabel] + ); + + return ( + + {withClickAwayListener( + theme.palette.border, + backgroundColor: isFocused + ? (theme) => alpha(theme.palette.primary.light, 0.1) + : "background.paper", + }} + > + drag(drop(node as HTMLElement)) + } + sx={{ + cursor: isDeactivated ? "default" : "grab", + display: "flex", + justifyContent: "center", + alignItems: "center", + flexGrow: 0, + }} + > + + + + + + + + {data?.name} + + + {data?.description} + + + + + + + + )} + + ); +}; + +const MoreActionsMenu = ({ + data, + isDeactivated, +}: { + data: StatusLabelTypes; + isDeactivated?: boolean; +}) => { + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + + const { openStatusLabelForm, openDeactivationDialog } = + useFormDialogContext(); + + const handleOpen = (event: MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const openEditForm = () => { + openStatusLabelForm({ values: data, isDeactivated: isDeactivated }); + setAnchorEl(null); + }; + + const openDeleteDialog = () => { + openDeactivationDialog({ ZUID: data?.ZUID, name: data?.name }); + setAnchorEl(null); + }; + + return ( + <> + + + + + + + + + + Edit Status + + + + {!isDeactivated && ( + + + + + + Deactivate Status + + + )} + + + ); +}; + +export const StatusLabelSkeleton = ({ width = 1 }: { width: number }) => { + return ( + + + + + + + + + + + + ); +}; + +export const StatusLabelLoader = () => ( + + + + + +); diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx new file mode 100644 index 0000000000..495b32dbc8 --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx @@ -0,0 +1,114 @@ +import { FC, useState } from "react"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import PauseCircleOutlineRoundedIcon from "@mui/icons-material/PauseCircleOutlineRounded"; +import Typography from "@mui/material/Typography"; +import { useDispatch } from "react-redux"; +import { notify } from "../../../../../../../../../shell/store/notifications"; +import { LoadingButton } from "@mui/lab"; +import { useDeactivateWorkflowStatusLabelMutation } from "../../../../../../../../../shell/services/instance"; + +type DeactivationDialogProps = { + open: boolean; + onClose: () => void; + name: string; + ZUID: string; + callBack?: () => void; +}; + +const DeactivationDialog: FC = ({ + open, + onClose, + name, + ZUID, + callBack, +}) => { + const dispatch = useDispatch(); + const [deactivateWorkflowStatusLabel, { isLoading }] = + useDeactivateWorkflowStatusLabelMutation(); + + const handleConfirm = async () => { + try { + await deactivateWorkflowStatusLabel({ ZUID }); + + onClose(); + callBack?.(); + + dispatch( + notify({ + kind: "error", + message: `Status De-activated: ${name}`, + }) + ); + } catch (error) { + console.error("Status Label Deactivation Error: ", error); + dispatch( + notify({ + kind: "error", + message: `Failed to deactivate status: ${name}. Please try again.`, + }) + ); + } + }; + + return ( + + + + + + + + + Deactivate Status: + + + {name} + + + + + Deactivating this status will remove it from all content items that + currently have it. You can always reactivate this status in the + future. + + + + + + + + Deactivate Status + + + + ); +}; + +export default DeactivationDialog; diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx new file mode 100644 index 0000000000..876644aea5 --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx @@ -0,0 +1,521 @@ +import { FC, FormEvent, ReactNode, useEffect, useState } from "react"; +import { + Box, + Button, + TextField, + IconButton, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Typography, + OutlinedInput, + FormControlLabel, + Checkbox, + Autocomplete, +} from "@mui/material"; +import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; +import Brightness1Icon from "@mui/icons-material/Brightness1"; +import InfoRoundedIcon from "@mui/icons-material/InfoRounded"; +import CheckIcon from "@mui/icons-material/Check"; +import SaveIcon from "@mui/icons-material/Save"; +import PauseCircleOutlineRoundedIcon from "@mui/icons-material/PauseCircleOutlineRounded"; +import { LoadingButton } from "@mui/lab"; +import { + useCreateWorkflowStatusLabelMutation, + useUpdateWorkflowStatusLabelMutation, +} from "../../../../../../../../../shell/services/instance"; +import { useFormDialogContext } from "."; +import { useDispatch } from "react-redux"; +import { notify } from "../../../../../../../../../shell/store/notifications"; +import { useGetUsersRolesQuery } from "../../../../../../../../../shell/services/accounts"; +import { + colorMenu, + CreateStatusLabel, + StatusLabel, + StatusLabelsColorMenu, + StatusLabelsRoleMenu, +} from "../../../../../../../../../shell/services/types"; + +interface FormInputFieldWrapperProps { + label: string; + required?: boolean; + description?: string; + error?: string; + children: ReactNode; +} + +export type StatusLabelFormProps = { + open: boolean; + onClose: () => void; + labels?: StatusLabel[]; + values?: StatusLabel | undefined; + isDeactivated?: boolean; +}; + +const FormInputFieldWrapper: FC = ({ + label, + required = false, + description, + error, + children, +}) => ( + + + {label} + {required && ( + + )} + + {description && ( + + {description} + + )} + + {children} + + {!!error && ( + + {error} + + )} + +); + +const ColorSelectInput = ({ + name, + defaultValue = "", + usedColors = [], +}: { + name: string; + defaultValue?: string | ""; + usedColors: string[]; +}) => { + const availableColors = colorMenu + .sort((a, b) => a.label.localeCompare(b.label)) + .filter((item) => !usedColors.includes(item.value)); + + const defaultColor = + colorMenu?.find( + (item) => + item?.value?.trim()?.toUpperCase() === + defaultValue?.trim()?.toUpperCase() + ) || + availableColors?.[0] || + colorMenu?.[0]; + + const [selectedColor, setSelectedColor] = + useState(defaultColor); + + return ( + <> + setSelectedColor(newValue)} + value={selectedColor} + renderOption={(props, option) => ( +
  • + + + + {option.label} + + +
  • + )} + renderInput={(params) => ( + + ), + }} + /> + )} + /> + + + ); +}; + +const RolesSelectInput = ({ + name, + listData, + defaultValue = "", +}: { + name: string; + listData: StatusLabelsRoleMenu[]; + defaultValue?: string; +}) => { + const [value, setSelectedColor] = useState(defaultValue || ""); + const sortedListData = [...listData].sort((a, b) => + a.label.localeCompare(b.label) + ); + const getRoleInfo = (zuids: string) => + zuids + ? sortedListData.filter((item) => + zuids.split(",").includes(item.value.trim()) + ) + : []; + const handleChange = (_: unknown, newValue: StatusLabelsRoleMenu[]) => + setSelectedColor(newValue.map((item) => item.value).join(",")); + + return ( + <> + option.label} + renderOption={(props, option, { selected }) => ( +
  • + } + checkedIcon={} + checked={selected} + sx={{ + marginRight: 5, + position: "absolute", + left: 0, + }} + /> + + {option.label} + +
  • + )} + renderInput={(params) => ( + + )} + ChipProps={{ + sx: { + height: "1.25rem", + backgroundColor: "transparent", + outline: "1px solid", + outlineColor: (theme) => theme.palette.border, + }, + }} + /> + + + ); +}; + +const validateFormData = (formData: CreateStatusLabel) => { + const errors: Record = {}; + if (!formData.name) errors.name = "Name is required"; + // if (!formData.description) errors.description = "Description is required"; + if (!formData.color) errors.color = "Color is required"; + return errors; +}; + +const StatusLabelForm: FC = ({ + open, + onClose, + labels = [], + values, + isDeactivated = false, +}) => { + const ZUID = values?.ZUID || undefined; + const [rolesMenuItems, setRolesMenuItems] = useState( + [] + ); + const { + isLoading: rolesLoading, + isFetching, + data: rolesMenuData, + } = useGetUsersRolesQuery(); + + const [formErrors, setFormErrors] = useState>({}); + const dispatch = useDispatch(); + const [createWorkflowStatusLabel, { isLoading }] = + useCreateWorkflowStatusLabelMutation(); + const [updateWorkflowStatusLabel] = useUpdateWorkflowStatusLabelMutation(); + const { openDeactivationDialog, setFocusedLabel } = useFormDialogContext(); + + const usedColors = labels.map((label) => label.color); + + const transformRoleValues = (value: string): string[] => + value ? value.split(",") : []; + + const handleFormSubmit = async (e: FormEvent) => { + e.preventDefault(); + // setIsLoading(true); + const formData = Object.fromEntries(new FormData(e.currentTarget)); + + const newStatusLabel: CreateStatusLabel = { + name: formData.name as string, + description: (formData.description as string) || "", + color: formData.color as string, + allowPublish: formData.allowPublish === "true", + addPermissionRoles: transformRoleValues( + formData.addPermissionRoles as string + ), + removePermissionRoles: transformRoleValues( + formData.removePermissionRoles as string + ), + }; + + const errors = validateFormData(newStatusLabel); + if (Object.keys(errors).length > 0) { + setFormErrors(errors); + return; + } else { + setFormErrors({}); + } + + try { + const response: any = ZUID + ? await updateWorkflowStatusLabel({ ZUID, payload: newStatusLabel }) + : await createWorkflowStatusLabel(newStatusLabel); + + if (response?.error) { + throw new Error(response.error.data?.error || "An error occurred."); + } + + setFocusedLabel(response?.data?.ZUID); + } catch (error) { + dispatch( + notify({ + kind: "error", + message: `Error: ${ + error instanceof Error + ? error.message + : "An unexpected error occurred." + }`, + }) + ); + } finally { + onClose(); + } + }; + const handleDeactivation = () => + openDeactivationDialog({ + ZUID, + name: values?.name, + callBack: onClose, + }); + + useEffect(() => { + if (!open) { + setFormErrors({}); + } + + if (rolesLoading || isFetching) return; + const roles = + rolesMenuData?.map((item) => ({ + label: item?.role?.name as string, + value: item?.role?.ZUID, + })) || []; + + const uniqueRoles = Array.from( + new Map(roles.map((role) => [role.value, role])).values() + ); + setRolesMenuItems(uniqueRoles); + }, [rolesLoading, isFetching, rolesMenuData, open]); + + return ( + + + + {!!ZUID && ( + + )} + + {values?.name ? `Edit ${values.name}` : "Create Status"} + + + + + + + + + + + + + + + + + + + + + + + + , + checked: boolean + ) => { + e.target.value = checked.toString(); + }} + sx={{ + p: 0, + ml: 1, + color: "action.active", + "&.Mui-checked": { + color: "primary.main", + }, + }} + /> + } + label={ + + + Allow publish with this status + + + This means a content item with this status can be published + + + } + /> + {!!ZUID && !isDeactivated && ( + + + + )} + + + + + } + > + {ZUID ? "Save" : "Create Status"} + + + + ); +}; + +export default StatusLabelForm; diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/index.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/index.tsx new file mode 100644 index 0000000000..a918d905a2 --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/index.tsx @@ -0,0 +1,130 @@ +import { createContext, useContext, useState, useCallback } from "react"; +import { + StatusLabel, + StatusLabelQuery, +} from "../../../../../../../../../shell/services/types"; + +import DeactivationDialog from "./DeactivationDialog"; +import StatusLabelForm from "./StatusLabelForm"; + +// Types for StatusLabelForm and DeactivationDialog +export type OpenStatusLabelFormTypes = { + labels?: StatusLabelQuery[] | []; + values?: StatusLabel; + isDeactivated?: boolean; +}; + +export type OpenDeactivationDialogTypes = { + ZUID: string; + name: string; + callBack?: () => void; +}; + +// FormDialogContext Types +export type FormDialogContextTypes = { + openStatusLabelForm: ({ + values, + labels, + isDeactivated, + }: OpenStatusLabelFormTypes) => void; + closeStatusLabelForm: () => void; + openDeactivationDialog: ({ + ZUID, + name, + callBack, + }: OpenDeactivationDialogTypes) => void; + closeDeactivationDialog: () => void; + focusedLabel: string | undefined; + setFocusedLabel: (id: string | undefined) => void; +}; + +const FormDialogContext = createContext(null); + +const FormDialogContextProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [focusedLabel, setFocusedLabel] = useState( + undefined + ); + + // Form dialog states + const [formIsOpen, setFormIsOpen] = useState(false); + const [formValues, setFormValues] = useState< + OpenStatusLabelFormTypes | undefined + >(undefined); + + // Deactivation dialog states + const [dialogIsOpen, setDialogIsOpen] = useState(false); + const [dialogProps, setDialogProps] = useState< + OpenDeactivationDialogTypes | undefined + >(undefined); + + // Handlers for status label form dialog + const openStatusLabelForm = useCallback( + ({ values, labels, isDeactivated }: OpenStatusLabelFormTypes) => { + setFormValues({ values, labels, isDeactivated }); + setFormIsOpen(true); + }, + [] + ); + + const closeStatusLabelForm = useCallback(() => { + setFormValues(undefined); + setFormIsOpen(false); + }, []); + + // Handlers for deactivation dialog + const openDeactivationDialog = useCallback( + ({ ZUID, name, callBack }: OpenDeactivationDialogTypes) => { + setDialogProps({ ZUID, name, callBack }); + setDialogIsOpen(true); + }, + [] + ); + + const closeDeactivationDialog = useCallback(() => { + setDialogProps(undefined); + setDialogIsOpen(false); + }, []); + + return ( + + {children} + + + + ); +}; + +export const useFormDialogContext = () => { + const context = useContext(FormDialogContext); + if (context === null) { + throw new Error( + "useFormDialogContext must be used within a FormDialogContextProvider" + ); + } + return context; +}; + +export default FormDialogContextProvider; diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/index.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/index.tsx new file mode 100644 index 0000000000..95edeb0b6b --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/index.tsx @@ -0,0 +1,257 @@ +import { useEffect, useRef, useState, useCallback, useMemo } from "react"; +import { + Box, + Typography, + Button, + TextField, + InputAdornment, + FormGroup, + FormControlLabel, + Switch, + Collapse, +} from "@mui/material"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import AddIcon from "@mui/icons-material/Add"; +import { Search } from "@mui/icons-material"; +import ActiveStatus from "./ActiveStatus"; +import DeactivatedStatus from "./DeactivatedStatus"; +import { useGetWorkflowStatusLabelsQuery } from "../../../../../../../../shell/services/instance"; +import { useFormDialogContext } from "./forms-dialogs"; +import { NoResults } from "../../../../../../../schema/src/app/components/NoResults"; +import { StatusLabelQuery } from "../../../../../../../../shell/services/types"; + +export type StatusLabelSorting = { + id: string; + index?: number; + data: StatusLabelQuery; + isFiltered: boolean; + isDeactivated: boolean; +}; + +const LabelHeader = ({ + title, + subtitle, +}: { + title: string; + subtitle: string; +}) => ( + + + {title} + + + {subtitle} + + +); + +export const AuthorizedUserPage = () => { + const searchInputRef = useRef(null); + const [showDeactivated, setShowDeactivated] = useState(false); + const [searchValue, setSearchValue] = useState(""); + const { openStatusLabelForm } = useFormDialogContext(); + const { + isLoading, + isFetching, + data: labels, + } = useGetWorkflowStatusLabelsQuery({ showDeleted: true }); + + const { activeLabels, deactivatedLabels, emptySearchResult } = useMemo(() => { + if (isLoading || !labels) + return { + activeLabels: [], + deactivatedLabels: [], + emptySearchResult: false, + }; + + const parsedLabels = labels + .map((label) => { + const searchString = `${label.name?.toLowerCase()}_${label.description?.toLowerCase()}`; + return { + id: label.ZUID, + data: label, + isFiltered: !searchString.includes(searchValue.toLowerCase().trim()), + isDeactivated: !!label.deletedAt, + }; + }) + .sort((a, b) => a?.data?.sort - b?.data?.sort); + + const activeDeactivated = parsedLabels.reduce( + (acc, curr) => { + if (curr.isDeactivated) { + acc.deactivated.push({ ...curr, index: acc.deactivated.length }); + } else { + acc.active.push({ ...curr, index: acc.active.length }); + } + return acc; + }, + { active: [], deactivated: [] } + ); + + const activeCount = activeDeactivated.active.filter( + (label) => !label.isFiltered + ).length; + const deactivatedCount = activeDeactivated.deactivated.filter( + (label) => !label.isFiltered + ).length; + + const searchResultsCount = showDeactivated + ? activeCount + deactivatedCount + : activeCount; + + return { + activeLabels: activeDeactivated.active, + deactivatedLabels: activeDeactivated.deactivated, + emptySearchResult: searchResultsCount < 1, + }; + }, [labels, isLoading, searchValue, showDeactivated]); + + const handleSearchRetry = () => { + setSearchValue(""); + searchInputRef.current?.focus(); + }; + + const handleOpenStatusLabelForm = () => { + const labelList = [ + ...activeLabels.map((item) => item.data), + ...deactivatedLabels.map((item) => item.data), + ]; + openStatusLabelForm({ labels: labelList }); + }; + + return ( + <> + + + Workflows + + + + + + setSearchValue(e.target.value)} + InputProps={{ + inputRef: searchInputRef, + startAdornment: ( + + + + ), + sx: { + backgroundColor: "background.paper", + minWidth: "320px", + }, + }} + /> + + setShowDeactivated(e.target.checked)} + /> + } + label={ + Show Deactivated + } + /> + + + + + {emptySearchResult ? ( + + + + ) : ( + + + + + + + + + + + + {showDeactivated && ( + + )} + + + + )} + + + ); +}; diff --git a/src/apps/settings/src/app/views/User/Workflows/index.tsx b/src/apps/settings/src/app/views/User/Workflows/index.tsx new file mode 100644 index 0000000000..e7636ab5ef --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/index.tsx @@ -0,0 +1,42 @@ +import { ThemeProvider, Box } from "@mui/material"; +import { theme } from "@zesty-io/material"; +import RestrictedPage from "./RestrictedPage"; +import { useSelector } from "react-redux"; +import { AppState } from "../../../../../../../shell/store/types"; +import { AuthorizedUserPage } from "./authorized"; +import FormDialogContextProvider from "./authorized/forms-dialogs"; +import { AUTHORIZED_ROLES } from "../../../../../../../shell/services/types"; + +type UserType = { + role: string; + staff: boolean; +}; + +const Workflows = () => { + const { role, staff }: UserType = useSelector((state: AppState) => ({ + role: state.userRole?.name, + staff: state.user?.staff, + })); + + const isAuthorized = AUTHORIZED_ROLES.includes(role) || staff; + + return ( + + + + {isAuthorized ? : } + + + + ); +}; + +export default Workflows; diff --git a/src/shell/services/instance.ts b/src/shell/services/instance.ts index 66ecd933dc..3bb30b7535 100644 --- a/src/shell/services/instance.ts +++ b/src/shell/services/instance.ts @@ -24,10 +24,14 @@ import { StyleCategory, WorkflowStatusLabel, ItemWorkflowStatus, - GroupItem, + WorkflowStatusLabelQueryParams, + CreateStatusLabel, + UpdateSortingOrder, } from "./types"; import { batchApiRequests } from "../../utility/batchApiRequests"; +// import {UpdateSortingOrderProps} from "../../apps/settings/src/app/views/User/Workflows/types"; + // Define a service using a base URL and expected endpoints export const instanceApi = createApi({ reducerPath: "instanceApi", @@ -661,8 +665,12 @@ export const instanceApi = createApi({ }), invalidatesTags: ["ItemWorkflowStatus"], }), - getWorkflowStatusLabels: builder.query({ - query: () => `/env/labels`, + getWorkflowStatusLabels: builder.query< + WorkflowStatusLabel[], + WorkflowStatusLabelQueryParams | void + >({ + query: ({ showDeleted = false }: WorkflowStatusLabelQueryParams = {}) => + `/env/labels?showDeleted=${showDeleted}`, transformResponse: getResponseData, providesTags: ["WorkflowStatusLabels"], }), @@ -695,6 +703,52 @@ export const instanceApi = createApi({ }), invalidatesTags: ["Groups"], }), + + createWorkflowStatusLabel: builder.mutation({ + query: (payload) => { + return { + url: `/env/labels`, + method: "POST", + body: payload, + }; + }, + transformResponse: getResponseData, + invalidatesTags: ["WorkflowStatusLabels"], + }), + + //Update workflow status label + updateWorkflowStatusLabel: builder.mutation< + any, + { ZUID: string; payload: CreateStatusLabel } + >({ + query: ({ ZUID, payload }) => ({ + url: `/env/labels/${ZUID}`, + method: "PUT", + body: payload, + }), + transformResponse: getResponseData, + invalidatesTags: ["WorkflowStatusLabels"], + }), + //Update workflow status order + updateWorkflowStatusLabelOrder: builder.mutation< + any[], + UpdateSortingOrder[] + >({ + query: (payload) => ({ + url: `/env/labels`, + method: "PUT", + body: { data: payload }, + }), + invalidatesTags: ["WorkflowStatusLabels"], + }), + //Deactivate workflow status label + deactivateWorkflowStatusLabel: builder.mutation({ + query: ({ ZUID }) => ({ + url: `/env/labels/${ZUID}`, + method: "DELETE", + }), + invalidatesTags: ["WorkflowStatusLabels"], + }), }), }); @@ -751,4 +805,8 @@ export const { useGetGroupsQuery, useGetGroupByZUIDQuery, useCreateGroupMutation, + useCreateWorkflowStatusLabelMutation, + useUpdateWorkflowStatusLabelMutation, + useUpdateWorkflowStatusLabelOrderMutation, + useDeactivateWorkflowStatusLabelMutation, } = instanceApi; diff --git a/src/shell/services/types.ts b/src/shell/services/types.ts index 16ee2a71c5..4bd0d79b7f 100644 --- a/src/shell/services/types.ts +++ b/src/shell/services/types.ts @@ -592,6 +592,7 @@ export type WorkflowStatusLabel = { updatedByUserZUID: string; createdAt: string; updatedAt: string; + deletedAt?: string; }; export type ItemWorkflowStatus = { @@ -617,3 +618,98 @@ export type GroupItem = { createdAt: string; updatedAt: string; }; + +export type WorkflowStatusLabelQueryParams = { + showDeleted?: boolean; +}; + +export const COLOR_NAMES = { + BLUE: "Blue", + DEEP_PURPLE: "Deep Purple", + GREEN: "Green", + ORANGE: "Orange", + PINK: "Pink", + PURPLE: "Purple", + RED: "Red", + ROSE: "Rose", + YELLOW: "Yellow", + GREY: "Grey", +} as const; + +export const COLOR_HEX = { + BLUE: "#0BA5EC", + DEEP_PURPLE: "#4E5BA6", + GREEN: "#12b76a", + ORANGE: "#FF5C08", + PINK: "#EE46BC", + PURPLE: "#7A5AF8", + RED: "#F04438", + ROSE: "#F63D68", + YELLOW: "#F79009", + GREY: "#667085", +} as const; + +export const AUTHORIZED_ROLES: string[] = ["Admin", "Owner"]; + +export const ROLE_SORT_ORDER = { Owner: 0, Admin: 1 }; + +type ObjectConstants = Type[keyof Type]; + +export type ColorNameTypes = ObjectConstants; +export type ColorHexTypes = ObjectConstants; +export type AuthorizedRoles = typeof AUTHORIZED_ROLES[number]; + +export type StatusLabelQuery = { + ZUID: string; + name: string; + description: string; + color: string; + allowPublish: boolean; + sort: number; + addPermissionRoles: string[]; + removePermissionRoles: string[]; + createdByUserZUID: string; + updatedByUserZUID: string; + createdAt: string; + updatedAt: string; + deletedAt?: string | undefined; +}; + +export type StatusLabel = Omit< + WorkflowStatusLabel, + | "createdByUserZUID" + | "updatedByUserZUID" + | "createdAt" + | "updatedAt" + | "deletedAt" +>; + +export type UpdateStatusLabel = Omit< + WorkflowStatusLabel, + | "createdByUserZUID" + | "updatedByUserZUID" + | "createdAt" + | "updatedAt" + | "deletedAt" +>; + +export type CreateStatusLabel = Partial>; + +export type UpdateSortingOrder = Pick; + +export type StatusLabelsColorMenu = { + label: ColorNameTypes; + value: ColorHexTypes; +}; + +export type StatusLabelsRoleMenu = { + label: string; + value: string; +}; + +export const colorMenu: StatusLabelsColorMenu[] = Object.keys(COLOR_NAMES).map( + (key) => ({ + label: COLOR_NAMES[key as keyof typeof COLOR_NAMES], + value: COLOR_HEX[key as keyof typeof COLOR_HEX], + }) +); From dbff4de95d31e42180b26fa0197485129f9ee999 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Fri, 20 Dec 2024 10:36:55 +0800 Subject: [PATCH 31/59] fix: sort labels --- .../VersionSelectorV2/VersionItem.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx index 318ad938db..649ff7b2d9 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx @@ -106,13 +106,19 @@ export const VersionItem = memo( useUnmount(() => saveLabelChanges()); const filteredStatusLabels = useMemo(() => { - if (!debouncedFilterKeyword) return statusLabels; - - return statusLabels?.filter((label) => - label.name - ?.toLowerCase() - .includes(debouncedFilterKeyword?.toLowerCase()?.trim()) + const sortedStatusLabels = [...statusLabels]?.sort( + (a, b) => a.sort - b.sort ); + + if (!debouncedFilterKeyword) return sortedStatusLabels; + + return sortedStatusLabels + ?.filter((label) => + label.name + ?.toLowerCase() + .includes(debouncedFilterKeyword?.toLowerCase()?.trim()) + ) + .sort((a, b) => a.sort - b.sort); }, [statusLabels, debouncedFilterKeyword]); const handleOpenAddNewLabel = (evt: any) => { From 0a2a67df94eb93437357a7483fe6bb3c6bfb6911 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Fri, 20 Dec 2024 14:58:20 +0800 Subject: [PATCH 32/59] task: add e2e tests --- cypress.config.js | 1 + cypress/e2e/content/pages/ContentItemPage.js | 22 ++++ cypress/e2e/content/workflows.spec.js | 92 +++++++++++++ .../ItemEditHeader/VersionSelector.tsx | 122 ------------------ .../NoResults.tsx | 0 .../Row.tsx | 0 .../VersionItem.tsx | 5 +- .../index.tsx | 1 + .../mocks.ts | 0 .../components/ItemEditHeader/index.tsx | 3 +- 10 files changed, 121 insertions(+), 125 deletions(-) create mode 100644 cypress/e2e/content/pages/ContentItemPage.js create mode 100644 cypress/e2e/content/workflows.spec.js delete mode 100644 src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector.tsx rename src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/{VersionSelectorV2 => VersionSelector}/NoResults.tsx (100%) rename src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/{VersionSelectorV2 => VersionSelector}/Row.tsx (100%) rename src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/{VersionSelectorV2 => VersionSelector}/VersionItem.tsx (98%) rename src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/{VersionSelectorV2 => VersionSelector}/index.tsx (99%) rename src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/{VersionSelectorV2 => VersionSelector}/mocks.ts (100%) diff --git a/cypress.config.js b/cypress.config.js index 2a88f86fa1..cd9cd2ee71 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -5,6 +5,7 @@ module.exports = defineConfig({ viewportWidth: 1920, viewportHeight: 1080, video: false, + defaultCommandTimeout: 30000, env: { API_AUTH: "https://auth.api.dev.zesty.io", COOKIE_NAME: "DEV_APP_SID", diff --git a/cypress/e2e/content/pages/ContentItemPage.js b/cypress/e2e/content/pages/ContentItemPage.js new file mode 100644 index 0000000000..f37daf65d9 --- /dev/null +++ b/cypress/e2e/content/pages/ContentItemPage.js @@ -0,0 +1,22 @@ +class ContentItemPage { + elements = { + createItemButton: () => cy.getBySelector("CreateItemSaveButton"), + duoModeToggle: () => cy.getBySelector("DuoModeToggle"), + moreMenu: () => cy.getBySelector("ContentItemMoreButton"), + deleteItemButton: () => cy.getBySelector("DeleteContentItem"), + confirmDeleteItemButton: () => + cy.getBySelector("DeleteContentItemConfirmButton"), + versionSelector: () => cy.getBySelector("VersionSelector"), + versionItem: () => cy.getBySelector("VersionItem"), + addWorkflowStatusLabel: () => cy.getBySelector("AddWorkflowStatusLabel"), + workflowStatusLabelOption: () => + cy.getBySelector("WorkflowStatusLabelOption"), + activeWorkflowStatusLabel: () => + cy.getBySelector("ActiveWorkflowStatusLabel"), + publishItemButton: () => cy.getBySelector("PublishButton"), + confirmPublishItemButton: () => cy.getBySelector("ConfirmPublishButton"), + toast: () => cy.getBySelector("toast"), + }; +} + +export default new ContentItemPage(); diff --git a/cypress/e2e/content/workflows.spec.js b/cypress/e2e/content/workflows.spec.js new file mode 100644 index 0000000000..30de78ff99 --- /dev/null +++ b/cypress/e2e/content/workflows.spec.js @@ -0,0 +1,92 @@ +import ContentItemPage from "./pages/ContentItemPage"; + +const TITLE = "Content item workflow test"; + +describe("Content Item Workflows", () => { + before(() => { + //TODO: Find a way to seed the labels prior to starting tests + + cy.visit("/content/6-b6cde1aa9f-wftv50/new"); + + cy.get("#12-a6d48ca2b7-zxqns2").should("exist").find("input").type(TITLE); + cy.get("#12-d29ab9bbe0-9k6j70") + .should("exist") + .find("textarea") + .first() + .type(TITLE); + ContentItemPage.elements.createItemButton().should("exist").click(); + ContentItemPage.elements.duoModeToggle().should("exist").click(); + }); + + after(() => { + // TODO: Delete seeded labels after test + ContentItemPage.elements.moreMenu().should("exist").click(); + ContentItemPage.elements.deleteItemButton().should("exist").click(); + ContentItemPage.elements.confirmDeleteItemButton().should("exist").click(); + }); + + it("Can add a workflow label", () => { + ContentItemPage.elements.versionSelector().should("exist").click(); + ContentItemPage.elements.addWorkflowStatusLabel().should("exist").click(); + ContentItemPage.elements + .workflowStatusLabelOption() + .last() + .should("exist") + .click(); + + cy.get("body").type("{esc}"); + + cy.intercept("PUT", "**/labels/*").as("updateLabel"); + cy.wait("@updateLabel"); + + cy.reload(); + + ContentItemPage.elements.versionSelector().should("exist").click(); + ContentItemPage.elements + .versionItem() + .first() + .within(() => { + ContentItemPage.elements + .activeWorkflowStatusLabel() + .should("have.length", 1); + }); + + cy.get("body").type("{esc}"); + }); + + it("Cannot add a workflow label when role has no permission", () => { + ContentItemPage.elements.versionSelector().should("exist").click(); + ContentItemPage.elements.addWorkflowStatusLabel().should("exist").click(); + ContentItemPage.elements + .workflowStatusLabelOption() + .first() + .should("exist") + .click(); + + cy.get("body").type("{esc}"); + + cy.reload(); + + ContentItemPage.elements.versionSelector().should("exist").click(); + ContentItemPage.elements + .versionItem() + .first() + .within(() => { + ContentItemPage.elements + .activeWorkflowStatusLabel() + .should("have.length", 1); + }); + + cy.get("body").type("{esc}"); + }); + + it("Cannot publish a content item if needed workflow label is missing", () => { + ContentItemPage.elements.publishItemButton().should("exist").click(); + ContentItemPage.elements.confirmPublishItemButton().should("exist").click(); + ContentItemPage.elements + .toast() + .contains( + `Cannot Publish: "${TITLE}". Does not have a status that allows publishing` + ); + }); +}); diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector.tsx deleted file mode 100644 index 80a4c93da8..0000000000 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { Button, Menu, MenuItem, Box, Tooltip } from "@mui/material"; -import { - useGetContentItemVersionsQuery, - useGetItemPublishingsQuery, -} from "../../../../../../../../shell/services/instance"; -import { useLocation, useParams } from "react-router"; -import { KeyboardArrowDownRounded } from "@mui/icons-material"; -import { useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { ContentItem } from "../../../../../../../../shell/services/types"; -import { AppState } from "../../../../../../../../shell/store/types"; -import { formatDate } from "../../../../../../../../utility/formatDate"; - -export const VersionSelector = () => { - const dispatch = useDispatch(); - const { modelZUID, itemZUID } = useParams<{ - modelZUID: string; - itemZUID: string; - }>(); - const location = useLocation(); - const queryParams = new URLSearchParams(location.search); - const [anchorEl, setAnchorEl] = useState(null); - const { data: versions } = useGetContentItemVersionsQuery({ - modelZUID, - itemZUID, - }); - const { data: itemPublishings } = useGetItemPublishingsQuery({ - modelZUID, - itemZUID, - }); - - const item = useSelector( - (state: AppState) => state.content[itemZUID] as ContentItem - ); - - const onSelect = (version: ContentItem) => { - dispatch({ - type: "LOAD_ITEM_VERSION", - itemZUID: itemZUID, - data: version, - }); - }; - - useEffect(() => { - const versionParam = queryParams.get("version"); - const version = versions?.find((v) => v.meta.version === +versionParam); - if (version) { - onSelect(version); - } - }, [queryParams.get("version"), versions]); - - return ( - <> - - - - setAnchorEl(null)} - anchorOrigin={{ - vertical: "bottom", - horizontal: "right", - }} - transformOrigin={{ - vertical: -10, - horizontal: "right", - }} - anchorEl={anchorEl} - open={!!anchorEl} - PaperProps={{ - style: { - maxHeight: "496px", - overflow: "auto", - width: "320px", - }, - }} - > - {versions?.map((version) => ( - { - setAnchorEl(null); - onSelect(version); - }} - > - - - {`v${version.meta.version}${ - itemPublishings?.find( - (itemPublishing) => itemPublishing._active - )?.version === version.meta.version - ? " - Live" - : "" - }`} - - {formatDate(version.web.createdAt)} - - - ))} - - - ); -}; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/NoResults.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/NoResults.tsx similarity index 100% rename from src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/NoResults.tsx rename to src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/NoResults.tsx diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/Row.tsx similarity index 100% rename from src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/Row.tsx rename to src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/Row.tsx diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx similarity index 98% rename from src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx rename to src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx index 649ff7b2d9..18c34abeae 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelectorV2/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx @@ -153,7 +153,7 @@ export const VersionItem = memo( }; return ( - + { flexGrow={0} > { > {emptySearchResult ? ( Date: Sat, 21 Dec 2024 04:51:56 +0800 Subject: [PATCH 35/59] Added test cases --- cypress/e2e/settings/workflows.spec.js | 57 +++++++++++--------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 697a85e581..1233b19a6d 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -150,7 +150,7 @@ const EDIT_STATUS_LABEL_DATA = { }; describe("Workflow Status Labels", () => { - describe("Restricted User", () => { + context("Restricted User", () => { before(() => { //RESTRICTED USER cy.intercept("GET", ENDPOINTS?.userRole, { @@ -167,22 +167,19 @@ describe("Workflow Status Labels", () => { }, }).as("getInstanceUsers"); }); - it("Should have a header: You need permission to view and edit workflows", () => { + it("displays restricted access message and admin profiles", () => { cy.visit("/settings/user/workflows"); cy.wait("@getRestrictedUser"); cy.wait("@getInstanceUsers"); cy.contains("You need permission to view and edit workflows").should( "exist" ); - }); - it("Should have a subheader: Contact the instance owner or administrators listed below to upgrade your role to Admin or Owner", () => { cy.contains( "Contact the instance owner or administrators listed below to upgrade your role to Admin or Owner" ).should("exist"); - }); - it("Should display restricted image", () => { cy.get('[data-cy="restricted-image"]').should("exist"); }); + it("Should display 3 admin profiles", () => { cy.get( '[data-cy="user-profile-container"] [data-cy="user-profile"]' @@ -190,7 +187,7 @@ describe("Workflow Status Labels", () => { }); }); - describe("Authorized User", () => { + context("Authorized User", () => { before(() => { cy.intercept("GET", ENDPOINTS?.userRole, { statusCode: 200, @@ -198,14 +195,14 @@ describe("Workflow Status Labels", () => { ...USERS_ROLE_AUTORIZED, }, }).as("getAuthorizedUser"); - cy.intercept("GET", "/v1/env/labels?showDeleted=true", { + cy.intercept("GET", ENDPOINTS.workflowStatusLabels, { statusCode: 200, body: { ...WORKFLOW_STATUS_LABELS, }, }).as("getWorkflowLabels"); }); - it("Workflows Page should have a heading, Create Status Label button, Search box and show deactivated switch", () => { + it("displays workflow page elements", () => { cy.visit("/settings/user/workflows"); cy.wait(["@getAuthorizedUser", "@getWorkflowLabels"]); cy.contains("Workflows").should("exist"); @@ -220,8 +217,8 @@ describe("Workflow Status Labels", () => { ).should("have.length", 2); }); - describe("Show Deactivated Labels", () => { - it("Should have the correct headings and sub-headings", () => { + context("Show Deactivated Labels", () => { + it("displays correct headings and labels", () => { cy.get('input[value="deactivated"]').click(); cy.get('input[value="deactivated"]').should("be.checked"); cy.contains("Active Statuses").should("exist"); @@ -245,8 +242,8 @@ describe("Workflow Status Labels", () => { ).should("have.length", 1); }); }); - describe("Search for Status Label", () => { - it("Keyword: 'Approved' - Should show 1 active and 0 deactivated labels", () => { + context("Search Functionality", () => { + it("filters active and deactivated labels", () => { cy.get('[data-cy="status-label-search-box"] input').type("approved"); cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' @@ -254,9 +251,7 @@ describe("Workflow Status Labels", () => { cy.get( '[data-cy="deactivated-labels-container"] [data-cy="status-label"]:visible' ).should("have.length", 0); - }); - it("Keyword: 'archived' - Should show 0 active and 1 deactivated labels", () => { cy.get('[data-cy="status-label-search-box"] input').clear(); cy.get('[data-cy="status-label-search-box"] input').type("archived"); cy.get( @@ -267,15 +262,10 @@ describe("Workflow Status Labels", () => { ).should("have.length", 1); }); - it("Keyword: 'xxxxxx' - Should show the no results page", () => { + it("handles no results and reset", () => { cy.get('[data-cy="status-label-search-box"] input').clear(); cy.get('[data-cy="status-label-search-box"] input').type("xxxxxx"); cy.get('[data-cy="no-results-page"]').should("exist"); - }); - it(` - Click search again: should clear out the search input and focuses it. - No results page should be replaced with 2 active and 1 deactivated labels - `, () => { cy.get("button").contains("Search Again").click(); cy.get('[data-cy="no-results-page"]').should("not.exist"); @@ -292,11 +282,11 @@ describe("Workflow Status Labels", () => { }); }); - describe("Create Status Label", () => { + context("Create Status Label", () => { before(() => { cy.get("button").contains("Create Status").click(); }); - it("Status Label Form: should contain all the fields", () => { + it("displays form with all fields", () => { cy.get('form[role="dialog"]').should("exist"); cy.get('input[name="name"]').should("exist"); cy.get('textarea[name="description"]').should("exist"); @@ -308,7 +298,7 @@ describe("Workflow Status Labels", () => { cy.get("button").contains("Create Status").should("exist"); }); - it("Fillout and submit form", () => { + it("fills out and submits form", () => { cy.get('input[name="name"]').type("Test Label"); cy.get('textarea[name="description"]').type("Test Label Description"); cy.get('input[name="color"]').parent().find("button").click(); @@ -328,7 +318,7 @@ describe("Workflow Status Labels", () => { cy.get('input[name="allowPublish"]').click(); }); - it("Submit Form: Request Data should match the form data", () => { + it("submits form and verifies request data", () => { cy.intercept("POST", ENDPOINTS?.createStatusLabel, { statusCode: 200, body: { @@ -358,7 +348,7 @@ describe("Workflow Status Labels", () => { cy.wait("@getWorkflowLabels"); }); - it("Once submitted, should show the new status label and highlight it", () => { + it("Shows the newly created label and focuses it", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ).should("have.length", 3); @@ -368,7 +358,7 @@ describe("Workflow Status Labels", () => { .should("have.css", "background-color", FOCUSED_LABEL_COLOR); }); - it("Click outside of the new status label, set its state to default", () => { + it("Clicking outside the focused label restores it to its default state.", () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') .eq(1) .click(); @@ -378,9 +368,8 @@ describe("Workflow Status Labels", () => { }); }); - describe("Edit Status Label", () => { - it(`Open more options menu and click edit: - should open the edit form and fill out the form with the label data`, () => { + context("Edit Status Label", () => { + it(`opens edit form and verifies pre-filled data`, () => { // const label = WORKFLOW_STATUS_LABELS.data[0]; cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' @@ -422,7 +411,7 @@ describe("Workflow Status Labels", () => { .and("be.enabled"); }); - it("Save Edit: Request Data should match the form data", () => { + it("verifies edited request data and apply changes to form", () => { cy.intercept( "PUT", `${ENDPOINTS?.createStatusLabel}/${EDIT_STATUS_LABEL_DATA?.ZUID}`, @@ -470,8 +459,8 @@ describe("Workflow Status Labels", () => { }); }); - describe("Deactivate Status Label", () => { - it("Deactivate Label from more options menu", () => { + context("Deactivate Status Label", () => { + it("opens deactivation dialog and verifies elements", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) @@ -487,7 +476,7 @@ describe("Workflow Status Labels", () => { cy.get("button").contains("Cancel").should("exist"); cy.get("button").contains("Deactivate Status").should("exist"); }); - it("Deactivation Dialog should contain the label name, cancel button and deactivate button", () => { + it("deactivates status label and verifies request", () => { cy.intercept( "DELETE", `${ENDPOINTS?.createStatusLabel}/${EDIT_STATUS_LABEL_DATA?.ZUID}`, From 0e7840bce16a2b538312a16294ab4b63113464ff Mon Sep 17 00:00:00 2001 From: geodem Date: Sat, 21 Dec 2024 04:51:56 +0800 Subject: [PATCH 36/59] Added test cases --- cypress/e2e/settings/workflows.spec.js | 108 ++++++++++-------- .../User/Workflows/authorized/StatusLabel.tsx | 1 + 2 files changed, 62 insertions(+), 47 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 697a85e581..74faf82624 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -5,6 +5,7 @@ const ENDPOINTS = { instanceUsers: `**/v1/instances/**/users/roles`, workflowStatusLabels: "**/v1/env/labels?showDeleted=true", createStatusLabel: "/v1/env/labels", + reOrderLabels: "/v1/env/labels", }; const USERS_ROLE_RESTRICTED = { data: [ @@ -148,9 +149,16 @@ const EDIT_STATUS_LABEL_DATA = { addPermissionRoles: [], removePermissionRoles: [], }; +const REORDER_STATUS_LABEL_DATA = { + data: [ + { ZUID: "status-1", sort: 1 }, + { ZUID: "status-2", sort: 2 }, + { ZUID: "status-4", sort: 3 }, + ], +}; describe("Workflow Status Labels", () => { - describe("Restricted User", () => { + context("Restricted User", () => { before(() => { //RESTRICTED USER cy.intercept("GET", ENDPOINTS?.userRole, { @@ -167,22 +175,19 @@ describe("Workflow Status Labels", () => { }, }).as("getInstanceUsers"); }); - it("Should have a header: You need permission to view and edit workflows", () => { + it("displays restricted access message and admin profiles", () => { cy.visit("/settings/user/workflows"); cy.wait("@getRestrictedUser"); cy.wait("@getInstanceUsers"); cy.contains("You need permission to view and edit workflows").should( "exist" ); - }); - it("Should have a subheader: Contact the instance owner or administrators listed below to upgrade your role to Admin or Owner", () => { cy.contains( "Contact the instance owner or administrators listed below to upgrade your role to Admin or Owner" ).should("exist"); - }); - it("Should display restricted image", () => { cy.get('[data-cy="restricted-image"]').should("exist"); }); + it("Should display 3 admin profiles", () => { cy.get( '[data-cy="user-profile-container"] [data-cy="user-profile"]' @@ -190,7 +195,7 @@ describe("Workflow Status Labels", () => { }); }); - describe("Authorized User", () => { + context("Authorized User", () => { before(() => { cy.intercept("GET", ENDPOINTS?.userRole, { statusCode: 200, @@ -198,14 +203,14 @@ describe("Workflow Status Labels", () => { ...USERS_ROLE_AUTORIZED, }, }).as("getAuthorizedUser"); - cy.intercept("GET", "/v1/env/labels?showDeleted=true", { + cy.intercept("GET", ENDPOINTS.workflowStatusLabels, { statusCode: 200, body: { ...WORKFLOW_STATUS_LABELS, }, }).as("getWorkflowLabels"); }); - it("Workflows Page should have a heading, Create Status Label button, Search box and show deactivated switch", () => { + it("displays workflow page elements", () => { cy.visit("/settings/user/workflows"); cy.wait(["@getAuthorizedUser", "@getWorkflowLabels"]); cy.contains("Workflows").should("exist"); @@ -220,8 +225,8 @@ describe("Workflow Status Labels", () => { ).should("have.length", 2); }); - describe("Show Deactivated Labels", () => { - it("Should have the correct headings and sub-headings", () => { + context("Show Deactivated Labels", () => { + it("displays correct headings and labels", () => { cy.get('input[value="deactivated"]').click(); cy.get('input[value="deactivated"]').should("be.checked"); cy.contains("Active Statuses").should("exist"); @@ -245,8 +250,8 @@ describe("Workflow Status Labels", () => { ).should("have.length", 1); }); }); - describe("Search for Status Label", () => { - it("Keyword: 'Approved' - Should show 1 active and 0 deactivated labels", () => { + context("Search Functionality", () => { + it("filters active and deactivated labels", () => { cy.get('[data-cy="status-label-search-box"] input').type("approved"); cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' @@ -254,9 +259,7 @@ describe("Workflow Status Labels", () => { cy.get( '[data-cy="deactivated-labels-container"] [data-cy="status-label"]:visible' ).should("have.length", 0); - }); - it("Keyword: 'archived' - Should show 0 active and 1 deactivated labels", () => { cy.get('[data-cy="status-label-search-box"] input').clear(); cy.get('[data-cy="status-label-search-box"] input').type("archived"); cy.get( @@ -267,15 +270,10 @@ describe("Workflow Status Labels", () => { ).should("have.length", 1); }); - it("Keyword: 'xxxxxx' - Should show the no results page", () => { + it("handles no results and reset", () => { cy.get('[data-cy="status-label-search-box"] input').clear(); cy.get('[data-cy="status-label-search-box"] input').type("xxxxxx"); cy.get('[data-cy="no-results-page"]').should("exist"); - }); - it(` - Click search again: should clear out the search input and focuses it. - No results page should be replaced with 2 active and 1 deactivated labels - `, () => { cy.get("button").contains("Search Again").click(); cy.get('[data-cy="no-results-page"]').should("not.exist"); @@ -292,11 +290,11 @@ describe("Workflow Status Labels", () => { }); }); - describe("Create Status Label", () => { + context("Create Status Label", () => { before(() => { cy.get("button").contains("Create Status").click(); }); - it("Status Label Form: should contain all the fields", () => { + it("displays form with all fields", () => { cy.get('form[role="dialog"]').should("exist"); cy.get('input[name="name"]').should("exist"); cy.get('textarea[name="description"]').should("exist"); @@ -308,7 +306,7 @@ describe("Workflow Status Labels", () => { cy.get("button").contains("Create Status").should("exist"); }); - it("Fillout and submit form", () => { + it("fills out and submits form", () => { cy.get('input[name="name"]').type("Test Label"); cy.get('textarea[name="description"]').type("Test Label Description"); cy.get('input[name="color"]').parent().find("button").click(); @@ -328,7 +326,7 @@ describe("Workflow Status Labels", () => { cy.get('input[name="allowPublish"]').click(); }); - it("Submit Form: Request Data should match the form data", () => { + it("submits form and verifies request data", () => { cy.intercept("POST", ENDPOINTS?.createStatusLabel, { statusCode: 200, body: { @@ -349,16 +347,12 @@ describe("Workflow Status Labels", () => { cy.wait("@createStatusLabel").then((interception) => { const { ZUID, sort, ...reqData } = CREATE_STATUS_LABEL_RESPONSE_DATA; - console.log("interception.request.body:", { - req: interception.request.body, - DATA: reqData, - }); expect(interception.request.body).to.deep.equal(reqData); }); cy.wait("@getWorkflowLabels"); }); - it("Once submitted, should show the new status label and highlight it", () => { + it("Shows the newly created label and focuses it", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ).should("have.length", 3); @@ -368,7 +362,7 @@ describe("Workflow Status Labels", () => { .should("have.css", "background-color", FOCUSED_LABEL_COLOR); }); - it("Click outside of the new status label, set its state to default", () => { + it("Clicking outside the focused label restores it to its default state.", () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') .eq(1) .click(); @@ -378,10 +372,8 @@ describe("Workflow Status Labels", () => { }); }); - describe("Edit Status Label", () => { - it(`Open more options menu and click edit: - should open the edit form and fill out the form with the label data`, () => { - // const label = WORKFLOW_STATUS_LABELS.data[0]; + context("Edit Status Label", () => { + it(`opens edit form and verifies pre-filled data`, () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) @@ -422,7 +414,7 @@ describe("Workflow Status Labels", () => { .and("be.enabled"); }); - it("Save Edit: Request Data should match the form data", () => { + it("verifies edited request data and apply changes to form", () => { cy.intercept( "PUT", `${ENDPOINTS?.createStatusLabel}/${EDIT_STATUS_LABEL_DATA?.ZUID}`, @@ -469,9 +461,35 @@ describe("Workflow Status Labels", () => { cy.wait("@getWorkflowLabels"); }); }); + context("Re-order Status Labels", () => { + it("Verify order sent to backend", () => { + cy.intercept("PUT", ENDPOINTS.reOrderLabels).as("reorderStatusLabel"); + const dataTransfer = new DataTransfer(); + cy.get(`[data-cy="status-label"]`) + .find('[data-cy="status-label-drag-handle"]') + .eq(0) + .trigger("dragstart", { + dataTransfer, + }); + + cy.get(".MuiTreeView-root"); + cy.get(`[data-cy="status-label"]`) + .find('[data-cy="status-label-drag-handle"]') + .eq(1) + .trigger("drop", { + dataTransfer, + }); + + cy.wait("@reorderStatusLabel").then((interception) => { + expect(interception.request.body).to.deep.equal( + REORDER_STATUS_LABEL_DATA + ); + }); + }); + }); - describe("Deactivate Status Label", () => { - it("Deactivate Label from more options menu", () => { + context("Deactivate Status Label", () => { + it("opens deactivation dialog and verifies elements", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) @@ -487,15 +505,11 @@ describe("Workflow Status Labels", () => { cy.get("button").contains("Cancel").should("exist"); cy.get("button").contains("Deactivate Status").should("exist"); }); - it("Deactivation Dialog should contain the label name, cancel button and deactivate button", () => { - cy.intercept( - "DELETE", - `${ENDPOINTS?.createStatusLabel}/${EDIT_STATUS_LABEL_DATA?.ZUID}`, - { - statusCode: 200, - body: {}, - } - ).as("deactivateStatusLabel"); + it("deactivates status label and verifies request", () => { + cy.intercept("DELETE", `${ENDPOINTS?.createStatusLabel}/**`, { + statusCode: 200, + body: {}, + }).as("deactivateStatusLabel"); cy.intercept("GET", ENDPOINTS?.workflowStatusLabels, { statusCode: 200, body: { diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx index 5b7b55e2da..610f1d17bc 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx @@ -132,6 +132,7 @@ export const StatusLabel: FC = ({ }} > drag(drop(node as HTMLElement)) From aea7d7ba4de8632ed029240503f1a0fba9686c84 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 2 Jan 2025 12:52:55 +0800 Subject: [PATCH 37/59] task: update tests --- cypress/e2e/content/pages/ContentItemPage.js | 2 + cypress/e2e/content/workflows.spec.js | 93 +++++++++++++++++--- cypress/e2e/settings/pages/SettingsPage.js | 43 +++++++++ 3 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 cypress/e2e/settings/pages/SettingsPage.js diff --git a/cypress/e2e/content/pages/ContentItemPage.js b/cypress/e2e/content/pages/ContentItemPage.js index f37daf65d9..b2379e8e0d 100644 --- a/cypress/e2e/content/pages/ContentItemPage.js +++ b/cypress/e2e/content/pages/ContentItemPage.js @@ -16,6 +16,8 @@ class ContentItemPage { publishItemButton: () => cy.getBySelector("PublishButton"), confirmPublishItemButton: () => cy.getBySelector("ConfirmPublishButton"), toast: () => cy.getBySelector("toast"), + contentPublishedIndicator: () => + cy.getBySelector("ContentPublishedIndicator"), }; } diff --git a/cypress/e2e/content/workflows.spec.js b/cypress/e2e/content/workflows.spec.js index 30de78ff99..e9d4fda394 100644 --- a/cypress/e2e/content/workflows.spec.js +++ b/cypress/e2e/content/workflows.spec.js @@ -1,28 +1,75 @@ import ContentItemPage from "./pages/ContentItemPage"; +import SettingsPage from "../settings/pages/SettingsPage"; -const TITLE = "Content item workflow test"; +const TITLES = { + contentItem: "Content item workflow test", + publishLabel: "Publish Approval", + testLabel: "Random Test Label", +}; describe("Content Item Workflows", () => { before(() => { - //TODO: Find a way to seed the labels prior to starting tests - + cy.intercept("POST", "**/labels").as("createLabel"); + cy.intercept("GET", "**/labels*").as("getLabels"); + + // Create allow publish workflow label + SettingsPage.createWorkflowLabel({ name: TITLES.testLabel }); + cy.wait(["@createLabel", "@getLabels"]); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' + ) + .contains(TITLES.testLabel) + .should("exist"); + + SettingsPage.createWorkflowLabel({ + name: TITLES.publishLabel, + allowPublish: true, + }); + cy.wait(["@createLabel", "@getLabels"]); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' + ) + .contains(TITLES.publishLabel) + .should("exist"); + + // Visit test page cy.visit("/content/6-b6cde1aa9f-wftv50/new"); - - cy.get("#12-a6d48ca2b7-zxqns2").should("exist").find("input").type(TITLE); + cy.get("#12-a6d48ca2b7-zxqns2") + .should("exist") + .find("input") + .type(TITLES.contentItem); cy.get("#12-d29ab9bbe0-9k6j70") .should("exist") .find("textarea") .first() - .type(TITLE); + .type(TITLES.contentItem); ContentItemPage.elements.createItemButton().should("exist").click(); ContentItemPage.elements.duoModeToggle().should("exist").click(); }); after(() => { - // TODO: Delete seeded labels after test + // Delete test content item ContentItemPage.elements.moreMenu().should("exist").click(); ContentItemPage.elements.deleteItemButton().should("exist").click(); ContentItemPage.elements.confirmDeleteItemButton().should("exist").click(); + cy.intercept("DELETE", "**/content/models/6-b6cde1aa9f-wftv50/items/*").as( + "deleteContentItem" + ); + cy.wait("@deleteContentItem"); + + // Delete allow publish label after test + SettingsPage.deactivateWorkflowLabel(TITLES.testLabel); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' + ) + .contains("Random Test Label") + .should("not.exist"); + SettingsPage.deactivateWorkflowLabel(TITLES.publishLabel); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' + ) + .contains("Publish Approval") + .should("not.exist"); }); it("Can add a workflow label", () => { @@ -30,7 +77,7 @@ describe("Content Item Workflows", () => { ContentItemPage.elements.addWorkflowStatusLabel().should("exist").click(); ContentItemPage.elements .workflowStatusLabelOption() - .last() + .contains(TITLES.testLabel) .should("exist") .click(); @@ -80,13 +127,39 @@ describe("Content Item Workflows", () => { cy.get("body").type("{esc}"); }); - it("Cannot publish a content item if needed workflow label is missing", () => { + it("Cannot publish a content item if label with allowPublish is missing", () => { ContentItemPage.elements.publishItemButton().should("exist").click(); ContentItemPage.elements.confirmPublishItemButton().should("exist").click(); ContentItemPage.elements .toast() .contains( - `Cannot Publish: "${TITLE}". Does not have a status that allows publishing` + `Cannot Publish: "${TITLES.contentItem}". Does not have a status that allows publishing` ); }); + + it("Can publish a content item if label with allowPublish is applied", () => { + cy.reload(); + ContentItemPage.elements.versionSelector().should("exist").click(); + ContentItemPage.elements.addWorkflowStatusLabel().should("exist").click(); + ContentItemPage.elements + .workflowStatusLabelOption() + .contains(TITLES.publishLabel) + .should("exist") + .click(); + + cy.get("body").type("{esc}"); + + cy.intercept("PUT", "**/labels/*").as("updateLabel"); + cy.wait("@updateLabel"); + + cy.reload(); + + ContentItemPage.elements.publishItemButton().should("exist").click(); + ContentItemPage.elements.confirmPublishItemButton().should("exist").click(); + + cy.intercept("GET", "**/publishings").as("publish"); + cy.wait("@publish"); + + ContentItemPage.elements.contentPublishedIndicator().should("exist"); + }); }); diff --git a/cypress/e2e/settings/pages/SettingsPage.js b/cypress/e2e/settings/pages/SettingsPage.js new file mode 100644 index 0000000000..14a8e4ecaa --- /dev/null +++ b/cypress/e2e/settings/pages/SettingsPage.js @@ -0,0 +1,43 @@ +class SettingsPage { + createWorkflowLabel({ name, allowPublish = false }) { + cy.visit("/settings/user/workflows"); + + cy.get("button").contains("Create Status").click(); + cy.get('input[name="name"]').type(name); + cy.get('textarea[name="description"]').type(`${name} Description`); + cy.get('input[name="color"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains("Yellow").click(); + cy.get('input[name="addPermissionRoles"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains("Admin").click(); + cy.get('ul li[role="option"]').contains("Developer").click(); + cy.get('form[role="dialog"]').click(); + cy.get('input[name="removePermissionRoles"]') + .parent() + .find("button") + .click(); + cy.get('ul li[role="option"]').contains("Admin").click(); + cy.get('ul li[role="option"]').contains("Developer").click(); + cy.get('form[role="dialog"]').click(); + if (allowPublish) { + cy.get('input[name="allowPublish"]').click(); + } + cy.get('[data-cy="create-status-label-submit-button"]').click(); + } + + deactivateWorkflowLabel(name) { + cy.intercept("DELETE", "**/labels/*").as("deleteLabel"); + + cy.visit("/settings/user/workflows"); + + cy.contains(name) + .closest('[data-cy="status-label"]:visible') + .find("button") + .click(); + cy.get('ul li[role="menuitem"]').contains("Deactivate Status").click(); + cy.get("button").contains("Deactivate Status").click(); + + cy.wait("@deleteLabel"); + } +} + +export default new SettingsPage(); From 00377ee5c7d28bf0a756e393194bb211370b288a Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 2 Jan 2025 12:53:12 +0800 Subject: [PATCH 38/59] fix: filter out undefined workflow labels --- .../VersionSelector/VersionItem.tsx | 2 +- .../ItemEditHeader/VersionSelector/index.tsx | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx index 18c34abeae..f1298979ea 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx @@ -92,7 +92,7 @@ export const VersionItem = memo( const [filterKeyword, setFilterKeyword] = useState(""); const [debouncedFilterKeyword, setDebouncedFilterKeyword] = useState(""); const [activeLabels, setActiveLabels] = useState( - data?.labels?.map((label) => label.ZUID) + data?.labels?.map((label) => label?.ZUID)?.filter((label) => !!label) ); const currentUserRoleZUID = usersRoles?.find( diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/index.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/index.tsx index 71b192cff6..ac26b5394c 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/index.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/index.tsx @@ -122,9 +122,9 @@ export const VersionSelector = memo( }, [versions, itemPublishings, itemWorkflowStatus, statusLabels]); const activeVersionLabels = useMemo(() => { - return mappedVersions?.find( - (version) => version.itemVersion === activeVersion - )?.labels; + return mappedVersions + ?.find((version) => version.itemVersion === activeVersion) + ?.labels?.filter((label) => !!label); }, [mappedVersions, activeVersion]); useEffect(() => { @@ -198,15 +198,15 @@ export const VersionSelector = memo( {!!activeVersionLabels?.length && ( <> Date: Thu, 2 Jan 2025 13:55:27 +0800 Subject: [PATCH 39/59] fix: update tests --- cypress/e2e/content/workflows.spec.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cypress/e2e/content/workflows.spec.js b/cypress/e2e/content/workflows.spec.js index e9d4fda394..5108522e39 100644 --- a/cypress/e2e/content/workflows.spec.js +++ b/cypress/e2e/content/workflows.spec.js @@ -79,7 +79,7 @@ describe("Content Item Workflows", () => { .workflowStatusLabelOption() .contains(TITLES.testLabel) .should("exist") - .click(); + .click({ force: true }); cy.get("body").type("{esc}"); @@ -108,7 +108,7 @@ describe("Content Item Workflows", () => { .workflowStatusLabelOption() .first() .should("exist") - .click(); + .click({ force: true }); cy.get("body").type("{esc}"); @@ -128,8 +128,14 @@ describe("Content Item Workflows", () => { }); it("Cannot publish a content item if label with allowPublish is missing", () => { - ContentItemPage.elements.publishItemButton().should("exist").click(); - ContentItemPage.elements.confirmPublishItemButton().should("exist").click(); + ContentItemPage.elements + .publishItemButton() + .should("exist") + .click({ force: true }); + ContentItemPage.elements + .confirmPublishItemButton() + .should("exist") + .click({ force: true }); ContentItemPage.elements .toast() .contains( @@ -145,7 +151,7 @@ describe("Content Item Workflows", () => { .workflowStatusLabelOption() .contains(TITLES.publishLabel) .should("exist") - .click(); + .click({ force: true }); cy.get("body").type("{esc}"); From 296ac60f894bcc0af109e4cfba02c5386a3e8822 Mon Sep 17 00:00:00 2001 From: geodem Date: Thu, 2 Jan 2025 13:59:46 +0800 Subject: [PATCH 40/59] Added workflow labels test cases, added config to support ts in cypress and add autocompletion --- cypress/e2e/settings/workflows.spec.js | 674 +++++++----------- cypress/support/index.ts | 17 + .../User/Workflows/authorized/StatusLabel.tsx | 8 +- .../forms-dialogs/StatusLabelForm.tsx | 12 +- tsconfig.json | 2 +- 5 files changed, 286 insertions(+), 427 deletions(-) create mode 100644 cypress/support/index.ts diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index ab93f7f04c..23bc2c0292 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -1,357 +1,200 @@ +import { colorMenu } from "../../../src/shell/services/types"; + const FOCUSED_LABEL_COLOR = "rgba(253, 133, 58, 0.1)"; -const ENDPOINTS = { - userRole: "**/v1/users/**/roles", - instanceUsers: `**/v1/instances/**/users/roles`, - workflowStatusLabels: "**/v1/env/labels?showDeleted=true", - createStatusLabel: "/v1/env/labels", - reOrderLabels: "/v1/env/labels", +const API_ENDPOINTS = { + instance: "**/v1/instances/**", + userRoles: "**/v1/users/**/roles", + instanceRoles: "**/v1/instances/**/roles", + instanceUsers: `**/v1/instances/**/users`, + instanceUserRoles: `**/v1/instances/**/users/roles`, + allStatusLabels: "**/v1/env/labels?showDeleted=true", + statusLabels: "/v1/env/labels", }; -const USERS_ROLE_RESTRICTED = { - data: [ - { - ZUID: "30-86f8ccec82-swp72s", - entityZUID: "8-f48cf3a682-7fthvk", - name: "Developer", - systemRoleZUID: "31-71cfc74-4dm13", - systemRole: { - ZUID: "31-71cfc74-4dm13", + +const USER_ROLES = { + restricted: { + data: [ + { + ZUID: "30-8ee88afe82-gmx631", + entityZUID: "8-f48cf3a682-7fthvk", name: "Developer", + systemRoleZUID: "31-71cfc74-d3v3l0p3r", + systemRole: { + ZUID: "31-71cfc74-d3v3l0p3r", + name: "Developer", + }, }, - }, - { - ZUID: "30-949785e98a-230bxj", - entityZUID: "8-e486ecc489-tvdpvb", - name: "Contributor", - systemRoleZUID: "31-71cfc74-c0ntr1b0t0r", - systemRole: { - ZUID: "31-71cfc74-c0ntr1b0t0r", - name: "Contributor", - }, - }, - ], -}; -const USERS_ROLE_AUTORIZED = { - data: [ - { - ZUID: "30-86f8ccec82-swp72s", - entityZUID: "8-f48cf3a682-7fthvk", - name: "Admin", - systemRoleZUID: "31-71cfc74-4dm13", - systemRole: { - ZUID: "31-71cfc74-4dm13", - name: "Admin", - }, - }, - { - ZUID: "30-949785e98a-230bxj", - entityZUID: "8-e486ecc489-tvdpvb", - name: "Contributor", - systemRoleZUID: "31-71cfc74-c0ntr1b0t0r", - systemRole: { - ZUID: "31-71cfc74-c0ntr1b0t0r", - name: "Contributor", - }, - }, - ], -}; -const INSTANCE_USERS = { - data: [ - { - ZUID: "5-44ccc74-xxxxxxx", - firstName: "Test", - lastName: "User-1", - email: "Test@User1.io", - role: { - ZUID: "30-88a0ead882-xxxxxxx", - entityZUID: "8-f48cf3a682-xxxxxxx", - name: "Owner", - }, - }, - { - ZUID: "5-57801f6-zzzzzz", - firstName: "Test", - lastName: "User-2", - email: "Test.blair@User2.io", - role: { - ZUID: "30-fcb3fc9083-zzzzzz", - entityZUID: "8-f48cf3a682-zzzzzz", - name: "Admin", - }, - }, - { - ZUID: "5-84d1e6d4ae-yyyyyy", - firstName: "Test", - lastName: "User-3", - email: "Test.galindo@User3.io", - role: { - ZUID: "30-86f8ccec82-yyyyyy", - entityZUID: "8-f48cf3a682-yyyyyy", + ], + }, + authorized: { + data: [ + { + ZUID: "30-8ee88afe82-gmx631", + entityZUID: "8-f48cf3a682-7fthvk", name: "Admin", + systemRoleZUID: "31-71cfc74-4dm13", + systemRole: { + ZUID: "31-71cfc74-4dm13", + name: "Admin", + }, }, - }, - ], + ], + }, }; -const WORKFLOW_STATUS_LABELS = { - data: [ - { - ZUID: "status-1", - name: "In Review", - description: "Content is being reviewed", - color: "#F04438", - sort: 1, - deletedAt: null, - allowPublish: true, - addPermissionRoles: ["role-1"], - removePermissionRoles: ["role-1"], - }, - { - ZUID: "status-2", - name: "Approved", - description: "Content has been approved", - color: "#12B76A", - sort: 2, - deletedAt: null, - allowPublish: true, - addPermissionRoles: [], - removePermissionRoles: [], - }, - { - ZUID: "status-3", - name: "Archived", - description: "Archived content", - color: "#7A5AF8", - sort: 3, - deletedAt: "2024-03-15", - allowPublish: false, - addPermissionRoles: [], - removePermissionRoles: [], - }, - ], -}; -const CREATE_STATUS_LABEL_RESPONSE_DATA = { - ZUID: "status-4", - name: "Test Label", - description: "Test Label Description", - color: "#F79009", - sort: 4, - allowPublish: true, - addPermissionRoles: ["30-86f8ccec82-swp72s"], - removePermissionRoles: ["30-86f8ccec82-swp72s"], -}; -const EDIT_STATUS_LABEL_DATA = { - ZUID: "status-4", - name: "Pending Approval", - description: "Pending Approval Description", - color: "#EE46BC", - sort: 4, - allowPublish: false, - addPermissionRoles: [], - removePermissionRoles: [], + +const LABELS = { + restrictedPageHeader: "You need permission to view and edit workflows", + restrictedPageSubheader: + "Contact the instance owner or administrators listed below to upgrade your role to Admin or Owner", + activeLabelsHeader: "Active Statuses", + activeLabelsSubheader: + "Active statuses are available to be added and removed from content items", + deactivatedLabelsHeader: "Deactivated Statuses", + deactivatedLabelsSubheader: + "These statuses can be re-activated at any time if you would like to add or remove them from content items", }; -const REORDER_STATUS_LABEL_DATA = { - data: [ - { ZUID: "status-1", sort: 1 }, - { ZUID: "status-2", sort: 2 }, - { ZUID: "status-4", sort: 3 }, - ], + +const FORM_DATA = { + create: { + name: "Test Status Label", + description: "Test Status Label Description", + color: "Grey", + addPermissionRoles: "Admin", + removePermissionRoles: "Admin", + allowPublish: true, + }, + edit: { + name: "Test Status Label - Edited", + description: "Test Status Label - Edited Description", + color: "Rose", + addPermissionRoles: "Admin", + removePermissionRoles: "Admin", + allowPublish: false, + }, }; -describe("Workflow Status Labels", () => { - context("Restricted User", () => { - before(() => { - //RESTRICTED USER - cy.intercept("GET", ENDPOINTS?.userRole, { - statusCode: 200, - body: { - ...USERS_ROLE_RESTRICTED, - }, - }).as("getRestrictedUser"); - // INSTANCE USERS - cy.intercept("GET", ENDPOINTS?.instanceUsers, { - statusCode: 200, - body: { - ...INSTANCE_USERS, - }, - }).as("getInstanceUsers"); - }); - it("displays restricted access message and admin profiles", () => { - cy.visit("/settings/user/workflows"); - cy.wait("@getRestrictedUser"); - cy.wait("@getInstanceUsers"); - cy.contains("You need permission to view and edit workflows").should( - "exist" - ); - cy.contains( - "Contact the instance owner or administrators listed below to upgrade your role to Admin or Owner" - ).should("exist"); - cy.get('[data-cy="restricted-image"]').should("exist"); - }); +describe("Workflow Status Labels: Restricted User", () => { + it("displays restricted access message and admin profiles", () => { + cy.intercept("GET", API_ENDPOINTS?.userRoles, { + statusCode: 200, + body: { + ...USER_ROLES?.restricted, + }, + }).as("getRestrictedUser"); - it("Should display 3 admin profiles", () => { - cy.get( - '[data-cy="user-profile-container"] [data-cy="user-profile"]' - ).should("have.length", 3); - }); + cy.intercept("GET", API_ENDPOINTS?.instanceUserRoles).as( + "getInstanceUserRoles" + ); + + cy.visit("/settings/user/workflows"); + cy.wait("@getRestrictedUser") + .its("response.body") + .then((body) => { + cy.wrap(body).as("userRoles"); + }); + cy.contains(LABELS?.restrictedPageHeader).should("exist"); + cy.contains(LABELS?.restrictedPageSubheader).should("exist"); + cy.get('[data-cy="restricted-image"]').should("exist"); + + cy.wait("@getInstanceUserRoles") + .its("response.body") + .then((body) => { + const authorizedUsers = body?.data.filter((user) => + ["Owner", "Admin"].includes(user?.role?.name) + ); + cy.get('[data-cy="user-profile-container"]') + .children() + .its("length") + .should("eq", authorizedUsers.length); + }); }); +}); - context("Authorized User", () => { +describe("Workflow Status Labels: Authorized User", () => { + context("Workflow Page", () => { before(() => { - cy.intercept("GET", ENDPOINTS?.userRole, { - statusCode: 200, - body: { - ...USERS_ROLE_AUTORIZED, - }, - }).as("getAuthorizedUser"); - cy.intercept("GET", ENDPOINTS.workflowStatusLabels, { - statusCode: 200, - body: { - ...WORKFLOW_STATUS_LABELS, - }, - }).as("getWorkflowLabels"); + cy.visit("/settings/user/workflows"); }); - it("displays workflow page elements", () => { + it("displays workflow page elements for authorized users", () => { cy.visit("/settings/user/workflows"); - cy.wait(["@getAuthorizedUser", "@getWorkflowLabels"]); cy.contains("Workflows").should("exist"); cy.get("button").contains("Create Status").should("exist"); cy.get('input[placeholder="Search Statuses"]').should("exist"); cy.get('input[value="deactivated"]').should("exist"); }); - it("Should display 2 active status labels", () => { - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 2); - }); - - context("Show Deactivated Labels", () => { - it("displays correct headings and labels", () => { - cy.get('input[value="deactivated"]').click(); - cy.get('input[value="deactivated"]').should("be.checked"); - cy.contains("Active Statuses").should("exist"); - cy.contains("Deactivated Statuses").should("exist"); - cy.contains( - "Active statuses are available to be added and removed from content items" - ).should("exist"); - cy.contains( - "These statuses can be re-activated at any time if you would like to add or remove them from content items" - ).should("exist"); - }); - it("Should display 2 active status labels", () => { - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 2); - }); - - it("Should display 1 deactivated status labels", () => { - cy.get( - '[data-cy="deactivated-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 1); - }); - }); - context("Search Functionality", () => { - it("filters active and deactivated labels", () => { - cy.get('[data-cy="status-label-search-box"] input').type("approved"); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 1); - cy.get( - '[data-cy="deactivated-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 0); - - cy.get('[data-cy="status-label-search-box"] input').clear(); - cy.get('[data-cy="status-label-search-box"] input').type("archived"); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 0); - cy.get( - '[data-cy="deactivated-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 1); - }); - - it("handles no results and reset", () => { - cy.get('[data-cy="status-label-search-box"] input').clear(); - cy.get('[data-cy="status-label-search-box"] input').type("xxxxxx"); - cy.get('[data-cy="no-results-page"]').should("exist"); - cy.get("button").contains("Search Again").click(); - - cy.get('[data-cy="no-results-page"]').should("not.exist"); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 2); - cy.get( - '[data-cy="deactivated-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 1); - cy.get('input[placeholder="Search Statuses"]') - .should("have.value", "") - .and("have.focus"); - }); + it("Show Deactivated Labels: Displays active and deactivated status labels", () => { + cy.get('input[value="deactivated"]').click(); + cy.contains(LABELS.activeLabelsHeader).should("exist"); + cy.contains(LABELS.activeLabelsHeader).should("exist"); + cy.contains(LABELS.deactivatedLabelsHeader).should("exist"); + cy.contains(LABELS.deactivatedLabelsHeader).should("exist"); }); }); - context("Create Status Label", () => { + context("Create New Status Label", () => { before(() => { + cy.intercept("GET", API_ENDPOINTS.userRoles, { + statusCode: 200, + body: USER_ROLES.authorized, + }).as("getAuthorizedUser"); + cy.intercept("GET", API_ENDPOINTS.allStatusLabels).as( + "getAllStatusLabels" + ); + + cy.visit("/settings/user/workflows"); + cy.wait(["@getAuthorizedUser", "@getAllStatusLabels"]); + cy.get("button").contains("Create Status").click(); }); - it("displays form with all fields", () => { - cy.get('form[role="dialog"]').should("exist"); - cy.get('input[name="name"]').should("exist"); - cy.get('textarea[name="description"]').should("exist"); - cy.get('input[name="color"]').should("exist"); - cy.get('input[name="addPermissionRoles"]').should("exist"); - cy.get('input[name="removePermissionRoles"]').should("exist"); - cy.get('input[name="allowPublish"]').should("exist"); - cy.get("button").contains("Cancel").should("exist"); - cy.get("button").contains("Create Status").should("exist"); - }); - it("fills out and submits form", () => { - cy.get('input[name="name"]').type("Test Label"); - cy.get('textarea[name="description"]').type("Test Label Description"); + it("Form Validation: should display error message when required fields are empty", () => { + const nameFieldErrorMessage = "Name is required"; + cy.get('[data-cy="status-label-submit-button"]').click(); + cy.contains(nameFieldErrorMessage).should("exist"); + }); + it("Fills out and submits form", () => { + cy.get('input[name="name"]').type(FORM_DATA.create.name); + cy.get('textarea[name="description"]').type(FORM_DATA.create.description); cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains("Yellow").click(); + cy.get('ul li[role="option"]').contains(FORM_DATA.create.color).click(); cy.get('input[name="addPermissionRoles"]') .parent() .find("button") .click(); - cy.get('ul li[role="option"]').contains("Admin").click(); + cy.get('ul li[role="option"]') + .contains(FORM_DATA.create.addPermissionRoles) + .click(); cy.get('form[role="dialog"]').click(); cy.get('input[name="removePermissionRoles"]') .parent() .find("button") .click(); - cy.get('ul li[role="option"]').contains("Admin").click(); + cy.get('ul li[role="option"]') + .contains(FORM_DATA.create.removePermissionRoles) + .click(); cy.get('form[role="dialog"]').click(); - cy.get('input[name="allowPublish"]').click(); - }); + cy.get('input[name="allowPublish"]').check(); - it("submits form and verifies request data", () => { - cy.intercept("POST", ENDPOINTS?.createStatusLabel, { - statusCode: 200, - body: { - data: CREATE_STATUS_LABEL_RESPONSE_DATA, - }, - }).as("createStatusLabel"); - cy.intercept("GET", ENDPOINTS?.workflowStatusLabels, { - statusCode: 200, - body: { - data: [ - ...WORKFLOW_STATUS_LABELS.data, - { ...CREATE_STATUS_LABEL_RESPONSE_DATA }, - ], - }, - }).as("getWorkflowLabels"); + cy.get('[data-cy="status-label-submit-button"]').click(); - cy.get('[data-cy="create-status-label-submit-button"]').click(); + cy.intercept("POST", API_ENDPOINTS?.statusLabels).as("createStatusLabel"); - cy.wait("@createStatusLabel").then((interception) => { - const { ZUID, sort, ...reqData } = CREATE_STATUS_LABEL_RESPONSE_DATA; - expect(interception.request.body).to.deep.equal(reqData); + cy.wait("@createStatusLabel").then(({ response }) => { + const responseData = response?.body?.data; + expect(response?.statusCode).to.be.ok; + expect(responseData.name).to.equal(FORM_DATA.create.name); + expect(responseData.description).to.equal(FORM_DATA.create.description); + expect(responseData.color).to.equal( + colorMenu.find((color) => color?.label === FORM_DATA.create.color) + ?.value + ); + expect(responseData.addPermissionRoles).to.have.lengthOf(1); + expect(responseData.removePermissionRoles).to.have.lengthOf(1); + expect(responseData.allowPublish).to.be.true; }); - cy.wait("@getWorkflowLabels"); }); - it("Shows the newly created label and focuses it", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' @@ -373,7 +216,20 @@ describe("Workflow Status Labels", () => { }); context("Edit Status Label", () => { - it(`opens edit form and verifies pre-filled data`, () => { + it("Open Status Label and Edit Details", () => { + cy.intercept("PUT", `${API_ENDPOINTS?.statusLabels}/**`).as( + "editStatusLabel" + ); + cy.intercept("GET", API_ENDPOINTS.allStatusLabels).as( + "getAllStatusLabels" + ); + + cy.visit("/settings/user/workflows"); + + cy.wait("@getAllStatusLabels").then((interception) => { + cy.wrap(interception.response.body.data).as("statusLabels"); + }); + cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) @@ -381,89 +237,70 @@ describe("Workflow Status Labels", () => { .find("button") .click(); cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); - cy.get('input[name="name"]').should( - "have.value", - CREATE_STATUS_LABEL_RESPONSE_DATA?.name - ); - cy.get('textarea[name="description"]').should( - "have.value", - CREATE_STATUS_LABEL_RESPONSE_DATA?.description - ); - cy.get('input[name="color"]').should( - "have.value", - CREATE_STATUS_LABEL_RESPONSE_DATA?.color - ); - cy.get('input[name="addPermissionRoles"]').should( - "have.value", - CREATE_STATUS_LABEL_RESPONSE_DATA?.addPermissionRoles.join(", ") - ); - cy.get('input[name="removePermissionRoles"]').should( - "have.value", - CREATE_STATUS_LABEL_RESPONSE_DATA?.removePermissionRoles.join(", ") - ); - cy.get('input[name="allowPublish"]').should( - !!CREATE_STATUS_LABEL_RESPONSE_DATA?.allowPublish - ? "be.checked" - : "not.be.checked" - ); - }); - it("Deactivation button should be visible and enabled", () => { + cy.get("button") .contains("Deactivate Status") .should("exist") .and("be.enabled"); - }); - - it("verifies edited request data and apply changes to form", () => { - cy.intercept( - "PUT", - `${ENDPOINTS?.createStatusLabel}/${EDIT_STATUS_LABEL_DATA?.ZUID}`, - { - statusCode: 200, - body: { - data: EDIT_STATUS_LABEL_DATA?.ZUID, - }, - } - ).as("editStatusLabel"); - cy.intercept("GET", ENDPOINTS?.workflowStatusLabels, { - statusCode: 200, - body: { - data: [...WORKFLOW_STATUS_LABELS.data, { ...EDIT_STATUS_LABEL_DATA }], - }, - }).as("getWorkflowLabels"); - cy.get('input[name="name"]').clear(); - cy.get('input[name="name"]').type(EDIT_STATUS_LABEL_DATA.name); - cy.get('textarea[name="description"]').clear(); - cy.get('textarea[name="description"]').type( - EDIT_STATUS_LABEL_DATA.description - ); + cy.get('input[name="name"]').clear().type(FORM_DATA.edit.name); + cy.get('textarea[name="description"]') + .clear() + .type(FORM_DATA.edit.description); cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains("Pink").click(); + cy.get('ul li[role="option"]').contains(FORM_DATA.edit.color).click(); cy.get("span.MuiChip-label") - .contains("Admin") + .contains(FORM_DATA.edit.addPermissionRoles) .parent() .find('svg[data-testid="CancelIcon"]') .click(); cy.get('form[role="dialog"]').click(); cy.get("span.MuiChip-label") - .contains("Admin") + .contains(FORM_DATA.edit.removePermissionRoles) .parent() .find('svg[data-testid="CancelIcon"]') .click(); cy.get('form[role="dialog"]').click(); - cy.get('input[name="allowPublish"]').click(); - cy.get('[data-cy="create-status-label-submit-button"]').click(); - cy.wait("@editStatusLabel").then((interception) => { - const { ZUID, sort, ...reqData } = EDIT_STATUS_LABEL_DATA; - expect(interception.request.body).to.deep.equal(reqData); - }); - cy.wait("@getWorkflowLabels"); + cy.get('input[name="allowPublish"]').uncheck(); + + cy.get('[data-cy="status-label-submit-button"]').click(); + + cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( + (editStatusLabel, getAllStatusLabels) => { + const targetLabelZUID = editStatusLabel.response.body.data; + const updatedLabel = getAllStatusLabels.response.body.data.find( + (label) => label.ZUID === targetLabelZUID + ); + + expect(editStatusLabel.response.statusCode).to.eq(200); + expect(getAllStatusLabels.response.statusCode).to.eq(200); + + expect(updatedLabel).to.deep.include({ + name: FORM_DATA.edit.name, + color: colorMenu.find( + (color) => color?.label === FORM_DATA.edit.color + )?.value, + addPermissionRoles: [], + removePermissionRoles: [], + allowPublish: false, + }); + } + ); }); }); + context("Re-order Status Labels", () => { - it("Verify order sent to backend", () => { - cy.intercept("PUT", ENDPOINTS.reOrderLabels).as("reorderStatusLabel"); + it("Drag status label to a new position", () => { + cy.intercept("PUT", API_ENDPOINTS.statusLabels).as("reorderStatusLabel"); + cy.intercept("GET", API_ENDPOINTS.allStatusLabels).as( + "getAllStatusLabels" + ); + + cy.visit("/settings/user/workflows"); + cy.wait("@getAllStatusLabels").then((interception) => { + cy.wrap(interception.response.body.data).as("oldStatusLabels"); + }); + const dataTransfer = new DataTransfer(); cy.get(`[data-cy="status-label"]`) .find('[data-cy="status-label-drag-handle"]') @@ -475,20 +312,34 @@ describe("Workflow Status Labels", () => { cy.get(`[data-cy="status-label"]`) .find('[data-cy="status-label-drag-handle"]') .eq(1) + .trigger("dragover", { + dataTransfer, + }) .trigger("drop", { dataTransfer, }); - cy.wait("@reorderStatusLabel").then((interception) => { - expect(interception.request.body).to.deep.equal( - REORDER_STATUS_LABEL_DATA - ); - }); + cy.wait("@reorderStatusLabel") + .its("response.statusCode") + .should("eq", 200); }); }); context("Deactivate Status Label", () => { - it("opens deactivation dialog and verifies elements", () => { + it("opens deactivation dialog and connfirms deactivation", () => { + cy.intercept("DELETE", `${API_ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" + ); + + cy.intercept("GET", API_ENDPOINTS.allStatusLabels).as( + "getAllStatusLabels" + ); + + cy.visit("/settings/user/workflows"); + cy.wait("@getAllStatusLabels").then((interception) => { + cy.wrap(interception.response.body.data).as("oldStatusLabels"); + }); + cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) @@ -499,53 +350,36 @@ describe("Workflow Status Labels", () => { cy.get('ul li[role="menuitem"]').contains("Deactivate Status").click(); cy.get('[data-cy="deactivation-dialog"]').should("exist"); cy.get("h5").contains(`Deactivate Status`).should("exist"); - cy.get("h5").contains(EDIT_STATUS_LABEL_DATA?.name).should("exist"); + cy.get("h5").contains(FORM_DATA?.edit?.name).should("exist"); cy.get("button").contains("Cancel").should("exist"); cy.get("button").contains("Deactivate Status").should("exist"); - }); - it("deactivates status label and verifies request", () => { - cy.intercept("DELETE", `${ENDPOINTS?.createStatusLabel}/**`, { - statusCode: 200, - body: {}, - }).as("deactivateStatusLabel"); - cy.intercept("GET", ENDPOINTS?.workflowStatusLabels, { - statusCode: 200, - body: { - data: [ - ...WORKFLOW_STATUS_LABELS.data, - { - ...EDIT_STATUS_LABEL_DATA, - deletedAt: new Date().toISOString(), - }, - ], - }, - }).as("getWorkflowLabels"); + cy.get("button").contains("Deactivate Status").click(); - cy.wait("@deactivateStatusLabel").then((interception) => { - expect(interception.request.url).to.include( - `${ENDPOINTS?.createStatusLabel}/${EDIT_STATUS_LABEL_DATA?.ZUID}` - ); - }); - cy.wait("@getWorkflowLabels"); - }); - it("Snackbar should show the success message with the label name", () => { - cy.get(".notistack-Snackbar") - .contains(`Status De-activated`) - .should("exist"); - cy.get(".notistack-Snackbar") - .contains(EDIT_STATUS_LABEL_DATA?.name) - .should("exist"); - }); - it("Labels should now show 2 active and 2 deactivated labels", () => { - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 2); + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( + (deactivateStatusLabel, newStatusLabels) => { + const targetLabelZUID = deactivateStatusLabel.response.body.data; - cy.get( - '[data-cy="deactivated-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", 2); + expect(deactivateStatusLabel.response.statusCode).to.eq(200); + expect(newStatusLabels.response.statusCode).to.eq(200); + + cy.get("@oldStatusLabels").then((oldStatusLabels) => { + const oldLabelCount = oldStatusLabels.filter( + (label) => !label?.deletedAt + ).length; + cy.get(".notistack-Snackbar") + .contains(`Status De-activated`) + .should("exist"); + cy.get(".notistack-Snackbar") + .contains(FORM_DATA?.edit?.name) + .should("exist"); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' + ).should("have.length", oldLabelCount - 1); + }); + } + ); }); }); }); diff --git a/cypress/support/index.ts b/cypress/support/index.ts new file mode 100644 index 0000000000..2cb059a3d8 --- /dev/null +++ b/cypress/support/index.ts @@ -0,0 +1,17 @@ +import "./commands"; + +declare global { + namespace Cypress { + interface Chainable { + waitOn(path: string, cb: () => void): Chainable>; + login(): Chainable>; + getBySelector( + selector: string, + ...args: any[] + ): Chainable>; + blockLock(): Chainable>; + assertClipboardValue(value: string): Chainable>; + blockAnnouncements(): Chainable>; + } + } +} diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx index 610f1d17bc..d94fcb0a4b 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx @@ -108,7 +108,9 @@ export const StatusLabel: FC = ({ data-cy="status-label" elevation={0} variant="outlined" - ref={isDeactivated ? null : preview} + ref={ + isDeactivated ? null : (node) => drop(preview(node as HTMLElement)) + } sx={{ mx: 4, my: 1, @@ -134,9 +136,7 @@ export const StatusLabel: FC = ({ drag(drop(node as HTMLElement)) - } + ref={isDeactivated ? null : (node) => drag(node as HTMLElement)} sx={{ cursor: isDeactivated ? "default" : "grab", display: "flex", diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx index d92d83ae6b..4f50d7a7c6 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx @@ -60,7 +60,13 @@ const FormInputFieldWrapper: FC = ({ error, children, }) => ( - + {label} {required && ( @@ -86,6 +92,7 @@ const FormInputFieldWrapper: FC = ({ {!!error && ( } checkedIcon={} checked={selected} + value={option.value} sx={{ marginRight: 5, position: "absolute", @@ -505,7 +513,7 @@ const StatusLabelForm: FC = ({ Cancel Date: Fri, 3 Jan 2025 09:45:51 +0800 Subject: [PATCH 41/59] task: remove unused mocks --- .../VersionSelector/VersionItem.tsx | 1 - .../ItemEditHeader/VersionSelector/mocks.ts | 143 ------------------ 2 files changed, 144 deletions(-) delete mode 100644 src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/mocks.ts diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx index f1298979ea..a946c3a3de 100644 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx +++ b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/VersionItem.tsx @@ -39,7 +39,6 @@ import { User, WorkflowStatusLabel, } from "../../../../../../../../../shell/services/types"; -// import { WORKFLOW_LABELS as statusLabels } from "./mocks"; import { AppState } from "../../../../../../../../../shell/store/types"; import { useGetUsersRolesQuery } from "../../../../../../../../../shell/services/accounts"; import { NoResults } from "./NoResults"; diff --git a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/mocks.ts b/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/mocks.ts deleted file mode 100644 index 418ae0ee84..0000000000 --- a/src/apps/content-editor/src/app/views/ItemEdit/components/ItemEditHeader/VersionSelector/mocks.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { WorkflowStatusLabel } from "../../../../../../../../../shell/services/types"; -export const WORKFLOW_LABELS: WorkflowStatusLabel[] = [ - { - ZUID: "36-14b315-4pp20v3d", - name: "Approved", - description: "Approved", - color: "#12b76a", - allowPublish: false, - sort: 3, - addPermissionRoles: ["55-8094cbd789-42sw0c"], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-14b315-d24ft", - name: "Draft", - description: "Content item is only available to preview in stage", - color: "#0BA5EC", - allowPublish: false, - sort: 1, - addPermissionRoles: [], - removePermissionRoles: ["55-8094cbd789-42sw0c"], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:02Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-n33d5-23v13w", - name: "Needs Review", - description: "Ready for review", - color: "#ff5c08", - allowPublish: false, - sort: 2, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:08Z", - updatedAt: "2024-11-25T06:21:23Z", - }, - { - ZUID: "36-14b315-4pp20v3e", - name: "Yellow", - description: "Test label", - color: "#f79009", - allowPublish: false, - sort: 4, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-14b315-4pp20v3e", - name: "Deep Purple", - description: "Test label", - color: "#4e5ba6", - allowPublish: false, - sort: 4, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-14b315-4pp20v3e", - name: "Purple", - description: "Test label", - color: "#7a5af8", - allowPublish: false, - sort: 4, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-14b315-4pp20v3e", - name: "Pink", - description: "Test label", - color: "#ee46bc", - allowPublish: false, - sort: 4, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-14b315-4pp20v3e", - name: "Red", - description: "Test label", - color: "#f04438", - allowPublish: false, - sort: 4, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-14b315-4pp20v3e", - name: "Rose", - description: "Test label", - color: "#f63d68", - allowPublish: false, - sort: 4, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, - { - ZUID: "36-14b315-4pp20v3e", - name: "Grey", - description: "Test label", - color: "#667085", - allowPublish: false, - sort: 4, - addPermissionRoles: [], - removePermissionRoles: [], - createdByUserZUID: "55-8094cbd789-42sw0c", - updatedByUserZUID: "55-8094cbd789-42sw0c", - createdAt: "2024-11-19T17:18:15Z", - updatedAt: "2024-11-25T06:21:22Z", - }, -]; From a5e8e81e34fb574747d00d2c03f0eb746f1c0f43 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Fri, 3 Jan 2025 09:46:06 +0800 Subject: [PATCH 42/59] task: fix cache invalidation --- src/shell/services/instance.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/shell/services/instance.ts b/src/shell/services/instance.ts index 3bb30b7535..5d5696e28d 100644 --- a/src/shell/services/instance.ts +++ b/src/shell/services/instance.ts @@ -645,7 +645,9 @@ export const instanceApi = createApi({ label_zuids: payload.label_zuids, }, }), - invalidatesTags: ["ItemWorkflowStatus"], + invalidatesTags: (_, __, { itemZUID }) => [ + { type: "ItemWorkflowStatus", id: itemZUID }, + ], }), updateItemWorkflowStatus: builder.mutation< any, @@ -663,7 +665,9 @@ export const instanceApi = createApi({ labelZUIDs, }, }), - invalidatesTags: ["ItemWorkflowStatus"], + invalidatesTags: (_, __, { itemZUID }) => [ + { type: "ItemWorkflowStatus", id: itemZUID }, + ], }), getWorkflowStatusLabels: builder.query< WorkflowStatusLabel[], From 85de838a246a3fa434554ef27e559ea688322a36 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Fri, 3 Jan 2025 09:46:13 +0800 Subject: [PATCH 43/59] task: remove timeout --- cypress.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/cypress.config.js b/cypress.config.js index cd9cd2ee71..2a88f86fa1 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -5,7 +5,6 @@ module.exports = defineConfig({ viewportWidth: 1920, viewportHeight: 1080, video: false, - defaultCommandTimeout: 30000, env: { API_AUTH: "https://auth.api.dev.zesty.io", COOKIE_NAME: "DEV_APP_SID", From 5695e41aff3356f00a5df13d1599aaa519671bbf Mon Sep 17 00:00:00 2001 From: geodem Date: Fri, 3 Jan 2025 12:11:27 +0800 Subject: [PATCH 44/59] Relocated prop types | refactored user role identifier - changed to systemRoleZUID instead of role name --- cypress/e2e/settings/workflows.spec.js | 134 ++++++++++++------ .../views/User/Workflows/RestrictedPage.tsx | 6 +- .../forms-dialogs/DeactivationDialog.tsx | 2 +- .../forms-dialogs/StatusLabelForm.tsx | 23 ++- .../app/views/User/Workflows/constants.tsx | 29 ++++ .../src/app/views/User/Workflows/index.tsx | 16 ++- src/shell/services/types.ts | 53 ------- 7 files changed, 140 insertions(+), 123 deletions(-) create mode 100644 src/apps/settings/src/app/views/User/Workflows/constants.tsx diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 021d185157..ba2930087d 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -1,4 +1,13 @@ -import { colorMenu } from "../../../src/shell/services/types"; +import instanceZUID from "../../../src/utility/instanceZUID"; +import CONFIG from "../../../src/shell/app.config"; +import { + AUTHORIZED_ROLES, + colorMenu, +} from "../../../src/apps/settings/src/app/views/User/Workflows/constants"; + +const INSTANCE_API = `${ + CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL +}${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; const FOCUSED_LABEL_COLOR = "rgba(253, 133, 58, 0.1)"; @@ -101,7 +110,7 @@ describe("Workflow Status Labels: Restricted User", () => { .its("response.body") .then((body) => { const authorizedUsers = body?.data.filter((user) => - ["Owner", "Admin"].includes(user?.role?.name) + AUTHORIZED_ROLES.includes(user?.role?.systemRoleZUID) ); cy.get('[data-cy="user-profile-container"]') .children() @@ -112,12 +121,16 @@ describe("Workflow Status Labels: Restricted User", () => { }); describe("Workflow Status Labels: Authorized User", () => { + before(() => { + // DELETE TEST DATA IF EXISTS + cy.cleanTestData(); + }); + context("Workflow Page", () => { before(() => { - cy.visit("/settings/user/workflows"); + cy.waitUntilStatusLabelAreLoaded(); }); it("displays workflow page elements for authorized users", () => { - // cy.visit("/settings/user/workflows"); cy.contains("Workflows").should("exist"); cy.get("button").contains("Create Status").should("exist"); cy.get('input[placeholder="Search Statuses"]').should("exist"); @@ -135,7 +148,7 @@ describe("Workflow Status Labels: Authorized User", () => { context("Create New Status Label", () => { before(() => { - cy.visit("/settings/user/workflows"); + cy.waitUntilStatusLabelAreLoaded(); cy.get("button").contains("Create Status").click(); }); @@ -174,7 +187,6 @@ describe("Workflow Status Labels: Authorized User", () => { cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( (createStatusLabel, getAllStatusLabels) => { - // cy.wait("@createStatusLabel").then(({ response }) => { const responseData = createStatusLabel?.response?.body?.data; expect(createStatusLabel?.response?.statusCode).to.be.ok; expect(getAllStatusLabels?.response?.statusCode).to.be.ok; @@ -210,10 +222,13 @@ describe("Workflow Status Labels: Authorized User", () => { context("Edit Status Label", () => { before(() => { - cy.visit("/settings/user/workflows"); + cy.waitUntilStatusLabelAreLoaded(); }); it("Open Status Label and Edit Details", () => { + cy.intercept("PUT", `${ENDPOINTS?.statusLabels}/**`).as( + "editStatusLabel" + ); cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) @@ -249,9 +264,6 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get('[data-cy="status-label-submit-button"]').click(); - cy.intercept("PUT", `${ENDPOINTS?.statusLabels}/**`).as( - "editStatusLabel" - ); cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( @@ -280,7 +292,7 @@ describe("Workflow Status Labels: Authorized User", () => { context("Re-order Status Labels", () => { before(() => { - cy.visit("/settings/user/workflows"); + cy.waitUntilStatusLabelAreLoaded(); }); it("Drag status label to a new position", () => { @@ -316,29 +328,16 @@ describe("Workflow Status Labels: Authorized User", () => { updatedLabel.find((item) => item?.ZUID === label?.ZUID).sort ).to.eq(label.sort); }); - - console.log("reorderStatusLabel | getAllStatusLabels:", { - reorderStatusLabel, - getAllStatusLabels, - }); } ); }); }); context("Deactivate Status Label", () => { + before(() => { + cy.waitUntilStatusLabelAreLoaded(); + }); it("opens deactivation dialog and connfirms deactivation", () => { - cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( - "deactivateStatusLabel" - ); - - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - cy.visit("/settings/user/workflows"); - cy.wait("@getAllStatusLabels").then((interception) => { - cy.wrap(interception.response.body.data).as("oldStatusLabels"); - }); - cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) @@ -356,29 +355,72 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get("button").contains("Deactivate Status").click(); - cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( - (deactivateStatusLabel, newStatusLabels) => { - const targetLabelZUID = deactivateStatusLabel.response.body.data; + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" + ); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( + (deactivateStatusLabel, getAllStatusLabels) => { expect(deactivateStatusLabel.response.statusCode).to.eq(200); - expect(newStatusLabels.response.statusCode).to.eq(200); - - cy.get("@oldStatusLabels").then((oldStatusLabels) => { - const oldLabelCount = oldStatusLabels.filter( - (label) => !label?.deletedAt - ).length; - cy.get(".notistack-Snackbar") - .contains(`Status De-activated`) - .should("exist"); - cy.get(".notistack-Snackbar") - .contains(FORM_DATA?.edit?.name) - .should("exist"); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ).should("have.length", oldLabelCount - 1); - }); + expect(getAllStatusLabels.response.statusCode).to.eq(200); + + cy.get(".notistack-Snackbar") + .contains(`Status De-activated`) + .should("exist"); + cy.get(".notistack-Snackbar") + .contains(FORM_DATA?.edit?.name) + .should("exist"); } ); }); }); }); + +Cypress.Commands.add("cleanTestData", () => { + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.visit("/settings/user/workflows"); + cy.wait("@getAllStatusLabels") + .its("response.body.data") + .then((data) => { + const testData = data.filter( + (label) => + !label?.deletedAt && + [FORM_DATA?.create?.name, FORM_DATA?.edit?.name]?.includes( + label?.name + ) + ); + if (testData?.length > 0) { + cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { + const token = cookie?.value; + testData.forEach((label) => { + cy.request({ + url: `${INSTANCE_API}/env/labels/${label?.ZUID}`, + method: "DELETE", + credentials: "include", + headers: { + authorization: `Bearer ${token}`, + }, + }); + }); + }); + } + }); +}); + +Cypress.Commands.add("waitUntilStatusLabelAreLoaded", () => { + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.visit("/settings/user/workflows"); + cy.wait("@getAllStatusLabels", { timeout: 30000 }).then( + (xhr) => { + if (xhr) { + cy.log("Request for status labels was made."); + } + }, + () => { + cy.log( + "No request made for @getAllStatusLabels, proceeding to the next step." + ); + } + ); +}); diff --git a/src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx b/src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx index b78d85996c..b03b34233a 100644 --- a/src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx @@ -2,8 +2,8 @@ import { FC } from "react"; import { Box, Typography, Avatar } from "@mui/material"; import { useGetUsersRolesQuery } from "../../../../../../../shell/services/accounts"; import restrictedImage from "../../../../../../../../public/images/restricted-image.svg"; +import { AUTHORIZED_ROLES } from "./constants"; -const AUTHORIZED_ROLES: string[] = ["Admin", "Owner"]; const ROLE_ORDER_MAPPING = { Owner: 1, Admin: 2 }; type ProfileInfoProps = { @@ -44,7 +44,9 @@ const RestrictedPage = () => { const { isLoading, isError, data } = useGetUsersRolesQuery(); const profileList = data - ?.filter((profile) => AUTHORIZED_ROLES.includes(profile?.role?.name)) + ?.filter((profile) => + AUTHORIZED_ROLES.includes(profile?.role?.systemRoleZUID) + ) .map((item, index) => ({ id: item?.ZUID, name: `${item?.firstName} ${item?.lastName}`, diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx index bb2a8f74c7..fa3f7a2f0d 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from "react"; +import { FC } from "react"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Dialog from "@mui/material/Dialog"; diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx index 4f50d7a7c6..e3b2a64a86 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx @@ -30,12 +30,10 @@ import { useDispatch } from "react-redux"; import { notify } from "../../../../../../../../../shell/store/notifications"; import { useGetUsersRolesQuery } from "../../../../../../../../../shell/services/accounts"; import { - colorMenu, CreateStatusLabel, StatusLabel, - StatusLabelsColorMenu, - StatusLabelsRoleMenu, } from "../../../../../../../../../shell/services/types"; +import { ColorMenu, colorMenu, RoleMenu } from "../../constants"; interface FormInputFieldWrapperProps { label: string; @@ -129,8 +127,7 @@ const ColorSelectInput = ({ availableColors?.[0] || colorMenu?.[0]; - const [selectedColor, setSelectedColor] = - useState(defaultColor); + const [selectedColor, setSelectedColor] = useState(defaultColor); return ( <> @@ -192,7 +189,7 @@ const RolesSelectInput = ({ defaultValue = "", }: { name: string; - listData: StatusLabelsRoleMenu[]; + listData: RoleMenu[]; defaultValue?: string; }) => { const [value, setSelectedColor] = useState(defaultValue || ""); @@ -205,7 +202,7 @@ const RolesSelectInput = ({ zuids.split(",").includes(item.value.trim()) ) : []; - const handleChange = (_: unknown, newValue: StatusLabelsRoleMenu[]) => + const handleChange = (_: unknown, newValue: RoleMenu[]) => setSelectedColor(newValue.map((item) => item.value).join(",")); return ( @@ -276,9 +273,7 @@ const StatusLabelForm: FC = ({ isDeactivated = false, }) => { const ZUID = values?.ZUID || undefined; - const [rolesMenuItems, setRolesMenuItems] = useState( - [] - ); + const [rolesMenuItems, setRolesMenuItems] = useState([]); const { isLoading: rolesLoading, isFetching, @@ -287,9 +282,10 @@ const StatusLabelForm: FC = ({ const [formErrors, setFormErrors] = useState>({}); const dispatch = useDispatch(); - const [createWorkflowStatusLabel, { isLoading }] = + const [createWorkflowStatusLabel, { isLoading: createLabelIsLoading }] = useCreateWorkflowStatusLabelMutation(); - const [updateWorkflowStatusLabel] = useUpdateWorkflowStatusLabelMutation(); + const [updateWorkflowStatusLabel, { isLoading: editLabelIsLoading }] = + useUpdateWorkflowStatusLabelMutation(); const { openDeactivationDialog, setFocusedLabel } = useFormDialogContext(); const usedColors = labels.map((label) => label.color); @@ -299,7 +295,6 @@ const StatusLabelForm: FC = ({ const handleFormSubmit = async (e: FormEvent) => { e.preventDefault(); - // setIsLoading(true); const formData = Object.fromEntries(new FormData(e.currentTarget)); const newStatusLabel: CreateStatusLabel = { @@ -517,7 +512,7 @@ const StatusLabelForm: FC = ({ type="submit" variant="contained" color="primary" - loading={isLoading} + loading={createLabelIsLoading || editLabelIsLoading} startIcon={} > {ZUID ? "Save" : "Create Status"} diff --git a/src/apps/settings/src/app/views/User/Workflows/constants.tsx b/src/apps/settings/src/app/views/User/Workflows/constants.tsx new file mode 100644 index 0000000000..40b29fb925 --- /dev/null +++ b/src/apps/settings/src/app/views/User/Workflows/constants.tsx @@ -0,0 +1,29 @@ +export type ColorMenu = { + label: string; + value: string; +}; + +export type RoleMenu = { + label: string; + value: string; +}; + +export const colorMenu: ColorMenu[] = [ + { label: "Blue", value: "#0BA5EC" }, + { label: "Deep Purple", value: "#4E5BA6" }, + { label: "Green", value: "#12b76a" }, + { label: "Orange", value: "#FF5C08" }, + { label: "Pink", value: "#EE46BC" }, + { label: "Purple", value: "#7A5AF8" }, + { label: "Red", value: "#F04438" }, + { label: "Rose", value: "#F63D68" }, + { label: "Yellow", value: "#F79009" }, + { label: "Grey", value: "#667085" }, +]; + +const ADMIN_ZUID = "31-71cfc74-4dm13"; +const OWNER_ZUID = "31-71cfc74-0wn3r"; + +export const AUTHORIZED_ROLES: string[] = [ADMIN_ZUID, OWNER_ZUID]; + +export type AuthorizedRole = typeof AUTHORIZED_ROLES[number]; diff --git a/src/apps/settings/src/app/views/User/Workflows/index.tsx b/src/apps/settings/src/app/views/User/Workflows/index.tsx index e7636ab5ef..dbc7d79180 100644 --- a/src/apps/settings/src/app/views/User/Workflows/index.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/index.tsx @@ -5,20 +5,22 @@ import { useSelector } from "react-redux"; import { AppState } from "../../../../../../../shell/store/types"; import { AuthorizedUserPage } from "./authorized"; import FormDialogContextProvider from "./authorized/forms-dialogs"; -import { AUTHORIZED_ROLES } from "../../../../../../../shell/services/types"; +import { AUTHORIZED_ROLES } from "./constants"; type UserType = { - role: string; + systemRoleZUID: string; staff: boolean; }; const Workflows = () => { - const { role, staff }: UserType = useSelector((state: AppState) => ({ - role: state.userRole?.name, - staff: state.user?.staff, - })); + const { systemRoleZUID, staff }: UserType = useSelector( + (state: AppState) => ({ + systemRoleZUID: state?.userRole?.systemRoleZUID, + staff: state.user?.staff, + }) + ); - const isAuthorized = AUTHORIZED_ROLES.includes(role) || staff; + const isAuthorized = AUTHORIZED_ROLES.includes(systemRoleZUID) || staff; return ( diff --git a/src/shell/services/types.ts b/src/shell/services/types.ts index 4bd0d79b7f..5995070af2 100644 --- a/src/shell/services/types.ts +++ b/src/shell/services/types.ts @@ -623,42 +623,6 @@ export type WorkflowStatusLabelQueryParams = { showDeleted?: boolean; }; -export const COLOR_NAMES = { - BLUE: "Blue", - DEEP_PURPLE: "Deep Purple", - GREEN: "Green", - ORANGE: "Orange", - PINK: "Pink", - PURPLE: "Purple", - RED: "Red", - ROSE: "Rose", - YELLOW: "Yellow", - GREY: "Grey", -} as const; - -export const COLOR_HEX = { - BLUE: "#0BA5EC", - DEEP_PURPLE: "#4E5BA6", - GREEN: "#12b76a", - ORANGE: "#FF5C08", - PINK: "#EE46BC", - PURPLE: "#7A5AF8", - RED: "#F04438", - ROSE: "#F63D68", - YELLOW: "#F79009", - GREY: "#667085", -} as const; - -export const AUTHORIZED_ROLES: string[] = ["Admin", "Owner"]; - -export const ROLE_SORT_ORDER = { Owner: 0, Admin: 1 }; - -type ObjectConstants = Type[keyof Type]; - -export type ColorNameTypes = ObjectConstants; -export type ColorHexTypes = ObjectConstants; -export type AuthorizedRoles = typeof AUTHORIZED_ROLES[number]; - export type StatusLabelQuery = { ZUID: string; name: string; @@ -696,20 +660,3 @@ export type UpdateStatusLabel = Omit< export type CreateStatusLabel = Partial>; export type UpdateSortingOrder = Pick; - -export type StatusLabelsColorMenu = { - label: ColorNameTypes; - value: ColorHexTypes; -}; - -export type StatusLabelsRoleMenu = { - label: string; - value: string; -}; - -export const colorMenu: StatusLabelsColorMenu[] = Object.keys(COLOR_NAMES).map( - (key) => ({ - label: COLOR_NAMES[key as keyof typeof COLOR_NAMES], - value: COLOR_HEX[key as keyof typeof COLOR_HEX], - }) -); From 78bb88b7a1793526d267dafde554a67c5aa50763 Mon Sep 17 00:00:00 2001 From: geodem Date: Fri, 3 Jan 2025 23:49:22 +0800 Subject: [PATCH 45/59] Added test cases to cover important scenarios --- cypress/e2e/settings/workflows.spec.js | 382 ++++++++++++++++++------- 1 file changed, 281 insertions(+), 101 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index ba2930087d..9b1af992f6 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -66,8 +66,8 @@ const LABELS = { const FORM_DATA = { create: { - name: "Test Status Label", - description: "Test Status Label Description", + name: "Test Status Label - Create", + description: "Test Status Label - Create Description", color: "Grey", addPermissionRoles: "Admin", removePermissionRoles: "Admin", @@ -81,9 +81,20 @@ const FORM_DATA = { removePermissionRoles: "Admin", allowPublish: false, }, + delete: { + name: "Test Status Label - Deleted", + description: "Test Status Label - Deleted Description", + color: "Red", + addPermissionRoles: [], + removePermissionRoles: [], + allowPublish: false, + }, }; describe("Workflow Status Labels: Restricted User", () => { + before(() => { + cy.waitUntilStatusLabelsAreLoaded(); + }); it("displays restricted access message and admin profiles", () => { cy.intercept("GET", ENDPOINTS?.userRoles, { statusCode: 200, @@ -97,6 +108,7 @@ describe("Workflow Status Labels: Restricted User", () => { ); cy.visit("/settings/user/workflows"); + cy.wait("@getRestrictedUser") .its("response.body") .then((body) => { @@ -122,13 +134,13 @@ describe("Workflow Status Labels: Restricted User", () => { describe("Workflow Status Labels: Authorized User", () => { before(() => { - // DELETE TEST DATA IF EXISTS cy.cleanTestData(); + cy.createTestData(); }); context("Workflow Page", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); + cy.waitUntilStatusLabelsAreLoaded(); }); it("displays workflow page elements for authorized users", () => { cy.contains("Workflows").should("exist"); @@ -148,81 +160,62 @@ describe("Workflow Status Labels: Authorized User", () => { context("Create New Status Label", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); - - cy.get("button").contains("Create Status").click(); + cy.waitUntilStatusLabelsAreLoaded(); }); - it("Form Validation: should display error message when required fields are empty", () => { + it("Form Validation: should display error message when required fields are empty", function () { + cy.get("button").contains("Create Status").click(); const nameFieldErrorMessage = "Name is required"; cy.get('[data-cy="status-label-submit-button"]').click(); cy.contains(nameFieldErrorMessage).should("exist"); + cy.get("button").contains("Cancel").click(); }); - it("Fills out and submits form", function () { - cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - cy.get('input[name="name"]').type(FORM_DATA.create.name); - cy.get('textarea[name="description"]').type(FORM_DATA.create.description); - cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains(FORM_DATA.create.color).click(); - cy.get('input[name="addPermissionRoles"]') - .parent() - .find("button") - .click(); - cy.get('ul li[role="option"]') - .contains(FORM_DATA.create.addPermissionRoles) - .click(); - cy.get('form[role="dialog"]').click(); - cy.get('input[name="removePermissionRoles"]') - .parent() - .find("button") - .click(); - cy.get('ul li[role="option"]') - .contains(FORM_DATA.create.removePermissionRoles) - .click(); - cy.get('form[role="dialog"]').click(); - cy.get('input[name="allowPublish"]').check(); - cy.get('[data-cy="status-label-submit-button"]').click(); + it("Fills out and submits form", () => { + cy.createStatusLabel({ ...FORM_DATA.create }).then(function (data) { + const { + statusLabels: { active }, + createdStatusLabel, + } = data; + expect(createdStatusLabel.name).to.equal(FORM_DATA.create.name); + expect(createdStatusLabel.description).to.equal( + FORM_DATA.create.description + ); + expect(createdStatusLabel.color).to.equal( + colorMenu.find((color) => color?.label === FORM_DATA.create.color) + ?.value + ); + expect(createdStatusLabel.addPermissionRoles).to.have.lengthOf(1); + expect(createdStatusLabel.removePermissionRoles).to.have.lengthOf(1); + expect(createdStatusLabel.allowPublish).to.be.true; - cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( - (createStatusLabel, getAllStatusLabels) => { - const responseData = createStatusLabel?.response?.body?.data; - expect(createStatusLabel?.response?.statusCode).to.be.ok; - expect(getAllStatusLabels?.response?.statusCode).to.be.ok; - expect(responseData.name).to.equal(FORM_DATA.create.name); - expect(responseData.description).to.equal( - FORM_DATA.create.description - ); - expect(responseData.color).to.equal( - colorMenu.find((color) => color?.label === FORM_DATA.create.color) - ?.value - ); - expect(responseData.addPermissionRoles).to.have.lengthOf(1); - expect(responseData.removePermissionRoles).to.have.lengthOf(1); - expect(responseData.allowPublish).to.be.true; - } - ); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]' + ).should("have.length", active.length); + }); + + cy.wait(1000); }); - it("Shows the newly created label and focuses it", () => { + + it("Highlights the newly created status label.", () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .last() + .contains(FORM_DATA.create.name) + .parents('[data-cy="status-label"]') .should("have.css", "background-color", FOCUSED_LABEL_COLOR); }); it("Clicking outside the focused label restores it to its default state.", () => { + cy.get('[data-cy="active-labels-container"]').click(); cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .eq(1) - .click(); - cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .last() + .contains(FORM_DATA.create.name) + .parents('[data-cy="status-label"]') .should("not.have.css", "background-color", FOCUSED_LABEL_COLOR); }); }); context("Edit Status Label", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); + cy.waitUntilStatusLabelsAreLoaded(); }); it("Open Status Label and Edit Details", () => { @@ -232,7 +225,8 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) - .last() + .contains(FORM_DATA.create.name) + .parents('[data-cy="status-label"]') .find("button") .click(); cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); @@ -264,7 +258,7 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get('[data-cy="status-label-submit-button"]').click(); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( (editStatusLabel, getAllStatusLabels) => { @@ -292,7 +286,7 @@ describe("Workflow Status Labels: Authorized User", () => { context("Re-order Status Labels", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); + cy.waitUntilStatusLabelsAreLoaded(); }); it("Drag status label to a new position", () => { @@ -330,66 +324,139 @@ describe("Workflow Status Labels: Authorized User", () => { }); } ); + cy.wait(1000); }); }); context("Deactivate Status Label", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); + cy.waitUntilStatusLabelsAreLoaded().then((data) => { + cy.wrap(data).as("deactivateInitialStatusLabels"); + }); }); - it("opens deactivation dialog and connfirms deactivation", () => { + + it("Deactivate using menu options", function () { + cy.deactivateStatusLabel(FORM_DATA?.edit?.name).then(function (data) { + expect( + !data?.statusLabels?.active?.find( + (label) => label?.name === FORM_DATA?.edit?.name + ) + ).to.be.true; + expect( + !!data?.statusLabels?.deactivated?.find( + (label) => label?.name === FORM_DATA?.edit?.name + ) + ).to.be.true; + }); + + cy.get(".notistack-Snackbar") + .contains(`Status De-activated`) + .should("exist"); + cy.get(".notistack-Snackbar") + .contains(FORM_DATA?.edit?.name) + .should("exist"); + }); + + it("Deactivate using deactivation button in edit status label form", function () { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) - .last() + .contains(FORM_DATA.delete.name) + .parents('[data-cy="status-label"]') .find("button") .click(); - cy.get('ul li[role="menuitem"]').contains("Deactivate Status").click(); - cy.get('[data-cy="deactivation-dialog"]').should("exist"); - cy.get("h5").contains(`Deactivate Status`).should("exist"); - cy.get("h5").contains(FORM_DATA?.edit?.name).should("exist"); + cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); - cy.get("button").contains("Cancel").should("exist"); - cy.get("button").contains("Deactivate Status").should("exist"); + cy.get("form button").contains("Deactivate Status").click(); - cy.get("button").contains("Deactivate Status").click(); + cy.get('[data-cy="deactivation-dialog"] button') + .contains("Deactivate Status") + .click(); - cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( - "deactivateStatusLabel" - ); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.intercept(`${ENDPOINTS?.statusLabels}/**`).as("deactivateStatusLabel"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( (deactivateStatusLabel, getAllStatusLabels) => { expect(deactivateStatusLabel.response.statusCode).to.eq(200); expect(getAllStatusLabels.response.statusCode).to.eq(200); - - cy.get(".notistack-Snackbar") - .contains(`Status De-activated`) - .should("exist"); - cy.get(".notistack-Snackbar") - .contains(FORM_DATA?.edit?.name) - .should("exist"); } ); + + cy.get(".notistack-Snackbar") + .contains(`Status De-activated`) + .should("exist"); + cy.get(".notistack-Snackbar") + .contains(FORM_DATA?.delete?.name) + .should("exist"); + }); + }); + + context("Filter Active and Deactivated Status Labels", () => { + before(() => { + cy.waitUntilStatusLabelsAreLoaded().then((data) => { + cy.wrap(data).as("activeDeactivatedStatusLabels"); + }); + cy.get('input[value="deactivated"]').click(); + }); + + it("Displays active/deactivated status labels", function () { + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]' + ).should( + "have.length", + this.activeDeactivatedStatusLabels?.active?.length + ); + + cy.get( + '[data-cy="deactivated-labels-container"] [data-cy="status-label"]' + ).should( + "have.length", + this.activeDeactivatedStatusLabels?.deactivated?.length + ); + }); + + it("Displays Error page when search results returns empty", function () { + cy.wait(1500); + cy.get('input[placeholder="Search Statuses"]').clear().type("xxxxx"); + + cy.get('[data-cy="active-labels-container"]').should("not.exist"); + cy.get('[data-cy="deactivated-labels-container"]').should("not.exist"); + cy.get('[data-cy="no-results-page"]').should("exist"); + }); + + it("Clears and focuses search field when clicking 'Search Again'", function () { + cy.wait(1500); + cy.get("button").contains("Search Again").click(); + + cy.get('[data-cy="active-labels-container"]').should("exist"); + cy.get('[data-cy="deactivated-labels-container"]').should("exist"); + cy.get('[data-cy="no-results-page"]').should("not.exist"); + + cy.get('input[placeholder="Search Statuses"]') + .should("be.empty") + .and("have.focus"); }); }); }); Cypress.Commands.add("cleanTestData", () => { - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllLabels"); cy.visit("/settings/user/workflows"); - cy.wait("@getAllStatusLabels") + cy.wait("@getAllLabels") .its("response.body.data") .then((data) => { - const testData = data.filter( + const testData = data?.filter( (label) => !label?.deletedAt && - [FORM_DATA?.create?.name, FORM_DATA?.edit?.name]?.includes( - label?.name - ) + [ + FORM_DATA?.create?.name, + FORM_DATA?.edit?.name, + FORM_DATA?.delete?.name, + ]?.includes(label?.name) ); + if (testData?.length > 0) { cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { const token = cookie?.value; @@ -397,7 +464,6 @@ Cypress.Commands.add("cleanTestData", () => { cy.request({ url: `${INSTANCE_API}/env/labels/${label?.ZUID}`, method: "DELETE", - credentials: "include", headers: { authorization: `Bearer ${token}`, }, @@ -408,19 +474,133 @@ Cypress.Commands.add("cleanTestData", () => { }); }); -Cypress.Commands.add("waitUntilStatusLabelAreLoaded", () => { - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); +Cypress.Commands.add("createTestData", () => { + cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { + const token = cookie?.value; + cy.request({ + url: `${INSTANCE_API}/env/labels`, + method: "POST", + headers: { + authorization: `Bearer ${token}`, + }, + body: { + ...FORM_DATA?.delete, + color: colorMenu.find( + (color) => color?.label === FORM_DATA.delete.color + )?.value, + }, + }); + }); +}); + +Cypress.Commands.add("waitUntilStatusLabelsAreLoaded", () => { + cy.intercept("GET", ENDPOINTS.allStatusLabels, (req) => { + req.continue(); + }).as("getAllStatusLabels"); cy.visit("/settings/user/workflows"); - cy.wait("@getAllStatusLabels", { timeout: 30000 }).then( - (xhr) => { - if (xhr) { - cy.log("Request for status labels was made."); - } - }, - () => { - cy.log( - "No request made for @getAllStatusLabels, proceeding to the next step." - ); + return cy + .wait("@getAllStatusLabels", { timeout: 30000 }) + .then(({ response }) => { + return parseStatusLabels(response?.body?.data); + }); +}); + +Cypress.Commands.add( + "createStatusLabel", + ({ + name, + description, + color, + addPermissionRoles, + removePermissionRoles, + allowPublish, + }) => { + cy.get("button").contains("Create Status").click(); + + cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + + cy.get('input[name="name"]').type(name); + cy.get('textarea[name="description"]').type(description); + cy.get('input[name="color"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains(color).click(); + cy.get('input[name="addPermissionRoles"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains(addPermissionRoles).click(); + cy.get('form[role="dialog"]').click(); + cy.get('input[name="removePermissionRoles"]') + .parent() + .find("button") + .click(); + cy.get('ul li[role="option"]').contains(removePermissionRoles).click(); + cy.get('form[role="dialog"]').click(); + + if (allowPublish) { + cy.get('input[name="allowPublish"]').check(); + } else { + cy.get('input[name="allowPublish"]').uncheck(); } + + cy.get('[data-cy="status-label-submit-button"]').click(); + + return cy + .wait(["@createStatusLabel", "@getAllStatusLabels"]) + .spread((createStatusLabel, getAllStatusLabels) => { + return { + createdStatusLabel: createStatusLabel?.response?.body?.data, + statusLabels: parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ), + }; + }); + } +); + +Cypress.Commands.add("deactivateStatusLabel", (labelName) => { + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" ); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + + return cy + .get('[data-cy="active-labels-container"] [data-cy="status-label"]:visible') + .contains(labelName) + .parents('[data-cy="status-label"]') + .find("button") + .click() + .then(() => { + // Click the "Deactivate Status" option in the menu + cy.get('ul li[role="menuitem"]').contains("Deactivate Status").click(); + + // Confirm the deactivation action by clicking the "Deactivate Status" button + cy.get("button").contains("Deactivate Status").click(); + + // Wait for both the DELETE request (deactivating the status) and the GET request (reloading the status labels) + return cy + .wait(["@deactivateStatusLabel", "@getAllStatusLabels"]) + .spread((deactivateStatusLabel, getAllStatusLabels) => { + // Return both responses as an object + return { + deactivatedStatusLabel: deactivateStatusLabel?.response?.body?.data, + statusLabels: parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ), + }; + }); + }); }); + +function parseStatusLabels(statusLabels) { + const { active, deactivated } = statusLabels?.reduce( + (acc, curr) => { + if (!!curr.deletedAt) { + acc.deactivated.push(curr); + } else { + acc.active.push(curr); + } + return acc; + }, + { active: [], deactivated: [] } + ); + + return { active, deactivated }; +} From f4340757f239f042fe564459981ac83a76f95b32 Mon Sep 17 00:00:00 2001 From: geodem Date: Fri, 3 Jan 2025 23:49:22 +0800 Subject: [PATCH 46/59] Added test cases to cover important scenarios --- cypress/e2e/settings/workflows.spec.js | 389 ++++++++++++++++++------- 1 file changed, 284 insertions(+), 105 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index ba2930087d..cfb1607d6b 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -66,8 +66,8 @@ const LABELS = { const FORM_DATA = { create: { - name: "Test Status Label", - description: "Test Status Label Description", + name: "Test Status Label - Create", + description: "Test Status Label - Create Description", color: "Grey", addPermissionRoles: "Admin", removePermissionRoles: "Admin", @@ -81,9 +81,20 @@ const FORM_DATA = { removePermissionRoles: "Admin", allowPublish: false, }, + delete: { + name: "Test Status Label - Deleted", + description: "Test Status Label - Deleted Description", + color: "Red", + addPermissionRoles: [], + removePermissionRoles: [], + allowPublish: false, + }, }; describe("Workflow Status Labels: Restricted User", () => { + before(() => { + cy.waitUntilStatusLabelsAreLoaded(); + }); it("displays restricted access message and admin profiles", () => { cy.intercept("GET", ENDPOINTS?.userRoles, { statusCode: 200, @@ -97,6 +108,7 @@ describe("Workflow Status Labels: Restricted User", () => { ); cy.visit("/settings/user/workflows"); + cy.wait("@getRestrictedUser") .its("response.body") .then((body) => { @@ -122,13 +134,13 @@ describe("Workflow Status Labels: Restricted User", () => { describe("Workflow Status Labels: Authorized User", () => { before(() => { - // DELETE TEST DATA IF EXISTS cy.cleanTestData(); + cy.createTestData(); }); context("Workflow Page", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); + cy.waitUntilStatusLabelsAreLoaded(); }); it("displays workflow page elements for authorized users", () => { cy.contains("Workflows").should("exist"); @@ -148,81 +160,62 @@ describe("Workflow Status Labels: Authorized User", () => { context("Create New Status Label", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); - - cy.get("button").contains("Create Status").click(); + cy.waitUntilStatusLabelsAreLoaded(); }); - it("Form Validation: should display error message when required fields are empty", () => { + it("Form Validation: should display error message when required fields are empty", function () { + cy.get("button").contains("Create Status").click(); const nameFieldErrorMessage = "Name is required"; cy.get('[data-cy="status-label-submit-button"]').click(); cy.contains(nameFieldErrorMessage).should("exist"); + cy.get("button").contains("Cancel").click(); }); - it("Fills out and submits form", function () { - cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - cy.get('input[name="name"]').type(FORM_DATA.create.name); - cy.get('textarea[name="description"]').type(FORM_DATA.create.description); - cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains(FORM_DATA.create.color).click(); - cy.get('input[name="addPermissionRoles"]') - .parent() - .find("button") - .click(); - cy.get('ul li[role="option"]') - .contains(FORM_DATA.create.addPermissionRoles) - .click(); - cy.get('form[role="dialog"]').click(); - cy.get('input[name="removePermissionRoles"]') - .parent() - .find("button") - .click(); - cy.get('ul li[role="option"]') - .contains(FORM_DATA.create.removePermissionRoles) - .click(); - cy.get('form[role="dialog"]').click(); - cy.get('input[name="allowPublish"]').check(); - cy.get('[data-cy="status-label-submit-button"]').click(); + it("Fills out and submits form", () => { + cy.createStatusLabel({ ...FORM_DATA.create }).then(function (data) { + const { + statusLabels: { active }, + createdStatusLabel, + } = data; + expect(createdStatusLabel.name).to.equal(FORM_DATA.create.name); + expect(createdStatusLabel.description).to.equal( + FORM_DATA.create.description + ); + expect(createdStatusLabel.color).to.equal( + colorMenu.find((color) => color?.label === FORM_DATA.create.color) + ?.value + ); + expect(createdStatusLabel.addPermissionRoles).to.have.lengthOf(1); + expect(createdStatusLabel.removePermissionRoles).to.have.lengthOf(1); + expect(createdStatusLabel.allowPublish).to.be.true; - cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( - (createStatusLabel, getAllStatusLabels) => { - const responseData = createStatusLabel?.response?.body?.data; - expect(createStatusLabel?.response?.statusCode).to.be.ok; - expect(getAllStatusLabels?.response?.statusCode).to.be.ok; - expect(responseData.name).to.equal(FORM_DATA.create.name); - expect(responseData.description).to.equal( - FORM_DATA.create.description - ); - expect(responseData.color).to.equal( - colorMenu.find((color) => color?.label === FORM_DATA.create.color) - ?.value - ); - expect(responseData.addPermissionRoles).to.have.lengthOf(1); - expect(responseData.removePermissionRoles).to.have.lengthOf(1); - expect(responseData.allowPublish).to.be.true; - } - ); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]' + ).should("have.length", active.length); + }); + + cy.wait(1000); }); - it("Shows the newly created label and focuses it", () => { + + it("Highlights the newly created status label.", () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .last() + .contains(FORM_DATA.create.name) + .parents('[data-cy="status-label"]') .should("have.css", "background-color", FOCUSED_LABEL_COLOR); }); it("Clicking outside the focused label restores it to its default state.", () => { + cy.get('[data-cy="active-labels-container"]').click(); cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .eq(1) - .click(); - cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .last() + .contains(FORM_DATA.create.name) + .parents('[data-cy="status-label"]') .should("not.have.css", "background-color", FOCUSED_LABEL_COLOR); }); }); context("Edit Status Label", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); + cy.waitUntilStatusLabelsAreLoaded(); }); it("Open Status Label and Edit Details", () => { @@ -232,7 +225,8 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) - .last() + .contains(FORM_DATA.create.name) + .parents('[data-cy="status-label"]') .find("button") .click(); cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); @@ -264,7 +258,7 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get('[data-cy="status-label-submit-button"]').click(); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( (editStatusLabel, getAllStatusLabels) => { @@ -292,7 +286,7 @@ describe("Workflow Status Labels: Authorized User", () => { context("Re-order Status Labels", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); + cy.waitUntilStatusLabelsAreLoaded(); }); it("Drag status label to a new position", () => { @@ -330,66 +324,138 @@ describe("Workflow Status Labels: Authorized User", () => { }); } ); + cy.wait(1000); }); }); context("Deactivate Status Label", () => { before(() => { - cy.waitUntilStatusLabelAreLoaded(); + cy.waitUntilStatusLabelsAreLoaded().then((data) => { + cy.wrap(data).as("deactivateInitialStatusLabels"); + }); }); - it("opens deactivation dialog and connfirms deactivation", () => { + + it("Deactivate using menu options", function () { + cy.deactivateStatusLabel(FORM_DATA?.edit?.name).then(function (data) { + expect( + !data?.statusLabels?.active?.find( + (label) => label?.name === FORM_DATA?.edit?.name + ) + ).to.be.true; + expect( + !!data?.statusLabels?.deactivated?.find( + (label) => label?.name === FORM_DATA?.edit?.name + ) + ).to.be.true; + }); + + cy.get(".notistack-Snackbar") + .contains(`Status De-activated`) + .should("exist"); + cy.get(".notistack-Snackbar") + .contains(FORM_DATA?.edit?.name) + .should("exist"); + }); + + it("Deactivate using deactivation button in edit status label form", function () { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' ) - .last() + .contains(FORM_DATA.delete.name) + .parents('[data-cy="status-label"]') .find("button") .click(); - cy.get('ul li[role="menuitem"]').contains("Deactivate Status").click(); - cy.get('[data-cy="deactivation-dialog"]').should("exist"); - cy.get("h5").contains(`Deactivate Status`).should("exist"); - cy.get("h5").contains(FORM_DATA?.edit?.name).should("exist"); + cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); - cy.get("button").contains("Cancel").should("exist"); - cy.get("button").contains("Deactivate Status").should("exist"); + cy.get("form button").contains("Deactivate Status").click(); - cy.get("button").contains("Deactivate Status").click(); + cy.get('[data-cy="deactivation-dialog"] button') + .contains("Deactivate Status") + .click(); - cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( - "deactivateStatusLabel" - ); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + // cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + // "deactivateStatusLabel" + // ); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( - (deactivateStatusLabel, getAllStatusLabels) => { - expect(deactivateStatusLabel.response.statusCode).to.eq(200); - expect(getAllStatusLabels.response.statusCode).to.eq(200); + cy.wait("@getAllStatusLabels"); - cy.get(".notistack-Snackbar") - .contains(`Status De-activated`) - .should("exist"); - cy.get(".notistack-Snackbar") - .contains(FORM_DATA?.edit?.name) - .should("exist"); - } + cy.wait(1500); + + cy.get(".notistack-Snackbar") + .contains(`Status De-activated`) + .should("exist"); + cy.get(".notistack-Snackbar") + .contains(FORM_DATA?.delete?.name) + .should("exist"); + }); + }); + + context("Filter Active and Deactivated Status Labels", () => { + before(() => { + cy.waitUntilStatusLabelsAreLoaded().then((data) => { + cy.wrap(data).as("activeDeactivatedStatusLabels"); + }); + cy.get('input[value="deactivated"]').click(); + }); + + it("Displays active/deactivated status labels", function () { + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]' + ).should( + "have.length", + this.activeDeactivatedStatusLabels?.active?.length + ); + + cy.get( + '[data-cy="deactivated-labels-container"] [data-cy="status-label"]' + ).should( + "have.length", + this.activeDeactivatedStatusLabels?.deactivated?.length ); }); + + it("Displays Error page when search results returns empty", function () { + cy.wait(1500); + cy.get('input[placeholder="Search Statuses"]').clear().type("xxxxx"); + + cy.get('[data-cy="active-labels-container"]').should("not.exist"); + cy.get('[data-cy="deactivated-labels-container"]').should("not.exist"); + cy.get('[data-cy="no-results-page"]').should("exist"); + }); + + it("Clears and focuses search field when clicking 'Search Again'", function () { + cy.wait(1500); + cy.get("button").contains("Search Again").click(); + + cy.get('[data-cy="active-labels-container"]').should("exist"); + cy.get('[data-cy="deactivated-labels-container"]').should("exist"); + cy.get('[data-cy="no-results-page"]').should("not.exist"); + + cy.get('input[placeholder="Search Statuses"]') + .should("be.empty") + .and("have.focus"); + }); }); }); Cypress.Commands.add("cleanTestData", () => { - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllLabels"); cy.visit("/settings/user/workflows"); - cy.wait("@getAllStatusLabels") + cy.wait("@getAllLabels") .its("response.body.data") .then((data) => { - const testData = data.filter( + const testData = data?.filter( (label) => !label?.deletedAt && - [FORM_DATA?.create?.name, FORM_DATA?.edit?.name]?.includes( - label?.name - ) + [ + FORM_DATA?.create?.name, + FORM_DATA?.edit?.name, + FORM_DATA?.delete?.name, + ]?.includes(label?.name) ); + if (testData?.length > 0) { cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { const token = cookie?.value; @@ -397,7 +463,6 @@ Cypress.Commands.add("cleanTestData", () => { cy.request({ url: `${INSTANCE_API}/env/labels/${label?.ZUID}`, method: "DELETE", - credentials: "include", headers: { authorization: `Bearer ${token}`, }, @@ -408,19 +473,133 @@ Cypress.Commands.add("cleanTestData", () => { }); }); -Cypress.Commands.add("waitUntilStatusLabelAreLoaded", () => { - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); +Cypress.Commands.add("createTestData", () => { + cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { + const token = cookie?.value; + cy.request({ + url: `${INSTANCE_API}/env/labels`, + method: "POST", + headers: { + authorization: `Bearer ${token}`, + }, + body: { + ...FORM_DATA?.delete, + color: colorMenu.find( + (color) => color?.label === FORM_DATA.delete.color + )?.value, + }, + }); + }); +}); + +Cypress.Commands.add("waitUntilStatusLabelsAreLoaded", () => { + cy.intercept("GET", ENDPOINTS.allStatusLabels, (req) => { + req.continue(); + }).as("getAllStatusLabels"); cy.visit("/settings/user/workflows"); - cy.wait("@getAllStatusLabels", { timeout: 30000 }).then( - (xhr) => { - if (xhr) { - cy.log("Request for status labels was made."); - } - }, - () => { - cy.log( - "No request made for @getAllStatusLabels, proceeding to the next step." - ); + return cy + .wait("@getAllStatusLabels", { timeout: 30000 }) + .then(({ response }) => { + return parseStatusLabels(response?.body?.data); + }); +}); + +Cypress.Commands.add( + "createStatusLabel", + ({ + name, + description, + color, + addPermissionRoles, + removePermissionRoles, + allowPublish, + }) => { + cy.get("button").contains("Create Status").click(); + + cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + + cy.get('input[name="name"]').type(name); + cy.get('textarea[name="description"]').type(description); + cy.get('input[name="color"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains(color).click(); + cy.get('input[name="addPermissionRoles"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains(addPermissionRoles).click(); + cy.get('form[role="dialog"]').click(); + cy.get('input[name="removePermissionRoles"]') + .parent() + .find("button") + .click(); + cy.get('ul li[role="option"]').contains(removePermissionRoles).click(); + cy.get('form[role="dialog"]').click(); + + if (allowPublish) { + cy.get('input[name="allowPublish"]').check(); + } else { + cy.get('input[name="allowPublish"]').uncheck(); } + + cy.get('[data-cy="status-label-submit-button"]').click(); + + return cy + .wait(["@createStatusLabel", "@getAllStatusLabels"]) + .spread((createStatusLabel, getAllStatusLabels) => { + return { + createdStatusLabel: createStatusLabel?.response?.body?.data, + statusLabels: parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ), + }; + }); + } +); + +Cypress.Commands.add("deactivateStatusLabel", (labelName) => { + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" ); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + + return cy + .get('[data-cy="active-labels-container"] [data-cy="status-label"]:visible') + .contains(labelName) + .parents('[data-cy="status-label"]') + .find("button") + .click() + .then(() => { + // Click the "Deactivate Status" option in the menu + cy.get('ul li[role="menuitem"]').contains("Deactivate Status").click(); + + // Confirm the deactivation action by clicking the "Deactivate Status" button + cy.get("button").contains("Deactivate Status").click(); + + // Wait for both the DELETE request (deactivating the status) and the GET request (reloading the status labels) + return cy + .wait(["@deactivateStatusLabel", "@getAllStatusLabels"]) + .spread((deactivateStatusLabel, getAllStatusLabels) => { + // Return both responses as an object + return { + deactivatedStatusLabel: deactivateStatusLabel?.response?.body?.data, + statusLabels: parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ), + }; + }); + }); }); + +function parseStatusLabels(statusLabels) { + const { active, deactivated } = statusLabels?.reduce( + (acc, curr) => { + if (!!curr.deletedAt) { + acc.deactivated.push(curr); + } else { + acc.active.push(curr); + } + return acc; + }, + { active: [], deactivated: [] } + ); + + return { active, deactivated }; +} From 0cc2171ccea258e4bc1a24ababff637e8453a02b Mon Sep 17 00:00:00 2001 From: geodem Date: Tue, 7 Jan 2025 00:08:24 +0800 Subject: [PATCH 47/59] Refactored test cases to resolve flaky test issues --- cypress/e2e/settings/workflows.spec.js | 497 ++++++++---------- .../views/User/Workflows/RestrictedPage.tsx | 1 + .../views/User/Workflows/authorized/index.tsx | 1 + 3 files changed, 219 insertions(+), 280 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 39d736c229..5440880ec7 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -12,44 +12,25 @@ const INSTANCE_API = `${ const FOCUSED_LABEL_COLOR = "rgba(253, 133, 58, 0.1)"; const ENDPOINTS = { - instance: "**/v1/instances/**", userRoles: "**/v1/users/**/roles", - instanceRoles: "**/v1/instances/**/roles", - instanceUsers: `**/v1/instances/**/users`, instanceUserRoles: `**/v1/instances/**/users/roles`, allStatusLabels: "**/v1/env/labels?showDeleted=true", statusLabels: "**/v1/env/labels", }; -const USER_ROLES = { - restricted: { - data: [ - { - ZUID: "30-8ee88afe82-gmx631", - entityZUID: "8-f48cf3a682-7fthvk", +const RESTRICTED_USER = { + data: [ + { + ZUID: "30-8ee88afe82-gmx631", + entityZUID: "8-f48cf3a682-7fthvk", + name: "Developer", + systemRoleZUID: "31-71cfc74-d3v3l0p3r", + systemRole: { + ZUID: "31-71cfc74-d3v3l0p3r", name: "Developer", - systemRoleZUID: "31-71cfc74-d3v3l0p3r", - systemRole: { - ZUID: "31-71cfc74-d3v3l0p3r", - name: "Developer", - }, }, - ], - }, - authorized: { - data: [ - { - ZUID: "30-8ee88afe82-gmx631", - entityZUID: "8-f48cf3a682-7fthvk", - name: "Admin", - systemRoleZUID: "31-71cfc74-4dm13", - systemRole: { - ZUID: "31-71cfc74-4dm13", - name: "Admin", - }, - }, - ], - }, + }, + ], }; const LABELS = { @@ -64,6 +45,8 @@ const LABELS = { "These statuses can be re-activated at any time if you would like to add or remove them from content items", }; +const EMPTY_SEARCH_TEXT = "xx_yy_zz_00"; + const FORM_DATA = { create: { name: "Test Status Label - Create", @@ -74,16 +57,16 @@ const FORM_DATA = { allowPublish: true, }, edit: { - name: "Test Status Label - Edited", - description: "Test Status Label - Edited Description", + name: "Test Status Label - Edit", + description: "Test Status Label - Edit Description", color: "Rose", addPermissionRoles: "Admin", removePermissionRoles: "Admin", allowPublish: false, }, delete: { - name: "Test Status Label - Deleted", - description: "Test Status Label - Deleted Description", + name: "Test Status Label - Delete", + description: "Test Status Label - Delete Description", color: "Red", addPermissionRoles: [], removePermissionRoles: [], @@ -92,15 +75,10 @@ const FORM_DATA = { }; describe("Workflow Status Labels: Restricted User", () => { - before(() => { - cy.waitUntilStatusLabelsAreLoaded(); - }); it("displays restricted access message and admin profiles", () => { cy.intercept("GET", ENDPOINTS?.userRoles, { statusCode: 200, - body: { - ...USER_ROLES?.restricted, - }, + body: RESTRICTED_USER, }).as("getRestrictedUser"); cy.intercept("GET", ENDPOINTS?.instanceUserRoles).as( @@ -108,17 +86,9 @@ describe("Workflow Status Labels: Restricted User", () => { ); cy.visit("/settings/user/workflows"); + cy.get('[data-cy="workflows-restricted-page"]', { timeout: 60000 }); - cy.wait("@getRestrictedUser") - .its("response.body") - .then((body) => { - cy.wrap(body).as("userRoles"); - }); - cy.contains(LABELS?.restrictedPageHeader).should("exist"); - cy.contains(LABELS?.restrictedPageSubheader).should("exist"); - cy.get('[data-cy="restricted-image"]').should("exist"); - - cy.wait("@getInstanceUserRoles") + cy.get("@getInstanceUserRoles") .its("response.body") .then((body) => { const authorizedUsers = body?.data.filter((user) => @@ -129,6 +99,9 @@ describe("Workflow Status Labels: Restricted User", () => { .its("length") .should("eq", authorizedUsers.length); }); + cy.contains(LABELS?.restrictedPageHeader).should("exist"); + cy.contains(LABELS?.restrictedPageSubheader).should("exist"); + cy.get('[data-cy="restricted-image"]').should("exist"); }); }); @@ -140,7 +113,7 @@ describe("Workflow Status Labels: Authorized User", () => { context("Workflow Page", () => { before(() => { - cy.waitUntilStatusLabelsAreLoaded(); + cy.goToWorkflowsPage(); }); it("displays workflow page elements for authorized users", () => { cy.contains("Workflows").should("exist"); @@ -149,7 +122,7 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get('input[value="deactivated"]').should("exist"); }); - it("Show Deactivated Labels: Displays active and deactivated status labels", () => { + it("Show Deactivated Labels: Displays active and deactivated sections", () => { cy.get('input[value="deactivated"]').click(); cy.contains(LABELS.activeLabelsHeader).should("exist"); cy.contains(LABELS.activeLabelsHeader).should("exist"); @@ -158,9 +131,9 @@ describe("Workflow Status Labels: Authorized User", () => { }); }); - context("Create New Status Label", () => { + context("Create New Status Label", { retries: 1 }, () => { before(() => { - cy.waitUntilStatusLabelsAreLoaded(); + cy.goToWorkflowsPage(); }); it("Form Validation: should display error message when required fields are empty", function () { @@ -172,34 +145,66 @@ describe("Workflow Status Labels: Authorized User", () => { }); it("Fills out and submits form", () => { - cy.createStatusLabel({ ...FORM_DATA.create }).then(function (data) { - const { - statusLabels: { active }, - createdStatusLabel, - } = data; - expect(createdStatusLabel.name).to.equal(FORM_DATA.create.name); - expect(createdStatusLabel.description).to.equal( - FORM_DATA.create.description - ); - expect(createdStatusLabel.color).to.equal( - colorMenu.find((color) => color?.label === FORM_DATA.create.color) - ?.value - ); - expect(createdStatusLabel.addPermissionRoles).to.have.lengthOf(1); - expect(createdStatusLabel.removePermissionRoles).to.have.lengthOf(1); - expect(createdStatusLabel.allowPublish).to.be.true; + cy.get("button").contains("Create Status").click(); + cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.get('input[name="name"]').type(FORM_DATA.create.name); + cy.get('textarea[name="description"]').type(FORM_DATA.create.description); + cy.get('input[name="color"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains(FORM_DATA.create.color).click(); + cy.get('input[name="addPermissionRoles"]') + .parent() + .find("button") + .click(); + cy.get('ul li[role="option"]') + .contains(FORM_DATA.create.addPermissionRoles) + .click(); + cy.get('form[role="dialog"]').click(); + cy.get('input[name="removePermissionRoles"]') + .parent() + .find("button") + .click(); + cy.get('ul li[role="option"]') + .contains(FORM_DATA.create.removePermissionRoles) + .click(); + cy.get('form[role="dialog"]').click(); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]' - ).should("have.length", active.length); - }); + if (FORM_DATA.create.allowPublish) { + cy.get('input[name="allowPublish"]').check(); + } else { + cy.get('input[name="allowPublish"]').uncheck(); + } - cy.wait(1000); + cy.get('[data-cy="status-label-submit-button"]').click(); + + cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( + (createStatusLabel, getAllStatusLabels) => { + const createdStatusLabel = createStatusLabel?.response?.body?.data; + const { active } = parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ); + expect(createdStatusLabel.name).to.equal(FORM_DATA.create.name); + expect(createdStatusLabel.description).to.equal( + FORM_DATA.create.description + ); + expect(createdStatusLabel.color).to.equal( + colorMenu.find((color) => color?.label === FORM_DATA.create.color) + ?.value + ); + expect(createdStatusLabel.addPermissionRoles).to.have.lengthOf(1); + expect(createdStatusLabel.removePermissionRoles).to.have.lengthOf(1); + expect(createdStatusLabel.allowPublish).to.be.true; + + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]' + ).should("have.length", active.length); + } + ); }); it("Highlights the newly created status label.", () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(FORM_DATA.create.name) + .contains(FORM_DATA.create.name, { timeout: 60000 }) .parents('[data-cy="status-label"]') .should("have.css", "background-color", FOCUSED_LABEL_COLOR); }); @@ -214,28 +219,18 @@ describe("Workflow Status Labels: Authorized User", () => { }); context("Edit Status Label", () => { - before(() => { - cy.waitUntilStatusLabelsAreLoaded(); - }); - - it("Open Status Label and Edit Details", () => { - cy.intercept("PUT", `${ENDPOINTS?.statusLabels}/**`).as( - "editStatusLabel" - ); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ) - .contains(FORM_DATA.create.name) + it("Open Status Label and Edit Details", { retries: 1 }, () => { + cy.goToWorkflowsPage(); + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') + .contains(FORM_DATA.create.name, { timeout: 60000 }) .parents('[data-cy="status-label"]') .find("button") .click(); cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); - cy.get("button") .contains("Deactivate Status") .should("exist") .and("be.enabled"); - cy.get('input[name="name"]').clear().type(FORM_DATA.edit.name); cy.get('textarea[name="description"]') .clear() @@ -255,21 +250,19 @@ describe("Workflow Status Labels: Authorized User", () => { .click(); cy.get('form[role="dialog"]').click(); cy.get('input[name="allowPublish"]').uncheck(); - cy.get('[data-cy="status-label-submit-button"]').click(); - + cy.intercept("PUT", `${ENDPOINTS?.statusLabels}/**`).as( + "editStatusLabel" + ); cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( (editStatusLabel, getAllStatusLabels) => { const targetLabelZUID = editStatusLabel.response.body.data; const updatedLabel = getAllStatusLabels.response.body.data.find( (label) => label.ZUID === targetLabelZUID ); - expect(editStatusLabel.response.statusCode).to.eq(200); expect(getAllStatusLabels.response.statusCode).to.eq(200); - expect(updatedLabel).to.deep.include({ name: FORM_DATA.edit.name, color: colorMenu.find( @@ -286,10 +279,10 @@ describe("Workflow Status Labels: Authorized User", () => { context("Re-order Status Labels", () => { before(() => { - cy.waitUntilStatusLabelsAreLoaded(); + cy.goToWorkflowsPage(); }); - it("Drag status label to a new position", () => { + it("Drag status label to a new position", { retries: 1 }, () => { const dataTransfer = new DataTransfer(); cy.get(`[data-cy="status-label"]`) .find('[data-cy="status-label-drag-handle"]') @@ -297,7 +290,6 @@ describe("Workflow Status Labels: Authorized User", () => { .trigger("dragstart", { dataTransfer, }); - cy.get(`[data-cy="status-label"]`) .find('[data-cy="status-label-drag-handle"]') .eq(1) @@ -307,14 +299,12 @@ describe("Workflow Status Labels: Authorized User", () => { .trigger("drop", { dataTransfer, }); - cy.intercept("PUT", ENDPOINTS.statusLabels).as("reorderStatusLabel"); cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); cy.wait(["@reorderStatusLabel", "@getAllStatusLabels"]).spread( (reorderStatusLabel, getAllStatusLabels) => { expect(reorderStatusLabel.response.statusCode).to.eq(200); expect(getAllStatusLabels.response.statusCode).to.eq(200); - const reorderedLabels = reorderStatusLabel?.request?.body?.data; const updatedLabel = getAllStatusLabels.response.body.data; reorderedLabels.forEach((label, index) => { @@ -329,74 +319,92 @@ describe("Workflow Status Labels: Authorized User", () => { }); context("Deactivate Status Label", () => { - before(() => { - cy.waitUntilStatusLabelsAreLoaded().then((data) => { - cy.wrap(data).as("deactivateInitialStatusLabels"); - }); - }); - - it("Deactivate using menu options", function () { - cy.deactivateStatusLabel(FORM_DATA?.edit?.name).then(function (data) { - expect( - !data?.statusLabels?.active?.find( - (label) => label?.name === FORM_DATA?.edit?.name - ) - ).to.be.true; - expect( - !!data?.statusLabels?.deactivated?.find( - (label) => label?.name === FORM_DATA?.edit?.name - ) - ).to.be.true; - }); - - cy.get(".notistack-Snackbar") - .contains(`Status De-activated`) - .should("exist"); - cy.get(".notistack-Snackbar") - .contains(FORM_DATA?.edit?.name) - .should("exist"); + it("Deactivate using menu options", { retries: 1 }, function () { + cy.goToWorkflowsPage(); + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') + .contains(FORM_DATA?.edit?.name, { timeout: 60000 }) + .parents('[data-cy="status-label"]') + .find("button") + .click() + .then(() => { + cy.get('ul li[role="menuitem"]') + .contains("Deactivate Status") + .click(); + cy.get("button").contains("Deactivate Status").click(); + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" + ); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as( + "getAllStatusLabels" + ); + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( + (deactivateStatusLabel, getAllStatusLabels) => { + const deactivatedLabel = + deactivateStatusLabel?.response?.body?.data; + const { active, deactivated } = parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ); + expect( + active?.filter((label) => label.ZUID === deactivatedLabel) + ?.length + ).to.eq(0); + expect( + deactivated?.filter((label) => label.ZUID === deactivatedLabel) + .length + ).to.eq(1); + } + ); + }); + cy.get(".notistack-Snackbar", { timeout: 30000 }).contains( + new RegExp(`Status De-activated:\\s*${FORM_DATA?.edit?.name}`) + ); }); - it("Deactivate using deactivation button in edit status label form", function () { - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ) - .contains(FORM_DATA.delete.name) + it("Deactivate using form button", { retries: 1 }, function () { + cy.goToWorkflowsPage(); + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') + .contains(FORM_DATA.delete.name, { timeout: 60000 }) .parents('[data-cy="status-label"]') .find("button") .click(); - cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); - cy.get("form button").contains("Deactivate Status").click(); - cy.get('[data-cy="deactivation-dialog"] button') .contains("Deactivate Status") .click(); - - cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - cy.wait("@getAllStatusLabels"); - - cy.wait(1500); - - cy.get(".notistack-Snackbar") - .contains(`Status De-activated`) - .should("exist"); - cy.get(".notistack-Snackbar") - .contains(FORM_DATA?.delete?.name) - .should("exist"); + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" + ); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( + (deactivateStatusLabel, getAllStatusLabels) => { + const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; + const { active, deactivated } = parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ); + expect( + active?.filter((label) => label.ZUID === deactivatedLabel)?.length + ).to.eq(0); + expect( + deactivated?.filter((label) => label.ZUID === deactivatedLabel) + .length + ).to.eq(1); + } + ); + cy.get(".notistack-Snackbar", { timeout: 30000 }).contains( + new RegExp(`Status De-activated:\\s*${FORM_DATA?.delete?.name}`) + ); }); }); context("Filter Active and Deactivated Status Labels", () => { before(() => { - cy.waitUntilStatusLabelsAreLoaded().then((data) => { + cy.getStatusLabels().then((data) => { cy.wrap(data).as("activeDeactivatedStatusLabels"); }); + cy.goToWorkflowsPage(); cy.get('input[value="deactivated"]').click(); }); - it("Displays active/deactivated status labels", function () { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]' @@ -404,7 +412,6 @@ describe("Workflow Status Labels: Authorized User", () => { "have.length", this.activeDeactivatedStatusLabels?.active?.length ); - cy.get( '[data-cy="deactivated-labels-container"] [data-cy="status-label"]' ).should( @@ -414,22 +421,19 @@ describe("Workflow Status Labels: Authorized User", () => { }); it("Displays Error page when search results returns empty", function () { - cy.wait(1500); - cy.get('input[placeholder="Search Statuses"]').clear().type("xxxxx"); - + cy.get('input[placeholder="Search Statuses"]') + .clear() + .type(EMPTY_SEARCH_TEXT); cy.get('[data-cy="active-labels-container"]').should("not.exist"); cy.get('[data-cy="deactivated-labels-container"]').should("not.exist"); cy.get('[data-cy="no-results-page"]').should("exist"); }); it("Clears and focuses search field when clicking 'Search Again'", function () { - cy.wait(1500); cy.get("button").contains("Search Again").click(); - cy.get('[data-cy="active-labels-container"]').should("exist"); cy.get('[data-cy="deactivated-labels-container"]').should("exist"); cy.get('[data-cy="no-results-page"]').should("not.exist"); - cy.get('input[placeholder="Search Statuses"]') .should("be.empty") .and("have.focus"); @@ -437,148 +441,82 @@ describe("Workflow Status Labels: Authorized User", () => { }); }); -Cypress.Commands.add("cleanTestData", () => { - cy.intercept(ENDPOINTS.allStatusLabels).as("getAllLabels"); +Cypress.Commands.add("goToWorkflowsPage", () => { cy.visit("/settings/user/workflows"); - cy.wait("@getAllLabels") - .its("response.body.data") - .then((data) => { - const testData = data?.filter( - (label) => - !label?.deletedAt && - [ - FORM_DATA?.create?.name, - FORM_DATA?.edit?.name, - FORM_DATA?.delete?.name, - ]?.includes(label?.name) - ); + cy.get('[data-cy="workflows-authorized-page"]', { timeout: 60000 }); +}); - if (testData?.length > 0) { - cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { - const token = cookie?.value; - testData.forEach((label) => { - cy.request({ - url: `${INSTANCE_API}/env/labels/${label?.ZUID}`, - method: "DELETE", - headers: { - authorization: `Bearer ${token}`, - }, - }); - }); +Cypress.Commands.add("cleanTestData", () => { + cy.apiRequest({ + url: `${INSTANCE_API}/env/labels?showDeleted=true`, + }).then((response) => { + const testData = response?.data?.filter( + (label) => + !label?.deletedAt && + [ + FORM_DATA?.create?.name, + FORM_DATA?.edit?.name, + FORM_DATA?.delete?.name, + ]?.includes(label?.name) + ); + if (testData?.length > 0) { + testData.forEach((label) => { + cy.apiRequest({ + url: `${INSTANCE_API}/env/labels/${label?.ZUID}`, + method: "DELETE", }); - } - }); + }); + } + }); }); Cypress.Commands.add("createTestData", () => { - cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { - const token = cookie?.value; - cy.request({ - url: `${INSTANCE_API}/env/labels`, - method: "POST", - headers: { - authorization: `Bearer ${token}`, - }, - body: { - ...FORM_DATA?.delete, - color: colorMenu.find( - (color) => color?.label === FORM_DATA.delete.color - )?.value, - }, - }); + cy.apiRequest({ + url: `${INSTANCE_API}/env/labels`, + method: "POST", + body: { + ...FORM_DATA?.delete, + color: colorMenu.find((color) => color?.label === FORM_DATA.delete.color) + ?.value, + }, }); }); -Cypress.Commands.add("waitUntilStatusLabelsAreLoaded", () => { - cy.intercept("GET", ENDPOINTS.allStatusLabels, (req) => { - req.continue(); - }).as("getAllStatusLabels"); - cy.visit("/settings/user/workflows"); +Cypress.Commands.add("getStatusLabels", () => { return cy - .wait("@getAllStatusLabels", { timeout: 30000 }) - .then(({ response }) => { - return parseStatusLabels(response?.body?.data); + .apiRequest({ + url: `${INSTANCE_API}/env/labels?showDeleted=true`, + }) + .then((response) => { + return { + ...parseStatusLabels(response?.data), + }; }); }); Cypress.Commands.add( - "createStatusLabel", - ({ - name, - description, - color, - addPermissionRoles, - removePermissionRoles, - allowPublish, - }) => { - cy.get("button").contains("Create Status").click(); - - cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); - cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - cy.get('input[name="name"]').type(name); - cy.get('textarea[name="description"]').type(description); - cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains(color).click(); - cy.get('input[name="addPermissionRoles"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains(addPermissionRoles).click(); - cy.get('form[role="dialog"]').click(); - cy.get('input[name="removePermissionRoles"]') - .parent() - .find("button") - .click(); - cy.get('ul li[role="option"]').contains(removePermissionRoles).click(); - cy.get('form[role="dialog"]').click(); - - if (allowPublish) { - cy.get('input[name="allowPublish"]').check(); - } else { - cy.get('input[name="allowPublish"]').uncheck(); - } - - cy.get('[data-cy="status-label-submit-button"]').click(); - - return cy - .wait(["@createStatusLabel", "@getAllStatusLabels"]) - .spread((createStatusLabel, getAllStatusLabels) => { - return { - createdStatusLabel: createStatusLabel?.response?.body?.data, - statusLabels: parseStatusLabels( - getAllStatusLabels?.response?.body?.data - ), - }; - }); - } -); - -Cypress.Commands.add("deactivateStatusLabel", (labelName) => { - cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( - "deactivateStatusLabel" - ); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - return cy - .get('[data-cy="active-labels-container"] [data-cy="status-label"]:visible') - .contains(labelName) - .parents('[data-cy="status-label"]') - .find("button") - .click() - .then(() => { - cy.get('ul li[role="menuitem"]').contains("Deactivate Status").click(); - - cy.get("button").contains("Deactivate Status").click(); + "apiRequest", + ({ method = "GET", url = "", body = undefined }) => { + return cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { + const token = cookie?.value; return cy - .wait(["@deactivateStatusLabel", "@getAllStatusLabels"]) - .spread((deactivateStatusLabel, getAllStatusLabels) => { + .request({ + url: url, + method: method, + headers: { + authorization: `Bearer ${token}`, + }, + ...(body ? { body: body } : {}), + }) + .then((response) => { return { - deactivatedStatusLabel: deactivateStatusLabel?.response?.body?.data, - statusLabels: parseStatusLabels( - getAllStatusLabels?.response?.body?.data - ), + status: !!response?.isOkStatusCode ? "success" : "error", + data: response?.body?.data, }; }); }); -}); + } +); function parseStatusLabels(statusLabels) { const { active, deactivated } = statusLabels?.reduce( @@ -592,6 +530,5 @@ function parseStatusLabels(statusLabels) { }, { active: [], deactivated: [] } ); - return { active, deactivated }; } diff --git a/src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx b/src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx index b03b34233a..36dc57673b 100644 --- a/src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/RestrictedPage.tsx @@ -69,6 +69,7 @@ const RestrictedPage = () => { bgcolor="grey.50" display="flex" flexDirection="column" + data-cy="workflows-restricted-page" > { justifyContent="space-between" alignItems="baseline" flexGrow={0} + data-cy="workflows-authorized-page" > Workflows From 73d5f5712991b5f5a6b43bb5f0765ba234249f65 Mon Sep 17 00:00:00 2001 From: geodem Date: Tue, 7 Jan 2025 03:26:28 +0800 Subject: [PATCH 48/59] Refactored test cases to resolve flaky test issues --- cypress/e2e/settings/workflows.spec.js | 294 +++++++++--------- .../User/Workflows/authorized/StatusLabel.tsx | 6 +- .../forms-dialogs/DeactivationDialog.tsx | 1 + .../forms-dialogs/StatusLabelForm.tsx | 1 + 4 files changed, 150 insertions(+), 152 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 5440880ec7..81788799c4 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -49,24 +49,24 @@ const EMPTY_SEARCH_TEXT = "xx_yy_zz_00"; const FORM_DATA = { create: { - name: "Test Status Label - Create", - description: "Test Status Label - Create Description", + name: "Test__Create", + description: "Test__Create Description", color: "Grey", addPermissionRoles: "Admin", removePermissionRoles: "Admin", allowPublish: true, }, edit: { - name: "Test Status Label - Edit", - description: "Test Status Label - Edit Description", + name: "Test__Edit", + description: "Test__Edit Description", color: "Rose", addPermissionRoles: "Admin", removePermissionRoles: "Admin", allowPublish: false, }, delete: { - name: "Test Status Label - Delete", - description: "Test Status Label - Delete Description", + name: "Test__Delete", + description: "Test__Delete Description", color: "Red", addPermissionRoles: [], removePermissionRoles: [], @@ -80,7 +80,6 @@ describe("Workflow Status Labels: Restricted User", () => { statusCode: 200, body: RESTRICTED_USER, }).as("getRestrictedUser"); - cy.intercept("GET", ENDPOINTS?.instanceUserRoles).as( "getInstanceUserRoles" ); @@ -88,16 +87,16 @@ describe("Workflow Status Labels: Restricted User", () => { cy.visit("/settings/user/workflows"); cy.get('[data-cy="workflows-restricted-page"]', { timeout: 60000 }); - cy.get("@getInstanceUserRoles") - .its("response.body") - .then((body) => { - const authorizedUsers = body?.data.filter((user) => + cy.wait("@getInstanceUserRoles") + .its("response.body.data") + .then((users) => { + const authorizedUsers = users.filter((user) => AUTHORIZED_ROLES.includes(user?.role?.systemRoleZUID) ); - cy.get('[data-cy="user-profile-container"]') - .children() - .its("length") - .should("eq", authorizedUsers.length); + cy.get('[data-cy="user-profile-container"] > *').should( + "have.length", + authorizedUsers.length + ); }); cy.contains(LABELS?.restrictedPageHeader).should("exist"); cy.contains(LABELS?.restrictedPageSubheader).should("exist"); @@ -148,10 +147,12 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get("button").contains("Create Status").click(); cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.get('input[name="name"]').type(FORM_DATA.create.name); cy.get('textarea[name="description"]').type(FORM_DATA.create.description); cy.get('input[name="color"]').parent().find("button").click(); cy.get('ul li[role="option"]').contains(FORM_DATA.create.color).click(); + cy.get('input[name="addPermissionRoles"]') .parent() .find("button") @@ -160,6 +161,7 @@ describe("Workflow Status Labels: Authorized User", () => { .contains(FORM_DATA.create.addPermissionRoles) .click(); cy.get('form[role="dialog"]').click(); + cy.get('input[name="removePermissionRoles"]') .parent() .find("button") @@ -176,24 +178,23 @@ describe("Workflow Status Labels: Authorized User", () => { } cy.get('[data-cy="status-label-submit-button"]').click(); - cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( (createStatusLabel, getAllStatusLabels) => { const createdStatusLabel = createStatusLabel?.response?.body?.data; const { active } = parseStatusLabels( getAllStatusLabels?.response?.body?.data ); - expect(createdStatusLabel.name).to.equal(FORM_DATA.create.name); - expect(createdStatusLabel.description).to.equal( - FORM_DATA.create.description - ); - expect(createdStatusLabel.color).to.equal( - colorMenu.find((color) => color?.label === FORM_DATA.create.color) - ?.value - ); + + expect(createdStatusLabel).to.include({ + name: FORM_DATA.create.name, + description: FORM_DATA.create.description, + color: colorMenu.find( + (color) => color?.label === FORM_DATA.create.color + )?.value, + allowPublish: FORM_DATA.create.allowPublish, + }); expect(createdStatusLabel.addPermissionRoles).to.have.lengthOf(1); expect(createdStatusLabel.removePermissionRoles).to.have.lengthOf(1); - expect(createdStatusLabel.allowPublish).to.be.true; cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]' @@ -221,46 +222,43 @@ describe("Workflow Status Labels: Authorized User", () => { context("Edit Status Label", () => { it("Open Status Label and Edit Details", { retries: 1 }, () => { cy.goToWorkflowsPage(); - cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(FORM_DATA.create.name, { timeout: 60000 }) - .parents('[data-cy="status-label"]') - .find("button") - .click(); - cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); - cy.get("button") - .contains("Deactivate Status") - .should("exist") - .and("be.enabled"); + cy.moreActionEdit(FORM_DATA.create.name); + + cy.get("button").contains("Deactivate Status").should("be.enabled"); + cy.get('input[name="name"]').clear().type(FORM_DATA.edit.name); cy.get('textarea[name="description"]') .clear() .type(FORM_DATA.edit.description); cy.get('input[name="color"]').parent().find("button").click(); cy.get('ul li[role="option"]').contains(FORM_DATA.edit.color).click(); + cy.get("span.MuiChip-label") .contains(FORM_DATA.edit.addPermissionRoles) .parent() .find('svg[data-testid="CancelIcon"]') .click(); - cy.get('form[role="dialog"]').click(); cy.get("span.MuiChip-label") .contains(FORM_DATA.edit.removePermissionRoles) .parent() .find('svg[data-testid="CancelIcon"]') .click(); - cy.get('form[role="dialog"]').click(); cy.get('input[name="allowPublish"]').uncheck(); + cy.get('[data-cy="status-label-submit-button"]').click(); + cy.intercept("PUT", `${ENDPOINTS?.statusLabels}/**`).as( "editStatusLabel" ); cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( (editStatusLabel, getAllStatusLabels) => { const targetLabelZUID = editStatusLabel.response.body.data; const updatedLabel = getAllStatusLabels.response.body.data.find( (label) => label.ZUID === targetLabelZUID ); + expect(editStatusLabel.response.statusCode).to.eq(200); expect(getAllStatusLabels.response.statusCode).to.eq(200); expect(updatedLabel).to.deep.include({ @@ -284,114 +282,95 @@ describe("Workflow Status Labels: Authorized User", () => { it("Drag status label to a new position", { retries: 1 }, () => { const dataTransfer = new DataTransfer(); + cy.get(`[data-cy="status-label"]`) .find('[data-cy="status-label-drag-handle"]') .eq(0) - .trigger("dragstart", { - dataTransfer, - }); + .trigger("dragstart", { dataTransfer }); + cy.get(`[data-cy="status-label"]`) .find('[data-cy="status-label-drag-handle"]') .eq(1) - .trigger("dragover", { - dataTransfer, - }) - .trigger("drop", { - dataTransfer, - }); + .trigger("dragover", { dataTransfer }) + .trigger("drop", { dataTransfer }); + cy.intercept("PUT", ENDPOINTS.statusLabels).as("reorderStatusLabel"); cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.wait(["@reorderStatusLabel", "@getAllStatusLabels"]).spread( (reorderStatusLabel, getAllStatusLabels) => { + const reorderedLabels = reorderStatusLabel?.request?.body?.data; + const updatedLabel = getAllStatusLabels?.response?.body?.data; + expect(reorderStatusLabel.response.statusCode).to.eq(200); expect(getAllStatusLabels.response.statusCode).to.eq(200); - const reorderedLabels = reorderStatusLabel?.request?.body?.data; - const updatedLabel = getAllStatusLabels.response.body.data; - reorderedLabels.forEach((label, index) => { + + reorderedLabels.forEach((label) => { expect( - updatedLabel.find((item) => item?.ZUID === label?.ZUID).sort + updatedLabel.find((item) => item?.ZUID === label?.ZUID)?.sort ).to.eq(label.sort); }); } ); - cy.wait(1000); }); }); context("Deactivate Status Label", () => { it("Deactivate using menu options", { retries: 1 }, function () { cy.goToWorkflowsPage(); - cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(FORM_DATA?.edit?.name, { timeout: 60000 }) - .parents('[data-cy="status-label"]') - .find("button") - .click() - .then(() => { - cy.get('ul li[role="menuitem"]') - .contains("Deactivate Status") - .click(); - cy.get("button").contains("Deactivate Status").click(); - cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( - "deactivateStatusLabel" - ); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as( - "getAllStatusLabels" - ); - cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( - (deactivateStatusLabel, getAllStatusLabels) => { - const deactivatedLabel = - deactivateStatusLabel?.response?.body?.data; - const { active, deactivated } = parseStatusLabels( - getAllStatusLabels?.response?.body?.data - ); - expect( - active?.filter((label) => label.ZUID === deactivatedLabel) - ?.length - ).to.eq(0); - expect( - deactivated?.filter((label) => label.ZUID === deactivatedLabel) - .length - ).to.eq(1); - } + cy.moreActionDeactivate(FORM_DATA?.edit?.name); + cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); + + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" + ); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( + (deactivateStatusLabel, getAllStatusLabels) => { + const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; + const { active, deactivated } = parseStatusLabels( + getAllStatusLabels?.response?.body?.data ); - }); - cy.get(".notistack-Snackbar", { timeout: 30000 }).contains( + + expect(active?.some((label) => label.ZUID === deactivatedLabel)).to.be + .false; + expect(deactivated?.some((label) => label.ZUID === deactivatedLabel)) + .to.be.true; + } + ); + + cy.get(".notistack-Snackbar").contains( new RegExp(`Status De-activated:\\s*${FORM_DATA?.edit?.name}`) ); }); it("Deactivate using form button", { retries: 1 }, function () { cy.goToWorkflowsPage(); - cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(FORM_DATA.delete.name, { timeout: 60000 }) - .parents('[data-cy="status-label"]') - .find("button") - .click(); - cy.get('ul li[role="menuitem"]').contains("Edit Status").click(); - cy.get("form button").contains("Deactivate Status").click(); - cy.get('[data-cy="deactivation-dialog"] button') - .contains("Deactivate Status") - .click(); + cy.moreActionEdit(FORM_DATA.delete.name); + cy.get('[data-cy="form-deactivate-status-button"]').click(); + cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( "deactivateStatusLabel" ); cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( (deactivateStatusLabel, getAllStatusLabels) => { const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; const { active, deactivated } = parseStatusLabels( getAllStatusLabels?.response?.body?.data ); - expect( - active?.filter((label) => label.ZUID === deactivatedLabel)?.length - ).to.eq(0); - expect( - deactivated?.filter((label) => label.ZUID === deactivatedLabel) - .length - ).to.eq(1); + + expect(active?.some((label) => label.ZUID === deactivatedLabel)).to.be + .false; + expect(deactivated?.some((label) => label.ZUID === deactivatedLabel)) + .to.be.true; } ); - cy.get(".notistack-Snackbar", { timeout: 30000 }).contains( + + cy.get(".notistack-Snackbar").contains( new RegExp(`Status De-activated:\\s*${FORM_DATA?.delete?.name}`) ); }); @@ -399,40 +378,38 @@ describe("Workflow Status Labels: Authorized User", () => { context("Filter Active and Deactivated Status Labels", () => { before(() => { - cy.getStatusLabels().then((data) => { - cy.wrap(data).as("activeDeactivatedStatusLabels"); - }); + cy.getStatusLabels().then((data) => + cy.wrap(data).as("activeDeactivatedStatusLabels") + ); cy.goToWorkflowsPage(); cy.get('input[value="deactivated"]').click(); }); + it("Displays active/deactivated status labels", function () { + const { active, deactivated } = this.activeDeactivatedStatusLabels || {}; cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]' - ).should( - "have.length", - this.activeDeactivatedStatusLabels?.active?.length - ); + ).should("have.length", active?.length); cy.get( '[data-cy="deactivated-labels-container"] [data-cy="status-label"]' - ).should( - "have.length", - this.activeDeactivatedStatusLabels?.deactivated?.length - ); + ).should("have.length", deactivated?.length); }); - it("Displays Error page when search results returns empty", function () { + it("Displays Error page when search results returns empty", () => { cy.get('input[placeholder="Search Statuses"]') .clear() .type(EMPTY_SEARCH_TEXT); - cy.get('[data-cy="active-labels-container"]').should("not.exist"); - cy.get('[data-cy="deactivated-labels-container"]').should("not.exist"); + cy.get( + '[data-cy="active-labels-container"], [data-cy="deactivated-labels-container"]' + ).should("not.exist"); cy.get('[data-cy="no-results-page"]').should("exist"); }); - it("Clears and focuses search field when clicking 'Search Again'", function () { + it("Clears and focuses search field when clicking 'Search Again'", () => { cy.get("button").contains("Search Again").click(); - cy.get('[data-cy="active-labels-container"]').should("exist"); - cy.get('[data-cy="deactivated-labels-container"]').should("exist"); + cy.get( + '[data-cy="active-labels-container"], [data-cy="deactivated-labels-container"]' + ).should("exist"); cy.get('[data-cy="no-results-page"]').should("not.exist"); cy.get('input[placeholder="Search Statuses"]') .should("be.empty") @@ -447,27 +424,48 @@ Cypress.Commands.add("goToWorkflowsPage", () => { }); Cypress.Commands.add("cleanTestData", () => { - cy.apiRequest({ - url: `${INSTANCE_API}/env/labels?showDeleted=true`, - }).then((response) => { - const testData = response?.data?.filter( - (label) => - !label?.deletedAt && - [ - FORM_DATA?.create?.name, - FORM_DATA?.edit?.name, - FORM_DATA?.delete?.name, - ]?.includes(label?.name) - ); - if (testData?.length > 0) { - testData.forEach((label) => { - cy.apiRequest({ - url: `${INSTANCE_API}/env/labels/${label?.ZUID}`, - method: "DELETE", + const labelsToDelete = [ + FORM_DATA?.create?.name, + FORM_DATA?.edit?.name, + FORM_DATA?.delete?.name, + ]; + + cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( + (response) => { + response?.data + ?.filter( + (label) => !label?.deletedAt && labelsToDelete.includes(label?.name) + ) + .forEach((label) => { + cy.apiRequest({ + url: `${INSTANCE_API}/env/labels/${label.ZUID}`, + method: "DELETE", + }); }); - }); } - }); + ); +}); + +Cypress.Commands.add("moreActionEdit", (label) => { + cy.get('[data-cy="active-labels-container"]') + .find('[data-cy="status-label-name"]') + .contains(label, { timeout: 60000 }) + .scrollIntoView() + .parents('[data-cy="status-label"]') + .find('[data-cy="status-label-more-actions"]') + .click(); + cy.get('[data-cy="menu-item-edit"]').click(); +}); + +Cypress.Commands.add("moreActionDeactivate", (label) => { + cy.get('[data-cy="active-labels-container"]') + .find('[data-cy="status-label-name"]') + .contains(label, { timeout: 60000 }) + .scrollIntoView() + .parents('[data-cy="status-label"]') + .find('[data-cy="status-label-more-actions"]') + .click(); + cy.get('[data-cy="menu-item-deactivate"]').click(); }); Cypress.Commands.add("createTestData", () => { @@ -501,19 +499,15 @@ Cypress.Commands.add( const token = cookie?.value; return cy .request({ - url: url, - method: method, - headers: { - authorization: `Bearer ${token}`, - }, + url, + method, + headers: { authorization: `Bearer ${token}` }, ...(body ? { body: body } : {}), }) - .then((response) => { - return { - status: !!response?.isOkStatusCode ? "success" : "error", - data: response?.body?.data, - }; - }); + .then((response) => ({ + status: response?.isOkStatusCode ? "success" : "error", + data: response?.body?.data, + })); }); } ); diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx index 75e470d18c..f7de5ee324 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/StatusLabel.tsx @@ -164,6 +164,7 @@ export const StatusLabel: FC = ({ color="text.primary" fontWeight={700} lineHeight="22px" + data-cy="status-label-name" > {data?.name} @@ -225,6 +226,7 @@ const MoreActionsMenu = ({ size="small" onClick={handleOpen} sx={{ borderRadius: "50%" }} + data-cy="status-label-more-actions" > @@ -236,7 +238,7 @@ const MoreActionsMenu = ({ anchorOrigin={{ vertical: "bottom", horizontal: "right" }} transformOrigin={{ vertical: "top", horizontal: "right" }} > - + @@ -246,7 +248,7 @@ const MoreActionsMenu = ({ {!isDeactivated && ( - + diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx index fa3f7a2f0d..9a9da157f3 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/DeactivationDialog.tsx @@ -109,6 +109,7 @@ const DeactivationDialog: FC = ({ color="error" loading={isLoading} loadingPosition="center" + data-cy="deactivation-dialog-confirm-button" > Deactivate Status diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx index e3b2a64a86..796ed55da6 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx @@ -496,6 +496,7 @@ const StatusLabelForm: FC = ({ color="inherit" onClick={handleDeactivation} startIcon={} + data-cy="form-deactivate-status-button" > Deactivate Status From b73228072fac470938c90ba6b3943a160b8e8f43 Mon Sep 17 00:00:00 2001 From: Nar Cuenca Date: Thu, 9 Jan 2025 09:18:56 +0800 Subject: [PATCH 49/59] fix: fixed broken tests --- cypress.config.js | 1 + cypress/e2e/content/workflows.spec.js | 127 +++++++++++++-------- cypress/e2e/settings/pages/SettingsPage.js | 43 ------- cypress/e2e/settings/workflows.spec.js | 20 ---- cypress/support/commands.js | 20 ++++ 5 files changed, 98 insertions(+), 113 deletions(-) delete mode 100644 cypress/e2e/settings/pages/SettingsPage.js diff --git a/cypress.config.js b/cypress.config.js index 2a88f86fa1..169a8f3dd4 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -5,6 +5,7 @@ module.exports = defineConfig({ viewportWidth: 1920, viewportHeight: 1080, video: false, + defaultCommandTimeout: 15000, env: { API_AUTH: "https://auth.api.dev.zesty.io", COOKIE_NAME: "DEV_APP_SID", diff --git a/cypress/e2e/content/workflows.spec.js b/cypress/e2e/content/workflows.spec.js index 5108522e39..2775672194 100644 --- a/cypress/e2e/content/workflows.spec.js +++ b/cypress/e2e/content/workflows.spec.js @@ -1,11 +1,33 @@ import ContentItemPage from "./pages/ContentItemPage"; -import SettingsPage from "../settings/pages/SettingsPage"; +import CONFIG from "../../../src/shell/app.config"; +import instanceZUID from "../../../src/utility/instanceZUID"; +const INSTANCE_API = `${ + CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL +}${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; const TITLES = { contentItem: "Content item workflow test", publishLabel: "Publish Approval", testLabel: "Random Test Label", }; +const LABEL_DATA = { + publishLabel: { + name: TITLES.publishLabel, + description: "", + color: "#4E5BA6", + allowPublish: true, + addPermissionRoles: ["30-86f8ccec82-swp72s", "30-8ee88afe82-gmx631"], + removePermissionRoles: ["30-86f8ccec82-swp72s", "30-8ee88afe82-gmx631"], + }, + testLabel: { + name: TITLES.testLabel, + description: "", + color: "#4E5BA6", + allowPublish: false, + addPermissionRoles: ["30-86f8ccec82-swp72s", "30-8ee88afe82-gmx631"], + removePermissionRoles: ["30-86f8ccec82-swp72s", "30-8ee88afe82-gmx631"], + }, +}; describe("Content Item Workflows", () => { before(() => { @@ -13,63 +35,68 @@ describe("Content Item Workflows", () => { cy.intercept("GET", "**/labels*").as("getLabels"); // Create allow publish workflow label - SettingsPage.createWorkflowLabel({ name: TITLES.testLabel }); - cy.wait(["@createLabel", "@getLabels"]); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ) - .contains(TITLES.testLabel) - .should("exist"); - - SettingsPage.createWorkflowLabel({ - name: TITLES.publishLabel, - allowPublish: true, + Object.values(LABEL_DATA).forEach((data) => { + cy.apiRequest({ + method: "POST", + url: `${INSTANCE_API}/env/labels`, + body: data, + }); }); - cy.wait(["@createLabel", "@getLabels"]); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ) - .contains(TITLES.publishLabel) - .should("exist"); // Visit test page - cy.visit("/content/6-b6cde1aa9f-wftv50/new"); - cy.get("#12-a6d48ca2b7-zxqns2") - .should("exist") - .find("input") - .type(TITLES.contentItem); - cy.get("#12-d29ab9bbe0-9k6j70") - .should("exist") - .find("textarea") - .first() - .type(TITLES.contentItem); - ContentItemPage.elements.createItemButton().should("exist").click(); - ContentItemPage.elements.duoModeToggle().should("exist").click(); + cy.apiRequest({ + method: "POST", + url: `${INSTANCE_API}/content/models/6-b6cde1aa9f-wftv50/items`, + body: { + data: { + title: TITLES.contentItem, + description: TITLES.contentItem, + tc_title: TITLES.contentItem, + tc_description: TITLES.contentItem, + tc_image: null, + }, + web: { + canonicalTagMode: 1, + parentZUID: "0", + metaLinkText: TITLES.contentItem, + metaTitle: TITLES.contentItem, + pathPart: TITLES.contentItem?.replaceAll(" ", "-")?.toLowerCase(), + metaDescription: TITLES.contentItem, + }, + meta: { langID: 1, contentModelZUID: "6-b6cde1aa9f-wftv50" }, + }, + }).then((response) => { + cy.visit(`/content/6-b6cde1aa9f-wftv50/${response.data?.ZUID}`); + }); }); after(() => { // Delete test content item - ContentItemPage.elements.moreMenu().should("exist").click(); - ContentItemPage.elements.deleteItemButton().should("exist").click(); - ContentItemPage.elements.confirmDeleteItemButton().should("exist").click(); - cy.intercept("DELETE", "**/content/models/6-b6cde1aa9f-wftv50/items/*").as( - "deleteContentItem" + cy.location("pathname").then((loc) => { + const [_, __, modelZUID, itemZUID] = loc?.split("/"); + cy.apiRequest({ + method: "DELETE", + url: `${INSTANCE_API}/content/models/${modelZUID}/items/${itemZUID}`, + }); + }); + + // Delete test labels + cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( + (response) => { + response?.data + ?.filter( + (label) => + !label?.deletedAt && + [TITLES.publishLabel, TITLES.testLabel].includes(label?.name) + ) + .forEach((label) => { + cy.apiRequest({ + url: `${INSTANCE_API}/env/labels/${label.ZUID}`, + method: "DELETE", + }); + }); + } ); - cy.wait("@deleteContentItem"); - - // Delete allow publish label after test - SettingsPage.deactivateWorkflowLabel(TITLES.testLabel); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ) - .contains("Random Test Label") - .should("not.exist"); - SettingsPage.deactivateWorkflowLabel(TITLES.publishLabel); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - ) - .contains("Publish Approval") - .should("not.exist"); }); it("Can add a workflow label", () => { diff --git a/cypress/e2e/settings/pages/SettingsPage.js b/cypress/e2e/settings/pages/SettingsPage.js deleted file mode 100644 index fc22ac6327..0000000000 --- a/cypress/e2e/settings/pages/SettingsPage.js +++ /dev/null @@ -1,43 +0,0 @@ -class SettingsPage { - createWorkflowLabel({ name, allowPublish = false }) { - cy.visit("/settings/user/workflows"); - - cy.get("button").contains("Create Status").click(); - cy.get('input[name="name"]').type(name); - cy.get('textarea[name="description"]').type(`${name} Description`); - cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains("Yellow").click(); - cy.get('input[name="addPermissionRoles"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains("Admin").click(); - cy.get('ul li[role="option"]').contains("Developer").click(); - cy.get('form[role="dialog"]').click(); - cy.get('input[name="removePermissionRoles"]') - .parent() - .find("button") - .click(); - cy.get('ul li[role="option"]').contains("Admin").click(); - cy.get('ul li[role="option"]').contains("Developer").click(); - cy.get('form[role="dialog"]').click(); - if (allowPublish) { - cy.get('input[name="allowPublish"]').click(); - } - cy.get('[data-cy="status-label-submit-button"]').click(); - } - - deactivateWorkflowLabel(name) { - cy.intercept("DELETE", "**/labels/*").as("deleteLabel"); - - cy.visit("/settings/user/workflows"); - - cy.contains(name) - .closest('[data-cy="status-label"]:visible') - .find("button") - .click(); - cy.get('ul li[role="menuitem"]').contains("Deactivate Status").click(); - cy.get("button").contains("Deactivate Status").click(); - - cy.wait("@deleteLabel"); - } -} - -export default new SettingsPage(); diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 81788799c4..54ca413c5e 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -492,26 +492,6 @@ Cypress.Commands.add("getStatusLabels", () => { }); }); -Cypress.Commands.add( - "apiRequest", - ({ method = "GET", url = "", body = undefined }) => { - return cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { - const token = cookie?.value; - return cy - .request({ - url, - method, - headers: { authorization: `Bearer ${token}` }, - ...(body ? { body: body } : {}), - }) - .then((response) => ({ - status: response?.isOkStatusCode ? "success" : "error", - data: response?.body?.data, - })); - }); - } -); - function parseStatusLabels(statusLabels) { const { active, deactivated } = statusLabels?.reduce( (acc, curr) => { diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 3ecb8d9bef..917707b7c4 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -51,3 +51,23 @@ Cypress.Commands.add("blockAnnouncements", () => { req.reply({}); }); }); + +Cypress.Commands.add( + "apiRequest", + ({ method = "GET", url = "", body = undefined }) => { + return cy.getCookie(Cypress.env("COOKIE_NAME")).then((cookie) => { + const token = cookie?.value; + return cy + .request({ + url, + method, + headers: { authorization: `Bearer ${token}` }, + ...(body ? { body: body } : {}), + }) + .then((response) => ({ + status: response?.isOkStatusCode ? "success" : "error", + data: response?.body?.data, + })); + }); + } +); From 45592564313e919936bf0d608a52515f7c799b05 Mon Sep 17 00:00:00 2001 From: geodem Date: Thu, 9 Jan 2025 10:49:20 +0800 Subject: [PATCH 50/59] Fixed workflow status labels test cases --- cypress/e2e/settings/workflows.spec.js | 75 +++++++++++++------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 54ca413c5e..f165222789 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -5,6 +5,8 @@ import { colorMenu, } from "../../../src/apps/settings/src/app/views/User/Workflows/constants"; +const TIMEOUT = { timeout: 60_000 }; + const INSTANCE_API = `${ CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL }${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; @@ -85,7 +87,7 @@ describe("Workflow Status Labels: Restricted User", () => { ); cy.visit("/settings/user/workflows"); - cy.get('[data-cy="workflows-restricted-page"]', { timeout: 60000 }); + cy.get('[data-cy="workflows-restricted-page"]', TIMEOUT); cy.wait("@getInstanceUserRoles") .its("response.body.data") @@ -178,6 +180,7 @@ describe("Workflow Status Labels: Authorized User", () => { } cy.get('[data-cy="status-label-submit-button"]').click(); + cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( (createStatusLabel, getAllStatusLabels) => { const createdStatusLabel = createStatusLabel?.response?.body?.data; @@ -201,11 +204,12 @@ describe("Workflow Status Labels: Authorized User", () => { ).should("have.length", active.length); } ); + cy.wait(1500); }); it("Highlights the newly created status label.", () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(FORM_DATA.create.name, { timeout: 60000 }) + .contains(FORM_DATA.create.name, TIMEOUT) .parents('[data-cy="status-label"]') .should("have.css", "background-color", FOCUSED_LABEL_COLOR); }); @@ -222,7 +226,15 @@ describe("Workflow Status Labels: Authorized User", () => { context("Edit Status Label", () => { it("Open Status Label and Edit Details", { retries: 1 }, () => { cy.goToWorkflowsPage(); - cy.moreActionEdit(FORM_DATA.create.name); + cy.wait(1500); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]', + TIMEOUT + ) + .last() + .find('[data-cy="status-label-more-actions"]') + .click(); + cy.get('[data-cy="menu-item-edit"]').click(); cy.get("button").contains("Deactivate Status").should("be.enabled"); @@ -318,7 +330,14 @@ describe("Workflow Status Labels: Authorized User", () => { context("Deactivate Status Label", () => { it("Deactivate using menu options", { retries: 1 }, function () { cy.goToWorkflowsPage(); - cy.moreActionDeactivate(FORM_DATA?.edit?.name); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]', + TIMEOUT + ) + .last() + .find('[data-cy="status-label-more-actions"]') + .click(); + cy.get('[data-cy="menu-item-deactivate"]').click(); cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( @@ -347,7 +366,14 @@ describe("Workflow Status Labels: Authorized User", () => { it("Deactivate using form button", { retries: 1 }, function () { cy.goToWorkflowsPage(); - cy.moreActionEdit(FORM_DATA.delete.name); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]', + TIMEOUT + ) + .last() + .find('[data-cy="status-label-more-actions"]') + .click(); + cy.get('[data-cy="menu-item-edit"]').click(); cy.get('[data-cy="form-deactivate-status-button"]').click(); cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); @@ -411,30 +437,29 @@ describe("Workflow Status Labels: Authorized User", () => { '[data-cy="active-labels-container"], [data-cy="deactivated-labels-container"]' ).should("exist"); cy.get('[data-cy="no-results-page"]').should("not.exist"); - cy.get('input[placeholder="Search Statuses"]') - .should("be.empty") - .and("have.focus"); + cy.get('input[placeholder="Search Statuses"]').should("be.empty"); }); }); }); Cypress.Commands.add("goToWorkflowsPage", () => { cy.visit("/settings/user/workflows"); - cy.get('[data-cy="workflows-authorized-page"]', { timeout: 60000 }); + cy.get('[data-cy="workflows-authorized-page"]', TIMEOUT); }); Cypress.Commands.add("cleanTestData", () => { - const labelsToDelete = [ - FORM_DATA?.create?.name, - FORM_DATA?.edit?.name, - FORM_DATA?.delete?.name, + const defaultLabelsZuid = [ + "36-14b315-d24ft", + "36-n33d5-23v13w", + "36-14b315-4pp20v3d", ]; cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( (response) => { response?.data ?.filter( - (label) => !label?.deletedAt && labelsToDelete.includes(label?.name) + (label) => + !label?.deletedAt && !defaultLabelsZuid.includes(label?.ZUID) ) .forEach((label) => { cy.apiRequest({ @@ -446,28 +471,6 @@ Cypress.Commands.add("cleanTestData", () => { ); }); -Cypress.Commands.add("moreActionEdit", (label) => { - cy.get('[data-cy="active-labels-container"]') - .find('[data-cy="status-label-name"]') - .contains(label, { timeout: 60000 }) - .scrollIntoView() - .parents('[data-cy="status-label"]') - .find('[data-cy="status-label-more-actions"]') - .click(); - cy.get('[data-cy="menu-item-edit"]').click(); -}); - -Cypress.Commands.add("moreActionDeactivate", (label) => { - cy.get('[data-cy="active-labels-container"]') - .find('[data-cy="status-label-name"]') - .contains(label, { timeout: 60000 }) - .scrollIntoView() - .parents('[data-cy="status-label"]') - .find('[data-cy="status-label-more-actions"]') - .click(); - cy.get('[data-cy="menu-item-deactivate"]').click(); -}); - Cypress.Commands.add("createTestData", () => { cy.apiRequest({ url: `${INSTANCE_API}/env/labels`, From 3a3172e85684ba2641db12a5df2beaff8028ad10 Mon Sep 17 00:00:00 2001 From: geodem Date: Thu, 9 Jan 2025 10:49:20 +0800 Subject: [PATCH 51/59] Fixed workflow status labels test cases --- cypress/e2e/settings/workflows.spec.js | 204 +++++++++++------- .../forms-dialogs/StatusLabelForm.tsx | 7 +- 2 files changed, 131 insertions(+), 80 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 54ca413c5e..8138698b20 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -5,6 +5,8 @@ import { colorMenu, } from "../../../src/apps/settings/src/app/views/User/Workflows/constants"; +const TIMEOUT = { timeout: 40_000 }; + const INSTANCE_API = `${ CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL }${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; @@ -47,25 +49,25 @@ const LABELS = { const EMPTY_SEARCH_TEXT = "xx_yy_zz_00"; -const FORM_DATA = { - create: { - name: "Test__Create", - description: "Test__Create Description", +const FORMx_DATA = { + test1: { + name: "Test__1", + description: "Test__1 Description", color: "Grey", addPermissionRoles: "Admin", removePermissionRoles: "Admin", allowPublish: true, }, - edit: { - name: "Test__Edit", - description: "Test__Edit Description", + test2: { + name: "Test__2", + description: "Test__2 Description", color: "Rose", addPermissionRoles: "Admin", removePermissionRoles: "Admin", allowPublish: false, }, - delete: { - name: "Test__Delete", + test3: { + name: "Test__3", description: "Test__Delete Description", color: "Red", addPermissionRoles: [], @@ -74,6 +76,33 @@ const FORM_DATA = { }, }; +const TEST_DATA = { + new: { + name: "Test__new", + description: "Test__new Description", + color: "Grey", + addPermissionRoles: "Admin", + removePermissionRoles: "Admin", + allowPublish: true, + }, + edited: { + name: "Test__edited", + description: "Test__edited Description", + color: "Rose", + addPermissionRoles: "Admin", + removePermissionRoles: "Admin", + allowPublish: false, + }, + temp: { + name: "Test__temp", + description: "Test__temp Description", + color: "Red", + addPermissionRoles: [], + removePermissionRoles: [], + allowPublish: false, + }, +}; + describe("Workflow Status Labels: Restricted User", () => { it("displays restricted access message and admin profiles", () => { cy.intercept("GET", ENDPOINTS?.userRoles, { @@ -85,7 +114,7 @@ describe("Workflow Status Labels: Restricted User", () => { ); cy.visit("/settings/user/workflows"); - cy.get('[data-cy="workflows-restricted-page"]', { timeout: 60000 }); + cy.get('[data-cy="workflows-restricted-page"]', TIMEOUT); cy.wait("@getInstanceUserRoles") .its("response.body.data") @@ -137,6 +166,9 @@ describe("Workflow Status Labels: Authorized User", () => { it("Form Validation: should display error message when required fields are empty", function () { cy.get("button").contains("Create Status").click(); + + cy.get('[data-cy="status-label-form"]', TIMEOUT); + const nameFieldErrorMessage = "Name is required"; cy.get('[data-cy="status-label-submit-button"]').click(); cy.contains(nameFieldErrorMessage).should("exist"); @@ -145,20 +177,23 @@ describe("Workflow Status Labels: Authorized User", () => { it("Fills out and submits form", () => { cy.get("button").contains("Create Status").click(); + + cy.get('[data-cy="status-label-form"]', TIMEOUT); + cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - cy.get('input[name="name"]').type(FORM_DATA.create.name); - cy.get('textarea[name="description"]').type(FORM_DATA.create.description); + cy.get('input[name="name"]').type(TEST_DATA.new.name); + cy.get('textarea[name="description"]').type(TEST_DATA.new.description); cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains(FORM_DATA.create.color).click(); + cy.get('ul li[role="option"]').contains(TEST_DATA.new.color).click(); cy.get('input[name="addPermissionRoles"]') .parent() .find("button") .click(); cy.get('ul li[role="option"]') - .contains(FORM_DATA.create.addPermissionRoles) + .contains(TEST_DATA.new.addPermissionRoles) .click(); cy.get('form[role="dialog"]').click(); @@ -167,31 +202,33 @@ describe("Workflow Status Labels: Authorized User", () => { .find("button") .click(); cy.get('ul li[role="option"]') - .contains(FORM_DATA.create.removePermissionRoles) + .contains(TEST_DATA.new.removePermissionRoles) .click(); cy.get('form[role="dialog"]').click(); - if (FORM_DATA.create.allowPublish) { + if (TEST_DATA.new.allowPublish) { cy.get('input[name="allowPublish"]').check(); } else { cy.get('input[name="allowPublish"]').uncheck(); } cy.get('[data-cy="status-label-submit-button"]').click(); - cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( - (createStatusLabel, getAllStatusLabels) => { + + cy + .wait(["@createStatusLabel", "@getAllStatusLabels"]) + .spread((createStatusLabel, getAllStatusLabels) => { const createdStatusLabel = createStatusLabel?.response?.body?.data; const { active } = parseStatusLabels( getAllStatusLabels?.response?.body?.data ); expect(createdStatusLabel).to.include({ - name: FORM_DATA.create.name, - description: FORM_DATA.create.description, + name: TEST_DATA.new.name, + description: TEST_DATA.new.description, color: colorMenu.find( - (color) => color?.label === FORM_DATA.create.color + (color) => color?.label === TEST_DATA.new.color )?.value, - allowPublish: FORM_DATA.create.allowPublish, + allowPublish: TEST_DATA.new.allowPublish, }); expect(createdStatusLabel.addPermissionRoles).to.have.lengthOf(1); expect(createdStatusLabel.removePermissionRoles).to.have.lengthOf(1); @@ -199,21 +236,21 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]' ).should("have.length", active.length); - } - ); + }).sh; + cy.wait(1500); }); it("Highlights the newly created status label.", () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(FORM_DATA.create.name, { timeout: 60000 }) + .contains(TEST_DATA.new.name) .parents('[data-cy="status-label"]') - .should("have.css", "background-color", FOCUSED_LABEL_COLOR); + .should("have.css", "background-color", FOCUSED_LABEL_COLOR, TIMEOUT); }); it("Clicking outside the focused label restores it to its default state.", () => { cy.get('[data-cy="active-labels-container"]').click(); cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(FORM_DATA.create.name) + .contains(TEST_DATA.new.name) .parents('[data-cy="status-label"]') .should("not.have.css", "background-color", FOCUSED_LABEL_COLOR); }); @@ -222,27 +259,44 @@ describe("Workflow Status Labels: Authorized User", () => { context("Edit Status Label", () => { it("Open Status Label and Edit Details", { retries: 1 }, () => { cy.goToWorkflowsPage(); - cy.moreActionEdit(FORM_DATA.create.name); + cy.wait(1500); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]', + TIMEOUT + ) + + .contains(TEST_DATA?.new?.name) + .parents('[data-cy="status-label"]') + .find('[data-cy="status-label-more-actions"]') + .trigger("click", { force: true }); + + cy.get('[data-cy="menu-item-edit"]').click({ force: true }); + + cy.get('[data-cy="status-label-form"]', TIMEOUT); cy.get("button").contains("Deactivate Status").should("be.enabled"); - cy.get('input[name="name"]').clear().type(FORM_DATA.edit.name); + cy.get('input[name="name"]').clear().type(TEST_DATA.edited.name); + cy.get('textarea[name="description"]') .clear() - .type(FORM_DATA.edit.description); + .type(TEST_DATA.edited.description); + cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains(FORM_DATA.edit.color).click(); + cy.get('ul li[role="option"]').contains(TEST_DATA.edited.color).click(); - cy.get("span.MuiChip-label") - .contains(FORM_DATA.edit.addPermissionRoles) + cy.get(".MuiChip-root") + .contains(TEST_DATA.edited.addPermissionRoles) .parent() - .find('svg[data-testid="CancelIcon"]') - .click(); - cy.get("span.MuiChip-label") - .contains(FORM_DATA.edit.removePermissionRoles) + .find(".MuiChip-deleteIcon") + .trigger("click", { force: true }); + + cy.get(".MuiChip-root") + .contains(TEST_DATA.edited.removePermissionRoles) .parent() - .find('svg[data-testid="CancelIcon"]') - .click(); + .find(".MuiChip-deleteIcon") + .trigger("click", { force: true }); + cy.get('input[name="allowPublish"]').uncheck(); cy.get('[data-cy="status-label-submit-button"]').click(); @@ -262,9 +316,9 @@ describe("Workflow Status Labels: Authorized User", () => { expect(editStatusLabel.response.statusCode).to.eq(200); expect(getAllStatusLabels.response.statusCode).to.eq(200); expect(updatedLabel).to.deep.include({ - name: FORM_DATA.edit.name, + name: TEST_DATA.edited.name, color: colorMenu.find( - (color) => color?.label === FORM_DATA.edit.color + (color) => color?.label === TEST_DATA.edited.color )?.value, addPermissionRoles: [], removePermissionRoles: [], @@ -272,6 +326,7 @@ describe("Workflow Status Labels: Authorized User", () => { }); } ); + cy.wait(1500); }); }); @@ -318,7 +373,16 @@ describe("Workflow Status Labels: Authorized User", () => { context("Deactivate Status Label", () => { it("Deactivate using menu options", { retries: 1 }, function () { cy.goToWorkflowsPage(); - cy.moreActionDeactivate(FORM_DATA?.edit?.name); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]', + TIMEOUT + ) + // .last() + .contains(TEST_DATA?.edited?.name) + .parents('[data-cy="status-label"]') + .find('[data-cy="status-label-more-actions"]') + .click(); + cy.get('[data-cy="menu-item-deactivate"]').click(); cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( @@ -341,13 +405,21 @@ describe("Workflow Status Labels: Authorized User", () => { ); cy.get(".notistack-Snackbar").contains( - new RegExp(`Status De-activated:\\s*${FORM_DATA?.edit?.name}`) + new RegExp(`Status De-activated:\\s*${TEST_DATA?.edited?.name}`) ); }); it("Deactivate using form button", { retries: 1 }, function () { cy.goToWorkflowsPage(); - cy.moreActionEdit(FORM_DATA.delete.name); + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]', + TIMEOUT + ) + .contains(TEST_DATA?.temp?.name) + .parents('[data-cy="status-label"]') + .find('[data-cy="status-label-more-actions"]') + .click(); + cy.get('[data-cy="menu-item-edit"]').click(); cy.get('[data-cy="form-deactivate-status-button"]').click(); cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); @@ -371,7 +443,7 @@ describe("Workflow Status Labels: Authorized User", () => { ); cy.get(".notistack-Snackbar").contains( - new RegExp(`Status De-activated:\\s*${FORM_DATA?.delete?.name}`) + new RegExp(`Status De-activated:\\s*${TEST_DATA?.temp?.name}`) ); }); }); @@ -411,30 +483,28 @@ describe("Workflow Status Labels: Authorized User", () => { '[data-cy="active-labels-container"], [data-cy="deactivated-labels-container"]' ).should("exist"); cy.get('[data-cy="no-results-page"]').should("not.exist"); - cy.get('input[placeholder="Search Statuses"]') - .should("be.empty") - .and("have.focus"); + cy.get('input[placeholder="Search Statuses"]').should("be.empty"); }); }); }); Cypress.Commands.add("goToWorkflowsPage", () => { cy.visit("/settings/user/workflows"); - cy.get('[data-cy="workflows-authorized-page"]', { timeout: 60000 }); + cy.get('[data-cy="workflows-authorized-page"]', { timeout: 60_000 }); }); Cypress.Commands.add("cleanTestData", () => { - const labelsToDelete = [ - FORM_DATA?.create?.name, - FORM_DATA?.edit?.name, - FORM_DATA?.delete?.name, + const testLabels = [ + TEST_DATA?.new?.name, + TEST_DATA?.edited?.name, + TEST_DATA?.temp?.name, ]; cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( (response) => { response?.data ?.filter( - (label) => !label?.deletedAt && labelsToDelete.includes(label?.name) + (label) => !label?.deletedAt && testLabels.includes(label?.name) ) .forEach((label) => { cy.apiRequest({ @@ -446,35 +516,13 @@ Cypress.Commands.add("cleanTestData", () => { ); }); -Cypress.Commands.add("moreActionEdit", (label) => { - cy.get('[data-cy="active-labels-container"]') - .find('[data-cy="status-label-name"]') - .contains(label, { timeout: 60000 }) - .scrollIntoView() - .parents('[data-cy="status-label"]') - .find('[data-cy="status-label-more-actions"]') - .click(); - cy.get('[data-cy="menu-item-edit"]').click(); -}); - -Cypress.Commands.add("moreActionDeactivate", (label) => { - cy.get('[data-cy="active-labels-container"]') - .find('[data-cy="status-label-name"]') - .contains(label, { timeout: 60000 }) - .scrollIntoView() - .parents('[data-cy="status-label"]') - .find('[data-cy="status-label-more-actions"]') - .click(); - cy.get('[data-cy="menu-item-deactivate"]').click(); -}); - Cypress.Commands.add("createTestData", () => { cy.apiRequest({ url: `${INSTANCE_API}/env/labels`, method: "POST", body: { - ...FORM_DATA?.delete, - color: colorMenu.find((color) => color?.label === FORM_DATA.delete.color) + ...TEST_DATA?.temp, + color: colorMenu.find((color) => color?.label === TEST_DATA.temp.color) ?.value, }, }); diff --git a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx index 796ed55da6..d051b0547b 100644 --- a/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx +++ b/src/apps/settings/src/app/views/User/Workflows/authorized/forms-dialogs/StatusLabelForm.tsx @@ -226,7 +226,7 @@ const RolesSelectInput = ({ checked={selected} value={option.value} sx={{ - marginRight: 5, + marginRight: "5px", position: "absolute", left: 0, }} @@ -394,7 +394,10 @@ const StatusLabelForm: FC = ({ - + Date: Thu, 9 Jan 2025 15:54:37 +0800 Subject: [PATCH 52/59] Fixed test cases --- cypress/e2e/settings/workflows.spec.js | 58 ++++++++------------------ 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 8138698b20..97d167abc2 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -49,33 +49,6 @@ const LABELS = { const EMPTY_SEARCH_TEXT = "xx_yy_zz_00"; -const FORMx_DATA = { - test1: { - name: "Test__1", - description: "Test__1 Description", - color: "Grey", - addPermissionRoles: "Admin", - removePermissionRoles: "Admin", - allowPublish: true, - }, - test2: { - name: "Test__2", - description: "Test__2 Description", - color: "Rose", - addPermissionRoles: "Admin", - removePermissionRoles: "Admin", - allowPublish: false, - }, - test3: { - name: "Test__3", - description: "Test__Delete Description", - color: "Red", - addPermissionRoles: [], - removePermissionRoles: [], - allowPublish: false, - }, -}; - const TEST_DATA = { new: { name: "Test__new", @@ -214,9 +187,8 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get('[data-cy="status-label-submit-button"]').click(); - cy - .wait(["@createStatusLabel", "@getAllStatusLabels"]) - .spread((createStatusLabel, getAllStatusLabels) => { + cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( + (createStatusLabel, getAllStatusLabels) => { const createdStatusLabel = createStatusLabel?.response?.body?.data; const { active } = parseStatusLabels( getAllStatusLabels?.response?.body?.data @@ -236,7 +208,8 @@ describe("Workflow Status Labels: Authorized User", () => { cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]' ).should("have.length", active.length); - }).sh; + } + ); cy.wait(1500); }); @@ -268,7 +241,7 @@ describe("Workflow Status Labels: Authorized User", () => { .contains(TEST_DATA?.new?.name) .parents('[data-cy="status-label"]') .find('[data-cy="status-label-more-actions"]') - .trigger("click", { force: true }); + .click({ force: true }); cy.get('[data-cy="menu-item-edit"]').click({ force: true }); @@ -289,13 +262,13 @@ describe("Workflow Status Labels: Authorized User", () => { .contains(TEST_DATA.edited.addPermissionRoles) .parent() .find(".MuiChip-deleteIcon") - .trigger("click", { force: true }); + .click({ force: true }); cy.get(".MuiChip-root") .contains(TEST_DATA.edited.removePermissionRoles) .parent() .find(".MuiChip-deleteIcon") - .trigger("click", { force: true }); + .click({ force: true }); cy.get('input[name="allowPublish"]').uncheck(); @@ -338,13 +311,13 @@ describe("Workflow Status Labels: Authorized User", () => { it("Drag status label to a new position", { retries: 1 }, () => { const dataTransfer = new DataTransfer(); - cy.get(`[data-cy="status-label"]`) - .find('[data-cy="status-label-drag-handle"]') + cy.get(`[data-cy="status-label"] [data-cy="status-label-drag-handle"]`) + // .find('[data-cy="status-label-drag-handle"]') .eq(0) .trigger("dragstart", { dataTransfer }); - cy.get(`[data-cy="status-label"]`) - .find('[data-cy="status-label-drag-handle"]') + cy.get(`[data-cy="status-label"] [data-cy="status-label-drag-handle"]`) + // .find('[data-cy="status-label-drag-handle"]') .eq(1) .trigger("dragover", { dataTransfer }) .trigger("drop", { dataTransfer }); @@ -377,19 +350,20 @@ describe("Workflow Status Labels: Authorized User", () => { '[data-cy="active-labels-container"] [data-cy="status-label"]', TIMEOUT ) - // .last() .contains(TEST_DATA?.edited?.name) .parents('[data-cy="status-label"]') .find('[data-cy="status-label-more-actions"]') .click(); + cy.get('[data-cy="menu-item-deactivate"]').click(); - cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( "deactivateStatusLabel" ); cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( (deactivateStatusLabel, getAllStatusLabels) => { const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; @@ -411,6 +385,7 @@ describe("Workflow Status Labels: Authorized User", () => { it("Deactivate using form button", { retries: 1 }, function () { cy.goToWorkflowsPage(); + cy.get( '[data-cy="active-labels-container"] [data-cy="status-label"]', TIMEOUT @@ -421,13 +396,14 @@ describe("Workflow Status Labels: Authorized User", () => { .click(); cy.get('[data-cy="menu-item-edit"]').click(); cy.get('[data-cy="form-deactivate-status-button"]').click(); - cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( "deactivateStatusLabel" ); cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( (deactivateStatusLabel, getAllStatusLabels) => { const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; From f08c7ca9ca3308771359266d523c16345a39c4d2 Mon Sep 17 00:00:00 2001 From: geodem Date: Thu, 9 Jan 2025 15:54:37 +0800 Subject: [PATCH 53/59] Fixed test cases --- cypress/e2e/settings/workflows.spec.js | 679 ++++++++++++------------- 1 file changed, 321 insertions(+), 358 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 8138698b20..b75471830c 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -49,33 +49,6 @@ const LABELS = { const EMPTY_SEARCH_TEXT = "xx_yy_zz_00"; -const FORMx_DATA = { - test1: { - name: "Test__1", - description: "Test__1 Description", - color: "Grey", - addPermissionRoles: "Admin", - removePermissionRoles: "Admin", - allowPublish: true, - }, - test2: { - name: "Test__2", - description: "Test__2 Description", - color: "Rose", - addPermissionRoles: "Admin", - removePermissionRoles: "Admin", - allowPublish: false, - }, - test3: { - name: "Test__3", - description: "Test__Delete Description", - color: "Red", - addPermissionRoles: [], - removePermissionRoles: [], - allowPublish: false, - }, -}; - const TEST_DATA = { new: { name: "Test__new", @@ -88,22 +61,47 @@ const TEST_DATA = { edited: { name: "Test__edited", description: "Test__edited Description", - color: "Rose", - addPermissionRoles: "Admin", - removePermissionRoles: "Admin", - allowPublish: false, + color: "Pink", + addPermissionRoles: [], + removePermissionRoles: [], + allowPublish: true, }, - temp: { - name: "Test__temp", - description: "Test__temp Description", + temp1: { + name: "Test__temp1", + description: "Test__temp1 Description", color: "Red", addPermissionRoles: [], removePermissionRoles: [], allowPublish: false, }, + temp2: { + name: "Test__temp2", + description: "Test__temp2 Description", + color: "Yellow", + addPermissionRoles: [], + removePermissionRoles: [], + allowPublish: false, + }, + temp3: { + name: "Test__temp3", + description: "Test__temp3 Description", + color: "Purple", + addPermissionRoles: [], + removePermissionRoles: [], + allowPublish: false, + }, }; -describe("Workflow Status Labels: Restricted User", () => { +before(() => { + cy.cleanTestData(); + cy.createTestData(); +}); + +after(() => { + cy.cleanTestData(); +}); + +describe("Restricted User", () => { it("displays restricted access message and admin profiles", () => { cy.intercept("GET", ENDPOINTS?.userRoles, { statusCode: 200, @@ -133,358 +131,321 @@ describe("Workflow Status Labels: Restricted User", () => { }); }); -describe("Workflow Status Labels: Authorized User", () => { +describe("Authorized User", () => { before(() => { - cy.cleanTestData(); - cy.createTestData(); + cy.goToWorkflowsPage(); }); - context("Workflow Page", () => { - before(() => { - cy.goToWorkflowsPage(); - }); - it("displays workflow page elements for authorized users", () => { - cy.contains("Workflows").should("exist"); - cy.get("button").contains("Create Status").should("exist"); - cy.get('input[placeholder="Search Statuses"]').should("exist"); - cy.get('input[value="deactivated"]').should("exist"); - }); + it("displays workflow page elements for authorized users", () => { + cy.contains("Workflows").should("exist"); + cy.get("button").contains("Create Status").should("exist"); + cy.get('input[placeholder="Search Statuses"]').should("exist"); + cy.get('input[value="deactivated"]').should("exist"); + }); - it("Show Deactivated Labels: Displays active and deactivated sections", () => { - cy.get('input[value="deactivated"]').click(); - cy.contains(LABELS.activeLabelsHeader).should("exist"); - cy.contains(LABELS.activeLabelsHeader).should("exist"); - cy.contains(LABELS.deactivatedLabelsHeader).should("exist"); - cy.contains(LABELS.deactivatedLabelsHeader).should("exist"); - }); + it("Show Deactivated Labels: Displays active and deactivated sections", () => { + cy.get('input[value="deactivated"]').click(); + cy.contains(LABELS.activeLabelsHeader).should("exist"); + cy.contains(LABELS.deactivatedLabelsHeader).should("exist"); }); +}); - context("Create New Status Label", { retries: 1 }, () => { - before(() => { - cy.goToWorkflowsPage(); - }); +describe("Create New Status Label", { retries: 1 }, () => { + before(() => { + cy.goToWorkflowsPage(); + }); - it("Form Validation: should display error message when required fields are empty", function () { - cy.get("button").contains("Create Status").click(); + it("Form Validation: should display error message when required fields are empty", function () { + cy.get("button").contains("Create Status").click(); - cy.get('[data-cy="status-label-form"]', TIMEOUT); + cy.get('[data-cy="status-label-form"]', TIMEOUT); - const nameFieldErrorMessage = "Name is required"; - cy.get('[data-cy="status-label-submit-button"]').click(); - cy.contains(nameFieldErrorMessage).should("exist"); - cy.get("button").contains("Cancel").click(); - }); + const nameFieldErrorMessage = "Name is required"; + cy.get('[data-cy="status-label-submit-button"]').click(); + cy.contains(nameFieldErrorMessage).should("exist"); + cy.get("button").contains("Cancel").click(); + }); - it("Fills out and submits form", () => { - cy.get("button").contains("Create Status").click(); - - cy.get('[data-cy="status-label-form"]', TIMEOUT); - - cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); - cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - cy.get('input[name="name"]').type(TEST_DATA.new.name); - cy.get('textarea[name="description"]').type(TEST_DATA.new.description); - cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains(TEST_DATA.new.color).click(); - - cy.get('input[name="addPermissionRoles"]') - .parent() - .find("button") - .click(); - cy.get('ul li[role="option"]') - .contains(TEST_DATA.new.addPermissionRoles) - .click(); - cy.get('form[role="dialog"]').click(); - - cy.get('input[name="removePermissionRoles"]') - .parent() - .find("button") - .click(); - cy.get('ul li[role="option"]') - .contains(TEST_DATA.new.removePermissionRoles) - .click(); - cy.get('form[role="dialog"]').click(); - - if (TEST_DATA.new.allowPublish) { - cy.get('input[name="allowPublish"]').check(); - } else { - cy.get('input[name="allowPublish"]').uncheck(); - } + it("Fills out and submits form", () => { + cy.get("button").contains("Create Status").click(); + + cy.get('[data-cy="status-label-form"]', TIMEOUT); + + cy.get('input[name="name"]').type(TEST_DATA.new.name); + cy.get('textarea[name="description"]').type(TEST_DATA.new.description); + cy.get('input[name="color"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains(TEST_DATA.new.color).click(); + + cy.get('input[name="addPermissionRoles"]').parent().find("button").click(); + cy.get('ul li[role="option"]') + .contains(TEST_DATA.new.addPermissionRoles) + .click(); + cy.get('form[role="dialog"]').click(); + + cy.get('input[name="removePermissionRoles"]') + .parent() + .find("button") + .click(); + cy.get('ul li[role="option"]') + .contains(TEST_DATA.new.removePermissionRoles) + .click(); + cy.get('form[role="dialog"]').click(); + + if (TEST_DATA.new.allowPublish) { + cy.get('input[name="allowPublish"]').check(); + } else { + cy.get('input[name="allowPublish"]').uncheck(); + } - cy.get('[data-cy="status-label-submit-button"]').click(); - - cy - .wait(["@createStatusLabel", "@getAllStatusLabels"]) - .spread((createStatusLabel, getAllStatusLabels) => { - const createdStatusLabel = createStatusLabel?.response?.body?.data; - const { active } = parseStatusLabels( - getAllStatusLabels?.response?.body?.data - ); - - expect(createdStatusLabel).to.include({ - name: TEST_DATA.new.name, - description: TEST_DATA.new.description, - color: colorMenu.find( - (color) => color?.label === TEST_DATA.new.color - )?.value, - allowPublish: TEST_DATA.new.allowPublish, - }); - expect(createdStatusLabel.addPermissionRoles).to.have.lengthOf(1); - expect(createdStatusLabel.removePermissionRoles).to.have.lengthOf(1); - - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]' - ).should("have.length", active.length); - }).sh; - cy.wait(1500); - }); + cy.intercept("POST", ENDPOINTS?.statusLabels).as("createStatusLabel"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - it("Highlights the newly created status label.", () => { - cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(TEST_DATA.new.name) - .parents('[data-cy="status-label"]') - .should("have.css", "background-color", FOCUSED_LABEL_COLOR, TIMEOUT); - }); + cy.get('[data-cy="status-label-submit-button"]').click(); - it("Clicking outside the focused label restores it to its default state.", () => { - cy.get('[data-cy="active-labels-container"]').click(); - cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') - .contains(TEST_DATA.new.name) - .parents('[data-cy="status-label"]') - .should("not.have.css", "background-color", FOCUSED_LABEL_COLOR); - }); + cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( + (createStatusLabel, getAllStatusLabels) => { + const createdStatusLabel = createStatusLabel?.response?.body?.data; + const { active } = parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ); + + expect(createdStatusLabel).to.include({ + name: TEST_DATA.new.name, + description: TEST_DATA.new.description, + color: colorMenu.find((color) => color?.label === TEST_DATA.new.color) + ?.value, + allowPublish: TEST_DATA.new.allowPublish, + }); + expect(createdStatusLabel.addPermissionRoles).to.have.lengthOf(1); + expect(createdStatusLabel.removePermissionRoles).to.have.lengthOf(1); + + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]' + ).should("have.length", active.length); + } + ); + cy.wait(1500); + }); + + it("Highlights the newly created status label.", () => { + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') + .contains(TEST_DATA.new.name) + .parents('[data-cy="status-label"]') + .should("have.css", "background-color", FOCUSED_LABEL_COLOR, TIMEOUT); }); - context("Edit Status Label", () => { - it("Open Status Label and Edit Details", { retries: 1 }, () => { - cy.goToWorkflowsPage(); - cy.wait(1500); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]', - TIMEOUT - ) + it("Clicking outside the focused label restores it to its default state.", () => { + cy.get('[data-cy="active-labels-container"]').click(); + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') + .contains(TEST_DATA.new.name) + .parents('[data-cy="status-label"]') + .should("not.have.css", "background-color", FOCUSED_LABEL_COLOR); + }); +}); - .contains(TEST_DATA?.new?.name) - .parents('[data-cy="status-label"]') - .find('[data-cy="status-label-more-actions"]') - .trigger("click", { force: true }); +describe("Edit Status Label", () => { + it("Open Status Label and Edit Details", { retries: 1 }, () => { + cy.goToWorkflowsPage(); + cy.wait(1500); + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') + .contains(TEST_DATA?.temp1?.name) + .parents('[data-cy="status-label"]') + .find('[data-cy="status-label-more-actions"]') + .click({ force: true }); - cy.get('[data-cy="menu-item-edit"]').click({ force: true }); + cy.get('[data-cy="menu-item-edit"]').click({ force: true }); - cy.get('[data-cy="status-label-form"]', TIMEOUT); + cy.get('[data-cy="status-label-form"]', TIMEOUT); - cy.get("button").contains("Deactivate Status").should("be.enabled"); + cy.get("button").contains("Deactivate Status").should("be.enabled"); - cy.get('input[name="name"]').clear().type(TEST_DATA.edited.name); + cy.get('input[name="name"]').clear().type(TEST_DATA.edited.name); - cy.get('textarea[name="description"]') - .clear() - .type(TEST_DATA.edited.description); + cy.get('textarea[name="description"]') + .clear() + .type(TEST_DATA.edited.description); - cy.get('input[name="color"]').parent().find("button").click(); - cy.get('ul li[role="option"]').contains(TEST_DATA.edited.color).click(); + cy.get('input[name="color"]').parent().find("button").click(); + cy.get('ul li[role="option"]').contains(TEST_DATA.edited.color).click(); - cy.get(".MuiChip-root") - .contains(TEST_DATA.edited.addPermissionRoles) - .parent() - .find(".MuiChip-deleteIcon") - .trigger("click", { force: true }); + cy.get('[data-cy="status-label-submit-button"]').click(); - cy.get(".MuiChip-root") - .contains(TEST_DATA.edited.removePermissionRoles) - .parent() - .find(".MuiChip-deleteIcon") - .trigger("click", { force: true }); + cy.intercept("PUT", `${ENDPOINTS?.statusLabels}/**`).as("editStatusLabel"); + cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - cy.get('input[name="allowPublish"]').uncheck(); + cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( + (editStatusLabel, getAllStatusLabels) => { + const targetLabelZUID = editStatusLabel.response.body.data; + const updatedLabel = getAllStatusLabels.response.body.data.find( + (label) => label.ZUID === targetLabelZUID + ); - cy.get('[data-cy="status-label-submit-button"]').click(); - - cy.intercept("PUT", `${ENDPOINTS?.statusLabels}/**`).as( - "editStatusLabel" - ); - cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( - (editStatusLabel, getAllStatusLabels) => { - const targetLabelZUID = editStatusLabel.response.body.data; - const updatedLabel = getAllStatusLabels.response.body.data.find( - (label) => label.ZUID === targetLabelZUID - ); - - expect(editStatusLabel.response.statusCode).to.eq(200); - expect(getAllStatusLabels.response.statusCode).to.eq(200); - expect(updatedLabel).to.deep.include({ - name: TEST_DATA.edited.name, - color: colorMenu.find( - (color) => color?.label === TEST_DATA.edited.color - )?.value, - addPermissionRoles: [], - removePermissionRoles: [], - allowPublish: false, - }); - } - ); - cy.wait(1500); - }); + expect(editStatusLabel.response.statusCode).to.eq(200); + expect(getAllStatusLabels.response.statusCode).to.eq(200); + expect(updatedLabel).to.deep.include({ + name: TEST_DATA.edited.name, + description: TEST_DATA.edited.description, + color: colorMenu.find( + (color) => color?.label === TEST_DATA.edited.color + )?.value, + }); + } + ); }); +}); - context("Re-order Status Labels", () => { - before(() => { - cy.goToWorkflowsPage(); - }); +describe("Re-order Status Labels", () => { + before(() => { + cy.goToWorkflowsPage(); + }); - it("Drag status label to a new position", { retries: 1 }, () => { - const dataTransfer = new DataTransfer(); + it("Drag status label to a new position", { retries: 1 }, () => { + const dataTransfer = new DataTransfer(); - cy.get(`[data-cy="status-label"]`) - .find('[data-cy="status-label-drag-handle"]') - .eq(0) - .trigger("dragstart", { dataTransfer }); + cy.get(`[data-cy="status-label"] [data-cy="status-label-drag-handle"]`) + .eq(0) + .trigger("dragstart", { dataTransfer }); - cy.get(`[data-cy="status-label"]`) - .find('[data-cy="status-label-drag-handle"]') - .eq(1) - .trigger("dragover", { dataTransfer }) - .trigger("drop", { dataTransfer }); + cy.get(`[data-cy="status-label"] [data-cy="status-label-drag-handle"]`) + .eq(1) + .trigger("dragover", { dataTransfer }) + .trigger("drop", { dataTransfer }); - cy.intercept("PUT", ENDPOINTS.statusLabels).as("reorderStatusLabel"); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.intercept("PUT", ENDPOINTS.statusLabels).as("reorderStatusLabel"); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - cy.wait(["@reorderStatusLabel", "@getAllStatusLabels"]).spread( - (reorderStatusLabel, getAllStatusLabels) => { - const reorderedLabels = reorderStatusLabel?.request?.body?.data; - const updatedLabel = getAllStatusLabels?.response?.body?.data; + cy.wait(["@reorderStatusLabel", "@getAllStatusLabels"]).spread( + (reorderStatusLabel, getAllStatusLabels) => { + const reorderedLabels = reorderStatusLabel?.request?.body?.data; + const updatedLabel = getAllStatusLabels?.response?.body?.data; - expect(reorderStatusLabel.response.statusCode).to.eq(200); - expect(getAllStatusLabels.response.statusCode).to.eq(200); + expect(reorderStatusLabel.response.statusCode).to.eq(200); + expect(getAllStatusLabels.response.statusCode).to.eq(200); - reorderedLabels.forEach((label) => { - expect( - updatedLabel.find((item) => item?.ZUID === label?.ZUID)?.sort - ).to.eq(label.sort); - }); - } - ); - }); + reorderedLabels.forEach((label) => { + expect( + updatedLabel.find((item) => item?.ZUID === label?.ZUID)?.sort + ).to.eq(label.sort); + }); + } + ); }); +}); - context("Deactivate Status Label", () => { - it("Deactivate using menu options", { retries: 1 }, function () { - cy.goToWorkflowsPage(); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]', - TIMEOUT - ) - // .last() - .contains(TEST_DATA?.edited?.name) - .parents('[data-cy="status-label"]') - .find('[data-cy="status-label-more-actions"]') - .click(); - cy.get('[data-cy="menu-item-deactivate"]').click(); - cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); - - cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( - "deactivateStatusLabel" - ); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( - (deactivateStatusLabel, getAllStatusLabels) => { - const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; - const { active, deactivated } = parseStatusLabels( - getAllStatusLabels?.response?.body?.data - ); - - expect(active?.some((label) => label.ZUID === deactivatedLabel)).to.be - .false; - expect(deactivated?.some((label) => label.ZUID === deactivatedLabel)) - .to.be.true; - } - ); - - cy.get(".notistack-Snackbar").contains( - new RegExp(`Status De-activated:\\s*${TEST_DATA?.edited?.name}`) - ); - }); +describe("Deactivate Status Label", () => { + beforeEach(() => { + cy.goToWorkflowsPage(); + }); - it("Deactivate using form button", { retries: 1 }, function () { - cy.goToWorkflowsPage(); - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]', - TIMEOUT - ) - .contains(TEST_DATA?.temp?.name) - .parents('[data-cy="status-label"]') - .find('[data-cy="status-label-more-actions"]') - .click(); - cy.get('[data-cy="menu-item-edit"]').click(); - cy.get('[data-cy="form-deactivate-status-button"]').click(); - cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); - - cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( - "deactivateStatusLabel" - ); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( - (deactivateStatusLabel, getAllStatusLabels) => { - const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; - const { active, deactivated } = parseStatusLabels( - getAllStatusLabels?.response?.body?.data - ); - - expect(active?.some((label) => label.ZUID === deactivatedLabel)).to.be - .false; - expect(deactivated?.some((label) => label.ZUID === deactivatedLabel)) - .to.be.true; - } - ); - - cy.get(".notistack-Snackbar").contains( - new RegExp(`Status De-activated:\\s*${TEST_DATA?.temp?.name}`) - ); - }); + it("Deactivate using menu options", { retries: 1 }, function () { + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') + .contains(TEST_DATA?.temp2?.name) + .parents('[data-cy="status-label"]') + .find('[data-cy="status-label-more-actions"]') + .click(TIMEOUT); + + cy.get('[data-cy="menu-item-deactivate"]').click(); + + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" + ); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + + cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); + + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( + (deactivateStatusLabel, getAllStatusLabels) => { + const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; + const { active, deactivated } = parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ); + + expect(active?.some((label) => label.ZUID === deactivatedLabel)).to.be + .false; + expect(deactivated?.some((label) => label.ZUID === deactivatedLabel)).to + .be.true; + } + ); + cy.wait(1000); + cy.get(".notistack-Snackbar").contains( + new RegExp(`Status De-activated:\\s*${TEST_DATA?.temp2?.name}`) + ); }); - context("Filter Active and Deactivated Status Labels", () => { - before(() => { - cy.getStatusLabels().then((data) => - cy.wrap(data).as("activeDeactivatedStatusLabels") - ); - cy.goToWorkflowsPage(); - cy.get('input[value="deactivated"]').click(); - }); + it("Deactivate using form button", { retries: 1 }, function () { + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') + .contains(TEST_DATA?.temp3?.name) + .parents('[data-cy="status-label"]') + .find('[data-cy="status-label-more-actions"]') + .click(TIMEOUT); - it("Displays active/deactivated status labels", function () { - const { active, deactivated } = this.activeDeactivatedStatusLabels || {}; - cy.get( - '[data-cy="active-labels-container"] [data-cy="status-label"]' - ).should("have.length", active?.length); - cy.get( - '[data-cy="deactivated-labels-container"] [data-cy="status-label"]' - ).should("have.length", deactivated?.length); - }); + cy.get('[data-cy="menu-item-edit"]').click(); - it("Displays Error page when search results returns empty", () => { - cy.get('input[placeholder="Search Statuses"]') - .clear() - .type(EMPTY_SEARCH_TEXT); - cy.get( - '[data-cy="active-labels-container"], [data-cy="deactivated-labels-container"]' - ).should("not.exist"); - cy.get('[data-cy="no-results-page"]').should("exist"); - }); + cy.get('[data-cy="form-deactivate-status-button"]').click(); - it("Clears and focuses search field when clicking 'Search Again'", () => { - cy.get("button").contains("Search Again").click(); - cy.get( - '[data-cy="active-labels-container"], [data-cy="deactivated-labels-container"]' - ).should("exist"); - cy.get('[data-cy="no-results-page"]').should("not.exist"); - cy.get('input[placeholder="Search Statuses"]').should("be.empty"); - }); + cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( + "deactivateStatusLabel" + ); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + + cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); + + cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( + (deactivateStatusLabel, getAllStatusLabels) => { + const deactivatedLabel = deactivateStatusLabel?.response?.body?.data; + const { active, deactivated } = parseStatusLabels( + getAllStatusLabels?.response?.body?.data + ); + + expect(active?.some((label) => label.ZUID === deactivatedLabel)).to.be + .false; + expect(deactivated?.some((label) => label.ZUID === deactivatedLabel)).to + .be.true; + } + ); + cy.wait(1000); + cy.get(".notistack-Snackbar").contains( + new RegExp(`Status De-activated:\\s*${TEST_DATA?.temp3?.name}`) + ); + }); +}); + +describe("Filter Active and Deactivated Status Labels", () => { + before(() => { + cy.getStatusLabels().then((data) => + cy.wrap(data).as("activeDeactivatedStatusLabels") + ); + cy.goToWorkflowsPage(); + cy.get('input[value="deactivated"]').click(); + }); + + it("Displays active/deactivated status labels", function () { + const { active, deactivated } = this.activeDeactivatedStatusLabels || {}; + cy.get( + '[data-cy="active-labels-container"] [data-cy="status-label"]' + ).should("have.length", active?.length); + cy.get( + '[data-cy="deactivated-labels-container"] [data-cy="status-label"]' + ).should("have.length", deactivated?.length); + }); + + it("Displays Error page when search results returns empty", () => { + cy.get('input[placeholder="Search Statuses"]') + .clear() + .type(EMPTY_SEARCH_TEXT); + cy.get( + '[data-cy="active-labels-container"], [data-cy="deactivated-labels-container"]' + ).should("not.exist"); + cy.get('[data-cy="no-results-page"]').should("exist"); + }); + + it("Clears and focuses search field when clicking 'Search Again'", () => { + cy.get("button").contains("Search Again").click(); + cy.get( + '[data-cy="active-labels-container"], [data-cy="deactivated-labels-container"]' + ).should("exist"); + cy.get('[data-cy="no-results-page"]').should("not.exist"); + cy.get('input[placeholder="Search Statuses"]').should("be.empty"); }); }); @@ -497,7 +458,9 @@ Cypress.Commands.add("cleanTestData", () => { const testLabels = [ TEST_DATA?.new?.name, TEST_DATA?.edited?.name, - TEST_DATA?.temp?.name, + TEST_DATA?.temp1?.name, + TEST_DATA?.temp2?.name, + TEST_DATA?.temp3?.name, ]; cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( @@ -517,14 +480,16 @@ Cypress.Commands.add("cleanTestData", () => { }); Cypress.Commands.add("createTestData", () => { - cy.apiRequest({ - url: `${INSTANCE_API}/env/labels`, - method: "POST", - body: { - ...TEST_DATA?.temp, - color: colorMenu.find((color) => color?.label === TEST_DATA.temp.color) - ?.value, - }, + const testLabels = [TEST_DATA?.temp1, TEST_DATA?.temp2, TEST_DATA?.temp3]; + testLabels.forEach((label) => { + cy.apiRequest({ + url: `${INSTANCE_API}/env/labels`, + method: "POST", + body: { + ...label, + color: colorMenu.find((color) => color?.label === label.color)?.value, + }, + }); }); }); @@ -534,14 +499,12 @@ Cypress.Commands.add("getStatusLabels", () => { url: `${INSTANCE_API}/env/labels?showDeleted=true`, }) .then((response) => { - return { - ...parseStatusLabels(response?.data), - }; + return parseStatusLabels(response?.data); }); }); -function parseStatusLabels(statusLabels) { - const { active, deactivated } = statusLabels?.reduce( +function parseStatusLabels(statusLabels = []) { + const { active, deactivated } = statusLabels.reduce( (acc, curr) => { if (!!curr.deletedAt) { acc.deactivated.push(curr); From 69ad65a1b095cb58d040d92396e094ba953b64b4 Mon Sep 17 00:00:00 2001 From: geodem Date: Thu, 9 Jan 2025 21:19:14 +0800 Subject: [PATCH 54/59] Fix flaky test --- cypress/e2e/settings/workflows.spec.js | 61 +++++++++++++++----------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index b75471830c..1ecc8b48c8 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -101,7 +101,7 @@ after(() => { cy.cleanTestData(); }); -describe("Restricted User", () => { +describe("Restricted User", { retries: 1 }, () => { it("displays restricted access message and admin profiles", () => { cy.intercept("GET", ENDPOINTS?.userRoles, { statusCode: 200, @@ -131,12 +131,14 @@ describe("Restricted User", () => { }); }); -describe("Authorized User", () => { +describe("Authorized User", { retries: 1 }, () => { before(() => { cy.goToWorkflowsPage(); }); it("displays workflow page elements for authorized users", () => { + cy.get('[data-cy="active-labels-container"]', TIMEOUT); + cy.contains("Workflows").should("exist"); cy.get("button").contains("Create Status").should("exist"); cy.get('input[placeholder="Search Statuses"]').should("exist"); @@ -156,7 +158,7 @@ describe("Create New Status Label", { retries: 1 }, () => { }); it("Form Validation: should display error message when required fields are empty", function () { - cy.get("button").contains("Create Status").click(); + cy.get("button").contains("Create Status").click(TIMEOUT); cy.get('[data-cy="status-label-form"]', TIMEOUT); @@ -167,7 +169,7 @@ describe("Create New Status Label", { retries: 1 }, () => { }); it("Fills out and submits form", () => { - cy.get("button").contains("Create Status").click(); + cy.get("button").contains("Create Status").click(TIMEOUT); cy.get('[data-cy="status-label-form"]', TIMEOUT); @@ -202,7 +204,7 @@ describe("Create New Status Label", { retries: 1 }, () => { cy.get('[data-cy="status-label-submit-button"]').click(); - cy.wait(["@createStatusLabel", "@getAllStatusLabels"]).spread( + cy.wait(["@createStatusLabel", "@getAllStatusLabels"], TIMEOUT).spread( (createStatusLabel, getAllStatusLabels) => { const createdStatusLabel = createStatusLabel?.response?.body?.data; const { active } = parseStatusLabels( @@ -243,10 +245,12 @@ describe("Create New Status Label", { retries: 1 }, () => { }); }); -describe("Edit Status Label", () => { - it("Open Status Label and Edit Details", { retries: 1 }, () => { +describe("Edit Status Label", { retries: 1 }, () => { + it("Open Status Label and Edit Details", () => { cy.goToWorkflowsPage(); - cy.wait(1500); + + cy.get('[data-cy="active-labels-container"]', TIMEOUT); + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') .contains(TEST_DATA?.temp1?.name) .parents('[data-cy="status-label"]') @@ -268,12 +272,12 @@ describe("Edit Status Label", () => { cy.get('input[name="color"]').parent().find("button").click(); cy.get('ul li[role="option"]').contains(TEST_DATA.edited.color).click(); - cy.get('[data-cy="status-label-submit-button"]').click(); - cy.intercept("PUT", `${ENDPOINTS?.statusLabels}/**`).as("editStatusLabel"); cy.intercept(ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - cy.wait(["@editStatusLabel", "@getAllStatusLabels"]).spread( + cy.get('[data-cy="status-label-submit-button"]').click(); + + cy.wait(["@editStatusLabel", "@getAllStatusLabels"], TIMEOUT).spread( (editStatusLabel, getAllStatusLabels) => { const targetLabelZUID = editStatusLabel.response.body.data; const updatedLabel = getAllStatusLabels.response.body.data.find( @@ -294,14 +298,19 @@ describe("Edit Status Label", () => { }); }); -describe("Re-order Status Labels", () => { +describe("Re-order Status Labels", { retries: 1 }, () => { before(() => { cy.goToWorkflowsPage(); }); - it("Drag status label to a new position", { retries: 1 }, () => { + it("Drag status label to a new position", () => { const dataTransfer = new DataTransfer(); + cy.get('[data-cy="active-labels-container"]', TIMEOUT); + + cy.intercept("PUT", ENDPOINTS.statusLabels).as("reorderStatusLabel"); + cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.get(`[data-cy="status-label"] [data-cy="status-label-drag-handle"]`) .eq(0) .trigger("dragstart", { dataTransfer }); @@ -311,10 +320,7 @@ describe("Re-order Status Labels", () => { .trigger("dragover", { dataTransfer }) .trigger("drop", { dataTransfer }); - cy.intercept("PUT", ENDPOINTS.statusLabels).as("reorderStatusLabel"); - cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); - - cy.wait(["@reorderStatusLabel", "@getAllStatusLabels"]).spread( + cy.wait(["@reorderStatusLabel", "@getAllStatusLabels"], TIMEOUT).spread( (reorderStatusLabel, getAllStatusLabels) => { const reorderedLabels = reorderStatusLabel?.request?.body?.data; const updatedLabel = getAllStatusLabels?.response?.body?.data; @@ -332,12 +338,14 @@ describe("Re-order Status Labels", () => { }); }); -describe("Deactivate Status Label", () => { +describe("Deactivate Status Label", { retries: 1 }, () => { beforeEach(() => { cy.goToWorkflowsPage(); }); - it("Deactivate using menu options", { retries: 1 }, function () { + it("Deactivate using menu options", () => { + cy.get('[data-cy="active-labels-container"]', TIMEOUT); + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') .contains(TEST_DATA?.temp2?.name) .parents('[data-cy="status-label"]') @@ -367,12 +375,15 @@ describe("Deactivate Status Label", () => { } ); cy.wait(1000); + cy.get(".notistack-Snackbar").contains( new RegExp(`Status De-activated:\\s*${TEST_DATA?.temp2?.name}`) ); }); - it("Deactivate using form button", { retries: 1 }, function () { + it("Deactivate using form button", () => { + cy.get('[data-cy="active-labels-container"]', TIMEOUT); + cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') .contains(TEST_DATA?.temp3?.name) .parents('[data-cy="status-label"]') @@ -381,13 +392,13 @@ describe("Deactivate Status Label", () => { cy.get('[data-cy="menu-item-edit"]').click(); - cy.get('[data-cy="form-deactivate-status-button"]').click(); - cy.intercept("DELETE", `${ENDPOINTS?.statusLabels}/**`).as( "deactivateStatusLabel" ); cy.intercept("GET", ENDPOINTS.allStatusLabels).as("getAllStatusLabels"); + cy.get('[data-cy="form-deactivate-status-button"]').click(); + cy.get('[data-cy="deactivation-dialog-confirm-button"]').click(); cy.wait(["@deactivateStatusLabel", "@getAllStatusLabels"]).spread( @@ -410,13 +421,13 @@ describe("Deactivate Status Label", () => { }); }); -describe("Filter Active and Deactivated Status Labels", () => { +describe("Filter Active and Deactivated Status Labels", { retries: 1 }, () => { before(() => { cy.getStatusLabels().then((data) => cy.wrap(data).as("activeDeactivatedStatusLabels") ); cy.goToWorkflowsPage(); - cy.get('input[value="deactivated"]').click(); + cy.get('input[value="deactivated"]').click(TIMEOUT); }); it("Displays active/deactivated status labels", function () { @@ -454,7 +465,7 @@ Cypress.Commands.add("goToWorkflowsPage", () => { cy.get('[data-cy="workflows-authorized-page"]', { timeout: 60_000 }); }); -Cypress.Commands.add("cleanTestData", () => { +Cypress.Commands.add("cleanTestData", function () { const testLabels = [ TEST_DATA?.new?.name, TEST_DATA?.edited?.name, From 08ab0814469bdf0e004978155b15e38f14a98ef6 Mon Sep 17 00:00:00 2001 From: geodem Date: Thu, 9 Jan 2025 09:26:31 +0800 Subject: [PATCH 55/59] cypress-test-config-experimental --- cypress.config.js | 5 ++ cypress/e2e/content/workflows.spec.js | 77 ++++++++++++++++++--------- cypress/support/commands.js | 2 +- tsconfig.json | 2 +- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/cypress.config.js b/cypress.config.js index 169a8f3dd4..39d3a04c15 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -28,4 +28,9 @@ module.exports = defineConfig({ specPattern: "cypress/e2e/**/*.{js,jsx,ts,tsx}", testIsolation: false, }, + defaultCommandTimeout: 30_000, + responseTimeout: 60_000, + retries: 1, + experimentalMemoryManagement: true, + chromeWebSecurity: false, }); diff --git a/cypress/e2e/content/workflows.spec.js b/cypress/e2e/content/workflows.spec.js index 2775672194..f4e044153b 100644 --- a/cypress/e2e/content/workflows.spec.js +++ b/cypress/e2e/content/workflows.spec.js @@ -1,6 +1,15 @@ import ContentItemPage from "./pages/ContentItemPage"; -import CONFIG from "../../../src/shell/app.config"; +import SettingsPage from "../settings/pages/SettingsPage"; import instanceZUID from "../../../src/utility/instanceZUID"; +import CONFIG from "../../../src/shell/app.config"; +import { + AUTHORIZED_ROLES, + colorMenu, +} from "../../../src/apps/settings/src/app/views/User/Workflows/constants"; + +const INSTANCE_API = `${ + CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL +}${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; const INSTANCE_API = `${ CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL @@ -72,31 +81,28 @@ describe("Content Item Workflows", () => { after(() => { // Delete test content item - cy.location("pathname").then((loc) => { - const [_, __, modelZUID, itemZUID] = loc?.split("/"); - cy.apiRequest({ - method: "DELETE", - url: `${INSTANCE_API}/content/models/${modelZUID}/items/${itemZUID}`, - }); - }); - - // Delete test labels - cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( - (response) => { - response?.data - ?.filter( - (label) => - !label?.deletedAt && - [TITLES.publishLabel, TITLES.testLabel].includes(label?.name) - ) - .forEach((label) => { - cy.apiRequest({ - url: `${INSTANCE_API}/env/labels/${label.ZUID}`, - method: "DELETE", - }); - }); - } - ); + // ContentItemPage.elements.moreMenu().should("exist").click(); + // ContentItemPage.elements.deleteItemButton().should("exist").click(); + // ContentItemPage.elements.confirmDeleteItemButton().should("exist").click(); + // cy.intercept("DELETE", "**/content/models/6-b6cde1aa9f-wftv50/items/*").as( + // "deleteContentItem" + // ); + // cy.wait("@deleteContentItem"); + + // // Delete allow publish label after test + // SettingsPage.deactivateWorkflowLabel(TITLES.testLabel); + // cy.get( + // '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' + // ) + // .contains("Random Test Label") + // .should("not.exist"); + // SettingsPage.deactivateWorkflowLabel(TITLES.publishLabel); + // cy.get( + // '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' + // ) + // .contains("Publish Approval") + // .should("not.exist"); + cy.cleanTestData(); }); it("Can add a workflow label", () => { @@ -196,3 +202,22 @@ describe("Content Item Workflows", () => { ContentItemPage.elements.contentPublishedIndicator().should("exist"); }); }); + +Cypress.Commands.add("cleanTestData", () => { + const labelsToDelete = ["Random Test Label", "Publish Approval"]; + + cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( + (response) => { + response?.data + ?.filter( + (label) => !label?.deletedAt && labelsToDelete.includes(label?.name) + ) + .forEach((label) => { + cy.apiRequest({ + url: `${INSTANCE_API}/env/labels/${label.ZUID}`, + method: "DELETE", + }); + }); + } + ); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 917707b7c4..ef6479f39a 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -43,7 +43,7 @@ Cypress.Commands.add("assertClipboardValue", (value) => { }); Cypress.Commands.add("getBySelector", (selector, ...args) => { - return cy.get(`[data-cy=${selector}]`, ...args); + return cy.get(`[data-cy=${selector}]`, { timeout: 50_000, ...args }); }); Cypress.Commands.add("blockAnnouncements", () => { diff --git a/tsconfig.json b/tsconfig.json index 230594e1fd..dfa4468e35 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src", "index.d.ts"], + "include": ["src", "index.d.ts", "cypress"], "compilerOptions": { "noImplicitAny": true, "allowJs": true, From 99385cc34cb3c5eef061d31baf7b7f7ea69be1e9 Mon Sep 17 00:00:00 2001 From: geodem Date: Thu, 9 Jan 2025 09:26:31 +0800 Subject: [PATCH 56/59] cypress-test-config-experimental --- cypress.config.js | 10 +- cypress/e2e/content/actions.spec.js | 10 +- cypress/e2e/content/workflows.spec.js | 77 +++++---------- cypress/e2e/settings/workflows.spec.js | 38 ++++---- cypress/fixtures/workflows/v1-env-labels.json | 97 +++++++++++++++++++ cypress/support/cleanup.ts | 73 ++++++++++++++ cypress/support/commands.js | 43 +++++++- cypress/support/e2e.js | 14 +++ cypress/support/index.ts | 29 ++++++ tsconfig.json | 3 +- 10 files changed, 310 insertions(+), 84 deletions(-) create mode 100644 cypress/fixtures/workflows/v1-env-labels.json create mode 100644 cypress/support/cleanup.ts create mode 100644 cypress/support/index.ts diff --git a/cypress.config.js b/cypress.config.js index 39d3a04c15..eb2ff59d6d 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -5,7 +5,7 @@ module.exports = defineConfig({ viewportWidth: 1920, viewportHeight: 1080, video: false, - defaultCommandTimeout: 15000, + env: { API_AUTH: "https://auth.api.dev.zesty.io", COOKIE_NAME: "DEV_APP_SID", @@ -28,9 +28,9 @@ module.exports = defineConfig({ specPattern: "cypress/e2e/**/*.{js,jsx,ts,tsx}", testIsolation: false, }, - defaultCommandTimeout: 30_000, - responseTimeout: 60_000, + defaultCommandTimeout: 15_000, + pageLoadTimeout: 60_000, + requestTimeout: 30_000, + responseTimeout: 80_000, retries: 1, - experimentalMemoryManagement: true, - chromeWebSecurity: false, }); diff --git a/cypress/e2e/content/actions.spec.js b/cypress/e2e/content/actions.spec.js index 1eb761b62e..bf33af1d94 100644 --- a/cypress/e2e/content/actions.spec.js +++ b/cypress/e2e/content/actions.spec.js @@ -122,7 +122,7 @@ describe("Actions in content editor", () => { cy.getBySelector("ConfirmPublishButton").click(); cy.intercept("GET", "**/publishings").as("publish"); - cy.wait("@publish"); + cy.wait("@publish", { timeout: 30_000 }); cy.getBySelector("ContentPublishedIndicator").should("exist"); }); @@ -257,9 +257,9 @@ describe("Actions in content editor", () => { // these waits are due to a delay // dealing with these specific endpoints // the local environment is slow - cy.contains("Successfully sent workflow request", { timeout: 5000 }).should( - "exist" - ); + cy.contains("Successfully sent workflow request", { + timeout: 15_000, + }).should("exist"); }); // it("Refreshes the CDN cache", () => { @@ -330,6 +330,6 @@ describe("Actions in content editor", () => { cy.getBySelector("CreateItemSaveButton").click(); - cy.contains("Created Item", { timeout: 5000 }).should("exist"); + cy.contains("Created Item", { timeout: 30_000 }).should("exist"); }); }); diff --git a/cypress/e2e/content/workflows.spec.js b/cypress/e2e/content/workflows.spec.js index f4e044153b..2775672194 100644 --- a/cypress/e2e/content/workflows.spec.js +++ b/cypress/e2e/content/workflows.spec.js @@ -1,15 +1,6 @@ import ContentItemPage from "./pages/ContentItemPage"; -import SettingsPage from "../settings/pages/SettingsPage"; -import instanceZUID from "../../../src/utility/instanceZUID"; import CONFIG from "../../../src/shell/app.config"; -import { - AUTHORIZED_ROLES, - colorMenu, -} from "../../../src/apps/settings/src/app/views/User/Workflows/constants"; - -const INSTANCE_API = `${ - CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL -}${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; +import instanceZUID from "../../../src/utility/instanceZUID"; const INSTANCE_API = `${ CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL @@ -81,28 +72,31 @@ describe("Content Item Workflows", () => { after(() => { // Delete test content item - // ContentItemPage.elements.moreMenu().should("exist").click(); - // ContentItemPage.elements.deleteItemButton().should("exist").click(); - // ContentItemPage.elements.confirmDeleteItemButton().should("exist").click(); - // cy.intercept("DELETE", "**/content/models/6-b6cde1aa9f-wftv50/items/*").as( - // "deleteContentItem" - // ); - // cy.wait("@deleteContentItem"); - - // // Delete allow publish label after test - // SettingsPage.deactivateWorkflowLabel(TITLES.testLabel); - // cy.get( - // '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - // ) - // .contains("Random Test Label") - // .should("not.exist"); - // SettingsPage.deactivateWorkflowLabel(TITLES.publishLabel); - // cy.get( - // '[data-cy="active-labels-container"] [data-cy="status-label"]:visible' - // ) - // .contains("Publish Approval") - // .should("not.exist"); - cy.cleanTestData(); + cy.location("pathname").then((loc) => { + const [_, __, modelZUID, itemZUID] = loc?.split("/"); + cy.apiRequest({ + method: "DELETE", + url: `${INSTANCE_API}/content/models/${modelZUID}/items/${itemZUID}`, + }); + }); + + // Delete test labels + cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( + (response) => { + response?.data + ?.filter( + (label) => + !label?.deletedAt && + [TITLES.publishLabel, TITLES.testLabel].includes(label?.name) + ) + .forEach((label) => { + cy.apiRequest({ + url: `${INSTANCE_API}/env/labels/${label.ZUID}`, + method: "DELETE", + }); + }); + } + ); }); it("Can add a workflow label", () => { @@ -202,22 +196,3 @@ describe("Content Item Workflows", () => { ContentItemPage.elements.contentPublishedIndicator().should("exist"); }); }); - -Cypress.Commands.add("cleanTestData", () => { - const labelsToDelete = ["Random Test Label", "Publish Approval"]; - - cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( - (response) => { - response?.data - ?.filter( - (label) => !label?.deletedAt && labelsToDelete.includes(label?.name) - ) - .forEach((label) => { - cy.apiRequest({ - url: `${INSTANCE_API}/env/labels/${label.ZUID}`, - method: "DELETE", - }); - }); - } - ); -}); diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 1ecc8b48c8..13ff309ede 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -7,7 +7,7 @@ import { const TIMEOUT = { timeout: 40_000 }; -const INSTANCE_API = `${ +const INSTANCE_API_ENDPOINT = `${ CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL }${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; @@ -93,13 +93,13 @@ const TEST_DATA = { }; before(() => { - cy.cleanTestData(); + // cy.cleanTestData(); cy.createTestData(); }); -after(() => { - cy.cleanTestData(); -}); +// after(() => { +// cy.cleanTestData(); +// }); describe("Restricted User", { retries: 1 }, () => { it("displays restricted access message and admin profiles", () => { @@ -474,27 +474,25 @@ Cypress.Commands.add("cleanTestData", function () { TEST_DATA?.temp3?.name, ]; - cy.apiRequest({ url: `${INSTANCE_API}/env/labels?showDeleted=true` }).then( - (response) => { - response?.data - ?.filter( - (label) => !label?.deletedAt && testLabels.includes(label?.name) - ) - .forEach((label) => { - cy.apiRequest({ - url: `${INSTANCE_API}/env/labels/${label.ZUID}`, - method: "DELETE", - }); + cy.apiRequest({ + url: `${INSTANCE_API_ENDPOINT}/env/labels?showDeleted=true`, + }).then((response) => { + response?.data + ?.filter((label) => !label?.deletedAt && testLabels.includes(label?.name)) + .forEach((label) => { + cy.apiRequest({ + url: `${INSTANCE_API_ENDPOINT}/env/labels/${label.ZUID}`, + method: "DELETE", }); - } - ); + }); + }); }); Cypress.Commands.add("createTestData", () => { const testLabels = [TEST_DATA?.temp1, TEST_DATA?.temp2, TEST_DATA?.temp3]; testLabels.forEach((label) => { cy.apiRequest({ - url: `${INSTANCE_API}/env/labels`, + url: `${INSTANCE_API_ENDPOINT}/env/labels`, method: "POST", body: { ...label, @@ -507,7 +505,7 @@ Cypress.Commands.add("createTestData", () => { Cypress.Commands.add("getStatusLabels", () => { return cy .apiRequest({ - url: `${INSTANCE_API}/env/labels?showDeleted=true`, + url: `${INSTANCE_API_ENDPOINT}/env/labels?showDeleted=true`, }) .then((response) => { return parseStatusLabels(response?.data); diff --git a/cypress/fixtures/workflows/v1-env-labels.json b/cypress/fixtures/workflows/v1-env-labels.json new file mode 100644 index 0000000000..37cf5681b8 --- /dev/null +++ b/cypress/fixtures/workflows/v1-env-labels.json @@ -0,0 +1,97 @@ +{ + "default": { + "_meta": { + "timestamp": "2025-01-10T13:03:18.129380816Z", + "totalResults": 3, + "start": 0, + "offset": 0, + "limit": 3 + }, + "data": [ + { + "ZUID": "36-14b315-4pp20v3d", + "name": "Approved", + "description": "Approved", + "color": "#12b76a", + "allowPublish": false, + "sort": 3, + "addPermissionRoles": null, + "removePermissionRoles": null, + "createdByUserZUID": "55-8094cbd789-42sw0c", + "updatedByUserZUID": "55-8094cbd789-42sw0c", + "createdAt": "2024-12-04T06:34:37Z", + "updatedAt": "2024-12-04T06:34:37Z" + }, + { + "ZUID": "36-14b315-d24ft", + "name": "Draft", + "description": "Content item is only available to preview in stage", + "color": "#0BA5EC", + "allowPublish": false, + "sort": 1, + "addPermissionRoles": null, + "removePermissionRoles": null, + "createdByUserZUID": "55-8094cbd789-42sw0c", + "updatedByUserZUID": "55-8094cbd789-42sw0c", + "createdAt": "2024-12-04T06:34:27Z", + "updatedAt": "2025-01-10T12:17:32Z" + }, + { + "ZUID": "36-n33d5-23v13w", + "name": "Needs Review", + "description": "Ready for review", + "color": "#ff5c08", + "allowPublish": false, + "sort": 2, + "addPermissionRoles": null, + "removePermissionRoles": null, + "createdByUserZUID": "55-8094cbd789-42sw0c", + "updatedByUserZUID": "55-8094cbd789-42sw0c", + "createdAt": "2024-12-04T06:34:32Z", + "updatedAt": "2025-01-10T12:17:32Z" + } + ] + }, + "test": { + "new": { + "name": "Test__new", + "description": "Test__new Description", + "color": "Grey", + "addPermissionRoles": "Admin", + "removePermissionRoles": "Admin", + "allowPublish": true + }, + "edited": { + "name": "Test__edited", + "description": "Test__edited Description", + "color": "Pink", + "addPermissionRoles": [], + "removePermissionRoles": [], + "allowPublish": true + }, + "temp1": { + "name": "Test__temp1", + "description": "Test__temp1 Description", + "color": "Red", + "addPermissionRoles": [], + "removePermissionRoles": [], + "allowPublish": false + }, + "temp2": { + "name": "Test__temp2", + "description": "Test__temp2 Description", + "color": "Yellow", + "addPermissionRoles": [], + "removePermissionRoles": [], + "allowPublish": false + }, + "temp3": { + "name": "Test__temp3", + "description": "Test__temp3 Description", + "color": "Purple", + "addPermissionRoles": [], + "removePermissionRoles": [], + "allowPublish": false + } + } +} diff --git a/cypress/support/cleanup.ts b/cypress/support/cleanup.ts new file mode 100644 index 0000000000..fad91b2e91 --- /dev/null +++ b/cypress/support/cleanup.ts @@ -0,0 +1,73 @@ +import instanceZUID from "../../src/utility/instanceZUID"; +import CONFIG from "../../src/shell/app.config"; +import { StatusLabelQuery } from "src/shell/services/types"; + +const API_ENDPOINTS = { + devInstance: `${ + CONFIG[process.env.NODE_ENV as keyof typeof CONFIG]?.API_INSTANCE_PROTOCOL + }${instanceZUID}${ + CONFIG[process.env.NODE_ENV as keyof typeof CONFIG]?.API_INSTANCE + }`, +}; + +export function preCleanUp(token: string) { + statusLabelCleanUp(token); +} + +// ========================= CLEANUP FUNCTIONS STARTS HERE =========================// + +// Status Labels +function statusLabelCleanUp(token: string) { + const DEFAULT_STATUS_LABELS_ZUIDS = [ + "36-14b315-4pp20v3d", + "36-14b315-d24ft", + "36-n33d5-23v13w", + ]; + const DEFAULT_STATUS_LABELS_NAMES = ["Approved", "Needs Review", "Draft"]; + sendRequest({ + method: "GET", + url: `${API_ENDPOINTS.devInstance}/env/labels?showDeleted=true`, + token: token, + }).then((response) => { + response?.data + ?.filter( + (resData: StatusLabelQuery) => + !resData?.deletedAt && + !DEFAULT_STATUS_LABELS_NAMES.includes(resData?.name) && + !DEFAULT_STATUS_LABELS_ZUIDS.includes(resData?.ZUID) + ) + .forEach((labelForDelete: StatusLabelQuery) => { + sendRequest({ + url: `${API_ENDPOINTS.devInstance}/env/labels/${labelForDelete.ZUID}`, + method: "DELETE", + token: token, + }); + }); + }); +} + +// ========================= CLEANUP FUNCTIONS ENDS HERE =========================// + +function sendRequest({ + method = "GET", + url = "", + body = undefined, + token = "", +}: { + method?: string; + url: string; + body?: any; + token: string; +}) { + return cy + .request({ + method, + url, + headers: { authorization: `Bearer ${token}` }, + ...(body ? { body: body } : {}), + }) + .then((response) => ({ + status: response?.isOkStatusCode ? "success" : "error", + data: response?.body?.data, + })); +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js index ef6479f39a..944014bed1 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,3 +1,18 @@ +import instanceZUID from "../../src/utility/instanceZUID"; +import CONFIG from "../../src/shell/app.config"; + +const INSTANCE_API_ENDPOINT = `${ + CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL +}${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; + +const DEFAULT_STATUS_LABELS_ZUIDS = [ + "36-14b315-4pp20v3d", + "36-14b315-d24ft", + "36-n33d5-23v13w", +]; + +const DEFAULT_STATUS_LABELS_NAMES = ["Approved", "Needs Review", "Draft"]; + Cypress.Commands.add("login", () => { const formBody = new FormData(); @@ -8,7 +23,6 @@ Cypress.Commands.add("login", () => { .request({ url: `${Cypress.env("API_AUTH")}/login`, method: "POST", - credentials: "include", body: formBody, }) .then(async (res) => { @@ -16,6 +30,9 @@ Cypress.Commands.add("login", () => { // We need the cookie value returned reset so it is unsecure and // accessible by javascript cy.setCookie(Cypress.env("COOKIE_NAME"), response.meta.token); + }) + .then(() => { + return cy.get("body"); }); }); @@ -43,7 +60,7 @@ Cypress.Commands.add("assertClipboardValue", (value) => { }); Cypress.Commands.add("getBySelector", (selector, ...args) => { - return cy.get(`[data-cy=${selector}]`, { timeout: 50_000, ...args }); + return cy.get(`[data-cy=${selector}]`, { timeout: 40_000, ...args }); }); Cypress.Commands.add("blockAnnouncements", () => { @@ -71,3 +88,25 @@ Cypress.Commands.add( }); } ); + +Cypress.Commands.add("workflowStatusLabelCleanUp", function () { + cy.apiRequest({ + url: `${INSTANCE_API_ENDPOINT}/env/labels?showDeleted=true`, + }).then((response) => { + console.debug("workflowStatusLabelCleanUp | response LABELS: ", response); + + response?.data + ?.filter( + (label) => + !label?.deletedAt && + !DEFAULT_STATUS_LABELS_NAMES.includes(label?.name) && + !DEFAULT_STATUS_LABELS_ZUIDS.includes(label?.ZUID) + ) + .forEach((label) => { + cy.apiRequest({ + url: `${INSTANCE_API_ENDPOINT}/env/labels/${label.ZUID}`, + method: "DELETE", + }); + }); + }); +}); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 62057a245e..57cef4de76 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -16,6 +16,9 @@ // Import commands.js using ES2015 syntax: import "./commands"; import "cypress-iframe"; +import { preCleanUp } from "./cleanup"; + +let preCleanupHasExeciuted = false; // @see https://docs.cypress.io/api/cypress-api/cookies.html#Set-global-default-cookies // Cypress.Cookies.defaults({ @@ -45,6 +48,17 @@ before(() => { // Blocks the api call to render the announcement popup cy.blockAnnouncements(); + + if (!preCleanupHasExeciuted) { + cy.getCookie(Cypress.env("COOKIE_NAME")) + .then((cookie) => { + preCleanUp(cookie?.value); + }) + .then(() => { + // SET TO FALSE TO EXECUTE CLEANUP BEFORE EACH TEST + preCleanupHasExeciuted = false; + }); + } }); // Before each test in spec diff --git a/cypress/support/index.ts b/cypress/support/index.ts new file mode 100644 index 0000000000..472f0d5791 --- /dev/null +++ b/cypress/support/index.ts @@ -0,0 +1,29 @@ +import "./commands"; + +declare global { + namespace Cypress { + interface Chainable { + /** + * Custom command to select DOM element by data-cy attribute. + * @example cy.dataCy('greeting') + */ + waitOn(path: string, cb: () => void): Chainable>; + login(): Chainable>; + getBySelector( + selector: string, + ...args: any[] + ): Chainable>; + blockLock(): Chainable>; + assertClipboardValue(value: string): Chainable>; + blockAnnouncements(): Chainable>; + apiRequest( + options: Partial + ): Chainable>; + workflowStatusLabelCleanUp(): Chainable>; + cleanTestData(): Chainable>; + createTestData(): Chainable>; + goToWorkflowsPage(): Chainable>; + getStatusLabel(): Chainable>; + } + } +} diff --git a/tsconfig.json b/tsconfig.json index dfa4468e35..25243003cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "jsx": "react-jsx", "moduleResolution": "node", "sourceMap": true, - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "baseUrl": "./" } } From 4edd15be3183eebc5d7df4446bd539c7bd137d5b Mon Sep 17 00:00:00 2001 From: geodem Date: Wed, 15 Jan 2025 00:20:24 +0800 Subject: [PATCH 57/59] updated cypress config, added custom commands for data cleanup, updated test cases --- cypress.config.js | 5 +- cypress/e2e/blocks/tests/blocks.spec.js | 18 +- cypress/e2e/content/actions.spec.js | 23 +- cypress/e2e/content/item-list-table.spec.js | 5 +- cypress/e2e/content/navigation.spec.js | 6 +- cypress/e2e/content/workflows.spec.js | 1 - cypress/e2e/schema/field.spec.js | 515 ++++++++------------ cypress/e2e/schema/models.spec.js | 99 +++- cypress/e2e/settings/workflows.spec.js | 36 +- cypress/jsconfig.json | 7 + cypress/support/cleanup.ts | 73 --- cypress/support/commands.js | 94 +++- cypress/support/e2e.js | 14 - cypress/support/index.d.ts | 51 ++ cypress/support/index.ts | 29 -- tsconfig.json | 2 +- 16 files changed, 454 insertions(+), 524 deletions(-) create mode 100644 cypress/jsconfig.json delete mode 100644 cypress/support/cleanup.ts create mode 100644 cypress/support/index.d.ts delete mode 100644 cypress/support/index.ts diff --git a/cypress.config.js b/cypress.config.js index eb2ff59d6d..4d01aa5327 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -29,8 +29,7 @@ module.exports = defineConfig({ testIsolation: false, }, defaultCommandTimeout: 15_000, - pageLoadTimeout: 60_000, - requestTimeout: 30_000, + pageLoadTimeout: 80_000, + requestTimeout: 40_000, responseTimeout: 80_000, - retries: 1, }); diff --git a/cypress/e2e/blocks/tests/blocks.spec.js b/cypress/e2e/blocks/tests/blocks.spec.js index f4292a274e..c46c03f770 100644 --- a/cypress/e2e/blocks/tests/blocks.spec.js +++ b/cypress/e2e/blocks/tests/blocks.spec.js @@ -7,6 +7,7 @@ const CypressTestVariant = "Cypress Test Variant"; describe("All Blocks Tests", () => { before(() => { + cy.deleteContentModels(["Cypress Test Block", "Cypress Test Variant"]); AllBlocksPage.visit(); }); @@ -28,11 +29,16 @@ describe("All Blocks Tests", () => { AllBlocksPage.createBlock(CypressTestBlock); cy.contains(CypressTestBlock).should("exist"); SchemaPage.visit(); + cy.intercept("POST", "**/v1/content/models/**").as("createModel"); + cy.intercept("GET", "**/v1/content/models/*/fields?showDeleted=true").as( + "getModels" + ); SchemaPage.addSingleLineTextFieldWithDefaultValue( CypressTestBlock, "Foo", "Default Foo" ); + cy.wait(["@createModel", "@getModels"], { timeout: 60_000 }); AllBlocksPage.visit(); }); @@ -50,16 +56,22 @@ describe("All Blocks Tests", () => { }); it("navigates to block detail page", () => { - cy.contains(CypressTestBlock).click(); - cy.contains("Start Creating Variants Now").should("exist"); + cy.contains(CypressTestBlock).click({ timeout: 30_000 }); + cy.contains("Start Creating Variants Now", { timeout: 30_000 }).should( + "exist" + ); }); it("creates a variant with default values", () => { + AllBlocksPage.visit(); cy.contains(CypressTestBlock).click(); BlockPage.createVariant(CypressTestVariant); cy.contains( new RegExp(`${CypressTestBlock}:\\s*${CypressTestVariant}`) ).should("exist"); - cy.get('input[name="foo"]').should("have.value", "Default Foo"); + cy.get('input[name="foo"]', { timeout: 30_000 }).should( + "have.value", + "Default Foo" + ); }); }); diff --git a/cypress/e2e/content/actions.spec.js b/cypress/e2e/content/actions.spec.js index bf33af1d94..e3226a1a29 100644 --- a/cypress/e2e/content/actions.spec.js +++ b/cypress/e2e/content/actions.spec.js @@ -11,6 +11,10 @@ const yesterdayTimestamp = moment() describe("Actions in content editor", () => { const timestamp = Date.now(); + before(() => { + cy.cleanStatusLabels(); + }); + it("Must not save when missing required Field", () => { cy.waitOn("/v1/content/models*", () => { cy.visit("/content/6-556370-8sh47g/7-82a5c7ffb0-07vj1c"); @@ -83,7 +87,7 @@ describe("Actions in content editor", () => { cy.get("#12-f8efe4e0f5-xj7pj6 input").should("not.exist"); // Make an edit to enable save button - cy.get("#12-849844-t8v5l6 input").clear().type(timestamp); + cy.get("#12-849844-t8v5l6 input").clear().type(timestamp.toString()); // save to api cy.waitOn( @@ -95,7 +99,6 @@ describe("Actions in content editor", () => { cy.get("[data-cy=toast]").contains("Item Saved"); }); - it("Saves homepage item metadata", () => { cy.waitOn("/v1/content/models*", () => { cy.visit("/content/6-a1a600-k0b6f0/7-a1be38-1b42ht/meta"); @@ -119,9 +122,8 @@ describe("Actions in content editor", () => { it("Publishes an item", () => { cy.getBySelector("PublishButton").click(); cy.getBySelector("ConfirmPublishModal").should("exist"); - cy.getBySelector("ConfirmPublishButton").click(); - cy.intercept("GET", "**/publishings").as("publish"); + cy.getBySelector("ConfirmPublishButton").click(); cy.wait("@publish", { timeout: 30_000 }); cy.getBySelector("ContentPublishedIndicator").should("exist"); @@ -212,17 +214,18 @@ describe("Actions in content editor", () => { cy.visit("/content/6-a1a600-k0b6f0/new"); }); - cy.get("input[name=title]", { timeout: 5000 }).click().type(timestamp); + cy.get("input[name=title]") + .click({ timeout: 30_000 }) + .type(timestamp.toString()); cy.getBySelector("ManualMetaFlow").click(); cy.getBySelector("metaDescription") .find("textarea") .first() - .type(timestamp); + .type(timestamp.toString()); cy.getBySelector("CreateItemSaveButton").click(); - cy.contains("Created Item", { timeout: 5000 }).should("exist"); + cy.contains("Created Item", { timeout: 30_000 }).should("exist"); }); - it("Saved item becomes publishable", () => { cy.get("#PublishButton").should("exist"); }); @@ -232,7 +235,7 @@ describe("Actions in content editor", () => { cy.visit("/content/6-a1a600-k0b6f0"); }); - cy.contains(timestamp, { timeout: 5000 }).should("exist"); + cy.contains(timestamp, { timeout: 30_000 }).should("exist"); }); it("Deletes an item", () => { @@ -258,7 +261,7 @@ describe("Actions in content editor", () => { // dealing with these specific endpoints // the local environment is slow cy.contains("Successfully sent workflow request", { - timeout: 15_000, + timeout: 30_000, }).should("exist"); }); diff --git a/cypress/e2e/content/item-list-table.spec.js b/cypress/e2e/content/item-list-table.spec.js index 3433144a39..ebee13d381 100644 --- a/cypress/e2e/content/item-list-table.spec.js +++ b/cypress/e2e/content/item-list-table.spec.js @@ -6,10 +6,11 @@ describe("Content item list table", () => { }); }); - cy.getBySelector("SingleRelationshipCell", { timeout: 10000 }) + cy.getBySelector("SingleRelationshipCell") .first() .contains( - "5 Tricks to Teach Your Pitbull: Fun & Easy Tips for You & Your Dog!" + "5 Tricks to Teach Your Pitbull: Fun & Easy Tips for You & Your Dog!", + { timeout: 30_000 } ); }); }); diff --git a/cypress/e2e/content/navigation.spec.js b/cypress/e2e/content/navigation.spec.js index 7d618e9fa3..0134ce5f0d 100644 --- a/cypress/e2e/content/navigation.spec.js +++ b/cypress/e2e/content/navigation.spec.js @@ -1,3 +1,7 @@ +const LABELS = { + cypressTest: "Cypress test (Group with visible fields in list)", +}; + describe("Navigation through content editor", () => { before(() => { cy.waitOn("/v1/env/nav", () => { @@ -24,7 +28,7 @@ describe("Navigation through content editor", () => { cy.getBySelector("create_new_content_item_dialog").should("exist"); cy.getBySelector("create_new_content_item_input") .find("input") - .type("cypress"); + .type(LABELS.cypressTest); cy.get(".MuiAutocomplete-listbox .MuiAutocomplete-option") .first() .should("exist") diff --git a/cypress/e2e/content/workflows.spec.js b/cypress/e2e/content/workflows.spec.js index 2775672194..e54ccaefa8 100644 --- a/cypress/e2e/content/workflows.spec.js +++ b/cypress/e2e/content/workflows.spec.js @@ -69,7 +69,6 @@ describe("Content Item Workflows", () => { cy.visit(`/content/6-b6cde1aa9f-wftv50/${response.data?.ZUID}`); }); }); - after(() => { // Delete test content item cy.location("pathname").then((loc) => { diff --git a/cypress/e2e/schema/field.spec.js b/cypress/e2e/schema/field.spec.js index b4a6cf4521..23065bd275 100644 --- a/cypress/e2e/schema/field.spec.js +++ b/cypress/e2e/schema/field.spec.js @@ -1,3 +1,6 @@ +import instanceZUID from "../../../src/utility/instanceZUID"; +import CONFIG from "../../../src/shell/app.config"; + const SELECTORS = { ADD_FIELD_BTN: "AddFieldBtn", ADD_FIELD_BTN_END_OF_LIST: "EndOfListAddFieldBtn", @@ -55,72 +58,73 @@ const SELECTORS = { MAX_CHARACTER_ERROR_MSG: "MaxCharacterErrorMsg", }; +const ENDPOINT = `${ + CONFIG[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL +}${instanceZUID}${CONFIG[process.env.NODE_ENV]?.API_INSTANCE}`; + +const TIMEOUT = { timeout: 60_000 }; + +const TEST_DATA = { + schema: { + description: "Cypress Test - Fields Description", + label: "Cypress Test - Fields", + type: "templateset", + name: "cypress_test___fields", + listed: true, + }, +}; + /** * Schema Fields E2E tests */ describe("Schema: Fields", () => { - const timestamp = Date.now(); + const suffix = "field"; before(() => { - cy.waitOn( - "/v1/content/models/6-ce80dbfe90-ptjpm6/fields?showDeleted=true", - () => { - cy.waitOn("/bin/1-6c9618c-r26pt/groups", () => { - cy.waitOn("/v1/content/models", () => { - cy.visit("/schema/6-ce80dbfe90-ptjpm6/fields"); - - cy.getBySelector("create_new_content_item").click(); - - cy.contains("Multi Page Model").click(); - cy.contains("Next").click(); - cy.contains("Display Name") - .next() - .type(`Cypress Test Model ${timestamp}`); - cy.get(".MuiDialog-container").within(() => { - cy.contains("Create Model").click(); - }); - cy.intercept("POST", "/models"); - cy.intercept("GET", "/models"); - }); - }); + cy.apiRequest({ url: `${ENDPOINT}/content/models` }).then((resData) => { + const schhemaModel = resData?.data?.find( + (item) => item?.label === TEST_DATA?.schema?.label + ); + if (!!schhemaModel) { + cy.deleteContentModels([TEST_DATA?.schema?.label]); } - ); + cy.createContentModel(TEST_DATA?.schema); + }); + }); + + after(() => { + cy.deleteContentModels([TEST_DATA?.schema?.label]); + }); + + beforeEach(() => { + openSchemaModel(TEST_DATA?.schema?.label); }); it("Opens Add Field Modal via button click", () => { - cy.wait(3000); // Open the modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); }); it("Creates a Single Line Text field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Text ${timestamp}`; - const fieldName = `text_${timestamp}`; + const fieldLabel = `Text ${suffix}`; + const fieldName = `text_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); - cy.get("input[name='label']") - .should("exist") - .should("have.value", fieldLabel); - cy.get("input[name='name']") - .should("exist") - .should("have.value", fieldName); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); + cy.get("input[name='label']").should("have.value", fieldLabel); + cy.get("input[name='name']").should("have.value", fieldName); // Navigate to rules tab and add default value cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); @@ -145,50 +149,38 @@ describe("Schema: Fields", () => { cy.getBySelector(SELECTORS.MIN_CHARACTER_ERROR_MSG).should("not.exist"); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a Dropdown field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Dropdown ${timestamp}`; - const fieldName = `dropdown_${timestamp}`; + const fieldLabel = `Dropdown ${suffix}`; + const fieldName = `dropdown_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Dropdown field - cy.getBySelector(SELECTORS.FIELD_SELECT_DROPDOWN).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_DROPDOWN).click(TIMEOUT); // Input field label and duplicate dropdown options - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); - cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_0`) - .should("exist") - .type("test"); - cy.getBySelector(SELECTORS.DROPDOWN_ADD_OPTION).should("exist").click(); - cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_1`) - .should("exist") - .type("test"); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); + cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_0`).type("test"); + cy.getBySelector(SELECTORS.DROPDOWN_ADD_OPTION).click(); + cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_1`).type("test"); // Verify that duplicate dropdown values causes an error - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(`${SELECTORS.ERROR_MESSAGE_OPTION_VALUE}_1`).should( "exist" ); // Delete duplicate option - cy.getBySelector(`${SELECTORS.DROPDOWN_DELETE_OPTION}_1`) - .should("exist") - .click(); + cy.getBySelector(`${SELECTORS.DROPDOWN_DELETE_OPTION}_1`).click(); // Navigate to rules tab and add default value cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); @@ -204,72 +196,56 @@ describe("Schema: Fields", () => { .should("have.value", "test"); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a Media field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Media ${timestamp}`; - const fieldName = `media_${timestamp}`; + const fieldLabel = `Media ${suffix}`; + const fieldName = `media_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Media field - cy.getBySelector(SELECTORS.FIELD_SELECT_MEDIA).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_MEDIA).click(TIMEOUT); // Input field label - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Navigate to rules tab and enable media limit and folder lock - cy.getBySelector(SELECTORS.RULES_TAB_BTN).should("exist").click(); - cy.getBySelector(SELECTORS.MEDIA_RULES_TAB).should("exist").click(); - cy.getBySelector(SELECTORS.MEDIA_CHECKBOX_LIMIT).should("exist").click(); - cy.getBySelector(SELECTORS.MEDIA_CHECKBOX_LOCK).should("exist").click(); + cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); + cy.getBySelector(SELECTORS.MEDIA_RULES_TAB).click(); + cy.getBySelector(SELECTORS.MEDIA_CHECKBOX_LIMIT).click(); + cy.getBySelector(SELECTORS.MEDIA_CHECKBOX_LOCK).click(); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a Boolean field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Boolean ${timestamp}`; - const fieldName = `boolean_${timestamp}`; + const fieldLabel = `Boolean ${suffix}`; + const fieldName = `boolean_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Boolean field - cy.getBySelector(SELECTORS.FIELD_SELECT_BOOLEAN).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_BOOLEAN).click(TIMEOUT); // Input field label and option labels - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); - cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_0`) - .should("exist") - .type("Test option 1"); - cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_1`) - .should("exist") - .type("Test option 2"); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); + cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_0`).type("Test option 1"); + cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_1`).type("Test option 2"); // Verify that delete option button does not exist cy.getBySelector(`${SELECTORS.DROPDOWN_DELETE_OPTION}_0`).should( @@ -292,48 +268,34 @@ describe("Schema: Fields", () => { .should("have.attr", "aria-pressed", "true"); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a One-to-one relationship field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `One to One ${timestamp}`; - const fieldName = `one_to_one_${timestamp}`; + const fieldLabel = `One to One ${suffix}`; + const fieldName = `one_to_one_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select one-to-one relationship field - cy.getBySelector(SELECTORS.FIELD_SELECT_ONE_TO_ONE).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_ONE_TO_ONE).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Select a related model - cy.getBySelector(SELECTORS.AUTOCOMPLETE_MODEL_ZUID) - .should("exist") - .type("cypress"); + cy.getBySelector(SELECTORS.AUTOCOMPLETE_MODEL_ZUID).type("cypress"); cy.get("[role=listbox] [role=option]").first().click(); - cy.wait("@getFields"); - - cy.wait(3000); - // Select a related field - cy.getBySelector(SELECTORS.AUTOCOMPLETE_FIELED_ZUID) - .should("exist") - .click(); - cy.get("[role=listbox] [role=option]").first().click(); + cy.getBySelector(SELECTORS.AUTOCOMPLETE_FIELED_ZUID).click(TIMEOUT); + cy.get("[role=listbox] [role=option]").first().click(TIMEOUT); // Navigate to rules tab and add default value cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); @@ -342,7 +304,7 @@ describe("Schema: Fields", () => { // enter a default value cy.getBySelector(SELECTORS.DEFAULT_VALUE_INPUT).click(); // Select the option - cy.get("[role=listbox] [role=option]").first().click(); + cy.get("[role=listbox] [role=option]").first().click(TIMEOUT); // verify that the default value is set cy.getBySelector(SELECTORS.DEFAULT_VALUE_INPUT) .find("input") @@ -350,34 +312,30 @@ describe("Schema: Fields", () => { cy.getBySelector(SELECTORS.DEFAULT_VALUE_CHECKBOX).click(); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a currency field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Currency ${timestamp}`; - const fieldName = `currency_${timestamp}`; + const fieldLabel = `Currency ${suffix}`; + const fieldName = `currency_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select one-to-one relationship field - cy.getBySelector(SELECTORS.FIELD_SELECT_CURRENCY).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_CURRENCY).click(TIMEOUT); // Select default currency - cy.getBySelector(SELECTORS.AUTOCOMPLETE_FIELD_CURRENCY).type("phil"); - cy.get("[role=listbox] [role=option]").first().click(); + cy.getBySelector(SELECTORS.AUTOCOMPLETE_FIELD_CURRENCY).type("philippine"); + cy.get("[role=listbox] [role=option]").first().click(TIMEOUT); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Navigate to rules tab and add default value cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); @@ -388,63 +346,49 @@ describe("Schema: Fields", () => { // Verify default currency cy.getBySelector(SELECTORS.DEFAULT_VALUE_INPUT).contains("PHP"); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a field via add another field button", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - const values = { number: { - label: `Number ${timestamp}`, - name: `number_${timestamp}`, + label: `Number ${suffix}`, + name: `number_${suffix}`, }, internal_link: { - label: `Internal Link ${timestamp}`, - name: `internal_link_${timestamp}`, + label: `Internal Link ${suffix}`, + name: `internal_link_${suffix}`, }, }; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select number field - cy.getBySelector(SELECTORS.FIELD_SELECT_NUMBER).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_NUMBER).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL) - .should("exist") - .type(values.number.label); + cy.getBySelector(SELECTORS.INPUT_LABEL).type(values.number.label); // Click add another field button - cy.getBySelector(SELECTORS.ADD_ANOTHER_FIELD_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_ANOTHER_FIELD_BTN).click(); // Select internal link field cy.getBySelector(SELECTORS.FIELD_SELECTION).should("exist"); - cy.getBySelector(SELECTORS.FIELD_SELECT_INTERNAL_LINK) - .should("exist") - .click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_INTERNAL_LINK).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL) - .should("exist") - .type(values.internal_link.label); + cy.getBySelector(SELECTORS.INPUT_LABEL).type(values.internal_link.label); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Verify that fields were created cy.getBySelector(`Field_${values.number.name}`); cy.getBySelector(`Field_${values.internal_link.name}`); @@ -452,106 +396,91 @@ describe("Schema: Fields", () => { it("Shows error messages during field creation", () => { // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); // Verify that error messages are shown cy.getBySelector(SELECTORS.ERROR_MESSAGE_LABEL).should("exist"); cy.getBySelector(SELECTORS.ERROR_MESSAGE_NAME).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); }); it("Opens Add Field Modal via end of list button", () => { // Click end of list button - cy.getBySelector(SELECTORS.ADD_FIELD_BTN_END_OF_LIST) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN_END_OF_LIST).click(TIMEOUT); // Verify modal cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); }); it("Opens Add Field Modal via in between field button", () => { // Click in-between field button - cy.getBySelector(SELECTORS.ADD_FIELD_BTN_IN_BETWEEN) - .first() - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN_IN_BETWEEN).first().click(TIMEOUT); // Verify modal cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); }); it("Switches tabs in Add Field Modal", () => { // Open the modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select single text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Verify that details tab is loaded cy.getBySelector(SELECTORS.DETAILS_TAB).should("exist"); // Click Learn tab - cy.getBySelector(SELECTORS.LEARN_TAB_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.LEARN_TAB_BTN).click(); cy.getBySelector(SELECTORS.LEARN_TAB).should("exist"); // Click Rules tab - cy.getBySelector(SELECTORS.RULES_TAB_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); }); it("Can navigate back to fields selection view", () => { // Open the modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select single text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Click the back button - cy.getBySelector(SELECTORS.BACK_TO_FIELD_SELECTION_BTN) - .should("exist") - .click(); + cy.getBySelector(SELECTORS.BACK_TO_FIELD_SELECTION_BTN).click(); // Verify that field selection screen is loaded cy.getBySelector(SELECTORS.FIELD_SELECTION).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); }); it("Can filter fields in field selection view", () => { // Open the modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Verify that field selection screen is loaded @@ -559,247 +488,209 @@ describe("Schema: Fields", () => { // Filter results cy.getBySelector(SELECTORS.FIELD_SELECTION_FILTER).as("fieldFilter"); - cy.get("@fieldFilter").should("exist").type("dropdown"); + cy.get("@fieldFilter").type("dropdown"); // Verify cy.getBySelector(SELECTORS.FIELD_SELECT_DROPDOWN).should("exist"); // Enter random string - cy.get("@fieldFilter").should("exist").type("asdasdasdasd"); + cy.get("@fieldFilter").type("asdasdasdasd"); // Show no results cy.getBySelector(SELECTORS.FIELD_SELECTION_EMPTY).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); }); it("Can filter fields in fields list", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Field to filter ${timestamp}`; - const fieldName = `field_to_filter_${timestamp}`; + const fieldLabel = `Field to filter ${suffix}`; + const fieldName = `field_to_filter_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select a field - cy.getBySelector(SELECTORS.FIELD_SELECT_NUMBER).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_NUMBER).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); - cy.get("input[name='label']") - .should("exist") - .should("have.value", fieldLabel); - cy.get("input[name='name']") - .should("exist") - .should("have.value", fieldName); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); + cy.get("input[name='label']", TIMEOUT).should("have.value", fieldLabel); + cy.get("input[name='name']", TIMEOUT).should("have.value", fieldName); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - cy.getBySelector(SELECTORS.FIELDS_LIST_FILTER).as("fieldListFilter"); // Filter fields - cy.get("@fieldListFilter").should("exist").type("field to filter"); + cy.get("@fieldListFilter").type("field to filter"); // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); // Enter a random filter keyword - cy.get("@fieldListFilter").should("exist").type("askljfkljfklsdjf"); + cy.get("@fieldListFilter").type("askljfkljfklsdjf"); // Field should not exist cy.getBySelector(`Field_${fieldName}`).should("not.exist"); cy.getBySelector(SELECTORS.FIELDS_LIST_NO_RESULTS).should("exist"); // Clear filter keyword - cy.get("@fieldListFilter").should("exist").type("{selectall} {backspace}"); + cy.get("@fieldListFilter").type("{selectall} {backspace}"); }); it("Can update a field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - cy.intercept("/v1/content/models/**").as("updateField"); - - const origFieldLabel = `Update me ${timestamp}`; - const updatedFieldLabel = `Rename field ${timestamp}`; - const fieldName = `update_me_${timestamp}`; + const origFieldLabel = `Update me ${suffix}`; + const updatedFieldLabel = `Rename field ${suffix}`; + const fieldName = `update_me_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL) - .should("exist") - .type(origFieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).type(origFieldLabel); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); // Open update modal - cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`) - .should("exist") - .click(); - cy.getBySelector(`${SELECTORS.FIELD_DROPDOWN_EDIT}_${fieldName}`) - .should("exist") - .click(); + cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`).click(); + cy.getBySelector(`${SELECTORS.FIELD_DROPDOWN_EDIT}_${fieldName}`).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Update field label - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").clear(); - cy.getBySelector(SELECTORS.INPUT_LABEL) - .should("exist") - .type(updatedFieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear(); + cy.getBySelector(SELECTORS.INPUT_LABEL).type(updatedFieldLabel); // Save changes - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@updateField"); - cy.wait("@getFields"); - // Verify field name - cy.getBySelector(`FieldLabel_${fieldName}`) - .should("exist") - .should("contain", updatedFieldLabel); + cy.getBySelector(`FieldLabel_${fieldName}`).should( + "contain", + updatedFieldLabel + ); }); it("Can deactivate & reactivate a field via dropdown menu", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - cy.intercept("/v1/content/models/**").as("updateField"); - - const fieldLabel = `Deactivate me ${timestamp}`; - const fieldName = `deactivate_me_${timestamp}`; + const fieldLabel = `Deactivate me ${suffix}`; + const fieldName = `deactivate_me_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); // Deactivate the field - cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`) - .should("exist") - .click(); + cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`).click(); cy.getBySelector( `${SELECTORS.FIELD_DROPDOWN_DEACTIVATE_REACTIVATE}_${fieldName}` - ) - .should("exist") - .click(); - - cy.wait("@updateField"); - cy.wait("@getFields"); + ).click(); // Verify field is deactivated - cy.get(`[data-cy-status=Field_${fieldName}_inactive]`).should("exist"); + cy.get(`[data-cy-status=Field_${fieldName}_inactive]`, TIMEOUT).should( + "exist" + ); // Reactivate the field - cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`) - .should("exist") - .click({ force: true }); + cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`).click(TIMEOUT); cy.getBySelector( `${SELECTORS.FIELD_DROPDOWN_DEACTIVATE_REACTIVATE}_${fieldName}` - ) - .should("exist") - .click({ force: true }); - - cy.wait("@updateField"); - cy.wait("@getFields"); + ).click(TIMEOUT); // Verify field is deactivated - cy.get(`[data-cy-status=Field_${fieldName}_active]`).should("exist"); + cy.get(`[data-cy-status=Field_${fieldName}_active]`, TIMEOUT).should( + "exist" + ); }); it("Can deactivate a field via edit modal", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - cy.intercept("/v1/content/models/**").as("updateField"); - - const fieldLabel = `Deactivate me via modal ${timestamp}`; - const fieldName = `deactivate_me_via_modal_${timestamp}`; + const fieldLabel = `Deactivate me via modal ${suffix}`; + const fieldName = `deactivate_me_via_modal_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); // Open update modal - cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`) - .should("exist") - .click(); - cy.getBySelector(`${SELECTORS.FIELD_DROPDOWN_EDIT}_${fieldName}`) - .should("exist") - .click(); + cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`).click(TIMEOUT); + cy.getBySelector(`${SELECTORS.FIELD_DROPDOWN_EDIT}_${fieldName}`).click( + TIMEOUT + ); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Deactivate the field - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_DEACTIVATE_REACTIVATE) - .should("exist") - .click(); - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); - - cy.wait("@updateField"); - cy.wait("@getFields"); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_DEACTIVATE_REACTIVATE).click( + TIMEOUT + ); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); // Verify field is deactivated - cy.get(`[data-cy-status=Field_${fieldName}_inactive]`).should("exist"); + cy.get(`[data-cy-status=Field_${fieldName}_inactive]`, TIMEOUT).should( + "exist" + ); }); it("Shows and hides system fields", () => { // Show system fields - cy.getBySelector(SELECTORS.SHOW_SYSTEM_FIELDS_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.SHOW_SYSTEM_FIELDS_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.SYSTEM_FIELDS).should("exist"); // Hide system fields - cy.getBySelector(SELECTORS.SHOW_SYSTEM_FIELDS_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.SHOW_SYSTEM_FIELDS_BTN).click(); cy.getBySelector(SELECTORS.SYSTEM_FIELDS).should("not.exist"); }); }); + +function openSchemaModel(label) { + cy.visit("/schema"); + cy.get('[data-cy="schema-nav-templateset"] li') + .contains(TEST_DATA?.schema?.label) + .scrollIntoView() + .click(TIMEOUT); +} + +Cypress.Commands.add("openSchemaModelzz", (label) => { + cy.visit("/schema"); + cy.get('[data-cy="schema-nav-templateset"] li') + .contains(TEST_DATA?.schema?.label) + .scrollIntoView() + .click(TIMEOUT); +}); diff --git a/cypress/e2e/schema/models.spec.js b/cypress/e2e/schema/models.spec.js index 9484ae677e..4f48e75d73 100644 --- a/cypress/e2e/schema/models.spec.js +++ b/cypress/e2e/schema/models.spec.js @@ -1,19 +1,57 @@ const SEARCH_TERM = `cypress ${Date.now()}`; const TIMESTAMP = Date.now(); +const TIMEOUT = { timeout: 60_00 }; + +const LABELS = { + testModel: "Cypress Test Model", + testModelNew: "Cypress Test Model New", + testModelUpdate: "Cypress Test Model Updated", + testModelDelete: "Cypress Test Model Delete", + blockTestModel: "Block Test Model", +}; +const TEST_DATA = { + new: { + label: LABELS.testModelNew, + description: LABELS.testModelNew, + listed: true, + name: LABELS.testModelNew.replace(/ /g, "_").toLowerCase().trim(), + parentZUID: null, + type: "pageset", + }, + delete: { + label: LABELS.testModelDelete, + description: LABELS.testModelDelete, + listed: true, + name: LABELS.testModelDelete.replace(/ /g, "_").toLowerCase().trim(), + parentZUID: null, + type: "pageset", + }, +}; describe("Schema: Models", () => { before(() => { + cy.cleanStatusLabels(); + cy.deleteContentModels([...Object.values(LABELS)]); + + [TEST_DATA.delete, TEST_DATA.new].forEach((model) => { + cy.createContentModel(model); + }); + cy.waitOn("/v1/content/models*", () => { cy.visit("/schema"); }); }); + after(() => { + cy.deleteContentModels([...Object.values(LABELS)]); + }); + it("Opens creation model with model type selector when triggered from All Models", () => { - cy.getBySelector("create-model-button-all-models").click(); + cy.getBySelector("create-model-button-all-models").click(TIMEOUT); cy.contains("Select Model Type").should("be.visible"); cy.get("body").type("{esc}"); }); it("Opens creation model with model type pre-selected when triggered from Sidebar", () => { - cy.getBySelector(`create-model-button-sidebar-templateset`).click(); + cy.getBySelector(`create-model-button-sidebar-templateset`).click(TIMEOUT); cy.contains("Create Single Page Model").should("be.visible"); cy.get("body").type("{esc}"); cy.getBySelector(`create-model-button-sidebar-pageset`).click(); @@ -24,14 +62,25 @@ describe("Schema: Models", () => { cy.get("body").type("{esc}"); }); it("Creates model", () => { - cy.getBySelector(`create-model-button-all-models`).click(); + cy.visit("/schema"); + + //make sure to load model parent dropdown + cy.intercept("**/env/nav").as("getModelParent"); + + cy.getBySelector(`create-model-button-all-models`).click(TIMEOUT); cy.contains("Multi Page Model").click(); cy.contains("Next").click(); - cy.contains("Display Name").next().type("Cypress Test Model"); + + cy.wait("@getModelParent"); + + cy.contains("Display Name").next().type(LABELS.testModel); cy.contains("Reference ID") .next() .find("input") - .should("have.value", "cypress_test_model"); + .should( + "have.value", + LABELS.testModel.replace(/ /g, "_").toLowerCase().trim() + ); cy.contains("Model Parent").next().click(); @@ -41,33 +90,31 @@ describe("Schema: Models", () => { cy.get(".MuiAutocomplete-popper") .contains("Cypress test (Group with visible fields in list)") - .click(); + .click({ timeout: 60_000 }); cy.contains("Description").next().type("Cypress test model description"); - cy.get(".MuiDialog-container").within(() => { - cy.contains("Create Model").click(); - }); - cy.intercept("POST", "/models"); - cy.intercept("GET", "/models"); + + cy.get('[data-cy="create-model-submit-button"]').click(); }); it("Renames model", () => { - cy.getBySelector(`model-header-menu`).click(); + openBlockModel(TEST_DATA.new.label); + cy.getBySelector(`model-header-menu`).click(TIMEOUT); cy.contains("Rename Model").click(); cy.get(".MuiDialog-container").within(() => { cy.get("label").contains("Display Name").next().type(" Updated"); cy.get("label").contains("Reference ID").next().type("_updated"); cy.contains("Save").click(); }); - cy.intercept("PUT", "/models"); - cy.intercept("GET", "/models"); - cy.contains("Cypress Test Model Updated").should("exist"); + + cy.contains(TEST_DATA.new.label).should("exist"); }); it("Deletes model", () => { + openBlockModel(TEST_DATA.delete.label); cy.getBySelector(`model-header-menu`).click(); cy.contains("Delete Model").click(); cy.get(".MuiDialog-container").within(() => { - cy.get(".MuiOutlinedInput-root").type("Cypress Test Model Updated"); + cy.get(".MuiOutlinedInput-root").type(TEST_DATA.delete.label); }); cy.contains("Delete Forever").click(); }); @@ -129,19 +176,27 @@ describe("Schema: Models", () => { cy.getBySelector(`create-model-button-all-models`).click(); cy.contains("Block Model").click(); cy.contains("Next").click(); - cy.contains("Display Name").next().type(`Block Test Model ${TIMESTAMP}`); + cy.contains("Display Name").next().type(LABELS.blockTestModel); cy.contains("Reference ID") .next() .find("input") - .should("have.value", `block_test_model_${TIMESTAMP}`); + .should( + "have.value", + LABELS.blockTestModel.replace(/ /g, "_").toLowerCase().trim() + ); cy.contains("Description").next().type("Block test model description"); cy.get(".MuiDialog-container").within(() => { cy.contains("Create Model").click(); }); - cy.intercept("POST", "/models"); - cy.intercept("GET", "/models"); - - cy.contains(`Block Test Model ${TIMESTAMP}`).should("exist"); + cy.contains(`Block Test Model`).should("exist"); }); }); + +function openBlockModel(label) { + cy.visit("/schema"); + cy.get('[data-cy="schema-nav-pageset"] li') + .contains(label) + .scrollIntoView() + .click(TIMEOUT); +} diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 13ff309ede..7111a54f54 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -5,7 +5,7 @@ import { colorMenu, } from "../../../src/apps/settings/src/app/views/User/Workflows/constants"; -const TIMEOUT = { timeout: 40_000 }; +const TIMEOUT = { timeout: 50_000 }; const INSTANCE_API_ENDPOINT = `${ CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL @@ -93,13 +93,13 @@ const TEST_DATA = { }; before(() => { - // cy.cleanTestData(); + cy.cleanStatusLabels(); cy.createTestData(); }); -// after(() => { -// cy.cleanTestData(); -// }); +after(() => { + cy.cleanStatusLabels(); +}); describe("Restricted User", { retries: 1 }, () => { it("displays restricted access message and admin profiles", () => { @@ -233,7 +233,8 @@ describe("Create New Status Label", { retries: 1 }, () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') .contains(TEST_DATA.new.name) .parents('[data-cy="status-label"]') - .should("have.css", "background-color", FOCUSED_LABEL_COLOR, TIMEOUT); + .should("have.css", "background-color") + .and("eq", FOCUSED_LABEL_COLOR); }); it("Clicking outside the focused label restores it to its default state.", () => { @@ -465,29 +466,6 @@ Cypress.Commands.add("goToWorkflowsPage", () => { cy.get('[data-cy="workflows-authorized-page"]', { timeout: 60_000 }); }); -Cypress.Commands.add("cleanTestData", function () { - const testLabels = [ - TEST_DATA?.new?.name, - TEST_DATA?.edited?.name, - TEST_DATA?.temp1?.name, - TEST_DATA?.temp2?.name, - TEST_DATA?.temp3?.name, - ]; - - cy.apiRequest({ - url: `${INSTANCE_API_ENDPOINT}/env/labels?showDeleted=true`, - }).then((response) => { - response?.data - ?.filter((label) => !label?.deletedAt && testLabels.includes(label?.name)) - .forEach((label) => { - cy.apiRequest({ - url: `${INSTANCE_API_ENDPOINT}/env/labels/${label.ZUID}`, - method: "DELETE", - }); - }); - }); -}); - Cypress.Commands.add("createTestData", () => { const testLabels = [TEST_DATA?.temp1, TEST_DATA?.temp2, TEST_DATA?.temp3]; testLabels.forEach((label) => { diff --git a/cypress/jsconfig.json b/cypress/jsconfig.json new file mode 100644 index 0000000000..6d2e8b5c27 --- /dev/null +++ b/cypress/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "types": ["cypress", "node"], + "jsx": "react-jsx", + "resolveJsonModule": true + } +} diff --git a/cypress/support/cleanup.ts b/cypress/support/cleanup.ts deleted file mode 100644 index fad91b2e91..0000000000 --- a/cypress/support/cleanup.ts +++ /dev/null @@ -1,73 +0,0 @@ -import instanceZUID from "../../src/utility/instanceZUID"; -import CONFIG from "../../src/shell/app.config"; -import { StatusLabelQuery } from "src/shell/services/types"; - -const API_ENDPOINTS = { - devInstance: `${ - CONFIG[process.env.NODE_ENV as keyof typeof CONFIG]?.API_INSTANCE_PROTOCOL - }${instanceZUID}${ - CONFIG[process.env.NODE_ENV as keyof typeof CONFIG]?.API_INSTANCE - }`, -}; - -export function preCleanUp(token: string) { - statusLabelCleanUp(token); -} - -// ========================= CLEANUP FUNCTIONS STARTS HERE =========================// - -// Status Labels -function statusLabelCleanUp(token: string) { - const DEFAULT_STATUS_LABELS_ZUIDS = [ - "36-14b315-4pp20v3d", - "36-14b315-d24ft", - "36-n33d5-23v13w", - ]; - const DEFAULT_STATUS_LABELS_NAMES = ["Approved", "Needs Review", "Draft"]; - sendRequest({ - method: "GET", - url: `${API_ENDPOINTS.devInstance}/env/labels?showDeleted=true`, - token: token, - }).then((response) => { - response?.data - ?.filter( - (resData: StatusLabelQuery) => - !resData?.deletedAt && - !DEFAULT_STATUS_LABELS_NAMES.includes(resData?.name) && - !DEFAULT_STATUS_LABELS_ZUIDS.includes(resData?.ZUID) - ) - .forEach((labelForDelete: StatusLabelQuery) => { - sendRequest({ - url: `${API_ENDPOINTS.devInstance}/env/labels/${labelForDelete.ZUID}`, - method: "DELETE", - token: token, - }); - }); - }); -} - -// ========================= CLEANUP FUNCTIONS ENDS HERE =========================// - -function sendRequest({ - method = "GET", - url = "", - body = undefined, - token = "", -}: { - method?: string; - url: string; - body?: any; - token: string; -}) { - return cy - .request({ - method, - url, - headers: { authorization: `Bearer ${token}` }, - ...(body ? { body: body } : {}), - }) - .then((response) => ({ - status: response?.isOkStatusCode ? "success" : "error", - data: response?.body?.data, - })); -} diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 944014bed1..27200e8661 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,17 +1,11 @@ import instanceZUID from "../../src/utility/instanceZUID"; import CONFIG from "../../src/shell/app.config"; -const INSTANCE_API_ENDPOINT = `${ - CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL -}${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; - -const DEFAULT_STATUS_LABELS_ZUIDS = [ - "36-14b315-4pp20v3d", - "36-14b315-d24ft", - "36-n33d5-23v13w", -]; - -const DEFAULT_STATUS_LABELS_NAMES = ["Approved", "Needs Review", "Draft"]; +const API_ENDPOINTS = { + devInstance: `${ + CONFIG[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL + }${instanceZUID}${CONFIG[process.env.NODE_ENV]?.API_INSTANCE}`, +}; Cypress.Commands.add("login", () => { const formBody = new FormData(); @@ -80,33 +74,85 @@ Cypress.Commands.add( method, headers: { authorization: `Bearer ${token}` }, ...(body ? { body: body } : {}), + failOnStatusCode: false, }) - .then((response) => ({ - status: response?.isOkStatusCode ? "success" : "error", - data: response?.body?.data, - })); + .then((response) => { + return { + status: response?.isOkStatusCode ? "success" : "error", + data: response?.body?.data, + }; + }); }); } ); -Cypress.Commands.add("workflowStatusLabelCleanUp", function () { +Cypress.Commands.add("deleteStatusLabels", (labels = []) => { + cy.log(`[CLEAN UP] Status Labels`); cy.apiRequest({ - url: `${INSTANCE_API_ENDPOINT}/env/labels?showDeleted=true`, + url: `${API_ENDPOINTS}/env/labels?showDeleted=true`, }).then((response) => { - console.debug("workflowStatusLabelCleanUp | response LABELS: ", response); + response?.data + ?.filter( + (resData) => !resData?.deletedAt && [...labels].includes(resData?.name) + ) + .forEach((labelForDelete) => { + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/env/labels/${labelForDelete.ZUID}`, + method: "DELETE", + }); + }); + }); +}); +Cypress.Commands.add("cleanStatusLabels", () => { + const DEFAULT_STATUS_LABELS_ZUIDS = [ + "36-14b315-4pp20v3d", + "36-14b315-d24ft", + "36-n33d5-23v13w", + ]; + const DEFAULT_STATUS_LABELS_NAMES = ["Approved", "Needs Review", "Draft"]; + cy.log(`[CLEAN UP] Status Labels`); + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/env/labels?showDeleted=true`, + }).then((response) => { response?.data ?.filter( - (label) => - !label?.deletedAt && - !DEFAULT_STATUS_LABELS_NAMES.includes(label?.name) && - !DEFAULT_STATUS_LABELS_ZUIDS.includes(label?.ZUID) + (resData) => + !resData?.deletedAt && + !DEFAULT_STATUS_LABELS_NAMES.includes(resData?.name) && + !DEFAULT_STATUS_LABELS_ZUIDS.includes(resData?.ZUID) ) - .forEach((label) => { + .forEach((labelForDelete) => { + console.debug("labelForDelete | labelForDelete: ", labelForDelete); cy.apiRequest({ - url: `${INSTANCE_API_ENDPOINT}/env/labels/${label.ZUID}`, + url: `${API_ENDPOINTS.devInstance}/env/labels/${labelForDelete.ZUID}`, method: "DELETE", }); }); }); }); + +Cypress.Commands.add("deleteContentModels", (models = []) => { + cy.log(`[CLEAN UP] Content Models`); + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/content/models`, + }).then((response) => { + response?.data + ?.filter((resData) => [...models].includes(resData?.label)) + .forEach((forDelete) => { + console.debug("models | forDelete: ", forDelete); + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/content/models/${forDelete.ZUID}`, + method: "DELETE", + }); + }); + }); +}); + +Cypress.Commands.add("createContentModel", (payload) => { + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/content/models`, + method: "POST", + body: payload, + }); +}); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 57cef4de76..62057a245e 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -16,9 +16,6 @@ // Import commands.js using ES2015 syntax: import "./commands"; import "cypress-iframe"; -import { preCleanUp } from "./cleanup"; - -let preCleanupHasExeciuted = false; // @see https://docs.cypress.io/api/cypress-api/cookies.html#Set-global-default-cookies // Cypress.Cookies.defaults({ @@ -48,17 +45,6 @@ before(() => { // Blocks the api call to render the announcement popup cy.blockAnnouncements(); - - if (!preCleanupHasExeciuted) { - cy.getCookie(Cypress.env("COOKIE_NAME")) - .then((cookie) => { - preCleanUp(cookie?.value); - }) - .then(() => { - // SET TO FALSE TO EXECUTE CLEANUP BEFORE EACH TEST - preCleanupHasExeciuted = false; - }); - } }); // Before each test in spec diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts new file mode 100644 index 0000000000..4bb837a188 --- /dev/null +++ b/cypress/support/index.d.ts @@ -0,0 +1,51 @@ +import "./commands"; + +interface AwaitResponseOptions { + alias?: string; + timeout?: number; + interval?: number; +} + +interface AwaitResponseResult { + started: number; + finished: number; + waited: number; +} + +declare global { + namespace Cypress { + interface Chainable { + waitOn(path: string, cb: () => void): Chainable>; + login(): Chainable>; + getBySelector( + selector: string, + ...args: any[] + ): Chainable>; + blockLock(): Chainable>; + assertClipboardValue(value: string): Chainable>; + blockAnnouncements(): Chainable>; + + apiRequest(options: { + url: string; + method?: string; + body?: any; + }): Chainable; + // workflowStatusLabelCleanUp(): Chainable>; + cleanTestData(): Chainable>; + createTestData(): Chainable>; + goToWorkflowsPage(): Chainable>; + getStatusLabels(): Chainable; + // deleteStatusLabels(labels: string[]): Chainable; + cleanStatusLabels(): Chainable; + deleteStatusLabels(labels: string[]): Chainable; + deleteContentModels(models: string[]): Chainable; + createContentModel({ + description: string, + label: string, + type: string, + name: string, + listed: boolean, + }): Chainable; + } + } +} diff --git a/cypress/support/index.ts b/cypress/support/index.ts deleted file mode 100644 index 472f0d5791..0000000000 --- a/cypress/support/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import "./commands"; - -declare global { - namespace Cypress { - interface Chainable { - /** - * Custom command to select DOM element by data-cy attribute. - * @example cy.dataCy('greeting') - */ - waitOn(path: string, cb: () => void): Chainable>; - login(): Chainable>; - getBySelector( - selector: string, - ...args: any[] - ): Chainable>; - blockLock(): Chainable>; - assertClipboardValue(value: string): Chainable>; - blockAnnouncements(): Chainable>; - apiRequest( - options: Partial - ): Chainable>; - workflowStatusLabelCleanUp(): Chainable>; - cleanTestData(): Chainable>; - createTestData(): Chainable>; - goToWorkflowsPage(): Chainable>; - getStatusLabel(): Chainable>; - } - } -} diff --git a/tsconfig.json b/tsconfig.json index 25243003cf..3c6bafb8b1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src", "index.d.ts", "cypress"], + "include": ["src", "index.d.ts"], "compilerOptions": { "noImplicitAny": true, "allowJs": true, From 6b783cda4e16db154578583e8fd364ec6819fbb2 Mon Sep 17 00:00:00 2001 From: geodem Date: Wed, 15 Jan 2025 00:20:24 +0800 Subject: [PATCH 58/59] updated cypress config, added custom commands for data cleanup, updated test cases --- cypress.config.js | 5 +- cypress/e2e/blocks/pages/AllBlocksPage.js | 5 +- cypress/e2e/blocks/tests/blocks.spec.js | 30 +- cypress/e2e/content/actions.spec.js | 23 +- cypress/e2e/content/item-list-table.spec.js | 5 +- cypress/e2e/content/navigation.spec.js | 6 +- cypress/e2e/content/workflows.spec.js | 1 - cypress/e2e/schema/field.spec.js | 515 ++++++++------------ cypress/e2e/schema/models.spec.js | 99 +++- cypress/e2e/schema/pages/SchemaPage.js | 13 +- cypress/e2e/settings/workflows.spec.js | 36 +- cypress/jsconfig.json | 7 + cypress/support/cleanup.ts | 73 --- cypress/support/commands.js | 93 +++- cypress/support/e2e.js | 14 - cypress/support/index.d.ts | 51 ++ cypress/support/index.ts | 29 -- tsconfig.json | 2 +- 18 files changed, 472 insertions(+), 535 deletions(-) create mode 100644 cypress/jsconfig.json delete mode 100644 cypress/support/cleanup.ts create mode 100644 cypress/support/index.d.ts delete mode 100644 cypress/support/index.ts diff --git a/cypress.config.js b/cypress.config.js index eb2ff59d6d..4d01aa5327 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -29,8 +29,7 @@ module.exports = defineConfig({ testIsolation: false, }, defaultCommandTimeout: 15_000, - pageLoadTimeout: 60_000, - requestTimeout: 30_000, + pageLoadTimeout: 80_000, + requestTimeout: 40_000, responseTimeout: 80_000, - retries: 1, }); diff --git a/cypress/e2e/blocks/pages/AllBlocksPage.js b/cypress/e2e/blocks/pages/AllBlocksPage.js index eed2961531..e0327ff3e0 100644 --- a/cypress/e2e/blocks/pages/AllBlocksPage.js +++ b/cypress/e2e/blocks/pages/AllBlocksPage.js @@ -1,3 +1,4 @@ +const TIMEOUT = { timeout: 30_000 }; class AllBlocksPage { visit() { cy.visit("/blocks"); @@ -28,9 +29,11 @@ class AllBlocksPage { } createBlock(name) { - this.createBlockButton.click(); + cy.intercept("POST", "**/content/models").as("createModel"); + this.createBlockButton.click(TIMEOUT); cy.getBySelector("create-model-display-name-input").type(name); cy.getBySelector("create-model-submit-button").click(); + cy.wait("@createModel"); } } diff --git a/cypress/e2e/blocks/tests/blocks.spec.js b/cypress/e2e/blocks/tests/blocks.spec.js index f4292a274e..2d975b691e 100644 --- a/cypress/e2e/blocks/tests/blocks.spec.js +++ b/cypress/e2e/blocks/tests/blocks.spec.js @@ -5,18 +5,21 @@ import SchemaPage from "../../schema/pages/SchemaPage"; const CypressTestBlock = "Cypress Test Block"; const CypressTestVariant = "Cypress Test Variant"; +const TIMEOUT = { timeout: 30_000 }; + describe("All Blocks Tests", () => { before(() => { + cy.deleteContentModels(["Cypress Test Block", "Cypress Test Variant"]); AllBlocksPage.visit(); }); after(() => { - SchemaPage.visit(); - SchemaPage.deleteModel(CypressTestBlock); + cy.deleteContentModels(["Cypress Test Block", "Cypress Test Variant"]); }); it("should show and traverse onboarding flow", () => { - AllBlocksPage.onboardingDialog.should("be.visible"); + cy.visit("/blocks"); + cy.get('[data-cy="onboarding-dialog"]', TIMEOUT).should("be.visible"); const totalSteps = 4; for (let i = 0; i < totalSteps; i++) { AllBlocksPage.clickOnboardingNextButton(); @@ -26,13 +29,15 @@ describe("All Blocks Tests", () => { it("creates new block with default values", () => { AllBlocksPage.createBlock(CypressTestBlock); - cy.contains(CypressTestBlock).should("exist"); + cy.contains(CypressTestBlock, TIMEOUT).should("exist"); SchemaPage.visit(); + SchemaPage.addSingleLineTextFieldWithDefaultValue( CypressTestBlock, "Foo", "Default Foo" ); + AllBlocksPage.visit(); }); @@ -50,16 +55,23 @@ describe("All Blocks Tests", () => { }); it("navigates to block detail page", () => { - cy.contains(CypressTestBlock).click(); - cy.contains("Start Creating Variants Now").should("exist"); + cy.contains(CypressTestBlock).click({ timeout: 30_000 }); + cy.contains("Start Creating Variants Now", { timeout: 30_000 }).should( + "exist" + ); }); it("creates a variant with default values", () => { - cy.contains(CypressTestBlock).click(); + AllBlocksPage.visit(); + cy.contains(CypressTestBlock).click(TIMEOUT); BlockPage.createVariant(CypressTestVariant); cy.contains( - new RegExp(`${CypressTestBlock}:\\s*${CypressTestVariant}`) + new RegExp(`${CypressTestBlock}:\\s*${CypressTestVariant}`), + TIMEOUT ).should("exist"); - cy.get('input[name="foo"]').should("have.value", "Default Foo"); + cy.get('input[name="foo"]', { timeout: 30_000 }).should( + "have.value", + "Default Foo" + ); }); }); diff --git a/cypress/e2e/content/actions.spec.js b/cypress/e2e/content/actions.spec.js index bf33af1d94..e3226a1a29 100644 --- a/cypress/e2e/content/actions.spec.js +++ b/cypress/e2e/content/actions.spec.js @@ -11,6 +11,10 @@ const yesterdayTimestamp = moment() describe("Actions in content editor", () => { const timestamp = Date.now(); + before(() => { + cy.cleanStatusLabels(); + }); + it("Must not save when missing required Field", () => { cy.waitOn("/v1/content/models*", () => { cy.visit("/content/6-556370-8sh47g/7-82a5c7ffb0-07vj1c"); @@ -83,7 +87,7 @@ describe("Actions in content editor", () => { cy.get("#12-f8efe4e0f5-xj7pj6 input").should("not.exist"); // Make an edit to enable save button - cy.get("#12-849844-t8v5l6 input").clear().type(timestamp); + cy.get("#12-849844-t8v5l6 input").clear().type(timestamp.toString()); // save to api cy.waitOn( @@ -95,7 +99,6 @@ describe("Actions in content editor", () => { cy.get("[data-cy=toast]").contains("Item Saved"); }); - it("Saves homepage item metadata", () => { cy.waitOn("/v1/content/models*", () => { cy.visit("/content/6-a1a600-k0b6f0/7-a1be38-1b42ht/meta"); @@ -119,9 +122,8 @@ describe("Actions in content editor", () => { it("Publishes an item", () => { cy.getBySelector("PublishButton").click(); cy.getBySelector("ConfirmPublishModal").should("exist"); - cy.getBySelector("ConfirmPublishButton").click(); - cy.intercept("GET", "**/publishings").as("publish"); + cy.getBySelector("ConfirmPublishButton").click(); cy.wait("@publish", { timeout: 30_000 }); cy.getBySelector("ContentPublishedIndicator").should("exist"); @@ -212,17 +214,18 @@ describe("Actions in content editor", () => { cy.visit("/content/6-a1a600-k0b6f0/new"); }); - cy.get("input[name=title]", { timeout: 5000 }).click().type(timestamp); + cy.get("input[name=title]") + .click({ timeout: 30_000 }) + .type(timestamp.toString()); cy.getBySelector("ManualMetaFlow").click(); cy.getBySelector("metaDescription") .find("textarea") .first() - .type(timestamp); + .type(timestamp.toString()); cy.getBySelector("CreateItemSaveButton").click(); - cy.contains("Created Item", { timeout: 5000 }).should("exist"); + cy.contains("Created Item", { timeout: 30_000 }).should("exist"); }); - it("Saved item becomes publishable", () => { cy.get("#PublishButton").should("exist"); }); @@ -232,7 +235,7 @@ describe("Actions in content editor", () => { cy.visit("/content/6-a1a600-k0b6f0"); }); - cy.contains(timestamp, { timeout: 5000 }).should("exist"); + cy.contains(timestamp, { timeout: 30_000 }).should("exist"); }); it("Deletes an item", () => { @@ -258,7 +261,7 @@ describe("Actions in content editor", () => { // dealing with these specific endpoints // the local environment is slow cy.contains("Successfully sent workflow request", { - timeout: 15_000, + timeout: 30_000, }).should("exist"); }); diff --git a/cypress/e2e/content/item-list-table.spec.js b/cypress/e2e/content/item-list-table.spec.js index 3433144a39..ebee13d381 100644 --- a/cypress/e2e/content/item-list-table.spec.js +++ b/cypress/e2e/content/item-list-table.spec.js @@ -6,10 +6,11 @@ describe("Content item list table", () => { }); }); - cy.getBySelector("SingleRelationshipCell", { timeout: 10000 }) + cy.getBySelector("SingleRelationshipCell") .first() .contains( - "5 Tricks to Teach Your Pitbull: Fun & Easy Tips for You & Your Dog!" + "5 Tricks to Teach Your Pitbull: Fun & Easy Tips for You & Your Dog!", + { timeout: 30_000 } ); }); }); diff --git a/cypress/e2e/content/navigation.spec.js b/cypress/e2e/content/navigation.spec.js index 7d618e9fa3..0134ce5f0d 100644 --- a/cypress/e2e/content/navigation.spec.js +++ b/cypress/e2e/content/navigation.spec.js @@ -1,3 +1,7 @@ +const LABELS = { + cypressTest: "Cypress test (Group with visible fields in list)", +}; + describe("Navigation through content editor", () => { before(() => { cy.waitOn("/v1/env/nav", () => { @@ -24,7 +28,7 @@ describe("Navigation through content editor", () => { cy.getBySelector("create_new_content_item_dialog").should("exist"); cy.getBySelector("create_new_content_item_input") .find("input") - .type("cypress"); + .type(LABELS.cypressTest); cy.get(".MuiAutocomplete-listbox .MuiAutocomplete-option") .first() .should("exist") diff --git a/cypress/e2e/content/workflows.spec.js b/cypress/e2e/content/workflows.spec.js index 2775672194..e54ccaefa8 100644 --- a/cypress/e2e/content/workflows.spec.js +++ b/cypress/e2e/content/workflows.spec.js @@ -69,7 +69,6 @@ describe("Content Item Workflows", () => { cy.visit(`/content/6-b6cde1aa9f-wftv50/${response.data?.ZUID}`); }); }); - after(() => { // Delete test content item cy.location("pathname").then((loc) => { diff --git a/cypress/e2e/schema/field.spec.js b/cypress/e2e/schema/field.spec.js index b4a6cf4521..23065bd275 100644 --- a/cypress/e2e/schema/field.spec.js +++ b/cypress/e2e/schema/field.spec.js @@ -1,3 +1,6 @@ +import instanceZUID from "../../../src/utility/instanceZUID"; +import CONFIG from "../../../src/shell/app.config"; + const SELECTORS = { ADD_FIELD_BTN: "AddFieldBtn", ADD_FIELD_BTN_END_OF_LIST: "EndOfListAddFieldBtn", @@ -55,72 +58,73 @@ const SELECTORS = { MAX_CHARACTER_ERROR_MSG: "MaxCharacterErrorMsg", }; +const ENDPOINT = `${ + CONFIG[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL +}${instanceZUID}${CONFIG[process.env.NODE_ENV]?.API_INSTANCE}`; + +const TIMEOUT = { timeout: 60_000 }; + +const TEST_DATA = { + schema: { + description: "Cypress Test - Fields Description", + label: "Cypress Test - Fields", + type: "templateset", + name: "cypress_test___fields", + listed: true, + }, +}; + /** * Schema Fields E2E tests */ describe("Schema: Fields", () => { - const timestamp = Date.now(); + const suffix = "field"; before(() => { - cy.waitOn( - "/v1/content/models/6-ce80dbfe90-ptjpm6/fields?showDeleted=true", - () => { - cy.waitOn("/bin/1-6c9618c-r26pt/groups", () => { - cy.waitOn("/v1/content/models", () => { - cy.visit("/schema/6-ce80dbfe90-ptjpm6/fields"); - - cy.getBySelector("create_new_content_item").click(); - - cy.contains("Multi Page Model").click(); - cy.contains("Next").click(); - cy.contains("Display Name") - .next() - .type(`Cypress Test Model ${timestamp}`); - cy.get(".MuiDialog-container").within(() => { - cy.contains("Create Model").click(); - }); - cy.intercept("POST", "/models"); - cy.intercept("GET", "/models"); - }); - }); + cy.apiRequest({ url: `${ENDPOINT}/content/models` }).then((resData) => { + const schhemaModel = resData?.data?.find( + (item) => item?.label === TEST_DATA?.schema?.label + ); + if (!!schhemaModel) { + cy.deleteContentModels([TEST_DATA?.schema?.label]); } - ); + cy.createContentModel(TEST_DATA?.schema); + }); + }); + + after(() => { + cy.deleteContentModels([TEST_DATA?.schema?.label]); + }); + + beforeEach(() => { + openSchemaModel(TEST_DATA?.schema?.label); }); it("Opens Add Field Modal via button click", () => { - cy.wait(3000); // Open the modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); }); it("Creates a Single Line Text field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Text ${timestamp}`; - const fieldName = `text_${timestamp}`; + const fieldLabel = `Text ${suffix}`; + const fieldName = `text_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); - cy.get("input[name='label']") - .should("exist") - .should("have.value", fieldLabel); - cy.get("input[name='name']") - .should("exist") - .should("have.value", fieldName); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); + cy.get("input[name='label']").should("have.value", fieldLabel); + cy.get("input[name='name']").should("have.value", fieldName); // Navigate to rules tab and add default value cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); @@ -145,50 +149,38 @@ describe("Schema: Fields", () => { cy.getBySelector(SELECTORS.MIN_CHARACTER_ERROR_MSG).should("not.exist"); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a Dropdown field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Dropdown ${timestamp}`; - const fieldName = `dropdown_${timestamp}`; + const fieldLabel = `Dropdown ${suffix}`; + const fieldName = `dropdown_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Dropdown field - cy.getBySelector(SELECTORS.FIELD_SELECT_DROPDOWN).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_DROPDOWN).click(TIMEOUT); // Input field label and duplicate dropdown options - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); - cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_0`) - .should("exist") - .type("test"); - cy.getBySelector(SELECTORS.DROPDOWN_ADD_OPTION).should("exist").click(); - cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_1`) - .should("exist") - .type("test"); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); + cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_0`).type("test"); + cy.getBySelector(SELECTORS.DROPDOWN_ADD_OPTION).click(); + cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_1`).type("test"); // Verify that duplicate dropdown values causes an error - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(`${SELECTORS.ERROR_MESSAGE_OPTION_VALUE}_1`).should( "exist" ); // Delete duplicate option - cy.getBySelector(`${SELECTORS.DROPDOWN_DELETE_OPTION}_1`) - .should("exist") - .click(); + cy.getBySelector(`${SELECTORS.DROPDOWN_DELETE_OPTION}_1`).click(); // Navigate to rules tab and add default value cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); @@ -204,72 +196,56 @@ describe("Schema: Fields", () => { .should("have.value", "test"); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a Media field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Media ${timestamp}`; - const fieldName = `media_${timestamp}`; + const fieldLabel = `Media ${suffix}`; + const fieldName = `media_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Media field - cy.getBySelector(SELECTORS.FIELD_SELECT_MEDIA).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_MEDIA).click(TIMEOUT); // Input field label - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Navigate to rules tab and enable media limit and folder lock - cy.getBySelector(SELECTORS.RULES_TAB_BTN).should("exist").click(); - cy.getBySelector(SELECTORS.MEDIA_RULES_TAB).should("exist").click(); - cy.getBySelector(SELECTORS.MEDIA_CHECKBOX_LIMIT).should("exist").click(); - cy.getBySelector(SELECTORS.MEDIA_CHECKBOX_LOCK).should("exist").click(); + cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); + cy.getBySelector(SELECTORS.MEDIA_RULES_TAB).click(); + cy.getBySelector(SELECTORS.MEDIA_CHECKBOX_LIMIT).click(); + cy.getBySelector(SELECTORS.MEDIA_CHECKBOX_LOCK).click(); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a Boolean field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Boolean ${timestamp}`; - const fieldName = `boolean_${timestamp}`; + const fieldLabel = `Boolean ${suffix}`; + const fieldName = `boolean_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Boolean field - cy.getBySelector(SELECTORS.FIELD_SELECT_BOOLEAN).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_BOOLEAN).click(TIMEOUT); // Input field label and option labels - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); - cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_0`) - .should("exist") - .type("Test option 1"); - cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_1`) - .should("exist") - .type("Test option 2"); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); + cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_0`).type("Test option 1"); + cy.getBySelector(`${SELECTORS.INPUT_OPTION_LABEL}_1`).type("Test option 2"); // Verify that delete option button does not exist cy.getBySelector(`${SELECTORS.DROPDOWN_DELETE_OPTION}_0`).should( @@ -292,48 +268,34 @@ describe("Schema: Fields", () => { .should("have.attr", "aria-pressed", "true"); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a One-to-one relationship field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `One to One ${timestamp}`; - const fieldName = `one_to_one_${timestamp}`; + const fieldLabel = `One to One ${suffix}`; + const fieldName = `one_to_one_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select one-to-one relationship field - cy.getBySelector(SELECTORS.FIELD_SELECT_ONE_TO_ONE).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_ONE_TO_ONE).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Select a related model - cy.getBySelector(SELECTORS.AUTOCOMPLETE_MODEL_ZUID) - .should("exist") - .type("cypress"); + cy.getBySelector(SELECTORS.AUTOCOMPLETE_MODEL_ZUID).type("cypress"); cy.get("[role=listbox] [role=option]").first().click(); - cy.wait("@getFields"); - - cy.wait(3000); - // Select a related field - cy.getBySelector(SELECTORS.AUTOCOMPLETE_FIELED_ZUID) - .should("exist") - .click(); - cy.get("[role=listbox] [role=option]").first().click(); + cy.getBySelector(SELECTORS.AUTOCOMPLETE_FIELED_ZUID).click(TIMEOUT); + cy.get("[role=listbox] [role=option]").first().click(TIMEOUT); // Navigate to rules tab and add default value cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); @@ -342,7 +304,7 @@ describe("Schema: Fields", () => { // enter a default value cy.getBySelector(SELECTORS.DEFAULT_VALUE_INPUT).click(); // Select the option - cy.get("[role=listbox] [role=option]").first().click(); + cy.get("[role=listbox] [role=option]").first().click(TIMEOUT); // verify that the default value is set cy.getBySelector(SELECTORS.DEFAULT_VALUE_INPUT) .find("input") @@ -350,34 +312,30 @@ describe("Schema: Fields", () => { cy.getBySelector(SELECTORS.DEFAULT_VALUE_CHECKBOX).click(); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a currency field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Currency ${timestamp}`; - const fieldName = `currency_${timestamp}`; + const fieldLabel = `Currency ${suffix}`; + const fieldName = `currency_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select one-to-one relationship field - cy.getBySelector(SELECTORS.FIELD_SELECT_CURRENCY).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_CURRENCY).click(TIMEOUT); // Select default currency - cy.getBySelector(SELECTORS.AUTOCOMPLETE_FIELD_CURRENCY).type("phil"); - cy.get("[role=listbox] [role=option]").first().click(); + cy.getBySelector(SELECTORS.AUTOCOMPLETE_FIELD_CURRENCY).type("philippine"); + cy.get("[role=listbox] [role=option]").first().click(TIMEOUT); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Navigate to rules tab and add default value cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); @@ -388,63 +346,49 @@ describe("Schema: Fields", () => { // Verify default currency cy.getBySelector(SELECTORS.DEFAULT_VALUE_INPUT).contains("PHP"); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); }); it("Creates a field via add another field button", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - const values = { number: { - label: `Number ${timestamp}`, - name: `number_${timestamp}`, + label: `Number ${suffix}`, + name: `number_${suffix}`, }, internal_link: { - label: `Internal Link ${timestamp}`, - name: `internal_link_${timestamp}`, + label: `Internal Link ${suffix}`, + name: `internal_link_${suffix}`, }, }; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select number field - cy.getBySelector(SELECTORS.FIELD_SELECT_NUMBER).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_NUMBER).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL) - .should("exist") - .type(values.number.label); + cy.getBySelector(SELECTORS.INPUT_LABEL).type(values.number.label); // Click add another field button - cy.getBySelector(SELECTORS.ADD_ANOTHER_FIELD_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_ANOTHER_FIELD_BTN).click(); // Select internal link field cy.getBySelector(SELECTORS.FIELD_SELECTION).should("exist"); - cy.getBySelector(SELECTORS.FIELD_SELECT_INTERNAL_LINK) - .should("exist") - .click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_INTERNAL_LINK).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL) - .should("exist") - .type(values.internal_link.label); + cy.getBySelector(SELECTORS.INPUT_LABEL).type(values.internal_link.label); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Verify that fields were created cy.getBySelector(`Field_${values.number.name}`); cy.getBySelector(`Field_${values.internal_link.name}`); @@ -452,106 +396,91 @@ describe("Schema: Fields", () => { it("Shows error messages during field creation", () => { // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); // Verify that error messages are shown cy.getBySelector(SELECTORS.ERROR_MESSAGE_LABEL).should("exist"); cy.getBySelector(SELECTORS.ERROR_MESSAGE_NAME).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); }); it("Opens Add Field Modal via end of list button", () => { // Click end of list button - cy.getBySelector(SELECTORS.ADD_FIELD_BTN_END_OF_LIST) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN_END_OF_LIST).click(TIMEOUT); // Verify modal cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); }); it("Opens Add Field Modal via in between field button", () => { // Click in-between field button - cy.getBySelector(SELECTORS.ADD_FIELD_BTN_IN_BETWEEN) - .first() - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN_IN_BETWEEN).first().click(TIMEOUT); // Verify modal cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); }); it("Switches tabs in Add Field Modal", () => { // Open the modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select single text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Verify that details tab is loaded cy.getBySelector(SELECTORS.DETAILS_TAB).should("exist"); // Click Learn tab - cy.getBySelector(SELECTORS.LEARN_TAB_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.LEARN_TAB_BTN).click(); cy.getBySelector(SELECTORS.LEARN_TAB).should("exist"); // Click Rules tab - cy.getBySelector(SELECTORS.RULES_TAB_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.RULES_TAB_BTN).click(); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); }); it("Can navigate back to fields selection view", () => { // Open the modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select single text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Click the back button - cy.getBySelector(SELECTORS.BACK_TO_FIELD_SELECTION_BTN) - .should("exist") - .click(); + cy.getBySelector(SELECTORS.BACK_TO_FIELD_SELECTION_BTN).click(); // Verify that field selection screen is loaded cy.getBySelector(SELECTORS.FIELD_SELECTION).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); }); it("Can filter fields in field selection view", () => { // Open the modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Verify that field selection screen is loaded @@ -559,247 +488,209 @@ describe("Schema: Fields", () => { // Filter results cy.getBySelector(SELECTORS.FIELD_SELECTION_FILTER).as("fieldFilter"); - cy.get("@fieldFilter").should("exist").type("dropdown"); + cy.get("@fieldFilter").type("dropdown"); // Verify cy.getBySelector(SELECTORS.FIELD_SELECT_DROPDOWN).should("exist"); // Enter random string - cy.get("@fieldFilter").should("exist").type("asdasdasdasd"); + cy.get("@fieldFilter").type("asdasdasdasd"); // Show no results cy.getBySelector(SELECTORS.FIELD_SELECTION_EMPTY).should("exist"); // Close the modal - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).should("exist").click(); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_CLOSE).click(); }); it("Can filter fields in fields list", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Field to filter ${timestamp}`; - const fieldName = `field_to_filter_${timestamp}`; + const fieldLabel = `Field to filter ${suffix}`; + const fieldName = `field_to_filter_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select a field - cy.getBySelector(SELECTORS.FIELD_SELECT_NUMBER).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_NUMBER).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); - cy.get("input[name='label']") - .should("exist") - .should("have.value", fieldLabel); - cy.get("input[name='name']") - .should("exist") - .should("have.value", fieldName); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); + cy.get("input[name='label']", TIMEOUT).should("have.value", fieldLabel); + cy.get("input[name='name']", TIMEOUT).should("have.value", fieldName); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - cy.getBySelector(SELECTORS.FIELDS_LIST_FILTER).as("fieldListFilter"); // Filter fields - cy.get("@fieldListFilter").should("exist").type("field to filter"); + cy.get("@fieldListFilter").type("field to filter"); // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); // Enter a random filter keyword - cy.get("@fieldListFilter").should("exist").type("askljfkljfklsdjf"); + cy.get("@fieldListFilter").type("askljfkljfklsdjf"); // Field should not exist cy.getBySelector(`Field_${fieldName}`).should("not.exist"); cy.getBySelector(SELECTORS.FIELDS_LIST_NO_RESULTS).should("exist"); // Clear filter keyword - cy.get("@fieldListFilter").should("exist").type("{selectall} {backspace}"); + cy.get("@fieldListFilter").type("{selectall} {backspace}"); }); it("Can update a field", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - cy.intercept("/v1/content/models/**").as("updateField"); - - const origFieldLabel = `Update me ${timestamp}`; - const updatedFieldLabel = `Rename field ${timestamp}`; - const fieldName = `update_me_${timestamp}`; + const origFieldLabel = `Update me ${suffix}`; + const updatedFieldLabel = `Rename field ${suffix}`; + const fieldName = `update_me_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL) - .should("exist") - .type(origFieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).type(origFieldLabel); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); // Open update modal - cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`) - .should("exist") - .click(); - cy.getBySelector(`${SELECTORS.FIELD_DROPDOWN_EDIT}_${fieldName}`) - .should("exist") - .click(); + cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`).click(); + cy.getBySelector(`${SELECTORS.FIELD_DROPDOWN_EDIT}_${fieldName}`).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Update field label - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").clear(); - cy.getBySelector(SELECTORS.INPUT_LABEL) - .should("exist") - .type(updatedFieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear(); + cy.getBySelector(SELECTORS.INPUT_LABEL).type(updatedFieldLabel); // Save changes - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@updateField"); - cy.wait("@getFields"); - // Verify field name - cy.getBySelector(`FieldLabel_${fieldName}`) - .should("exist") - .should("contain", updatedFieldLabel); + cy.getBySelector(`FieldLabel_${fieldName}`).should( + "contain", + updatedFieldLabel + ); }); it("Can deactivate & reactivate a field via dropdown menu", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - cy.intercept("/v1/content/models/**").as("updateField"); - - const fieldLabel = `Deactivate me ${timestamp}`; - const fieldName = `deactivate_me_${timestamp}`; + const fieldLabel = `Deactivate me ${suffix}`; + const fieldName = `deactivate_me_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); // Deactivate the field - cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`) - .should("exist") - .click(); + cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`).click(); cy.getBySelector( `${SELECTORS.FIELD_DROPDOWN_DEACTIVATE_REACTIVATE}_${fieldName}` - ) - .should("exist") - .click(); - - cy.wait("@updateField"); - cy.wait("@getFields"); + ).click(); // Verify field is deactivated - cy.get(`[data-cy-status=Field_${fieldName}_inactive]`).should("exist"); + cy.get(`[data-cy-status=Field_${fieldName}_inactive]`, TIMEOUT).should( + "exist" + ); // Reactivate the field - cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`) - .should("exist") - .click({ force: true }); + cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`).click(TIMEOUT); cy.getBySelector( `${SELECTORS.FIELD_DROPDOWN_DEACTIVATE_REACTIVATE}_${fieldName}` - ) - .should("exist") - .click({ force: true }); - - cy.wait("@updateField"); - cy.wait("@getFields"); + ).click(TIMEOUT); // Verify field is deactivated - cy.get(`[data-cy-status=Field_${fieldName}_active]`).should("exist"); + cy.get(`[data-cy-status=Field_${fieldName}_active]`, TIMEOUT).should( + "exist" + ); }); it("Can deactivate a field via edit modal", () => { - cy.intercept("**/fields?showDeleted=true").as("getFields"); - cy.intercept("/v1/content/models/**").as("updateField"); - - const fieldLabel = `Deactivate me via modal ${timestamp}`; - const fieldName = `deactivate_me_via_modal_${timestamp}`; + const fieldLabel = `Deactivate me via modal ${suffix}`; + const fieldName = `deactivate_me_via_modal_${suffix}`; // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN) - .should("exist") - .click({ force: true }); + cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Select Text field - cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).should("exist").click(); + cy.getBySelector(SELECTORS.FIELD_SELECT_TEXT).click(); // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).should("exist").type(fieldLabel); + cy.getBySelector(SELECTORS.INPUT_LABEL).clear().type(fieldLabel); // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - cy.wait("@getFields"); - // Check if field exists cy.getBySelector(`Field_${fieldName}`).should("exist"); // Open update modal - cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`) - .should("exist") - .click(); - cy.getBySelector(`${SELECTORS.FIELD_DROPDOWN_EDIT}_${fieldName}`) - .should("exist") - .click(); + cy.getBySelector(`${SELECTORS.FIELD_MENU_BTN}_${fieldName}`).click(TIMEOUT); + cy.getBySelector(`${SELECTORS.FIELD_DROPDOWN_EDIT}_${fieldName}`).click( + TIMEOUT + ); cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); // Deactivate the field - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_DEACTIVATE_REACTIVATE) - .should("exist") - .click(); - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).should("exist").click(); - - cy.wait("@updateField"); - cy.wait("@getFields"); + cy.getBySelector(SELECTORS.ADD_FIELD_MODAL_DEACTIVATE_REACTIVATE).click( + TIMEOUT + ); + cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); // Verify field is deactivated - cy.get(`[data-cy-status=Field_${fieldName}_inactive]`).should("exist"); + cy.get(`[data-cy-status=Field_${fieldName}_inactive]`, TIMEOUT).should( + "exist" + ); }); it("Shows and hides system fields", () => { // Show system fields - cy.getBySelector(SELECTORS.SHOW_SYSTEM_FIELDS_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.SHOW_SYSTEM_FIELDS_BTN).click(TIMEOUT); cy.getBySelector(SELECTORS.SYSTEM_FIELDS).should("exist"); // Hide system fields - cy.getBySelector(SELECTORS.SHOW_SYSTEM_FIELDS_BTN).should("exist").click(); + cy.getBySelector(SELECTORS.SHOW_SYSTEM_FIELDS_BTN).click(); cy.getBySelector(SELECTORS.SYSTEM_FIELDS).should("not.exist"); }); }); + +function openSchemaModel(label) { + cy.visit("/schema"); + cy.get('[data-cy="schema-nav-templateset"] li') + .contains(TEST_DATA?.schema?.label) + .scrollIntoView() + .click(TIMEOUT); +} + +Cypress.Commands.add("openSchemaModelzz", (label) => { + cy.visit("/schema"); + cy.get('[data-cy="schema-nav-templateset"] li') + .contains(TEST_DATA?.schema?.label) + .scrollIntoView() + .click(TIMEOUT); +}); diff --git a/cypress/e2e/schema/models.spec.js b/cypress/e2e/schema/models.spec.js index 9484ae677e..4f48e75d73 100644 --- a/cypress/e2e/schema/models.spec.js +++ b/cypress/e2e/schema/models.spec.js @@ -1,19 +1,57 @@ const SEARCH_TERM = `cypress ${Date.now()}`; const TIMESTAMP = Date.now(); +const TIMEOUT = { timeout: 60_00 }; + +const LABELS = { + testModel: "Cypress Test Model", + testModelNew: "Cypress Test Model New", + testModelUpdate: "Cypress Test Model Updated", + testModelDelete: "Cypress Test Model Delete", + blockTestModel: "Block Test Model", +}; +const TEST_DATA = { + new: { + label: LABELS.testModelNew, + description: LABELS.testModelNew, + listed: true, + name: LABELS.testModelNew.replace(/ /g, "_").toLowerCase().trim(), + parentZUID: null, + type: "pageset", + }, + delete: { + label: LABELS.testModelDelete, + description: LABELS.testModelDelete, + listed: true, + name: LABELS.testModelDelete.replace(/ /g, "_").toLowerCase().trim(), + parentZUID: null, + type: "pageset", + }, +}; describe("Schema: Models", () => { before(() => { + cy.cleanStatusLabels(); + cy.deleteContentModels([...Object.values(LABELS)]); + + [TEST_DATA.delete, TEST_DATA.new].forEach((model) => { + cy.createContentModel(model); + }); + cy.waitOn("/v1/content/models*", () => { cy.visit("/schema"); }); }); + after(() => { + cy.deleteContentModels([...Object.values(LABELS)]); + }); + it("Opens creation model with model type selector when triggered from All Models", () => { - cy.getBySelector("create-model-button-all-models").click(); + cy.getBySelector("create-model-button-all-models").click(TIMEOUT); cy.contains("Select Model Type").should("be.visible"); cy.get("body").type("{esc}"); }); it("Opens creation model with model type pre-selected when triggered from Sidebar", () => { - cy.getBySelector(`create-model-button-sidebar-templateset`).click(); + cy.getBySelector(`create-model-button-sidebar-templateset`).click(TIMEOUT); cy.contains("Create Single Page Model").should("be.visible"); cy.get("body").type("{esc}"); cy.getBySelector(`create-model-button-sidebar-pageset`).click(); @@ -24,14 +62,25 @@ describe("Schema: Models", () => { cy.get("body").type("{esc}"); }); it("Creates model", () => { - cy.getBySelector(`create-model-button-all-models`).click(); + cy.visit("/schema"); + + //make sure to load model parent dropdown + cy.intercept("**/env/nav").as("getModelParent"); + + cy.getBySelector(`create-model-button-all-models`).click(TIMEOUT); cy.contains("Multi Page Model").click(); cy.contains("Next").click(); - cy.contains("Display Name").next().type("Cypress Test Model"); + + cy.wait("@getModelParent"); + + cy.contains("Display Name").next().type(LABELS.testModel); cy.contains("Reference ID") .next() .find("input") - .should("have.value", "cypress_test_model"); + .should( + "have.value", + LABELS.testModel.replace(/ /g, "_").toLowerCase().trim() + ); cy.contains("Model Parent").next().click(); @@ -41,33 +90,31 @@ describe("Schema: Models", () => { cy.get(".MuiAutocomplete-popper") .contains("Cypress test (Group with visible fields in list)") - .click(); + .click({ timeout: 60_000 }); cy.contains("Description").next().type("Cypress test model description"); - cy.get(".MuiDialog-container").within(() => { - cy.contains("Create Model").click(); - }); - cy.intercept("POST", "/models"); - cy.intercept("GET", "/models"); + + cy.get('[data-cy="create-model-submit-button"]').click(); }); it("Renames model", () => { - cy.getBySelector(`model-header-menu`).click(); + openBlockModel(TEST_DATA.new.label); + cy.getBySelector(`model-header-menu`).click(TIMEOUT); cy.contains("Rename Model").click(); cy.get(".MuiDialog-container").within(() => { cy.get("label").contains("Display Name").next().type(" Updated"); cy.get("label").contains("Reference ID").next().type("_updated"); cy.contains("Save").click(); }); - cy.intercept("PUT", "/models"); - cy.intercept("GET", "/models"); - cy.contains("Cypress Test Model Updated").should("exist"); + + cy.contains(TEST_DATA.new.label).should("exist"); }); it("Deletes model", () => { + openBlockModel(TEST_DATA.delete.label); cy.getBySelector(`model-header-menu`).click(); cy.contains("Delete Model").click(); cy.get(".MuiDialog-container").within(() => { - cy.get(".MuiOutlinedInput-root").type("Cypress Test Model Updated"); + cy.get(".MuiOutlinedInput-root").type(TEST_DATA.delete.label); }); cy.contains("Delete Forever").click(); }); @@ -129,19 +176,27 @@ describe("Schema: Models", () => { cy.getBySelector(`create-model-button-all-models`).click(); cy.contains("Block Model").click(); cy.contains("Next").click(); - cy.contains("Display Name").next().type(`Block Test Model ${TIMESTAMP}`); + cy.contains("Display Name").next().type(LABELS.blockTestModel); cy.contains("Reference ID") .next() .find("input") - .should("have.value", `block_test_model_${TIMESTAMP}`); + .should( + "have.value", + LABELS.blockTestModel.replace(/ /g, "_").toLowerCase().trim() + ); cy.contains("Description").next().type("Block test model description"); cy.get(".MuiDialog-container").within(() => { cy.contains("Create Model").click(); }); - cy.intercept("POST", "/models"); - cy.intercept("GET", "/models"); - - cy.contains(`Block Test Model ${TIMESTAMP}`).should("exist"); + cy.contains(`Block Test Model`).should("exist"); }); }); + +function openBlockModel(label) { + cy.visit("/schema"); + cy.get('[data-cy="schema-nav-pageset"] li') + .contains(label) + .scrollIntoView() + .click(TIMEOUT); +} diff --git a/cypress/e2e/schema/pages/SchemaPage.js b/cypress/e2e/schema/pages/SchemaPage.js index 9d6defec5b..a57d2d3105 100644 --- a/cypress/e2e/schema/pages/SchemaPage.js +++ b/cypress/e2e/schema/pages/SchemaPage.js @@ -1,3 +1,4 @@ +const TIMEOUT = { timeout: 30_000 }; class SchemaPage { visit() { cy.visit("/schema"); @@ -28,7 +29,7 @@ class SchemaPage { } deleteModel(name) { - cy.contains(name).click(); + cy.contains(name).click(TIMEOUT); this.modelHeaderMenuButton.click(); this.modelMenuDeleteButton.click(); this.deleteModelConfirmationInput.type(name); @@ -36,14 +37,18 @@ class SchemaPage { } addSingleLineTextFieldWithDefaultValue(modelName, fieldName, defaultValue) { - cy.contains(modelName).click(); - this.addFieldButton.click(); - cy.contains("Single Line Text").click(); + cy.intercept("POST", "**/content/models/*/fields").as("createFields"); + cy.contains(modelName).click(TIMEOUT); + this.addFieldButton.click(TIMEOUT); + cy.get('[data-cy="FieldItem_text"]') + .contains("Single Line Text") + .click(TIMEOUT); cy.getBySelector("FieldFormInput_label").type(fieldName); this.rulesTabButton.click(); cy.getBySelector("DefaultValueCheckbox").click(); cy.getBySelector("DefaultValueInput").type(defaultValue); cy.getBySelector("FieldFormAddFieldBtn").click(); + cy.wait(["@createFields"]); } } diff --git a/cypress/e2e/settings/workflows.spec.js b/cypress/e2e/settings/workflows.spec.js index 13ff309ede..7111a54f54 100644 --- a/cypress/e2e/settings/workflows.spec.js +++ b/cypress/e2e/settings/workflows.spec.js @@ -5,7 +5,7 @@ import { colorMenu, } from "../../../src/apps/settings/src/app/views/User/Workflows/constants"; -const TIMEOUT = { timeout: 40_000 }; +const TIMEOUT = { timeout: 50_000 }; const INSTANCE_API_ENDPOINT = `${ CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL @@ -93,13 +93,13 @@ const TEST_DATA = { }; before(() => { - // cy.cleanTestData(); + cy.cleanStatusLabels(); cy.createTestData(); }); -// after(() => { -// cy.cleanTestData(); -// }); +after(() => { + cy.cleanStatusLabels(); +}); describe("Restricted User", { retries: 1 }, () => { it("displays restricted access message and admin profiles", () => { @@ -233,7 +233,8 @@ describe("Create New Status Label", { retries: 1 }, () => { cy.get('[data-cy="active-labels-container"] [data-cy="status-label"]') .contains(TEST_DATA.new.name) .parents('[data-cy="status-label"]') - .should("have.css", "background-color", FOCUSED_LABEL_COLOR, TIMEOUT); + .should("have.css", "background-color") + .and("eq", FOCUSED_LABEL_COLOR); }); it("Clicking outside the focused label restores it to its default state.", () => { @@ -465,29 +466,6 @@ Cypress.Commands.add("goToWorkflowsPage", () => { cy.get('[data-cy="workflows-authorized-page"]', { timeout: 60_000 }); }); -Cypress.Commands.add("cleanTestData", function () { - const testLabels = [ - TEST_DATA?.new?.name, - TEST_DATA?.edited?.name, - TEST_DATA?.temp1?.name, - TEST_DATA?.temp2?.name, - TEST_DATA?.temp3?.name, - ]; - - cy.apiRequest({ - url: `${INSTANCE_API_ENDPOINT}/env/labels?showDeleted=true`, - }).then((response) => { - response?.data - ?.filter((label) => !label?.deletedAt && testLabels.includes(label?.name)) - .forEach((label) => { - cy.apiRequest({ - url: `${INSTANCE_API_ENDPOINT}/env/labels/${label.ZUID}`, - method: "DELETE", - }); - }); - }); -}); - Cypress.Commands.add("createTestData", () => { const testLabels = [TEST_DATA?.temp1, TEST_DATA?.temp2, TEST_DATA?.temp3]; testLabels.forEach((label) => { diff --git a/cypress/jsconfig.json b/cypress/jsconfig.json new file mode 100644 index 0000000000..6d2e8b5c27 --- /dev/null +++ b/cypress/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "types": ["cypress", "node"], + "jsx": "react-jsx", + "resolveJsonModule": true + } +} diff --git a/cypress/support/cleanup.ts b/cypress/support/cleanup.ts deleted file mode 100644 index fad91b2e91..0000000000 --- a/cypress/support/cleanup.ts +++ /dev/null @@ -1,73 +0,0 @@ -import instanceZUID from "../../src/utility/instanceZUID"; -import CONFIG from "../../src/shell/app.config"; -import { StatusLabelQuery } from "src/shell/services/types"; - -const API_ENDPOINTS = { - devInstance: `${ - CONFIG[process.env.NODE_ENV as keyof typeof CONFIG]?.API_INSTANCE_PROTOCOL - }${instanceZUID}${ - CONFIG[process.env.NODE_ENV as keyof typeof CONFIG]?.API_INSTANCE - }`, -}; - -export function preCleanUp(token: string) { - statusLabelCleanUp(token); -} - -// ========================= CLEANUP FUNCTIONS STARTS HERE =========================// - -// Status Labels -function statusLabelCleanUp(token: string) { - const DEFAULT_STATUS_LABELS_ZUIDS = [ - "36-14b315-4pp20v3d", - "36-14b315-d24ft", - "36-n33d5-23v13w", - ]; - const DEFAULT_STATUS_LABELS_NAMES = ["Approved", "Needs Review", "Draft"]; - sendRequest({ - method: "GET", - url: `${API_ENDPOINTS.devInstance}/env/labels?showDeleted=true`, - token: token, - }).then((response) => { - response?.data - ?.filter( - (resData: StatusLabelQuery) => - !resData?.deletedAt && - !DEFAULT_STATUS_LABELS_NAMES.includes(resData?.name) && - !DEFAULT_STATUS_LABELS_ZUIDS.includes(resData?.ZUID) - ) - .forEach((labelForDelete: StatusLabelQuery) => { - sendRequest({ - url: `${API_ENDPOINTS.devInstance}/env/labels/${labelForDelete.ZUID}`, - method: "DELETE", - token: token, - }); - }); - }); -} - -// ========================= CLEANUP FUNCTIONS ENDS HERE =========================// - -function sendRequest({ - method = "GET", - url = "", - body = undefined, - token = "", -}: { - method?: string; - url: string; - body?: any; - token: string; -}) { - return cy - .request({ - method, - url, - headers: { authorization: `Bearer ${token}` }, - ...(body ? { body: body } : {}), - }) - .then((response) => ({ - status: response?.isOkStatusCode ? "success" : "error", - data: response?.body?.data, - })); -} diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 944014bed1..ae95b44d0b 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,17 +1,11 @@ import instanceZUID from "../../src/utility/instanceZUID"; import CONFIG from "../../src/shell/app.config"; -const INSTANCE_API_ENDPOINT = `${ - CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL -}${instanceZUID}${CONFIG?.[process.env.NODE_ENV]?.API_INSTANCE}`; - -const DEFAULT_STATUS_LABELS_ZUIDS = [ - "36-14b315-4pp20v3d", - "36-14b315-d24ft", - "36-n33d5-23v13w", -]; - -const DEFAULT_STATUS_LABELS_NAMES = ["Approved", "Needs Review", "Draft"]; +const API_ENDPOINTS = { + devInstance: `${ + CONFIG[process.env.NODE_ENV]?.API_INSTANCE_PROTOCOL + }${instanceZUID}${CONFIG[process.env.NODE_ENV]?.API_INSTANCE}`, +}; Cypress.Commands.add("login", () => { const formBody = new FormData(); @@ -80,33 +74,84 @@ Cypress.Commands.add( method, headers: { authorization: `Bearer ${token}` }, ...(body ? { body: body } : {}), + failOnStatusCode: false, }) - .then((response) => ({ - status: response?.isOkStatusCode ? "success" : "error", - data: response?.body?.data, - })); + .then((response) => { + return { + status: response?.isOkStatusCode ? "success" : "error", + data: response?.body?.data, + }; + }); }); } ); -Cypress.Commands.add("workflowStatusLabelCleanUp", function () { +Cypress.Commands.add("deleteStatusLabels", (labels = []) => { + cy.log(`[CLEAN UP] Status Labels`); cy.apiRequest({ - url: `${INSTANCE_API_ENDPOINT}/env/labels?showDeleted=true`, + url: `${API_ENDPOINTS}/env/labels?showDeleted=true`, }).then((response) => { - console.debug("workflowStatusLabelCleanUp | response LABELS: ", response); + response?.data + ?.filter( + (resData) => !resData?.deletedAt && [...labels].includes(resData?.name) + ) + .forEach((labelForDelete) => { + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/env/labels/${labelForDelete.ZUID}`, + method: "DELETE", + }); + }); + }); +}); +Cypress.Commands.add("cleanStatusLabels", () => { + const DEFAULT_STATUS_LABELS_ZUIDS = [ + "36-14b315-4pp20v3d", + "36-14b315-d24ft", + "36-n33d5-23v13w", + ]; + const DEFAULT_STATUS_LABELS_NAMES = ["Approved", "Needs Review", "Draft"]; + cy.log(`[CLEAN UP] Status Labels`); + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/env/labels?showDeleted=true`, + }).then((response) => { response?.data ?.filter( - (label) => - !label?.deletedAt && - !DEFAULT_STATUS_LABELS_NAMES.includes(label?.name) && - !DEFAULT_STATUS_LABELS_ZUIDS.includes(label?.ZUID) + (resData) => + !resData?.deletedAt && + !DEFAULT_STATUS_LABELS_NAMES.includes(resData?.name) && + !DEFAULT_STATUS_LABELS_ZUIDS.includes(resData?.ZUID) ) - .forEach((label) => { + .forEach((labelForDelete) => { + console.debug("labelForDelete | labelForDelete: ", labelForDelete); cy.apiRequest({ - url: `${INSTANCE_API_ENDPOINT}/env/labels/${label.ZUID}`, + url: `${API_ENDPOINTS.devInstance}/env/labels/${labelForDelete.ZUID}`, method: "DELETE", }); }); }); }); + +Cypress.Commands.add("deleteContentModels", (models = []) => { + cy.log(`[CLEAN UP] Content Models`); + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/content/models`, + }).then((response) => { + response?.data + ?.filter((resData) => [...models].includes(resData?.label)) + .forEach((forDelete) => { + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/content/models/${forDelete.ZUID}`, + method: "DELETE", + }); + }); + }); +}); + +Cypress.Commands.add("createContentModel", (payload) => { + cy.apiRequest({ + url: `${API_ENDPOINTS.devInstance}/content/models`, + method: "POST", + body: payload, + }); +}); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 57cef4de76..62057a245e 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -16,9 +16,6 @@ // Import commands.js using ES2015 syntax: import "./commands"; import "cypress-iframe"; -import { preCleanUp } from "./cleanup"; - -let preCleanupHasExeciuted = false; // @see https://docs.cypress.io/api/cypress-api/cookies.html#Set-global-default-cookies // Cypress.Cookies.defaults({ @@ -48,17 +45,6 @@ before(() => { // Blocks the api call to render the announcement popup cy.blockAnnouncements(); - - if (!preCleanupHasExeciuted) { - cy.getCookie(Cypress.env("COOKIE_NAME")) - .then((cookie) => { - preCleanUp(cookie?.value); - }) - .then(() => { - // SET TO FALSE TO EXECUTE CLEANUP BEFORE EACH TEST - preCleanupHasExeciuted = false; - }); - } }); // Before each test in spec diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts new file mode 100644 index 0000000000..4bb837a188 --- /dev/null +++ b/cypress/support/index.d.ts @@ -0,0 +1,51 @@ +import "./commands"; + +interface AwaitResponseOptions { + alias?: string; + timeout?: number; + interval?: number; +} + +interface AwaitResponseResult { + started: number; + finished: number; + waited: number; +} + +declare global { + namespace Cypress { + interface Chainable { + waitOn(path: string, cb: () => void): Chainable>; + login(): Chainable>; + getBySelector( + selector: string, + ...args: any[] + ): Chainable>; + blockLock(): Chainable>; + assertClipboardValue(value: string): Chainable>; + blockAnnouncements(): Chainable>; + + apiRequest(options: { + url: string; + method?: string; + body?: any; + }): Chainable; + // workflowStatusLabelCleanUp(): Chainable>; + cleanTestData(): Chainable>; + createTestData(): Chainable>; + goToWorkflowsPage(): Chainable>; + getStatusLabels(): Chainable; + // deleteStatusLabels(labels: string[]): Chainable; + cleanStatusLabels(): Chainable; + deleteStatusLabels(labels: string[]): Chainable; + deleteContentModels(models: string[]): Chainable; + createContentModel({ + description: string, + label: string, + type: string, + name: string, + listed: boolean, + }): Chainable; + } + } +} diff --git a/cypress/support/index.ts b/cypress/support/index.ts deleted file mode 100644 index 472f0d5791..0000000000 --- a/cypress/support/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import "./commands"; - -declare global { - namespace Cypress { - interface Chainable { - /** - * Custom command to select DOM element by data-cy attribute. - * @example cy.dataCy('greeting') - */ - waitOn(path: string, cb: () => void): Chainable>; - login(): Chainable>; - getBySelector( - selector: string, - ...args: any[] - ): Chainable>; - blockLock(): Chainable>; - assertClipboardValue(value: string): Chainable>; - blockAnnouncements(): Chainable>; - apiRequest( - options: Partial - ): Chainable>; - workflowStatusLabelCleanUp(): Chainable>; - cleanTestData(): Chainable>; - createTestData(): Chainable>; - goToWorkflowsPage(): Chainable>; - getStatusLabel(): Chainable>; - } - } -} diff --git a/tsconfig.json b/tsconfig.json index 25243003cf..3c6bafb8b1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src", "index.d.ts", "cypress"], + "include": ["src", "index.d.ts"], "compilerOptions": { "noImplicitAny": true, "allowJs": true, From 42219f4c13d347c61af147cbc53b97a81c60f6b8 Mon Sep 17 00:00:00 2001 From: geodem Date: Wed, 15 Jan 2025 22:40:07 +0800 Subject: [PATCH 59/59] Updaated Field.spec --- cypress/e2e/schema/field.spec.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/cypress/e2e/schema/field.spec.js b/cypress/e2e/schema/field.spec.js index 3215234406..69118af28c 100644 --- a/cypress/e2e/schema/field.spec.js +++ b/cypress/e2e/schema/field.spec.js @@ -440,34 +440,6 @@ describe("Schema: Fields", () => { cy.getBySelector(`Field_${fieldName}`).should("exist"); }); - it.skip("Creates a Block field", () => { - // cy.intercept("**/fields?showDeleted=true").as("getFields"); - - const fieldLabel = `Block Selector ${suffix}`; - const fieldName = `block_selector_${suffix}`; - - // Open the add field modal - cy.getBySelector(SELECTORS.ADD_FIELD_BTN).click(TIMEOUT); - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("exist"); - - // Select one-to-one relationship field - cy.getBySelector(SELECTORS.FIELD_SELECT_BLOCK_SELECTOR).click(TIMEOUT); - - // Fill up fields - cy.getBySelector(SELECTORS.INPUT_LABEL).type(fieldLabel); - cy.get("input[name='label']").should("exist").and("have.value", fieldLabel); - cy.get("input[name='name']").should("exist").and("have.value", fieldName); - - // Click done - cy.getBySelector(SELECTORS.SAVE_FIELD_BUTTON).click(); - cy.getBySelector(SELECTORS.ADD_FIELD_MODAL).should("not.exist"); - - // cy.wait("@getFields"); - - // Check if field exists - cy.getBySelector(`Field_${fieldName}`).should("exist"); - }); - it("Creates a field via add another field button", () => { const values = { number: {