blob: dfe99d1b75506137e25a2f26feecb19fa3cbe9a0 [file] [log] [blame]
/**
* @module modules/revision-info-sk
* @description <h2><code>revision-info-sk</code></h2>
*
* Displays information regarding the anomalies that were detected around
* a specific revision.
*/
import { html } from 'lit/html.js';
import { define } from '../../../elements-sk/modules/define';
import { ElementSk } from '../../../infra-sk/modules/ElementSk';
import { jsonOrThrow } from '../../../infra-sk/modules/jsonOrThrow';
import { RevisionInfo } from '../json';
import '../../../elements-sk/modules/spinner-sk';
import '../../../elements-sk/modules/checkbox-sk';
import { stateReflector } from '../../../infra-sk/modules/stateReflector';
import { HintableObject } from '../../../infra-sk/modules/hintable';
import { GraphConfig, updateShortcut } from '../explore-simple-sk/explore-simple-sk';
import { CheckOrRadio } from '../../../elements-sk/modules/checkbox-sk/checkbox-sk';
import { errorMessage } from '../errorMessage';
class State {
revisionId: number = 0; // The revisionId.
}
export class RevisionInfoSk extends ElementSk {
constructor() {
super(RevisionInfoSk.template);
}
revisionId: HTMLInputElement | null = null;
revisionInfos: RevisionInfo[] | null = null;
private revisionInfoContainer: HTMLDivElement | null = null;
private showSpinner: boolean = false;
private selectAll: boolean = false;
private enableMultiGraph: boolean = false;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private stateHasChanged = () => {};
private state: State = {
revisionId: 0,
};
// toggleSelectAll checks/unchecks all revision ranges on the page
public async toggleSelectAll() {
this.selectAll = !this.selectAll;
this.querySelectorAll<CheckOrRadio>('#selectRevision').forEach((ele) => {
ele.checked = this.selectAll;
});
this.updateMultiGraphStatus();
this._render();
}
// viewMultiGraph redirects to multi graph view for the checked revision ranges
public async viewMultiGraph(): Promise<void> {
const selectedRevs: RevisionInfo[] = [];
this.querySelectorAll<HTMLInputElement>('#selectRevision').forEach((ele, i) => {
if (this.revisionInfos && this.revisionInfos[i] && ele.checked) {
selectedRevs.push(this.revisionInfos[i]);
}
});
const url = await this.getMultiGraphUrl(selectedRevs);
if (url === '') {
errorMessage(`Failed to view graph. Please file a bug at ${window.perf.feedback_url}`);
return;
}
window.open(url, '_self');
}
// getMultiGraphUrl generates the redirect url for the multigraph
// view of the checked revision ranges
async getMultiGraphUrl(revisions: RevisionInfo[]): Promise<string> {
const graphConfigs: GraphConfig[] = this.getGraphConfigs(revisions);
const newShortcut = await updateShortcut(graphConfigs);
if (newShortcut === '') {
return '';
}
const begin: number = revisions.map((rev) => rev.start_time).sort()[0];
const end: number = revisions
.map((rev) => rev.end_time)
.sort()
.reverse()[0];
const uniqueAnomalies: Set<string> = new Set();
let highlightAnomalies = '';
// Gather the unique anomaly ids from all revisioninfos.
revisions
.map((rev) => rev.anomaly_ids)
.forEach((anomalyArr) => {
anomalyArr!.forEach((anomalyId) => {
uniqueAnomalies.add(anomalyId);
});
});
uniqueAnomalies.forEach((anomalyId) => {
highlightAnomalies += `&highlight_anomalies=${anomalyId}`;
});
const url =
`/m/?begin=${begin}&end=${end}` +
`&shortcut=${newShortcut}` +
`&totalGraphs=${graphConfigs.length}` +
`${highlightAnomalies}`;
return url;
}
// getGraphConfigs generates GraphConfig[] object from the checked revision ranges
// provided as func args.
getGraphConfigs(revisions: RevisionInfo[]): GraphConfig[] {
const graphConfigs: GraphConfig[] = [];
revisions.forEach((rev) => {
graphConfigs.push({
formulas: [],
queries: [rev.query],
keys: '',
});
});
return graphConfigs;
}
private static template = (ele: RevisionInfoSk) => html`
<h3>Revision Information</h3>
<input id=revision_id type=text alt="Revision Id"></input>
<button id="getRevisionInfo" @click=${() =>
ele.getRevisionInfo()}>Get Revision Information</button>
<div>
<button
id="getRevisionInfo"
?disabled=${!ele.enableMultiGraph}
@click=${() => ele.viewMultiGraph()}
>
View Selected Graph(s)
</button>
<div>
<spinner-sk ?active=${ele.showSpinner}></spinner-sk>
</div>
<div id="revision_info_container" hidden=true>
${ele.getRevInfosTemplate()}
</div>
`;
private getRevInfosTemplate() {
if (this.revisionInfos === null) {
return html``;
}
return html`
<table class="sortable">
<tr>
<th>
<checkbox-sk
?checked=${this.selectAll}
@change=${this.toggleSelectAll}
id="selectAllRevisions">
</checkbox-sk>
</th>
<th>Bug ID</th>
<th>Revisions</th>
<th>Master</th>
<th>Bot</th>
<th>Benchmark</th>
<th>Test</th>
</tr>
${this.revisionInfos.map((revInfo) => this.revInfoRowTemplate(revInfo))}
</table>
`;
}
private updateMultiGraphStatus = () => {
const hasCheckedRevs =
this.querySelectorAll<HTMLInputElement>('#selectRevision input:checked').length > 0;
if (!hasCheckedRevs) {
this.selectAll = false;
}
this.enableMultiGraph = hasCheckedRevs;
this._render();
};
// Return the template for an individual row.
private revInfoRowTemplate = (revInfo: RevisionInfo) => html`
<tr>
<td>
<checkbox-sk id="selectRevision" @change=${this.updateMultiGraphStatus}> </checkbox-sk>
</td>
<td>
<a target="_blank" href="http://crbug/${revInfo.bug_id}">${revInfo.bug_id}</a>
</td>
<td>
<a target="_blank" href="${revInfo.explore_url}"
>${revInfo.start_revision} - ${revInfo.end_revision}</a
>
</td>
<td>${revInfo.master}</td>
<td>${revInfo.bot}</td>
<td>${revInfo.benchmark}</td>
<td>${revInfo.test}</td>
</tr>
`;
connectedCallback(): void {
super.connectedCallback();
this._render();
this.revisionId = this.querySelector('#revision_id');
this.revisionInfoContainer = this.querySelector('#revision_info_container');
// Set up the state reflector to update the revision id values
// in the url as well as the text box.
this.stateHasChanged = stateReflector(
/* getState */ () => this.state as unknown as HintableObject,
/* setState */ async (newState) => {
this.state = newState as unknown as State;
if (this.state.revisionId > 0) {
this.revisionId!.value = this.state.revisionId.toString();
if (this.state.revisionId > 0) {
await this.getRevisionInfo();
}
}
}
);
}
async getRevisionInfo(): Promise<void> {
// Update the UI to reflect the specified revision id and display spinner
this.showSpinner = true;
this.revisionInfoContainer!.hidden = true;
this.state.revisionId = +this.revisionId!.value;
this.stateHasChanged();
this._render();
// Send the request to get the revision info items to display
const response = await fetch(`/_/revision/?rev=${this.revisionId!.value}`);
const json = await jsonOrThrow(response);
this.revisionInfos = json;
this.revisionInfoContainer!.hidden = false;
this.showSpinner = false;
this._render();
}
}
define('revision-info-sk', RevisionInfoSk);