diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx index 2eb39b04d3f43..caa034d77c29f 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx @@ -117,10 +117,8 @@ export const RestartButton: FC = ({ handleAction, loading, workspace, - disabled, - tooltipText, }) => { - const buttonContent = ( + return ( = ({ borderLeft: "1px solid #FFF", }, }} - disabled={disabled} > } onClick={() => handleAction()} data-testid="workspace-restart-button" - disabled={disabled || loading} + disabled={loading} > {loading ? <>Restarting… : <>Restart…} @@ -147,11 +144,20 @@ export const RestartButton: FC = ({ /> ); +}; - return tooltipText ? ( - {buttonContent} - ) : ( - buttonContent +export const UpdateAndStartButton: FC = ({ + handleAction, +}) => { + return ( + + } + onClick={() => handleAction()} + > + Update and start… + + ); }; diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.tsx index e90d844dbda70..ad79ce1be9c95 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.tsx @@ -24,6 +24,7 @@ import { UpdateButton, ActivateButton, FavoriteButton, + UpdateAndStartButton, } from "./Buttons"; import { type ActionType, abilitiesByWorkspaceStatus } from "./constants"; import { DebugButton } from "./DebugButton"; @@ -89,6 +90,7 @@ export const WorkspaceActions: FC = ({ // A mapping of button type to the corresponding React component const buttonMapping: Record = { update: , + updateAndStart: , updating: , start: ( = ({ data-testid="workspace-actions" > {canBeUpdated && ( - <>{isUpdating ? buttonMapping.updating : buttonMapping.update} + <> + {isUpdating + ? buttonMapping.updating + : workspace.template_require_active_version + ? buttonMapping.updateAndStart + : buttonMapping.update} + )} {isRestarting @@ -236,10 +244,6 @@ function getTooltipText( return "This template requires automatic updates on workspace startup, but template administrators can ignore this policy."; } - if (workspace.template_require_active_version) { - return "This template requires automatic updates on workspace startup. Contact your administrator if you want to preserve the template version."; - } - if (workspace.automatic_updates === "always") { return "Automatic updates are enabled for this workspace. Modify the update policy in workspace settings if you want to preserve the template version."; } diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/constants.ts b/site/src/pages/WorkspacePage/WorkspaceActions/constants.ts index 3c7347cc52864..c2a85da8cb121 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/constants.ts +++ b/site/src/pages/WorkspacePage/WorkspaceActions/constants.ts @@ -1,4 +1,4 @@ -import type { Workspace, WorkspaceStatus } from "api/typesGenerated"; +import type { Workspace } from "api/typesGenerated"; /** * An iterable of all action types supported by the workspace UI @@ -23,6 +23,10 @@ export const actionTypes = [ "retry", "debug", + // When a template requires updates, we aim to display a distinct update + // button that clearly indicates a mandatory update. + "updateAndStart", + // These are buttons that should be used with disabled UI elements "canceling", "deleted", @@ -52,67 +56,105 @@ export const abilitiesByWorkspaceStatus = ( const status = workspace.latest_build.status; if (status === "failed" && canDebug) { return { - ...statusToAbility.failed, actions: ["retry", "debug"], + canCancel: false, + canAcceptJobs: true, }; } - return statusToAbility[status]; -}; + switch (status) { + case "starting": { + return { + actions: ["starting"], + canCancel: true, + canAcceptJobs: false, + }; + } + case "running": { + const actions: ActionType[] = ["stop"]; -const statusToAbility: Record = { - starting: { - actions: ["starting"], - canCancel: true, - canAcceptJobs: false, - }, - running: { - actions: ["stop", "restart"], - canCancel: false, - canAcceptJobs: true, - }, - stopping: { - actions: ["stopping"], - canCancel: true, - canAcceptJobs: false, - }, - stopped: { - actions: ["start"], - canCancel: false, - canAcceptJobs: true, - }, - canceled: { - actions: ["start", "stop"], - canCancel: false, - canAcceptJobs: true, - }, + // If the template requires the latest version, we prevent the user from + // restarting the workspace without updating it first. In the Buttons + // component, we display an UpdateAndStart component to facilitate this. + if (!workspace.template_require_active_version) { + actions.push("restart"); + } - // in the case of an error - failed: { - actions: ["retry"], - canCancel: false, - canAcceptJobs: true, - }, + return { + actions, + canCancel: false, + canAcceptJobs: true, + }; + } + case "stopping": { + return { + actions: ["stopping"], + canCancel: true, + canAcceptJobs: false, + }; + } + case "stopped": { + const actions: ActionType[] = []; - // Disabled states - canceling: { - actions: ["canceling"], - canCancel: false, - canAcceptJobs: false, - }, - deleting: { - actions: ["deleting"], - canCancel: true, - canAcceptJobs: false, - }, - deleted: { - actions: ["deleted"], - canCancel: false, - canAcceptJobs: false, - }, - pending: { - actions: ["pending"], - canCancel: false, - canAcceptJobs: false, - }, + // If the template requires the latest version, we prevent the user from + // starting the workspace without updating it first. In the Buttons + // component, we display an UpdateAndStart component to facilitate this. + if (!workspace.template_require_active_version) { + actions.push("start"); + } + + return { + actions, + canCancel: false, + canAcceptJobs: true, + }; + } + case "canceled": { + return { + actions: ["start", "stop"], + canCancel: false, + canAcceptJobs: true, + }; + } + case "failed": { + return { + actions: ["retry"], + canCancel: false, + canAcceptJobs: true, + }; + } + + // Disabled states + case "canceling": { + return { + actions: ["canceling"], + canCancel: false, + canAcceptJobs: false, + }; + } + case "deleting": { + return { + actions: ["deleting"], + canCancel: true, + canAcceptJobs: false, + }; + } + case "deleted": { + return { + actions: ["deleted"], + canCancel: false, + canAcceptJobs: false, + }; + } + case "pending": { + return { + actions: ["pending"], + canCancel: false, + canAcceptJobs: false, + }; + } + default: { + throw new Error(`Unknown workspace status: ${status}`); + } + } };