| <!-- The <digest-details2-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="bower_components/paper-checkbox/paper-checkbox.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-details2-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 img { |
| display: block; |
| max-width: 100%; |
| background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAAXNSR0IArs4c6QAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH3gUBEi4DGRAQYgAAAB1JREFUGNNjfMoAAVJQmokBDdBHgPE/lPFsYN0BABdaAwN6tehMAAAAAElFTkSuQmCC'); |
| } |
| |
| .hidden * { |
| display: none; |
| } |
| |
| .triageInfo, |
| .triageInfo > * { |
| padding: 0.5em; |
| } |
| .untriagedImage svg { |
| margin: auto; |
| } |
| |
| .digestDetailImages { |
| margin-right: 1.5em; |
| } |
| |
| .warning { |
| font-weight: bold; |
| padding: 1em; |
| color: #E7298A; |
| padding-top: 18px; |
| display: block; |
| width: 10em; |
| } |
| |
| #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; |
| } |
| |
| #toggleRefButton, |
| #zoomButton { |
| height: 45px; |
| margin: 5px; |
| } |
| |
| .metricHead { |
| padding: 0 1em; |
| font-weight: bold; |
| } |
| |
| .rotateTitle { |
| height: 28px; |
| } |
| |
| .smallbar { |
| } |
| |
| </style> |
| <div class="vertical layout wrap" hidden$="[[_noData(details)]]"> |
| <!-- Triage Controls --> |
| <div class="horizontal layout triageInfo"> |
| <triage-sk value="{{details.status}}" |
| id="triageControls" |
| data-digest$="[[details.digest]]" |
| data-test$="[[details.test]]"> |
| </triage-sk> |
| <div class="smallbar horizontal center layout"> |
| <div class="metricHead" hidden$="[[embedded]]">[[details.test]]</div> |
| <div class="horizontal layout" 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> |
| <!-- Links to Grid and Cluster. --> |
| <div hidden$="_hideLinks(details, embedded)"><a href$="[[_cmpUrl(details.test)]]"><iron-icon icon="apps"></iron-icon></a></div> |
| <div hidden$="_hideLinks(details, embedded)"><a href$="[[_clusterUrl(details.test)]]"><iron-icon icon="radio-button-unchecked"></iron-icon></a></div> |
| </div> |
| <paper-button id="zoomButton" raised>Zoom</paper-button> |
| <paper-button id="toggleRefButton" raised hidden$="[[_hideRefToggle(_refKeys)]]">Toggle Closest</paper-button> |
| <div class="warning" hidden$="[[!_negIsClosest]]"> |
| Closest Image Is Negative! |
| </div> |
| </div> |
| |
| <div> |
| <paper-checkbox title="Image" id=leftCheckbox checked></paper-checkbox> |
| <paper-checkbox title="Closest" id=rightCheckbox checked></paper-checkbox> |
| <paper-checkbox title="Diff" id=diffCheckbox></paper-checkbox> |
| |
| <!-- Image title --> |
| <!-- left --> |
| <div hidden$="[[_noteq(_rotateImageIndex, 0)]]" class=rotateTitle> |
| <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> |
| |
| <!-- right --> |
| <div hidden$="[[_noteq(_rotateImageIndex, 1)]]" class=rotateTitle> |
| <a href$="[[_detailHref(_right.digest)]]" target="_blank"> |
| [[_rightParamTitle]] |
| </a> |
| <span class="flex self-start testHeader" hidden$="[[!_hasRight(_right)]]">[[_right.digest]]</span> |
| </div> |
| |
| <!-- diff --> |
| <div hidden$="[[_noteq(_rotateImageIndex, 2)]]" class=rotateTitle> |
| Diff |
| </div> |
| </div> |
| |
| <!-- Images --> |
| <div> |
| |
| <!-- left --> |
| <div class="preview" hidden$="[[_noteq(_rotateImageIndex, 0)]]"> |
| <img src$="[[_digestHref(details.digest)]]"> |
| </div> |
| |
| <!-- right --> |
| <div class="preview" hidden$="[[_noteq(_rotateImageIndex, 1)]]"> |
| <img src$="[[_digestHref(_right.digest)]]"> |
| </div> |
| |
| <!-- diff --> |
| <div class="preview" hidden$="[[_noteq(_rotateImageIndex, 2)]]"> |
| <img src$="[[_diffImgHref]]"> |
| </div> |
| </div> |
| |
| |
| |
| <div class="horizontal layout center" hidden$="[[_hideNegPosFound(_right)]]"> |
| <div class="noCompare"> |
| <strong>No Positive or Negative Digests Found.</strong> |
| </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> |
| |
| <!-- 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> |
| </template> |
| <script> |
| Polymer({ |
| is: 'digest-details2-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 []; } |
| }, |
| |
| _rotateImageIndex: { |
| type: Number, |
| value: 0, |
| } |
| }, |
| |
| 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'); |
| |
| this.rotateIDs = ["leftCheckbox", "rightCheckbox", "diffCheckbox"]; |
| |
| setInterval(() => this._showNext(), 500); |
| }, |
| |
| _showNext: function() { |
| let index = this._rotateImageIndex; |
| for (var i = 0; i < this.rotateIDs.length; i++) { |
| index = (index + 1) % this.rotateIDs.length; |
| let id = this.rotateIDs[index]; |
| if (this.querySelector('#' + id).checked) { |
| this._rotateImageIndex = index; |
| break; |
| } |
| } |
| }, |
| |
| 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; |
| if (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; |
| }, |
| |
| _noteq: function(val, expected) { |
| return val !== expected; |
| }, |
| |
| _hideRefToggle: function(refKeys) { |
| return !(refKeys && refKeys.length && refKeys.length > 1); |
| } |
| }); |
| </script> |
| </dom-module> |