Skip to content

Commit 0b3aa0a

Browse files
authored
Let extensions prepopulate the issue reporter title and description (microsoft#91039)
* Let extensions prepopulate the issue reporter title and description Fixes microsoft#91028 Adds two new optional arguments to the `vscode.openIssueReporter` command: `issueTitle` and `issueBody`. These are taken using an options object and are used to pre-populate the native issue reporter fields Hooks up these fields for TypeScript's report issue prompt. We use this to post the most recent TS Server error stack * Extract duplicate command id to constant * Log version directly instead of prompting users for it
1 parent 9061a91 commit 0b3aa0a

8 files changed

Lines changed: 99 additions & 23 deletions

File tree

extensions/typescript-language-features/src/typescriptServiceClient.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType
529529
id: MessageAction;
530530
}
531531

532+
const previousVersion = this.apiVersion;
533+
const previousState = this.serverState;
532534
this.serverState = ServerState.None;
535+
533536
if (restart) {
534537
const diff = Date.now() - this.lastStart;
535538
this.numberRestarts++;
@@ -565,8 +568,11 @@ export default class TypeScriptServiceClient extends Disposable implements IType
565568
}
566569
if (prompt) {
567570
prompt.then(item => {
568-
if (item && item.id === MessageAction.reportIssue) {
569-
return vscode.commands.executeCommand('workbench.action.openIssueReporter');
571+
if (item?.id === MessageAction.reportIssue) {
572+
const args = previousState.type === ServerState.Type.Errored && previousState.error instanceof TypeScriptServerError
573+
? getReportIssueArgsForError(previousState.error, previousVersion)
574+
: undefined;
575+
return vscode.commands.executeCommand('workbench.action.openIssueReporter', args);
570576
}
571577
return undefined;
572578
});
@@ -719,9 +725,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType
719725
}
720726

721727
private fatalError(command: string, error: unknown): void {
722-
if (!(error instanceof TypeScriptServerError)) {
723-
console.log('fdasfasdf');
724-
}
725728
/* __GDPR__
726729
"fatalError" : {
727730
"${include}": [
@@ -740,6 +743,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType
740743
if (this.serverState.type === ServerState.Type.Running) {
741744
this.info('Killing TS Server');
742745
this.serverState.server.kill();
746+
if (error instanceof TypeScriptServerError) {
747+
this.serverState = new ServerState.Errored(error);
748+
}
743749
}
744750
}
745751

@@ -869,6 +875,32 @@ export default class TypeScriptServiceClient extends Disposable implements IType
869875
}
870876
}
871877

878+
function getReportIssueArgsForError(error: TypeScriptServerError, apiVersion: API): { issueTitle: string, issueBody: string } | undefined {
879+
if (!error.serverStack || !error.serverMessage) {
880+
return undefined;
881+
}
882+
883+
// Note these strings are intentionally not localized
884+
// as we want users to file issues in english
885+
return {
886+
issueTitle: `TS Server fatal error: ${error.serverMessage}`,
887+
888+
issueBody: `**TypeScript Version:** ${apiVersion.fullVersionString}
889+
890+
**Steps to reproduce crash**
891+
892+
1.
893+
2.
894+
3.
895+
896+
** TS Server Error Stack **
897+
898+
\`\`\`
899+
${error.serverStack}
900+
\`\`\``,
901+
};
902+
}
903+
872904
function getDignosticsKind(event: Proto.Event) {
873905
switch (event.event) {
874906
case 'syntaxDiag': return DiagnosticKind.Syntax;

src/vs/code/electron-browser/issue/issueReporterMain.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,23 @@ export class IssueReporter extends Disposable {
9797
this.previewButton = new Button(issueReporterElement);
9898
}
9999

100+
const issueTitle = configuration.data.issueTitle;
101+
if (issueTitle) {
102+
const issueTitleElement = this.getElementById<HTMLInputElement>('issue-title');
103+
if (issueTitleElement) {
104+
issueTitleElement.value = issueTitle;
105+
}
106+
}
107+
108+
const issueBody = configuration.data.issueBody;
109+
if (issueBody) {
110+
const description = this.getElementById<HTMLTextAreaElement>('description');
111+
if (description) {
112+
description.value = issueBody;
113+
this.issueReporterModel.update({ issueDescription: issueBody });
114+
}
115+
}
116+
100117
ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_: unknown, info: Partial<IssueReporterData>) => {
101118
this.logService.trace('issueReporter: Received performance data');
102119
this.issueReporterModel.update(info);
@@ -1175,8 +1192,8 @@ export class IssueReporter extends Disposable {
11751192
}
11761193
}
11771194

1178-
private getElementById(elementId: string): HTMLElement | undefined {
1179-
const element = document.getElementById(elementId);
1195+
private getElementById<T extends HTMLElement = HTMLElement>(elementId: string): T | undefined {
1196+
const element = document.getElementById(elementId) as T | undefined;
11801197
if (element) {
11811198
return element;
11821199
} else {

src/vs/platform/issue/node/issue.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ export interface IssueReporterData extends WindowData {
5959
enabledExtensions: IssueReporterExtensionData[];
6060
issueType?: IssueType;
6161
extensionId?: string;
62+
readonly issueTitle?: string;
63+
readonly issueBody?: string;
6264
}
6365

6466
export interface ISettingSearchResult {

src/vs/workbench/api/common/apiCommands.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,20 @@ export class RemoveFromRecentlyOpenedAPICommand {
174174
}
175175
CommandsRegistry.registerCommand(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute));
176176

177+
export interface OpenIssueReporterArgs {
178+
readonly extensionId: string;
179+
readonly issueTitle?: string;
180+
readonly issueBody?: string;
181+
}
182+
177183
export class OpenIssueReporter {
178184
public static readonly ID = 'vscode.openIssueReporter';
179-
public static execute(executor: ICommandsExecutor, extensionId: string): Promise<void> {
180-
return executor.executeCommand('workbench.action.openIssueReporter', [extensionId]);
185+
186+
public static execute(executor: ICommandsExecutor, args: string | OpenIssueReporterArgs): Promise<void> {
187+
const commandArgs = typeof args === 'string'
188+
? { extensionId: args }
189+
: args;
190+
return executor.executeCommand('workbench.action.openIssueReporter', commandArgs);
181191
}
182192
}
183193

src/vs/workbench/api/common/extHostApiCommands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as search from 'vs/workbench/contrib/search/common/search';
1414
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
1515
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
1616
import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures';
17-
import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter } from './apiCommands';
17+
import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter, OpenIssueReporterArgs } from './apiCommands';
1818
import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
1919
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
2020
import { IRange } from 'vs/editor/common/core/range';
@@ -364,7 +364,7 @@ export class ExtHostApiCommands {
364364
this._register(OpenIssueReporter.ID, adjustHandler(OpenIssueReporter.execute), {
365365
description: 'Opens the issue reporter with the provided extension id as the selected source',
366366
args: [
367-
{ name: 'extensionId', description: 'extensionId to report an issue on', constraint: (value: any) => typeof value === 'string' }
367+
{ name: 'extensionId', description: 'extensionId to report an issue on', constraint: (value: unknown) => typeof value === 'string' || (typeof value === 'object' && typeof (value as OpenIssueReporterArgs).extensionId === 'string') }
368368
]
369369
});
370370
}

src/vs/workbench/contrib/issue/browser/issue.contribution.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,23 @@ import { IProductService } from 'vs/platform/product/common/productService';
1212
import { Registry } from 'vs/platform/registry/common/platform';
1313
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
1414
import { IWebIssueService, WebIssueService } from 'vs/workbench/contrib/issue/browser/issueService';
15+
import { OpenIssueReporterArgs, OpenIssueReporterActionId } from 'vs/workbench/contrib/issue/common/commands';
1516

1617
class RegisterIssueContribution implements IWorkbenchContribution {
1718

1819
constructor(@IProductService readonly productService: IProductService) {
1920
if (productService.reportIssueUrl) {
2021
const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' };
21-
const OpenIssueReporterActionId = 'workbench.action.openIssueReporter';
2222
const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue");
2323

24-
CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string]) {
24+
CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string] | OpenIssueReporterArgs) {
2525
let extensionId: string | undefined;
26-
if (args && Array.isArray(args)) {
27-
[extensionId] = args;
26+
if (args) {
27+
if (Array.isArray(args)) {
28+
[extensionId] = args;
29+
} else {
30+
extensionId = args.extensionId;
31+
}
2832
}
2933

3034
return accessor.get(IWebIssueService).openReporter({ extensionId });
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
export const OpenIssueReporterActionId = 'workbench.action.openIssueReporter';
7+
8+
export interface OpenIssueReporterArgs {
9+
readonly extensionId?: string;
10+
readonly issueTitle?: string;
11+
readonly issueBody?: string;
12+
}

src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,23 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
1313
import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue';
1414
import { WorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issueService';
1515
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
16-
import { IIssueService } from 'vs/platform/issue/node/issue';
16+
import { IIssueService, IssueReporterData } from 'vs/platform/issue/node/issue';
17+
import { OpenIssueReporterArgs, OpenIssueReporterActionId } from 'vs/workbench/contrib/issue/common/commands';
1718

1819
const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' };
1920
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
2021

2122
if (!!product.reportIssueUrl) {
2223
workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory.value);
2324

24-
const OpenIssueReporterActionId = 'workbench.action.openIssueReporter';
2525
const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue");
2626

27-
CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string]) {
28-
let extensionId: string | undefined;
29-
if (args && Array.isArray(args)) {
30-
[extensionId] = args;
31-
}
27+
CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string] | OpenIssueReporterArgs) {
28+
const data: Partial<IssueReporterData> = Array.isArray(args)
29+
? { extensionId: args[0] }
30+
: args || {};
3231

33-
return accessor.get(IWorkbenchIssueService).openReporter({ extensionId });
32+
return accessor.get(IWorkbenchIssueService).openReporter(data);
3433
});
3534

3635
const command: ICommandAction = {

0 commit comments

Comments
 (0)