mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Fix #43268 - implement a special issue reporter mode for settings search
This commit is contained in:
parent
3b8696579f
commit
db5143d574
|
@ -11,6 +11,7 @@ import { localize } from 'vs/nls';
|
|||
import { $ } from 'vs/base/browser/dom';
|
||||
import * as collections from 'vs/base/common/collections';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
import * as os from 'os';
|
||||
|
@ -30,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 } from 'vs/platform/issue/common/issue';
|
||||
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData } 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';
|
||||
|
@ -67,7 +68,8 @@ export class IssueReporter extends Disposable {
|
|||
includeSystemInfo: true,
|
||||
includeWorkspaceInfo: true,
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true,
|
||||
versionInfo: {
|
||||
vscodeVersion: `${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`,
|
||||
os: `${os.type()} ${os.arch()} ${os.release()}`
|
||||
|
@ -101,14 +103,18 @@ export class IssueReporter extends Disposable {
|
|||
show(document.getElementById('english'));
|
||||
}
|
||||
|
||||
this.setUpTypes();
|
||||
this.setEventHandlers();
|
||||
this.applyZoom(configuration.data.zoomLevel);
|
||||
this.applyStyles(configuration.data.styles);
|
||||
this.handleExtensionData(configuration.data.enabledExtensions);
|
||||
|
||||
if (configuration.data.issueType === IssueType.SettingsSearchIssue) {
|
||||
this.handleSettingsSearchData(<ISettingsSearchIssueReporterData>configuration.data);
|
||||
}
|
||||
}
|
||||
|
||||
render(): void {
|
||||
(<HTMLSelectElement>document.getElementById('issue-type')).value = this.issueReporterModel.getData().issueType.toString();
|
||||
this.renderBlocks();
|
||||
}
|
||||
|
||||
|
@ -203,6 +209,46 @@ export class IssueReporter extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
private handleSettingsSearchData(data: ISettingsSearchIssueReporterData): void {
|
||||
this.issueReporterModel.update({
|
||||
actualSearchResults: data.actualSearchResults,
|
||||
query: data.query,
|
||||
filterResultCount: data.filterResultCount
|
||||
});
|
||||
this.updateSearchedExtensionTable(data.enabledExtensions);
|
||||
this.updateSettingsSearchDetails(data);
|
||||
}
|
||||
|
||||
private updateSettingsSearchDetails(data: ISettingsSearchIssueReporterData): void {
|
||||
const target = document.querySelector('.block-settingsSearchResults .block-info');
|
||||
|
||||
const details = `
|
||||
<div class='block-settingsSearchResults-details'>
|
||||
<div>Query: "${data.query}"</div>
|
||||
<div>Literal match count: ${data.filterResultCount}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let table = `
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Extension</th>
|
||||
<th>Score</th>
|
||||
</tr>`;
|
||||
|
||||
data.actualSearchResults
|
||||
.forEach(setting => {
|
||||
table += `
|
||||
<tr>
|
||||
<td>${setting.key}</td>
|
||||
<td>${setting.extensionId}</td>
|
||||
<td>${String(setting.score).slice(0, 5)}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
target.innerHTML = `${details}<table>${table}</table>`;
|
||||
}
|
||||
|
||||
private initServices(configuration: IWindowConfiguration): void {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
const mainProcessClient = new ElectronIPCClient(String(`window${configuration.windowId}`));
|
||||
|
@ -238,7 +284,7 @@ export class IssueReporter extends Disposable {
|
|||
this.render();
|
||||
});
|
||||
|
||||
['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions'].forEach(elementId => {
|
||||
['includeSystemInfo', 'includeProcessInfo', 'includeWorkspaceInfo', 'includeExtensions', 'includeSearchedExtensions', 'includeSettingsSearchDetails'].forEach(elementId => {
|
||||
document.getElementById(elementId).addEventListener('click', (event: Event) => {
|
||||
event.stopPropagation();
|
||||
this.issueReporterModel.update({ [elementId]: !this.issueReporterModel.getData()[elementId] });
|
||||
|
@ -351,6 +397,10 @@ export class IssueReporter extends Disposable {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (issueType === IssueType.SettingsSearchIssue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -425,6 +475,25 @@ export class IssueReporter extends Disposable {
|
|||
this.telemetryService.publicLog('issueReporterSearchError', { message: error.message });
|
||||
}
|
||||
|
||||
private setUpTypes(): void {
|
||||
const makeOption = (issueType: IssueType, description: string) => `<option value="${issueType.valueOf()}">${escape(description)}</option>`;
|
||||
|
||||
const typeSelect = (<HTMLSelectElement>document.getElementById('issue-type'));
|
||||
const { issueType } = this.issueReporterModel.getData();
|
||||
if (issueType === IssueType.SettingsSearchIssue) {
|
||||
typeSelect.innerHTML = makeOption(IssueType.SettingsSearchIssue, localize('settingsSearchIssue', "Settings Search Issue"));
|
||||
typeSelect.disabled = true;
|
||||
} else {
|
||||
typeSelect.innerHTML = [
|
||||
makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")),
|
||||
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")),
|
||||
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request"))
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
typeSelect.value = issueType.toString();
|
||||
}
|
||||
|
||||
private renderBlocks(): void {
|
||||
// Depending on Issue Type, we render different blocks and text
|
||||
const { issueType } = this.issueReporterModel.getData();
|
||||
|
@ -432,16 +501,24 @@ export class IssueReporter extends Disposable {
|
|||
const processBlock = document.querySelector('.block-process');
|
||||
const workspaceBlock = document.querySelector('.block-workspace');
|
||||
const extensionsBlock = document.querySelector('.block-extensions');
|
||||
const disabledExtensions = document.getElementById('disabledExtensions');
|
||||
const searchedExtensionsBlock = document.querySelector('.block-searchedExtensions');
|
||||
const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults');
|
||||
|
||||
const disabledExtensions = document.getElementById('disabledExtensions');
|
||||
const descriptionTitle = document.getElementById('issue-description-label');
|
||||
const descriptionSubtitle = document.getElementById('issue-description-subtitle');
|
||||
|
||||
// Hide all by default
|
||||
hide(systemBlock);
|
||||
hide(processBlock);
|
||||
hide(workspaceBlock);
|
||||
hide(extensionsBlock);
|
||||
hide(searchedExtensionsBlock);
|
||||
hide(settingsSearchResultsBlock);
|
||||
hide(disabledExtensions);
|
||||
|
||||
if (issueType === IssueType.Bug) {
|
||||
show(systemBlock);
|
||||
hide(processBlock);
|
||||
hide(workspaceBlock);
|
||||
show(extensionsBlock);
|
||||
show(disabledExtensions);
|
||||
|
||||
|
@ -456,15 +533,15 @@ export class IssueReporter extends Disposable {
|
|||
|
||||
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 {
|
||||
hide(systemBlock);
|
||||
hide(processBlock);
|
||||
hide(workspaceBlock);
|
||||
hide(extensionsBlock);
|
||||
hide(disabledExtensions);
|
||||
|
||||
} 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.");
|
||||
} else if (issueType === IssueType.SettingsSearchIssue) {
|
||||
show(searchedExtensionsBlock);
|
||||
show(settingsSearchResultsBlock);
|
||||
|
||||
descriptionTitle.innerHTML = `${localize('expectedResults', "Expected Results")} <span class="required-input">*</span>`;
|
||||
descriptionSubtitle.innerHTML = localize('settingsSearchResultsDescription', "Please list the results that you were expecting to see when you searched with this query. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -591,6 +668,23 @@ export class IssueReporter extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const table = this.getExtensionTableHtml(extensions);
|
||||
target.innerHTML = `<table>${table}</table>${themeExclusionStr}`;
|
||||
}
|
||||
|
||||
private updateSearchedExtensionTable(extensions: ILocalExtension[]): void {
|
||||
const target = document.querySelector('.block-searchedExtensions .block-info');
|
||||
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none';
|
||||
return;
|
||||
}
|
||||
|
||||
const table = this.getExtensionTableHtml(extensions);
|
||||
target.innerHTML = `<table>${table}</table>`;
|
||||
}
|
||||
|
||||
private getExtensionTableHtml(extensions: ILocalExtension[]): string {
|
||||
let table = `
|
||||
<tr>
|
||||
<th>Extension</th>
|
||||
|
@ -598,16 +692,16 @@ export class IssueReporter extends Disposable {
|
|||
<th>Version</th>
|
||||
</tr>`;
|
||||
|
||||
extensions.forEach(extension => {
|
||||
table += `
|
||||
table += extensions.map(extension => {
|
||||
return `
|
||||
<tr>
|
||||
<td>${extension.manifest.name}</td>
|
||||
<td>${extension.manifest.publisher.substr(0, 3)}</td>
|
||||
<td>${extension.manifest.version}</td>
|
||||
</tr>`;
|
||||
});
|
||||
}).join('');
|
||||
|
||||
target.innerHTML = `<table>${table}</table>${themeExclusionStr}`;
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IssueType } from 'vs/platform/issue/common/issue';
|
||||
import { IssueType, ISettingSearchResult } from 'vs/platform/issue/common/issue';
|
||||
|
||||
export interface IssueReporterData {
|
||||
issueType?: IssueType;
|
||||
|
@ -22,11 +22,16 @@ export interface IssueReporterData {
|
|||
includeWorkspaceInfo?: boolean;
|
||||
includeProcessInfo?: boolean;
|
||||
includeExtensions?: boolean;
|
||||
includeSearchedExtensions?: boolean;
|
||||
includeSettingsSearchDetails?: boolean;
|
||||
|
||||
numberOfThemeExtesions?: number;
|
||||
enabledNonThemeExtesions?: ILocalExtension[];
|
||||
extensionsDisabled?: boolean;
|
||||
reprosWithoutExtensions?: boolean;
|
||||
actualSearchResults?: ISettingSearchResult[];
|
||||
query?: string;
|
||||
filterResultCount?: number;
|
||||
}
|
||||
|
||||
export class IssueReporterModel {
|
||||
|
@ -99,6 +104,17 @@ ${this.getInfos()}`;
|
|||
info += this._data.reprosWithoutExtensions ? '\nReproduces without extensions' : '\nReproduces only with extensions';
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.SettingsSearchIssue) {
|
||||
if (this._data.includeSearchedExtensions) {
|
||||
info += this.generateExtensionsMd();
|
||||
}
|
||||
|
||||
if (this._data.includeSettingsSearchDetails) {
|
||||
info += this.generateSettingSearchResultsMd();
|
||||
info += '\n' + this.generateSettingsSearchResultDetailsMd();
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
@ -171,6 +187,35 @@ ${tableHeader}
|
|||
${table}
|
||||
${themeExclusionStr}
|
||||
|
||||
</details>`;
|
||||
}
|
||||
|
||||
private generateSettingsSearchResultDetailsMd(): string {
|
||||
return `
|
||||
Query: ${this._data.query}
|
||||
Literal matches: ${this._data.filterResultCount}`;
|
||||
}
|
||||
|
||||
private generateSettingSearchResultsMd(): string {
|
||||
if (!this._data.actualSearchResults) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!this._data.actualSearchResults.length) {
|
||||
return `No fuzzy results`;
|
||||
}
|
||||
|
||||
let tableHeader = `Setting|Extension|Score
|
||||
---|---|---`;
|
||||
const table = this._data.actualSearchResults.map(setting => {
|
||||
return `${setting.key}|${setting.extensionId}|${String(setting.score).slice(0, 5)}`;
|
||||
}).join('\n');
|
||||
|
||||
return `<details><summary>Results</summary>
|
||||
|
||||
${tableHeader}
|
||||
${table}
|
||||
|
||||
</details>`;
|
||||
}
|
||||
}
|
|
@ -15,9 +15,7 @@ export default (): string => `
|
|||
<div class="input-group">
|
||||
<label id="issue-type-label" class="inline-form-control" for="issue-type">${escape(localize('issueTypeLabel', "This is a"))}</label>
|
||||
<select id="issue-type" class="inline-form-control">
|
||||
<option value="0">${escape(localize('bugReporter', "Bug Report"))}</option>
|
||||
<option value="1">${escape(localize('performanceIssue', "Performance Issue"))}</option>
|
||||
<option value="2">${escape(localize('featureRequest', "Feature Request"))}</option>
|
||||
<!-- To be dynamically filled -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
@ -86,6 +84,32 @@ export default (): string => `
|
|||
</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>
|
||||
|
||||
|
|
|
@ -30,6 +30,14 @@ td {
|
|||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.block-settingsSearchResults-details {
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
|
||||
.block-settingsSearchResults-details > div {
|
||||
padding: .5rem .75rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ export const IIssueService = createDecorator<IIssueService>(ID);
|
|||
export enum IssueType {
|
||||
Bug,
|
||||
PerformanceIssue,
|
||||
FeatureRequest
|
||||
FeatureRequest,
|
||||
SettingsSearchIssue
|
||||
}
|
||||
|
||||
export interface IssueReporterStyles {
|
||||
|
@ -42,6 +43,19 @@ export interface IssueReporterData {
|
|||
issueType?: IssueType;
|
||||
}
|
||||
|
||||
export interface ISettingSearchResult {
|
||||
extensionId: string;
|
||||
key: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface ISettingsSearchIssueReporterData extends IssueReporterData {
|
||||
issueType: IssueType.SettingsSearchIssue;
|
||||
actualSearchResults: ISettingSearchResult[];
|
||||
query: string;
|
||||
filterResultCount: number;
|
||||
}
|
||||
|
||||
export interface IIssueService {
|
||||
_serviceBrand: any;
|
||||
openReporter(data: IssueReporterData): TPromise<void>;
|
||||
|
|
|
@ -7,7 +7,6 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
|||
import * as nls from 'vs/nls';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
|
@ -18,7 +17,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
|
|||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPreferencesService, ISettingsGroup, ISetting, IPreferencesEditorModel, IFilterResult, ISettingsEditorModel, IScoredResults, IWorkbenchSettingsConfiguration, IExtensionSetting } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { IPreferencesService, ISettingsGroup, ISetting, IPreferencesEditorModel, IFilterResult, ISettingsEditorModel, IWorkbenchSettingsConfiguration, IExtensionSetting } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { SettingsEditorModel, DefaultSettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels';
|
||||
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { IContextMenuService, ContextSubMenu } from 'vs/platform/contextview/browser/contextView';
|
||||
|
@ -26,8 +25,7 @@ import { SettingsGroupTitleWidget, EditPreferenceWidget, SettingsHeaderWidget, D
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations';
|
||||
import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Severity } from 'vs/platform/message/common/message';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
|
@ -38,6 +36,11 @@ import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/ed
|
|||
import { CodeLensProviderRegistry, CodeLensProvider, ICodeLensSymbol } from 'vs/editor/common/modes';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { IIssueService, IssueType, ISettingsSearchIssueReporterData, ISettingSearchResult } from 'vs/platform/issue/common/issue';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IExtensionManagementService, IExtensionEnablementService, LocalExtensionType, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { getIssueReporterStyles } from 'vs/workbench/electron-browser/actions';
|
||||
import { webFrame } from 'electron';
|
||||
|
||||
export interface IPreferencesRenderer<T> extends IDisposable {
|
||||
readonly preferencesModel: IPreferencesEditorModel<T>;
|
||||
|
@ -572,23 +575,16 @@ export class HiddenAreasRenderer extends Disposable {
|
|||
}
|
||||
|
||||
export class FeedbackWidgetRenderer extends Disposable {
|
||||
private static readonly DEFAULT_COMMENT_TEXT = 'Replace this comment with any text feedback.';
|
||||
private static readonly INSTRUCTION_TEXT = [
|
||||
'// Modify the "resultScores" section to contain only your expected results. Assign scores to indicate their relevance.',
|
||||
'// Results present in "resultScores" will be automatically "boosted" for this query, if they are not already at the top of the result set.',
|
||||
'// Add phrase pairs to the "alts" section to have them considered to be synonyms in queries.'
|
||||
].join('\n');
|
||||
|
||||
private _feedbackWidget: FloatingClickWidget;
|
||||
private _currentResult: IFilterResult;
|
||||
|
||||
constructor(private editor: ICodeEditor,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
@IIssueService private issueService: IIssueService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -606,151 +602,54 @@ export class FeedbackWidgetRenderer extends Disposable {
|
|||
private showWidget(): void {
|
||||
if (!this._feedbackWidget) {
|
||||
this._feedbackWidget = this._register(this.instantiationService.createInstance(FloatingClickWidget, this.editor, 'Provide feedback', null));
|
||||
this._register(this._feedbackWidget.onClick(() => this.getFeedback()));
|
||||
this._register(this._feedbackWidget.onClick(() => this.showIssueReporter()));
|
||||
this._feedbackWidget.render();
|
||||
}
|
||||
}
|
||||
|
||||
private getFeedback(): void {
|
||||
if (!this.telemetryService.isOptedIn && this.environmentService.appQuality) {
|
||||
this.messageService.show(Severity.Error, 'Can\'t send feedback, user is opted out of telemetry');
|
||||
return;
|
||||
}
|
||||
private showIssueReporter(): void {
|
||||
const filterResultGroup = arrays.first(this._currentResult.filteredGroups, group => group.id === 'filterResult');
|
||||
const filterResultCount = filterResultGroup ?
|
||||
filterResultGroup.sections[0].settings.length :
|
||||
0;
|
||||
|
||||
const result = this._currentResult;
|
||||
const metadata = result.metadata['nlpResult']; // Feedback only on nlpResult set for now
|
||||
const actualResults = metadata ? metadata.scoredResults : {};
|
||||
const actualResultIds = Object.keys(actualResults);
|
||||
const results = this._currentResult.metadata['nlpResult'].scoredResults;
|
||||
|
||||
const feedbackQuery: any = {};
|
||||
feedbackQuery['comment'] = FeedbackWidgetRenderer.DEFAULT_COMMENT_TEXT;
|
||||
feedbackQuery['queryString'] = result.query;
|
||||
feedbackQuery['duration'] = metadata ? metadata.duration : -1;
|
||||
feedbackQuery['resultScores'] = [];
|
||||
actualResultIds.forEach(settingId => {
|
||||
feedbackQuery['resultScores'].push({
|
||||
packageID: actualResults[settingId].packageId,
|
||||
key: actualResults[settingId].key,
|
||||
score: 10
|
||||
});
|
||||
});
|
||||
feedbackQuery['alts'] = [];
|
||||
this.extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
|
||||
const enabledExtensions = extensions
|
||||
.filter(extension => this.extensionEnablementService.isEnabled(extension.identifier))
|
||||
.filter(ext => ext.manifest.contributes && ext.manifest.contributes.configuration);
|
||||
const theme = this.themeService.getTheme();
|
||||
|
||||
const groupCountsText = result.filteredGroups
|
||||
.map(group => `// ${group.id}: ${group.sections[0].settings.length}`)
|
||||
.join('\n');
|
||||
const issueResults = Object.keys(results)
|
||||
.map(key => (<ISettingSearchResult>{
|
||||
key: key.split('##')[1],
|
||||
extensionId: results[key].packageId === 'core' ?
|
||||
'core' :
|
||||
this.getExtensionIdByGuid(enabledExtensions, results[key].packageId),
|
||||
score: results[key].score
|
||||
}))
|
||||
.slice(0, 20);
|
||||
|
||||
const contents = FeedbackWidgetRenderer.INSTRUCTION_TEXT + '\n' +
|
||||
JSON.stringify(feedbackQuery, undefined, ' ') + '\n\n' +
|
||||
this.getScoreText(actualResults) + '\n\n' +
|
||||
groupCountsText + '\n';
|
||||
|
||||
this.editorService.openEditor({ contents, language: 'jsonc' }, /*sideBySide=*/true).then(feedbackEditor => {
|
||||
const sendFeedbackWidget = this._register(this.instantiationService.createInstance(FloatingClickWidget, feedbackEditor.getControl(), 'Send feedback', null));
|
||||
sendFeedbackWidget.render();
|
||||
|
||||
this._register(sendFeedbackWidget.onClick(() => {
|
||||
this.sendFeedback(feedbackEditor.getControl() as ICodeEditor, result, actualResults).then(() => {
|
||||
sendFeedbackWidget.dispose();
|
||||
this.messageService.show(Severity.Info, 'Feedback sent successfully');
|
||||
}, err => {
|
||||
this.messageService.show(Severity.Error, 'Error sending feedback: ' + err.message);
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private getScoreText(results?: IScoredResults): string {
|
||||
if (!results) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Object.keys(results)
|
||||
.map(name => {
|
||||
return `// ${results[name].key}: ${results[name].score}`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
private sendFeedback(feedbackEditor: ICodeEditor, result: IFilterResult, scoredResults: IScoredResults): TPromise<void> {
|
||||
const model = feedbackEditor.getModel();
|
||||
const expectedQueryLines = model.getLinesContent()
|
||||
.filter(line => !strings.startsWith(line, '//'));
|
||||
|
||||
let expectedQuery: any;
|
||||
try {
|
||||
expectedQuery = JSON.parse(expectedQueryLines.join('\n'));
|
||||
} catch (e) {
|
||||
// invalid JSON
|
||||
return TPromise.wrapError(new Error('Invalid JSON: ' + e.message));
|
||||
}
|
||||
|
||||
const userComment = expectedQuery.comment === FeedbackWidgetRenderer.DEFAULT_COMMENT_TEXT ? undefined : expectedQuery.comment;
|
||||
|
||||
// validate alts
|
||||
if (!this.validateAlts(expectedQuery.alts)) {
|
||||
return TPromise.wrapError(new Error('alts must be an array of 2-element string arrays'));
|
||||
}
|
||||
|
||||
const altsAdded = expectedQuery.alts && expectedQuery.alts.length;
|
||||
const alts = altsAdded ? expectedQuery.alts : undefined;
|
||||
const workbenchSettings = this.configurationService.getValue<IWorkbenchSettingsConfiguration>().workbench.settings;
|
||||
const autoIngest = workbenchSettings.naturalLanguageSearchAutoIngestFeedback;
|
||||
|
||||
const nlpMetadata = result.metadata && result.metadata['nlpResult'];
|
||||
const duration = nlpMetadata && nlpMetadata.duration;
|
||||
const requestBody = nlpMetadata && nlpMetadata.requestBody;
|
||||
|
||||
const actualResultScores = {};
|
||||
for (let key in scoredResults) {
|
||||
actualResultScores[key] = {
|
||||
score: scoredResults[key].score
|
||||
const issueReporterData: ISettingsSearchIssueReporterData = {
|
||||
styles: getIssueReporterStyles(theme),
|
||||
zoomLevel: webFrame.getZoomLevel(),
|
||||
enabledExtensions,
|
||||
issueType: IssueType.SettingsSearchIssue,
|
||||
actualSearchResults: issueResults,
|
||||
filterResultCount,
|
||||
query: this._currentResult.query
|
||||
};
|
||||
}
|
||||
|
||||
/* __GDPR__
|
||||
"settingsSearchResultFeedback" : {
|
||||
"query" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
|
||||
"requestBody" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
|
||||
"userComment" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
|
||||
"actualResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"expectedResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"buildNumber" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"alts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"autoIngest" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
return this.telemetryService.publicLog('settingsSearchResultFeedback', {
|
||||
query: result.query,
|
||||
requestBody,
|
||||
userComment,
|
||||
actualResults: actualResultScores,
|
||||
expectedResults: expectedQuery.resultScores,
|
||||
duration,
|
||||
buildNumber: this.environmentService.settingsSearchBuildId,
|
||||
alts,
|
||||
autoIngest
|
||||
return this.issueService.openReporter(issueReporterData);
|
||||
});
|
||||
}
|
||||
|
||||
private validateAlts(alts?: string[][]): boolean {
|
||||
if (!alts) {
|
||||
return true;
|
||||
}
|
||||
private getExtensionIdByGuid(extensions: ILocalExtension[], guid: string): string {
|
||||
const match = arrays.first(extensions, ext => ext.identifier.uuid === guid);
|
||||
|
||||
if (!Array.isArray(alts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!alts.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!alts.every(altPair => Array.isArray(altPair) && altPair.length === 2 && typeof altPair[0] === 'string' && typeof altPair[1] === 'string')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// identifier.id includes the version, not needed here
|
||||
return match && `${match.manifest.publisher}.${match.manifest.name}`;
|
||||
}
|
||||
|
||||
private disposeWidget(): void {
|
||||
|
@ -762,7 +661,6 @@ export class FeedbackWidgetRenderer extends Disposable {
|
|||
|
||||
public dispose() {
|
||||
this.disposeWidget();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue