| <!-- |
| The <search-page-sk> custom element declaration. |
| |
| Shows the results of a search request. |
| It sends the query string as a JSON request to the |
| search entpoint ('/json/search') and renders the result. |
| |
| It assumes to the be part of a client site routed system |
| of views and therefore offers the 'pageSelected' and 'pageUnselected' |
| functions. These need to be called whenever the page goes |
| in and out of view. |
| |
| Attributes: |
| None |
| |
| Methods: |
| pageSelected(ctx) - Called by the router when the view becomes visible. |
| ctx is the context provided in the route dispatch of page.js. |
| |
| pageDeselected - Called by the router when the view is no longer visible. |
| |
| Events: |
| None |
| |
| Mailboxes: |
| None |
| --> |
| |
| <link rel="import" href="bower_components/polymer/polymer.html"> |
| <link rel="import" href="bower_components/iron-flex-layout/iron-flex-layout-classes.html"> |
| <link rel="import" href="bower_components/iron-icons/iron-icons.html"> |
| <link rel="import" href="bower_components/paper-dialog/paper-dialog.html"> |
| <link rel="import" href="bower_components/paper-button/paper-button.html"> |
| <link rel="import" href="bower_components/paper-menu-button/paper-menu-button.html"> |
| <link rel="import" href="bower_components/paper-toggle-button/paper-toggle-button.html"> |
| |
| <link rel="import" href="activity-sk.html"> |
| <link rel=import href="detail-list-sk.html"> |
| <link rel=import href="search-controls-sk.html"> |
| |
| <dom-module id="search-page-sk"> |
| <template> |
| <style include="iron-flex iron-flex-alignment"></style> |
| <style include="shared-styles"> |
| digest-details-sk { |
| display: block; |
| box-shadow: 3px 3px 6px 1px rgba(133,133,133,1); |
| margin-bottom: 1em; |
| padding: 0.5em; |
| } |
| |
| digest-details-sk[data-focus] { |
| box-shadow: 3px 3px 6px 5px #FF7F00; |
| } |
| |
| #missing { |
| padding-top: 2em; |
| } |
| |
| .searchPageWrapper { |
| padding: 5px; |
| } |
| |
| paper-button { |
| min-width: 2em; |
| margin: 5px; |
| background: #eee; |
| } |
| |
| .actionMenu { |
| max-width: 10em; |
| } |
| |
| .actionButton { |
| color: green; |
| } |
| |
| #closestButton[raised] > iron-icon { |
| color: #bbb; |
| } |
| |
| #closestButton[raised] { |
| background: #fff; |
| } |
| |
| #closestButton:hover, |
| #closestButton[raised]:hover { |
| background: #ddd; |
| } |
| |
| .searchResponse { |
| padding: 1em 1em 0 1em; |
| margin: 1em 1em 1em 0; |
| border: 1px solid #555555; |
| } |
| |
| .searchStats { |
| padding-bottom: 1em; |
| } |
| |
| #searchControls { |
| max-width: 100%; |
| } |
| </style> |
| |
| <div class="searchPageWrapper layout vertical"> |
| <activity-sk id="activityBar" busy="{{_hideAll}}"></activity-sk> |
| <div class="layout horizontal"> |
| <search-controls-sk id="searchControls" beta></search-controls-sk> |
| |
| <paper-menu-button close-on-activate no-animations class="actionMenu"> |
| <paper-button class="dropdown-trigger actionButton" raised>Actions</paper-button> |
| <paper-menu class="dropdown-content"> |
| <paper-item id="actionTestView">Switch To Test View</paper-item> |
| <paper-item id="actionBulkTriage">Bulk Triage</paper-item> |
| <paper-item id="actionExport">Export</paper-item> |
| <paper-item id="actionLegacySearch">Legacy Search</paper-item> |
| <paper-item id="actionHelpDialog">Help</paper-item> |
| </paper-menu> |
| </paper-menu-button> |
| </div> |
| |
| <div class="searchResponse" hidden$="[[_noResult(_allData)]]"> |
| <div class="searchStats"> |
| Displaying: [[_allData.digests.length]] of [[_allData.size]] images starting at [[_allData.offset]]<br> |
| UniqueTests Visible: [[_uniqueTests.length]] <br> |
| UniqueTests Total: [[_uniqueTestsAll.length]]<br> |
| </div> |
| <changelist-controls-sk id="changeListControls" |
| ps_order=[[_state.patchsets]] include_master=[[_state.master]]></changelist-controls-sk> |
| </div> |
| <div hidden$="[[_hideAll]]"> |
| <div id="missing" hidden$="[[_nonEmptyResult(data)]]"> |
| No digests match your query. |
| </div> |
| <div hidden$="[[_emptyResult(data)]]"> |
| <detail-list-sk id="detailList"> |
| <template is="dom-repeat" items="[[data.digests]]"> |
| <digest-details-sk |
| id$="[[_entryId(item)]]" |
| mode="list" |
| details="[[item]]" |
| commits="[[data.commits]]" |
| metric="[[_state.metric]]" |
| issue="[[_changeListID]]"> |
| </digest-details-sk> |
| </template> |
| </detail-list-sk> |
| </div> |
| </div> |
| </div> |
| <paper-dialog id="bulkDialog" with-backdrop> |
| <h2>Bulk Triage</h2> |
| <p>Assign the status to all images on this page at once.</p> |
| <div class="layout horizontal"> |
| <triage-sk id="triageControls"></triage-sk> |
| <paper-button id="closestButton" on-tap="_handleClosestButton" title="Assign status of closest image"> |
| <iron-icon icon="icons:view-agenda"></iron-icon> |
| </paper-button> |
| </div> |
| <div hidden$="[[_noMore(_allData)]]"> |
| <paper-toggle-button checked="{{_triageAll}}">Triage all [[_getBulkTriageCount(_allData.digests, _bulkStatus)]].</paper-toggle-button> |
| </div> |
| <div class="buttons"> |
| <paper-button raise dialog-dismiss>Cancel</paper-button> |
| <paper-button raise dialog-dismiss on-tap="_doBulkTriage" disabled="[[!_bulkStatus]]">Triage</paper-button> |
| </div> |
| </paper-dialog> |
| |
| </template> |
| <script> |
| Polymer({ |
| is: "search-page-sk", |
| |
| behaviors: [gold.PageStateBehavior], |
| |
| properties: { |
| data: { |
| type: Object, |
| value: function() { return {}; } |
| }, |
| _bulkStatus: { |
| type: String, |
| value: "", |
| observer: "_handleBulkStatusChanged" |
| }, |
| _changeListID: { |
| type: String, |
| value: "", |
| }, |
| _triageAll: { |
| type: Boolean, |
| value: false |
| } |
| }, |
| |
| ready: function() { |
| this.listen(this.$.searchControls, 'state-change', '_handleStateChange'); |
| this.listen(this.$.searchControls, 'query-dialog-open', '_disableDigestListKeybindings'); |
| this.listen(this.$.searchControls, 'query-dialog-close', '_enableDigestListKeybindings'); |
| |
| this.$.changeListControls.addEventListener('cl-control-change', (e) => { |
| this._state.master = e.detail.include_master; |
| this._state.patchsets = e.detail.ps_order; |
| this._redirectToState(this._state); |
| }); |
| this.listen(this.$.actionTestView, 'tap', '_handleSwitchToByTest'); |
| this.listen(this.$.actionBulkTriage, 'tap', '_handleBulkButton'); |
| this.$.triageControls.addEventListener('change', (e) => { |
| this.set('_bulkStatus', e.detail); |
| }); |
| this.listen(this.$.actionExport, 'tap', '_handleExport'); |
| this.listen(this.$.actionLegacySearch, 'tap', '_handleLegacySearchButton'); |
| this.listen(this.$.actionHelpDialog, 'tap', '_openHelpDialog'); |
| this._setDefaultState(gold.defaultSearchState, false); |
| }, |
| |
| pageSelected: function(ctx) { |
| this.set('data', {}); |
| this.set('_allData', {}); |
| this._enableDigestListKeybindings(); |
| |
| // Initialize the state and set values of the controls. |
| this._initState(ctx, this._getDefaultStateWithCorpus()); |
| this.$.searchControls.setState(this._state); |
| this.$.changeListControls.ps_order = +this._state.patchsets; |
| this.$.changeListControls.include_master = this._state.master; |
| |
| // load the commits, parameters and data. |
| this._loadCommits(); |
| this._loadParamset(); |
| this._load(); |
| }, |
| |
| pageDeselected: function() { |
| this._disableDigestListKeybindings(); |
| }, |
| |
| _disableDigestListKeybindings: function() { |
| this.$.detailList.endUse(); |
| }, |
| |
| _enableDigestListKeybindings: function() { |
| this.$.detailList.startUse(); |
| }, |
| |
| _handleStateChange: function(ev) { |
| // Reload the page with the new state in the query parameters. |
| this._redirectToState(ev.detail); |
| }, |
| |
| _handleStateUpdate: function(ev) { |
| // Update the state and the URL and then distribute the new state to |
| // the control elements. |
| this._replaceState(ev.detail); |
| this.$.searchControls.setState(this._state); |
| }, |
| |
| _handleSwitchToByTest: function() { |
| this._redirectToState({}, "/list"); |
| }, |
| |
| _openHelpDialog: function(ev) { |
| this.$.detailList.openHelpDialog(); |
| }, |
| |
| _handleBulkButton: function(ev) { |
| this.set("_bulkStatus", ""); |
| this.$.triageControls.value = ''; |
| this.set("_triageAll", false); |
| this.$.bulkDialog.open(); |
| }, |
| |
| _handleExport: function(ev) { |
| var url = this._getRedirectURL({}, "/json/export"); |
| window.open(url, "_blank"); |
| }, |
| |
| _handleLegacySearchButton: function(ev) { |
| this._redirectToState({}, "/legacysearch"); |
| }, |
| |
| _handleClosestButton: function(ev) { |
| this.set("_bulkStatus", '='); |
| this.$.triageControls.value = ''; |
| }, |
| |
| _handleBulkStatusChanged: function(newVal) { |
| if (newVal === '=') { |
| Polymer.dom(this.$.closestButton).removeAttribute('raised'); |
| this.$.triageControls.value = ''; |
| } else { |
| Polymer.dom(this.$.closestButton).setAttribute('raised', ''); |
| } |
| }, |
| |
| // _statusClosest returns label based on whether the digest is closest to |
| // a positive or negative image. |
| _statusClosest:function(digest) { |
| if (digest.closestRef === gold.REF_NEG) { |
| return gold.NEGATIVE; |
| } |
| return gold.POSITIVE; |
| }, |
| |
| // _getBulkTriageCount returns the number of digests that will be triaged |
| // in a bulk triage operation. |
| _getBulkTriageCount: function(digests, bulkStatus) { |
| if (bulkStatus !== '=') { |
| return digests.length; |
| } |
| |
| // If we compare to the closest we only show the number of digests |
| // that actually have a reference image. |
| var count = 0; |
| for(var i=0; i < digests.length; i++) { |
| if (digests[i].closestRef !== '') { |
| count++; |
| } |
| } |
| return count; |
| }, |
| |
| _doBulkTriage: function(ev) { |
| var targetStatus = this.get('_bulkStatus'); |
| var digests = (this._triageAll) ? this._allData.digests : this.data.digests; |
| var triageList = []; |
| if (targetStatus === '=') { |
| for(var i=0; i < digests.length; i++) { |
| if (digests[i].closestRef !== '') { |
| var status = this._statusClosest(digests[i]); |
| triageList.push([digests[i].test, digests[i].digest, status]); |
| } |
| } |
| } else { |
| for(var i=0; i < digests.length; i++) { |
| triageList.push([digests[i].test, digests[i].digest, targetStatus]); |
| } |
| } |
| var query = new gold.TriageQuery(triageList); |
| query.setIssue(this._changeListID); |
| this.$.activityBar.startSpinner("Triaging ..."); |
| sk.post('/json/triage', JSON.stringify(query)).then(function() { |
| this.$.activityBar.stopSpinner(); |
| }.bind(this)).catch(function(e) { |
| this.$.activityBar.stopSpinner(); |
| sk.errorMessage(e); |
| }.bind(this)); |
| }, |
| |
| _load: function() { |
| this.$.activityBar.startSpinner("Loading ..."); |
| |
| var q = window.location.search; |
| // Maybe if we detect that issue is set, do the query to |
| // /json/changelist/gerrit/1786867 for the issue data. |
| sk.get("/json/search" + q).then(JSON.parse).then(function (json) { |
| // Split out the digests that are being displayed and keep a copy |
| // of all digests that matched the query. |
| var displayData = sk.object.shallowCopy(json); |
| this.set('_allData', json); |
| |
| this.set('_uniqueTestsAll', this._getUniqueTests(json.digests)); |
| this.set('_uniqueTests', this._getUniqueTests(displayData.digests)); |
| |
| this.set('data', displayData); |
| this.$.activityBar.stopSpinner(); |
| }.bind(this)).catch(function(e) { |
| this.$.activityBar.stopSpinner(); |
| sk.errorMessage(e); |
| }.bind(this)); |
| |
| if (this._state.issue) { |
| sk.get("/json/changelist/gerrit/"+this._state.issue).then(JSON.parse).then((json) => { |
| this.$.changeListControls.setSummary(json); |
| this.set('_changeListID', json.cl.id); |
| }); |
| } |
| }, |
| |
| _getUniqueTests: function(digests) { |
| var unique = {}; |
| for(var i=0; i < digests.length; i++) { |
| unique[digests[i].test] = true; |
| } |
| return Object.keys(unique); |
| }, |
| |
| _loadCommits: function() { |
| sk.get("/json/commits").then(JSON.parse).then(function (json) { |
| this.$.searchControls.setCommitInfo(json); |
| this._commits = json; |
| }.bind(this)).catch(sk.errorMessage); |
| }, |
| |
| _loadParamset: function() { |
| let url = '/json/paramset'; |
| if (this._state.issue) { |
| url += '?changelist_id='+this._state.issue; |
| } |
| sk.get(url).then(JSON.parse).then(function (json) { |
| this.$.searchControls.setParamSet(json); |
| this._paramSet = json; |
| }.bind(this)).catch(sk.errorMessage); |
| }, |
| |
| _entryId: function(item) { |
| // Make sure it's a valid selector. |
| return (item.test + '_' + item.digest).replace(/[^_A-Za-z0-9]/g, '_'); |
| }, |
| |
| // Returns true if there was a query and result is not empty. |
| _nonEmptyResult: function(data) { |
| return !data.digests || data.digests.length > 0; |
| }, |
| |
| // Returns true if there was a query and the result is empty. |
| _emptyResult: function(data) { |
| return !data.digests || data.digests.length === 0; |
| }, |
| |
| _noResult: function(data) { |
| return !data.digests; |
| }, |
| |
| _noMore: function(allData) { |
| return (allData.digests && allData.digests.length <= allData.size); |
| } |
| }); |
| </script> |
| </dom-module> |