Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -9,6 +9,11 @@ const path = require('path');
const fs = require('fs');
const remote = require('electron').remote;
function assign(destination, source) {
return Object.keys(source)
.reduce(function (r, key) { r[key] = source[key]; return r; }, destination);
}
function parseURLQueryArgs() {
const search = window.location.search || '';
@@ -19,15 +24,6 @@ function parseURLQueryArgs() {
.reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {});
}
function createScript(src, onload) {
const script = document.createElement('script');
script.src = src;
script.addEventListener('load', onload);
const head = document.getElementsByTagName('head')[0];
head.insertBefore(script, head.lastChild);
}
function uriFromPath(_path) {
var pathName = path.resolve(_path).replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
@@ -53,6 +49,8 @@ function main() {
const args = parseURLQueryArgs();
const configuration = JSON.parse(args['config'] || '{}') || {};
assign(process.env, configuration.userEnv);
//#region Add support for using node_modules.asar
(function () {
const path = require('path');
@@ -65,10 +63,10 @@ function main() {
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';
const originalResolveLookupPaths = Module._resolveLookupPaths;
Module._resolveLookupPaths = function (request, parent) {
const result = originalResolveLookupPaths(request, parent);
Module._resolveLookupPaths = function (request, parent, newReturn) {
const result = originalResolveLookupPaths(request, parent, newReturn);
const paths = result[1];
const paths = newReturn ? result : result[1];
for (let i = 0, len = paths.length; i < len; i++) {
if (paths[i] === NODE_MODULES_PATH) {
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
@@ -143,31 +141,34 @@ function main() {
window.document.documentElement.setAttribute('lang', locale);
// In the bundled version the nls plugin is packaged with the loader so the NLS Plugins
// loads as soon as the loader loads. To be able to have pseudo translation
createScript(rootUrl + '/vs/loader.js', function () {
var define = global.define;
global.define = undefined;
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
// Load the loader
const loaderFilename = configuration.appRoot + '/out/vs/loader.js';
const loaderSource = fs.readFileSync(loaderFilename);
require('vm').runInThisContext(loaderSource, { filename: loaderFilename });
var define = global.define;
global.define = undefined;
window.MonacoEnvironment = {};
window.nodeRequire = require.__$__nodeRequire;
require.config({
baseUrl: rootUrl,
'vs/nls': nlsConfig,
nodeCachedDataDir: configuration.nodeCachedDataDir,
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code
window.MonacoEnvironment = {};
require.config({
baseUrl: rootUrl,
'vs/nls': nlsConfig,
nodeCachedDataDir: configuration.nodeCachedDataDir,
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
});
if (nlsConfig.pseudo) {
require(['vs/nls'], function (nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}
if (nlsConfig.pseudo) {
require(['vs/nls'], function (nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}
require(['vs/code/electron-browser/issue/issueReporterMain'], (issueReporter) => {
issueReporter.startup(configuration);
});
require(['vs/code/electron-browser/issue/issueReporterMain'], (issueReporter) => {
issueReporter.startup(configuration);
});
}

View File

@@ -40,6 +40,7 @@ import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { normalizeGitHubIssuesUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
const MAX_URL_LENGTH = platform.isWindows ? 2081 : 5400;
@@ -69,6 +70,7 @@ export class IssueReporter extends Disposable {
private numberOfSearchResultsDisplayed = 0;
private receivedSystemInfo = false;
private receivedPerformanceInfo = false;
private shouldQueueSearch = false;
constructor(configuration: IssueReporterConfiguration) {
super();
@@ -105,7 +107,9 @@ export class IssueReporter extends Disposable {
});
ipcRenderer.send('issueSystemInfoRequest');
ipcRenderer.send('issuePerformanceInfoRequest');
if (configuration.data.issueType === IssueType.PerformanceIssue) {
ipcRenderer.send('issuePerformanceInfoRequest');
}
this.logService.trace('issueReporter: Sent data requests');
if (window.document.documentElement.lang !== 'en') {
@@ -160,7 +164,7 @@ export class IssueReporter extends Disposable {
}
if (styles.inputActiveBorder) {
content.push(`input[type='text']:focus, textarea:focus, select:focus, summary:focus, button:focus { border: 1px solid ${styles.inputActiveBorder}; outline-style: none; }`);
content.push(`input[type='text']:focus, textarea:focus, select:focus, summary:focus, button:focus, a:focus, .workbenchCommand:focus { border: 1px solid ${styles.inputActiveBorder}; outline-style: none; }`);
}
if (styles.textLinkColor) {
@@ -208,14 +212,14 @@ export class IssueReporter extends Disposable {
});
const numberOfThemeExtesions = themes && themes.length;
this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes });
this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: extensions });
this.updateExtensionTable(nonThemes, numberOfThemeExtesions);
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 {
@@ -292,7 +296,11 @@ export class IssueReporter extends Disposable {
private setEventHandlers(): void {
this.addEventListener('issue-type', 'change', (event: Event) => {
this.issueReporterModel.update({ issueType: parseInt((<HTMLInputElement>event.target).value) });
const issueType = parseInt((<HTMLInputElement>event.target).value);
this.issueReporterModel.update({ issueType: issueType });
if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) {
ipcRenderer.send('issuePerformanceInfoRequest');
}
this.updatePreviewButtonState();
this.render();
});
@@ -322,30 +330,33 @@ export class IssueReporter extends Disposable {
});
}
this.addEventListener('reproducesWithoutExtensions', 'click', (e) => {
this.issueReporterModel.update({ reprosWithoutExtensions: true });
});
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.addEventListener('reproducesWithExtensions', 'click', (e) => {
this.issueReporterModel.update({ reprosWithoutExtensions: false });
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
if (fileOnExtension) {
this.searchExtensionIssues(title);
} else {
const description = this.issueReporterModel.getData().issueDescription;
this.searchVSCodeIssues(title, description);
}
});
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();
// Only search for extension issues on title change
if (!this.issueReporterModel.fileOnExtension()) {
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
this.searchVSCodeIssues(title, issueDescription);
}
});
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,10 +364,11 @@ export class IssueReporter extends Disposable {
hide(lengthValidationMessage);
}
if (title || description) {
this.searchDuplicates(title, description);
if (this.issueReporterModel.fileOnExtension()) {
this.searchExtensionIssues(title);
} else {
this.clearSearchResults();
const description = this.issueReporterModel.getData().issueDescription;
this.searchVSCodeIssues(title, description);
}
});
@@ -367,43 +379,32 @@ export class IssueReporter extends Disposable {
});
this.addEventListener('disableExtensions', 'keydown', (e: KeyboardEvent) => {
e.stopPropagation();
if (e.keyCode === 13 || e.keyCode === 32) {
ipcRenderer.send('workbenchCommand', 'workbench.extensions.action.disableAll');
ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindow');
}
});
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');
document.onkeydown = (e: KeyboardEvent) => {
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
// Cmd/Ctrl+Enter previews issue and closes window
if (cmdOrCtrlKey && e.keyCode === 13) {
if (this.createIssue()) {
remote.getCurrentWindow().close();
}
}
});
// Cmd+Enter or Mac or Ctrl+Enter on other platforms previews issue and closes window
if (platform.isMacintosh) {
let prevKeyWasCommand = false;
document.onkeydown = (e: KeyboardEvent) => {
if (prevKeyWasCommand && e.keyCode === 13) {
if (this.createIssue()) {
remote.getCurrentWindow().close();
}
}
// Cmd/Ctrl + zooms in
if (cmdOrCtrlKey && e.keyCode === 187) {
this.applyZoom(webFrame.getZoomLevel() + 1);
}
prevKeyWasCommand = e.keyCode === 91 || e.keyCode === 93;
};
} else {
document.onkeydown = (e: KeyboardEvent) => {
if (e.ctrlKey && e.keyCode === 13) {
if (this.createIssue()) {
remote.getCurrentWindow().close();
}
}
};
}
// Cmd/Ctrl - zooms out
if (cmdOrCtrlKey && e.keyCode === 189) {
this.applyZoom(webFrame.getZoomLevel() - 1);
}
};
}
private updatePreviewButtonState() {
@@ -438,12 +439,77 @@ export class IssueReporter extends Disposable {
return false;
}
private getExtensionRepositoryUrl(): string {
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
return selectedExtension && selectedExtension.manifest && selectedExtension.manifest.repository && selectedExtension.manifest.repository.url;
}
private getExtensionBugsUrl(): string {
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
return selectedExtension && selectedExtension.manifest && selectedExtension.manifest.bugs && selectedExtension.manifest.bugs.url;
}
private searchVSCodeIssues(title: string, issueDescription: string): void {
if (title) {
this.searchDuplicates(title, issueDescription);
} else {
this.clearSearchResults();
}
}
private searchExtensionIssues(title: string): void {
const url = this.getExtensionRepositoryUrl();
if (title) {
const matches = /^https?:\/\/github\.com\/(.*)(?:.git)/.exec(url);
if (matches && matches.length) {
const repo = matches[1];
return this.searchGitHub(repo, title);
}
}
this.clearSearchResults();
}
private clearSearchResults(): void {
const similarIssues = document.getElementById('similar-issues');
similarIssues.innerHTML = '';
this.numberOfSearchResultsDisplayed = 0;
}
@debounce(300)
private searchGitHub(repo: string, title: string): void {
const query = `is:issue+repo:${repo}+${title}`;
const similarIssues = document.getElementById('similar-issues');
window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => {
response.json().then(result => {
similarIssues.innerHTML = '';
if (result && result.items) {
this.displaySearchResults(result.items);
} else {
// If the items property isn't present, the rate limit has been hit
const message = $('div.list-title');
message.textContent = localize('rateLimited', "GitHub query limit exceeded. Please wait.");
similarIssues.appendChild(message);
const resetTime = response.headers.get('X-RateLimit-Reset');
const timeToWait = parseInt(resetTime) - Math.floor(Date.now() / 1000);
if (this.shouldQueueSearch) {
this.shouldQueueSearch = false;
setTimeout(() => {
this.searchGitHub(repo, title);
this.shouldQueueSearch = true;
}, timeToWait * 1000);
}
}
}).catch(e => {
this.logSearchError(e);
});
}).catch(e => {
this.logSearchError(e);
});
}
@debounce(300)
private searchDuplicates(title: string, body: string): void {
const url = 'https://vscode-probot.westus.cloudapp.azure.com:7890/duplicate_candidates';
@@ -516,7 +582,7 @@ export class IssueReporter extends Disposable {
similarIssues.appendChild(issues);
} else {
const message = $('div.list-title');
message.textContent = localize('noResults', "No results found");
message.textContent = localize('noSimilarIssues', "No similar issues found");
similarIssues.appendChild(message);
}
}
@@ -542,8 +608,8 @@ export class IssueReporter extends Disposable {
} else {
typeSelect.innerHTML = [
makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")),
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")),
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request"))
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")),
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue"))
].join('\n');
}
@@ -552,7 +618,8 @@ 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');
const workspaceBlock = document.querySelector('.block-workspace');
@@ -560,39 +627,64 @@ 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 problemSourceHelpText = document.getElementById('problem-source-help-text');
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);
hide(systemBlock);
hide(processBlock);
hide(workspaceBlock);
hide(extensionsBlock);
hide(searchedExtensionsBlock);
hide(settingsSearchResultsBlock);
hide(disabledExtensions);
hide(problemSource);
hide(problemSourceHelpText);
hide(extensionSelector);
if (issueType === IssueType.Bug) {
show(blockContainer);
show(systemBlock);
show(extensionsBlock);
show(disabledExtensions);
show(problemSource);
if (fileOnExtension) {
show(extensionSelector);
} else {
show(extensionsBlock);
show(problemSourceHelpText);
}
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.");
} else if (issueType === IssueType.PerformanceIssue) {
show(blockContainer);
show(systemBlock);
show(processBlock);
show(workspaceBlock);
show(extensionsBlock);
show(disabledExtensions);
show(problemSource);
if (fileOnExtension) {
show(extensionSelector);
} else {
show(extensionsBlock);
show(problemSourceHelpText);
}
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.");
} else if (issueType === IssueType.FeatureRequest) {
descriptionTitle.innerHTML = `${localize('description', "Description")} <span class="required-input">*</span>`;
descriptionSubtitle.innerHTML = localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.");
show(problemSource);
if (fileOnExtension) {
show(extensionSelector);
}
} else if (issueType === IssueType.SettingsSearchIssue) {
show(blockContainer);
show(searchedExtensionsBlock);
show(settingsSearchResultsBlock);
@@ -614,11 +706,14 @@ export class IssueReporter extends Disposable {
private validateInputs(): boolean {
let isValid = true;
['issue-title', 'description'].forEach(elementId => {
['issue-title', 'description', 'issue-source'].forEach(elementId => {
isValid = this.validateInput(elementId) && isValid;
});
if (this.issueReporterModel.fileOnExtension()) {
isValid = this.validateInput('extension-selector') && isValid;
}
return isValid;
}
@@ -626,7 +721,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');
@@ -641,8 +739,8 @@ export class IssueReporter extends Disposable {
/* __GDPR__
"issueReporterSubmit" : {
"issueType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"numSimilarIssuesDisplayed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"issueType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"numSimilarIssuesDisplayed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
@@ -660,24 +758,88 @@ export class IssueReporter extends Disposable {
return true;
}
private getIssueUrlWithTitle(issueTitle: string) {
private getIssueUrlWithTitle(issueTitle: string): string {
let repositoryUrl = product.reportIssueUrl;
if (this.issueReporterModel.fileOnExtension()) {
const bugsUrl = this.getExtensionBugsUrl();
const extensionUrl = this.getExtensionRepositoryUrl();
// If given, try to match the extension's bug url
if (bugsUrl && bugsUrl.match(/^https?:\/\/github\.com\/(.*)/)) {
repositoryUrl = normalizeGitHubIssuesUrl(bugsUrl);
} else if (extensionUrl && extensionUrl.match(/^https?:\/\/github\.com\/(.*)/)) {
repositoryUrl = normalizeGitHubIssuesUrl(extensionUrl);
}
}
const queryStringPrefix = product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&';
return `${product.reportIssueUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
}
private updateSystemInfo = (state) => {
const target = document.querySelector('.block-system .block-info');
let tableHtml = '';
Object.keys(state.systemInfo).forEach(k => {
const data = typeof state.systemInfo[k] === 'object'
? Object.keys(state.systemInfo[k]).map(key => `${key}: ${state.systemInfo[k][key]}`).join('<br>')
: state.systemInfo[k];
tableHtml += `
<tr>
<td>${k}</td>
<td>${state.systemInfo[k]}</td>
<td>${data}</td>
</tr>`;
});
target.innerHTML = `<table>${tableHtml}</table>`;
}
private updateExtensionSelector(extensions: ILocalExtension[]): void {
interface IOption {
name: string;
id: string;
}
const extensionOptions: IOption[] = extensions.map(extension => {
return {
name: extension.manifest.displayName || extension.manifest.name || '',
id: extension.identifier.id
};
});
// Sort extensions by name
extensionOptions.sort((a, b) => {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
if (aName > bName) {
return 1;
}
if (aName < bName) {
return -1;
}
return 0;
});
const makeOption = (extension: IOption) => `<option value="${extension.id}">${escape(extension.name)}</option>`;
const extensionsSelector = document.getElementById('extension-selector');
extensionsSelector.innerHTML = '<option></option>' + extensionOptions.map(makeOption).join('\n');
this.addEventListener('extension-selector', 'change', (e: Event) => {
const selectedExtensionId = (<HTMLInputElement>e.target).value;
const extensions = this.issueReporterModel.getData().allExtensions;
const matches = extensions.filter(extension => extension.identifier.id === selectedExtensionId);
if (matches.length) {
this.issueReporterModel.update({ selectedExtension: matches[0] });
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
this.searchExtensionIssues(title);
} else {
this.issueReporterModel.update({ selectedExtension: null });
this.clearSearchResults();
}
});
}
private updateProcessInfo = (state) => {
const target = document.querySelector('.block-process .block-info');
target.innerHTML = `<code>${state.processInfo}</code>`;

View File

@@ -26,9 +26,11 @@ export interface IssueReporterData {
includeSettingsSearchDetails?: boolean;
numberOfThemeExtesions?: number;
allExtensions?: ILocalExtension[];
enabledNonThemeExtesions?: ILocalExtension[];
extensionsDisabled?: boolean;
reprosWithoutExtensions?: boolean;
fileOnExtension?: boolean;
selectedExtension?: ILocalExtension;
actualSearchResults?: ISettingSearchResult[];
query?: string;
filterResultCount?: number;
@@ -44,8 +46,7 @@ export class IssueReporterModel {
includeProcessInfo: true,
includeExtensions: true,
includeSearchedExtensions: true,
includeSettingsSearchDetails: true,
reprosWithoutExtensions: false
includeSettingsSearchDetails: true
};
this._data = initialData ? assign(defaultData, initialData) : defaultData;
@@ -73,6 +74,22 @@ ${this.getInfos()}
<!-- generated by issue reporter -->`;
}
fileOnExtension(): boolean {
const fileOnExtensionSupported = this._data.issueType === IssueType.Bug
|| this._data.issueType === IssueType.PerformanceIssue
|| this._data.issueType === IssueType.FeatureRequest;
return fileOnExtensionSupported && this._data.fileOnExtension;
}
private getExtensionVersion(): string {
if (this.fileOnExtension()) {
return `\nExtension version: ${this._data.selectedExtension.manifest.version}`;
} else {
return '';
}
}
private getIssueTypeTitle(): string {
if (this._data.issueType === IssueType.Bug) {
return 'Bug';
@@ -109,8 +126,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) {
@@ -136,7 +151,11 @@ ${this.getInfos()}
`;
Object.keys(this._data.systemInfo).forEach(k => {
md += `|${k}|${this._data.systemInfo[k]}|\n`;
const data = typeof this._data.systemInfo[k] === 'object'
? Object.keys(this._data.systemInfo[k]).map(key => `${key}: ${this._data.systemInfo[k][key]}`).join('<br>')
: this._data.systemInfo[k];
md += `|${k}|${data}|\n`;
});
md += '\n</details>';

View File

@@ -19,6 +19,25 @@ export default (): string => `
</select>
</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">
<!-- {{ SQL CARBON EDIT }} -->
<option value="false">${escape(localize('sqlops', "SQL Operations Studio"))}</option>
<option value="true">${escape(localize('extension', "An Extension"))}</option>
</select>
<div id="problem-source-help-text" 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 class="input-group">
<label class="inline-label" for="issue-title">${escape(localize('issueTitleLabel', "Title"))} <span class="required-input">*</span></label>
<input id="issue-title" type="text" class="inline-form-control" placeholder="${escape(localize('issueTitleRequired', "Please enter a title."))}" required>
@@ -27,118 +46,93 @@ export default (): string => `
<!-- To be dynamically filled -->
</small>
</div>
</div>
<div class="system-info">
<div id="block-container">
<div class="block block-system">
<details>
<summary>${escape(localize('systemInfo', "My System Info"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeSystemInfo" checked/>
<label class="caption" for="includeSystemInfo">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-process">
<details>
<summary>${escape(localize('processes', "Currently Running Processes"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeProcessInfo" checked/>
<label class="caption" for="includeProcessInfo">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<pre class="block-info">
<!-- To be dynamically filled -->
</pre>
</details>
</div>
<div class="block block-workspace">
<details>
<summary>${escape(localize('workspaceStats', "My Workspace Stats"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeWorkspaceInfo" checked/>
<label class="caption" for="includeWorkspaceInfo">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<pre class="block-info">
<code>
<!-- To be dynamically filled -->
</code>
</pre>
</details>
</div>
<div class="block block-extensions">
<details>
<summary>${escape(localize('extensions', "My Extensions"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeExtensions" checked/>
<label class="caption" for="includeExtensions">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-searchedExtensions">
<details>
<summary>${escape(localize('searchedExtensions', "Searched Extensions"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/>
<label class="caption" for="includeSearchedExtensions">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-settingsSearchResults">
<details>
<summary>${escape(localize('settingsSearchDetails', "Settings Search Details"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/>
<label class="caption" for="includeSettingsSearchDetails">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
</div>
</div>
<div class="section">
<div 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 class="system-info" id="block-container">
<div class="block block-system">
<details>
<summary>${escape(localize('systemInfo', "My System Info"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeSystemInfo" checked/>
<label class="caption" for="includeSystemInfo">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</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>
</details>
</div>
<div class="block block-process">
<details>
<summary>${escape(localize('processes', "Currently Running Processes"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeProcessInfo" checked/>
<label class="caption" for="includeProcessInfo">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<pre class="block-info">
<!-- To be dynamically filled -->
</pre>
</details>
</div>
<div class="block block-workspace">
<details>
<summary>${escape(localize('workspaceStats', "My Workspace Stats"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeWorkspaceInfo" checked/>
<label class="caption" for="includeWorkspaceInfo">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<pre class="block-info">
<code>
<!-- To be dynamically filled -->
</code>
</pre>
</details>
</div>
<div class="block block-extensions">
<details>
<summary>${escape(localize('extensions', "My Extensions"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeExtensions" checked/>
<label class="caption" for="includeExtensions">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-searchedExtensions">
<details>
<summary>${escape(localize('searchedExtensions', "Searched Extensions"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/>
<label class="caption" for="includeSearchedExtensions">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-settingsSearchResults">
<details>
<summary>${escape(localize('settingsSearchDetails', "Settings Search Details"))}
<div class="include-data">
<input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/>
<label class="caption" for="includeSettingsSearchDetails">${escape(localize('sendData', "Send my data"))}</label>
</div>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
</div>
<div class="input-group">
<div class="input-group description-section">
<label for="description" id="issue-description-label">
<!-- To be dynamically filled -->
</label>
@@ -146,7 +140,7 @@ export default (): string => `
<!-- To be dynamically filled -->
</div>
<div class="block-info-text">
<textarea name="description" id="description" cols="100" rows="12" placeholder="${escape(localize('details', "Please enter details."))}" required></textarea>
<textarea name="description" id="description" placeholder="${escape(localize('details', "Please enter details."))}" required></textarea>
</div>
</div>

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { endsWith, rtrim } from 'vs/base/common/strings';
export function normalizeGitHubIssuesUrl(url: string): string {
// If the url has a .git suffix, remove it
if (endsWith(url, '.git')) {
url = url.substr(0, url.length - 4);
}
// Remove trailing slash
url = rtrim(url, '/');
// If the url already ends with issues/new, it's beautiful, return it
if (endsWith(url, 'issues/new')) {
return url;
}
// Add new segment if it does not exist
if (endsWith(url, 'issues')) {
return url + '/new';
}
return url + '/issues/new';
}

View File

@@ -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;
}
/**
@@ -103,21 +98,19 @@ textarea {
html {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
color: #CCCCCC;
height: 100%;
}
body {
margin: 0;
overflow: scroll;
height: 100%;
}
.hidden {
display: none;
}
#block-container {
margin-top: 1em;
}
.block .block-info {
width: 100%;
font-family: 'Menlo', 'Courier New', 'Courier', monospace;
@@ -144,11 +137,32 @@ button:disabled {
max-width: 85vw;
margin-left: auto;
margin-right: auto;
margin-top: 2em;
padding-top: 2em;
display: flex;
flex-direction: column;
height: 100%;
}
.description-section {
flex-grow: 1;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
textarea {
flex-grow: 1;
min-height: 150px;
}
.block-info-text {
display: flex;
flex-grow: 1;
}
#github-submit-btn {
float: right;
flex-shrink: 0;
margin-left: auto;
margin-top: 10px;
margin-bottom: 10px;
}
@@ -166,31 +180,8 @@ button:disabled {
margin-bottom: 1em;
}
.extensions-form {
display: flex;
}
.extensions-form > .form-buttons {
display: flex;
margin-left: 20px;
}
.extensions-form > .form-buttons > .choice {
margin-right: 35px;
position: relative;
}
.extensions-form > .form-buttons > .choice > label, .extensions-form > .form-buttons > .choice > input {
cursor: pointer;
height: 100%;
margin-top: 1px;
}
.extensions-form > .form-buttons > .choice > label {
position: absolute;
top: 50%;
margin-top: -50%;
left: 20px;
#extension-selection {
margin-top: 1em;
}
.system-info {
@@ -206,6 +197,7 @@ summary {
border: 1px solid transparent;
padding: 0 10px;
margin-bottom: 5px;
cursor: pointer;
}
.validation-error {
@@ -250,8 +242,9 @@ input:disabled {
margin-top: .5em;
}
.workbenchCommand {
a, .workbenchCommand {
cursor: pointer;
border: 1px solid transparent;
}
.workbenchCommand:disabled {
@@ -309,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);
}
}
@@ -328,7 +321,7 @@ button {
display: none !important;
}
.inline-form-control {
.section .inline-form-control {
width: 100%;
}
@@ -352,26 +345,28 @@ button {
.issues-container {
margin-left: 1.5em;
margin-top: .5em;
height: 92px;
max-height: 92px;
overflow-y: auto;
}
.issues-container > .issue {
padding: 4px 0;
display: flex;
}
.issues-container > .issue > .issue-link {
display: inline-block;
width: calc(100% - 82px);
vertical-align: top;
overflow: hidden;
padding-top: 3px;
white-space: nowrap;
text-overflow: ellipsis;
}
.issues-container > .issue > .issue-state .octicon {
width: 16px;
}
.issues-container > .issue > .issue-state {
display: inline-block;
width: 77px;
padding: 3px 6px;
margin-right: 5px;
@@ -380,14 +375,8 @@ button {
border-radius: .25rem;
}
.issues-container > .issue > .issue-state .octicon {
vertical-align: top;
width: 16px;
}
.issues-container > .issue .label {
margin-left: 5px;
display: inline-block;
width: 44px;
text-overflow: ellipsis;
overflow: hidden;

View File

@@ -7,6 +7,8 @@
import * as assert from 'assert';
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
import { normalizeGitHubIssuesUrl } from 'vs/code/electron-browser/issue/issueReporterUtil';
import { IssueType } from 'vs/platform/issue/common/issue';
suite('IssueReporter', () => {
@@ -18,8 +20,7 @@ suite('IssueReporter', () => {
includeProcessInfo: true,
includeExtensions: true,
includeSearchedExtensions: true,
includeSettingsSearchDetails: true,
reprosWithoutExtensions: false
includeSettingsSearchDetails: true
});
});
@@ -38,4 +39,75 @@ OS version: undefined
<!-- generated by issue reporter -->`);
});
test('serializes GPU information when data is provided', () => {
const issueReporterModel = new IssueReporterModel({
issueType: 0,
systemInfo: {
'GPU Status': {
'2d_canvas': 'enabled',
'checker_imaging': 'disabled_off'
}
}
});
assert.equal(issueReporterModel.serialize(),
// {{SQL CARBON EDIT}}
`
Issue Type: <b>Bug</b>
undefined
SQL Operations Studio version: undefined
OS version: undefined
<details>
<summary>System Info</summary>
|Item|Value|
|---|---|
|GPU Status|2d_canvas: enabled<br>checker_imaging: disabled_off|
</details>Extensions: none
<!-- generated by issue reporter -->`);
});
test('should normalize GitHub urls', () => {
[
'https://github.com/repo',
'https://github.com/repo/',
'https://github.com/repo.git',
'https://github.com/repo/issues',
'https://github.com/repo/issues/',
'https://github.com/repo/issues/new',
'https://github.com/repo/issues/new/'
].forEach(url => {
assert.equal('https://github.com/repo/issues/new', normalizeGitHubIssuesUrl(url));
});
});
test('should have support for filing on extensions for bugs, performance issues, and feature requests', () => {
[
IssueType.Bug,
IssueType.FeatureRequest,
IssueType.PerformanceIssue
].forEach(type => {
const issueReporterModel = new IssueReporterModel({
issueType: type,
fileOnExtension: true
});
assert.equal(issueReporterModel.fileOnExtension(), true);
});
[
IssueType.SettingsSearchIssue
].forEach(type => {
const issueReporterModel = new IssueReporterModel({
issueType: type,
fileOnExtension: true
});
assert.equal(issueReporterModel.fileOnExtension(), false);
});
});
});