mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat: disable start/restart if active version required (#10809)
This commit is contained in:
@@ -11,10 +11,13 @@ import { Workspace, WorkspaceBuildParameter } from "api/typesGenerated";
|
||||
import { BuildParametersPopover } from "./BuildParametersPopover";
|
||||
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
|
||||
interface WorkspaceAction {
|
||||
loading?: boolean;
|
||||
handleAction: () => void;
|
||||
disabled?: boolean;
|
||||
tooltipText?: string;
|
||||
}
|
||||
|
||||
export const UpdateButton: FC<WorkspaceAction> = ({
|
||||
@@ -55,8 +58,8 @@ export const StartButton: FC<
|
||||
workspace: Workspace;
|
||||
handleAction: (buildParameters?: WorkspaceBuildParameter[]) => void;
|
||||
}
|
||||
> = ({ handleAction, workspace, loading }) => {
|
||||
return (
|
||||
> = ({ handleAction, workspace, loading, disabled, tooltipText }) => {
|
||||
const buttonContent = (
|
||||
<ButtonGroup
|
||||
variant="outlined"
|
||||
sx={{
|
||||
@@ -65,12 +68,14 @@ export const StartButton: FC<
|
||||
borderLeft: "1px solid #FFF",
|
||||
},
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
loadingPosition="start"
|
||||
startIcon={<PlayCircleOutlineIcon />}
|
||||
onClick={() => handleAction()}
|
||||
disabled={disabled}
|
||||
>
|
||||
{loading ? <>Starting…</> : "Start"}
|
||||
</LoadingButton>
|
||||
@@ -81,6 +86,12 @@ export const StartButton: FC<
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
|
||||
return tooltipText ? (
|
||||
<Tooltip title={tooltipText}>{buttonContent}</Tooltip>
|
||||
) : (
|
||||
buttonContent
|
||||
);
|
||||
};
|
||||
|
||||
export const StopButton: FC<WorkspaceAction> = ({ handleAction, loading }) => {
|
||||
@@ -102,8 +113,8 @@ export const RestartButton: FC<
|
||||
workspace: Workspace;
|
||||
handleAction: (buildParameters?: WorkspaceBuildParameter[]) => void;
|
||||
}
|
||||
> = ({ handleAction, loading, workspace }) => {
|
||||
return (
|
||||
> = ({ handleAction, loading, workspace, disabled, tooltipText }) => {
|
||||
const buttonContent = (
|
||||
<ButtonGroup
|
||||
variant="outlined"
|
||||
sx={{
|
||||
@@ -112,6 +123,7 @@ export const RestartButton: FC<
|
||||
borderLeft: "1px solid #FFF",
|
||||
},
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
@@ -119,6 +131,7 @@ export const RestartButton: FC<
|
||||
startIcon={<ReplayIcon />}
|
||||
onClick={() => handleAction()}
|
||||
data-testid="workspace-restart-button"
|
||||
disabled={disabled}
|
||||
>
|
||||
{loading ? <>Restarting…</> : <>Restart…</>}
|
||||
</LoadingButton>
|
||||
@@ -129,6 +142,12 @@ export const RestartButton: FC<
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
|
||||
return tooltipText ? (
|
||||
<Tooltip title={tooltipText}>{buttonContent}</Tooltip>
|
||||
) : (
|
||||
buttonContent
|
||||
);
|
||||
};
|
||||
|
||||
export const CancelButton: FC<WorkspaceAction> = ({ handleAction }) => {
|
||||
|
||||
@@ -93,3 +93,17 @@ export const RequireActiveVersionStopped: Story = {
|
||||
canChangeVersions: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const AlwaysUpdateStarted: Story = {
|
||||
args: {
|
||||
workspace: Mocks.MockOutdatedRunningWorkspaceAlwaysUpdate,
|
||||
canChangeVersions: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const AlwaysUpdateStopped: Story = {
|
||||
args: {
|
||||
workspace: Mocks.MockOutdatedStoppedWorkspaceAlwaysUpdate,
|
||||
canChangeVersions: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
MoreMenuTrigger,
|
||||
ThreeDotsButton,
|
||||
} from "components/MoreMenu/MoreMenu";
|
||||
import { workspaceUpdatePolicy } from "utils/workspace";
|
||||
|
||||
export interface WorkspaceActionsProps {
|
||||
workspace: Workspace;
|
||||
@@ -67,15 +68,28 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
canCancel,
|
||||
canAcceptJobs,
|
||||
actions: actionsByStatus,
|
||||
} = actionsByWorkspaceStatus(
|
||||
workspace,
|
||||
workspace.latest_build.status,
|
||||
canChangeVersions,
|
||||
);
|
||||
} = actionsByWorkspaceStatus(workspace, workspace.latest_build.status);
|
||||
const canBeUpdated = workspace.outdated && canAcceptJobs;
|
||||
const { duplicateWorkspace, isDuplicationReady } =
|
||||
useWorkspaceDuplication(workspace);
|
||||
|
||||
const disabled =
|
||||
workspaceUpdatePolicy(workspace, canChangeVersions) === "always" &&
|
||||
workspace.outdated;
|
||||
|
||||
const tooltipText = ((): string => {
|
||||
if (!disabled) {
|
||||
return "";
|
||||
}
|
||||
if (workspace.template_require_active_version) {
|
||||
return "This template requires automatic updates";
|
||||
}
|
||||
if (workspace.automatic_updates === "always") {
|
||||
return "You have enabled automatic updates for this workspace";
|
||||
}
|
||||
return "";
|
||||
})();
|
||||
|
||||
// A mapping of button type to the corresponding React component
|
||||
const buttonMapping: ButtonMapping = {
|
||||
[ButtonTypesEnum.update]: <UpdateButton handleAction={handleUpdate} />,
|
||||
@@ -83,23 +97,41 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
<UpdateButton loading handleAction={handleUpdate} />
|
||||
),
|
||||
[ButtonTypesEnum.start]: (
|
||||
<StartButton workspace={workspace} handleAction={handleStart} />
|
||||
<StartButton
|
||||
workspace={workspace}
|
||||
handleAction={handleStart}
|
||||
disabled={disabled}
|
||||
tooltipText={tooltipText}
|
||||
/>
|
||||
),
|
||||
[ButtonTypesEnum.starting]: (
|
||||
<StartButton loading workspace={workspace} handleAction={handleStart} />
|
||||
<StartButton
|
||||
loading
|
||||
workspace={workspace}
|
||||
handleAction={handleStart}
|
||||
disabled={disabled}
|
||||
tooltipText={tooltipText}
|
||||
/>
|
||||
),
|
||||
[ButtonTypesEnum.stop]: <StopButton handleAction={handleStop} />,
|
||||
[ButtonTypesEnum.stopping]: (
|
||||
<StopButton loading handleAction={handleStop} />
|
||||
),
|
||||
[ButtonTypesEnum.restart]: (
|
||||
<RestartButton workspace={workspace} handleAction={handleRestart} />
|
||||
<RestartButton
|
||||
workspace={workspace}
|
||||
handleAction={handleRestart}
|
||||
disabled={disabled}
|
||||
tooltipText={tooltipText}
|
||||
/>
|
||||
),
|
||||
[ButtonTypesEnum.restarting]: (
|
||||
<RestartButton
|
||||
loading
|
||||
workspace={workspace}
|
||||
handleAction={handleRestart}
|
||||
disabled={disabled}
|
||||
tooltipText={tooltipText}
|
||||
/>
|
||||
),
|
||||
[ButtonTypesEnum.deleting]: <ActionLoadingButton label="Deleting" />,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Workspace, WorkspaceStatus } from "api/typesGenerated";
|
||||
import { ReactNode } from "react";
|
||||
import { workspaceUpdatePolicy } from "utils/workspace";
|
||||
|
||||
// the button types we have
|
||||
export enum ButtonTypesEnum {
|
||||
@@ -34,7 +33,6 @@ interface WorkspaceAbilities {
|
||||
export const actionsByWorkspaceStatus = (
|
||||
workspace: Workspace,
|
||||
status: WorkspaceStatus,
|
||||
canChangeVersions: boolean,
|
||||
): WorkspaceAbilities => {
|
||||
if (workspace.dormant_at) {
|
||||
return {
|
||||
@@ -43,25 +41,6 @@ export const actionsByWorkspaceStatus = (
|
||||
canAcceptJobs: false,
|
||||
};
|
||||
}
|
||||
if (
|
||||
workspace.outdated &&
|
||||
workspaceUpdatePolicy(workspace, canChangeVersions) === "always"
|
||||
) {
|
||||
if (status === "running") {
|
||||
return {
|
||||
actions: [ButtonTypesEnum.stop],
|
||||
canCancel: false,
|
||||
canAcceptJobs: true,
|
||||
};
|
||||
}
|
||||
if (status === "stopped") {
|
||||
return {
|
||||
actions: [],
|
||||
canCancel: false,
|
||||
canAcceptJobs: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
return statusToActions[status];
|
||||
};
|
||||
|
||||
|
||||
@@ -1057,6 +1057,17 @@ export const MockOutdatedRunningWorkspaceRequireActiveVersion: TypesGen.Workspac
|
||||
},
|
||||
};
|
||||
|
||||
export const MockOutdatedRunningWorkspaceAlwaysUpdate: TypesGen.Workspace = {
|
||||
...MockWorkspace,
|
||||
id: "test-outdated-workspace-always-update",
|
||||
outdated: true,
|
||||
automatic_updates: "always",
|
||||
latest_build: {
|
||||
...MockWorkspaceBuild,
|
||||
status: "running",
|
||||
},
|
||||
};
|
||||
|
||||
export const MockOutdatedStoppedWorkspaceRequireActiveVersion: TypesGen.Workspace =
|
||||
{
|
||||
...MockOutdatedRunningWorkspaceRequireActiveVersion,
|
||||
@@ -1066,6 +1077,14 @@ export const MockOutdatedStoppedWorkspaceRequireActiveVersion: TypesGen.Workspac
|
||||
},
|
||||
};
|
||||
|
||||
export const MockOutdatedStoppedWorkspaceAlwaysUpdate: TypesGen.Workspace = {
|
||||
...MockOutdatedRunningWorkspaceAlwaysUpdate,
|
||||
latest_build: {
|
||||
...MockWorkspaceBuild,
|
||||
status: "stopped",
|
||||
},
|
||||
};
|
||||
|
||||
export const MockPendingWorkspace: TypesGen.Workspace = {
|
||||
...MockWorkspace,
|
||||
id: "test-pending-workspace",
|
||||
|
||||
Reference in New Issue
Block a user