| <!-- |
| 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="digest-details-sk.html"> |
| <link rel=import href="search-controls-sk.html"> |
| <link rel=import href="trybot-controls-sk.html"> |
| <link rel="import" href="../common/imp/triage-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-top: 1em; |
| margin-bottom: 1em; |
| margin-left: 0; |
| margin-right: 0; |
| padding-left: 1em; |
| padding-top: 1em; |
| padding-bottom: 1em; |
| padding-right: 0; |
| } |
| |
| digest-details-sk[data-focus] { |
| box-shadow: 3px 3px 6px 5px #FF7F00; |
| } |
| |
| #missing { |
| padding-top: 2em; |
| } |
| |
| .searchPageWrapper { |
| padding-left: 1em; |
| padding-top: 1em; |
| margin-left:1em; |
| margin-right: 3em; |
| } |
| |
| #triggerSearchButton { |
| font-size: 18px; |
| } |
| |
| .helpIconContainer { |
| padding-left: 3em; |
| } |
| |
| paper-button { |
| min-width: 2em; |
| margin: 5px; |
| background: #eee; |
| } |
| |
| .actionMenu { |
| max-width: 10em; |
| margin-left: 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; |
| } |
| </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> |
| <div> |
| <paper-button id="triggerSearchButton" raised disabled="[[_hideAll]]">Search</paper-button> |
| </div> |
| |
| <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.size]] of [[_allData.digests.length]] images starting at [[_allData.offset]]<br> |
| UniqueTests Visible: [[_uniqueTests.length]] <br> |
| UniqueTests Total: [[_uniqueTestsAll.length]]<br> |
| </div> |
| <trybot-controls-sk id="trybotControls"></trybot-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" |
| triage |
| details="[[item]]" |
| commits="[[data.commits]]" |
| metric="[[_state.metric]]" |
| issue="[[data.issue.id]]"> |
| </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 value="{{_bulkStatus}}" 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" |
| }, |
| _triageAll: { |
| type: Boolean, |
| value: false |
| } |
| }, |
| |
| ready: function() { |
| this.listen(this.$.searchControls, 'state-change', '_handleStateChange'); |
| this.listen(this.$.trybotControls, 'state-change', '_handleStateChange'); |
| this.listen(this.$.trybotControls, 'state-update', '_handleStateUpdate'); |
| this.listen(this.$.triggerSearchButton, 'tap', '_handleSearchButton'); |
| this.listen(this.$.actionTestView, 'tap', '_handleSwitchToByTest'); |
| this.listen(this.$.actionBulkTriage, 'tap', '_handleBulkButton'); |
| 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.$.detailList.startUse(); |
| |
| // Initialize the state and set values of the controls. |
| this._initState(ctx, this._getDefaultStateWithCorpus()); |
| this.$.searchControls.setState(this._state); |
| this.$.trybotControls.setState(this._state); |
| |
| // load the commits, parameters and data. |
| this._loadCommits(); |
| this._loadParamset(); |
| this._load(); |
| }, |
| |
| pageDeselected: function() { |
| this.$.detailList.endUse(); |
| }, |
| |
| _handleSearchButton: function(ev) { |
| // Reload the page with the current state of the controls. |
| this._redirectToState(this.$.searchControls.state); |
| }, |
| |
| _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); |
| this.$.trybotControls.setState(this._state); |
| }, |
| |
| _handleSwitchToByTest: function() { |
| this._redirectToState({}, "/list"); |
| }, |
| |
| _openHelpDialog: function(ev) { |
| this.$.detailList.openHelpDialog(); |
| }, |
| |
| _handleBulkButton: function(ev) { |
| this.set("_bulkStatus", ""); |
| 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", '='); |
| }, |
| |
| _handleBulkStatusChanged: function(newVal) { |
| if (newVal === '=') { |
| Polymer.dom(this.$.closestButton).removeAttribute('raised'); |
| } 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.$.triageControls.value; |
| 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(sk.robust_get(this._issue, ["id"])); |
| 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; |
| 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); |
| displayData.digests = displayData.digests.slice(json.offset, json.offset+json.size); |
| this.set('_allData', json); |
| |
| this.set('_uniqueTestsAll', this._getUniqueTests(json.digests)); |
| this.set('_uniqueTests', this._getUniqueTests(displayData.digests)); |
| |
| this.set('data', displayData); |
| this.set('_issue', json.issue); |
| // TODO(kjlubick): update this to handle the .changelist |
| // returned by the new clstore/tjstore. |
| // Maybe fork the current trybot controls or make a new one in lit-html |
| this.$.trybotControls.setIssue(json.issue); |
| this.$.activityBar.stopSpinner(); |
| }.bind(this)).catch(function(e) { |
| this.$.activityBar.stopSpinner(); |
| sk.errorMessage(e); |
| }.bind(this)); |
| }, |
| |
| _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() { |
| sk.get("/json/paramset").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> |