mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
fix: fix workspace actions options (#13572)
This commit is contained in:
committed by
GitHub
parent
eed9794516
commit
07cd9acb2c
@ -99,6 +99,21 @@ export const StartButton: FC<ActionButtonPropsWithWorkspace> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const UpdateAndStartButton: FC<ActionButtonProps> = ({
|
||||||
|
handleAction,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Tooltip title="This template requires automatic updates on workspace startup. Contact your administrator if you want to preserve the template version.">
|
||||||
|
<TopbarButton
|
||||||
|
startIcon={<PlayCircleOutlineIcon />}
|
||||||
|
onClick={() => handleAction()}
|
||||||
|
>
|
||||||
|
Update and start…
|
||||||
|
</TopbarButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const StopButton: FC<ActionButtonProps> = ({
|
export const StopButton: FC<ActionButtonProps> = ({
|
||||||
handleAction,
|
handleAction,
|
||||||
loading,
|
loading,
|
||||||
@ -148,16 +163,13 @@ export const RestartButton: FC<ActionButtonPropsWithWorkspace> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UpdateAndStartButton: FC<ActionButtonProps> = ({
|
export const UpdateAndRestartButton: FC<ActionButtonProps> = ({
|
||||||
handleAction,
|
handleAction,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip title="This template requires automatic updates on workspace startup. Contact your administrator if you want to preserve the template version.">
|
<Tooltip title="This template requires automatic updates on workspace startup. Contact your administrator if you want to preserve the template version.">
|
||||||
<TopbarButton
|
<TopbarButton startIcon={<ReplayIcon />} onClick={() => handleAction()}>
|
||||||
startIcon={<PlayCircleOutlineIcon />}
|
Update and restart…
|
||||||
onClick={() => handleAction()}
|
|
||||||
>
|
|
||||||
Update and start…
|
|
||||||
</TopbarButton>
|
</TopbarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
@ -34,6 +34,37 @@ export const Running: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const RunningUpdateAvailable: Story = {
|
||||||
|
name: "Running (Update available)",
|
||||||
|
args: {
|
||||||
|
workspace: {
|
||||||
|
...Mocks.MockWorkspace,
|
||||||
|
outdated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RunningRequireActiveVersion: Story = {
|
||||||
|
name: "Running (No required update)",
|
||||||
|
args: {
|
||||||
|
workspace: {
|
||||||
|
...Mocks.MockWorkspace,
|
||||||
|
template_require_active_version: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RunningUpdateRequired: Story = {
|
||||||
|
name: "Running (Update Required)",
|
||||||
|
args: {
|
||||||
|
workspace: {
|
||||||
|
...Mocks.MockWorkspace,
|
||||||
|
template_require_active_version: true,
|
||||||
|
outdated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const Stopping: Story = {
|
export const Stopping: Story = {
|
||||||
args: {
|
args: {
|
||||||
workspace: Mocks.MockStoppingWorkspace,
|
workspace: Mocks.MockStoppingWorkspace,
|
||||||
@ -46,15 +77,54 @@ export const Stopped: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Canceling: Story = {
|
export const StoppedUpdateAvailable: Story = {
|
||||||
|
name: "Stopped (Update available)",
|
||||||
args: {
|
args: {
|
||||||
workspace: Mocks.MockCancelingWorkspace,
|
workspace: {
|
||||||
|
...Mocks.MockStoppedWorkspace,
|
||||||
|
outdated: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Canceled: Story = {
|
export const StoppedRequireActiveVersion: Story = {
|
||||||
|
name: "Stopped (No required update)",
|
||||||
args: {
|
args: {
|
||||||
workspace: Mocks.MockCanceledWorkspace,
|
workspace: {
|
||||||
|
...Mocks.MockStoppedWorkspace,
|
||||||
|
template_require_active_version: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StoppedUpdateRequired: Story = {
|
||||||
|
name: "Stopped (Update Required)",
|
||||||
|
args: {
|
||||||
|
workspace: {
|
||||||
|
...Mocks.MockStoppedWorkspace,
|
||||||
|
template_require_active_version: true,
|
||||||
|
outdated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Updating: Story = {
|
||||||
|
args: {
|
||||||
|
workspace: Mocks.MockOutdatedWorkspace,
|
||||||
|
isUpdating: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Restarting: Story = {
|
||||||
|
args: {
|
||||||
|
workspace: Mocks.MockStoppingWorkspace,
|
||||||
|
isRestarting: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Canceling: Story = {
|
||||||
|
args: {
|
||||||
|
workspace: Mocks.MockCancelingWorkspace,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,41 +159,6 @@ export const FailedWithDebug: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Updating: Story = {
|
|
||||||
args: {
|
|
||||||
isUpdating: true,
|
|
||||||
workspace: Mocks.MockOutdatedWorkspace,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RequireActiveVersionStarted: Story = {
|
|
||||||
args: {
|
|
||||||
workspace: Mocks.MockOutdatedRunningWorkspaceRequireActiveVersion,
|
|
||||||
canChangeVersions: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RequireActiveVersionStopped: Story = {
|
|
||||||
args: {
|
|
||||||
workspace: Mocks.MockOutdatedStoppedWorkspaceRequireActiveVersion,
|
|
||||||
canChangeVersions: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AlwaysUpdateStarted: Story = {
|
|
||||||
args: {
|
|
||||||
workspace: Mocks.MockOutdatedRunningWorkspaceAlwaysUpdate,
|
|
||||||
canChangeVersions: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AlwaysUpdateStopped: Story = {
|
|
||||||
args: {
|
|
||||||
workspace: Mocks.MockOutdatedStoppedWorkspaceAlwaysUpdate,
|
|
||||||
canChangeVersions: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CancelShownForOwner: Story = {
|
export const CancelShownForOwner: Story = {
|
||||||
args: {
|
args: {
|
||||||
workspace: {
|
workspace: {
|
||||||
@ -133,6 +168,7 @@ export const CancelShownForOwner: Story = {
|
|||||||
isOwner: true,
|
isOwner: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CancelShownForUser: Story = {
|
export const CancelShownForUser: Story = {
|
||||||
args: {
|
args: {
|
||||||
workspace: Mocks.MockStartingWorkspace,
|
workspace: Mocks.MockStartingWorkspace,
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
ActivateButton,
|
ActivateButton,
|
||||||
FavoriteButton,
|
FavoriteButton,
|
||||||
UpdateAndStartButton,
|
UpdateAndStartButton,
|
||||||
|
UpdateAndRestartButton,
|
||||||
} from "./Buttons";
|
} from "./Buttons";
|
||||||
import { type ActionType, abilitiesByWorkspaceStatus } from "./constants";
|
import { type ActionType, abilitiesByWorkspaceStatus } from "./constants";
|
||||||
import { DebugButton } from "./DebugButton";
|
import { DebugButton } from "./DebugButton";
|
||||||
@ -89,12 +90,12 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
|||||||
|
|
||||||
const mustUpdate = mustUpdateWorkspace(workspace, canChangeVersions);
|
const mustUpdate = mustUpdateWorkspace(workspace, canChangeVersions);
|
||||||
const tooltipText = getTooltipText(workspace, mustUpdate, canChangeVersions);
|
const tooltipText = getTooltipText(workspace, mustUpdate, canChangeVersions);
|
||||||
const canBeUpdated = workspace.outdated && canAcceptJobs;
|
|
||||||
|
|
||||||
// A mapping of button type to the corresponding React component
|
// A mapping of button type to the corresponding React component
|
||||||
const buttonMapping: Record<ActionType, ReactNode> = {
|
const buttonMapping: Record<ActionType, ReactNode> = {
|
||||||
update: <UpdateButton handleAction={handleUpdate} />,
|
update: <UpdateButton handleAction={handleUpdate} />,
|
||||||
updateAndStart: <UpdateAndStartButton handleAction={handleUpdate} />,
|
updateAndStart: <UpdateAndStartButton handleAction={handleUpdate} />,
|
||||||
|
updateAndRestart: <UpdateAndRestartButton handleAction={handleUpdate} />,
|
||||||
updating: <UpdateButton loading handleAction={handleUpdate} />,
|
updating: <UpdateButton loading handleAction={handleUpdate} />,
|
||||||
start: (
|
start: (
|
||||||
<StartButton
|
<StartButton
|
||||||
@ -152,13 +153,6 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
|||||||
enableBuildParameters={workspace.latest_build.transition === "start"}
|
enableBuildParameters={workspace.latest_build.transition === "start"}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
toggleFavorite: (
|
|
||||||
<FavoriteButton
|
|
||||||
workspaceID={workspace.id}
|
|
||||||
isFavorite={workspace.favorite}
|
|
||||||
onToggle={handleToggleFavorite}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -166,30 +160,22 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
|||||||
css={{ display: "flex", alignItems: "center", gap: 8 }}
|
css={{ display: "flex", alignItems: "center", gap: 8 }}
|
||||||
data-testid="workspace-actions"
|
data-testid="workspace-actions"
|
||||||
>
|
>
|
||||||
{canBeUpdated && (
|
{/* Restarting must be handled separately, because it otherwise would appear as stopping */}
|
||||||
<>
|
{isUpdating
|
||||||
{isUpdating
|
? buttonMapping.updating
|
||||||
? buttonMapping.updating
|
: isRestarting
|
||||||
: workspace.template_require_active_version
|
? buttonMapping.restarting
|
||||||
? buttonMapping.updateAndStart
|
: actions.map((action) => (
|
||||||
: buttonMapping.update}
|
<Fragment key={action}>{buttonMapping[action]}</Fragment>
|
||||||
</>
|
))}
|
||||||
)}
|
|
||||||
|
|
||||||
{!canBeUpdated &&
|
|
||||||
!isUpdating &&
|
|
||||||
workspace.template_require_active_version &&
|
|
||||||
buttonMapping.start}
|
|
||||||
|
|
||||||
{isRestarting
|
|
||||||
? buttonMapping.restarting
|
|
||||||
: actions.map((action) => (
|
|
||||||
<Fragment key={action}>{buttonMapping[action]}</Fragment>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{showCancel && <CancelButton handleAction={handleCancel} />}
|
{showCancel && <CancelButton handleAction={handleCancel} />}
|
||||||
|
|
||||||
{buttonMapping.toggleFavorite}
|
<FavoriteButton
|
||||||
|
workspaceID={workspace.id}
|
||||||
|
isFavorite={workspace.favorite}
|
||||||
|
onToggle={handleToggleFavorite}
|
||||||
|
/>
|
||||||
|
|
||||||
<MoreMenu>
|
<MoreMenu>
|
||||||
<MoreMenuTrigger>
|
<MoreMenuTrigger>
|
||||||
|
@ -6,16 +6,19 @@ import type { Workspace } from "api/typesGenerated";
|
|||||||
export const actionTypes = [
|
export const actionTypes = [
|
||||||
"start",
|
"start",
|
||||||
"starting",
|
"starting",
|
||||||
|
// Replaces start when an update is required.
|
||||||
|
"updateAndStart",
|
||||||
"stop",
|
"stop",
|
||||||
"stopping",
|
"stopping",
|
||||||
"restart",
|
"restart",
|
||||||
"restarting",
|
"restarting",
|
||||||
|
// Replaces restart when an update is required.
|
||||||
|
"updateAndRestart",
|
||||||
"deleting",
|
"deleting",
|
||||||
"update",
|
"update",
|
||||||
"updating",
|
"updating",
|
||||||
"activate",
|
"activate",
|
||||||
"activating",
|
"activating",
|
||||||
"toggleFavorite",
|
|
||||||
|
|
||||||
// There's no need for a retrying state because retrying starts a transition
|
// There's no need for a retrying state because retrying starts a transition
|
||||||
// into one of the starting, stopping, or deleting states (based on the
|
// into one of the starting, stopping, or deleting states (based on the
|
||||||
@ -23,10 +26,6 @@ export const actionTypes = [
|
|||||||
"retry",
|
"retry",
|
||||||
"debug",
|
"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
|
// These are buttons that should be used with disabled UI elements
|
||||||
"canceling",
|
"canceling",
|
||||||
"deleted",
|
"deleted",
|
||||||
@ -54,13 +53,6 @@ export const abilitiesByWorkspaceStatus = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const status = workspace.latest_build.status;
|
const status = workspace.latest_build.status;
|
||||||
if (status === "failed" && canDebug) {
|
|
||||||
return {
|
|
||||||
actions: ["retry", "debug"],
|
|
||||||
canCancel: false,
|
|
||||||
canAcceptJobs: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "starting": {
|
case "starting": {
|
||||||
@ -73,10 +65,12 @@ export const abilitiesByWorkspaceStatus = (
|
|||||||
case "running": {
|
case "running": {
|
||||||
const actions: ActionType[] = ["stop"];
|
const actions: ActionType[] = ["stop"];
|
||||||
|
|
||||||
// If the template requires the latest version, we prevent the user from
|
if (workspace.template_require_active_version && workspace.outdated) {
|
||||||
// restarting the workspace without updating it first. In the Buttons
|
actions.push("updateAndRestart");
|
||||||
// component, we display an UpdateAndStart component to facilitate this.
|
} else {
|
||||||
if (!workspace.template_require_active_version) {
|
if (workspace.outdated) {
|
||||||
|
actions.unshift("update");
|
||||||
|
}
|
||||||
actions.push("restart");
|
actions.push("restart");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,10 +90,12 @@ export const abilitiesByWorkspaceStatus = (
|
|||||||
case "stopped": {
|
case "stopped": {
|
||||||
const actions: ActionType[] = [];
|
const actions: ActionType[] = [];
|
||||||
|
|
||||||
// If the template requires the latest version, we prevent the user from
|
if (workspace.template_require_active_version && workspace.outdated) {
|
||||||
// starting the workspace without updating it first. In the Buttons
|
actions.push("updateAndStart");
|
||||||
// component, we display an UpdateAndStart component to facilitate this.
|
} else {
|
||||||
if (!workspace.template_require_active_version) {
|
if (workspace.outdated) {
|
||||||
|
actions.unshift("update");
|
||||||
|
}
|
||||||
actions.push("start");
|
actions.push("start");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,14 +113,31 @@ export const abilitiesByWorkspaceStatus = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "failed": {
|
case "failed": {
|
||||||
|
const actions: ActionType[] = ["retry"];
|
||||||
|
|
||||||
|
if (canDebug) {
|
||||||
|
actions.push("debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspace.outdated) {
|
||||||
|
actions.unshift("update");
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actions: ["retry"],
|
actions,
|
||||||
canCancel: false,
|
canCancel: false,
|
||||||
canAcceptJobs: true,
|
canAcceptJobs: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disabled states
|
// Disabled states
|
||||||
|
case "pending": {
|
||||||
|
return {
|
||||||
|
actions: ["pending"],
|
||||||
|
canCancel: false,
|
||||||
|
canAcceptJobs: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
case "canceling": {
|
case "canceling": {
|
||||||
return {
|
return {
|
||||||
actions: ["canceling"],
|
actions: ["canceling"],
|
||||||
@ -146,15 +159,8 @@ export const abilitiesByWorkspaceStatus = (
|
|||||||
canAcceptJobs: false,
|
canAcceptJobs: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "pending": {
|
|
||||||
return {
|
default:
|
||||||
actions: ["pending"],
|
|
||||||
canCancel: false,
|
|
||||||
canAcceptJobs: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new Error(`Unknown workspace status: ${status}`);
|
throw new Error(`Unknown workspace status: ${status}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1175,10 +1175,6 @@ export const MockOutdatedRunningWorkspaceRequireActiveVersion: TypesGen.Workspac
|
|||||||
id: "test-outdated-workspace-require-active-version",
|
id: "test-outdated-workspace-require-active-version",
|
||||||
outdated: true,
|
outdated: true,
|
||||||
template_require_active_version: true,
|
template_require_active_version: true,
|
||||||
latest_build: {
|
|
||||||
...MockWorkspaceBuild,
|
|
||||||
status: "running",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MockOutdatedRunningWorkspaceAlwaysUpdate: TypesGen.Workspace = {
|
export const MockOutdatedRunningWorkspaceAlwaysUpdate: TypesGen.Workspace = {
|
||||||
|
Reference in New Issue
Block a user