mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-03 15:01:57 +01:00
Add support for filing an issue on an extension within the issue reporter, #45673
This commit is contained in:
@@ -213,9 +213,9 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
if (this.environmentService.disableExtensions || extensions.length === 0) {
|
||||
(<HTMLButtonElement>document.getElementById('disableExtensions')).disabled = true;
|
||||
(<HTMLInputElement>document.getElementById('reproducesWithoutExtensions')).checked = true;
|
||||
this.issueReporterModel.update({ reprosWithoutExtensions: true });
|
||||
}
|
||||
|
||||
this.updateExtensionSelector(extensions);
|
||||
}
|
||||
|
||||
private handleSettingsSearchData(data: ISettingsSearchIssueReporterData): void {
|
||||
@@ -322,30 +322,21 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
this.addEventListener('reproducesWithoutExtensions', 'click', (e) => {
|
||||
this.issueReporterModel.update({ reprosWithoutExtensions: true });
|
||||
});
|
||||
|
||||
this.addEventListener('reproducesWithExtensions', 'click', (e) => {
|
||||
this.issueReporterModel.update({ reprosWithoutExtensions: false });
|
||||
this.addEventListener('issue-source', 'change', (event: Event) => {
|
||||
const fileOnExtension = JSON.parse((<HTMLInputElement>event.target).value);
|
||||
this.issueReporterModel.update({ fileOnExtension: fileOnExtension, includeExtensions: !fileOnExtension });
|
||||
this.render();
|
||||
this.search();
|
||||
});
|
||||
|
||||
this.addEventListener('description', 'input', (event: Event) => {
|
||||
const issueDescription = (<HTMLInputElement>event.target).value;
|
||||
this.issueReporterModel.update({ issueDescription });
|
||||
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
if (title || issueDescription) {
|
||||
this.searchDuplicates(title, issueDescription);
|
||||
} else {
|
||||
this.clearSearchResults();
|
||||
}
|
||||
this.search();
|
||||
});
|
||||
|
||||
this.addEventListener('issue-title', 'input', (e) => {
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
const title = (<HTMLInputElement>event.target).value;
|
||||
|
||||
const lengthValidationMessage = document.getElementById('issue-title-length-validation-error');
|
||||
if (title && this.getIssueUrlWithTitle(title).length > MAX_URL_LENGTH) {
|
||||
show(lengthValidationMessage);
|
||||
@@ -353,11 +344,7 @@ export class IssueReporter extends Disposable {
|
||||
hide(lengthValidationMessage);
|
||||
}
|
||||
|
||||
if (title || description) {
|
||||
this.searchDuplicates(title, description);
|
||||
} else {
|
||||
this.clearSearchResults();
|
||||
}
|
||||
this.search();
|
||||
});
|
||||
|
||||
this.addEventListener('github-submit-btn', 'click', () => this.createIssue());
|
||||
@@ -373,16 +360,6 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener('showRunning', 'click', () => {
|
||||
ipcRenderer.send('workbenchCommand', 'workbench.action.showRuntimeExtensions');
|
||||
});
|
||||
|
||||
this.addEventListener('showRunning', 'keydown', (e: KeyboardEvent) => {
|
||||
if (e.keyCode === 13 || e.keyCode === 32) {
|
||||
ipcRenderer.send('workbenchCommand', 'workbench.action.showRuntimeExtensions');
|
||||
}
|
||||
});
|
||||
|
||||
// Cmd+Enter or Mac or Ctrl+Enter on other platforms previews issue and closes window
|
||||
if (platform.isMacintosh) {
|
||||
let prevKeyWasCommand = false;
|
||||
@@ -438,6 +415,23 @@ export class IssueReporter extends Disposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
private search(): void {
|
||||
// Only search issues in VSCode for now.
|
||||
const fileOnExtension = this.issueReporterModel.getData().fileOnExtension;
|
||||
if (fileOnExtension) {
|
||||
this.clearSearchResults();
|
||||
return;
|
||||
}
|
||||
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
const issueDescription = (<HTMLInputElement>document.getElementById('description')).value;
|
||||
if (title || issueDescription) {
|
||||
this.searchDuplicates(title, issueDescription);
|
||||
} else {
|
||||
this.clearSearchResults();
|
||||
}
|
||||
}
|
||||
|
||||
private clearSearchResults(): void {
|
||||
const similarIssues = document.getElementById('similar-issues');
|
||||
similarIssues.innerHTML = '';
|
||||
@@ -551,7 +545,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private renderBlocks(): void {
|
||||
// Depending on Issue Type, we render different blocks and text
|
||||
const { issueType } = this.issueReporterModel.getData();
|
||||
const { issueType, fileOnExtension } = this.issueReporterModel.getData();
|
||||
const blockContainer = document.getElementById('block-container');
|
||||
const systemBlock = document.querySelector('.block-system');
|
||||
const processBlock = document.querySelector('.block-process');
|
||||
@@ -560,9 +554,10 @@ export class IssueReporter extends Disposable {
|
||||
const searchedExtensionsBlock = document.querySelector('.block-searchedExtensions');
|
||||
const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults');
|
||||
|
||||
const disabledExtensions = document.getElementById('disabledExtensions');
|
||||
const problemSource = document.getElementById('problem-source');
|
||||
const descriptionTitle = document.getElementById('issue-description-label');
|
||||
const descriptionSubtitle = document.getElementById('issue-description-subtitle');
|
||||
const extensionSelector = document.getElementById('extension-selection');
|
||||
|
||||
// Hide all by default
|
||||
hide(blockContainer);
|
||||
@@ -572,13 +567,20 @@ export class IssueReporter extends Disposable {
|
||||
hide(extensionsBlock);
|
||||
hide(searchedExtensionsBlock);
|
||||
hide(settingsSearchResultsBlock);
|
||||
hide(disabledExtensions);
|
||||
hide(problemSource);
|
||||
|
||||
if (issueType === IssueType.Bug) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(extensionsBlock);
|
||||
show(disabledExtensions);
|
||||
show(problemSource);
|
||||
|
||||
if (fileOnExtension) {
|
||||
hide(extensionsBlock);
|
||||
show(extensionSelector);
|
||||
} else {
|
||||
show(extensionsBlock);
|
||||
hide(extensionSelector);
|
||||
}
|
||||
|
||||
descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} <span class="required-input">*</span>`;
|
||||
descriptionSubtitle.innerHTML = localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.");
|
||||
@@ -588,7 +590,15 @@ export class IssueReporter extends Disposable {
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
show(extensionsBlock);
|
||||
show(disabledExtensions);
|
||||
show(problemSource);
|
||||
|
||||
if (fileOnExtension) {
|
||||
hide(extensionsBlock);
|
||||
show(extensionSelector);
|
||||
} else {
|
||||
show(extensionsBlock);
|
||||
hide(extensionSelector);
|
||||
}
|
||||
|
||||
descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} <span class="required-input">*</span>`;
|
||||
descriptionSubtitle.innerHTML = localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.");
|
||||
@@ -618,9 +628,8 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private validateInputs(): boolean {
|
||||
let isValid = true;
|
||||
['issue-title', 'description'].forEach(elementId => {
|
||||
['issue-title', 'description', 'issue-source', 'extension-selector'].forEach(elementId => {
|
||||
isValid = this.validateInput(elementId) && isValid;
|
||||
|
||||
});
|
||||
|
||||
return isValid;
|
||||
@@ -630,7 +639,10 @@ export class IssueReporter extends Disposable {
|
||||
if (!this.validateInputs()) {
|
||||
// If inputs are invalid, set focus to the first one and add listeners on them
|
||||
// to detect further changes
|
||||
(<HTMLInputElement>document.getElementsByClassName('invalid-input')[0]).focus();
|
||||
const invalidInput = document.getElementsByClassName('invalid-input');
|
||||
if (invalidInput.length) {
|
||||
(<HTMLInputElement>invalidInput[0]).focus();
|
||||
}
|
||||
|
||||
document.getElementById('issue-title').addEventListener('input', (event) => {
|
||||
this.validateInput('issue-title');
|
||||
@@ -664,9 +676,19 @@ export class IssueReporter extends Disposable {
|
||||
return true;
|
||||
}
|
||||
|
||||
private getIssueUrlWithTitle(issueTitle: string) {
|
||||
private getIssueUrlWithTitle(issueTitle: string): string {
|
||||
let repositoryUrl = product.reportIssueUrl;
|
||||
if (this.issueReporterModel.getData().fileOnExtension) {
|
||||
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
|
||||
const extensionUrl = selectedExtension && selectedExtension.manifest && selectedExtension.manifest.repository && selectedExtension.manifest.repository.url;
|
||||
if (extensionUrl) {
|
||||
// Remove '.git' suffix
|
||||
repositoryUrl = `${extensionUrl.indexOf('.git') !== -1 ? extensionUrl.substr(0, extensionUrl.length - 4) : extensionUrl}/issues/new/`;
|
||||
}
|
||||
}
|
||||
|
||||
const queryStringPrefix = product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
return `${product.reportIssueUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
|
||||
return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
|
||||
}
|
||||
|
||||
private updateSystemInfo = (state) => {
|
||||
@@ -682,6 +704,21 @@ export class IssueReporter extends Disposable {
|
||||
target.innerHTML = `<table>${tableHtml}</table>`;
|
||||
}
|
||||
|
||||
private updateExtensionSelector(extensions: ILocalExtension[]): void {
|
||||
const makeOption = (extension: ILocalExtension) => `<option value="${extension.identifier.id}">${escape(extension.manifest.displayName)}</option>`;
|
||||
const extensionsSelector = document.getElementById('extension-selector');
|
||||
extensionsSelector.innerHTML = '<option></option>' + extensions.map(makeOption).join('\n');
|
||||
|
||||
this.addEventListener('extension-selector', 'change', (e: Event) => {
|
||||
const selectedExtensionId = (<HTMLInputElement>e.target).value;
|
||||
const extensions = this.issueReporterModel.getData().enabledNonThemeExtesions;
|
||||
const matches = extensions.filter(extension => extension.identifier.id === selectedExtensionId);
|
||||
if (matches.length) {
|
||||
this.issueReporterModel.update({ selectedExtension: matches[0] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateProcessInfo = (state) => {
|
||||
const target = document.querySelector('.block-process .block-info');
|
||||
target.innerHTML = `<code>${state.processInfo}</code>`;
|
||||
|
||||
@@ -28,7 +28,8 @@ export interface IssueReporterData {
|
||||
numberOfThemeExtesions?: number;
|
||||
enabledNonThemeExtesions?: ILocalExtension[];
|
||||
extensionsDisabled?: boolean;
|
||||
reprosWithoutExtensions?: boolean;
|
||||
fileOnExtension?: boolean;
|
||||
selectedExtension?: ILocalExtension;
|
||||
actualSearchResults?: ISettingSearchResult[];
|
||||
query?: string;
|
||||
filterResultCount?: number;
|
||||
@@ -44,8 +45,7 @@ export class IssueReporterModel {
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
reprosWithoutExtensions: false
|
||||
includeSettingsSearchDetails: true
|
||||
};
|
||||
|
||||
this._data = initialData ? assign(defaultData, initialData) : defaultData;
|
||||
@@ -64,7 +64,7 @@ export class IssueReporterModel {
|
||||
Issue Type: <b>${this.getIssueTypeTitle()}</b>
|
||||
|
||||
${this._data.issueDescription}
|
||||
|
||||
${this.getExtensionVersion()}
|
||||
VS Code version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion}
|
||||
OS version: ${this._data.versionInfo && this._data.versionInfo.os}
|
||||
|
||||
@@ -72,6 +72,14 @@ ${this.getInfos()}
|
||||
<!-- generated by issue reporter -->`;
|
||||
}
|
||||
|
||||
private getExtensionVersion(): string {
|
||||
if (this._data.fileOnExtension) {
|
||||
return `\nExtension version: ${this._data.selectedExtension.manifest.version}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private getIssueTypeTitle(): string {
|
||||
if (this._data.issueType === IssueType.Bug) {
|
||||
return 'Bug';
|
||||
@@ -108,8 +116,6 @@ ${this.getInfos()}
|
||||
if (this._data.includeExtensions) {
|
||||
info += this.generateExtensionsMd();
|
||||
}
|
||||
|
||||
info += this._data.reprosWithoutExtensions ? '\nReproduces without extensions' : '\nReproduces only with extensions';
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.SettingsSearchIssue) {
|
||||
|
||||
@@ -27,6 +27,25 @@ export default (): string => `
|
||||
<!-- To be dynamically filled -->
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="input-group" id="problem-source">
|
||||
<label class="inline-label" for="issue-source">${escape(localize('issueSourceLabel', "File on"))}</label>
|
||||
<select id="issue-source" class="inline-form-control">
|
||||
<option value="false">${escape(localize('vscode', "Visual Studio Code"))}</option>
|
||||
<option value="true">${escape(localize('extension', "An Extension"))}</option>
|
||||
</select>
|
||||
<div class="instructions">${escape(localize('disableExtensionsLabelText', "Try to reproduce the problem after {0}. If the problem only reproduces when extensions are active, it is likely an issue with an extension."))
|
||||
.replace('{0}', `<span tabIndex=0 role="button" id="disableExtensions" class="workbenchCommand">${escape(localize('disableExtensions', "disabling all extensions and reloading the window"))}</span>`)}
|
||||
</div>
|
||||
|
||||
<div id="extension-selection">
|
||||
<label class="inline-label" for="extension-selector">${escape(localize('chooseExtension', "Extension"))} <span class="required-input">*</span></label>
|
||||
<select id="extension-selector" class="inline-form-control">
|
||||
<!-- To be dynamically filled -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="system-info" id="block-container">
|
||||
@@ -112,28 +131,6 @@ export default (): string => `
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" id="disabledExtensions">
|
||||
<div class="extensions-form">
|
||||
<label>${escape(localize('tryDisablingExtensions', "Is the problem reproducible when extensions are disabled?"))}</label>
|
||||
<div class="form-buttons">
|
||||
<div class="choice">
|
||||
<input type="radio" id="reproducesWithoutExtensions" value=true name="reprosWithoutExtensions" />
|
||||
<label for="reproducesWithoutExtensions">${escape(localize('yes', "Yes"))}</label>
|
||||
</div>
|
||||
<div class="choice">
|
||||
<input type="radio" id="reproducesWithExtensions" value=false name="reprosWithoutExtensions" checked/>
|
||||
<label for="reproducesWithExtensions">${escape(localize('no', "No"))}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="instructions">${escape(localize('disableExtensionsLabelText', "Try to reproduce the problem after {0}."))
|
||||
.replace('{0}', `<span tabIndex=0 role="button" id="disableExtensions" class="workbenchCommand">${escape(localize('disableExtensions', "disabling all extensions and reloading the window"))}</span>`)}
|
||||
</div>
|
||||
<div class="instructions">${escape(localize('showRunningExtensionsLabelText', "If you suspect it's an extension issue, {0} to report the issue on the extension."))
|
||||
.replace('{0}', `<span tabIndex=0 role="button"id="showRunning" class="workbenchCommand">${escape(localize('showRunningExtensions', "view all running extensions"))}</span>`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group description-section">
|
||||
<label for="description" id="issue-description-label">
|
||||
<!-- To be dynamically filled -->
|
||||
|
||||
@@ -18,16 +18,11 @@ th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
padding: .75rem;
|
||||
border-top: 1px solid #e9ecef;
|
||||
text-align: inherit;
|
||||
}
|
||||
tr:nth-of-type(even) {
|
||||
background-color: rgba(0,0,0,.05);
|
||||
}
|
||||
td {
|
||||
padding: .75rem;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.block-settingsSearchResults-details {
|
||||
@@ -39,7 +34,7 @@ td {
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 1.5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,24 +180,8 @@ textarea {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.extensions-form {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.extensions-form > .form-buttons {
|
||||
display: flex;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.extensions-form > .form-buttons > .choice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.extensions-form > .form-buttons > .choice > label, .extensions-form > .form-buttons > .choice > input {
|
||||
cursor: pointer;
|
||||
margin-top: 0;
|
||||
#extension-selection {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.system-info {
|
||||
@@ -323,17 +302,17 @@ button {
|
||||
}
|
||||
|
||||
#similar-issues {
|
||||
margin-left: 12%;
|
||||
margin-left: 13%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.section .inline-label {
|
||||
width: 12%;
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
.section .inline-form-control {
|
||||
width: calc(88% - 5px);
|
||||
width: calc(87% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +321,7 @@ button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.inline-form-control {
|
||||
.section .inline-form-control {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ suite('IssueReporter', () => {
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
reprosWithoutExtensions: false
|
||||
includeSettingsSearchDetails: true
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user