blob: 264b32c42e7f8dfb9dd5625faf0bce49b12d78c9 [file] [log] [blame]
<!-- The <triage-page-sk> custom element declaration.
Allows triaging clusters.
Attributes:
None.
Events:
None.
Methods:
None.
-->
<link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="/res/imp/bower_components/paper-spinner/paper-spinner.html">
<link rel="import" href="/res/imp/bower_components/iron-selector/iron-selector.html">
<link rel="import" href="/res/imp/bower_components/paper-dialog/paper-dialog.html">
<link rel="import" href="/res/common/imp/query-summary-sk.html" />
<link rel="import" href="/res/common/imp/details-summary.html" />
<link rel="import" href="/res/imp/cluster2.html">
<link rel="import" href="/res/imp/triage-status.html" />
<link rel="import" href="/res/imp/commit-detail.html" />
<dom-module id="triage-page-sk">
<style include="iron-flex iron-flex-alignment iron-positioning">
details-sk {
margin-top: 0.5em;
}
day-range-sk {
display: block;
}
.fixed {
font-family: 'Roboto Mono', monospace;
}
.cluster {
text-align: center;
}
triage-status-sk {
text-align: left;
}
tr:nth-child(even) {
background-color: #eee;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
#table.hidden {
display: none;
}
.dot {
text-decoration: none;
color: black;
}
.iron-selected {
background: #eee;
}
iron-selector div {
width: 30em;
margin: 0.3em 1em;
padding: 0.2em;
}
summary-sk h2 {
display: inline;
}
details-sk {
display: block;
margin: 1em 0.4em;
}
details-sk table {
margin: 1em;
}
th {
padding: 0 1em;
}
cluster-summary2-sk {
display: block;
margin: 2em;
}
paper-dialog {
overflow: auto;
}
header {
display: flex;
flex-flow: row wrap;
}
</style>
<template>
<header>
<details-sk>
<summary-sk>
<h2 id="filter">Filter</h2>
</summary-sk>
<h3>Which commits to display.</h3>
<iron-selector
attr-for-selected="value"
selected="{{state.subset}}"
fallback-selection="untriaged"
on-selected-item-changed="_updateRange"
>
<div value=all title="Show results for all commits in the time range.">All</div>
<div value=flagged title="Show only the commits with regressions in the given time range regardless of triage status.">Regressions</div>
<div value=untriaged title="Show only commits with untriaged regressions in the given time range.">Untriaged</div>
</iron-selector>
<h3>Which alerts to display.</h3>
<iron-selector
attr-for-selected="value"
selected="{{state.filter}}"
on-selected-item-changed="_updateRange"
>
<div value=ALL title="Show all alerts.">Show all alerts</div>
<div value=OWNER title="Show only the alerts owned by the logged in
user (or all alerts if the user doesn't own any alerts).">Show alerts you own</div>
<template is="dom-repeat" items="[[_reg.categories]]">
<div value="cat:[[item]]" title="Show only the alerts in the
[[_categoryDisplay(item)]] category.">Category: [[_categoryDisplay(item)]]</div>
</template>
</iron-selector>
</details-sk>
<details-sk>
<summary-sk>
<h2 id="range">Range</h2>
</summary-sk>
<day-range-sk id=range on-day-range-change="_rangeChange"></day-range-sk>
</details-sk>
<details-sk open="{{_statusOpen}}">
<summary-sk>
<h2>Status</h2>
</summary-sk>
<div id=status>
<p>The current work on detecting regressions:</p>
<table>
<tr>
<th>Alert</th>
<td><a href="/a/?[[_currentState.alert.id]]">[[_currentState.alert.display_name]]</a></td>
</tr>
<tr>
<th>Commit</th>
<td><commit-detail-sk cid="[[_currentState.commit]]"></commit-detail-sk></td>
</tr>
</table>
</div>
</details-sk>
</header>
<paper-spinner id=spinner></paper-spinner>
<paper-dialog id=dialog on-open-keys=_openKeys>
<cluster-summary2-sk id=cluster_summary on-triaged=_triaged id=summary full_summary="[[_dialog_state.full_summary]]" triage="[[_dialog_state.triage]]"></cluster-summary2-sk>
<div class=buttons>
<button on-click=_close>Close</button>
</div>
</paper-dialog>
<table on-start-triage=_triage_start id=table>
<tr>
<th>Commit</th>
<template is="dom-repeat" items="[[_reg.header]]">
<th colspan=2><a href="/a/?[[item.id]]">[[_displayName(item.query,item.display_name)]]</a></th>
</template>
</tr>
<tr>
<th></th>
<template is="dom-repeat" items="[[_reg.header]]">
<template is="dom-if" if="[[_stepDownAt(index)]]">
<th>Low</th>
</template>
<template is="dom-if" if="[[_stepUpAt(index)]]">
<th>High</th>
</template>
<template is="dom-if" if="[[_notBoth(index)]]">
<th></th>
</template>
</template>
</tr>
<template is="dom-repeat" items="[[_reg.table]]" index-as="tableIndex">
<tr>
<td class=fixed>
<commit-detail-sk cid="[[item.cid]]"></commit-detail-sk>
</td>
<template is="dom-repeat" items="[[item.columns]]">
<template is="dom-if" if="[[_stepDownAt(index)]]">
<td class=cluster>
<template is="dom-if" if="[[item.low]]">
<triage-status-sk alert="[[_alertAt(index)]]" cluster_type=low full_summary="[[_full_summary(item.frame, item.low)]]" triage="[[item.low_status]]"></triage-status-sk>
</template>
<template is="dom-if" if="[[_not(item.low)]]">
<a class=dot title="No clusters found." href="/g/c/[[_hashFrom(tableIndex)]]?query=[[_encQueryFrom(index)]]">[[_display(tableIndex,state.end)]]</a>
</template>
</td>
</template>
<template is="dom-if" if="[[_stepUpAt(index)]]">
<td class=cluster>
<template is="dom-if" if="[[item.high]]">
<triage-status-sk alert="[[_alertAt(index)]]" cluster_type=high full_summary="[[_full_summary(item.frame, item.high)]]" triage="[[item.high_status]]"></triage-status-sk>
</template>
<template is="dom-if" if="[[_not(item.high)]]">
<a class=dot title="No clusters found." href="/g/c/[[_hashFrom(tableIndex)]]?query=[[_encQueryFrom(index)]]">[[_display(tableIndex,state.end)]]</a>
</template>
</td>
</template>
<template is="dom-if" if="[[_notBoth(index)]]">
<td></td><!-- Dummy column for colspan. -->
</template>
</template>
</tr>
</template>
</table>
</template>
</dom-module>
<script>
(function () {
var now = Math.floor(Date.now()/1000);
Polymer({
is: "triage-page-sk",
properties: {
state: {
type: Object,
value: function() {
return {
begin: now - 14*24*60*60, // 2 weeks.
end: now,
subset: "untriaged",
filter: "ALL",
categories: [],
};
},
},
_reg: {
type: Object,
value: function() { return {}; },
},
_triageInProgress: {
type: Boolean,
value: false,
},
_currentState: {
type: Object,
value: function() { return {}; },
},
_statusOpen: {
type: Boolean,
value: false,
},
_dialog_state: {
type: Object,
value: function() { return {}; },
}
},
ready: function() {
sk.stateReflector(this, this._updateRange.bind(this));
this._poll();
this._lastBody = {};
},
_poll: function() {
if (this._statusOpen) {
sk.get('/_/reg/current').then(JSON.parse).then(function (json) {
this.set('_currentState', json);
window.setTimeout(this._poll.bind(this), 1000);
}.bind(this)).catch(function() {
window.setTimeout(this._poll.bind(this), 1000);
}.bind(this));
} else {
window.setTimeout(this._poll.bind(this), 1000);
}
},
_openKeys: function(e) {
var query = {
keys: e.detail.shortcut,
begin: e.detail.begin,
end: e.detail.end,
xbaroffset: e.detail.xbar.offset
};
window.open('/e/?' + sk.query.fromObject(query), '_blank');
},
_rangeChange: function(e) {
if (!this.state) {
return
}
this.set('state.begin', e.detail.begin);
this.set('state.end', e.detail.end);
this._updateRange();
},
_updateRange: function() {
if (this.$.spinner.active) {
return
}
this.$.range.begin = this.state.begin;
this.$.range.end = this.state.end;
var body = {
begin: this.state.begin,
end: this.state.end,
subset: this.state.subset,
alert_filter: this.state.filter,
categories: this.state.categories,
};
// We need to protect from too many change events because iron-selector is awful.
if (JSON.stringify(this._lastBody) === JSON.stringify(body)) {
return
}
this.$.spinner.active = true;
this.$.table.classList.toggle("hidden", true);
sk.post("/_/reg/", JSON.stringify(body), "application/json").then(JSON.parse).then(function(json) {
this._lastBody = body;
this.$.spinner.active = false;
this.$.table.classList.toggle("hidden", false);
this.set('_reg', {});
this.set('_reg', json);
}.bind(this)).catch(function(msg) {
if (msg) {
sk.errorMessage(msg, 10000);
}
this.$.spinner.active = false;
}.bind(this));
},
_triaged: function(e) {
e.stopPropagation();
e.detail.alert= this._dialog_state.alert;
e.detail.cluster_type = this._dialog_state.cluster_type;
this.$.dialog.close();
this.$.spinner.active = true;
if (this._triageInProgress === true) {
sk.errorMessage("A triage request is in progress.");
return
}
this._triageInProgress = true;
sk.post("/_/triage/", JSON.stringify(e.detail), "application/json").then(JSON.parse).then(function(json) {
this.$.spinner.active = false;
this._triageInProgress = false;
// Jam the values back into triage, because Polymer.
this._dialog_state.element.set('triage.status', e.detail.triage.status);
this._dialog_state.element.set('triage.message', e.detail.triage.message);
if (json.bug) {
// Open the bug reporting page in a new window.
window.open(json.bug, '_blank');
}
}.bind(this)).catch(function(msg) {
if (msg) {
sk.errorMessage(msg, 10000);
}
this.$.spinner.active = false;
this._triageInProgress = false;
}.bind(this));
},
_triage_start: function(e) {
this._dialog_state = e.detail;
this.$.dialog.open();
},
_encQueryFrom: function(index) {
return encodeURIComponent(this._reg.header[index].query);
},
_hashFrom: function(index) {
return this._reg.table[index].cid.hash;
},
_full_summary: function(frame, summary) {
return {
frame: frame,
summary: summary,
}
},
_alertAt: function(index) {
return this._reg.header[index];
},
_stepUpAt: function(index) {
var dir = this._reg.header[index].direction;
return dir == "UP" || dir == "BOTH";
},
_stepDownAt: function(index) {
var dir = this._reg.header[index].direction;
return dir == "DOWN" || dir == "BOTH";
},
_notBoth: function(index) {
return this._reg.header[index].direction != "BOTH";
},
_not: function(x) {
return !x;
},
_display: function(index) {
if (this.state.end == now && index < sk.perf.radius && this.state.subset == "all") {
return "";
} else {
return "∅";
}
},
_displayName: function(query, display_name) {
if (!display_name) {
return query.slice(0, 10);
} else {
return display_name;
}
},
_close: function() {
this.$.dialog.close();
},
_categoryDisplay: function(s) {
if (s === '') {
return '(default)'
}
return s
},
});
})();
</script>