Skip to content

Commit ba1dbf3

Browse files
committed
add cancel confirmation dialog for workspace builds and add expect_status for pending builds
1 parent c49c33e commit ba1dbf3

File tree

7 files changed

+102
-6
lines changed

7 files changed

+102
-6
lines changed

site/src/api/api.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1277,9 +1277,14 @@ class ApiMethods {
12771277

12781278
cancelWorkspaceBuild = async (
12791279
workspaceBuildId: TypesGen.WorkspaceBuild["id"],
1280+
request?: TypesGen.CancelWorkspaceBuildRequest,
12801281
): Promise<TypesGen.Response> => {
1282+
const params = request?.expect_status
1283+
? new URLSearchParams({ expect_status: request.expect_status }).toString()
1284+
: "";
1285+
12811286
const response = await this.axios.patch(
1282-
`/api/v2/workspacebuilds/${workspaceBuildId}/cancel`,
1287+
`/api/v2/workspacebuilds/${workspaceBuildId}/cancel${params ? `?${params}` : ""}`,
12831288
);
12841289

12851290
return response.data;

site/src/api/queries/workspaces.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ export const startWorkspace = (
266266
export const cancelBuild = (workspace: Workspace, queryClient: QueryClient) => {
267267
return {
268268
mutationFn: () => {
269+
// If workspace status is pending, include expect_status parameter
270+
if (workspace.latest_build.status === "pending") {
271+
return API.cancelWorkspaceBuild(workspace.latest_build.id, {
272+
expect_status: "pending",
273+
});
274+
}
269275
return API.cancelWorkspaceBuild(workspace.latest_build.id);
270276
},
271277
onSuccess: async () => {

site/src/api/typesGenerated.ts

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/modules/workspaces/actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export const abilitiesByWorkspaceStatus = (
145145
case "pending": {
146146
return {
147147
actions: ["pending"],
148-
canCancel: false,
148+
canCancel: true,
149149
canAcceptJobs: false,
150150
};
151151
}

site/src/pages/WorkspacePage/WorkspacePage.test.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
MockFailedWorkspace,
1919
MockOrganization,
2020
MockOutdatedWorkspace,
21+
MockPendingWorkspace,
2122
MockStartingWorkspace,
2223
MockStoppedWorkspace,
2324
MockTemplate,
@@ -223,11 +224,53 @@ describe("WorkspacePage", () => {
223224
}),
224225
);
225226

227+
const user = userEvent.setup({ delay: 0 });
226228
const cancelWorkspaceMock = jest
227229
.spyOn(API, "cancelWorkspaceBuild")
228230
.mockImplementation(() => Promise.resolve({ message: "job canceled" }));
231+
await renderWorkspacePage(MockStartingWorkspace);
232+
233+
// Click on Cancel
234+
const cancelButton = await screen.findByRole("button", { name: "Cancel" });
235+
await user.click(cancelButton);
236+
237+
// Get dialog and confirm
238+
const dialog = await screen.findByTestId("dialog");
239+
const confirmButton = within(dialog).getByRole("button", {
240+
name: "Confirm",
241+
hidden: false,
242+
});
243+
await user.click(confirmButton);
244+
245+
expect(cancelWorkspaceMock).toHaveBeenCalledWith(MockStartingWorkspace.latest_build.id);
246+
});
247+
248+
it("requests cancellation when the user presses Cancel and the workspace is pending", async () => {
249+
server.use(
250+
http.get("/api/v2/users/:userId/workspace/:workspaceName", () => {
251+
return HttpResponse.json(MockPendingWorkspace);
252+
}),
253+
);
254+
255+
const user = userEvent.setup({ delay: 0 });
256+
const cancelWorkspaceMock = jest
257+
.spyOn(API, "cancelWorkspaceBuild")
258+
.mockImplementation(() => Promise.resolve({ message: "job canceled" }));
259+
await renderWorkspacePage(MockPendingWorkspace);
260+
261+
// Click on Cancel
262+
const cancelButton = await screen.findByRole("button", { name: "Cancel" });
263+
await user.click(cancelButton);
264+
265+
// Get dialog and confirm
266+
const dialog = await screen.findByTestId("dialog");
267+
const confirmButton = within(dialog).getByRole("button", {
268+
name: "Confirm",
269+
hidden: false,
270+
});
271+
await user.click(confirmButton);
229272

230-
await testButton(MockStartingWorkspace, "Cancel", cancelWorkspaceMock);
273+
expect(cancelWorkspaceMock).toHaveBeenCalledWith(MockPendingWorkspace.latest_build.id, { expect_status: "pending" });
231274
});
232275

233276
it("requests an update when the user presses Update", async () => {

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
8080
ephemeralParameters: TypesGen.TemplateVersionParameter[];
8181
}>({ open: false, action: "start", ephemeralParameters: [] });
8282

83+
const [isCancelConfirmOpen, setIsCancelConfirmOpen] = useState(false);
84+
8385
const { mutate: mutateRestartWorkspace, isPending: isRestarting } =
8486
useMutation({
8587
mutationFn: API.restartWorkspace,
@@ -316,7 +318,7 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
316318
}
317319
}}
318320
handleUpdate={workspaceUpdate.update}
319-
handleCancel={cancelBuildMutation.mutate}
321+
handleCancel={() => setIsCancelConfirmOpen(true)}
320322
handleRetry={handleRetry}
321323
handleDebug={handleDebug}
322324
handleDormantActivate={async () => {
@@ -352,6 +354,21 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
352354
}
353355
/>
354356

357+
{/* Cancel confirmation dialog */}
358+
<ConfirmDialog
359+
open={isCancelConfirmOpen}
360+
title="Cancel workspace build"
361+
description={`Are you sure you want to cancel the build for workspace "${workspace.name}"? This will stop the current build process.`}
362+
confirmText="Confirm"
363+
cancelText="Discard"
364+
onClose={() => setIsCancelConfirmOpen(false)}
365+
onConfirm={() => {
366+
cancelBuildMutation.mutate();
367+
setIsCancelConfirmOpen(false);
368+
}}
369+
type="delete"
370+
/>
371+
355372
<EphemeralParametersDialog
356373
open={ephemeralParametersDialog.open}
357374
onClose={() =>

site/src/pages/WorkspacesPage/WorkspacesTable.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,8 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
497497

498498
// State for stop confirmation dialog
499499
const [isStopConfirmOpen, setIsStopConfirmOpen] = useState(false);
500+
// State for cancel confirmation dialog
501+
const [isCancelConfirmOpen, setIsCancelConfirmOpen] = useState(false);
500502

501503
const isRetrying =
502504
startWorkspaceMutation.isPending ||
@@ -606,7 +608,7 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
606608

607609
{abilities.canCancel && (
608610
<PrimaryAction
609-
onClick={cancelBuildMutation.mutate}
611+
onClick={() => setIsCancelConfirmOpen(true)}
610612
isLoading={cancelBuildMutation.isPending}
611613
label="Cancel build"
612614
>
@@ -643,6 +645,21 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
643645
}}
644646
type="delete"
645647
/>
648+
649+
{/* Cancel workspace build confirmation dialog */}
650+
<ConfirmDialog
651+
open={isCancelConfirmOpen}
652+
title="Cancel workspace build"
653+
description={`Are you sure you want to cancel the build for workspace "${workspace.name}"? This will stop the current build process.`}
654+
confirmText="Confirm"
655+
cancelText="Discard"
656+
onClose={() => setIsCancelConfirmOpen(false)}
657+
onConfirm={() => {
658+
cancelBuildMutation.mutate();
659+
setIsCancelConfirmOpen(false);
660+
}}
661+
type="delete"
662+
/>
646663
</TableCell>
647664
);
648665
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy