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 02b4a0dfb267a..34cbb665c85a8 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,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 { decreaseDeadline, increaseDeadline } from "api/queries/workspaces"; +import { getErrorMessage } from "api/errors"; +import { displaySuccess, displayError } from "components/GlobalSnackbar/utils"; interface WorkspaceReadyPageProps { workspaceState: StateFrom; @@ -56,9 +58,6 @@ export const WorkspaceReadyPage = ({ isLoadingMoreBuilds, hasMoreBuilds, }: WorkspaceReadyPageProps): JSX.Element => { - const [_, bannerSend] = useActor( - workspaceState.children["scheduleBannerMachine"], - ); const { buildInfo } = useDashboard(); const featureVisibility = useFeatureVisibility(); const { @@ -121,10 +120,25 @@ export const WorkspaceReadyPage = ({ } = useMutation({ mutationFn: restartWorkspace, }); - // keep banner machine in sync with workspace - useEffect(() => { - bannerSend({ type: "REFRESH_WORKSPACE", workspace }); - }, [bannerSend, 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 ( <> @@ -144,18 +158,8 @@ export const WorkspaceReadyPage = ({ { - bannerSend({ - type: "DECREASE_DEADLINE", - hours, - }); - }, - onDeadlinePlus: (hours: number) => { - bannerSend({ - type: "INCREASE_DEADLINE", - hours, - }); - }, + onDeadlineMinus: decreaseMutation.mutate, + onDeadlinePlus: increaseMutation.mutate, maxDeadlineDecrease: getMaxDeadlineChange(deadline, getMinDeadline()), maxDeadlineIncrease: getMaxDeadlineChange( getMaxDeadline(workspace), 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); - }, - }, - }, -);