From 29c0d8ab57119bf34247eaae49393a289422698d Mon Sep 17 00:00:00 2001 From: Daniel Inge Date: Wed, 16 Aug 2023 17:30:50 -0400 Subject: [PATCH 1/5] Enable users to change the ordering of environments --- .../controllers/v2/environmentController.ts | 43 +++++++++++- backend/src/routes/v2/environment.ts | 18 +++++ frontend/src/hooks/api/workspace/index.tsx | 1 + frontend/src/hooks/api/workspace/queries.tsx | 16 +++++ frontend/src/hooks/api/workspace/types.ts | 9 +++ .../EnvironmentSection/EnvironmentTable.tsx | 69 ++++++++++++++++++- 6 files changed, 151 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index 8cbf1a229..99aa91a10 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -85,6 +85,43 @@ export const createWorkspaceEnvironment = async ( }); }; +/** + * Swaps the ordering of two environments in the database. This is purely for asthetic purposes. + * @param req + * @param res + * @returns + */ +export const reorderWorkspaceEnvironments = async ( + req: Request, + res: Response +) => { + const { workspaceId } = req.params; + const { environmentSlug, environmentName, otherEnvironmentSlug, otherEnvironmentName } = req.body; + + // atomic update the env to avoid conflict + const workspace = await Workspace.findById(workspaceId).exec(); + if (!workspace) { + throw new Error("Failed to create workspace environment"); + } + + const environmentIndex = workspace.environments.findIndex((env) => env.name === environmentName && env.slug === environmentSlug) + const otherEnvironmentIndex = workspace.environments.findIndex((env) => env.name === otherEnvironmentName && env.slug === otherEnvironmentSlug) + + if (environmentIndex === undefined || otherEnvironmentIndex === undefined) { + throw new Error("environment or otherEnvironment couldn't be found") + } + + // swap the order of the environments + [workspace.environments[environmentIndex], workspace.environments[otherEnvironmentIndex]] = [workspace.environments[otherEnvironmentIndex], workspace.environments[environmentIndex]] + + await workspace.save() + + return res.status(200).send({ + message: "Successfully reordered environments", + workspace: workspaceId, + }); +}; + /** * Rename workspace environment with new name and slug of a workspace with [workspaceId] * Old slug [oldEnvironmentSlug] must be provided @@ -124,7 +161,7 @@ export const renameWorkspaceEnvironment = async ( if (envIndex === -1) { throw new Error("Invalid environment given"); } - + const oldEnvironment = workspace.environments[envIndex]; workspace.environments[envIndex].name = environmentName; @@ -159,7 +196,7 @@ export const renameWorkspaceEnvironment = async ( { $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } }, { arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] } ); - + await EEAuditLogService.createAuditLog( req.authData, { @@ -210,7 +247,7 @@ export const deleteWorkspaceEnvironment = async ( if (envIndex === -1) { throw new Error("Invalid environment given"); } - + const oldEnvironment = workspace.environments[envIndex]; workspace.environments.splice(envIndex, 1); diff --git a/backend/src/routes/v2/environment.ts b/backend/src/routes/v2/environment.ts index e9e7fcad3..133f0d717 100644 --- a/backend/src/routes/v2/environment.ts +++ b/backend/src/routes/v2/environment.ts @@ -46,6 +46,24 @@ router.put( environmentController.renameWorkspaceEnvironment ); +router.patch( + "/:workspaceId/environments", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT], + }), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + locationWorkspaceId: "params", + }), + param("workspaceId").exists().trim(), + body("environmentSlug").exists().isString().trim(), + body("environmentName").exists().isString().trim(), + body("otherEnvironmentSlug").exists().isString().trim(), + body("otherEnvironmentName").exists().isString().trim(), + validateRequest, + environmentController.reorderWorkspaceEnvironments +); + router.delete( "/:workspaceId/environments", requireAuth({ diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/workspace/index.tsx index 42d3467d1..fe63c2ad2 100644 --- a/frontend/src/hooks/api/workspace/index.tsx +++ b/frontend/src/hooks/api/workspace/index.tsx @@ -16,6 +16,7 @@ export { useGetWorkspaceUsers, useNameWorkspaceSecrets, useRenameWorkspace, + useReorderWsEnvironment, useToggleAutoCapitalization, useUpdateUserWorkspaceRole, useUpdateWsEnvironment} from "./queries"; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index e5c2d6c96..12e571ad9 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -13,6 +13,7 @@ import { GetWsEnvironmentDTO, NameWorkspaceSecretsDTO, RenameWorkspaceDTO, + ReorderEnvironmentsDTO, ToggleAutoCapitalizationDTO, UpdateEnvironmentDTO, Workspace, @@ -244,6 +245,21 @@ export const useCreateWsEnvironment = () => { }); }; +export const useReorderWsEnvironment = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, ReorderEnvironmentsDTO>({ + mutationFn: ({ workspaceID, environmentSlug, environmentName, otherEnvironmentSlug, otherEnvironmentName}) => { + return apiRequest.patch(`/api/v2/workspace/${workspaceID}/environments`, { + environmentSlug, environmentName, otherEnvironmentSlug, otherEnvironmentName + }); + }, + onSuccess: () => { + queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); + } + }); +}; + export const useUpdateWsEnvironment = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts index 82a89a9ba..f6728988a 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/workspace/types.ts @@ -46,6 +46,15 @@ export type CreateEnvironmentDTO = { environmentName: string; }; +export type ReorderEnvironmentsDTO = { + workspaceID: string; + environmentSlug: string; + environmentName: string; + otherEnvironmentSlug: string; + otherEnvironmentName: string; + +}; + export type UpdateEnvironmentDTO = { workspaceID: string; oldEnvironmentSlug: string; diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx index e749e4e9a..bb867da51 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx @@ -1,6 +1,7 @@ -import { faPencil, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { faArrowDown,faArrowUp, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { EmptyState, IconButton, @@ -14,6 +15,9 @@ import { Tr } from "@app/components/v2"; import { useWorkspace } from "@app/context"; +import { + useReorderWsEnvironment +} from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { @@ -31,6 +35,43 @@ type Props = { export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { const { currentWorkspace, isLoading } = useWorkspace(); + const { createNotification } = useNotificationContext(); + const reorderWsEnvironment = useReorderWsEnvironment(); + + const reorderEnvs = async (moveUp: boolean, name: string, slug: string) => { + try { + if (!currentWorkspace?._id) return; + + const indexOfEnv = currentWorkspace.environments.findIndex((env) => env.name === name && env.slug === slug); + + // check that this reordering is possible + if (indexOfEnv === 0 && moveUp || indexOfEnv === currentWorkspace.environments.length - 1 && !moveUp) { + return + } + + const indexToSwap = moveUp ? indexOfEnv - 1 : indexOfEnv + 1 + + await reorderWsEnvironment.mutateAsync({ + workspaceID: currentWorkspace._id, + environmentSlug: slug, + environmentName: name, + otherEnvironmentSlug: currentWorkspace.environments[indexToSwap].slug, + otherEnvironmentName: currentWorkspace.environments[indexToSwap].name + }); + + createNotification({ + text: "Successfully re-ordered environments", + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to re-order environments", + type: "error" + }); + } + }; + return ( @@ -45,11 +86,35 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { {isLoading && } {!isLoading && currentWorkspace && - currentWorkspace.environments.map(({ name, slug }) => ( + currentWorkspace.environments.map(({ name, slug }, pos) => (
{name} {slug} + { + reorderEnvs(false, name, slug) + }} + colorSchema="primary" + variant="plain" + ariaLabel="update" + isDisabled={pos === currentWorkspace.environments.length - 1} + > + + + { + reorderEnvs(true, name, slug) + }} + colorSchema="primary" + variant="plain" + ariaLabel="update" + isDisabled={pos === 0} + > + + { From a7f52a929836e7157b67f0940231d98bdadc4e5a Mon Sep 17 00:00:00 2001 From: Daniel Inge Date: Wed, 16 Aug 2023 17:36:07 -0400 Subject: [PATCH 2/5] Small formatting fixes --- backend/src/controllers/v2/environmentController.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index 99aa91a10..ab553aef3 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -161,7 +161,6 @@ export const renameWorkspaceEnvironment = async ( if (envIndex === -1) { throw new Error("Invalid environment given"); } - const oldEnvironment = workspace.environments[envIndex]; workspace.environments[envIndex].name = environmentName; @@ -196,7 +195,6 @@ export const renameWorkspaceEnvironment = async ( { $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } }, { arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] } ); - await EEAuditLogService.createAuditLog( req.authData, { @@ -247,7 +245,6 @@ export const deleteWorkspaceEnvironment = async ( if (envIndex === -1) { throw new Error("Invalid environment given"); } - const oldEnvironment = workspace.environments[envIndex]; workspace.environments.splice(envIndex, 1); From 0073fe459e182ecf466f7e3ac85cad16a67eefbb Mon Sep 17 00:00:00 2001 From: Daniel Inge Date: Wed, 16 Aug 2023 17:37:41 -0400 Subject: [PATCH 3/5] Fix typo --- backend/src/controllers/v2/environmentController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index ab553aef3..dcc14a64a 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -86,7 +86,7 @@ export const createWorkspaceEnvironment = async ( }; /** - * Swaps the ordering of two environments in the database. This is purely for asthetic purposes. + * Swaps the ordering of two environments in the database. This is purely for aesthetic purposes. * @param req * @param res * @returns From c0ce92cf3d1809b9bad42142004da299eba5254d Mon Sep 17 00:00:00 2001 From: Daniel Inge Date: Wed, 16 Aug 2023 17:42:39 -0400 Subject: [PATCH 4/5] Formattting fix --- backend/src/controllers/v2/environmentController.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index dcc14a64a..5eb923e7b 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -161,6 +161,7 @@ export const renameWorkspaceEnvironment = async ( if (envIndex === -1) { throw new Error("Invalid environment given"); } + const oldEnvironment = workspace.environments[envIndex]; workspace.environments[envIndex].name = environmentName; @@ -195,6 +196,7 @@ export const renameWorkspaceEnvironment = async ( { $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } }, { arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] } ); + await EEAuditLogService.createAuditLog( req.authData, { @@ -245,6 +247,7 @@ export const deleteWorkspaceEnvironment = async ( if (envIndex === -1) { throw new Error("Invalid environment given"); } + const oldEnvironment = workspace.environments[envIndex]; workspace.environments.splice(envIndex, 1); From 2b65f650630c51817450e517f8307b8761e9fe3c Mon Sep 17 00:00:00 2001 From: Daniel Inge Date: Fri, 18 Aug 2023 17:33:59 -0400 Subject: [PATCH 5/5] Rename things and fix bug in error checking --- backend/src/controllers/v2/environmentController.ts | 6 +++--- .../components/EnvironmentSection/EnvironmentTable.tsx | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index 5eb923e7b..4e7d39fab 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -101,14 +101,14 @@ export const reorderWorkspaceEnvironments = async ( // atomic update the env to avoid conflict const workspace = await Workspace.findById(workspaceId).exec(); if (!workspace) { - throw new Error("Failed to create workspace environment"); + throw BadRequestError({message: "Couldn't load workspace"}); } const environmentIndex = workspace.environments.findIndex((env) => env.name === environmentName && env.slug === environmentSlug) const otherEnvironmentIndex = workspace.environments.findIndex((env) => env.name === otherEnvironmentName && env.slug === otherEnvironmentSlug) - if (environmentIndex === undefined || otherEnvironmentIndex === undefined) { - throw new Error("environment or otherEnvironment couldn't be found") + if (environmentIndex === -1 || otherEnvironmentIndex === -1) { + throw BadRequestError({message: "environment or otherEnvironment couldn't be found"}) } // swap the order of the environments diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx index bb867da51..e799f9e9a 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentTable.tsx @@ -38,18 +38,18 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { const { createNotification } = useNotificationContext(); const reorderWsEnvironment = useReorderWsEnvironment(); - const reorderEnvs = async (moveUp: boolean, name: string, slug: string) => { + const handleReorderEnv= async (shouldMoveUp: boolean, name: string, slug: string) => { try { if (!currentWorkspace?._id) return; const indexOfEnv = currentWorkspace.environments.findIndex((env) => env.name === name && env.slug === slug); // check that this reordering is possible - if (indexOfEnv === 0 && moveUp || indexOfEnv === currentWorkspace.environments.length - 1 && !moveUp) { + if (indexOfEnv === 0 && shouldMoveUp || indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp) { return } - const indexToSwap = moveUp ? indexOfEnv - 1 : indexOfEnv + 1 + const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1 await reorderWsEnvironment.mutateAsync({ workspaceID: currentWorkspace._id, @@ -94,7 +94,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { { - reorderEnvs(false, name, slug) + handleReorderEnv(false, name, slug) }} colorSchema="primary" variant="plain" @@ -106,7 +106,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { { - reorderEnvs(true, name, slug) + handleReorderEnv(true, name, slug) }} colorSchema="primary" variant="plain"