fix: fix workspace actions options (#13572)

This commit is contained in:
Kayla Washburn-Love
2024-06-17 10:24:30 -06:00
committed by GitHub
parent eed9794516
commit 07cd9acb2c
5 changed files with 144 additions and 108 deletions

View File

@ -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&hellip;
</TopbarButton>
</Tooltip>
);
};
export const StopButton: FC<ActionButtonProps> = ({
handleAction,
loading,
@ -148,16 +163,13 @@ export const RestartButton: FC<ActionButtonPropsWithWorkspace> = ({
);
};
export const UpdateAndStartButton: FC<ActionButtonProps> = ({
export const UpdateAndRestartButton: 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&hellip;
<TopbarButton startIcon={<ReplayIcon />} onClick={() => handleAction()}>
Update and restart&hellip;
</TopbarButton>
</Tooltip>
);

View File

@ -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 = {
args: {
workspace: Mocks.MockStoppingWorkspace,
@ -46,15 +77,54 @@ export const Stopped: Story = {
},
};
export const Canceling: Story = {
export const StoppedUpdateAvailable: Story = {
name: "Stopped (Update available)",
args: {
workspace: Mocks.MockCancelingWorkspace,
workspace: {
...Mocks.MockStoppedWorkspace,
outdated: true,
},
},
};
export const Canceled: Story = {
export const StoppedRequireActiveVersion: Story = {
name: "Stopped (No required update)",
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 = {
args: {
workspace: {
@ -133,6 +168,7 @@ export const CancelShownForOwner: Story = {
isOwner: true,
},
};
export const CancelShownForUser: Story = {
args: {
workspace: Mocks.MockStartingWorkspace,

View File

@ -26,6 +26,7 @@ import {
ActivateButton,
FavoriteButton,
UpdateAndStartButton,
UpdateAndRestartButton,
} from "./Buttons";
import { type ActionType, abilitiesByWorkspaceStatus } from "./constants";
import { DebugButton } from "./DebugButton";
@ -89,12 +90,12 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
const mustUpdate = mustUpdateWorkspace(workspace, canChangeVersions);
const tooltipText = getTooltipText(workspace, mustUpdate, canChangeVersions);
const canBeUpdated = workspace.outdated && canAcceptJobs;
// A mapping of button type to the corresponding React component
const buttonMapping: Record<ActionType, ReactNode> = {
update: <UpdateButton handleAction={handleUpdate} />,
updateAndStart: <UpdateAndStartButton handleAction={handleUpdate} />,
updateAndRestart: <UpdateAndRestartButton handleAction={handleUpdate} />,
updating: <UpdateButton loading handleAction={handleUpdate} />,
start: (
<StartButton
@ -152,13 +153,6 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
enableBuildParameters={workspace.latest_build.transition === "start"}
/>
),
toggleFavorite: (
<FavoriteButton
workspaceID={workspace.id}
isFavorite={workspace.favorite}
onToggle={handleToggleFavorite}
/>
),
};
return (
@ -166,30 +160,22 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
css={{ display: "flex", alignItems: "center", gap: 8 }}
data-testid="workspace-actions"
>
{canBeUpdated && (
<>
{isUpdating
? buttonMapping.updating
: workspace.template_require_active_version
? buttonMapping.updateAndStart
: buttonMapping.update}
</>
)}
{!canBeUpdated &&
!isUpdating &&
workspace.template_require_active_version &&
buttonMapping.start}
{isRestarting
? buttonMapping.restarting
: actions.map((action) => (
<Fragment key={action}>{buttonMapping[action]}</Fragment>
))}
{/* Restarting must be handled separately, because it otherwise would appear as stopping */}
{isUpdating
? buttonMapping.updating
: isRestarting
? buttonMapping.restarting
: actions.map((action) => (
<Fragment key={action}>{buttonMapping[action]}</Fragment>
))}
{showCancel && <CancelButton handleAction={handleCancel} />}
{buttonMapping.toggleFavorite}
<FavoriteButton
workspaceID={workspace.id}
isFavorite={workspace.favorite}
onToggle={handleToggleFavorite}
/>
<MoreMenu>
<MoreMenuTrigger>

View File

@ -6,16 +6,19 @@ import type { Workspace } from "api/typesGenerated";
export const actionTypes = [
"start",
"starting",
// Replaces start when an update is required.
"updateAndStart",
"stop",
"stopping",
"restart",
"restarting",
// Replaces restart when an update is required.
"updateAndRestart",
"deleting",
"update",
"updating",
"activate",
"activating",
"toggleFavorite",
// 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
@ -23,10 +26,6 @@ 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",
@ -54,13 +53,6 @@ export const abilitiesByWorkspaceStatus = (
}
const status = workspace.latest_build.status;
if (status === "failed" && canDebug) {
return {
actions: ["retry", "debug"],
canCancel: false,
canAcceptJobs: true,
};
}
switch (status) {
case "starting": {
@ -73,10 +65,12 @@ export const abilitiesByWorkspaceStatus = (
case "running": {
const actions: ActionType[] = ["stop"];
// 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) {
if (workspace.template_require_active_version && workspace.outdated) {
actions.push("updateAndRestart");
} else {
if (workspace.outdated) {
actions.unshift("update");
}
actions.push("restart");
}
@ -96,10 +90,12 @@ export const abilitiesByWorkspaceStatus = (
case "stopped": {
const actions: ActionType[] = [];
// 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) {
if (workspace.template_require_active_version && workspace.outdated) {
actions.push("updateAndStart");
} else {
if (workspace.outdated) {
actions.unshift("update");
}
actions.push("start");
}
@ -117,14 +113,31 @@ export const abilitiesByWorkspaceStatus = (
};
}
case "failed": {
const actions: ActionType[] = ["retry"];
if (canDebug) {
actions.push("debug");
}
if (workspace.outdated) {
actions.unshift("update");
}
return {
actions: ["retry"],
actions,
canCancel: false,
canAcceptJobs: true,
};
}
// Disabled states
case "pending": {
return {
actions: ["pending"],
canCancel: false,
canAcceptJobs: false,
};
}
case "canceling": {
return {
actions: ["canceling"],
@ -146,15 +159,8 @@ export const abilitiesByWorkspaceStatus = (
canAcceptJobs: false,
};
}
case "pending": {
return {
actions: ["pending"],
canCancel: false,
canAcceptJobs: false,
};
}
default: {
default:
throw new Error(`Unknown workspace status: ${status}`);
}
}
};

View File

@ -1175,10 +1175,6 @@ export const MockOutdatedRunningWorkspaceRequireActiveVersion: TypesGen.Workspac
id: "test-outdated-workspace-require-active-version",
outdated: true,
template_require_active_version: true,
latest_build: {
...MockWorkspaceBuild,
status: "running",
},
};
export const MockOutdatedRunningWorkspaceAlwaysUpdate: TypesGen.Workspace = {