From 88dd059098e5e484c250fcbba94ce976c4478d17 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Thu, 29 Sep 2022 02:55:13 +0000 Subject: [PATCH 01/10] Start refactor --- .../src/pages/WorkspacePage/WorkspacePage.tsx | 96 +++++++------ site/src/util/schedule.test.ts | 6 +- site/src/util/schedule.ts | 32 +++-- .../xServices/workspace/workspaceXService.ts | 39 ++++++ .../workspaceScheduleBannerXService.ts | 131 +++++++++++++++--- 5 files changed, 226 insertions(+), 78 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 20259771298e5..a23a76bfc8002 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -2,51 +2,33 @@ import { makeStyles } from "@material-ui/core/styles" import { useActor, useMachine, useSelector } from "@xstate/react" import { FeatureNames } from "api/types" import dayjs from "dayjs" -import minMax from "dayjs/plugin/minMax" import { FC, useContext, useEffect } from "react" import { Helmet } from "react-helmet-async" import { useTranslation } from "react-i18next" import { useParams } from "react-router-dom" import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors" +import { ResolveTypegenMeta, State } from "xstate" import { DeleteDialog } from "../../components/Dialogs/DeleteDialog/DeleteDialog" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" import { Workspace, WorkspaceErrors } from "../../components/Workspace/Workspace" import { firstOrItem } from "../../util/array" import { pageTitle } from "../../util/page" -import { canExtendDeadline, canReduceDeadline, maxDeadline, minDeadline } from "../../util/schedule" import { getFaviconByStatus } from "../../util/workspace" import { XServiceContext } from "../../xServices/StateContext" -import { workspaceMachine } from "../../xServices/workspace/workspaceXService" -import { workspaceScheduleBannerMachine } from "../../xServices/workspaceSchedule/workspaceScheduleBannerXService" - -dayjs.extend(minMax) +import { WorkspaceContext, WorkspaceEvent, workspaceMachine } from "../../xServices/workspace/workspaceXService" export const WorkspacePage: FC = () => { const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams() const username = firstOrItem(usernameQueryParam, null) const workspaceName = firstOrItem(workspaceQueryParam, null) - const { t } = useTranslation("workspacePage") - const xServices = useContext(XServiceContext) - const featureVisibility = useSelector(xServices.entitlementsXService, selectFeatureVisibility) const [workspaceState, workspaceSend] = useMachine(workspaceMachine) const { workspace, getWorkspaceError, - template, getTemplateWarning, - refreshWorkspaceWarning, - builds, - getBuildsError, - permissions, checkPermissionsError, - buildError, - cancellationError, - applicationsHost, } = workspaceState.context - const canUpdateWorkspace = Boolean(permissions?.updateWorkspace) - const [bannerState, bannerSend] = useMachine(workspaceScheduleBannerMachine) - const [buildInfoState] = useActor(xServices.buildInfoXService) const styles = useStyles() /** @@ -65,13 +47,41 @@ export const WorkspacePage: FC = () => { {Boolean(checkPermissionsError) && } ) - } else if (!workspace || !permissions) { - return - } else if (!template) { - return + } else if (workspace && workspaceState.matches("ready")) { + return } else { - const deadline = dayjs(workspace.latest_build.deadline).utc() - const favicon = getFaviconByStatus(workspace.latest_build) + return + } +} + +interface WorkspaceReadyPageProps { + workspaceState: State> + workspaceSend: (event: WorkspaceEvent) => void +} + +export const WorkspaceReadyPage = ({ workspaceState, workspaceSend }: WorkspaceReadyPageProps): JSX.Element => { + const [bannerState, bannerSend] = useActor(workspaceState.children["scheduleBannerMachine"]) + console.log(bannerState.matches("atMinDeadline")) + const xServices = useContext(XServiceContext) + const featureVisibility = useSelector(xServices.entitlementsXService, selectFeatureVisibility) + const [buildInfoState] = useActor(xServices.buildInfoXService) + const { + workspace, + refreshWorkspaceWarning, + builds, + getBuildsError, + buildError, + cancellationError, + applicationsHost, + permissions + } = workspaceState.context + if (workspace === undefined) { + throw Error("Workspace is undefined") + } + const canUpdateWorkspace = Boolean(permissions?.updateWorkspace) + const { t } = useTranslation("workspacePage") + const favicon = getFaviconByStatus(workspace.latest_build) + return ( <> @@ -85,41 +95,38 @@ export const WorkspacePage: FC = () => { isLoading: bannerState.hasTag("loading"), onExtend: () => { bannerSend({ - type: "UPDATE_DEADLINE", - workspaceId: workspace.id, - newDeadline: dayjs.min(deadline.add(4, "hours"), maxDeadline(workspace, template)), + type: "INCREASE_DEADLINE", + hours: 4, }) }, }} scheduleProps={{ onDeadlineMinus: () => { bannerSend({ - type: "UPDATE_DEADLINE", - workspaceId: workspace.id, - newDeadline: dayjs.max(deadline.add(-1, "hours"), minDeadline()), + type: "DECREASE_DEADLINE", + hours: 1, }) }, onDeadlinePlus: () => { bannerSend({ - type: "UPDATE_DEADLINE", - workspaceId: workspace.id, - newDeadline: dayjs.min(deadline.add(1, "hours"), maxDeadline(workspace, template)), + type: "INCREASE_DEADLINE", + hours: 1, }) }, deadlineMinusEnabled: () => { - return canReduceDeadline(deadline) + return !bannerState.matches("atMinDeadline") }, deadlinePlusEnabled: () => { - return canExtendDeadline(deadline, workspace, template) + return !bannerState.matches("atMaxDeadline") }, }} isUpdating={workspaceState.hasTag("updating")} workspace={workspace} - handleStart={() => workspaceSend("START")} - handleStop={() => workspaceSend("STOP")} - handleDelete={() => workspaceSend("ASK_DELETE")} - handleUpdate={() => workspaceSend("UPDATE")} - handleCancel={() => workspaceSend("CANCEL")} + handleStart={() => workspaceSend({ type: "START" })} + handleStop={() => workspaceSend({ type: "STOP" })} + handleDelete={() => workspaceSend({ type: "ASK_DELETE" })} + handleUpdate={() => workspaceSend({ type: "UPDATE" })} + handleCancel={() => workspaceSend({ type: "CANCEL" })} resources={workspace.latest_build.resources} builds={builds} canUpdateWorkspace={canUpdateWorkspace} @@ -138,15 +145,14 @@ export const WorkspacePage: FC = () => { name={workspace.name} info={t("deleteDialog.info", { timeAgo: dayjs(workspace.created_at).fromNow() })} isOpen={workspaceState.matches({ ready: { build: "askingDelete" } })} - onCancel={() => workspaceSend("CANCEL_DELETE")} + onCancel={() => workspaceSend({ type: "CANCEL_DELETE" })} onConfirm={() => { - workspaceSend("DELETE") + workspaceSend({ type: "DELETE" }) }} /> ) } -} const useStyles = makeStyles((theme) => ({ error: { diff --git a/site/src/util/schedule.test.ts b/site/src/util/schedule.test.ts index 584fc9b12a422..163a83c7d6679 100644 --- a/site/src/util/schedule.test.ts +++ b/site/src/util/schedule.test.ts @@ -8,7 +8,7 @@ import { deadlineExtensionMax, deadlineExtensionMin, extractTimezone, - maxDeadline, + getMaxDeadline, minDeadline, stripTimezone, } from "./schedule" @@ -55,7 +55,7 @@ describe("maxDeadline", () => { } // Then: deadlineMinusDisabled should be falsy - const delta = maxDeadline(workspace, template).diff(now) + const delta = getMaxDeadline(workspace, template).diff(now) expect(delta).toBeLessThanOrEqual(deadlineExtensionMax.asMilliseconds()) }) }) @@ -68,7 +68,7 @@ describe("maxDeadline", () => { } // Then: deadlineMinusDisabled should be falsy - const delta = maxDeadline(workspace, template).diff(now) + const delta = getMaxDeadline(workspace, template).diff(now) expect(delta).toBeLessThanOrEqual(deadlineExtensionMax.asMilliseconds()) }) }) diff --git a/site/src/util/schedule.ts b/site/src/util/schedule.ts index 41560d0b934ce..c41fbcb911b9b 100644 --- a/site/src/util/schedule.ts +++ b/site/src/util/schedule.ts @@ -113,27 +113,33 @@ export const autoStopDisplay = (workspace: Workspace): string => { export const deadlineExtensionMin = dayjs.duration(30, "minutes") export const deadlineExtensionMax = dayjs.duration(24, "hours") -export function maxDeadline(ws: Workspace, tpl: Template): dayjs.Dayjs { +/** + * Depends on the time the workspace was last updated, the template config, + * and a global constant. + * @param ws workspace + * @param tpl template + * @returns the latest datetime at which the workspace can be automatically shut down. + */ +export function getMaxDeadline(ws: Workspace|undefined, tpl: Template): dayjs.Dayjs { // note: we count runtime from updated_at as started_at counts from the start of // the workspace build process, which can take a while. + if (ws === undefined) { + throw Error("Cannot calculate max deadline because workspace is undefined") + } const startedAt = dayjs(ws.latest_build.updated_at) const maxTemplateDeadline = startedAt.add(dayjs.duration(tpl.max_ttl_ms, "milliseconds")) const maxGlobalDeadline = startedAt.add(deadlineExtensionMax) return dayjs.min(maxTemplateDeadline, maxGlobalDeadline) } -export function minDeadline(): dayjs.Dayjs { +/** + * Depends on the current time and a global constant. + * @returns the earliest datetime at which the workspace can be automatically shut down. + */ +export function getMinDeadline (): dayjs.Dayjs { return dayjs().add(deadlineExtensionMin) } -export function canExtendDeadline( - deadline: dayjs.Dayjs, - workspace: Workspace, - template: Template, -): boolean { - return deadline < maxDeadline(workspace, template) -} - -export function canReduceDeadline(deadline: dayjs.Dayjs): boolean { - return deadline > minDeadline() -} +export const getDeadline = (workspace: Workspace): dayjs.Dayjs => ( + dayjs(workspace.latest_build.deadline).utc() +) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 7538a2453f0fa..ccdaede95cd36 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -1,4 +1,5 @@ import { getErrorMessage } from "api/errors" +import { workspaceScheduleBannerMachine } from "xServices/workspaceSchedule/workspaceScheduleBannerXService" import { assign, createMachine, send } from "xstate" import * as API from "../../api/api" import * as Types from "../../api/types" @@ -78,6 +79,8 @@ export type WorkspaceEvent = | { type: "CANCEL" } | { type: "REFRESH_TIMELINE"; checkRefresh?: boolean; data?: TypesGen.ServerSentEvent["data"] } | { type: "EVENT_SOURCE_ERROR"; error: Error | unknown } + | { type: "INCREASE_DEADLINE"; hours: number } + | { type: "DECREASE_DEADLINE"; hours: number } export const checks = { readWorkspace: "readWorkspace", @@ -216,6 +219,7 @@ export const workspaceMachine = createMachine( }, ], }, + tags: "loading" }, ready: { type: "parallel", @@ -440,6 +444,19 @@ export const workspaceMachine = createMachine( }, }, }, + schedule: { + invoke: { + id: "scheduleBannerMachine", + src: workspaceScheduleBannerMachine, + data: { + workspace: (context: WorkspaceContext) => context.workspace, + template: (context: WorkspaceContext) => context.template + } + }, + on: { + REFRESH_WORKSPACE: { actions: "sendWorkspaceToSchedule" } + } + } }, }, error: { @@ -551,6 +568,28 @@ export const workspaceMachine = createMachine( const message = getErrorMessage(data, "Error getting the applications host.") displayError(message) }, + // assignScheduleBannerRef: assign((context) => { + // if (!context.workspace || !context.template) { + // throw Error("Cannot manage workspace schedule without workspace and template.") + // } + // const scheduleContext = { + // workspace: context.workspace, + // template: context.template, + // deadline: getDeadline(context.workspace) + // } + // const scheduleBannerRef = spawn(workspaceScheduleBannerMachine.withContext(scheduleContext), "scheduleBannerMachine") + // return { ...context, scheduleBannerRef } + // }), + // stopScheduleBannerRef: (context) => { + // context.scheduleBannerRef?.stop && context.scheduleBannerRef.stop() + // }, + // clearScheduleBannerRef: assign({ + // scheduleBannerRef: (_) => undefined + // }), + sendWorkspaceToSchedule: send((context) => ({ + type: "REFRESH_WORKSPACE", + workspace: context.workspace, + }), { to: "scheduleBannerMachine" }) }, guards: { moreBuildsAvailable, diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts index 5dd803bc3b9ae..92366b80754e2 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts @@ -3,22 +3,43 @@ * presented as an Alert/banner, for reactively updating a workspace schedule. */ import { getErrorMessage } from "api/errors" +import { Template, Workspace } from "api/typesGenerated" import dayjs from "dayjs" -import { createMachine } from "xstate" +import minMax from "dayjs/plugin/minMax" +import { getDeadline, getMaxDeadline, getMinDeadline } from "util/schedule" +import { ActorRefFrom, 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 type WorkspaceScheduleBannerEvent = { - type: "UPDATE_DEADLINE" - workspaceId: string - newDeadline: dayjs.Dayjs +export interface WorkspaceScheduleBannerContext { + workspace: Workspace + template: Template + deadline?: dayjs.Dayjs } +export type WorkspaceScheduleBannerEvent = + | { + type: "INCREASE_DEADLINE" + hours: number + } + | { + type: "DECREASE_DEADLINE" + hours: number + } + | { + type: "REFRESH_WORKSPACE" + workspace: Workspace + } + +export type WorkspaceScheduleBannerMachineRef = ActorRefFrom + export const workspaceScheduleBannerMachine = createMachine( { id: "workspaceScheduleBannerState", @@ -26,24 +47,75 @@ export const workspaceScheduleBannerMachine = createMachine( tsTypes: {} as import("./workspaceScheduleBannerXService.typegen").Typegen0, schema: { events: {} as WorkspaceScheduleBannerEvent, + context: {} as WorkspaceScheduleBannerContext, + }, + initial: "initialize", + on: { + REFRESH_WORKSPACE: { actions: "assignWorkspace" } }, - initial: "idle", states: { - idle: { + initialize: { + always: [ + { cond: "isAtMaxDeadline", target: "atMaxDeadline" }, + { cond: "isAtMinDeadline", target: "atMinDeadline" }, + { target: "midRange" }, + ], + }, + midRange: { + on: { + INCREASE_DEADLINE: "increasingDeadline", + DECREASE_DEADLINE: "decreasingDeadline", + }, + }, + atMaxDeadline: { + on: { + DECREASE_DEADLINE: "decreasingDeadline", + }, + }, + atMinDeadline: { on: { - UPDATE_DEADLINE: "updatingDeadline", + INCREASE_DEADLINE: "increasingDeadline", }, }, - updatingDeadline: { + increasingDeadline: { invoke: { - src: "updateDeadline", - id: "updateDeadline", - onDone: { - target: "idle", - actions: "displaySuccessMessage", + src: "increaseDeadline", + id: "increaseDeadline", + onDone: [ + { + cond: "isAtMaxDeadline", + target: "atMaxDeadline", + actions: "displaySuccessMessage", + }, + { + target: "midRange", + actions: "displaySuccessMessage", + }, + ], + onError: { + target: "midRange", + actions: "displayFailureMessage", }, + }, + tags: "loading", + }, + decreasingDeadline: { + invoke: { + src: "decreaseDeadline", + id: "decreaseDeadline", + onDone: [ + { + cond: "isAtMinDeadline", + target: "atMinDeadline", + actions: "displaySuccessMessage", + }, + { + target: "midRange", + actions: "displaySuccessMessage", + }, + ], onError: { - target: "idle", + target: "midRange", actions: "displayFailureMessage", }, }, @@ -52,6 +124,14 @@ export const workspaceScheduleBannerMachine = createMachine( }, }, { + guards: { + isAtMaxDeadline: (context) => { + return context.deadline?.isSame(getMaxDeadline(context.workspace, context.template)) || false + }, + isAtMinDeadline: (context) => { + return context.deadline?.isSame(getMinDeadline()) || false + }, + }, actions: { // This error does not have a detail, so using the snackbar is okay displayFailureMessage: (_, event) => { @@ -60,11 +140,28 @@ export const workspaceScheduleBannerMachine = createMachine( displaySuccessMessage: () => { displaySuccess(Language.successExtension) }, + assignWorkspace: assign((_, event) => ({ + workspace: event.workspace, + deadline: getDeadline(event.workspace) + })) }, services: { - updateDeadline: async (_, event) => { - await API.putWorkspaceExtension(event.workspaceId, event.newDeadline) + increaseDeadline: async (context, event) => { + if (!context.deadline) { + throw Error("Deadline is undefined.") + } + const proposedDeadline = context.deadline.add(event.hours, "hours") + const newDeadline = dayjs.min(proposedDeadline, getMaxDeadline(context.workspace, context.template)) + await API.putWorkspaceExtension(context.workspace.id, newDeadline) + }, + decreaseDeadline: async (context, event) => { + if (!context.deadline) { + throw Error("Deadline is undefined.") + } + const proposedDeadline = context.deadline.subtract(event.hours, "hours") + const newDeadline = dayjs.min(proposedDeadline, getMinDeadline()) + await API.putWorkspaceExtension(context.workspace.id, newDeadline) }, }, }, From dd6b914bb4a851ae528efeb4a9681288c771c3f2 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 13:34:05 +0000 Subject: [PATCH 02/10] Fix color of auto stop switch --- .../components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index 106a653ecb66e..05f225813d371 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -309,6 +309,7 @@ export const WorkspaceScheduleForm: FC } label={Language.stopSwitch} From 7ccabafe57ca4018cd0022808fed384289d57f3c Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 13:34:39 +0000 Subject: [PATCH 03/10] Format --- .../src/pages/WorkspacePage/WorkspacePage.tsx | 169 +++++++++--------- site/src/util/schedule.ts | 7 +- .../xServices/workspace/workspaceXService.ts | 23 +-- .../workspaceScheduleBannerXService.ts | 21 ++- 4 files changed, 118 insertions(+), 102 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index a23a76bfc8002..e1437bc2f993a 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -16,19 +16,19 @@ import { firstOrItem } from "../../util/array" import { pageTitle } from "../../util/page" import { getFaviconByStatus } from "../../util/workspace" import { XServiceContext } from "../../xServices/StateContext" -import { WorkspaceContext, WorkspaceEvent, workspaceMachine } from "../../xServices/workspace/workspaceXService" +import { + WorkspaceContext, + WorkspaceEvent, + workspaceMachine, +} from "../../xServices/workspace/workspaceXService" export const WorkspacePage: FC = () => { const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams() const username = firstOrItem(usernameQueryParam, null) const workspaceName = firstOrItem(workspaceQueryParam, null) const [workspaceState, workspaceSend] = useMachine(workspaceMachine) - const { - workspace, - getWorkspaceError, - getTemplateWarning, - checkPermissionsError, - } = workspaceState.context + const { workspace, getWorkspaceError, getTemplateWarning, checkPermissionsError } = + workspaceState.context const styles = useStyles() /** @@ -55,11 +55,20 @@ export const WorkspacePage: FC = () => { } interface WorkspaceReadyPageProps { - workspaceState: State> + workspaceState: State< + WorkspaceContext, + WorkspaceEvent, + any, + any, + ResolveTypegenMeta + > workspaceSend: (event: WorkspaceEvent) => void } -export const WorkspaceReadyPage = ({ workspaceState, workspaceSend }: WorkspaceReadyPageProps): JSX.Element => { +export const WorkspaceReadyPage = ({ + workspaceState, + workspaceSend, +}: WorkspaceReadyPageProps): JSX.Element => { const [bannerState, bannerSend] = useActor(workspaceState.children["scheduleBannerMachine"]) console.log(bannerState.matches("atMinDeadline")) const xServices = useContext(XServiceContext) @@ -73,7 +82,7 @@ export const WorkspaceReadyPage = ({ workspaceState, workspaceSend }: WorkspaceR buildError, cancellationError, applicationsHost, - permissions + permissions, } = workspaceState.context if (workspace === undefined) { throw Error("Workspace is undefined") @@ -82,77 +91,77 @@ export const WorkspaceReadyPage = ({ workspaceState, workspaceSend }: WorkspaceR const { t } = useTranslation("workspacePage") const favicon = getFaviconByStatus(workspace.latest_build) - return ( - <> - - {pageTitle(`${workspace.owner_name}/${workspace.name}`)} - - - + return ( + <> + + {pageTitle(`${workspace.owner_name}/${workspace.name}`)} + + + - { - bannerSend({ - type: "INCREASE_DEADLINE", - hours: 4, - }) - }, - }} - scheduleProps={{ - onDeadlineMinus: () => { - bannerSend({ - type: "DECREASE_DEADLINE", - hours: 1, - }) - }, - onDeadlinePlus: () => { - bannerSend({ - type: "INCREASE_DEADLINE", - hours: 1, - }) - }, - deadlineMinusEnabled: () => { - return !bannerState.matches("atMinDeadline") - }, - deadlinePlusEnabled: () => { - return !bannerState.matches("atMaxDeadline") - }, - }} - isUpdating={workspaceState.hasTag("updating")} - workspace={workspace} - handleStart={() => workspaceSend({ type: "START" })} - handleStop={() => workspaceSend({ type: "STOP" })} - handleDelete={() => workspaceSend({ type: "ASK_DELETE" })} - handleUpdate={() => workspaceSend({ type: "UPDATE" })} - handleCancel={() => workspaceSend({ type: "CANCEL" })} - resources={workspace.latest_build.resources} - builds={builds} - canUpdateWorkspace={canUpdateWorkspace} - hideSSHButton={featureVisibility[FeatureNames.BrowserOnly]} - workspaceErrors={{ - [WorkspaceErrors.GET_RESOURCES_ERROR]: refreshWorkspaceWarning, - [WorkspaceErrors.GET_BUILDS_ERROR]: getBuildsError, - [WorkspaceErrors.BUILD_ERROR]: buildError, - [WorkspaceErrors.CANCELLATION_ERROR]: cancellationError, - }} - buildInfo={buildInfoState.context.buildInfo} - applicationsHost={applicationsHost} - /> - workspaceSend({ type: "CANCEL_DELETE" })} - onConfirm={() => { - workspaceSend({ type: "DELETE" }) - }} - /> - - ) - } + { + bannerSend({ + type: "INCREASE_DEADLINE", + hours: 4, + }) + }, + }} + scheduleProps={{ + onDeadlineMinus: () => { + bannerSend({ + type: "DECREASE_DEADLINE", + hours: 1, + }) + }, + onDeadlinePlus: () => { + bannerSend({ + type: "INCREASE_DEADLINE", + hours: 1, + }) + }, + deadlineMinusEnabled: () => { + return !bannerState.matches("atMinDeadline") + }, + deadlinePlusEnabled: () => { + return !bannerState.matches("atMaxDeadline") + }, + }} + isUpdating={workspaceState.hasTag("updating")} + workspace={workspace} + handleStart={() => workspaceSend({ type: "START" })} + handleStop={() => workspaceSend({ type: "STOP" })} + handleDelete={() => workspaceSend({ type: "ASK_DELETE" })} + handleUpdate={() => workspaceSend({ type: "UPDATE" })} + handleCancel={() => workspaceSend({ type: "CANCEL" })} + resources={workspace.latest_build.resources} + builds={builds} + canUpdateWorkspace={canUpdateWorkspace} + hideSSHButton={featureVisibility[FeatureNames.BrowserOnly]} + workspaceErrors={{ + [WorkspaceErrors.GET_RESOURCES_ERROR]: refreshWorkspaceWarning, + [WorkspaceErrors.GET_BUILDS_ERROR]: getBuildsError, + [WorkspaceErrors.BUILD_ERROR]: buildError, + [WorkspaceErrors.CANCELLATION_ERROR]: cancellationError, + }} + buildInfo={buildInfoState.context.buildInfo} + applicationsHost={applicationsHost} + /> + workspaceSend({ type: "CANCEL_DELETE" })} + onConfirm={() => { + workspaceSend({ type: "DELETE" }) + }} + /> + + ) +} const useStyles = makeStyles((theme) => ({ error: { diff --git a/site/src/util/schedule.ts b/site/src/util/schedule.ts index c41fbcb911b9b..dab8d1e3d3af2 100644 --- a/site/src/util/schedule.ts +++ b/site/src/util/schedule.ts @@ -120,7 +120,7 @@ export const deadlineExtensionMax = dayjs.duration(24, "hours") * @param tpl template * @returns the latest datetime at which the workspace can be automatically shut down. */ -export function getMaxDeadline(ws: Workspace|undefined, tpl: Template): dayjs.Dayjs { +export function getMaxDeadline(ws: Workspace | undefined, tpl: Template): dayjs.Dayjs { // note: we count runtime from updated_at as started_at counts from the start of // the workspace build process, which can take a while. if (ws === undefined) { @@ -136,10 +136,9 @@ export function getMaxDeadline(ws: Workspace|undefined, tpl: Template): dayjs.Da * Depends on the current time and a global constant. * @returns the earliest datetime at which the workspace can be automatically shut down. */ -export function getMinDeadline (): dayjs.Dayjs { +export function getMinDeadline(): dayjs.Dayjs { return dayjs().add(deadlineExtensionMin) } -export const getDeadline = (workspace: Workspace): dayjs.Dayjs => ( +export const getDeadline = (workspace: Workspace): dayjs.Dayjs => dayjs(workspace.latest_build.deadline).utc() -) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index ccdaede95cd36..5fd2da600c53e 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -219,7 +219,7 @@ export const workspaceMachine = createMachine( }, ], }, - tags: "loading" + tags: "loading", }, ready: { type: "parallel", @@ -450,13 +450,13 @@ export const workspaceMachine = createMachine( src: workspaceScheduleBannerMachine, data: { workspace: (context: WorkspaceContext) => context.workspace, - template: (context: WorkspaceContext) => context.template - } + template: (context: WorkspaceContext) => context.template, + }, }, on: { - REFRESH_WORKSPACE: { actions: "sendWorkspaceToSchedule" } - } - } + REFRESH_WORKSPACE: { actions: "sendWorkspaceToSchedule" }, + }, + }, }, }, error: { @@ -586,10 +586,13 @@ export const workspaceMachine = createMachine( // clearScheduleBannerRef: assign({ // scheduleBannerRef: (_) => undefined // }), - sendWorkspaceToSchedule: send((context) => ({ - type: "REFRESH_WORKSPACE", - workspace: context.workspace, - }), { to: "scheduleBannerMachine" }) + sendWorkspaceToSchedule: send( + (context) => ({ + type: "REFRESH_WORKSPACE", + workspace: context.workspace, + }), + { to: "scheduleBannerMachine" }, + ), }, guards: { moreBuildsAvailable, diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts index 92366b80754e2..abf05ea588423 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts @@ -34,9 +34,9 @@ export type WorkspaceScheduleBannerEvent = hours: number } | { - type: "REFRESH_WORKSPACE" - workspace: Workspace - } + type: "REFRESH_WORKSPACE" + workspace: Workspace + } export type WorkspaceScheduleBannerMachineRef = ActorRefFrom @@ -51,7 +51,7 @@ export const workspaceScheduleBannerMachine = createMachine( }, initial: "initialize", on: { - REFRESH_WORKSPACE: { actions: "assignWorkspace" } + REFRESH_WORKSPACE: { actions: "assignWorkspace" }, }, states: { initialize: { @@ -126,7 +126,9 @@ export const workspaceScheduleBannerMachine = createMachine( { guards: { isAtMaxDeadline: (context) => { - return context.deadline?.isSame(getMaxDeadline(context.workspace, context.template)) || false + return ( + context.deadline?.isSame(getMaxDeadline(context.workspace, context.template)) || false + ) }, isAtMinDeadline: (context) => { return context.deadline?.isSame(getMinDeadline()) || false @@ -142,8 +144,8 @@ export const workspaceScheduleBannerMachine = createMachine( }, assignWorkspace: assign((_, event) => ({ workspace: event.workspace, - deadline: getDeadline(event.workspace) - })) + deadline: getDeadline(event.workspace), + })), }, services: { @@ -152,7 +154,10 @@ export const workspaceScheduleBannerMachine = createMachine( throw Error("Deadline is undefined.") } const proposedDeadline = context.deadline.add(event.hours, "hours") - const newDeadline = dayjs.min(proposedDeadline, getMaxDeadline(context.workspace, context.template)) + const newDeadline = dayjs.min( + proposedDeadline, + getMaxDeadline(context.workspace, context.template), + ) await API.putWorkspaceExtension(context.workspace.id, newDeadline) }, decreaseDeadline: async (context, event) => { From 16e5fd4bb294e8c9fd183f92de5fcbab68472190 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 13:43:48 +0000 Subject: [PATCH 04/10] Use helper functions for min/max check --- site/src/util/schedule.test.ts | 4 ++-- site/src/util/schedule.ts | 12 ++++++++++ .../workspaceScheduleBannerXService.ts | 22 +++++++++++-------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/site/src/util/schedule.test.ts b/site/src/util/schedule.test.ts index 163a83c7d6679..998c4666cd866 100644 --- a/site/src/util/schedule.test.ts +++ b/site/src/util/schedule.test.ts @@ -9,7 +9,7 @@ import { deadlineExtensionMin, extractTimezone, getMaxDeadline, - minDeadline, + getMinDeadline, stripTimezone, } from "./schedule" @@ -76,7 +76,7 @@ describe("maxDeadline", () => { describe("minDeadline", () => { it("should never be less than 30 minutes", () => { - const delta = minDeadline().diff(now) + const delta = getMinDeadline().diff(now) expect(delta).toBeGreaterThanOrEqual(deadlineExtensionMin.asMilliseconds()) }) }) diff --git a/site/src/util/schedule.ts b/site/src/util/schedule.ts index dab8d1e3d3af2..fffa8cb294d47 100644 --- a/site/src/util/schedule.ts +++ b/site/src/util/schedule.ts @@ -140,5 +140,17 @@ export function getMinDeadline(): dayjs.Dayjs { return dayjs().add(deadlineExtensionMin) } +export function canExtendDeadline( + deadline: dayjs.Dayjs, + workspace: Workspace, + template: Template, +): boolean { + return deadline < getMaxDeadline(workspace, template) +} + +export function canReduceDeadline(deadline: dayjs.Dayjs): boolean { + return deadline > getMinDeadline() +} + export const getDeadline = (workspace: Workspace): dayjs.Dayjs => dayjs(workspace.latest_build.deadline).utc() diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts index abf05ea588423..9597002f0912f 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts @@ -6,7 +6,13 @@ import { getErrorMessage } from "api/errors" import { Template, Workspace } from "api/typesGenerated" import dayjs from "dayjs" import minMax from "dayjs/plugin/minMax" -import { getDeadline, getMaxDeadline, getMinDeadline } from "util/schedule" +import { + canExtendDeadline, + canReduceDeadline, + getDeadline, + getMaxDeadline, + getMinDeadline, +} from "util/schedule" import { ActorRefFrom, assign, createMachine } from "xstate" import * as API from "../../api/api" import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" @@ -125,14 +131,12 @@ export const workspaceScheduleBannerMachine = createMachine( }, { guards: { - isAtMaxDeadline: (context) => { - return ( - context.deadline?.isSame(getMaxDeadline(context.workspace, context.template)) || false - ) - }, - isAtMinDeadline: (context) => { - return context.deadline?.isSame(getMinDeadline()) || false - }, + isAtMaxDeadline: (context) => + context.deadline + ? !canExtendDeadline(context.deadline, context.workspace, context.template) + : false, + isAtMinDeadline: (context) => + context.deadline ? !canReduceDeadline(context.deadline) : false, }, actions: { // This error does not have a detail, so using the snackbar is okay From 744aeebb1c24e82a6057861827b48d4a660f6fc2 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 17:21:33 +0000 Subject: [PATCH 05/10] Fix type --- site/src/pages/WorkspacePage/WorkspacePage.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index e1437bc2f993a..314b074268a28 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -7,7 +7,7 @@ import { Helmet } from "react-helmet-async" import { useTranslation } from "react-i18next" import { useParams } from "react-router-dom" import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors" -import { ResolveTypegenMeta, State } from "xstate" +import { StateFrom } from "xstate" import { DeleteDialog } from "../../components/Dialogs/DeleteDialog/DeleteDialog" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" @@ -17,7 +17,6 @@ import { pageTitle } from "../../util/page" import { getFaviconByStatus } from "../../util/workspace" import { XServiceContext } from "../../xServices/StateContext" import { - WorkspaceContext, WorkspaceEvent, workspaceMachine, } from "../../xServices/workspace/workspaceXService" @@ -55,13 +54,7 @@ export const WorkspacePage: FC = () => { } interface WorkspaceReadyPageProps { - workspaceState: State< - WorkspaceContext, - WorkspaceEvent, - any, - any, - ResolveTypegenMeta - > + workspaceState: StateFrom workspaceSend: (event: WorkspaceEvent) => void } @@ -70,7 +63,6 @@ export const WorkspaceReadyPage = ({ workspaceSend, }: WorkspaceReadyPageProps): JSX.Element => { const [bannerState, bannerSend] = useActor(workspaceState.children["scheduleBannerMachine"]) - console.log(bannerState.matches("atMinDeadline")) const xServices = useContext(XServiceContext) const featureVisibility = useSelector(xServices.entitlementsXService, selectFeatureVisibility) const [buildInfoState] = useActor(xServices.buildInfoXService) From ffedaef8a3fc5a76901eb2231fa9bd85da41711e Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 17:24:50 +0000 Subject: [PATCH 06/10] Put new component in own file --- .../src/pages/WorkspacePage/WorkspacePage.tsx | 123 +----------------- .../WorkspacePage/WorkspaceReadyPage.tsx | 116 +++++++++++++++++ 2 files changed, 120 insertions(+), 119 deletions(-) create mode 100644 site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 314b074268a28..0d6337ccc21bf 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -1,25 +1,12 @@ import { makeStyles } from "@material-ui/core/styles" -import { useActor, useMachine, useSelector } from "@xstate/react" -import { FeatureNames } from "api/types" -import dayjs from "dayjs" -import { FC, useContext, useEffect } from "react" -import { Helmet } from "react-helmet-async" -import { useTranslation } from "react-i18next" +import { useMachine } from "@xstate/react" +import { FC, useEffect } from "react" import { useParams } from "react-router-dom" -import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors" -import { StateFrom } from "xstate" -import { DeleteDialog } from "../../components/Dialogs/DeleteDialog/DeleteDialog" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" -import { Workspace, WorkspaceErrors } from "../../components/Workspace/Workspace" import { firstOrItem } from "../../util/array" -import { pageTitle } from "../../util/page" -import { getFaviconByStatus } from "../../util/workspace" -import { XServiceContext } from "../../xServices/StateContext" -import { - WorkspaceEvent, - workspaceMachine, -} from "../../xServices/workspace/workspaceXService" +import { workspaceMachine } from "../../xServices/workspace/workspaceXService" +import { WorkspaceReadyPage } from "./WorkspaceReadyPage" export const WorkspacePage: FC = () => { const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams() @@ -53,108 +40,6 @@ export const WorkspacePage: FC = () => { } } -interface WorkspaceReadyPageProps { - workspaceState: StateFrom - workspaceSend: (event: WorkspaceEvent) => void -} - -export const WorkspaceReadyPage = ({ - workspaceState, - workspaceSend, -}: WorkspaceReadyPageProps): JSX.Element => { - const [bannerState, bannerSend] = useActor(workspaceState.children["scheduleBannerMachine"]) - const xServices = useContext(XServiceContext) - const featureVisibility = useSelector(xServices.entitlementsXService, selectFeatureVisibility) - const [buildInfoState] = useActor(xServices.buildInfoXService) - const { - workspace, - refreshWorkspaceWarning, - builds, - getBuildsError, - buildError, - cancellationError, - applicationsHost, - permissions, - } = workspaceState.context - if (workspace === undefined) { - throw Error("Workspace is undefined") - } - const canUpdateWorkspace = Boolean(permissions?.updateWorkspace) - const { t } = useTranslation("workspacePage") - const favicon = getFaviconByStatus(workspace.latest_build) - - return ( - <> - - {pageTitle(`${workspace.owner_name}/${workspace.name}`)} - - - - - { - bannerSend({ - type: "INCREASE_DEADLINE", - hours: 4, - }) - }, - }} - scheduleProps={{ - onDeadlineMinus: () => { - bannerSend({ - type: "DECREASE_DEADLINE", - hours: 1, - }) - }, - onDeadlinePlus: () => { - bannerSend({ - type: "INCREASE_DEADLINE", - hours: 1, - }) - }, - deadlineMinusEnabled: () => { - return !bannerState.matches("atMinDeadline") - }, - deadlinePlusEnabled: () => { - return !bannerState.matches("atMaxDeadline") - }, - }} - isUpdating={workspaceState.hasTag("updating")} - workspace={workspace} - handleStart={() => workspaceSend({ type: "START" })} - handleStop={() => workspaceSend({ type: "STOP" })} - handleDelete={() => workspaceSend({ type: "ASK_DELETE" })} - handleUpdate={() => workspaceSend({ type: "UPDATE" })} - handleCancel={() => workspaceSend({ type: "CANCEL" })} - resources={workspace.latest_build.resources} - builds={builds} - canUpdateWorkspace={canUpdateWorkspace} - hideSSHButton={featureVisibility[FeatureNames.BrowserOnly]} - workspaceErrors={{ - [WorkspaceErrors.GET_RESOURCES_ERROR]: refreshWorkspaceWarning, - [WorkspaceErrors.GET_BUILDS_ERROR]: getBuildsError, - [WorkspaceErrors.BUILD_ERROR]: buildError, - [WorkspaceErrors.CANCELLATION_ERROR]: cancellationError, - }} - buildInfo={buildInfoState.context.buildInfo} - applicationsHost={applicationsHost} - /> - workspaceSend({ type: "CANCEL_DELETE" })} - onConfirm={() => { - workspaceSend({ type: "DELETE" }) - }} - /> - - ) -} - const useStyles = makeStyles((theme) => ({ error: { margin: theme.spacing(2), diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx new file mode 100644 index 0000000000000..908b274e29a50 --- /dev/null +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -0,0 +1,116 @@ +import { useActor, useSelector } from "@xstate/react" +import { FeatureNames } from "api/types" +import dayjs from "dayjs" +import { useContext } from "react" +import { Helmet } from "react-helmet-async" +import { useTranslation } from "react-i18next" +import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors" +import { StateFrom } from "xstate" +import { DeleteDialog } from "../../components/Dialogs/DeleteDialog/DeleteDialog" +import { Workspace, WorkspaceErrors } from "../../components/Workspace/Workspace" +import { pageTitle } from "../../util/page" +import { getFaviconByStatus } from "../../util/workspace" +import { XServiceContext } from "../../xServices/StateContext" +import { WorkspaceEvent, workspaceMachine } from "../../xServices/workspace/workspaceXService" + +interface WorkspaceReadyPageProps { + workspaceState: StateFrom + workspaceSend: (event: WorkspaceEvent) => void +} + +export const WorkspaceReadyPage = ({ + workspaceState, + workspaceSend, +}: WorkspaceReadyPageProps): JSX.Element => { + const [bannerState, bannerSend] = useActor(workspaceState.children["scheduleBannerMachine"]) + const xServices = useContext(XServiceContext) + const featureVisibility = useSelector(xServices.entitlementsXService, selectFeatureVisibility) + const [buildInfoState] = useActor(xServices.buildInfoXService) + const { + workspace, + refreshWorkspaceWarning, + builds, + getBuildsError, + buildError, + cancellationError, + applicationsHost, + permissions, + } = workspaceState.context + if (workspace === undefined) { + throw Error("Workspace is undefined") + } + const canUpdateWorkspace = Boolean(permissions?.updateWorkspace) + const { t } = useTranslation("workspacePage") + const favicon = getFaviconByStatus(workspace.latest_build) + + return ( + <> + + {pageTitle(`${workspace.owner_name}/${workspace.name}`)} + + + + + { + bannerSend({ + type: "INCREASE_DEADLINE", + hours: 4, + }) + }, + }} + scheduleProps={{ + onDeadlineMinus: () => { + bannerSend({ + type: "DECREASE_DEADLINE", + hours: 1, + }) + }, + onDeadlinePlus: () => { + bannerSend({ + type: "INCREASE_DEADLINE", + hours: 1, + }) + }, + deadlineMinusEnabled: () => { + return !bannerState.matches("atMinDeadline") + }, + deadlinePlusEnabled: () => { + return !bannerState.matches("atMaxDeadline") + }, + }} + isUpdating={workspaceState.hasTag("updating")} + workspace={workspace} + handleStart={() => workspaceSend({ type: "START" })} + handleStop={() => workspaceSend({ type: "STOP" })} + handleDelete={() => workspaceSend({ type: "ASK_DELETE" })} + handleUpdate={() => workspaceSend({ type: "UPDATE" })} + handleCancel={() => workspaceSend({ type: "CANCEL" })} + resources={workspace.latest_build.resources} + builds={builds} + canUpdateWorkspace={canUpdateWorkspace} + hideSSHButton={featureVisibility[FeatureNames.BrowserOnly]} + workspaceErrors={{ + [WorkspaceErrors.GET_RESOURCES_ERROR]: refreshWorkspaceWarning, + [WorkspaceErrors.GET_BUILDS_ERROR]: getBuildsError, + [WorkspaceErrors.BUILD_ERROR]: buildError, + [WorkspaceErrors.CANCELLATION_ERROR]: cancellationError, + }} + buildInfo={buildInfoState.context.buildInfo} + applicationsHost={applicationsHost} + /> + workspaceSend({ type: "CANCEL_DELETE" })} + onConfirm={() => { + workspaceSend({ type: "DELETE" }) + }} + /> + + ) +} From 7d2f6181ed4b270f7b4d0f678d2f21109000a08f Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 17:39:56 +0000 Subject: [PATCH 07/10] Fix decrease deadline bug --- .../workspaceSchedule/workspaceScheduleBannerXService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts index 9597002f0912f..0f09b48825594 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts @@ -169,7 +169,7 @@ export const workspaceScheduleBannerMachine = createMachine( throw Error("Deadline is undefined.") } const proposedDeadline = context.deadline.subtract(event.hours, "hours") - const newDeadline = dayjs.min(proposedDeadline, getMinDeadline()) + const newDeadline = dayjs.max(proposedDeadline, getMinDeadline()) await API.putWorkspaceExtension(context.workspace.id, newDeadline) }, }, From 9c3db83b5b2ff4dd204e108dd2a7a40396f2a06a Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 17:43:37 +0000 Subject: [PATCH 08/10] Simplify functions --- site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 908b274e29a50..25dad00ca3181 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -74,12 +74,8 @@ export const WorkspaceReadyPage = ({ hours: 1, }) }, - deadlineMinusEnabled: () => { - return !bannerState.matches("atMinDeadline") - }, - deadlinePlusEnabled: () => { - return !bannerState.matches("atMaxDeadline") - }, + deadlineMinusEnabled: () => !bannerState.matches("atMinDeadline"), + deadlinePlusEnabled: () => !bannerState.matches("atMaxDeadline"), }} isUpdating={workspaceState.hasTag("updating")} workspace={workspace} From 56553f6177f0f36480ee001870585598a23c788a Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 21:14:07 +0000 Subject: [PATCH 09/10] Use ChooseOne --- .../src/pages/WorkspacePage/WorkspacePage.tsx | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 0d6337ccc21bf..7daa934990507 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -1,5 +1,6 @@ import { makeStyles } from "@material-ui/core/styles" import { useMachine } from "@xstate/react" +import { ChooseOne, Cond } from "components/Conditionals/ChooseOne" import { FC, useEffect } from "react" import { useParams } from "react-router-dom" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" @@ -25,19 +26,23 @@ export const WorkspacePage: FC = () => { username && workspaceName && workspaceSend({ type: "GET_WORKSPACE", username, workspaceName }) }, [username, workspaceName, workspaceSend]) - if (workspaceState.matches("error")) { - return ( -
- {Boolean(getWorkspaceError) && } - {Boolean(getTemplateWarning) && } - {Boolean(checkPermissionsError) && } -
- ) - } else if (workspace && workspaceState.matches("ready")) { - return - } else { - return - } + return ( + + +
+ {Boolean(getWorkspaceError) && } + {Boolean(getTemplateWarning) && } + {Boolean(checkPermissionsError) && } +
+
+ + + + + + +
+ ) } const useStyles = makeStyles((theme) => ({ From fe7ba4eb4dfeefd69834f4e8559d9b253ed3e8ec Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 30 Sep 2022 21:15:24 +0000 Subject: [PATCH 10/10] Remove commented code --- .../xServices/workspace/workspaceXService.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 5fd2da600c53e..e8cee0d67f6e5 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -568,24 +568,6 @@ export const workspaceMachine = createMachine( const message = getErrorMessage(data, "Error getting the applications host.") displayError(message) }, - // assignScheduleBannerRef: assign((context) => { - // if (!context.workspace || !context.template) { - // throw Error("Cannot manage workspace schedule without workspace and template.") - // } - // const scheduleContext = { - // workspace: context.workspace, - // template: context.template, - // deadline: getDeadline(context.workspace) - // } - // const scheduleBannerRef = spawn(workspaceScheduleBannerMachine.withContext(scheduleContext), "scheduleBannerMachine") - // return { ...context, scheduleBannerRef } - // }), - // stopScheduleBannerRef: (context) => { - // context.scheduleBannerRef?.stop && context.scheduleBannerRef.stop() - // }, - // clearScheduleBannerRef: assign({ - // scheduleBannerRef: (_) => undefined - // }), sendWorkspaceToSchedule: send( (context) => ({ type: "REFRESH_WORKSPACE",