Skip to content

Commit 6ca6295

Browse files
authored
Job cancallation (#2763)
Add the ability to cancel jobs from the Quantum Workspace tree view.
1 parent 7421e7d commit 6ca6295

5 files changed

Lines changed: 93 additions & 3 deletions

File tree

source/vscode/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@
205205
"command": "qsharp-vscode.downloadRawResults",
206206
"when": "qsharp-vscode.treeItemSupportsDownload"
207207
},
208+
{
209+
"command": "qsharp-vscode.cancelJob",
210+
"when": "qsharp-vscode.treeItemIsCancelable"
211+
},
208212
{
209213
"command": "qsharp-vscode.getQir",
210214
"when": "resourceLangId == qsharp || resourceLangId == openqasm"
@@ -272,6 +276,11 @@
272276
"group": "inline",
273277
"when": "view == quantum-workspaces && viewItem == qir-target"
274278
},
279+
{
280+
"command": "qsharp-vscode.cancelJob",
281+
"group": "inline",
282+
"when": "view == quantum-workspaces && viewItem == job-cancelable"
283+
},
275284
{
276285
"command": "qsharp-vscode.downloadResults",
277286
"group": "inline",
@@ -424,6 +433,12 @@
424433
"title": "Download Azure Quantum job results as text",
425434
"icon": "$(output)"
426435
},
436+
{
437+
"command": "qsharp-vscode.cancelJob",
438+
"category": "QDK",
439+
"title": "Cancel Azure Quantum job",
440+
"icon": "$(stop-circle)"
441+
},
427442
{
428443
"command": "qsharp-vscode.createNotebook",
429444
"category": "QDK",

source/vscode/src/azure/commands.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
WorkspaceTreeProvider,
3434
} from "./treeView";
3535
import {
36+
cancelPendingJob,
3637
getAzurePortalWorkspaceLink,
3738
getJobFiles,
3839
getPythonCodeForWorkspace,
@@ -76,6 +77,7 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
7677
let supportsQir = false;
7778
let supportsDownload = false;
7879
let isWorkspace = false;
80+
let isCancelable = false;
7981

8082
if (e.selection.length === 1) {
8183
currentTreeItem = e.selection[0] as WorkspaceTreeItem;
@@ -90,6 +92,9 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
9092
if (job.status === "Succeeded" && job.outputDataUri) {
9193
supportsDownload = true;
9294
}
95+
if (job.status === "Waiting" || job.status === "Executing") {
96+
isCancelable = true;
97+
}
9398
}
9499
if (currentTreeItem.type === "workspace") {
95100
isWorkspace = true;
@@ -113,6 +118,11 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
113118
`${qsharpExtensionId}.treeItemIsWorkspace`,
114119
isWorkspace,
115120
);
121+
await vscode.commands.executeCommand(
122+
"setContext",
123+
`${qsharpExtensionId}.treeItemIsCancelable`,
124+
isCancelable,
125+
);
116126
}),
117127
);
118128

@@ -236,6 +246,43 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
236246
),
237247
);
238248

249+
async function cancelJob(arg?: WorkspaceTreeItem) {
250+
// Could be run via the treeItem icon or the menu command.
251+
const treeItem = arg || currentTreeItem;
252+
if (treeItem?.type !== "job") return;
253+
254+
const job = treeItem.itemData as Job;
255+
256+
// Confirm cancellation with the user
257+
const confirm = await vscode.window.showWarningMessage(
258+
`Are you sure you want to cancel the job "${job.name}"?`,
259+
{ modal: true },
260+
{ title: "Yes", isCloseAffordance: false },
261+
{ title: "No", isCloseAffordance: true },
262+
);
263+
if (confirm?.title !== "Yes") return;
264+
265+
try {
266+
// Get the token
267+
const token = await getTokenForWorkspace(treeItem.workspace);
268+
if (!token) throw "Unable to get an authentication token";
269+
270+
// Call the network request
271+
await cancelPendingJob(treeItem.workspace, token, job.id);
272+
273+
// Report success/failure to the user
274+
vscode.window.showInformationMessage(
275+
"The cancel request has been submitted.",
276+
);
277+
} catch (e: any) {
278+
log.error("Failed to cancel the job: ", e);
279+
vscode.window.showErrorMessage("Failed to cancel the job.", {
280+
modal: true,
281+
detail: e instanceof Error ? e.message : undefined,
282+
});
283+
}
284+
}
285+
239286
async function downloadResults(arg?: WorkspaceTreeItem, showText?: boolean) {
240287
// Could be run via the treeItem icon or the menu command.
241288
const treeItem = arg || currentTreeItem;
@@ -287,6 +334,13 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
287334
}
288335
}
289336

337+
context.subscriptions.push(
338+
vscode.commands.registerCommand(
339+
`${qsharpExtensionId}.cancelJob`,
340+
async (arg: WorkspaceTreeItem) => await cancelJob(arg),
341+
),
342+
);
343+
290344
context.subscriptions.push(
291345
vscode.commands.registerCommand(
292346
`${qsharpExtensionId}.downloadResults`,

source/vscode/src/azure/networkRequests.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,16 @@ export async function azureRequest(
4949
}
5050

5151
log.debug(`Got response ${response.status} ${response.statusText}`);
52-
const result = await response.json();
53-
log.trace("Response value: ", result);
5452

55-
return result;
53+
// No payload is expected from delete requests
54+
if (method === "DELETE") {
55+
return;
56+
} else {
57+
const result = await response.json();
58+
log.trace("Response value: ", result);
59+
60+
return result;
61+
}
5662
} catch (e) {
5763
if (associationId) {
5864
sendTelemetryEvent(

source/vscode/src/azure/treeView.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,14 @@ export class WorkspaceTreeItem extends vscode.TreeItem {
243243
this.collapsibleState = vscode.TreeItemCollapsibleState.None;
244244
switch (job.status) {
245245
case "Executing":
246+
this.contextValue = "job-cancelable";
247+
// falls through
246248
case "Finishing":
247249
this.iconPath = new vscode.ThemeIcon("run-all");
248250
break;
249251
case "Waiting":
250252
this.iconPath = new vscode.ThemeIcon("loading~spin");
253+
this.contextValue = "job-cancelable";
251254
break;
252255
case "Cancelled":
253256
this.iconPath = new vscode.ThemeIcon("circle-slash");

source/vscode/src/azure/workspaceActions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,18 @@ export async function submitJob(
577577
return { jobId, storageUris, quantumUris, token };
578578
}
579579

580+
export async function cancelPendingJob(
581+
workspace: WorkspaceConnection,
582+
token: string,
583+
jobId: string,
584+
): Promise<void> {
585+
const quantumUris = new QuantumUris(workspace.endpointUri, workspace.id);
586+
587+
const cancelJobUri = quantumUris.jobs(jobId);
588+
589+
await azureRequest(cancelJobUri, token, undefined, "DELETE", undefined);
590+
}
591+
580592
async function putJobData(
581593
quantumUris: QuantumUris,
582594
storageUris: StorageUris,

0 commit comments

Comments
 (0)