blob: c367d43155484897a42bab6e8a287bd1b476eec3 [file] [log] [blame]
/**
* @module module/cluster-lastn-page-sk
* @description <h2><code>cluster-lastn-page-sk</code></h2>
*
* Allows trying out an alert by clustering over a range of commits.
*/
import dialogPolyfill from 'dialog-polyfill'
import { ElementSk } from '../../../infra-sk/modules/ElementSk'
import { define } from 'elements-sk/define'
import { errorMessage } from 'elements-sk/errorMessage'
import { fromObject } from 'common-sk/modules/query'
import { html } from 'lit-html'
import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow'
import { stateReflector } from 'common-sk/modules/stateReflector'
import 'elements-sk/spinner-sk'
import 'elements-sk/styles/buttons'
import '../cluster-summary2-sk'
import '../commit-detail-sk'
import '../triage-status-sk'
import '../alert-config-sk'
import '../domain-picker-sk'
function _stepUpAt(dir) {
return dir === 'UP' || dir === 'BOTH';
}
function _stepDownAt(dir) {
return dir === 'DOWN' || dir === 'BOTH';
}
function _notBoth(dir) {
return dir !== 'BOTH';
}
const _tableHeader = (ele) => {
const ret = [html`<th></th>`];
if (_stepDownAt(ele._state.direction)) {
ret.push(html`<th>Low</th>`);
}
if (_stepUpAt(ele._state.direction)) {
ret.push(html`<th>High</th>`);
}
if (_notBoth(ele._state.direction)) {
ret.push(html`<th></th>`);
}
return ret;
}
function _full_summary(frame, summary) {
return {
frame: frame,
summary: summary,
}
}
const _low = (ele, reg) => {
if (!_stepDownAt(ele._state.direction)) {
return html``;
}
if (reg.regression.low) {
return html`
<td class=cluster>
<triage-status-sk
.alert=${ele._state}
cluster_type=low
.full_summary=${_full_summary(reg.regression.frame, reg.regression.low)}
.triage=${reg.regression.low_status}>
</triage-status-sk>
</td>
`;
} else {
return html`
<td class=cluster>
</td>
`;
}
}
const _high = (ele, reg) => {
if (!_stepUpAt(ele._state.direction)) {
return html``;
}
if (reg.regression.high) {
return html`
<td class=cluster>
<triage-status-sk
.alert=${ele._state}
cluster_type=high
.full_summary=${_full_summary(reg.regression.frame, reg.regression.high)}
.triage=${reg.regression.high_status}>
</triage-status-sk>
</td>
`;
} else {
return html`
<td class=cluster>
</td>
`;
}
}
const _filler = (ele) => {
if (_notBoth(ele._state.direction)) {
return html`<td></td>`;
}
return html``;
}
const _tableRows = (ele) => ele._regressions.map((reg, rowIndex) => html`
<tr>
<td class=fixed>
<commit-detail-sk .cid=${reg.cid}></commit-detail-sk>
</td>
${_low(ele, reg)}
${_high(ele, reg)}
${_filler(ele)}
</tr>
`);
const _configTitle = (ele) => html`Algo: ${ele._state.algo} - Radius: ${ele._state.radius} - Sparse: ${ele._state.sparse} - Threshold: ${ele._state.interesting}`;
const _table = (ele) => {
if (ele._requestId && !ele._regressions.length) {
return html`No regressions found yet.`;
}
return html`
<table @start-triage=${ele._triage_start}>
<tr>
<th>Commit</th>
<th colspan=2>Regressions</th>
</tr>
<tr>
${_tableHeader(ele)}
</tr>
${_tableRows(ele)}
</table>`;
}
const template = (ele) => html`
<dialog id=alert-config-dialog>
<alert-config-sk
.config=${ele._state}
.paramset=${ele._paramset}
.key_order=${sk.perf.key_order}
></alert-config-sk>
<div class=buttons>
<button @click=${ele._alertClose}>Cancel</button>
<button @click=${ele._alertAccept}>Accept</button>
</div>
</dialog>
<div class=controls>
<label>Alert Configuration: <button @click=${ele._alertEdit}>${_configTitle(ele)}</button></label>
<label>Domain: <domain-picker-sk id=range .state=${ele._domain} @domain-changed=${ele._rangeChange} force_request_type=dense></domain-picker-sk></label>
<div class=running>
<button class=action ?disabled=${!ele._state.query || !!ele._requestId} @click=${ele._run}>Run</button>
<spinner-sk ?active=${!!ele._requestId}></spinner-sk>
<span>${ele._runningStatus}</span>
</div>
</div>
<hr>
<dialog id=triage-cluster-dialog @open-keys=${ele._openKeys}>
<cluster-summary2-sk
.full_summary=${ele._dialog_state.full_summary}
.triage=${ele._dialog_state.triage}
notriage
></cluster-summary2-sk>
<div class=buttons>
<button @click=${ele._triageClose}>Close</button>
</div>
</dialog>
${_table(ele)}
`;
define('cluster-lastn-page-sk', class extends ElementSk {
constructor() {
super(template);
// The fetch in connectedCallback will fill this is with the defaults.
this._state = {};
// The range of commits over which we are clustering.
this._domain = {
end: Math.floor(Date.now()/1000),
num_commits: 200,
request_type: 1,
};
// The regressions found when running.
this._regressions = [];
// The state of the cluster-summary2-sk dialog.
this._dialog_state = {};
// The paramsets for the alert config.
this._paramset = {};
// The id of the currently running request.
this._requestId = null;
// The text status of the currently running request.
this._runningStatus = '';
}
connectedCallback() {
super.connectedCallback();
const init = fetch('/_/initpage/').then(jsonOrThrow).then((json) => {
this._paramset = json.dataframe.paramset;
});
const alertNew = fetch('/_/alert/new').then(jsonOrThrow).then((json) => {
this._state = json;
});
Promise.all([init, alertNew]).then(() => {
this._render();
this._alertDialog = this.querySelector('#alert-config-dialog');
this._triageDialog = this.querySelector('#triage-cluster-dialog');
dialogPolyfill.registerDialog(this._alertDialog);
dialogPolyfill.registerDialog(this._triageDialog);
this._alertConfig = this.querySelector('alert-config-sk');
this._stateHasChanged = stateReflector(() => this._state, (state) => {
this._state = state;
this._render();
});
}).catch(errorMessage);
}
_rangeChange(e) {
this._domain = e.detail.state;
}
_alertEdit() {
this._alertDialog.showModal();
}
_alertClose() {
this._alertDialog.close();
}
_alertAccept(e) {
this._alertDialog.close();
this._state = this._alertConfig.config;
this._stateHasChanged();
this._render();
}
_triage_start(e) {
this._dialog_state = e.detail;
this._render();
this._triageDialog.show();
}
_triageClose() {
this._triageDialog.close();
}
_openKeys(e) {
const query = {
keys: e.detail.shortcut,
begin: e.detail.begin,
end: e.detail.end,
xbaroffset: e.detail.xbar.offset
};
window.open(`/e/?${fromObject(query)}`, '_blank');
}
_catch(msg) {
this._requestId = null;
this._runningStatus = '';
this._render();
if (msg) {
errorMessage(msg, 10000);
}
}
_run(e) {
if (this._requestId) {
errorMessage('There is a pending query already running.');
return;
}
const body = {
domain: this._domain,
config: this._state,
}
fetch('/_/dryrun/start', {
method: 'POST',
body: JSON.stringify(body),
headers:{
'Content-Type': 'application/json'
}
}).then(jsonOrThrow).then((json) => {
this._requestId = json.id;
this._render();
this._checkDryRunStatus((regressions) => {
this._regressions = regressions;
this._render();
});
}).catch((msg) => this._catch(msg));
}
_checkDryRunStatus(cb) {
fetch(`/_/dryrun/status/${this._requestId}`).then(jsonOrThrow).then((json) => {
if (!json.finished) {
this._runningStatus = json.message;
// json.regressions will get filled in incrementally, so display them
// as they appear.
cb(json.regressions);
window.setTimeout(() => this._checkDryRunStatus(cb), 300);
} else {
cb(json.regressions);
this._catch();
}
}).catch((msg) => this._catch(msg));
}
});