| <!-- The <explore-sk> custom element declaration. |
| |
| Main page of Perf, for exploring data. |
| |
| Attributes: |
| None. |
| |
| Events: |
| None. |
| |
| Methods: |
| None. |
| |
| --> |
| <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-layout-classes.html"> |
| <link rel="import" href="/res/imp/bower_components/iron-icon/iron-icon.html"> |
| <link rel="import" href="/res/imp/bower_components/iron-icons/iron-icons.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-input/paper-input.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-spinner/paper-spinner.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-tabs/paper-tabs.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-checkbox/paper-checkbox.html"> |
| |
| <link rel="import" href="/res/common/imp/query2-sk.html" /> |
| <link rel="import" href="/res/common/imp/paramset.html" /> |
| <link rel="import" href="/res/common/imp/query-summary-sk.html" /> |
| <link rel="stylesheet" href="/res/common/css/md.css"> |
| |
| <link rel="import" href="/res/imp/plot-simple.html" /> |
| <link rel="import" href="/res/imp/commit-detail-panel.html" /> |
| <link rel="import" href="/res/imp/highlight.html" /> |
| <link rel="import" href="/res/imp/domain-picker-sk.html" /> |
| <link rel="import" href="/res/imp/json-source.html" /> |
| |
| <dom-module id="explore-sk"> |
| <style include="iron-flex iron-flex-alignment iron-positioning"> |
| h3 { |
| margin: 0.5em 0 0 0; |
| } |
| |
| paper-input-decorator { |
| margin: 0.5em 0; |
| } |
| |
| #selections { |
| margin: 0 1em; |
| } |
| |
| #commits, |
| #paramset { |
| display: inline-block; |
| margin: 0.5em; |
| overflow-y: auto; |
| } |
| |
| #paramset { |
| height: 480px; |
| } |
| |
| #details.hidden, |
| #queryTab.hidden, |
| #commits.hidden, |
| #paramset.hidden, |
| #simple_paramset.hidden { |
| display: none; |
| } |
| |
| paper-checkbox { |
| --paper-checkbox-checked-color: #1f78b4; |
| --paper-checkbox-checked-ink-color: #1f78b4; |
| --paper-checkbox-unchecked-color: #1f78b4; |
| --paper-checkbox-unchecked-ink-color: #1f78b4; |
| --paper-checkbox-label-color: #1f78b4; |
| margin: 0.6em; |
| } |
| |
| paper-tabs { |
| background: #A6CEE3; |
| color: #555; |
| } |
| |
| paper-tab.iron-selected { |
| background: #1F78B4; |
| color: white; |
| border: #1F78B4 solid 2px; |
| } |
| |
| paper-tab { |
| border: white solid 2px; |
| } |
| |
| #detail { |
| border: #1F78B4 2px solid; |
| margin: 0; |
| } |
| |
| #queryTab { |
| margin: 1em; |
| } |
| |
| iron-icon { |
| color: #1F78B4; |
| } |
| |
| domain-picker-sk { |
| display: block; |
| } |
| |
| #commits { |
| margin: 1em; |
| } |
| |
| #simple_paramset { |
| margin: 1em 0 1em 1em; |
| } |
| |
| #percent { |
| margin: 0.6em; |
| font-family: 'Roboto Mono',monospace; |
| width: 3em; |
| } |
| |
| #plotDiv { |
| grid-area: graph; |
| } |
| |
| #buttons { |
| grid-area: buttons; |
| } |
| |
| #tabs { |
| grid-area: tabs; |
| } |
| |
| .container { |
| width: 100%; |
| display: inline-grid; |
| grid-template-columns: auto 20%; |
| grid-template-rows: auto auto; |
| grid-template-areas: |
| "graph buttons" |
| "tabs tabs"; |
| } |
| </style> |
| <template> |
| <div class=container> |
| <div id=plotDiv class="layout flex"> |
| <highlightbar-sk id=highlight></highlightbar-sk> |
| <plot-simple-sk width=1024 height=256 id=plot></plot-simple-sk> |
| <div class="layout horizontal center-justified"> |
| <domain-picker-sk id=range state="[[state]]" on-domain-changed="_rangeChange"></domain-picker-sk> |
| <paper-spinner id=spinner></paper-spinner> |
| <span id=percent></span> |
| </div> |
| </div> |
| |
| <div class="layout vertical" id=buttons> |
| <button on-click="_removeHighlighted" title="Remove all the highlighted traces.">Remove Highlighted</button> |
| <button on-click="_removeAll" title="Remove all the traces.">Remove All</button> |
| <button on-click="_highlightedOnly" title="Remove all but the highlighted traces.">Highlighted Only</button> |
| <button on-click="_clearHighlights" title="Remove highlights from all traces.">Clear Highlights</button> |
| <button on-click="_resetAxes" title="Reset back to the original zoom level.">Reset Axes</button> |
| <button on-click="_shiftBoth" title="Expand the display [[_numShift]] commits in |
| both directions."><< +[[_numShift]] >></button> |
| <button on-click="_shiftLeft" title="Move 10 commits in the past."><< [[_numShift]]</button> |
| <button on-click="_shiftRight" title="Move 10 commits in the future.">[[_numShift]] >></button> |
| <div> |
| <button on-click="_zoomToRange" id=zoom_range disabled title="Fit the time range to the current zoom window.">Zoom Range</button> |
| <span title="Number of commits skipped between each point displayed." hidden="[[_isZero(_dataframe.skip)]]" id=skip>[[_dataframe.skip]]</span> |
| </div> |
| <paper-checkbox on-click=_zeroChanged checked="{{state.show_zero}}" title="Toggle the presence of the zero line.">Zero</paper-checkbox> |
| <paper-checkbox on-click=_autoRefreshChanged checked="{{state.auto_refresh}}" title="Auto-refresh the data displayed in the graph.">Auto-Refresh</paper-checkbox> |
| <button on-click="_normalize" title="Apply norm() to all the traces.">Normalize</button> |
| </div> |
| |
| <div id=tabs class="flex"> |
| <paper-tabs selected=0 id=detailtab no-bar on-iron-select="_tabSelect"> |
| <paper-tab>Query</paper-tab> |
| <paper-tab>Params</paper-tab> |
| <paper-tab id=commitsTab disabled>Details</paper-tab> |
| </paper-tabs> |
| <div id=detail> |
| <div id=queryTab class="layout vertical"> |
| <div class="layout horizontal"> |
| <query2-sk id=query></query2-sk> |
| <div class="layout vertical" id=selections> |
| <h3>Selections</h3> |
| <query-summary-sk id=summary></query-summary-sk> |
| <div> |
| Matches: <span id=matches></span> |
| </div> |
| <button on-tap="_add" class=action>Plot</button> |
| </div> |
| </div> |
| <h3>Calculated Traces</h3> |
| <div class="layout horizontal center"> |
| <paper-input-decorator floatingLabel label="Formula" flex> |
| <textarea id="formula" rows=3 cols=80></textarea> |
| </paper-input-decorator> |
| <button on-tap="_addCalculated" class=action>Add</button> |
| <a href="/help/" target=_blank><iron-icon icon="help"></iron-icon></a> |
| </div> |
| </div> |
| <paramset-sk id=paramset class=hidden clickable-values></paramset-sk> |
| <div id=details class="layout horizontal wrap hidden"> |
| <paramset-sk id=simple_paramset clickable-values></paramset-sk> |
| <div class="layout vertical"> |
| <commit-detail-panel-sk id=commits></commit-detail-panel-sk> |
| <json-source id=jsonsource></json-source> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </template> |
| </dom-module> |
| |
| <script> |
| Polymer({ |
| is: "explore-sk", |
| |
| properties: { |
| // Keep track of the data sent to plot. |
| _lines: { |
| type: Object, |
| value: function() { return {}; }, |
| }, |
| _dataframe: { |
| type: Object, |
| value: function() { return { |
| traceset: {}, |
| }; }, |
| }, |
| // Keep track of whether a request is inflight to count the number of traces that match the current query. |
| _countInProgress: { |
| type: Boolean, |
| value: false, |
| }, |
| // The state that goes into the URL. |
| // |
| state: { |
| type: Object, |
| value: function() { return { |
| begin: Math.floor(Date.now()/1000 - 24*60*60), |
| end: Math.floor(Date.now()/1000), |
| formulas: [], |
| queries: [], |
| keys: "", // The id of the shortcut to a list of trace keys. |
| xbaroffset: -1, // The offset of the commit in the repo. |
| show_zero: true, |
| auto_refresh: false, |
| num_commits: 50, |
| request_type: 1, |
| }; }, |
| }, |
| // The [begin, end] timestamps of the region |
| // that the user is zoomed in on. |
| _zoomRange: { |
| type: Array, |
| value: function() { return []; }, |
| }, |
| // The id of the current frame request. Will be the empty string |
| // if there is no pending request. |
| _requestId: { |
| type: String, |
| value: "", |
| }, |
| _show_zero: { |
| type: Boolean, |
| value: true, |
| observer: "_zeroChanged", |
| }, |
| _numShift: { |
| type: Number, |
| value: sk.perf.num_shift, |
| }, |
| _refreshId: { |
| type: Number, |
| value: -1, |
| } |
| }, |
| |
| |
| ready: function() { |
| this.ZERO_NAME = "special_zero"; |
| |
| this.REFRESH_TIMEOUT = 30*1000; // milliseconds |
| |
| // Populate the query element. |
| var tz = Intl.DateTimeFormat().resolvedOptions().timeZone; |
| sk.get("/_/initpage/?tz=" + tz).then(JSON.parse).then(function(json) { |
| let now = Math.floor(Date.now()/1000); |
| this.state.begin = now - 60*60*24; |
| this.state.end = now; |
| this.$.range.state = this.state; |
| |
| this.$.query.setKeyOrder(sk.perf.key_order); |
| this.$.query.setParamset(json.dataframe.paramset); |
| |
| // Remove the paramset so it doesn't get displayed in the Params tab. |
| json.dataframe.paramset = {}; |
| |
| // Display this SearchResult, which has no traces, but this will |
| // set up the tick marks, the skp bands, and the 0-trace. |
| this._addTraces(json, true, false); |
| |
| // From this point on reflect the state to the URL. |
| this._startStateReflector(); |
| }.bind(this)).catch(sk.errorMessage); |
| |
| // Reflect the current query to the query summary. |
| this.$.query.addEventListener("query-change", function(e){ |
| var query = e.detail.q; |
| this.$.summary.selection = query; |
| var formula = this.$.formula.value; |
| if (formula == "") { |
| this.$.formula.value = 'filter("' + query + '")'; |
| } else if (2 == (formula.match(/\"/g) || []).length) { |
| // Only update the filter query if there's one string in the formula. |
| this.$.formula.value = formula.replace(/".*"/, '"' + query + '"'); |
| } |
| }.bind(this)); |
| |
| this.$.query.addEventListener("query-change-delayed", function(e){ |
| this._updateCount(); |
| }.bind(this)); |
| |
| // Highlight trace when a paramset value is clicked. |
| this.$.paramset.addEventListener("paramset-key-value-click", |
| this._paramsetKeyValueClick.bind(this)); |
| this.$.simple_paramset.addEventListener("paramset-key-value-click", |
| this._paramsetKeyValueClick.bind(this)); |
| |
| // Reflect the focused trace in the paramset. |
| this.$.plot.addEventListener("trace_focused", function(e) { |
| this.$.paramset.clearHighlight(); |
| this.$.paramset.setHighlight(sk.key.toObject(e.detail.id)); |
| this.$.highlight.key = e.detail.id; |
| this.$.highlight.value = e.detail.pt[1]; |
| }.bind(this)); |
| |
| // User has zoomed in on the graph. |
| this.$.plot.addEventListener("zoom", function(e) { |
| var begin = this._dataframe.header[Math.floor(e.detail.xMin)].timestamp; |
| var endIndex = Math.min(Math.floor(e.detail.xMax), this._dataframe.header.length-1); |
| var end = this._dataframe.header[endIndex].timestamp+1; |
| this._zoomRange = [begin, end]; |
| this.$.zoom_range.disabled = false; |
| }.bind(this)); |
| |
| // Highlight a trace when it is clicked on. |
| this.$.plot.addEventListener("trace_selected", function(e) { |
| this.$.plot.clearHighlight(); |
| this.$.plot.setHighlight(e.detail.id); |
| this.$.commits.setCommitDetail([]); |
| |
| var x = +e.detail.pt[0]|0; |
| // loop backwards from x until you get the next |
| // non 1e32 point. |
| var commits = [this._dataframe.header[x]]; |
| var trace = this._dataframe.traceset[e.detail.id]; |
| for (var i = x-1; i >= 0; i--) { |
| if (trace[i] != 1e32) { |
| break; |
| } |
| commits.push(this._dataframe.header[i]); |
| } |
| // Convert the trace id into a paramset to display. |
| var params = sk.key.toObject(e.detail.id); |
| var paramset = {} |
| Object.keys(params).forEach(function(key) { |
| paramset[key] = [params[key]]; |
| }); |
| // Request populated commits from the server. |
| sk.post("/_/cid/", JSON.stringify(commits)).then(JSON.parse).then(function(json){ |
| this.$.commits.setCommitDetail(json); |
| this.$.commitsTab.disabled = false; |
| this.$.simple_paramset.setParamSets([paramset]); |
| this.$.detailtab.selected = 2; |
| this.$.jsonsource.cid = commits[0]; |
| this.$.jsonsource.traceid = e.detail.id; |
| }.bind(this)).catch(sk.errorMessage); |
| }.bind(this)); |
| |
| this._updateCount(); |
| this._resizeWatcher(); |
| }, |
| |
| _startStateReflector: function() { |
| sk.stateReflector(this, function() { |
| this.$.range.state = this.state; |
| this._zeroChanged(); |
| this._autoRefreshChanged(); |
| this._rangeChangeImpl(); |
| }.bind(this)); |
| }, |
| |
| _paramsetKeyValueClick: function(e) { |
| var keys = []; |
| Object.keys(this._lines).forEach(function(key) { |
| if (sk.key.matches(key, e.detail.key, e.detail.value)) { |
| keys.push(key); |
| } |
| }); |
| // Additively highlight if the ctrl key is pressed. |
| if (!e.detail.ctrl) { |
| this.$.plot.clearHighlight(); |
| } |
| this.$.plot.setHighlight(keys); |
| }, |
| |
| // Fit the time range to the zoom being displayed. |
| // Reload all the queries/formulas on the new time range. |
| _zoomToRange: function() { |
| this.state.begin = this._zoomRange[0]; |
| this.state.end = this._zoomRange[1]; |
| this._rangeChangeImpl(); |
| }, |
| |
| // Called when the domain-picker-sk control has changed, causes all the |
| // queries/formulas to be reloaded for the new time range. |
| _rangeChange: function(e) { |
| this.state.begin = e.detail.state.begin; |
| this.state.end = e.detail.state.end; |
| this.state.num_commits = e.detail.state.num_commits; |
| this.state.request_type = e.detail.state.request_type; |
| this._rangeChangeImpl(); |
| }, |
| |
| _shiftBoth: function(e) { |
| this._shiftImpl(-this._numShift, this._numShift); |
| }, |
| |
| _shiftRight: function(e) { |
| this._shiftImpl(this._numShift, this._numShift); |
| }, |
| |
| _shiftLeft: function(e) { |
| this._shiftImpl(-this._numShift, -this._numShift); |
| }, |
| |
| // Change the current range by the following +/- offsets. |
| _shiftImpl: function(beginOffset, endOffset) { |
| var body = { |
| begin: this.state.begin, |
| begin_offset: beginOffset, |
| end: this.state.end, |
| end_offset: endOffset, |
| num_commits: this.state.num_commits, |
| request_type: this.state.request_type, |
| } |
| sk.post('/_/shift/', JSON.stringify(body)).then(JSON.parse).then(function(json) { |
| this.state.begin = json.begin; |
| this.state.end = json.end; |
| this.state.num_commits = json.num_commits; |
| // Now let's try to fool Polymer into updating the page. |
| this.set('state', Object.assign({}, this.state)); |
| this._rangeChangeImpl(); |
| }.bind(this)).catch(sk.errorMessage); |
| }, |
| |
| // Fill in the basic data needed for a FrameRequest that will be common |
| // to all situations. |
| _requestFrameBodyFromState: function() { |
| return { |
| begin: this.state.begin, |
| end: this.state.end, |
| num_commits: this.state.num_commits, |
| request_type: this.state.request_type, |
| }; |
| }, |
| |
| // Create a FrameRequest that will re-create the current state of the page. |
| _requestFrameBodyFullFromState: function() { |
| var body = this._requestFrameBodyFromState(); |
| return Object.assign(body, { |
| formulas: this.state.formulas, |
| queries: this.state.queries, |
| keys: this.state.keys, |
| }); |
| }, |
| |
| // Reload all the queries/formulas on the given time range. |
| _rangeChangeImpl: function(begin, end) { |
| if (!this.state) { |
| return |
| } |
| if (this.state.formulas.length == 0 && this.state.queries.length == 0 && this.state.keys == "") { |
| return |
| } |
| var body = this._requestFrameBodyFullFromState(); |
| var switchToTab = body.formulas.length > 0 || body.queries.length > 0 || body.keys != ""; |
| this._requestFrame(body, function(json) { |
| this.$.plot.removeAll(); |
| this._lines = []; |
| this._addTraces(json, false, switchToTab); |
| this.$.zoom_range.disabled = true; |
| }.bind(this)); |
| }, |
| |
| _updateCount: function() { |
| if (this._countInProgress === true) { |
| return |
| } |
| this._countInProgress = true; |
| let body = { |
| q: this.$.query.current_query, |
| begin: this.state.begin, |
| end: this.state.end, |
| }; |
| sk.post("/_/count/", JSON.stringify(body)).then(JSON.parse).then(function(json) { |
| this._countInProgress = false; |
| this.$.matches.textContent = json.count; |
| |
| if (json.paramset) { |
| this.$.query.setParamset(json.paramset); |
| } |
| }.bind(this)).catch(function() { |
| this._countInProgress = false; |
| }); |
| }, |
| |
| _zeroChanged: function() { |
| if (!this._dataframe.header) { |
| return; |
| } |
| if (this.state.show_zero) { |
| var line = []; |
| for (var i = 0; i < this._dataframe.header.length; i++) { |
| line.push([i, 0]); |
| } |
| var lines = {}; |
| lines[this.ZERO_NAME] = line; |
| this.$.plot.addLines(lines); |
| } else { |
| this.$.plot.deleteLine(this.ZERO_NAME); |
| } |
| this.$.plot.resetAxes(); |
| }, |
| |
| _autoRefreshChanged: function() { |
| if (!this.state.auto_refresh) { |
| if (this._refreshId != -1) { |
| clearInterval(this._refreshId); |
| } |
| } else { |
| this._refreshId = setInterval(() => this._autoRefresh(), this.REFRESH_TIMEOUT); |
| } |
| }, |
| |
| _autoRefresh: function() { |
| // Update end to be now. |
| this.state.end = Math.floor(Date.now()/1000); |
| var body = this._requestFrameBodyFullFromState(); |
| var switchToTab = body.formulas.length > 0 || body.queries.length > 0 || body.keys != ""; |
| this._requestFrame(body, (json) => { |
| this.$.plot.removeAll(); |
| this._lines = []; |
| this._addTraces(json, false, switchToTab); |
| this.$.zoom_range.disabled = true; |
| }); |
| }, |
| |
| // _addTraces adds the traces to the graph in the serialized SearchResults |
| // found in json. If incremental is true then the traces are added w/o |
| // removing the old traces. If tab is true then the details tab is |
| // switched to the params for the displayed traces. |
| _addTraces: function(json, incremental, tab) { |
| var dataframe = json.dataframe; |
| var lines = {}; |
| |
| if (dataframe.traceset === null) { |
| return |
| } |
| |
| // Add in the 0-trace. |
| if (this.state.show_zero) { |
| dataframe.traceset[this.ZERO_NAME] = Array(dataframe.header.length).fill(0); |
| } |
| |
| // Convert the dataframe into a form suitable for the plot element. |
| var keys = Object.keys(dataframe.traceset); |
| keys.forEach(function(key) { |
| var values = []; |
| dataframe.traceset[key].forEach(function(y, x) { |
| if (y != 1e32) { |
| values.push([x, y]); |
| } |
| }); |
| lines[key] = values; |
| }); |
| |
| if (incremental) { |
| Object.keys(lines).forEach(function(key) { |
| this._lines[key] = lines[key]; |
| }.bind(this)); |
| if (this._dataframe.header == undefined) { |
| this._dataframe = dataframe; |
| } else { |
| Object.keys(dataframe.traceset).forEach(function(key) { |
| this._dataframe.traceset[key] = dataframe.traceset[key]; |
| }.bind(this)); |
| } |
| } else { |
| this._lines = lines; |
| this._dataframe = dataframe; |
| } |
| if (!incremental) { |
| this.$.plot.removeAll(); |
| } |
| this.$.plot.addLines(this._lines); |
| |
| // Ticks are serialized as a slice of slices, ala [[0, "foo"], [1.5, "bar"], ..] |
| // because the structure we actually need, a map[float64]string, isn't able |
| // to be serialized as JSON, so we transform the representations here. |
| // |
| // TODO(jcgregorio) Once we are the only customer of plot-simple-sk, then change |
| // signature of setTicks(). |
| var tickmap = {}; |
| json.ticks.forEach(function(t) { |
| tickmap[t[0]] = t[1]; |
| }); |
| this.$.plot.setTicks(tickmap); |
| |
| // Always add in the last index so we draw a band if there's an odd |
| // number of skp updates. |
| json.skps.push(json.dataframe.header.length-1); |
| |
| var bands = []; |
| for (var i = 1; i < json.skps.length; i+=2) { |
| bands.push([json.skps[i-1], json.skps[i]]); |
| } |
| this.$.plot.setBanding(bands); |
| |
| // Populate the xbar if present. |
| if (this.state.xbaroffset != -1) { |
| var xbaroffset = this.state.xbaroffset; |
| var xbar = -1; |
| this._dataframe.header.forEach(function(h, i) { |
| if (h.offset == xbaroffset) { |
| xbar = i; |
| } |
| }); |
| if (xbar != -1) { |
| this.$.plot.setXBar(xbar); |
| } else { |
| this.$.plot.clearXBar(); |
| } |
| } else { |
| this.$.plot.clearXBar(); |
| } |
| |
| |
| // Populate the paramset element. |
| this.$.paramset.setParamSets([dataframe.paramset]); |
| if (tab) { |
| this.$.detailtab.selected = 1; |
| } |
| }, |
| |
| _add: function() { |
| var q = this.$.query.current_query.trim(); |
| if (q == "") { |
| return |
| } |
| if (this.state.queries.indexOf(q) == -1) { |
| this.state.queries.push(q); |
| } |
| var body = this._requestFrameBodyFromState(); |
| Object.assign(body, { |
| queries: [q], |
| }); |
| this._requestFrame(body, function(json) { |
| this._addTraces(json, true, true); |
| }.bind(this)); |
| }, |
| |
| _tabSelect: function() { |
| var sel = this.$.detailtab.selected; |
| this.$.queryTab.classList.toggle('hidden', !(sel == 0)); |
| this.$.paramset.classList.toggle('hidden', !(sel == 1)); |
| this.$.details.classList.toggle('hidden', !(sel == 2)); |
| }, |
| |
| |
| // Watch the size of the plot parents element and when it changes then |
| // then resize the plot element to match. |
| _resizeWatcher: function () { |
| this.async(this._resizeWatcher.bind(this), 300); |
| |
| var w = window.innerWidth * 0.75; |
| if (w != this.$.plot.width) { |
| this.$.plot.setAttribute('width', w); |
| |
| // Measure again since the resize of svg might have caused a tiny change in the size of |
| // the parent window. |
| w = window.innerWidth * 0.75; |
| this.$.plot.setAttribute('width', w); |
| this.$.plot.resetAxes(); |
| } |
| }, |
| |
| _removeAll: function() { |
| this.state.formulas = []; |
| this.state.queries = []; |
| this.state.keys = ""; |
| this.$.plot.removeAll(); |
| this._lines = []; |
| }, |
| |
| // When Remove Highlighted or Highlighted Only are pressed then create a |
| // shortcut for just the traces that are displayed. |
| // |
| // Note that removing a trace doesn't work if the trace came from a |
| // formula that returns multiple traces. This is a known issue that |
| // isn't currently worth solving. |
| // |
| // Returns the Promise that's creating the shortcut, or undefined if |
| // there isn't a shortcut to create. |
| _reShortCut: function(keys) { |
| if (keys.length == 0) { |
| this.state.keys = ""; |
| this.state.queries = []; |
| return undefined |
| } |
| var state = { |
| keys: keys, |
| }; |
| return sk.post('/_/keys/', JSON.stringify(state)).then(JSON.parse).then(function (json) { |
| this.state.keys = json.id; |
| this.state.queries = []; |
| }.bind(this)); |
| }, |
| |
| // Create a shortcut for all of the traces currently being displayed. |
| // |
| // Returns the Promise that's creating the shortcut, or undefined if |
| // there isn't a shortcut to create. |
| _shortcutAll: function() { |
| var toShortcut = []; |
| |
| Object.keys(this._dataframe.traceset).forEach(function(key) { |
| if (key[0] == ",") { |
| toShortcut.push(key); |
| } |
| }); |
| |
| return this._reShortCut(toShortcut); |
| }, |
| |
| // Apply norm() to all the traces currently being displayed. |
| _normalize: function() { |
| let promise = this._shortcutAll(); |
| if (!promise) { |
| sk.errorMessage("No traces to normalize."); |
| return |
| } |
| promise.then(() => { |
| let f = `norm(shortcut("${this.state.keys}"))` |
| this._removeAll(); |
| var body = this._requestFrameBodyFromState(); |
| Object.assign(body, { |
| formulas: [f], |
| }); |
| this.state.formulas.push(f); |
| this._requestFrame(body, (json) => { |
| this._addTraces(json, true, false); |
| }); |
| }); |
| }, |
| |
| _removeHighlighted: function() { |
| var ids = this.$.plot.highlighted(); |
| var toadd = {}; |
| var toShortcut = []; |
| |
| Object.keys(this._dataframe.traceset).forEach(function(key) { |
| if (ids.indexOf(key) != -1) { |
| // Detect if it is a formula being removed. |
| if (this.state.formulas.indexOf(key) != -1) { |
| this.state.formulas.splice(this.state.formulas.indexOf(key), 1) |
| } |
| return |
| } |
| if (key[0] == ",") { |
| toShortcut.push(key); |
| } |
| var values = []; |
| this._dataframe.traceset[key].forEach(function(y, x) { |
| if (y != 1e32) { |
| values.push([x, y]); |
| } |
| }); |
| toadd[key] = values; |
| }.bind(this)); |
| |
| // Remove the traces from the traceset so they don't reappear. |
| ids.forEach(function(key) { |
| if (this._dataframe.traceset[key] != undefined) { |
| delete this._dataframe.traceset[key]; |
| } |
| }.bind(this)); |
| |
| this._lines = toadd; |
| this.$.plot.removeAll(); |
| this.$.plot.addLines(toadd); |
| this._reShortCut(toShortcut); |
| }, |
| |
| _highlightedOnly: function() { |
| var ids = this.$.plot.highlighted(); |
| var toadd = {}; |
| var toremove = []; |
| var toShortcut = []; |
| |
| Object.keys(this._dataframe.traceset).forEach(function(key) { |
| if (ids.indexOf(key) == -1 && !key.startsWith("special")) { |
| // Detect if it is a formula being removed. |
| if (this.state.formulas.indexOf(key) != -1) { |
| this.state.formulas.splice(this.state.formulas.indexOf(key), 1) |
| } else { |
| toremove.push(key); |
| } |
| return |
| } |
| if (key[0] == ",") { |
| toShortcut.push(key); |
| } |
| var values = []; |
| this._dataframe.traceset[key].forEach(function(y, x) { |
| if (y != 1e32) { |
| values.push([x, y]); |
| } |
| }); |
| toadd[key] = values; |
| }.bind(this)); |
| |
| // Remove the traces from the traceset so they don't reappear. |
| Object.keys(this._dataframe.traceset).forEach(function(key) { |
| if (key in toremove) { |
| delete this._dataframe.traceset[key]; |
| } |
| }.bind(this)); |
| |
| this._lines = toadd; |
| this.$.plot.removeAll(); |
| this.$.plot.addLines(toadd); |
| this._reShortCut(toShortcut); |
| }, |
| |
| _clearHighlights: function() { |
| this.$.plot.clearHighlight(); |
| }, |
| |
| _resetAxes: function() { |
| this.$.plot.resetAxes(); |
| this.$.zoom_range.disabled = true; |
| }, |
| |
| _addCalculated: function() { |
| var f = this.$.formula.value; |
| if (f.trim() == "") { |
| return |
| } |
| if (this.state.formulas.indexOf(f) == -1) { |
| this.state.formulas.push(f); |
| } |
| var body = this._requestFrameBodyFromState(); |
| Object.assign(body, { |
| formulas: [f], |
| }); |
| this._requestFrame(body, function(json) { |
| // TODO(jcgregorio) Remove all returned trace ids from hidden. |
| this._addTraces(json, true, false); |
| }.bind(this)); |
| }, |
| |
| // Common catch function for _requestFrame and _checkFrameRequestStatus. |
| _catch: function(msg) { |
| this._requestId = ""; |
| if (msg) { |
| sk.errorMessage(msg, 10000); |
| } |
| this.$.percent.textContent = ""; |
| this.$.spinner.active = false; |
| }, |
| |
| // Requests a new dataframe, where body is a serialized FrameRequest: |
| // |
| // { |
| // begin: 1448325780, |
| // end: 1476706336, |
| // formulas: ["ave(filter("name=desk_nytimes.skp&sub_result=min_ms"))"], |
| // hidden: [], |
| // queries: [ |
| // "name=AndroidCodec_01_original.jpg_SampleSize8", |
| // "name=AndroidCodec_1.bmp_SampleSize8"], |
| // tz: "America/New_York" |
| // }; |
| // |
| // The 'cb' callback function will be called with the decoded JSON body |
| // of the response once it's available. |
| _requestFrame: function(body, cb) { |
| body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone; |
| if (this._requestId != "") { |
| sk.errorMessage("There is a pending query already running."); |
| return |
| } else { |
| this._requestId = "About to make request"; |
| } |
| this.$.spinner.active = true; |
| |
| sk.post("/_/frame/start", JSON.stringify(body), "application/json").then(JSON.parse).then(function(json) { |
| this._requestId = json.id; |
| this._checkFrameRequestStatus(cb); |
| }.bind(this)).catch(this._catch.bind(this)); |
| }, |
| |
| // Periodically check the status of a pending FrameRequest, calling the |
| // 'cb' callback with the decoded JSON upon success. |
| _checkFrameRequestStatus: function(cb) { |
| sk.get("/_/frame/status/"+this._requestId).then(JSON.parse).then(function(json) { |
| if (json.state == "Running") { |
| this.$.percent.textContent = Math.floor(json.percent*100) + "%"; |
| window.setTimeout(this._checkFrameRequestStatus.bind(this, cb), 300); |
| } else { |
| sk.get("/_/frame/results/"+this._requestId).then(JSON.parse).then(function(json) { |
| cb(json); |
| this._catch(json.msg); |
| }.bind(this)).catch(this._catch.bind(this)); |
| } |
| }.bind(this)).catch(this._catch.bind(this)); |
| }, |
| |
| _isZero: function(n) { |
| return n === 0; |
| } |
| |
| }); |
| </script> |