From a8c3b7383d7be5c14c02d648c6f50867ae617720 Mon Sep 17 00:00:00 2001 From: Huiwen Date: Thu, 23 Nov 2023 09:28:37 +0000 Subject: [PATCH] Update dashboard --- .../SelectWorkspaceClassComponent.tsx | 10 ++-- .../data/workspaces/list-workspaces-query.ts | 18 +++++- .../toggle-workspace-pinned-mutation.ts | 44 -------------- .../toggle-workspace-shared-mutation.ts | 57 ------------------- .../update-workspace-description-mutation.ts | 47 --------------- .../workspaces/update-workspace-mutation.ts | 26 +++++++++ .../workspaces/workspace-classes-query.ts | 9 +-- .../src/workspaces/RenameWorkspaceModal.tsx | 24 ++++---- .../src/workspaces/WorkspaceOverflowMenu.tsx | 17 +++--- 9 files changed, 73 insertions(+), 179 deletions(-) delete mode 100644 components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts delete mode 100644 components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts delete mode 100644 components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts create mode 100644 components/dashboard/src/data/workspaces/update-workspace-mutation.ts diff --git a/components/dashboard/src/components/SelectWorkspaceClassComponent.tsx b/components/dashboard/src/components/SelectWorkspaceClassComponent.tsx index 25712a409b94c8..a33ac56cadeacf 100644 --- a/components/dashboard/src/components/SelectWorkspaceClassComponent.tsx +++ b/components/dashboard/src/components/SelectWorkspaceClassComponent.tsx @@ -4,11 +4,11 @@ * See License.AGPL.txt in the project root for license information. */ -import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class"; import { FC, useCallback, useEffect, useMemo } from "react"; -import WorkspaceClass from "../icons/WorkspaceClass.svg"; +import WorkspaceClassIcon from "../icons/WorkspaceClass.svg"; import { Combobox, ComboboxElement, ComboboxSelectedItem } from "./podkit/combobox/Combobox"; import { useWorkspaceClasses } from "../data/workspaces/workspace-classes-query"; +import { WorkspaceClass } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; interface SelectWorkspaceClassProps { selectedWorkspaceClass?: string; @@ -78,7 +78,7 @@ export default function SelectWorkspaceClassComponent({ } type WorkspaceClassDropDownElementSelectedProps = { - wsClass?: SupportedWorkspaceClass; + wsClass?: WorkspaceClass; loading?: boolean; }; @@ -90,7 +90,7 @@ const WorkspaceClassDropDownElementSelected: FC{title}} @@ -106,7 +106,7 @@ const WorkspaceClassDropDownElementSelected: FC diff --git a/components/dashboard/src/data/workspaces/list-workspaces-query.ts b/components/dashboard/src/data/workspaces/list-workspaces-query.ts index 0c3f2c5b5815db..2b3a48de7e2c38 100644 --- a/components/dashboard/src/data/workspaces/list-workspaces-query.ts +++ b/components/dashboard/src/data/workspaces/list-workspaces-query.ts @@ -4,7 +4,7 @@ * See License.AGPL.txt in the project root for license information. */ -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useCurrentOrg } from "../organizations/orgs-query"; import { workspaceClient } from "../../service/public-api"; import { Workspace } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; @@ -57,3 +57,19 @@ export function getListWorkspacesQueryKey(orgId?: string) { } return ["workspaces", "list", orgId]; } + +export const useUpdateWorkspaceInCache = () => { + const queryClient = useQueryClient(); + const org = useCurrentOrg(); + return (newWorkspace: Workspace) => { + const queryKey = getListWorkspacesQueryKey(org.data?.id); + queryClient.setQueryData(queryKey, (oldWorkspacesData) => { + return oldWorkspacesData?.map((info) => { + if (info.id !== newWorkspace.id) { + return info; + } + return newWorkspace; + }); + }); + }; +}; diff --git a/components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts b/components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts deleted file mode 100644 index 3308310dc2f6c6..00000000000000 --- a/components/dashboard/src/data/workspaces/toggle-workspace-pinned-mutation.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2023 Gitpod GmbH. All rights reserved. - * Licensed under the GNU Affero General Public License (AGPL). - * See License.AGPL.txt in the project root for license information. - */ - -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { getGitpodService } from "../../service/service"; -import { getListWorkspacesQueryKey, ListWorkspacesQueryResult } from "./list-workspaces-query"; -import { useCurrentOrg } from "../organizations/orgs-query"; -import { Workspace } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; - -type ToggleWorkspacePinnedArgs = { - workspaceId: string; -}; - -export const useToggleWorkspacedPinnedMutation = () => { - const queryClient = useQueryClient(); - const org = useCurrentOrg(); - - return useMutation({ - mutationFn: async ({ workspaceId }: ToggleWorkspacePinnedArgs) => { - return await getGitpodService().server.updateWorkspaceUserPin(workspaceId, "toggle"); - }, - onSuccess: (_, { workspaceId }) => { - // TODO: use `useUpdateWorkspaceInCache` after respond Workspace object, see EXP-960 - const queryKey = getListWorkspacesQueryKey(org.data?.id); - - // Update workspace.pinned to account for the toggle so it's reflected immediately - queryClient.setQueryData(queryKey, (oldWorkspaceData) => { - return oldWorkspaceData?.map((info) => { - if (info.id !== workspaceId) { - return info; - } - const workspace = new Workspace(info); - workspace.pinned = !workspace.pinned; - return workspace; - }); - }); - - queryClient.invalidateQueries({ queryKey }); - }, - }); -}; diff --git a/components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts b/components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts deleted file mode 100644 index 48346e7fcee1d3..00000000000000 --- a/components/dashboard/src/data/workspaces/toggle-workspace-shared-mutation.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2023 Gitpod GmbH. All rights reserved. - * Licensed under the GNU Affero General Public License (AGPL). - * See License.AGPL.txt in the project root for license information. - */ - -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { getGitpodService } from "../../service/service"; -import { getListWorkspacesQueryKey, ListWorkspacesQueryResult } from "./list-workspaces-query"; -import { useCurrentOrg } from "../organizations/orgs-query"; -import { AdmissionLevel, Workspace } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; - -type ToggleWorkspaceSharedArgs = { - workspaceId: string; - level: AdmissionLevel; -}; - -export const useToggleWorkspaceSharedMutation = () => { - const queryClient = useQueryClient(); - const org = useCurrentOrg(); - - return useMutation({ - mutationFn: async ({ workspaceId, level }: ToggleWorkspaceSharedArgs) => { - if (level === AdmissionLevel.UNSPECIFIED) { - return; - } - return await getGitpodService().server.controlAdmission( - workspaceId, - level === AdmissionLevel.EVERYONE ? "everyone" : "owner", - ); - }, - onSuccess: (_, { workspaceId, level }) => { - if (level === AdmissionLevel.UNSPECIFIED) { - return; - } - // TODO: use `useUpdateWorkspaceInCache` after respond Workspace object, see EXP-960 - const queryKey = getListWorkspacesQueryKey(org.data?.id); - - // Update workspace.shareable to the level we set so it's reflected immediately - queryClient.setQueryData(queryKey, (oldWorkspacesData) => { - return oldWorkspacesData?.map((info) => { - if (info.id !== workspaceId) { - return info; - } - - const workspace = new Workspace(info); - if (workspace.status) { - workspace.status.admission = level; - } - return workspace; - }); - }); - - queryClient.invalidateQueries({ queryKey }); - }, - }); -}; diff --git a/components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts b/components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts deleted file mode 100644 index cd3c34c7d49dbf..00000000000000 --- a/components/dashboard/src/data/workspaces/update-workspace-description-mutation.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2023 Gitpod GmbH. All rights reserved. - * Licensed under the GNU Affero General Public License (AGPL). - * See License.AGPL.txt in the project root for license information. - */ - -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { getGitpodService } from "../../service/service"; -import { getListWorkspacesQueryKey, ListWorkspacesQueryResult } from "./list-workspaces-query"; -import { useCurrentOrg } from "../organizations/orgs-query"; -import { Workspace } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; - -type UpdateWorkspaceDescriptionArgs = { - workspaceId: string; - newDescription: string; -}; -export const useUpdateWorkspaceDescriptionMutation = () => { - const queryClient = useQueryClient(); - const org = useCurrentOrg(); - - return useMutation({ - mutationFn: async ({ workspaceId, newDescription }: UpdateWorkspaceDescriptionArgs) => { - return await getGitpodService().server.setWorkspaceDescription(workspaceId, newDescription); - }, - onSuccess: (_, { workspaceId, newDescription }) => { - // TODO: use `useUpdateWorkspaceInCache` after respond Workspace object, see EXP-960 - const queryKey = getListWorkspacesQueryKey(org.data?.id); - - // pro-actively update workspace description rather than reload all workspaces - queryClient.setQueryData(queryKey, (oldWorkspacesData) => { - return oldWorkspacesData?.map((info) => { - if (info.id !== workspaceId) { - return info; - } - - // TODO: Once the update description response includes an updated record, - // we can return that instead of having to know what to merge manually (same for other mutations) - const workspace = new Workspace(info); - workspace.name = newDescription; - return workspace; - }); - }); - - queryClient.invalidateQueries({ queryKey }); - }, - }); -}; diff --git a/components/dashboard/src/data/workspaces/update-workspace-mutation.ts b/components/dashboard/src/data/workspaces/update-workspace-mutation.ts new file mode 100644 index 00000000000000..ce0e8bb960f79a --- /dev/null +++ b/components/dashboard/src/data/workspaces/update-workspace-mutation.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License.AGPL.txt in the project root for license information. + */ + +import { useMutation } from "@tanstack/react-query"; +import { useUpdateWorkspaceInCache } from "./list-workspaces-query"; +import { UpdateWorkspaceRequest } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; +import { PartialMessage } from "@bufbuild/protobuf"; +import { workspaceClient } from "../../service/public-api"; + +export const useUpdateWorkspaceMutation = () => { + const updateWorkspace = useUpdateWorkspaceInCache(); + + return useMutation({ + mutationFn: async (data: PartialMessage) => { + return await workspaceClient.updateWorkspace(data); + }, + onSuccess: (data) => { + if (data.workspace) { + updateWorkspace(data.workspace); + } + }, + }); +}; diff --git a/components/dashboard/src/data/workspaces/workspace-classes-query.ts b/components/dashboard/src/data/workspaces/workspace-classes-query.ts index a8c1d24d27594d..157bb3814ebcf5 100644 --- a/components/dashboard/src/data/workspaces/workspace-classes-query.ts +++ b/components/dashboard/src/data/workspaces/workspace-classes-query.ts @@ -4,15 +4,16 @@ * See License.AGPL.txt in the project root for license information. */ -import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class"; import { useQuery } from "@tanstack/react-query"; -import { getGitpodService } from "../../service/service"; +import { workspaceClient } from "../../service/public-api"; +import { WorkspaceClass } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; export const useWorkspaceClasses = () => { - return useQuery({ + return useQuery({ queryKey: ["workspace-classes"], queryFn: async () => { - return getGitpodService().server.getSupportedWorkspaceClasses(); + const response = await workspaceClient.listWorkspaceClasses({}); + return response.workspaceClasses; }, cacheTime: 1000 * 60 * 60, // 1h staleTime: 1000 * 60 * 60, // 1h diff --git a/components/dashboard/src/workspaces/RenameWorkspaceModal.tsx b/components/dashboard/src/workspaces/RenameWorkspaceModal.tsx index c7e5be563e4950..52830bfb48360e 100644 --- a/components/dashboard/src/workspaces/RenameWorkspaceModal.tsx +++ b/components/dashboard/src/workspaces/RenameWorkspaceModal.tsx @@ -6,9 +6,9 @@ import { FunctionComponent, useCallback, useState } from "react"; import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal"; -import { useUpdateWorkspaceDescriptionMutation } from "../data/workspaces/update-workspace-description-mutation"; import { Button } from "../components/Button"; import { Workspace } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; +import { useUpdateWorkspaceMutation } from "../data/workspaces/update-workspace-mutation"; type Props = { workspace: Workspace; @@ -16,17 +16,17 @@ type Props = { }; export const RenameWorkspaceModal: FunctionComponent = ({ workspace, onClose }) => { const [errorMessage, setErrorMessage] = useState(""); - const [description, setDescription] = useState(workspace.name || ""); - const updateDescription = useUpdateWorkspaceDescriptionMutation(); + const [name, setName] = useState(workspace.name || ""); + const updateWorkspace = useUpdateWorkspaceMutation(); const updateWorkspaceDescription = useCallback(async () => { try { - if (description.length === 0) { + if (name.length === 0) { setErrorMessage("Description cannot not be empty."); return; } - if (description.length > 250) { + if (name.length > 250) { setErrorMessage("Description is too long for readability."); return; } @@ -34,14 +34,14 @@ export const RenameWorkspaceModal: FunctionComponent = ({ workspace, onCl setErrorMessage(""); // Using mutateAsync here so we can close the modal after it completes successfully - await updateDescription.mutateAsync({ workspaceId: workspace.id, newDescription: description }); + await updateWorkspace.mutateAsync({ workspaceId: workspace.id, name }); // Close the modal onClose(); } catch (error) { setErrorMessage("Something went wrong. Please try renaming again."); } - }, [description, onClose, updateDescription, workspace.id]); + }, [name, onClose, updateWorkspace, workspace.id]); return ( @@ -54,9 +54,9 @@ export const RenameWorkspaceModal: FunctionComponent = ({ workspace, onCl autoFocus className="w-full truncate" type="text" - value={description} - disabled={updateDescription.isLoading} - onChange={(e) => setDescription(e.target.value)} + value={name} + disabled={updateWorkspace.isLoading} + onChange={(e) => setName(e.target.value)} />

Change the description to make it easier to go back to a workspace.

@@ -64,10 +64,10 @@ export const RenameWorkspaceModal: FunctionComponent = ({ workspace, onCl
- - diff --git a/components/dashboard/src/workspaces/WorkspaceOverflowMenu.tsx b/components/dashboard/src/workspaces/WorkspaceOverflowMenu.tsx index 92a4263f8b78d6..01cf9819d70ee9 100644 --- a/components/dashboard/src/workspaces/WorkspaceOverflowMenu.tsx +++ b/components/dashboard/src/workspaces/WorkspaceOverflowMenu.tsx @@ -9,14 +9,13 @@ import { FunctionComponent, useCallback, useMemo, useState } from "react"; import { ContextMenuEntry } from "../components/ContextMenu"; import { ItemFieldContextMenu } from "../components/ItemsList"; import { useStopWorkspaceMutation } from "../data/workspaces/stop-workspace-mutation"; -import { useToggleWorkspacedPinnedMutation } from "../data/workspaces/toggle-workspace-pinned-mutation"; -import { useToggleWorkspaceSharedMutation } from "../data/workspaces/toggle-workspace-shared-mutation"; import { getGitpodService } from "../service/service"; import ConnectToSSHModal from "./ConnectToSSHModal"; import { DeleteWorkspaceModal } from "./DeleteWorkspaceModal"; import { useToast } from "../components/toasts/Toasts"; import { RenameWorkspaceModal } from "./RenameWorkspaceModal"; import { AdmissionLevel, Workspace, WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb"; +import { useUpdateWorkspaceMutation } from "../data/workspaces/update-workspace-mutation"; type WorkspaceEntryOverflowMenuProps = { info: Workspace; @@ -34,8 +33,7 @@ export const WorkspaceEntryOverflowMenu: FunctionComponent { - toggleWorkspacePinned.mutate({ + updateWorkspace.mutate({ workspaceId: workspace.id, + pinned: !workspace.pinned, }); - }, [toggleWorkspacePinned, workspace.id]); + }, [updateWorkspace, workspace.id, workspace.pinned]); // Can we use `/start#${workspace.id}` instead? const startUrl = useMemo(