Perf - Migrate triage-page-sk away from Polymer.
Bug: skia:9219
Change-Id: If8c8837bc278c744024f9ff1884376df178bb8fb
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/233458
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/perf/elements.html b/perf/elements.html
index 6d5c63e..d1f72c0 100644
--- a/perf/elements.html
+++ b/perf/elements.html
@@ -15,6 +15,5 @@
<link href="/res/common/imp/commit-panel.html" rel="import" />
- <link href="/res/imp/triage-page.html" rel="import" />
<link href="/res/imp/cluster-lastn-page.html" rel="import" />
</head>
diff --git a/perf/modules/triage-page-sk/index.js b/perf/modules/triage-page-sk/index.js
new file mode 100644
index 0000000..0904e2e
--- /dev/null
+++ b/perf/modules/triage-page-sk/index.js
@@ -0,0 +1,2 @@
+import './triage-page-sk.js'
+import './triage-page-sk.scss'
diff --git a/perf/modules/triage-page-sk/triage-page-sk-demo.html b/perf/modules/triage-page-sk/triage-page-sk-demo.html
new file mode 100644
index 0000000..5a54a38
--- /dev/null
+++ b/perf/modules/triage-page-sk/triage-page-sk-demo.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>triage-page-sk</title>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+<body>
+ <h1>triage-page-sk</h1>
+ <triage-page-sk></triage-page-sk>
+
+ <h2>Events</h2>
+ <pre id=events></pre>
+ <script type="text/javascript" charset="utf-8">
+ document.querySelector('triage-page-sk').addEventListener('some-event-name', (e) => {
+ document.querySelector('#events').textContent = JSON.stringify(e.detail, null, ' ');
+ });
+ </script>
+</body>
+</html>
diff --git a/perf/modules/triage-page-sk/triage-page-sk-demo.js b/perf/modules/triage-page-sk/triage-page-sk-demo.js
new file mode 100644
index 0000000..1c51332
--- /dev/null
+++ b/perf/modules/triage-page-sk/triage-page-sk-demo.js
@@ -0,0 +1 @@
+import './index.js'
diff --git a/perf/modules/triage-page-sk/triage-page-sk.js b/perf/modules/triage-page-sk/triage-page-sk.js
new file mode 100644
index 0000000..55b3b6f
--- /dev/null
+++ b/perf/modules/triage-page-sk/triage-page-sk.js
@@ -0,0 +1,451 @@
+/**
+ * @module module/triage-page-sk
+ * @description <h2><code>triage-page-sk</code></h2>
+ *
+ * Allows triaging clusters.
+ *
+ */
+import dialogPolyfill from 'dialog-polyfill'
+import { ElementSk } from '../../../infra-sk/modules/ElementSk'
+import { equals, deepCopy } from 'common-sk/modules/object'
+import { errorMessage } from 'elements-sk/errorMessage.js'
+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 'elements-sk/styles/select'
+
+import '../cluster-summary2-sk'
+import '../commit-detail-sk'
+import '../day-range-sk'
+import '../triage-status-sk'
+
+const _allFilters = (ele) => ele._all_filter_options.map(
+ (o) => html`
+ <option
+ ?selected=${ele._state.alert_filter === o.value}
+ value=${o.value}
+ title=${o.title}
+ >${o.display}
+ </option>`);
+
+const _statusItems = (ele) => ele._currentClusteringStatus.map((item) => html`
+ <table>
+ <tr>
+ <th>Alert</th>
+ <td><a href='/a/?${item.alert.id}'>${item.alert.display_name}</a></td>
+ </tr>
+ <tr>
+ <th>Commit</th>
+ <td><commit-detail-sk .cid=${item.commit}></commit-detail-sk></td>
+ </tr>
+ <tr>
+ <th>Step</th>
+ <td>${item.step}/${item.total}</td>
+ </tr>
+ </table>
+`);
+
+const _headers = (ele) => ele._reg.header.map((item) => {
+ let displayName = item.display_name;
+ if (!item.display_name) {
+ displayName = item.query.slice(0, 10);
+ }
+ // The colspan=2 is important since we will have two columns under each
+ // header, one for high and one for low.
+ return html`<th colspan=2><a href='/a/?${item.id}'>${displayName}</a></th>`;
+});
+
+const _subHeaders = (ele) => ele._reg.header.map((_, index) => {
+ const ret = [];
+ if (ele._stepDownAt(index)) {
+ ret.push(html`<th>Low</th>`);
+ }
+ if (ele._stepUpAt(index)) {
+ ret.push(html`<th>High</th>`);
+ }
+ // If we have only one of High or Low we stuff in an empty th to match
+ // colspan=2 above.
+ if (ele._notBoth(index)) {
+ ret.push(html`<th></th>`);
+ }
+ return ret;
+});
+
+function _full_summary(frame, summary) {
+ return {
+ frame: frame,
+ summary: summary,
+ }
+}
+
+const _lowCell = (ele, rowIndex, col, colIndex) => {
+ if (col && col.low) {
+ return html`<triage-status-sk
+ .alert=${ele._alertAt(colIndex)}
+ .cluster_type=low
+ .full_summary=${_full_summary(col.frame, col.low)}
+ .triage=${col.low_status}></triage-status-sk> `;
+ } else {
+ return html`<a
+ title='No clusters found.'
+ href='/g/c/${ele._hashFrom(rowIndex)}?query=${ele._encQueryFrom(colIndex)}'>∅</a> `;
+ }
+}
+
+const _highCell = (ele, rowIndex, col, colIndex) => {
+ if (col && col.high) {
+ return html`<triage-status-sk
+ .alert=${ele._alertAt(colIndex)}
+ .cluster_type=high
+ .full_summary=${_full_summary(col.frame, col.high)}
+ .triage=${col.high_status}></triage-status-sk> `;
+ } else {
+ return html`<a
+ title='No clusters found.'
+ href='/g/c/${ele._hashFrom(rowIndex)}?query=${ele._encQueryFrom(colIndex)}'>∅</a> `;
+ }
+}
+
+const _columns = (ele, row, rowIndex) => row.columns.map((col, colIndex) => {
+ const ret = [];
+
+ if (ele._stepDownAt(colIndex)) {
+ ret.push(html`
+ <td class=cluster>
+ ${_lowCell(ele, rowIndex, col, colIndex)}
+ </td>
+ `);
+ }
+
+ if (ele._stepUpAt(colIndex)) {
+ ret.push(html`
+ <td class=cluster>
+ ${_highCell(ele, rowIndex, col, colIndex)}
+ </td>
+ `);
+ }
+
+ if (ele._notBoth(colIndex)) {
+ ret.push('<td></td>');
+ }
+ return ret;
+});
+
+const _rows= (ele) => ele._reg.table.map((row, rowIndex) => html`
+ <tr>
+ <td class=fixed>
+ <commit-detail-sk .cid=${row.cid}></commit-detail-sk>
+ </td>
+ ${_columns(ele, row, rowIndex)}
+ </tr>`);
+
+const template = (ele) => html`
+ <header>
+ <details>
+ <summary>
+ <h2>Filter</h2>
+ </summary>
+ <h3>Which commits to display.</h3>
+ <select
+ @input=${ele._commitsChange}
+ >
+ <option
+ ?selected=${ele._state.subset==='all'}
+ value=all
+ title='Show results for all commits in the time range.'>All</option>
+ <option
+ ?selected=${ele._state.subset==='flagged'}
+ value=flagged
+ title='Show only the commits with regressions in the given time range regardless of triage status.'>Regressions</option>
+ <option
+ ?selected=${ele._state.subset==='untriaged'}
+ value=untriaged
+ title='Show only commits with untriaged regressions in the given time range.'>Untriaged</option>
+ </select>
+
+ <h3>Which alerts to display.</h3>
+
+ <select @input=${ele._filterChange}>
+ ${_allFilters(ele)}
+ </select>
+ </details>
+ <details>
+ <summary>
+ <h2>Range</h2>
+ </summary>
+ <day-range-sk day-range-change=${ele._rangeChange} begin=${ele._state.begin} end=${ele._state.end}></day-range-sk>
+ </details>
+ <details @toggle=${ele._toggleStatus}>
+ <summary>
+ <h2>Status</h2>
+ </summary>
+ <div>
+ <p>The current work on detecting regressions:</p>
+ <div class=status>
+ ${_statusItems(ele)}
+ </div>
+ </div>
+ </details>
+ </header>
+ <spinner-sk ?active=${ele._triageInProgress || ele._refreshRangeInProgress}></spinner-sk>
+
+ <dialog>
+ <cluster-summary2-sk
+ @open-keys=${ele._openKeys}
+ @triaged=${ele._triaged}
+ .full_summary=${ele._dialog_state.full_summary}
+ .triage=${ele._dialog_state.triage}>
+ </cluster-summary2-sk>
+ <div class=buttons>
+ <button @click=${ele._close}>Close</button>
+ </div>
+ </dialog>
+
+ <table @start-triage=${ele._triage_start}>
+ <tr>
+ <th>Commit</th>
+ ${_headers(ele)}
+ </tr>
+ <tr>
+ <th></th>
+ ${_subHeaders(ele)}
+ </tr>
+ ${_rows(ele)}
+ </table>
+ </template>
+`;
+
+window.customElements.define('triage-page-sk', class extends ElementSk {
+ constructor() {
+ super(template);
+ const now = Math.floor(Date.now()/1000);
+
+ // The state to reflect to the URL, also the body of the POST request
+ // we send to /_/reg/.
+ this._state = {
+ begin: now - 2*7*24*60*60, // 2 weeks.
+ end: now,
+ subset: "untriaged",
+ filter: "ALL",
+ };
+
+ this._reg = {
+ header: [],
+ table: [],
+ };
+
+ this._all_filter_options = []
+
+ this._triageInProgress = false;
+
+ this._refreshRangeInProgress = false;
+
+ this._currentClusteringStatus = [];
+
+ // The ID of the setInterval that is updating _currentClusteringStatus.
+ this._statusIntervalID = 0;
+
+ this._dialog_state = {
+ full_summary: {},
+ triage: {},
+ };
+
+ this._lastState = {};
+
+ this._firstConnect = false;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ if (this._firstConnect) {
+ return;
+ }
+ this._firstConnect = true;
+
+ this._render();
+ this._dialog = this.querySelector('triage-page-sk > dialog');
+ dialogPolyfill.registerDialog(this.querySelector('dialog'));
+ this._stateHasChanged = stateReflector(() => this._state, (state) => {
+ this._state = state;
+ this._render();
+ this._updateRange();
+ });
+ }
+
+ _commitsChange(e) {
+ this._state.subset = e.target.value;
+ this._updateRange();
+ this._stateHasChanged();
+ }
+
+ _filterChange(e) {
+ this._state.alert_filter = e.target.value;
+ this._updateRange();
+ this._stateHasChanged();
+ }
+
+ _toggleStatus(e) {
+ if (e.target.open) {
+ this._statusIntervalID = window.setInterval(() => this._pollStatus(), 5000);
+ this._pollStatus();
+ } else {
+ window.clearInterval(this._statusIntervalID);
+ }
+ }
+
+ _pollStatus() {
+ fetch('/_/reg/current').then(jsonOrThrow).then((json) => {
+ this._currentClusteringStatus = json;
+ this._render();
+ }).catch(errorMessage);
+ }
+
+ _triage_start(e) {
+ this._dialog_state = e.detail;
+ this._render();
+ this._dialog.show();
+ }
+
+ _triaged(e) {
+ e.stopPropagation();
+ const body = Object.assign({}, e.detail);
+ body.alert = this._dialog_state.alert;
+ body.cluster_type = this._dialog_state.cluster_type;
+ this._dialog.close();
+ this._render();
+ if (this._triageInProgress) {
+ errorMessage("A triage request is in progress.");
+ return;
+ }
+ this._triageInProgress = true;
+ fetch('/_/triage/', {
+ method: 'POST',
+ body: JSON.stringify(body),
+ headers:{
+ 'Content-Type': 'application/json'
+ }
+ }).then(jsonOrThrow).then((json) => {
+ this._triageInProgress = false;
+ this._render();
+ if (json.bug) {
+ // Open the bug reporting page in a new window.
+ window.open(json.bug, '_blank');
+ }
+ }).catch((msg) => {
+ if (msg) {
+ errorMessage(msg, 10000);
+ }
+ this._triageInProgress = false;
+ this._render();
+ });
+ }
+
+ _close() {
+ this._dialog.close();
+ }
+
+ _stepUpAt(index) {
+ const dir = this._reg.header[index].direction;
+ return dir === 'UP' || dir === 'BOTH';
+ }
+
+ _stepDownAt(index) {
+ const dir = this._reg.header[index].direction;
+ return dir === 'DOWN' || dir === 'BOTH';
+ }
+
+ _notBoth(index) {
+ return this._reg.header[index].direction != 'BOTH';
+ }
+
+ _alertAt(index) {
+ return this._reg.header[index];
+ }
+
+ _encQueryFrom(colIndex) {
+ return encodeURIComponent(this._reg.header[colIndex].query);
+ }
+
+ _hashFrom(rowIndex) {
+ return this._reg.table[rowIndex].cid.hash;
+ }
+
+ _openKeys(e) {
+ const query = {
+ keys: e.detail.shortcut,
+ begin: e.detail.begin,
+ end: e.detail.end,
+ xbaroffset: e.detail.xbar.offset,
+ num_commits: 50,
+ request_type: 1,
+ };
+ window.open('/e/?' + fromObject(query), '_blank');
+ }
+
+ _rangeChange(e) {
+ this._state.begin = Math.floor(e.detail.begin);
+ this._state.end = Math.floor(e.detail.end);
+ this._stateHasChanged();
+ this._updateRange();
+ }
+
+ _updateRange() {
+ if (this._refreshRangeInProgress) {
+ return;
+ }
+ if (equals(this._lastState, this._state)) {
+ return;
+ }
+ this._lastState = deepCopy(this._state);
+ this._refreshRangeInProgress = true;
+ this._render();
+ fetch('/_/reg/', {
+ method: 'POST',
+ body: JSON.stringify(this._state),
+ headers:{
+ 'Content-Type': 'application/json'
+ }
+ }).then(jsonOrThrow).then((json) => {
+ this._refreshRangeInProgress = false;
+ this._reg = json;
+ this._calc_all_filter_options();
+ this._render();
+ }).catch((msg) => {
+ if (msg) {
+ errorMessage(msg, 10000);
+ }
+ this._refreshRangeInProgress = false;
+ this._render();
+ });
+ }
+
+ _calc_all_filter_options() {
+ const opts = [
+ {
+ value: 'ALL',
+ title: 'Show all alerts.',
+ display: 'Show all alerts.',
+ },
+ {
+ value: 'OWNER',
+ title: 'Show only the alerts owned by the logged in user (or all alerts if the user doesn\'t own any alerts).',
+ display: 'Show alerts you own.',
+ },
+ ];
+ if (this._reg && this._reg.categories) {
+ this._reg.categories.forEach((cat) => {
+ const displayName = cat || '(default)';
+ opts.push({
+ value: `cat:${cat}`,
+ title: `Show only the alerts in the ${displayName} category.`,
+ display: `Category: ${displayName}`,
+ });
+ });
+ }
+ this._all_filter_options = opts;
+ }
+});
diff --git a/perf/modules/triage-page-sk/triage-page-sk.scss b/perf/modules/triage-page-sk/triage-page-sk.scss
new file mode 100644
index 0000000..e8d0b99
--- /dev/null
+++ b/perf/modules/triage-page-sk/triage-page-sk.scss
@@ -0,0 +1,63 @@
+@import '~elements-sk/colors';
+
+triage-page-sk {
+ > dialog {
+ position: fixed;
+ z-index: 2;
+ }
+
+ summary h2 {
+ display: inline-block;
+ margin: 0;
+ }
+
+ header {
+ display: flex;
+ }
+
+ details {
+ display: inline-block;
+ margin: 1em 0.4em;
+ padding: 1em;
+ background: var(--dark-white);
+ }
+
+ details[open] {
+ border: solid 1px var(--light-gray);
+ background: var(--white);
+ }
+
+ details table {
+ margin: 1em;
+ }
+
+ summary {
+ padding: 0 0.6em;
+ }
+
+ summary:focus {
+ outline: dotted thick var(--blue);
+ }
+
+ tr:nth-child(even) {
+ background-color: var(--dark-white);
+ }
+
+ table {
+ border-collapse: collapse;
+ border-spacing: 0;
+ }
+
+ .cluster {
+ text-align: center;
+ }
+
+ th {
+ padding: 0 1em;
+ }
+
+ a {
+ color: var(--blue);
+ }
+
+}
diff --git a/perf/modules/triage-status-sk/triage-status-sk.scss b/perf/modules/triage-status-sk/triage-status-sk.scss
index 4292c91..48b8c32 100644
--- a/perf/modules/triage-status-sk/triage-status-sk.scss
+++ b/perf/modules/triage-status-sk/triage-status-sk.scss
@@ -1,2 +1,5 @@
triage-status-sk {
+ button {
+ padding: 0;
+ }
}
diff --git a/perf/modules/triage2-sk/triage2-sk.scss b/perf/modules/triage2-sk/triage2-sk.scss
index adc8fe7..cfbeae8 100644
--- a/perf/modules/triage2-sk/triage2-sk.scss
+++ b/perf/modules/triage2-sk/triage2-sk.scss
@@ -30,6 +30,10 @@
.untriaged[raised]:hover {
background: #ddd;
}
+
+ button {
+ padding: 0;
+ }
}
.darkmode triage2-sk {
diff --git a/perf/pages/main.js b/perf/pages/main.js
index a91579e..6bc88b5 100644
--- a/perf/pages/main.js
+++ b/perf/pages/main.js
@@ -26,5 +26,6 @@
import '../modules/perf-scaffold-sk'
import '../modules/triage-status-sk'
import '../modules/triage2-sk'
+import '../modules/triage-page-sk'
import '../modules/tricon2-sk'
import '../modules/word-cloud-sk'
diff --git a/perf/templates/triage.html b/perf/templates/triage.html
index 5ae3aaa..624fa98 100644
--- a/perf/templates/triage.html
+++ b/perf/templates/triage.html
@@ -6,7 +6,46 @@
this.sk = this.sk || {};
this.sk.perf = {{.context}};
</script>
- {{template "header.html" .}}
+ <!-- Can't use header.html here until everything is ported away from Polymer. -->
+ <meta charset="utf-8">
+ <meta name="theme-color" content="#1f78b4">
+ <link rel="shortcut icon" href="/res/img/favicon.ico" />
+ <link rel="manifest" href="/res/manifest.json">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <script src="/res/js/core.js" type="text/javascript" charset="utf-8"></script>
+ <style type="text/css" media="screen">
+ body {
+ margin: 0;
+ padding: 0;
+ }
+
+ .main * {
+ font-size: 12px;
+ }
+
+ .main h2 {
+ font-size: 16px !important;
+ }
+
+ .main h3 {
+ font-size: 14px !important;
+ }
+
+ .main button {
+ text-transform: none !important;
+ }
+ </style>
+ <script>
+ // Check that service workers are registered
+ if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('/service-worker.js');
+ });
+ }
+ </script>
+ <link href="/dist/main-bundle.css" rel="stylesheet">
+ <script type="text/javascript" src="/dist/main-bundle.js"></script>
</head>
<body>
<perf-scaffold-sk>