| <!-- |
| 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 endpoint ('/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"> |
| |
| <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; |
| } |
| |
| .searchResponse { |
| padding: 1em 1em 0 1em; |
| margin: 1em 1em 1em 0; |
| border: 1px solid #555555; |
| } |
| |
| .searchStats { |
| padding-bottom: 1em; |
| } |
| |
| #searchControls { |
| flex-grow: 1; |
| } |
| </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> |
| <dialog id="bulkDialog"> |
| <bulk-triage-sk id="bulkTriage"></bulk-triage-sk> |
| </dialog> |
| |
| </template> |
| <script> |
| Polymer({ |
| is: "search-page-sk", |
| |
| behaviors: [gold.PageStateBehavior], |
| |
| properties: { |
| data: { |
| type: Object, |
| value: function() { return {}; } |
| }, |
| _changeListID: { |
| type: String, |
| value: "", |
| }, |
| }, |
| |
| ready: function() { |
| this.listen(this.$.searchControls, 'search-controls-sk-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', '_showBulkTriageDialog'); |
| this.listen(this.$.bulkTriage, 'bulk_triage_cancelled', '_hideBulkTriageDialog'); |
| this.listen(this.$.bulkTriage, 'bulk_triage_invoked', '_startBulkTriage'); |
| this.listen(this.$.bulkTriage, 'bulk_triage_finished', '_endBulkTriage'); |
| 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()); |
| |
| // Extract corpus from query and remove from it. |
| const queryParamSet = sk.query.toParamSet(this._state.query); |
| const corpus = queryParamSet.source_type[0]; |
| delete queryParamSet["source_type"]; |
| |
| // Set search-control-sk's search criteria. |
| const searchCriteria = { |
| corpus: corpus, |
| leftHandTraceFilter: queryParamSet, |
| rightHandTraceFilter: sk.query.toParamSet(this._state.rquery), |
| includePositiveDigests: this._state.pos, |
| includeNegativeDigests: this._state.neg, |
| includeUntriagedDigests: this._state.unt, |
| includeDigestsNotAtHead: !this._state.head, |
| includeIgnoredDigests: this._state.include, |
| minRGBADelta: this._state.frgbamin, |
| maxRGBADelta: this._state.frgbamax, |
| mustHaveReferenceImage: this._state.fref, |
| sortOrder: this._state.sort === 'asc' ? 'ascending' : 'descending' |
| }; |
| this.$.searchControls.searchCriteria = searchCriteria; |
| |
| 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) { |
| const searchCriteria = ev.detail; |
| |
| // Make a copy of the current state. |
| const newState = JSON.parse(JSON.stringify(this._state)); |
| |
| // Set query fields. |
| const queryParamSet = searchCriteria.leftHandTraceFilter; |
| queryParamSet.source_type = [searchCriteria.corpus]; |
| newState.query = sk.query.fromParamSet(queryParamSet); |
| newState.rquery = sk.query.fromParamSet(searchCriteria.rightHandTraceFilter); |
| |
| // Set remaining fields. |
| newState.pos = searchCriteria.includePositiveDigests; |
| newState.neg = searchCriteria.includeNegativeDigests; |
| newState.unt = searchCriteria.includeUntriagedDigests; |
| newState.head = !searchCriteria.includeDigestsNotAtHead; |
| newState.include = searchCriteria.includeIgnoredDigests; |
| newState.frgbamin = searchCriteria.minRGBADelta; |
| newState.frgbamax = searchCriteria.maxRGBADelta; |
| newState.fref = searchCriteria.mustHaveReferenceImage; |
| newState.sort = searchCriteria.sortOrder === 'ascending' ? 'asc' : 'desc'; |
| |
| // Reload the page with the new state in the query parameters. |
| this._redirectToState(newState); |
| }, |
| |
| _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(); |
| }, |
| |
| _handleExport: function(ev) { |
| var url = this._getRedirectURL({}, "/json/export"); |
| window.open(url, "_blank"); |
| }, |
| |
| _handleLegacySearchButton: function(ev) { |
| this._redirectToState({}, "/legacysearch"); |
| }, |
| |
| // _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; |
| }, |
| |
| _showBulkTriageDialog: function() { |
| this.$.bulkDialog.showModal(); |
| }, |
| |
| _hideBulkTriageDialog: function() { |
| this.$.bulkDialog.close(); |
| }, |
| |
| _startBulkTriage: function() { |
| this.$.activityBar.startSpinner("Triaging ..."); |
| }, |
| |
| _endBulkTriage: function() { |
| this.$.bulkDialog.close(); |
| this._load(); |
| }, |
| |
| _load: function() { |
| this.$.activityBar.startSpinner("Loading ..."); |
| |
| var q = window.location.search; |
| // Maybe if we detect that issue is set, do the query to something like |
| // /json/changelist/gerrit/1786867 for the changelist 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. |
| const 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); |
| |
| const allBulkTriageData = displayData.bulk_triage_data; |
| const pageBulkTriageData = getPageBulkTriageData(displayData.digests); |
| this.$.bulkTriage.setDigests(pageBulkTriageData, allBulkTriageData); |
| |
| this.$.activityBar.stopSpinner(); |
| }.bind(this)).catch(function(e) { |
| this.$.activityBar.stopSpinner(); |
| sk.errorMessage(e); |
| }.bind(this)); |
| |
| if (this._state.issue) { |
| sk.get(`/json/changelist/${this._state.crs}/${this._state.issue}`).then(JSON.parse).then((json) => { |
| this.$.changeListControls.setSummary(json); |
| this.set('_changeListID', json.cl.id); |
| }); |
| this.$.bulkTriage.changeListID = this._state.issue; |
| } |
| }, |
| |
| _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._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._paramSet = json; |
| |
| // Extract corpora from response. |
| this.$.searchControls.corpora = json.source_type; |
| |
| // Delete source_type and set the paramSet. |
| const paramSetCopy = JSON.parse(JSON.stringify(json)); |
| const corpora = paramSetCopy.source_type; |
| delete paramSetCopy["source_type"]; |
| this.$.searchControls.paramSet = paramSetCopy; |
| |
| }.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); |
| } |
| }); |
| |
| // The return value here is a map of test name to digest to the label string of |
| // the closest reference image. If there is no closest reference image, the label string |
| // will be empty string (it still needs to be included for a bulk triage positive/negative). |
| function getPageBulkTriageData(searchResults) { |
| const testDigestClosest = {}; |
| for (const result of searchResults) { |
| let byTest = testDigestClosest[result.test]; |
| if (!byTest) { |
| byTest = {}; |
| testDigestClosest[result.test] = byTest; |
| } |
| let valueToSet = ''; |
| if (result.closestRef === 'pos') { |
| valueToSet = 'positive'; |
| } else if (result.closestRef === 'neg') { |
| valueToSet = 'negative'; |
| } |
| byTest[result.digest] = valueToSet; |
| } |
| return testDigestClosest; |
| } |
| </script> |
| </dom-module> |