Strict null check issueReporterMain.ts, fixes #64757

This commit is contained in:
Rachel Macfarlane
2018-12-11 11:49:06 -08:00
parent aec79b9e41
commit c06d3b984c
3 changed files with 160 additions and 130 deletions

View File

@@ -99,6 +99,7 @@
"./vs/base/worker/defaultWorkerFactory.ts",
"./vs/base/worker/workerMain.ts",
"./vs/code/code.main.ts",
"./vs/code/electron-browser/issue/issueReporterMain.ts",
"./vs/code/electron-browser/issue/issueReporterModel.ts",
"./vs/code/electron-browser/issue/issueReporterPage.ts",
"./vs/code/electron-browser/issue/issueReporterUtil.ts",

View File

@@ -87,9 +87,12 @@ export class IssueReporter extends Disposable {
extensionsDisabled: !!this.environmentService.disableExtensions,
});
this.previewButton = new Button(document.getElementById('issue-reporter'));
const issueReporterElement = this.getElementById('issue-reporter');
if (issueReporterElement) {
this.previewButton = new Button(issueReporterElement);
}
ipcRenderer.on('vscode:issuePerformanceInfoResponse', (event, info) => {
ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_, info) => {
this.logService.trace('issueReporter: Received performance data');
this.issueReporterModel.update(info);
this.receivedPerformanceInfo = true;
@@ -100,7 +103,7 @@ export class IssueReporter extends Disposable {
this.updatePreviewButtonState();
});
ipcRenderer.on('vscode:issueSystemInfoResponse', (event, info) => {
ipcRenderer.on('vscode:issueSystemInfoResponse', (_, info) => {
this.logService.trace('issueReporter: Received system data');
this.issueReporterModel.update({ systemInfo: info });
this.receivedSystemInfo = true;
@@ -116,7 +119,7 @@ export class IssueReporter extends Disposable {
this.logService.trace('issueReporter: Sent data requests');
if (window.document.documentElement.lang !== 'en') {
show(document.getElementById('english'));
show(this.getElementById('english'));
}
this.setUpTypes();
@@ -208,7 +211,7 @@ export class IssueReporter extends Disposable {
styleTag.innerHTML = content.join('\n');
document.head.appendChild(styleTag);
document.body.style.color = styles.color;
document.body.style.color = styles.color || null;
}
private handleExtensionData(extensions: IssueReporterExtensionData[]) {
@@ -221,7 +224,7 @@ export class IssueReporter extends Disposable {
this.updateExtensionTable(nonThemes, numberOfThemeExtesions);
if (this.environmentService.disableExtensions || extensions.length === 0) {
(<HTMLButtonElement>document.getElementById('disableExtensions')).disabled = true;
(<HTMLButtonElement>this.getElementById('disableExtensions')).disabled = true;
}
this.updateExtensionSelector(extensions);
@@ -239,32 +242,33 @@ export class IssueReporter extends Disposable {
private updateSettingsSearchDetails(data: ISettingsSearchIssueReporterData): void {
const target = document.querySelector('.block-settingsSearchResults .block-info');
const details = `
if (target) {
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>`;
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>`;
});
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>`;
target.innerHTML = `${details}<table>${table}</table>`;
}
}
private initServices(configuration: IWindowConfiguration): void {
@@ -286,7 +290,7 @@ export class IssueReporter extends Disposable {
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender')));
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService));
const commonProperties = resolveCommonProperties(product.commit, pkg.version, configuration.machineId, this.environmentService.installSourcePath);
const commonProperties = resolveCommonProperties(product.commit || 'Commit unknown', pkg.version, configuration.machineId, this.environmentService.installSourcePath);
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
@@ -320,27 +324,29 @@ export class IssueReporter extends Disposable {
const showInfoElements = document.getElementsByClassName('showInfo');
for (let i = 0; i < showInfoElements.length; i++) {
const showInfo = showInfoElements.item(i);
showInfo.addEventListener('click', (e) => {
showInfo!.addEventListener('click', (e) => {
e.preventDefault();
const label = (<HTMLDivElement>e.target);
const containingElement = label.parentElement.parentElement;
const info = containingElement.lastElementChild;
if (info.classList.contains('hidden')) {
show(info);
label.textContent = localize('hide', "hide");
} else {
hide(info);
label.textContent = localize('show', "show");
if (label) {
const containingElement = label.parentElement && label.parentElement.parentElement;
const info = containingElement && containingElement.lastElementChild;
if (info && info.classList.contains('hidden')) {
show(info);
label.textContent = localize('hide', "hide");
} else {
hide(info);
label.textContent = localize('show', "show");
}
}
});
}
this.addEventListener('issue-source', 'change', (event: Event) => {
const fileOnExtension = JSON.parse((<HTMLInputElement>event.target).value);
this.addEventListener('issue-source', 'change', (e: Event) => {
const fileOnExtension = JSON.parse((<HTMLInputElement>e.target).value);
this.issueReporterModel.update({ fileOnExtension: fileOnExtension, includeExtensions: !fileOnExtension });
this.render();
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
if (fileOnExtension) {
this.searchExtensionIssues(title);
} else {
@@ -349,20 +355,20 @@ export class IssueReporter extends Disposable {
}
});
this.addEventListener('description', 'input', (event: Event) => {
const issueDescription = (<HTMLInputElement>event.target).value;
this.addEventListener('description', 'input', (e: Event) => {
const issueDescription = (<HTMLInputElement>e.target).value;
this.issueReporterModel.update({ issueDescription });
// Only search for extension issues on title change
if (!this.issueReporterModel.fileOnExtension()) {
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
this.searchVSCodeIssues(title, issueDescription);
}
});
this.addEventListener('issue-title', 'input', (e) => {
const title = (<HTMLInputElement>event.target).value;
const lengthValidationMessage = document.getElementById('issue-title-length-validation-error');
this.addEventListener('issue-title', 'input', (e: Event) => {
const title = (<HTMLInputElement>e.target).value;
const lengthValidationMessage = this.getElementById('issue-title-length-validation-error');
if (title && this.getIssueUrlWithTitle(title).length > MAX_URL_LENGTH) {
show(lengthValidationMessage);
} else {
@@ -409,7 +415,7 @@ export class IssueReporter extends Disposable {
e.stopPropagation();
e.preventDefault();
const issueTitle = (<HTMLInputElement>document.getElementById('issue-title'))!.value;
const issueTitle = (<HTMLInputElement>this.getElementById('issue-title'))!.value;
const { issueDescription } = this.issueReporterModel.getData();
if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) {
ipcRenderer.send('vscode:issueReporterConfirmClose');
@@ -471,17 +477,17 @@ export class IssueReporter extends Disposable {
return false;
}
private getExtensionRepositoryUrl(): string {
private getExtensionRepositoryUrl(): string | undefined {
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
return selectedExtension && selectedExtension.repositoryUrl;
}
private getExtensionBugsUrl(): string {
private getExtensionBugsUrl(): string | undefined {
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
return selectedExtension && selectedExtension.bugsUrl;
}
private searchVSCodeIssues(title: string, issueDescription: string): void {
private searchVSCodeIssues(title: string, issueDescription?: string): void {
if (title) {
this.searchDuplicates(title, issueDescription);
} else {
@@ -510,7 +516,7 @@ export class IssueReporter extends Disposable {
}
private clearSearchResults(): void {
const similarIssues = document.getElementById('similar-issues');
const similarIssues = this.getElementById('similar-issues')!;
similarIssues.innerHTML = '';
this.numberOfSearchResultsDisplayed = 0;
}
@@ -518,7 +524,7 @@ export class IssueReporter extends Disposable {
@debounce(300)
private searchGitHub(repo: string, title: string): void {
const query = `is:issue+repo:${repo}+${title}`;
const similarIssues = document.getElementById('similar-issues');
const similarIssues = this.getElementById('similar-issues')!;
window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => {
response.json().then(result => {
@@ -532,7 +538,7 @@ export class IssueReporter extends Disposable {
similarIssues.appendChild(message);
const resetTime = response.headers.get('X-RateLimit-Reset');
const timeToWait = parseInt(resetTime) - Math.floor(Date.now() / 1000);
const timeToWait = resetTime ? parseInt(resetTime) - Math.floor(Date.now() / 1000) : 1;
if (this.shouldQueueSearch) {
this.shouldQueueSearch = false;
setTimeout(() => {
@@ -550,7 +556,7 @@ export class IssueReporter extends Disposable {
}
@debounce(300)
private searchDuplicates(title: string, body: string): void {
private searchDuplicates(title: string, body?: string): void {
const url = 'https://vscode-probot.westus.cloudapp.azure.com:7890/duplicate_candidates';
const init = {
method: 'POST',
@@ -581,7 +587,7 @@ export class IssueReporter extends Disposable {
}
private displaySearchResults(results: SearchResult[]) {
const similarIssues = document.getElementById('similar-issues');
const similarIssues = this.getElementById('similar-issues')!;
if (results.length) {
const issues = $('div.issues-container');
const issuesText = $('div.list-title');
@@ -597,6 +603,7 @@ export class IssueReporter extends Disposable {
link.addEventListener('auxclick', (e) => this.openLink(<MouseEvent>e));
let issueState: HTMLElement;
let item: HTMLElement;
if (issue.state) {
issueState = $('span.issue-state');
@@ -610,9 +617,12 @@ export class IssueReporter extends Disposable {
issueState.title = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed");
issueState.appendChild(issueIcon);
issueState.appendChild(issueStateLabel);
item = $('div.issue', {}, issueState, link);
} else {
item = $('div.issue', {}, link);
}
const item = $('div.issue', {}, issueState, link);
issues.appendChild(item);
}
@@ -638,7 +648,7 @@ export class IssueReporter extends Disposable {
private setUpTypes(): void {
const makeOption = (issueType: IssueType, description: string) => `<option value="${issueType.valueOf()}">${escape(description)}</option>`;
const typeSelect = (<HTMLSelectElement>document.getElementById('issue-type'));
const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement;
const { issueType } = this.issueReporterModel.getData();
if (issueType === IssueType.SettingsSearchIssue) {
typeSelect.innerHTML = makeOption(IssueType.SettingsSearchIssue, localize('settingsSearchIssue', "Settings Search Issue"));
@@ -657,7 +667,7 @@ export class IssueReporter extends Disposable {
private renderBlocks(): void {
// Depending on Issue Type, we render different blocks and text
const { issueType, fileOnExtension } = this.issueReporterModel.getData();
const blockContainer = document.getElementById('block-container');
const blockContainer = this.getElementById('block-container');
const systemBlock = document.querySelector('.block-system');
const processBlock = document.querySelector('.block-process');
const workspaceBlock = document.querySelector('.block-workspace');
@@ -665,11 +675,11 @@ export class IssueReporter extends Disposable {
const searchedExtensionsBlock = document.querySelector('.block-searchedExtensions');
const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults');
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');
const problemSource = this.getElementById('problem-source')!;
const problemSourceHelpText = this.getElementById('problem-source-help-text')!;
const descriptionTitle = this.getElementById('issue-description-label')!;
const descriptionSubtitle = this.getElementById('issue-description-subtitle')!;
const extensionSelector = this.getElementById('extension-selection')!;
// Hide all by default
hide(blockContainer);
@@ -732,7 +742,7 @@ export class IssueReporter extends Disposable {
}
private validateInput(inputId: string): boolean {
const inputElement = (<HTMLInputElement>document.getElementById(inputId));
const inputElement = (<HTMLInputElement>this.getElementById(inputId));
if (!inputElement.value) {
inputElement.classList.add('invalid-input');
return false;
@@ -764,16 +774,16 @@ export class IssueReporter extends Disposable {
(<HTMLInputElement>invalidInput[0]).focus();
}
document.getElementById('issue-title').addEventListener('input', (event) => {
this.addEventListener('issue-title', 'input', _ => {
this.validateInput('issue-title');
});
document.getElementById('description').addEventListener('input', (event) => {
this.addEventListener('description', 'input', _ => {
this.validateInput('description');
});
if (this.issueReporterModel.fileOnExtension()) {
document.getElementById('extension-selector').addEventListener('change', (event) => {
this.addEventListener('extension-selector', 'change', _ => {
this.validateInput('extension-selector');
});
}
@@ -790,7 +800,7 @@ export class IssueReporter extends Disposable {
this.telemetryService.publicLog('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
this.hasBeenSubmitted = true;
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>document.getElementById('issue-title')).value);
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value);
const issueBody = this.issueReporterModel.serialize();
let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`;
@@ -832,19 +842,21 @@ export class IssueReporter extends Disposable {
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];
if (target) {
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>${data}</td>
</tr>`;
});
target.innerHTML = `<table>${tableHtml}</table>`;
tableHtml += `
<tr>
<td>${k}</td>
<td>${data}</td>
</tr>`;
});
target.innerHTML = `<table>${tableHtml}</table>`;
}
}
private updateExtensionSelector(extensions: IssueReporterExtensionData[]): void {
@@ -876,64 +888,70 @@ export class IssueReporter extends Disposable {
});
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');
const extensionsSelector = this.getElementById('extension-selector');
if (extensionsSelector) {
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.id === selectedExtensionId);
if (matches.length) {
this.issueReporterModel.update({ selectedExtension: matches[0] });
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.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();
}
});
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
this.searchExtensionIssues(title);
} else {
this.issueReporterModel.update({ selectedExtension: undefined });
this.clearSearchResults();
}
});
}
}
private updateProcessInfo = (state) => {
const target = document.querySelector('.block-process .block-info');
target.innerHTML = `<code>${state.processInfo}</code>`;
if (target) {
target.innerHTML = `<code>${state.processInfo}</code>`;
}
}
private updateWorkspaceInfo = (state) => {
document.querySelector('.block-workspace .block-info code').textContent = '\n' + state.workspaceInfo;
document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo;
}
private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void {
const target = document.querySelector('.block-extensions .block-info');
if (target) {
if (this.environmentService.disableExtensions) {
target.innerHTML = localize('disabledExtensions', "Extensions are disabled");
return;
}
if (this.environmentService.disableExtensions) {
target.innerHTML = localize('disabledExtensions', "Extensions are disabled");
return;
const themeExclusionStr = numThemeExtensions ? `\n(${numThemeExtensions} theme extensions excluded)` : '';
extensions = extensions || [];
if (!extensions.length) {
target.innerHTML = 'Extensions: none' + themeExclusionStr;
return;
}
const table = this.getExtensionTableHtml(extensions);
target.innerHTML = `<table>${table}</table>${themeExclusionStr}`;
}
const themeExclusionStr = numThemeExtensions ? `\n(${numThemeExtensions} theme extensions excluded)` : '';
extensions = extensions || [];
if (!extensions.length) {
target.innerHTML = 'Extensions: none' + themeExclusionStr;
return;
}
const table = this.getExtensionTableHtml(extensions);
target.innerHTML = `<table>${table}</table>${themeExclusionStr}`;
}
private updateSearchedExtensionTable(extensions: IssueReporterExtensionData[]): void {
const target = document.querySelector('.block-searchedExtensions .block-info');
if (target) {
if (!extensions.length) {
target.innerHTML = 'Extensions: none';
return;
}
if (!extensions.length) {
target.innerHTML = 'Extensions: none';
return;
const table = this.getExtensionTableHtml(extensions);
target.innerHTML = `<table>${table}</table>`;
}
const table = this.getExtensionTableHtml(extensions);
target.innerHTML = `<table>${table}</table>`;
}
private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): string {
@@ -970,19 +988,28 @@ export class IssueReporter extends Disposable {
}
}
private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void {
private getElementById(elementId: string): HTMLElement | undefined {
const element = document.getElementById(elementId);
if (element) {
element.addEventListener(eventType, handler);
return element;
} else {
const error = new Error(`${elementId} not found.`);
this.logService.error(error);
/* __GDPR__
"issueReporterAddEventListenerError" : {
"issueReporterGetElementError" : {
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
}
*/
this.telemetryService.publicLog('issueReporterAddEventListenerError', { message: error.message });
this.telemetryService.publicLog('issueReporterGetElementError', { message: error.message });
return undefined;
}
}
private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void {
const element = this.getElementById(elementId);
if (element) {
element.addEventListener(eventType, handler);
}
}
}

View File

@@ -7,7 +7,7 @@ import { assign } from 'vs/base/common/objects';
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
export interface IssueReporterData {
issueType?: IssueType;
issueType: IssueType;
issueDescription?: string;
versionInfo?: any;
@@ -15,15 +15,15 @@ export interface IssueReporterData {
processInfo?: any;
workspaceInfo?: any;
includeSystemInfo?: boolean;
includeWorkspaceInfo?: boolean;
includeProcessInfo?: boolean;
includeExtensions?: boolean;
includeSearchedExtensions?: boolean;
includeSettingsSearchDetails?: boolean;
includeSystemInfo: boolean;
includeWorkspaceInfo: boolean;
includeProcessInfo: boolean;
includeExtensions: boolean;
includeSearchedExtensions: boolean;
includeSettingsSearchDetails: boolean;
numberOfThemeExtesions?: number;
allExtensions?: IssueReporterExtensionData[];
allExtensions: IssueReporterExtensionData[];
enabledNonThemeExtesions?: IssueReporterExtensionData[];
extensionsDisabled?: boolean;
fileOnExtension?: boolean;
@@ -36,14 +36,16 @@ export interface IssueReporterData {
export class IssueReporterModel {
private _data: IssueReporterData;
constructor(initialData?: IssueReporterData) {
constructor(initialData?: Partial<IssueReporterData>) {
const defaultData = {
issueType: IssueType.Bug,
includeSystemInfo: true,
includeWorkspaceInfo: true,
includeProcessInfo: true,
includeExtensions: true,
includeSearchedExtensions: true,
includeSettingsSearchDetails: true
includeSettingsSearchDetails: true,
allExtensions: []
};
this._data = initialData ? assign(defaultData, initialData) : defaultData;
@@ -53,7 +55,7 @@ export class IssueReporterModel {
return this._data;
}
update(newData: IssueReporterData): void {
update(newData: Partial<IssueReporterData>): void {
assign(this._data, newData);
}