From 75e7904669909936753b3721834c8cfe27478461 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 8 Nov 2023 14:03:48 +0000 Subject: [PATCH 1/3] Extract all schedule controls from machine --- .../WorkspacePage/WorkspaceReadyPage.tsx | 25 ++-------- .../pages/WorkspacePage/scheduleControls.ts | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 site/src/pages/WorkspacePage/scheduleControls.ts diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 02b4a0dfb267a..bb1fcbdcd6834 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -1,4 +1,3 @@ -import { useActor } from "@xstate/react"; import { useDashboard } from "components/Dashboard/DashboardProvider"; import dayjs from "dayjs"; import { useFeatureVisibility } from "hooks/useFeatureVisibility"; @@ -34,6 +33,7 @@ import { templateVersion, templateVersions } from "api/queries/templates"; import { Alert } from "components/Alert/Alert"; import { Stack } from "components/Stack/Stack"; import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs"; +import { useDecreaseDeadline, useIncreaseDeadline } from "./scheduleControls"; interface WorkspaceReadyPageProps { workspaceState: StateFrom; @@ -56,9 +56,6 @@ export const WorkspaceReadyPage = ({ isLoadingMoreBuilds, hasMoreBuilds, }: WorkspaceReadyPageProps): JSX.Element => { - const [_, bannerSend] = useActor( - workspaceState.children["scheduleBannerMachine"], - ); const { buildInfo } = useDashboard(); const featureVisibility = useFeatureVisibility(); const { @@ -121,10 +118,8 @@ export const WorkspaceReadyPage = ({ } = useMutation({ mutationFn: restartWorkspace, }); - // keep banner machine in sync with workspace - useEffect(() => { - bannerSend({ type: "REFRESH_WORKSPACE", workspace }); - }, [bannerSend, workspace]); + const decreaseDeadline = useDecreaseDeadline(workspace); + const increaseDeadline = useIncreaseDeadline(workspace); return ( <> @@ -144,18 +139,8 @@ export const WorkspaceReadyPage = ({ { - bannerSend({ - type: "DECREASE_DEADLINE", - hours, - }); - }, - onDeadlinePlus: (hours: number) => { - bannerSend({ - type: "INCREASE_DEADLINE", - hours, - }); - }, + onDeadlineMinus: decreaseDeadline, + onDeadlinePlus: increaseDeadline, maxDeadlineDecrease: getMaxDeadlineChange(deadline, getMinDeadline()), maxDeadlineIncrease: getMaxDeadlineChange( getMaxDeadline(workspace), diff --git a/site/src/pages/WorkspacePage/scheduleControls.ts b/site/src/pages/WorkspacePage/scheduleControls.ts new file mode 100644 index 0000000000000..120bbe9de93aa --- /dev/null +++ b/site/src/pages/WorkspacePage/scheduleControls.ts @@ -0,0 +1,50 @@ +import { putWorkspaceExtension } from "api/api"; +import { Workspace } from "api/typesGenerated"; +import dayjs from "dayjs"; +import { getDeadline, getMaxDeadline, getMinDeadline } from "utils/schedule"; +import minMax from "dayjs/plugin/minMax"; +import { useMutation } from "react-query"; +import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; +import { getErrorMessage } from "api/errors"; + +dayjs.extend(minMax); + +const callbacks = { + onSuccess: () => { + displaySuccess("Updated workspace shutdown time."); + }, + onError: (error: unknown) => { + displayError( + getErrorMessage(error, "Failed to update workspace shutdown time."), + ); + }, +}; + +export const useIncreaseDeadline = (workspace: Workspace) => { + const mutation = useMutation({ + mutationFn: (hours: number) => increaseDeadline(workspace, hours), + ...callbacks, + }); + return mutation.mutate; +}; + +export const useDecreaseDeadline = (workspace: Workspace) => { + const mutation = useMutation({ + mutationFn: (hours: number) => decreaseDeadline(workspace, hours), + ...callbacks, + }); + return mutation.mutate; +}; + +const increaseDeadline = (workspace: Workspace, hours: number) => { + const proposedDeadline = getDeadline(workspace).add(hours, "hours"); + const newDeadline = dayjs.min(proposedDeadline, getMaxDeadline(workspace)); + + return putWorkspaceExtension(workspace.id, newDeadline); +}; + +const decreaseDeadline = (workspace: Workspace, hours: number) => { + const proposedDeadline = getDeadline(workspace).subtract(hours, "hours"); + const newDeadline = dayjs.max(proposedDeadline, getMinDeadline()); + return putWorkspaceExtension(workspace.id, newDeadline); +}; From 8d1ee309ef604687767cde2bd18524e8559eb6fc Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 8 Nov 2023 14:06:16 +0000 Subject: [PATCH 2/3] Remove workspace schedule banner xservice --- .../xServices/workspace/workspaceXService.ts | 11 -- .../workspaceScheduleBannerXService.ts | 133 ------------------ 2 files changed, 144 deletions(-) delete mode 100644 site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 74161d95fed6d..4c41695d9a59d 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -1,5 +1,4 @@ import { getErrorMessage } from "api/errors"; -import { workspaceScheduleBannerMachine } from "xServices/workspaceSchedule/workspaceScheduleBannerXService"; import { assign, createMachine } from "xstate"; import * as API from "api/api"; import * as TypesGen from "api/typesGenerated"; @@ -416,15 +415,6 @@ export const workspaceMachine = createMachine( }, }, }, - schedule: { - invoke: { - id: "scheduleBannerMachine", - src: "scheduleBannerMachine", - data: { - workspace: (context: WorkspaceContext) => context.workspace, - }, - }, - }, }, }, error: { @@ -672,7 +662,6 @@ export const workspaceMachine = createMachine( context.eventSource?.close(); }; }, - scheduleBannerMachine: workspaceScheduleBannerMachine, getSSHPrefix: async () => { return API.getDeploymentSSHConfig(); }, diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts deleted file mode 100644 index 73905019cddda..0000000000000 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @fileoverview workspaceScheduleBanner is an xstate machine backing a form, - * presented as an Alert/banner, for reactively updating a workspace schedule. - */ -import { getErrorMessage } from "api/errors"; -import { Workspace } from "api/typesGenerated"; -import dayjs from "dayjs"; -import minMax from "dayjs/plugin/minMax"; -import { getDeadline, getMaxDeadline, getMinDeadline } from "utils/schedule"; -import { assign, createMachine } from "xstate"; -import * as API from "api/api"; -import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; - -dayjs.extend(minMax); - -export const Language = { - errorExtension: "Failed to update workspace shutdown time.", - successExtension: "Updated workspace shutdown time.", -}; - -export interface WorkspaceScheduleBannerContext { - workspace: Workspace; -} - -export type WorkspaceScheduleBannerEvent = - | { - type: "INCREASE_DEADLINE"; - hours: number; - } - | { - type: "DECREASE_DEADLINE"; - hours: number; - } - | { - type: "REFRESH_WORKSPACE"; - workspace: Workspace; - }; - -export const workspaceScheduleBannerMachine = createMachine( - { - id: "workspaceScheduleBannerState", - predictableActionArguments: true, - tsTypes: {} as import("./workspaceScheduleBannerXService.typegen").Typegen0, - schema: { - events: {} as WorkspaceScheduleBannerEvent, - context: {} as WorkspaceScheduleBannerContext, - }, - initial: "idle", - on: { - REFRESH_WORKSPACE: { actions: "assignWorkspace" }, - }, - states: { - idle: { - on: { - INCREASE_DEADLINE: "increasingDeadline", - DECREASE_DEADLINE: "decreasingDeadline", - }, - }, - increasingDeadline: { - invoke: { - src: "increaseDeadline", - id: "increaseDeadline", - onDone: { - target: "idle", - actions: "displaySuccessMessage", - }, - onError: { - target: "idle", - actions: "displayFailureMessage", - }, - }, - tags: "loading", - }, - decreasingDeadline: { - invoke: { - src: "decreaseDeadline", - id: "decreaseDeadline", - onDone: { - target: "idle", - actions: "displaySuccessMessage", - }, - onError: { - target: "idle", - actions: "displayFailureMessage", - }, - }, - tags: "loading", - }, - }, - }, - { - actions: { - // This error does not have a detail, so using the snackbar is okay - displayFailureMessage: (_, event) => { - displayError(getErrorMessage(event.data, Language.errorExtension)); - }, - displaySuccessMessage: () => { - displaySuccess(Language.successExtension); - }, - assignWorkspace: assign((_, event) => ({ - workspace: event.workspace, - })), - }, - - services: { - increaseDeadline: async (context, event) => { - if (!context.workspace.latest_build.deadline) { - throw Error("Deadline is undefined."); - } - const proposedDeadline = getDeadline(context.workspace).add( - event.hours, - "hours", - ); - const newDeadline = dayjs.min( - proposedDeadline, - getMaxDeadline(context.workspace), - ); - await API.putWorkspaceExtension(context.workspace.id, newDeadline); - }, - decreaseDeadline: async (context, event) => { - if (!context.workspace.latest_build.deadline) { - throw Error("Deadline is undefined."); - } - const proposedDeadline = getDeadline(context.workspace).subtract( - event.hours, - "hours", - ); - const newDeadline = dayjs.max(proposedDeadline, getMinDeadline()); - await API.putWorkspaceExtension(context.workspace.id, newDeadline); - }, - }, - }, -); From 293ce964a137e1e3992e34f1de96c1ec20dc3d26 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 8 Nov 2023 17:40:10 +0000 Subject: [PATCH 3/3] Move mutations to queries --- site/src/api/queries/workspaces.ts | 27 +++++++++- .../WorkspacePage/WorkspaceReadyPage.tsx | 29 +++++++++-- .../pages/WorkspacePage/scheduleControls.ts | 50 ------------------- 3 files changed, 50 insertions(+), 56 deletions(-) delete mode 100644 site/src/pages/WorkspacePage/scheduleControls.ts diff --git a/site/src/api/queries/workspaces.ts b/site/src/api/queries/workspaces.ts index bd5b76f951838..f37c65f9ed589 100644 --- a/site/src/api/queries/workspaces.ts +++ b/site/src/api/queries/workspaces.ts @@ -1,6 +1,8 @@ import * as API from "api/api"; import { QueryClient, type QueryOptions } from "react-query"; - +import { putWorkspaceExtension } from "api/api"; +import dayjs from "dayjs"; +import { getDeadline, getMaxDeadline, getMinDeadline } from "utils/schedule"; import { type WorkspaceBuildParameter, type Workspace, @@ -99,3 +101,26 @@ export function workspaces(config: WorkspacesRequest = {}) { queryFn: () => API.getWorkspaces({ q, limit }), } as const satisfies QueryOptions; } + +export const decreaseDeadline = (workspace: Workspace) => { + return { + mutationFn: (hours: number) => { + const proposedDeadline = getDeadline(workspace).subtract(hours, "hours"); + const newDeadline = dayjs.max(proposedDeadline, getMinDeadline()); + return putWorkspaceExtension(workspace.id, newDeadline); + }, + }; +}; + +export const increaseDeadline = (workspace: Workspace) => { + return { + mutationFn: (hours: number) => { + const proposedDeadline = getDeadline(workspace).add(hours, "hours"); + const newDeadline = dayjs.min( + proposedDeadline, + getMaxDeadline(workspace), + ); + return putWorkspaceExtension(workspace.id, newDeadline); + }, + }; +}; diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index bb1fcbdcd6834..34cbb665c85a8 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -33,7 +33,9 @@ import { templateVersion, templateVersions } from "api/queries/templates"; import { Alert } from "components/Alert/Alert"; import { Stack } from "components/Stack/Stack"; import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs"; -import { useDecreaseDeadline, useIncreaseDeadline } from "./scheduleControls"; +import { decreaseDeadline, increaseDeadline } from "api/queries/workspaces"; +import { getErrorMessage } from "api/errors"; +import { displaySuccess, displayError } from "components/GlobalSnackbar/utils"; interface WorkspaceReadyPageProps { workspaceState: StateFrom; @@ -118,8 +120,25 @@ export const WorkspaceReadyPage = ({ } = useMutation({ mutationFn: restartWorkspace, }); - const decreaseDeadline = useDecreaseDeadline(workspace); - const increaseDeadline = useIncreaseDeadline(workspace); + + const onDeadlineChangeSuccess = () => { + displaySuccess("Updated workspace shutdown time."); + }; + const onDeadlineChangeFails = (error: unknown) => { + displayError( + getErrorMessage(error, "Failed to update workspace shutdown time."), + ); + }; + const decreaseMutation = useMutation({ + ...decreaseDeadline(workspace), + onSuccess: onDeadlineChangeSuccess, + onError: onDeadlineChangeFails, + }); + const increaseMutation = useMutation({ + ...increaseDeadline(workspace), + onSuccess: onDeadlineChangeSuccess, + onError: onDeadlineChangeFails, + }); return ( <> @@ -139,8 +158,8 @@ export const WorkspaceReadyPage = ({ { - displaySuccess("Updated workspace shutdown time."); - }, - onError: (error: unknown) => { - displayError( - getErrorMessage(error, "Failed to update workspace shutdown time."), - ); - }, -}; - -export const useIncreaseDeadline = (workspace: Workspace) => { - const mutation = useMutation({ - mutationFn: (hours: number) => increaseDeadline(workspace, hours), - ...callbacks, - }); - return mutation.mutate; -}; - -export const useDecreaseDeadline = (workspace: Workspace) => { - const mutation = useMutation({ - mutationFn: (hours: number) => decreaseDeadline(workspace, hours), - ...callbacks, - }); - return mutation.mutate; -}; - -const increaseDeadline = (workspace: Workspace, hours: number) => { - const proposedDeadline = getDeadline(workspace).add(hours, "hours"); - const newDeadline = dayjs.min(proposedDeadline, getMaxDeadline(workspace)); - - return putWorkspaceExtension(workspace.id, newDeadline); -}; - -const decreaseDeadline = (workspace: Workspace, hours: number) => { - const proposedDeadline = getDeadline(workspace).subtract(hours, "hours"); - const newDeadline = dayjs.max(proposedDeadline, getMinDeadline()); - return putWorkspaceExtension(workspace.id, newDeadline); -};