diff --git a/src/vs/code/electron-browser/issue/issueReporter.html b/src/vs/code/electron-browser/issue/issueReporter.html
index 695de78a4cb9e455069110872b7e640864cd0936..b3c0a427fc71a79c589ab6b5545ee1889680aa15 100644
--- a/src/vs/code/electron-browser/issue/issueReporter.html
+++ b/src/vs/code/electron-browser/issue/issueReporter.html
@@ -4,7 +4,8 @@
-
+
+
diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts
index 8c6a04f03a5145789ecad9f302221f0f9c2fc42c..d3a82fe23779861f8d725df3b5a082a3fdc56e02 100644
--- a/src/vs/code/electron-browser/issue/issueReporterMain.ts
+++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts
@@ -31,7 +31,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper
import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
-import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData } from 'vs/platform/issue/common/issue';
+import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures } from 'vs/platform/issue/common/issue';
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { debounce } from 'vs/base/common/decorators';
@@ -39,8 +39,14 @@ import * as platform from 'vs/base/common/platform';
const MAX_URL_LENGTH = 5400;
+interface SearchResult {
+ html_url: string;
+ title: string;
+}
+
export interface IssueReporterConfiguration extends IWindowConfiguration {
data: IssueReporterData;
+ features: IssueReporterFeatures;
}
export function startup(configuration: IssueReporterConfiguration) {
@@ -55,6 +61,7 @@ export class IssueReporter extends Disposable {
private telemetryService: ITelemetryService;
private issueReporterModel: IssueReporterModel;
private shouldQueueSearch = true;
+ private features: IssueReporterFeatures;
private receivedSystemInfo = false;
private receivedPerformanceInfo = false;
@@ -78,6 +85,8 @@ export class IssueReporter extends Disposable {
reprosWithoutExtensions: false
});
+ this.features = configuration.features;
+
ipcRenderer.on('issuePerformanceInfoResponse', (event, info) => {
this.issueReporterModel.update(info);
this.receivedPerformanceInfo = true;
@@ -175,15 +184,15 @@ export class IssueReporter extends Disposable {
}
if (styles.sliderBackgroundColor) {
- content.push(`body::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`);
+ content.push(`.issues-container::-webkit-scrollbar-thumb, body::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`);
}
if (styles.sliderActiveColor) {
- content.push(`body::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`);
+ content.push(`.issues-container::-webkit-scrollbar-thumb:active, body::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`);
}
if (styles.sliderHoverColor) {
- content.push(`body::-webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`);
+ content.push(`.issues-container::-webkit-scrollbar-thumb:hover, body::-webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`);
}
styleTag.innerHTML = content.join('\n');
@@ -321,7 +330,7 @@ export class IssueReporter extends Disposable {
this.issueReporterModel.update({ issueDescription: (event.target).value });
});
- document.getElementById('issue-title').addEventListener('input', (e) => { this.searchGitHub(e); });
+ document.getElementById('issue-title').addEventListener('input', (e) => { this.searchIssues(e); });
document.getElementById('github-submit-btn').addEventListener('click', () => this.createIssue());
@@ -405,62 +414,120 @@ export class IssueReporter extends Disposable {
}
@debounce(300)
- private searchGitHub(event: Event): void {
+ private searchIssues(event: Event): void {
const title = (event.target).value;
- const similarIssues = document.getElementById('similar-issues');
if (title) {
- const query = `is:issue+repo:microsoft/vscode+${title}`;
- window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => {
- response.json().then(result => {
- similarIssues.innerHTML = '';
- if (result && result.items && result.items.length) {
- const issues = $('ul');
- const issuesText = $('div.list-title');
- issuesText.textContent = localize('similarIssues', "Similar issues");
-
- const { items } = result;
- const numResultsToDisplay = items.length < 5 ? items.length : 5;
- for (let i = 0; i < numResultsToDisplay; i++) {
- const link = $('a', { href: items[i].html_url });
- link.textContent = items[i].title;
- link.addEventListener('click', openLink);
- link.addEventListener('auxclick', openLink);
-
- const item = $('li', {}, link);
- issues.appendChild(item);
- }
-
- similarIssues.appendChild(issuesText);
- similarIssues.appendChild(issues);
- } else if (result && result.items) {
- const message = $('div.list-title');
- message.textContent = localize('noResults', "No results found");
- similarIssues.appendChild(message);
- } else {
- 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(event);
- this.shouldQueueSearch = true;
- }, timeToWait * 1000);
- }
-
- throw new Error(result.message);
+ if (this.features.useDuplicateSearch) {
+ this.searchDuplicates(title);
+ } else {
+ this.searchGitHub(title);
+ }
+ } else {
+ this.clearSearchResults();
+ }
+ }
+
+ private clearSearchResults(): void {
+ const similarIssues = document.getElementById('similar-issues');
+ similarIssues.innerHTML = '';
+ }
+
+ private searchDuplicates(title: string): void {
+ // TODO: Change to HTTPS
+ const url = 'http://vscode-probot.westus.cloudapp.azure.com:5010/duplicate_candidates';
+ const init = {
+ method: 'POST',
+ body: JSON.stringify({
+ title
+ }),
+ headers: new Headers({
+ 'Content-Type': 'application/json'
+ })
+ };
+
+ window.fetch(url, init).then((response) => {
+ response.json().then(result => {
+ this.clearSearchResults();
+
+ if (result && result.candidates) {
+ const normalizedResults = result.candidates.map(result => {
+ return {
+ html_url: `https://github.com/Microsoft/vscode/issues/${result.number}`,
+ title: result.title
+ };
+ });
+
+ this.displaySearchResults(normalizedResults);
+ } else {
+ throw new Error();
+ }
+ }).catch((error) => {
+ this.logSearchError(error);
+ });
+ }).catch((error) => {
+ this.logSearchError(error);
+ });
+ }
+
+ private searchGitHub(title: string): void {
+ const query = `is:issue+repo:microsoft/vscode+${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(title);
+ this.shouldQueueSearch = true;
+ }, timeToWait * 1000);
}
- }).catch((error) => {
- this.logSearchError(error);
- });
+
+ throw new Error(result.message);
+ }
}).catch((error) => {
this.logSearchError(error);
});
+ }).catch((error) => {
+ this.logSearchError(error);
+ });
+ }
+
+ private displaySearchResults(results: SearchResult[]) {
+ const similarIssues = document.getElementById('similar-issues');
+ if (results.length) {
+ const issues = $('ul.issues-container');
+ const issuesText = $('div.list-title');
+ issuesText.textContent = localize('similarIssues', "Similar issues");
+
+ const numResultsToDisplay = results.length < 5 ? results.length : 5;
+ for (let i = 0; i < numResultsToDisplay; i++) {
+ const link = $('a', { href: results[i].html_url });
+ link.textContent = results[i].title;
+ link.addEventListener('click', (e) => this.openLink(e));
+ link.addEventListener('auxclick', (e) => this.openLink(e));
+
+ const item = $('li.issue', {}, link);
+ issues.appendChild(item);
+ }
+
+ similarIssues.appendChild(issuesText);
+ similarIssues.appendChild(issues);
} else {
- similarIssues.innerHTML = '';
+ const message = $('div.list-title');
+ message.textContent = localize('noResults', "No results found");
+ similarIssues.appendChild(message);
}
}
@@ -703,6 +770,22 @@ export class IssueReporter extends Disposable {
return table;
}
+
+ private openLink(event: MouseEvent): void {
+ event.preventDefault();
+ event.stopPropagation();
+ // Exclude right click
+ if (event.which < 3) {
+ shell.openExternal((event.target).href);
+
+ /* __GDPR__
+ "issueReporterViewSimilarIssue" : {
+ "usingDuplicatesAPI" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+ }
+ */
+ this.telemetryService.publicLog('issueReporterViewSimilarIssue', { usingDuplicatesAPI: this.features.useDuplicateSearch });
+ }
+ }
}
// helper functions
@@ -713,12 +796,3 @@ function hide(el) {
function show(el) {
el.classList.remove('hidden');
}
-
-function openLink(event: MouseEvent) {
- event.preventDefault();
- event.stopPropagation();
- // Exclude right click
- if (event.which < 3) {
- shell.openExternal((event.target).href);
- }
-}
diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-browser/issue/media/issueReporter.css
index 74c25832695c128a5d4efe8a7481ef2c48d0891d..6e89ecb0dce2fe568331339d05f034c2f3e85da7 100644
--- a/src/vs/code/electron-browser/issue/media/issueReporter.css
+++ b/src/vs/code/electron-browser/issue/media/issueReporter.css
@@ -281,6 +281,7 @@ input, select, textarea {
a {
color: #CCCCCC;
+ text-decoration: none;
}
.invalid-input {
@@ -347,10 +348,20 @@ button {
}
}
-body::-webkit-scrollbar {
+.issues-container::-webkit-scrollbar, body::-webkit-scrollbar {
width: 14px;
}
-body::-webkit-scrollbar-thumb {
+.issues-container::-webkit-scrollbar, body::-webkit-scrollbar-thumb {
min-height: 20px;
}
+
+.issues-container {
+ margin-top: .5em;
+ height: 108px;
+ overflow-y: auto;
+}
+
+.issues-container > .issue {
+ padding: 1px 0;
+}
\ No newline at end of file
diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts
index e095b0e2a4ba66332fa104c32bbcd3afb637e618..31ab391651443c0043e1bf726c1a986d63bb55be 100644
--- a/src/vs/platform/issue/common/issue.ts
+++ b/src/vs/platform/issue/common/issue.ts
@@ -55,6 +55,10 @@ export interface ISettingsSearchIssueReporterData extends IssueReporterData {
filterResultCount: number;
}
+export interface IssueReporterFeatures {
+ useDuplicateSearch: boolean;
+}
+
export interface IIssueService {
_serviceBrand: any;
openReporter(data: IssueReporterData): TPromise;
diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts
index 13179eb7184140f169854f289e8f8782a785eeb2..ff083572f83579a52156d4b37654a7853980a16d 100644
--- a/src/vs/platform/issue/electron-main/issueService.ts
+++ b/src/vs/platform/issue/electron-main/issueService.ts
@@ -9,12 +9,13 @@ import { TPromise, Promise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls';
import * as objects from 'vs/base/common/objects';
import { parseArgs } from 'vs/platform/environment/node/argv';
-import { IIssueService, IssueReporterData } from 'vs/platform/issue/common/issue';
+import { IIssueService, IssueReporterData, IssueReporterFeatures } from 'vs/platform/issue/common/issue';
import { BrowserWindow, ipcMain, screen } from 'electron';
import { ILaunchService } from 'vs/code/electron-main/launch';
import { getPerformanceInfo, PerformanceInfo, getSystemInfo, SystemInfo } from 'vs/code/electron-main/diagnostics';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { isMacintosh } from 'vs/base/common/platform';
+import { IConfigurationService } from '../../configuration/common/configuration';
const DEFAULT_BACKGROUND_COLOR = '#1E1E1E';
@@ -26,7 +27,8 @@ export class IssueService implements IIssueService {
constructor(
private machineId: string,
@IEnvironmentService private environmentService: IEnvironmentService,
- @ILaunchService private launchService: ILaunchService
+ @ILaunchService private launchService: ILaunchService,
+ @IConfigurationService private configurationService: IConfigurationService
) { }
openReporter(data: IssueReporterData): TPromise {
@@ -61,7 +63,11 @@ export class IssueService implements IIssueService {
this._issueWindow.setMenuBarVisibility(false); // workaround for now, until a menu is implemented
- this._issueWindow.loadURL(this.getIssueReporterPath(data));
+ const features: IssueReporterFeatures = {
+ useDuplicateSearch: this.configurationService.getValue('issueReporter.searchDuplicates')
+ };
+
+ this._issueWindow.loadURL(this.getIssueReporterPath(data, features));
return TPromise.as(null);
}
@@ -158,13 +164,14 @@ export class IssueService implements IIssueService {
});
}
- private getIssueReporterPath(data: IssueReporterData) {
+ private getIssueReporterPath(data: IssueReporterData, features: IssueReporterFeatures) {
const windowConfiguration = {
appRoot: this.environmentService.appRoot,
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
windowId: this._issueWindow.id,
machineId: this.machineId,
- data
+ data,
+ features
};
const environment = parseArgs(process.argv);