microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
32bcffe86a3647ddd45528ecb551f8feddd749ab

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/vscode/src/azure/treeView.ts

345lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import * as vscode from "vscode";
5import { queryWorkspace } from "./workspaceActions";
6import { log } from "qsharp-lang";
7import { targetSupportQir } from "./providerProperties";
8
9// See docs at https://code.visualstudio.com/api/extension-guides/tree-view
10
11const pendingStatuses = ["Waiting", "Executing", "Finishing"];
12const noQirMsq = `Note: As this target does not currently support QIR, this VS Code extension cannot submit jobs to it. See https://aka.ms/qdk.qir for more info`;
13
14// Convert a date such as "2023-07-24T17:25:09.1309979Z" into local time
15function localDate(date: string) {
16 return new Date(date).toLocaleString();
17}
18
19export class WorkspaceTreeProvider
20 implements vscode.TreeDataProvider<WorkspaceTreeItem>
21{
22 static instance: WorkspaceTreeProvider;
23 private treeState: Map<string, WorkspaceConnection> = new Map();
24
25 private didChangeTreeDataEmitter = new vscode.EventEmitter<
26 WorkspaceTreeItem | undefined
27 >();
28
29 readonly onDidChangeTreeData: vscode.Event<WorkspaceTreeItem | undefined> =
30 this.didChangeTreeDataEmitter.event;
31
32 updateWorkspace(workspace: WorkspaceConnection) {
33 this.treeState.set(workspace.id, workspace);
34 this.didChangeTreeDataEmitter.fire(undefined);
35 }
36
37 removeWorkspace(workspaceId: string) {
38 this.treeState.delete(workspaceId);
39 this.didChangeTreeDataEmitter.fire(undefined);
40 }
41
42 async refreshWorkspace(workspace: WorkspaceConnection) {
43 log.debug("In refreshWorkspace for workspace: ", workspace.id);
44 await queryWorkspace(workspace);
45 this.updateWorkspace(workspace);
46 }
47
48 getWorkspaceIds() {
49 return [...this.treeState.keys()];
50 }
51
52 getWorkspace(workspaceId: string) {
53 return this.treeState.get(workspaceId);
54 }
55
56 hasWorkspace(workspaceId: string) {
57 return this.treeState.has(workspaceId);
58 }
59
60 workspaceHasJob(workspaceId: string, jobId: string): boolean {
61 const workspace = this.getWorkspace(workspaceId);
62 if (!workspace) return false;
63
64 return workspace.jobs.some((job) => job.id === jobId);
65 }
66
67 hasJobsPending(workspaceId: string): boolean {
68 const workspace = this.getWorkspace(workspaceId);
69 if (!workspace) return false;
70
71 return workspace.jobs.some((job) => pendingStatuses.includes(job.status));
72 }
73
74 getTreeItem(
75 element: WorkspaceTreeItem,
76 ): vscode.TreeItem | Thenable<vscode.TreeItem> {
77 return element;
78 }
79
80 getChildren(
81 element?: WorkspaceTreeItem | undefined,
82 ): vscode.ProviderResult<WorkspaceTreeItem[]> {
83 if (!element) {
84 return [...this.treeState.values()].map(
85 (workspace) =>
86 new WorkspaceTreeItem(
87 workspace.name,
88 workspace,
89 "workspace",
90 workspace,
91 ),
92 );
93 } else {
94 return element.getChildren();
95 }
96 }
97}
98
99export type WorkspaceConnection = {
100 id: string;
101 name: string;
102 endpointUri: string;
103 tenantId: string;
104 apiKey?: string;
105 providers: Provider[];
106 jobs: Job[];
107 lastRequestError?: string;
108};
109
110export type Provider = {
111 providerId: string;
112 currentAvailability: "Available" | "Degraded" | "Unavailable";
113 targets: Target[];
114};
115
116export type Target = {
117 id: string;
118 providerId: string;
119 currentAvailability: "Available" | "Degraded" | "Unavailable";
120 averageQueueTime: number; // minutes
121};
122
123export type Job = {
124 id: string;
125 name: string;
126 target: string;
127 status:
128 | "Waiting"
129 | "Executing"
130 | "Succeeded"
131 | "Failed"
132 | "Finishing"
133 | "Cancelled";
134 outputDataUri?: string;
135 count?: number;
136 shots?: number;
137 creationTime: string;
138 beginExecutionTime?: string;
139 endExecutionTime?: string;
140 cancellationTime?: string;
141 costEstimate?: any;
142 errorData?: { code: string; message: string };
143};
144
145function shouldShowQueueTime(target: Target) {
146 return (
147 target.currentAvailability !== "Unavailable" &&
148 typeof target.averageQueueTime === "number"
149 ); // Could be 0 seconds
150}
151
152function getQueueTimeText(seconds: number): string {
153 // If the queue time is less than 2 minute, show it in seconds
154 if (seconds < 120) {
155 return `${seconds} seconds`;
156 } else if (seconds < 60 * 60 * 4) {
157 // Otherwise, show it in minutes if less than 4 hours
158 return `${Math.round(seconds / 60)} minutes`;
159 } else {
160 // Otherwise, show it in hours
161 return `${Math.round(seconds / (60 * 60))} hours`;
162 }
163}
164
165// A workspace has an array in properties.providers, each of which has a 'providerId' property,
166// e.g. 'ionq', and a 'provisioningState' property, e.g. 'Succeeded'. Filter the list to only
167// include those that have succeeded. Then, when querying the providerStatus, only add the targets
168// for the providers that are present. (Also, filter out providers that have no targets).
169
170export class WorkspaceTreeItem extends vscode.TreeItem {
171 constructor(
172 label: string,
173 public workspace: WorkspaceConnection,
174 public type:
175 | "workspace"
176 | "providerHeader"
177 | "provider"
178 | "target"
179 | "jobHeader"
180 | "job",
181 public itemData:
182 | WorkspaceConnection
183 | Provider[]
184 | Provider
185 | Target[]
186 | Target
187 | Job[]
188 | Job,
189 ) {
190 super(label, vscode.TreeItemCollapsibleState.Collapsed);
191
192 this.contextValue = type;
193
194 switch (type) {
195 case "workspace":
196 if (!this.workspace.lastRequestError) {
197 this.iconPath = new vscode.ThemeIcon("notebook");
198 } else {
199 this.iconPath = new vscode.ThemeIcon("alert");
200 this.tooltip = this.workspace.lastRequestError;
201 }
202 break;
203 case "providerHeader": {
204 break;
205 }
206 case "provider": {
207 this.iconPath = new vscode.ThemeIcon("layers");
208 break;
209 }
210 case "target": {
211 const target = itemData as Target;
212 const supportsQir = targetSupportQir(target.id);
213
214 if (supportsQir) {
215 this.contextValue = "qir-target";
216 }
217
218 this.iconPath = new vscode.ThemeIcon("package");
219 this.collapsibleState = vscode.TreeItemCollapsibleState.None;
220 if (target.currentAvailability || target.averageQueueTime) {
221 const hover = new vscode.MarkdownString(
222 `${
223 target.currentAvailability
224 ? `__Status__: ${target.currentAvailability}<br>`
225 : ""
226 }
227 ${
228 shouldShowQueueTime(target)
229 ? `__Queue time__: ${getQueueTimeText(
230 target.averageQueueTime,
231 )}<br>`
232 : ""
233 }
234 ${supportsQir ? "" : "\n" + noQirMsq}`,
235 );
236 hover.supportHtml = true;
237 this.tooltip = hover;
238 }
239 break;
240 }
241 case "job": {
242 const job = itemData as Job;
243 this.collapsibleState = vscode.TreeItemCollapsibleState.None;
244 switch (job.status) {
245 case "Executing":
246 case "Finishing":
247 this.iconPath = new vscode.ThemeIcon("run-all");
248 break;
249 case "Waiting":
250 this.iconPath = new vscode.ThemeIcon("loading~spin");
251 break;
252 case "Cancelled":
253 this.iconPath = new vscode.ThemeIcon("circle-slash");
254 this.contextValue = "result";
255 break;
256 case "Failed":
257 this.iconPath = new vscode.ThemeIcon("error");
258 this.contextValue = "result";
259 break;
260 case "Succeeded":
261 this.iconPath = new vscode.ThemeIcon("pass");
262 this.contextValue = "result-download";
263 break;
264 }
265 // Tooltip
266 const md = `__Created__: ${localDate(job.creationTime)}<br>
267__Target__: ${job.target}<br>
268__Status__: ${job.status}<br>
269${
270 job.beginExecutionTime
271 ? `__Started__: ${localDate(job.beginExecutionTime)}<br>`
272 : ""
273}
274${
275 job.endExecutionTime
276 ? `__Completed__: ${localDate(job.endExecutionTime)}<br>`
277 : ""
278}
279${
280 job.errorData
281 ? `__Error__: <br>\n\`\`\`\n${job.errorData.code}: ${job.errorData.message}\n\`\`\`\n<br>`
282 : ""
283}
284`;
285 const hover = new vscode.MarkdownString(md);
286 hover.supportHtml = true;
287 this.tooltip = hover;
288 break;
289 }
290
291 default:
292 break;
293 }
294 }
295
296 getChildren(): WorkspaceTreeItem[] {
297 switch (this.type) {
298 case "workspace":
299 return [
300 new WorkspaceTreeItem(
301 "Providers",
302 this.workspace,
303 "providerHeader",
304 this.workspace.providers,
305 ),
306 new WorkspaceTreeItem(
307 "Jobs",
308 this.workspace,
309 "jobHeader",
310 this.workspace.jobs,
311 ),
312 ];
313
314 case "providerHeader":
315 return (this.itemData as Provider[]).map(
316 (provider) =>
317 new WorkspaceTreeItem(
318 provider.providerId,
319 this.workspace,
320 "provider",
321 provider,
322 ),
323 );
324 case "provider":
325 return (this.itemData as Provider).targets.map(
326 (target) =>
327 new WorkspaceTreeItem(target.id, this.workspace, "target", target),
328 );
329 case "jobHeader":
330 return (this.itemData as Job[]).map(
331 (job) =>
332 new WorkspaceTreeItem(
333 job.name || job.id,
334 this.workspace,
335 "job",
336 job,
337 ),
338 );
339 case "target":
340 case "job":
341 default:
342 return [];
343 }
344 }
345}
346