mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
fix(site): retry and debug passing build parameters options (#12384)
This commit is contained in:
@@ -74,14 +74,17 @@ interface SidebarIconButton extends ComponentProps<typeof TopbarIconButton> {
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export const SidebarIconButton: FC<SidebarIconButton> = (props) => {
|
||||
export const SidebarIconButton: FC<SidebarIconButton> = ({
|
||||
isActive,
|
||||
...buttonProps
|
||||
}) => {
|
||||
return (
|
||||
<TopbarIconButton
|
||||
css={[
|
||||
{ opacity: 0.75, "&:hover": { opacity: 1 } },
|
||||
props.isActive && styles.activeSidebarIconButton,
|
||||
isActive && styles.activeSidebarIconButton,
|
||||
]}
|
||||
{...props}
|
||||
{...buttonProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import Button from "@mui/material/Button";
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import { type FC } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -43,9 +42,9 @@ export interface WorkspaceProps {
|
||||
buildInfo?: TypesGen.BuildInfoResponse;
|
||||
sshPrefix?: string;
|
||||
template: TypesGen.Template;
|
||||
canRetryDebugMode: boolean;
|
||||
handleBuildRetry: () => void;
|
||||
handleBuildRetryDebug: () => void;
|
||||
canDebugMode: boolean;
|
||||
handleRetry: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
|
||||
handleDebug: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
|
||||
buildLogs?: React.ReactNode;
|
||||
latestVersion?: TypesGen.TemplateVersion;
|
||||
permissions: WorkspacePermissions;
|
||||
@@ -75,9 +74,9 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
buildInfo,
|
||||
sshPrefix,
|
||||
template,
|
||||
canRetryDebugMode,
|
||||
handleBuildRetry,
|
||||
handleBuildRetryDebug,
|
||||
canDebugMode,
|
||||
handleRetry,
|
||||
handleDebug,
|
||||
buildLogs,
|
||||
latestVersion,
|
||||
permissions,
|
||||
@@ -129,12 +128,12 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
handleUpdate={handleUpdate}
|
||||
handleCancel={handleCancel}
|
||||
handleSettings={handleSettings}
|
||||
handleBuildRetry={handleBuildRetry}
|
||||
handleBuildRetryDebug={handleBuildRetryDebug}
|
||||
handleRetry={handleRetry}
|
||||
handleDebug={handleDebug}
|
||||
handleChangeVersion={handleChangeVersion}
|
||||
handleDormantActivate={handleDormantActivate}
|
||||
handleToggleFavorite={handleToggleFavorite}
|
||||
canRetryDebugMode={canRetryDebugMode}
|
||||
canDebugMode={canDebugMode}
|
||||
canChangeVersions={canChangeVersions}
|
||||
isUpdating={isUpdating}
|
||||
isRestarting={isRestarting}
|
||||
@@ -208,20 +207,7 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
)}
|
||||
|
||||
{workspace.latest_build.job.error && (
|
||||
<Alert
|
||||
severity="error"
|
||||
actions={
|
||||
<Button
|
||||
onClick={
|
||||
canRetryDebugMode ? handleBuildRetryDebug : handleBuildRetry
|
||||
}
|
||||
variant="text"
|
||||
size="small"
|
||||
>
|
||||
Retry{canRetryDebugMode && " in debug mode"}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Alert severity="error">
|
||||
<AlertTitle>Workspace build failed</AlertTitle>
|
||||
<AlertDetail>{workspace.latest_build.job.error}</AlertDetail>
|
||||
</Alert>
|
||||
|
||||
@@ -32,16 +32,19 @@ import {
|
||||
usePopover,
|
||||
} from "components/Popover/Popover";
|
||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||
import visuallyHidden from "@mui/utils/visuallyHidden";
|
||||
|
||||
interface BuildParametersPopoverProps {
|
||||
workspace: Workspace;
|
||||
disabled?: boolean;
|
||||
onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const BuildParametersPopover: FC<BuildParametersPopoverProps> = ({
|
||||
workspace,
|
||||
disabled,
|
||||
label,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const { data: parameters } = useQuery({
|
||||
@@ -62,6 +65,7 @@ export const BuildParametersPopover: FC<BuildParametersPopoverProps> = ({
|
||||
css={{ paddingLeft: 0, paddingRight: 0, minWidth: "28px !important" }}
|
||||
>
|
||||
<ExpandMoreOutlined css={{ fontSize: 14 }} />
|
||||
<span css={{ ...visuallyHidden }}>{label}</span>
|
||||
</TopbarButton>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
|
||||
@@ -7,8 +7,6 @@ import ReplayIcon from "@mui/icons-material/Replay";
|
||||
import BlockIcon from "@mui/icons-material/Block";
|
||||
import OutlinedBlockIcon from "@mui/icons-material/BlockOutlined";
|
||||
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
|
||||
import RetryIcon from "@mui/icons-material/BuildOutlined";
|
||||
import RetryDebugIcon from "@mui/icons-material/BugReportOutlined";
|
||||
import Star from "@mui/icons-material/Star";
|
||||
import StarBorder from "@mui/icons-material/StarBorder";
|
||||
import { type FC } from "react";
|
||||
@@ -16,7 +14,7 @@ import type { Workspace, WorkspaceBuildParameter } from "api/typesGenerated";
|
||||
import { BuildParametersPopover } from "./BuildParametersPopover";
|
||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||
|
||||
interface ActionButtonProps {
|
||||
export interface ActionButtonProps {
|
||||
loading?: boolean;
|
||||
handleAction: (buildParameters?: WorkspaceBuildParameter[]) => void;
|
||||
disabled?: boolean;
|
||||
@@ -84,6 +82,7 @@ export const StartButton: FC<ActionButtonPropsWithWorkspace> = ({
|
||||
{loading ? <>Starting…</> : "Start"}
|
||||
</TopbarButton>
|
||||
<BuildParametersPopover
|
||||
label="Start with build parameters"
|
||||
workspace={workspace}
|
||||
disabled={loading}
|
||||
onSubmit={handleAction}
|
||||
@@ -141,6 +140,7 @@ export const RestartButton: FC<ActionButtonPropsWithWorkspace> = ({
|
||||
{loading ? <>Restarting…</> : <>Restart…</>}
|
||||
</TopbarButton>
|
||||
<BuildParametersPopover
|
||||
label="Restart with build parameters"
|
||||
workspace={workspace}
|
||||
disabled={loading}
|
||||
onSubmit={handleAction}
|
||||
@@ -175,24 +175,6 @@ export const DisabledButton: FC<DisabledButtonProps> = ({ label }) => {
|
||||
);
|
||||
};
|
||||
|
||||
type RetryButtonProps = Omit<ActionButtonProps, "loading"> & {
|
||||
debug?: boolean;
|
||||
};
|
||||
|
||||
export const RetryButton: FC<RetryButtonProps> = ({
|
||||
handleAction,
|
||||
debug = false,
|
||||
}) => {
|
||||
return (
|
||||
<TopbarButton
|
||||
startIcon={debug ? <RetryDebugIcon /> : <RetryIcon />}
|
||||
onClick={() => handleAction()}
|
||||
>
|
||||
Retry{debug && " (Debug)"}
|
||||
</TopbarButton>
|
||||
);
|
||||
};
|
||||
|
||||
interface FavoriteButtonProps {
|
||||
onToggle: (workspaceID: string) => void;
|
||||
workspaceID: string;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
import { DebugButton } from "./DebugButton";
|
||||
import { MockWorkspace } from "testHelpers/entities";
|
||||
import { userEvent, waitFor, within, expect } from "@storybook/test";
|
||||
|
||||
const meta: Meta<typeof DebugButton> = {
|
||||
title: "pages/WorkspacePage/DebugButton",
|
||||
component: DebugButton,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof DebugButton>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithBuildParameters: Story = {
|
||||
args: {
|
||||
enableBuildParameters: true,
|
||||
workspace: MockWorkspace,
|
||||
},
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["workspace", MockWorkspace.id, "parameters"],
|
||||
data: { templateVersionRichParameters: [], buildParameters: [] },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const WithOpenBuildParameters: Story = {
|
||||
args: {
|
||||
enableBuildParameters: true,
|
||||
workspace: MockWorkspace,
|
||||
},
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["workspace", MockWorkspace.id, "parameters"],
|
||||
data: { templateVersionRichParameters: [], buildParameters: [] },
|
||||
},
|
||||
],
|
||||
},
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const screen = within(canvasElement);
|
||||
|
||||
await step("open popover", async () => {
|
||||
await userEvent.click(screen.getByTestId("build-parameters-button"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText("Build Options")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import DebugIcon from "@mui/icons-material/BugReportOutlined";
|
||||
import { type FC } from "react";
|
||||
import type { Workspace } from "api/typesGenerated";
|
||||
import { BuildParametersPopover } from "./BuildParametersPopover";
|
||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||
import { ActionButtonProps } from "./Buttons";
|
||||
|
||||
type DebugButtonProps = Omit<ActionButtonProps, "loading"> & {
|
||||
workspace: Workspace;
|
||||
enableBuildParameters: boolean;
|
||||
};
|
||||
|
||||
export const DebugButton: FC<DebugButtonProps> = ({
|
||||
handleAction,
|
||||
workspace,
|
||||
enableBuildParameters,
|
||||
}) => {
|
||||
const mainAction = (
|
||||
<TopbarButton startIcon={<DebugIcon />} onClick={() => handleAction()}>
|
||||
Debug
|
||||
</TopbarButton>
|
||||
);
|
||||
|
||||
if (!enableBuildParameters) {
|
||||
return mainAction;
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
variant="outlined"
|
||||
css={{
|
||||
// Workaround to make the border transitions smoothly on button groups
|
||||
"& > button:hover + button": {
|
||||
borderLeft: "1px solid #FFF",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{mainAction}
|
||||
<BuildParametersPopover
|
||||
label="Debug with build parameters"
|
||||
workspace={workspace}
|
||||
onSubmit={handleAction}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
import { RetryButton } from "./RetryButton";
|
||||
import { MockWorkspace } from "testHelpers/entities";
|
||||
import { userEvent, waitFor, within, expect } from "@storybook/test";
|
||||
|
||||
const meta: Meta<typeof RetryButton> = {
|
||||
title: "pages/WorkspacePage/RetryButton",
|
||||
component: RetryButton,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof RetryButton>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithBuildParameters: Story = {
|
||||
args: {
|
||||
enableBuildParameters: true,
|
||||
workspace: MockWorkspace,
|
||||
},
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["workspace", MockWorkspace.id, "parameters"],
|
||||
data: { templateVersionRichParameters: [], buildParameters: [] },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const WithOpenBuildParameters: Story = {
|
||||
args: {
|
||||
enableBuildParameters: true,
|
||||
workspace: MockWorkspace,
|
||||
},
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["workspace", MockWorkspace.id, "parameters"],
|
||||
data: { templateVersionRichParameters: [], buildParameters: [] },
|
||||
},
|
||||
],
|
||||
},
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const screen = within(canvasElement);
|
||||
|
||||
await step("open popover", async () => {
|
||||
await userEvent.click(screen.getByTestId("build-parameters-button"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText("Build Options")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import RetryIcon from "@mui/icons-material/CachedOutlined";
|
||||
import { type FC } from "react";
|
||||
import type { Workspace } from "api/typesGenerated";
|
||||
import { BuildParametersPopover } from "./BuildParametersPopover";
|
||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||
import { ActionButtonProps } from "./Buttons";
|
||||
|
||||
type RetryButtonProps = Omit<ActionButtonProps, "loading"> & {
|
||||
enableBuildParameters: boolean;
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
export const RetryButton: FC<RetryButtonProps> = ({
|
||||
handleAction,
|
||||
workspace,
|
||||
enableBuildParameters,
|
||||
}) => {
|
||||
const mainAction = (
|
||||
<TopbarButton startIcon={<RetryIcon />} onClick={() => handleAction()}>
|
||||
Retry
|
||||
</TopbarButton>
|
||||
);
|
||||
|
||||
if (!enableBuildParameters) {
|
||||
return mainAction;
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
variant="outlined"
|
||||
css={{
|
||||
// Workaround to make the border transitions smoothly on button groups
|
||||
"& > button:hover + button": {
|
||||
borderLeft: "1px solid #FFF",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{mainAction}
|
||||
<BuildParametersPopover
|
||||
label="Retry with build parameters"
|
||||
workspace={workspace}
|
||||
onSubmit={handleAction}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
@@ -73,6 +73,13 @@ export const Failed: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const FailedWithDebug: Story = {
|
||||
args: {
|
||||
workspace: Mocks.MockFailedWorkspace,
|
||||
canDebug: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Updating: Story = {
|
||||
args: {
|
||||
isUpdating: true,
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
RestartButton,
|
||||
UpdateButton,
|
||||
ActivateButton,
|
||||
RetryButton,
|
||||
FavoriteButton,
|
||||
} from "./Buttons";
|
||||
|
||||
@@ -28,6 +27,8 @@ import {
|
||||
} from "components/MoreMenu/MoreMenu";
|
||||
import { TopbarIconButton } from "components/FullPageLayout/Topbar";
|
||||
import MoreVertOutlined from "@mui/icons-material/MoreVertOutlined";
|
||||
import { RetryButton } from "./RetryButton";
|
||||
import { DebugButton } from "./DebugButton";
|
||||
|
||||
export interface WorkspaceActionsProps {
|
||||
workspace: Workspace;
|
||||
@@ -40,14 +41,14 @@ export interface WorkspaceActionsProps {
|
||||
handleCancel: () => void;
|
||||
handleSettings: () => void;
|
||||
handleChangeVersion: () => void;
|
||||
handleRetry: () => void;
|
||||
handleRetryDebug: () => void;
|
||||
handleRetry: (buildParameters?: WorkspaceBuildParameter[]) => void;
|
||||
handleDebug: (buildParameters?: WorkspaceBuildParameter[]) => void;
|
||||
handleDormantActivate: () => void;
|
||||
isUpdating: boolean;
|
||||
isRestarting: boolean;
|
||||
children?: ReactNode;
|
||||
canChangeVersions: boolean;
|
||||
canRetryDebug: boolean;
|
||||
canDebug: boolean;
|
||||
isOwner: boolean;
|
||||
}
|
||||
|
||||
@@ -62,13 +63,13 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
handleCancel,
|
||||
handleSettings,
|
||||
handleRetry,
|
||||
handleRetryDebug,
|
||||
handleDebug,
|
||||
handleChangeVersion,
|
||||
handleDormantActivate,
|
||||
isUpdating,
|
||||
isRestarting,
|
||||
canChangeVersions,
|
||||
canRetryDebug,
|
||||
canDebug,
|
||||
isOwner,
|
||||
}) => {
|
||||
const { duplicateWorkspace, isDuplicationReady } =
|
||||
@@ -76,7 +77,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
|
||||
const { actions, canCancel, canAcceptJobs } = abilitiesByWorkspaceStatus(
|
||||
workspace,
|
||||
canRetryDebug,
|
||||
canDebug,
|
||||
);
|
||||
const showCancel =
|
||||
canCancel &&
|
||||
@@ -132,8 +133,20 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
pending: <DisabledButton label="Pending..." />,
|
||||
activate: <ActivateButton handleAction={handleDormantActivate} />,
|
||||
activating: <ActivateButton loading handleAction={handleDormantActivate} />,
|
||||
retry: <RetryButton handleAction={handleRetry} />,
|
||||
retryDebug: <RetryButton debug handleAction={handleRetryDebug} />,
|
||||
retry: (
|
||||
<RetryButton
|
||||
handleAction={handleRetry}
|
||||
workspace={workspace}
|
||||
enableBuildParameters={workspace.latest_build.transition === "start"}
|
||||
/>
|
||||
),
|
||||
debug: (
|
||||
<DebugButton
|
||||
handleAction={handleDebug}
|
||||
workspace={workspace}
|
||||
enableBuildParameters={workspace.latest_build.transition === "start"}
|
||||
/>
|
||||
),
|
||||
toggleFavorite: (
|
||||
<FavoriteButton
|
||||
workspaceID={workspace.id}
|
||||
|
||||
@@ -21,7 +21,7 @@ export const actionTypes = [
|
||||
// into one of the starting, stopping, or deleting states (based on the
|
||||
// WorkspaceTransition type)
|
||||
"retry",
|
||||
"retryDebug",
|
||||
"debug",
|
||||
|
||||
// These are buttons that should be used with disabled UI elements
|
||||
"canceling",
|
||||
@@ -39,7 +39,7 @@ type WorkspaceAbilities = {
|
||||
|
||||
export const abilitiesByWorkspaceStatus = (
|
||||
workspace: Workspace,
|
||||
canRetryDebug: boolean,
|
||||
canDebug: boolean,
|
||||
): WorkspaceAbilities => {
|
||||
if (workspace.dormant_at) {
|
||||
return {
|
||||
@@ -50,10 +50,10 @@ export const abilitiesByWorkspaceStatus = (
|
||||
}
|
||||
|
||||
const status = workspace.latest_build.status;
|
||||
if (status === "failed" && canRetryDebug) {
|
||||
if (status === "failed" && canDebug) {
|
||||
return {
|
||||
...statusToAbility.failed,
|
||||
actions: ["retry", "retryDebug"],
|
||||
actions: ["retry", "debug"],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Workspace } from "api/typesGenerated";
|
||||
import { TemplateVersionParameter, type Workspace } from "api/typesGenerated";
|
||||
import { screen, waitFor, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import EventSourceMock from "eventsourcemock";
|
||||
@@ -357,7 +357,7 @@ describe("WorkspacePage", () => {
|
||||
// each function gets called with
|
||||
describe("Retrying failed workspaces", () => {
|
||||
const retryButtonRe = /^Retry$/i;
|
||||
const retryDebugButtonRe = /^Retry \(Debug\)$/i;
|
||||
const retryDebugButtonRe = /^Debug$/i;
|
||||
|
||||
describe("Retries a failed 'Start' transition", () => {
|
||||
const mockStart = jest.spyOn(api, "startWorkspace");
|
||||
@@ -438,4 +438,102 @@ describe("WorkspacePage", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("retry with build parameters", async () => {
|
||||
const user = userEvent.setup();
|
||||
const workspace = {
|
||||
...MockFailedWorkspace,
|
||||
latest_build: {
|
||||
...MockFailedWorkspace.latest_build,
|
||||
transition: "start",
|
||||
},
|
||||
} satisfies Workspace;
|
||||
const parameter = {
|
||||
...MockTemplateVersionParameter1,
|
||||
display_name: "Parameter 1",
|
||||
ephemeral: true,
|
||||
} satisfies TemplateVersionParameter;
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
"/api/v2/templateversions/:versionId/rich-parameters",
|
||||
(req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json([parameter]));
|
||||
},
|
||||
),
|
||||
);
|
||||
const startWorkspaceSpy = jest.spyOn(api, "startWorkspace");
|
||||
|
||||
await renderWorkspacePage(workspace);
|
||||
const retryWithBuildParametersButton = await screen.findByRole("button", {
|
||||
name: "Retry with build parameters",
|
||||
});
|
||||
await user.click(retryWithBuildParametersButton);
|
||||
await screen.findByText("Build Options");
|
||||
const parameterField = screen.getByLabelText(parameter.display_name, {
|
||||
exact: false,
|
||||
});
|
||||
await user.clear(parameterField);
|
||||
await user.type(parameterField, "some-value");
|
||||
const submitButton = screen.getByText("Build workspace");
|
||||
await user.click(submitButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(startWorkspaceSpy).toBeCalledWith(
|
||||
workspace.id,
|
||||
workspace.latest_build.template_version_id,
|
||||
undefined,
|
||||
[{ name: parameter.name, value: "some-value" }],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("debug with build parameters", async () => {
|
||||
const user = userEvent.setup();
|
||||
const workspace = {
|
||||
...MockFailedWorkspace,
|
||||
latest_build: {
|
||||
...MockFailedWorkspace.latest_build,
|
||||
transition: "start",
|
||||
},
|
||||
} satisfies Workspace;
|
||||
const parameter = {
|
||||
...MockTemplateVersionParameter1,
|
||||
display_name: "Parameter 1",
|
||||
ephemeral: true,
|
||||
} satisfies TemplateVersionParameter;
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
"/api/v2/templateversions/:versionId/rich-parameters",
|
||||
(req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json([parameter]));
|
||||
},
|
||||
),
|
||||
);
|
||||
const startWorkspaceSpy = jest.spyOn(api, "startWorkspace");
|
||||
|
||||
await renderWorkspacePage(workspace);
|
||||
const retryWithBuildParametersButton = await screen.findByRole("button", {
|
||||
name: "Debug with build parameters",
|
||||
});
|
||||
await user.click(retryWithBuildParametersButton);
|
||||
await screen.findByText("Build Options");
|
||||
const parameterField = screen.getByLabelText(parameter.display_name, {
|
||||
exact: false,
|
||||
});
|
||||
await user.clear(parameterField);
|
||||
await user.type(parameterField, "some-value");
|
||||
const submitButton = screen.getByText("Build workspace");
|
||||
await user.click(submitButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(startWorkspaceSpy).toBeCalledWith(
|
||||
workspace.id,
|
||||
workspace.latest_build.template_version_id,
|
||||
"debug",
|
||||
[{ name: parameter.name, value: "some-value" }],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -153,12 +153,18 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
||||
// Cancel build
|
||||
const cancelBuildMutation = useMutation(cancelBuild(workspace, queryClient));
|
||||
|
||||
const handleBuildRetry = (debug = false) => {
|
||||
const runLastBuild = (
|
||||
buildParameters: TypesGen.WorkspaceBuildParameter[] | undefined,
|
||||
debug: boolean,
|
||||
) => {
|
||||
const logLevel = debug ? "debug" : undefined;
|
||||
|
||||
switch (workspace.latest_build.transition) {
|
||||
case "start":
|
||||
startWorkspaceMutation.mutate({ logLevel });
|
||||
startWorkspaceMutation.mutate({
|
||||
logLevel,
|
||||
buildParameters,
|
||||
});
|
||||
break;
|
||||
case "stop":
|
||||
stopWorkspaceMutation.mutate({ logLevel });
|
||||
@@ -169,6 +175,18 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleRetry = (
|
||||
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
||||
) => {
|
||||
runLastBuild(buildParameters, false);
|
||||
};
|
||||
|
||||
const handleDebug = (
|
||||
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
||||
) => {
|
||||
runLastBuild(buildParameters, true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
@@ -207,9 +225,9 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
|
||||
}}
|
||||
handleCancel={cancelBuildMutation.mutate}
|
||||
handleSettings={() => navigate("settings")}
|
||||
handleBuildRetry={() => handleBuildRetry(false)}
|
||||
handleBuildRetryDebug={() => handleBuildRetry(true)}
|
||||
canRetryDebugMode={
|
||||
handleRetry={handleRetry}
|
||||
handleDebug={handleDebug}
|
||||
canDebugMode={
|
||||
deploymentValues?.config.enable_terraform_debug_mode ?? false
|
||||
}
|
||||
handleChangeVersion={() => {
|
||||
|
||||
@@ -52,9 +52,9 @@ export interface WorkspaceProps {
|
||||
workspace: TypesGen.Workspace;
|
||||
canUpdateWorkspace: boolean;
|
||||
canChangeVersions: boolean;
|
||||
canRetryDebugMode: boolean;
|
||||
handleBuildRetry: () => void;
|
||||
handleBuildRetryDebug: () => void;
|
||||
canDebugMode: boolean;
|
||||
handleRetry: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
|
||||
handleDebug: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
|
||||
isOwner: boolean;
|
||||
template: TypesGen.Template;
|
||||
permissions: WorkspacePermissions;
|
||||
@@ -78,9 +78,9 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
|
||||
isRestarting,
|
||||
canUpdateWorkspace,
|
||||
canChangeVersions,
|
||||
canRetryDebugMode,
|
||||
handleBuildRetry,
|
||||
handleBuildRetryDebug,
|
||||
canDebugMode,
|
||||
handleRetry,
|
||||
handleDebug,
|
||||
isOwner,
|
||||
template,
|
||||
latestVersion,
|
||||
@@ -266,12 +266,12 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
|
||||
handleUpdate={handleUpdate}
|
||||
handleCancel={handleCancel}
|
||||
handleSettings={handleSettings}
|
||||
handleRetry={handleBuildRetry}
|
||||
handleRetryDebug={handleBuildRetryDebug}
|
||||
handleRetry={handleRetry}
|
||||
handleDebug={handleDebug}
|
||||
handleChangeVersion={handleChangeVersion}
|
||||
handleDormantActivate={handleDormantActivate}
|
||||
handleToggleFavorite={handleToggleFavorite}
|
||||
canRetryDebug={canRetryDebugMode}
|
||||
canDebug={canDebugMode}
|
||||
canChangeVersions={canChangeVersions}
|
||||
isUpdating={isUpdating}
|
||||
isRestarting={isRestarting}
|
||||
|
||||
@@ -245,6 +245,12 @@ export const handlers = [
|
||||
rest.put("/api/v2/workspaces/:workspaceId/extend", async (req, res, ctx) => {
|
||||
return res(ctx.status(200));
|
||||
}),
|
||||
rest.get(
|
||||
"/api/v2/workspaces/:workspaceId/resolve-autostart",
|
||||
async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json({ parameter_mismatch: false }));
|
||||
},
|
||||
),
|
||||
|
||||
// workspace builds
|
||||
rest.post("/api/v2/workspaces/:workspaceId/builds", async (req, res, ctx) => {
|
||||
@@ -438,4 +444,8 @@ export const handlers = [
|
||||
rest.get("/api/v2/workspaceagents/:agent/listening-ports", (_, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(M.MockListeningPortsResponse));
|
||||
}),
|
||||
|
||||
rest.get("/api/v2/integrations/jfrog/xray-scan", (_, res, ctx) => {
|
||||
return res(ctx.status(404));
|
||||
}),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user