Skip to content

Commit 326dc5a

Browse files
Fix Issue Reporter API (microsoft#180971)
* Fix Issue Reporter API CancellationTokens aren't hydrated over ProxyChannels so I've removed cancellation logic. Additionally, there was a bad string compare that needed toLower when checking if an extension has a handler. Additionally, added docs. Fixes microsoft#180890 Fixes microsoft#180920 Fixes microsoft#180887 * remove import * remove another import
1 parent 407cb5a commit 326dc5a

File tree

5 files changed

+54
-42
lines changed

5 files changed

+54
-42
lines changed

src/vs/code/electron-sandbox/issue/IssueReporterService.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil';
2020
import { INativeHostService } from 'vs/platform/native/common/native';
2121
import { applyZoom, zoomIn, zoomOut } from 'vs/platform/window/electron-sandbox/window';
2222
import { CancellationError } from 'vs/base/common/errors';
23-
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
2423

2524
// GitHub has let us know that we could up our limit here to 8k. We chose 7500 to play it safe.
2625
// ref https://github.com/microsoft/vscode/issues/159191
@@ -91,15 +90,15 @@ export class IssueReporter extends Disposable {
9190
}
9291
}
9392

94-
this.issueMainService.getSystemInfo().then(info => {
93+
this.issueMainService.$getSystemInfo().then(info => {
9594
this.issueReporterModel.update({ systemInfo: info });
9695
this.receivedSystemInfo = true;
9796

9897
this.updateSystemInfo(this.issueReporterModel.getData());
9998
this.updatePreviewButtonState();
10099
});
101100
if (configuration.data.issueType === IssueType.PerformanceIssue) {
102-
this.issueMainService.getPerformanceInfo().then(info => {
101+
this.issueMainService.$getPerformanceInfo().then(info => {
103102
this.updatePerformanceInfo(info as Partial<IssueReporterData>);
104103
});
105104
}
@@ -225,9 +224,9 @@ export class IssueReporter extends Disposable {
225224
this.updateExtensionSelector(installedExtensions);
226225
}
227226

228-
private async updateIssueReporterUri(extension: IssueReporterExtensionData, token: CancellationToken): Promise<void> {
227+
private async updateIssueReporterUri(extension: IssueReporterExtensionData): Promise<void> {
229228
try {
230-
const uri = await this.issueMainService.getIssueReporterUri(extension.id, token);
229+
const uri = await this.issueMainService.$getIssueReporterUri(extension.id);
231230
extension.bugsUrl = uri.toString(true);
232231
} catch (e) {
233232
extension.hasIssueUriRequestHandler = false;
@@ -241,7 +240,7 @@ export class IssueReporter extends Disposable {
241240
const issueType = parseInt((<HTMLInputElement>event.target).value);
242241
this.issueReporterModel.update({ issueType: issueType });
243242
if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) {
244-
this.issueMainService.getPerformanceInfo().then(info => {
243+
this.issueMainService.$getPerformanceInfo().then(info => {
245244
this.updatePerformanceInfo(info as Partial<IssueReporterData>);
246245
});
247246
}
@@ -340,7 +339,7 @@ export class IssueReporter extends Disposable {
340339
});
341340

342341
this.addEventListener('disableExtensions', 'click', () => {
343-
this.issueMainService.reloadWithExtensionsDisabled();
342+
this.issueMainService.$reloadWithExtensionsDisabled();
344343
});
345344

346345
this.addEventListener('extensionBugsLink', 'click', (e: Event) => {
@@ -351,7 +350,7 @@ export class IssueReporter extends Disposable {
351350
this.addEventListener('disableExtensions', 'keydown', (e: Event) => {
352351
e.stopPropagation();
353352
if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) {
354-
this.issueMainService.reloadWithExtensionsDisabled();
353+
this.issueMainService.$reloadWithExtensionsDisabled();
355354
}
356355
});
357356

@@ -375,7 +374,7 @@ export class IssueReporter extends Disposable {
375374
const { issueDescription } = this.issueReporterModel.getData();
376375
if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) {
377376
// fire and forget
378-
this.issueMainService.showConfirmCloseDialog();
377+
this.issueMainService.$showConfirmCloseDialog();
379378
} else {
380379
this.close();
381380
}
@@ -505,7 +504,7 @@ export class IssueReporter extends Disposable {
505504
}
506505

507506
private async close(): Promise<void> {
508-
await this.issueMainService.closeReporter();
507+
await this.issueMainService.$closeReporter();
509508
}
510509

511510
private clearSearchResults(): void {
@@ -882,7 +881,7 @@ export class IssueReporter extends Disposable {
882881
}
883882

884883
private async writeToClipboard(baseUrl: string, issueBody: string): Promise<string> {
885-
const shouldWrite = await this.issueMainService.showClipboardDialog();
884+
const shouldWrite = await this.issueMainService.$showClipboardDialog();
886885
if (!shouldWrite) {
887886
throw new CancellationError();
888887
}
@@ -1058,23 +1057,19 @@ export class IssueReporter extends Disposable {
10581057
const { selectedExtension } = this.issueReporterModel.getData();
10591058
reset(extensionsSelector, $<HTMLOptionElement>('option'), ...extensionOptions.map(extension => makeOption(extension, selectedExtension)));
10601059

1061-
let tokenSource: CancellationTokenSource | undefined;
10621060
this.addEventListener('extension-selector', 'change', (e: Event) => {
1063-
tokenSource?.cancel();
10641061
const selectedExtensionId = (<HTMLInputElement>e.target).value;
10651062
const extensions = this.issueReporterModel.getData().allExtensions;
10661063
const matches = extensions.filter(extension => extension.id === selectedExtensionId);
10671064
if (matches.length) {
10681065
this.issueReporterModel.update({ selectedExtension: matches[0] });
1069-
this.validateSelectedExtension();
1070-
10711066
if (matches[0].hasIssueUriRequestHandler) {
1072-
tokenSource = new CancellationTokenSource();
1073-
this.updateIssueReporterUri(matches[0], tokenSource?.token);
1067+
this.updateIssueReporterUri(matches[0]);
1068+
} else {
1069+
this.validateSelectedExtension();
1070+
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
1071+
this.searchExtensionIssues(title);
10741072
}
1075-
1076-
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
1077-
this.searchExtensionIssues(title);
10781073
} else {
10791074
this.issueReporterModel.update({ selectedExtension: undefined });
10801075
this.clearSearchResults();
@@ -1096,13 +1091,14 @@ export class IssueReporter extends Disposable {
10961091
hide(extensionValidationMessage);
10971092
hide(extensionValidationNoUrlsMessage);
10981093

1099-
if (!this.issueReporterModel.getData().selectedExtension) {
1094+
const extension = this.issueReporterModel.getData().selectedExtension;
1095+
if (!extension) {
11001096
this.previewButton.enabled = true;
11011097
return;
11021098
}
11031099

11041100
const hasValidGitHubUrl = this.getExtensionGitHubUrl();
1105-
if (hasValidGitHubUrl) {
1101+
if (hasValidGitHubUrl || extension.hasIssueUriRequestHandler) {
11061102
this.previewButton.enabled = true;
11071103
} else {
11081104
this.setExtensionValidationMessage();

src/vs/platform/issue/common/issue.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { CancellationToken } from 'vs/base/common/cancellation';
76
import { URI } from 'vs/base/common/uri';
87
import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes';
98
import { PerformanceInfo, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
@@ -124,11 +123,11 @@ export interface IIssueMainService {
124123

125124
// Used by the issue reporter
126125

127-
getSystemInfo(): Promise<SystemInfo>;
128-
getPerformanceInfo(): Promise<PerformanceInfo>;
129-
reloadWithExtensionsDisabled(): Promise<void>;
130-
showConfirmCloseDialog(): Promise<void>;
131-
showClipboardDialog(): Promise<boolean>;
132-
getIssueReporterUri(extensionId: string, token: CancellationToken): Promise<URI>;
133-
closeReporter(): Promise<void>;
126+
$getSystemInfo(): Promise<SystemInfo>;
127+
$getPerformanceInfo(): Promise<PerformanceInfo>;
128+
$reloadWithExtensionsDisabled(): Promise<void>;
129+
$showConfirmCloseDialog(): Promise<void>;
130+
$showClipboardDialog(): Promise<boolean>;
131+
$getIssueReporterUri(extensionId: string): Promise<URI>;
132+
$closeReporter(): Promise<void>;
134133
}

src/vs/platform/issue/electron-main/issueMainService.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { randomPath } from 'vs/base/common/extpath';
2727
import { withNullAsUndefined } from 'vs/base/common/types';
2828
import { IStateService } from 'vs/platform/state/node/state';
2929
import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess';
30-
import { CancellationToken } from 'vs/base/common/cancellation';
30+
import { CancellationTokenSource } from 'vs/base/common/cancellation';
3131
import { URI } from 'vs/base/common/uri';
3232
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
3333
import { Promises, timeout } from 'vs/base/common/async';
@@ -302,13 +302,13 @@ export class IssueMainService implements IIssueMainService {
302302

303303
//#region used by issue reporter window
304304

305-
async getSystemInfo(): Promise<SystemInfo> {
305+
async $getSystemInfo(): Promise<SystemInfo> {
306306
const [info, remoteData] = await Promise.all([this.diagnosticsMainService.getMainDiagnostics(), this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]);
307307
const msg = await this.diagnosticsService.getSystemInfo(info, remoteData);
308308
return msg;
309309
}
310310

311-
async getPerformanceInfo(): Promise<PerformanceInfo> {
311+
async $getPerformanceInfo(): Promise<PerformanceInfo> {
312312
try {
313313
const [info, remoteData] = await Promise.all([this.diagnosticsMainService.getMainDiagnostics(), this.diagnosticsMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]);
314314
return await this.diagnosticsService.getPerformanceInfo(info, remoteData);
@@ -319,7 +319,7 @@ export class IssueMainService implements IIssueMainService {
319319
}
320320
}
321321

322-
async reloadWithExtensionsDisabled(): Promise<void> {
322+
async $reloadWithExtensionsDisabled(): Promise<void> {
323323
if (this.issueReporterParentWindow) {
324324
try {
325325
await this.nativeHostMainService.reload(this.issueReporterParentWindow.id, { disableExtensions: true });
@@ -329,7 +329,7 @@ export class IssueMainService implements IIssueMainService {
329329
}
330330
}
331331

332-
async showConfirmCloseDialog(): Promise<void> {
332+
async $showConfirmCloseDialog(): Promise<void> {
333333
if (this.issueReporterWindow) {
334334
const { response } = await this.dialogMainService.showMessageBox({
335335
type: 'warning',
@@ -349,7 +349,7 @@ export class IssueMainService implements IIssueMainService {
349349
}
350350
}
351351

352-
async showClipboardDialog(): Promise<boolean> {
352+
async $showClipboardDialog(): Promise<boolean> {
353353
if (this.issueReporterWindow) {
354354
const { response } = await this.dialogMainService.showMessageBox({
355355
type: 'warning',
@@ -366,7 +366,7 @@ export class IssueMainService implements IIssueMainService {
366366
return false;
367367
}
368368

369-
async getIssueReporterUri(extensionId: string, token: CancellationToken): Promise<URI> {
369+
async $getIssueReporterUri(extensionId: string): Promise<URI> {
370370
if (!this.issueReporterParentWindow) {
371371
throw new Error('Issue reporter window not available');
372372
}
@@ -376,22 +376,25 @@ export class IssueMainService implements IIssueMainService {
376376
}
377377
const replyChannel = `vscode:triggerIssueUriRequestHandlerResponse${window.id}`;
378378
return Promises.withAsyncBody<URI>(async (resolve, reject) => {
379-
window.sendWhenReady('vscode:triggerIssueUriRequestHandler', token, { replyChannel, extensionId });
379+
380+
const cts = new CancellationTokenSource();
381+
window.sendWhenReady('vscode:triggerIssueUriRequestHandler', cts.token, { replyChannel, extensionId });
380382

381383
validatedIpcMain.once(replyChannel, (_: unknown, data: string) => {
382384
resolve(URI.parse(data));
383385
});
384386

385387
try {
386-
await timeout(5000, token);
388+
await timeout(5000);
389+
cts.cancel();
387390
reject(new Error('Timed out waiting for issue reporter URI'));
388391
} finally {
389392
validatedIpcMain.removeHandler(replyChannel);
390393
}
391394
});
392395
}
393396

394-
async closeReporter(): Promise<void> {
397+
async $closeReporter(): Promise<void> {
395398
this.issueReporterWindow?.close();
396399
}
397400

src/vs/workbench/services/issue/electron-sandbox/issueService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class NativeIssueService implements IWorkbenchIssueService {
6464
version: manifest.version,
6565
repositoryUrl: manifest.repository && manifest.repository.url,
6666
bugsUrl: manifest.bugs && manifest.bugs.url,
67-
hasIssueUriRequestHandler: this._handlers.has(extension.identifier.id),
67+
hasIssueUriRequestHandler: this._handlers.has(extension.identifier.id.toLowerCase()),
6868
displayName: manifest.displayName,
6969
id: extension.identifier.id,
7070
isTheme,
@@ -145,7 +145,7 @@ export class NativeIssueService implements IWorkbenchIssueService {
145145
}
146146

147147
registerIssueUriRequestHandler(extensionId: string, handler: IIssueUriRequestHandler): IDisposable {
148-
this._handlers.set(extensionId, handler);
148+
this._handlers.set(extensionId.toLowerCase(), handler);
149149
return {
150150
dispose: () => this._handlers.delete(extensionId)
151151
};

src/vscode-dts/vscode.proposed.handleIssueUri.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,24 @@ declare module 'vscode' {
88
// https://github.com/microsoft/vscode/issues/46726
99

1010
export interface IssueUriRequestHandler {
11+
/**
12+
*Handle the request by the issue reporter for the Uri you want to direct the user to.
13+
*/
1114
handleIssueUrlRequest(): ProviderResult<Uri>;
1215
}
1316

1417
export namespace env {
18+
/**
19+
* Register an {@link IssueUriRequestHandler}. By registering an issue uri request handler,
20+
* you can direct the built-in issue reporter to your issue reporting web experience of choice.
21+
* The Uri that the handler returns will be opened in the user's browser.
22+
*
23+
* Examples of this include:
24+
* - Using GitHub Issue Forms or GitHub Discussions you can pre-fill the issue creation with relevant information from the current workspace using query parameters
25+
* - Directing to a different web form that isn't on GitHub for reporting issues
26+
*
27+
* @param handler the issue uri request handler to register for this extension.
28+
*/
1529
export function registerIssueUriRequestHandler(handler: IssueUriRequestHandler): Disposable;
1630
}
1731
}

0 commit comments

Comments
 (0)