From 29c0d8ab57119bf34247eaae49393a289422698d Mon Sep 17 00:00:00 2001 From: Daniel Inge Date: Wed, 16 Aug 2023 17:30:50 -0400 Subject: [PATCH] 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} + > + + {