blob: 9f14c609fa2a4003f61fc3fd8cec5facf1884429 [file] [log] [blame]
<!-- The <digest-details-sk> custom element declaration.
Displays the details about a digest.
Attributes:
mode: determines behavior of this element. Supported values:
* list: Element is part of a list view. It only considers the contents
of the 'details' field and expects closes digests to be part of
the 'diff' field within 'details'.
* detail: Element is part of a detail view about an individual digest.
Only the contents of 'details' is considered, but different
UI elements are exposed.
* diff: Element is used to show diff of two digests.
'details' attribute contains information about the left digest.
'right' attribute contains information around right digest
and 'diff' contains diff information. Any 'diff' field within
'details' is ignored.
details - Object, a deserialized instance of search.SRDigest.
right - Object, a deserialized instance of search.SRDigest.
diff - Object, a deserialized instance of search.SRDiffDigest.
commits - Array, a list of commits that 'details' refers to.
embedded - Boolean, a flag that indicates that this is embedded as an
auxiliary view and some field like title should be omitted.
issue - Number, the id of the code review issue that generated this digest
via a tryjob run.
Events:
triage - A triage event is generated when the triage button is pressed. The e.detail
of the event looks like:
{
digest: ["ba636123..."],
status: "positive",
test: "blurs"
}
zoom-clicked - This event is triggered when the user clicks on the 'zoom'
button. The 'detail' in the event is the object expected by the
multi-zoom-sk element.
clear - Clears the current digest shown.
Methods:
triggerTriage(status) - Set the triage status of the digest, triggering the triage event.
getZoomDetail: Return the information used by the multi-zoom-sk element to show a zoomed
view of the images related to this digest.
statusClosest: Returns the status of the closest digest as a string or null if there is
no closest digest.
-->
<link rel="import" href="bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="bower_components/paper-button/paper-button.html">
<link rel="import" href="../common/imp/paramset.html">
<link rel="import" href="../common/imp/triage-sk.html">
<link rel="import" href="blame-sk.html">
<link rel="import" href="dots-sk.html">
<link rel="import" href="dot-legend-sk.html">
<link rel="import" href="purge-sk.html">
<link rel="import" href="shared-styles.html">
<dom-module id="digest-details-sk">
<template>
<style include="iron-flex iron-flex-alignment shared-styles">
circle.status0 {
fill: #000000;
stroke: #000000;
}
dot-legend-sk {
margin-left: 5em;
margin-bottom: 2em;
}
dots-sk {
display: block;
}
.more {
margin-left: 3em;
}
.preview {
margin: 5px;
border: solid 2px lightgray;
display: block;
width: 132px;
height: 132px;
}
.preview img {
display: block;
max-width: 128px;
max-height: 128px;
width: auto;
height: auto;
}
.hidden * {
display: none;
}
.triageInfo,
.triageInfo div {
padding: 0.5em;
}
.untriagedImage svg {
margin: auto;
}
.digestDetailImages {
margin-right: 1.5em;
}
.warning {
font-weight: bold;
width: 5em;
padding: 1em;
color: #E7298A;
}
#paramsets {
max-width: 40em;
padding-left: 1em;
padding-right: 1em;
}
.noCompare {
padding-right: 1em;
}
.dotInfo {
display: block;
border: 1px solid #eeeeee;
margin-top: 1em;
margin-bottom: 1em;
margin-left: 1em;
padding: 1em;
}
.leftCol {
margin-right: 3em;
}
.testHeader {
font-size: 16px;
font-weight: bold;
margin-left: 0.5em;
margin-bottom: 0.5em;
white-space: nowrap;
}
#zoomButton {
margin-top: .5em;
}
.metricHead {
min-width: 12em;
}
</style>
<div class="horizontal layout wrap" hidden$="[[_noData(details)]]">
<div class="vertical layout leftCol">
<div class="horizontal layout">
<div class="flex self-start testHeader" hidden$="[[embedded]]">Test: [[details.test]]</div>
<!-- Links to Grid and Cluster. -->
<div id="links" hidden$="_hideLinks(details, embedded)">
<span><a href$="[[_cmpUrl(details.test)]]"><iron-icon icon="apps"></iron-icon></a></span>
<span><a href$="[[_clusterUrl(details.test)]]"><iron-icon icon="radio-button-unchecked"></iron-icon></a></span>
</div>
</div>
<div class="horizontal layout">
<div class="flex self-start testHeader">Left: [[details.digest]]</div>
<div class="flex self-start testHeader" hidden$="[[!_hasRight(_right)]]">Right: [[_right.digest]]</div>
</div>
<div class="horizontal layout">
<!-- Triage Controls -->
<div class="vertical layout triageInfo">
<div hidden$="[[!_hasRight(_right)]]">
<div>
<a href$="[[_diffPageUrl(details, _right)]]">Diff Details</a>
</div>
<div><span class="metricHead">Diff Metric:</span> <span>[[_fixedMetric(_diff, metric)]]</span></div>
<div><span class="metricHead">Diff %:</span> <span>[[_fixedPercent(_diff)]]</span></div>
<div><span class="metricHead">Pixels:</span> [[_diff.numDiffPixels]]</div>
<div><span class="metricHead">Max RGBA:</span> [<span>[[_diff.maxRGBADiffs]]</span>]</div>
</div>
<triage-sk value="{{details.status}}"
id="triageControls"
data-digest$="[[details.digest]]"
data-test$="[[details.test]]">
</triage-sk>
</div>
<!-- Images -->
<div class="vertical layout">
<div class="horizontal layout digestDetailImages">
<div class="vertical layout untriagedImage">
<div class="horizontal layout">
<div class="preview">
<img src$="[[_digestHref(details.digest)]]">
</div>
<div class="vertical layout justified">
<a target="_blank" href$="[[_digestHref(details.digest)]]" rel="noopener">
<iron-icon icon="open-in-new"></iron-icon>
</a>
</div>
</div>
<div>
<span hidden$="[[_hideDotsBlame(details)]]">
<svg width="10" height="10" viewBox="-1 -1 2 2">
<circle cx="0" cy="0" r="0.3" class="status0"></circle>
</svg>
</span>
<a href$="[[_detailHref(details.digest)]]" target="_blank">
[[_leftParamTitle]]
</a>
</div>
</div>
<div class="horizontal layout center" hidden$="[[_hideNegPosFound(_right)]]">
<div class="noCompare">
<strong>No Positive or Negative Digests Found.</strong>
</div>
</div>
<div class="horizontal layout" hidden$="[[!_hasRight(_right)]]">
<!-- diff image -->
<div class="preview">
<img src$="[[_diffImgHref]]">
</div>
<a href$="[[_diffImgHref]]" target="_blank" >
<iron-icon icon="open-in-new"></iron-icon>
</a>
<!-- closest image -->
<div class="vertical layout center untriagedImage">
<div class="horizontal layout">
<div class="preview">
<img src$="[[_digestHref(_right.digest)]]">
</div>
<a href$="[[_digestHref(_right.digest)]]" target="_blank">
<iron-icon icon="open-in-new"></iron-icon>
</a>
</div>
<a href$="[[_detailHref(_right.digest)]]" target="_blank">
[[_rightParamTitle]]
</a>
</div>
</div>
</div>
<paper-button id="zoomButton" raised>Zoom</paper-button>
</div>
<div class="vertical layout">
<paper-button id="toggleRefButton" raised hidden$="[[_hideRefToggle(_refKeys)]]">Toggle Closest</paper-button>
<div class="warning" hidden$="[[!_negIsClosest]]">
Closest Image Is Negative!
</div>
</div>
</div>
<!-- dots, dot-legend and blame -->
<div class="vertical layout dotInfo" hidden$="[[_hideDotsBlame(details)]]">
<dots-sk id="dots"></dots-sk>
<dot-legend-sk id="dotlegend"
test="{{details.test}}"
digests={{details.traces.digests}}>
</dot-legend-sk>
<blame-sk id="blame"
commits="{{commits}}"
value="{{details.diff.blame}}">
</blame-sk>
</div>
</div>
<!-- ParamSet -->
<div class="vertical layout" id="paramset">
<paramset-sk id="paramsets"></paramset-sk>
<template is="dom-if" if="[[_eq(mode, 'detail')]]">
<purge-sk digest="[[details.digest]]"></purge-sk>
</template>
</div>
</div>
</template>
<script>
Polymer({
is: 'digest-details-sk',
properties: {
mode: {
type: String,
value: "list",
},
details: {
type: Object,
value: function() { return {}; },
},
right: {
type: Object,
value: null
},
diff: {
type: Object,
value: null
},
commits: {
type: Array,
value: function() { return []; },
},
embedded: {
type: Boolean,
value: false
},
metric: {
type: String,
},
issue: {
type: Number,
value: 0,
},
_negIsClosest: {
type: Boolean,
value: false
},
_right: {
type: Object,
value: null
},
_diff: {
type: Object,
value: null
},
_refKeys: {
type: Array,
value: function() { return []; }
}
},
observers: [
'_changedInput(details, commits, right, diff)'
],
ready: function () {
this.listen(this.$.zoomButton, 'click', '_zoomHandler');
this.listen(this.$.toggleRefButton, 'tap', '_toggleRefHandler');
this.listen(this.$.dots, 'hover', '_hoverHandler');
this.listen(this.$.dots, 'mouseleave', '_mouseLeaveHandler');
this.listen(this.$.triageControls, 'change', '_triageChangeHandler');
this.listen(this.$.blame, 'hover-blame', '_blameHoverHandler');
},
triggerTriage: function (status) {
this.set('$.triageControls.value', status);
var detail = new gold.TriageQuery(this.$.triageControls.dataset.test,
this.$.triageControls.dataset.digest,
status, this.issue);
this.fire('triage', detail);
},
getZoomDetail: function() {
return {
leftImgUrl: this._digestHref(this.details.digest),
rightImgUrl: this._digestHref(sk.robust_get(this._right, ['digest'])),
middleImgUrl: this._diffImgHref,
llabel: this._leftParamTitle,
rlabel: this._rightParamTitle
};
},
clear: function() {
this.set('details', {});
this.set('right', null);
this.set('diff', null);
},
_triageChangeHandler: function(e) {
// Convert the change event from the triage button into a more detailed triage event.
var detail = new gold.TriageQuery(this.details.test,
this.details.digest,
this.details.status,
this.issue);
this.fire('triage', detail);
},
_zoomHandler: function() {
this.fire('zoom-clicked', this.getZoomDetail());
},
_toggleRefHandler: function() {
this._refIdx = (this._refIdx+1) % this._refKeys.length;
this.details.closestRef = this._refKeys[this._refIdx];
this._setRefDiff();
this._setProperties();
},
_hoverHandler: function(e) {
var id = e.detail;
var params = {};
var traces = this.details.traces.traces;
// Find the matching trace in details.traces.
for (var i=0, len = traces.length; i < len; i++) {
if (traces[i].label == id) {
params = traces[i].params;
break;
}
}
this.$.paramsets.setHighlight(params);
},
_blameHoverHandler: function(ev) {
this.$.dots.highlight(ev.detail.index, ev.detail.b);
},
_mouseLeaveHandler: function() {
this.$.paramsets.clearHighlight();
},
_changedInput: function(details, commits, right, diff) {
// Check if we have the most basic data.
if (!this.details || !this.details.digest) {
return;
}
// Set the data according to the mode.
switch(this.mode) {
case 'list':
this._setRightFromDetails(false);
break;
case 'detail':
this._setRightFromDetails(true);
break;
case 'diff':
this._right = this.right;
this._diff = this.right;
this._rightParamTitle = this._abbrev(this._right.digest);
break;
default:
console.log("Unknow mode set for digest-detail-sk element.");
return
}
this._setProperties();
},
_setRightFromDetails: function(detailsView) {
this._negIsClosest = false;
this._right = null;
this._diff = null;
this._rightParamTitle = '';
// Set the reference image returned by the server.
var hasRef = this._setRefDiff();
if (hasRef) {
// Get all the reference images that are availalble.
var refKeys = [];
if (this.details.refDiffs) {
for(var k in this.details.refDiffs) {
if (this.details.refDiffs.hasOwnProperty(k)) {
refKeys.push(k);
}
}
}
this._refIdx = refKeys.indexOf(this.details.closestRef);
this.set("_refKeys", refKeys);
}
// Set the blame, dots, dotlegend
this.$.dots.setCommits(this.commits);
this.$.dots.setValue(this.details.traces);
},
statusClosest: function() {
return (this._right) ? ((this._negIsClosest) ? gold.NEGATIVE : gold.POSITIVE) : null;
},
// _setRefDiff sets the reference image and returns true if there is one.
_setRefDiff: function() {
if (this.details.closestRef && this.details.closestRef !== '') {
this._negIsClosest = (this.details.closestRef === gold.REF_NEG);
this._right = this.details.refDiffs[this.details.closestRef];
this._diff = this._right;
this._rightParamTitle = 'Closest ' + this._statusStr(this._negIsClosest);
return true;
}
return false;
},
_digestFromDiff: function(test, refDiff, status) {
return {
test: test,
digest: refDiff.digest,
status: status,
paramset: diff.paramset
};
},
_setProperties: function() {
var paramSets = [];
var paramTitles = [];
this._leftParamTitle = this._abbrev(this.details.digest);
if (this.details.paramset) {
paramSets.push(this.details.paramset);
paramTitles.push(this._leftParamTitle);
}
if (this._right) {
this.set('_diffImgHref', gold.diffImgHref(this._right.digest, this.details.digest));
// TODO(stephana): Fix this on the backend to make sure we never get an empty
// set of parameters. Currently it can occur on occasion.
if (this._right.paramset) {
paramSets.push(this._right.paramset);
paramTitles.push(this._rightParamTitle);
}
}
// TODO(stephana): Remove the forced re-render of _diff by cleaning up how
// the properties are set outside this function.
if (this._diff) {
this.set('_diff', this._diff);
}
if (paramSets.length > 0) {
this.$.paramsets.setParamSets(paramSets, paramTitles);
}
},
_abbrev: function(str) {
if (str.length <= 12) {
return str;
}
return str.substr(0, 12) + '...';
},
_noData: function(details) {
return !(details && details.test && details.digest);
},
_concatOrdered: function(d1, d2) {
},
_hasRight: function(right) {
return (right && right.digest !== '');
},
_digestHref: gold.imgHref,
_fixedPercent: function (refDiff) {
return (refDiff) ? refDiff.pixelDiffPercent.toFixed(2) : '';
},
_fixedMetric: function (refDiff, metric) {
return (refDiff && metric) ? refDiff.diffs[metric].toFixed(2) : '';
},
_diffPageUrl: function (left, right) {
if (!left || !right) {
return "";
}
return '/diff' + gold.diffQuery(left.test, left.digest, right.digest);
},
_detailHref: function (digest) {
if (!digest) {
return '';
}
return '/detail' + gold.detailQuery(this.details.test, digest);
},
_hideNegPosFound: function(closest) {
return closest || (this.mode !== 'list');
},
_statusStr: function(_negIsClosest) {
return _negIsClosest ? 'Negative' : 'Positive';
},
_hideLinks: function(details, isEmbedded) {
return isEmbedded || !details.paramset || !detail.traces;
},
_cmpUrl: function (test) {
if (!test) {
return '';
}
// See gold.defaultSearchState why we need to embed an encoded query
// before encoding the search string.
return 'cmp?' + sk.query.fromObject({query: this._getRefQuery(test)});
},
_hideListFeatures: function(mode) {
return mode != 'list';
},
_hideDotsBlame: function(details) {
return (!details || !details.traces || !details.traces.traces);
},
_clusterUrl: function (test) {
if (!test) {
return '';
}
var q = {
query: this._getRefQuery(test),
head: true,
pos: true,
neg: true,
unt: true,
limit: 200
};
return 'cluster?' + sk.query.fromObject(q);
},
_getRefQuery: function(test) {
var qObj = {name: [test]};
var corpus = this.details && this.details.paramset && this.details.paramset['source_type'];
if (corpus) {
qObj['source_type'] = corpus;
}
return sk.query.fromParamSet(qObj);
},
_eq: function(val, expected) {
return val === expected;
},
_hideRefToggle: function(refKeys) {
return !(refKeys && refKeys.length && refKeys.length > 1);
}
});
</script>
</dom-module>