| Index: dashboard/dashboard/elements/chart-container.html
|
| diff --git a/dashboard/dashboard/elements/chart-container.html b/dashboard/dashboard/elements/chart-container.html
|
| index 7484a4e550b2e785ab4b7653016906f2f5337029..38b9d5898632718d0d6fb49209bcd73a8dd564e8 100644
|
| --- a/dashboard/dashboard/elements/chart-container.html
|
| +++ b/dashboard/dashboard/elements/chart-container.html
|
| @@ -14,13 +14,16 @@ triaging functionality in the chart.
|
| <link rel="import" href="/components/iron-flex-layout/iron-flex-layout-classes.html">
|
| <link rel="import" href="/components/iron-icon/iron-icon.html">
|
| <link rel="import" href="/components/paper-button/paper-button.html">
|
| +<link rel="import" href="/components/paper-tabs/paper-tabs.html">
|
|
|
| <link rel="import" href="/dashboard/elements/alert-icon.html">
|
| <link rel="import" href="/dashboard/elements/chart-legend.html">
|
| <link rel="import" href="/dashboard/elements/chart-slider.html">
|
| +<link rel="import" href="/dashboard/elements/chart-sparkline.html">
|
| <link rel="import" href="/dashboard/elements/chart-title.html">
|
| <link rel="import" href="/dashboard/elements/chart-tooltip.html">
|
| <link rel="import" href="/dashboard/static/events.html">
|
| +<link rel="import" href="/dashboard/static/related_timeseries.html">
|
| <link rel="import" href="/dashboard/static/series_group.html">
|
| <link rel="import" href="/dashboard/static/simple_xhr.html">
|
| <link rel="import" href="/dashboard/static/testselection.html">
|
| @@ -64,6 +67,10 @@ triaging functionality in the chart.
|
| z-index: 1000;
|
| }
|
|
|
| + #vline-container {
|
| + display: flex;
|
| + width: 100%;
|
| + }
|
| #plots-container {
|
| flex-grow: 1;
|
| display: flex;
|
| @@ -134,6 +141,53 @@ triaging functionality in the chart.
|
| #top-bar {
|
| width: 100%;
|
| }
|
| +
|
| + #related-tabs-container {
|
| + display: flex;
|
| + align-items: center;
|
| + width: 100%;
|
| + }
|
| + #related-tabs {
|
| + margin-left: 1em;
|
| + flex-grow: 1;
|
| + --paper-tabs-selection-bar-color: black;
|
| + --paper-tabs: {
|
| + background-color: #ccc;
|
| + height: 2em;
|
| + };
|
| + }
|
| + #related-sparkline-container {
|
| + width: 100%;
|
| + max-height: 290px;
|
| + overflow-y: scroll;
|
| + }
|
| + #related-sparkline-container chart-sparkline {
|
| + margin-top: 0.5em;
|
| + }
|
| + paper-tab {
|
| + flex-grow: 0;
|
| + color: #555;
|
| + }
|
| + paper-tab:last-of-type {
|
| + flex-grow: 1;
|
| + }
|
| + paper-tab:last-of-type {
|
| + --paper-tab-content: {
|
| + justify-content: flex-end;
|
| + };
|
| + }
|
| + paper-tab.iron-selected {
|
| + color: black;
|
| + }
|
| +
|
| + #vline {
|
| + width: 0;
|
| + height: 214px;
|
| + position: relative;
|
| + top: 8px;
|
| + border-left: 1px solid black;
|
| + display: none;
|
| + }
|
| </style>
|
|
|
| <div id="container" compact$="{{showCompact}}">
|
| @@ -156,13 +210,17 @@ triaging functionality in the chart.
|
| <chart-tooltip id="tooltip"
|
| xsrf-token="{{xsrfToken}}"></chart-tooltip>
|
|
|
| - <div id="plots-container" on-mouseleave="onMouseLeave">
|
| - <div id="plot">
|
| - <div id="loading-div">
|
| - <img src="//www.google.com/images/loading.gif">
|
| + <div id="vline-container">
|
| + <div id="vline"> </div>
|
| + <div id="plots-container" on-mouseleave="onMouseLeave">
|
| + <div id="plot">
|
| + <div id="loading-div">
|
| + <!-- TODO(#3803): Use paper spinner. -->
|
| + <img src="//www.google.com/images/loading.gif">
|
| + </div>
|
| </div>
|
| + <chart-slider id="slider" on-revisionrange="onRevisionRange"></chart-slider>
|
| </div>
|
| - <chart-slider id="slider" on-revisionrange="onRevisionRange"></chart-slider>
|
| </div>
|
|
|
| <div id="warning">
|
| @@ -188,8 +246,31 @@ triaging functionality in the chart.
|
|
|
| <canvas hidden id="text_measurement"></canvas>
|
|
|
| + <div hidden$="[[!showRelatedTabs_(relatedTabs)]]" style="width: 100%">
|
| + <div id="related-tabs-container">
|
| + <div>Related</div>
|
| + <paper-tabs id="related-tabs" selected="{{selectedRelatedTabIndex}}">
|
| + <template is="dom-repeat" items="[[relatedTabs]]">
|
| + <paper-tab>[[item.name]]</paper-tab>
|
| + </template>
|
| + </paper-tabs>
|
| + </div>
|
| + <div id="related-sparkline-container">
|
| + <template is="dom-repeat" items="[[selectedRelatedTab]]">
|
| + <chart-sparkline name="[[item.name]]"
|
| + chart-options="[[getSparklineChartOptions()]]"
|
| + revision-map="[[revisionMap]]"
|
| + start-rev="[[sliderStartRev]]"
|
| + end-rev="[[sliderEndRev]]"
|
| + vline-point-id="[[vlinePointId]]"
|
| + testpaths="[[item.testpaths]]">
|
| + </chart-sparkline>
|
| + </template>
|
| + </div>
|
| + </div>
|
| </div>
|
| </template>
|
| +
|
| <script>
|
| 'use strict';
|
| (function() {
|
| @@ -498,8 +579,37 @@ triaging functionality in the chart.
|
| revisionInfo: { notify: true },
|
| showCompact: { notify: true },
|
| testSuites: { notify: true },
|
| - xsrfToken: { notify: true }
|
| + xsrfToken: { notify: true },
|
| +
|
| + relatedTabs: {
|
| + type: Array,
|
| + value: () => [],
|
| + },
|
| +
|
| + selectedRelatedTab: {
|
| + type: Array,
|
| + value: () => [],
|
| + },
|
| +
|
| + selectedRelatedTabIndex: {
|
| + type: Number,
|
| + value: -1,
|
| + observer: 'onSelectedRelatedTabIndexChange_',
|
| + },
|
| +
|
| + vlinePointId: {
|
| + type: Number,
|
| + },
|
| +
|
| + sliderStartRev: {
|
| + type: String,
|
| + },
|
| +
|
| + sliderEndRev: {
|
| + type: String,
|
| + },
|
| },
|
| +
|
| observers: [
|
| 'indicesToGraphChanged(indicesToGraph.splices)'
|
| ],
|
| @@ -672,6 +782,7 @@ triaging functionality in the chart.
|
| }
|
|
|
| this.updateSeriesGroupDisplayNames();
|
| + this.buildRelatedTabs_();
|
|
|
| if (Object.keys(selectedTestPathDict).length > 0) {
|
| this.sendGraphJsonRequest(selectedTestPathDict, true);
|
| @@ -857,7 +968,7 @@ triaging functionality in the chart.
|
| return;
|
| }
|
| }
|
| - this.push('warnings', {'value': warning});
|
| + this.push('warnings', {value: warning});
|
| },
|
|
|
| /**
|
| @@ -865,15 +976,15 @@ triaging functionality in the chart.
|
| */
|
| updateWarningsForSelectedSeries() {
|
| this.warnings = this.warnings.filter(function(value) {
|
| - return (value.indexOf('Graph out of date!') == -1 &&
|
| - value.indexOf('No data available.') == -1);
|
| + return (value.value.indexOf('Graph out of date!') == -1 &&
|
| + value.value.indexOf('No data available.') == -1);
|
| });
|
|
|
| for (let i = 0; i < this.indicesToGraph.length; i++) {
|
| const index = this.indicesToGraph[i];
|
| const series = this.json.annotations[index];
|
| if (!series) {
|
| - this.warnings.push('No data available.');
|
| + this.warnings.push({value: 'No data available.'});
|
| }
|
| }
|
|
|
| @@ -889,8 +1000,10 @@ triaging functionality in the chart.
|
| if (timestamp != null) {
|
| const currentTime = new Date().getTime();
|
| if (timestamp < currentTime - this.STALE_DATA_DELTA_MS) {
|
| - this.warnings.push('Graph out of date! Last data received: ' +
|
| - new Date(timestamp).toISOString());
|
| + this.warnings.push({value:
|
| + 'Graph out of date! Last data received: ' +
|
| + new Date(timestamp).toISOString(),
|
| + });
|
| break;
|
| }
|
| }
|
| @@ -1007,6 +1120,7 @@ triaging functionality in the chart.
|
| this.updateSlider();
|
| this.updateYAxisLabel();
|
| this.updateSmartAutoscaleMap();
|
| + this.buildRelatedTabs_();
|
|
|
| if (isSelected) {
|
| this.updateIndicesToGraph();
|
| @@ -1273,6 +1387,8 @@ triaging functionality in the chart.
|
| this.graphParams = newGraphParams;
|
| this.reloadChart();
|
| this.fireChartStateChangedEvent(null);
|
| + this.set('sliderStartRev', detail.start_rev);
|
| + this.set('sliderEndRev', detail.end_rev);
|
| },
|
|
|
| /**
|
| @@ -1529,8 +1645,8 @@ triaging functionality in the chart.
|
| const endRev = uri.getParameter('end_rev');
|
| const firstSeriesIsEmpty = data[0].data.length == 0;
|
| if (startRev && endRev && firstSeriesIsEmpty) {
|
| - this.warnings.push('Data not available for revision range ' +
|
| - startRev + ':' + endRev + '.');
|
| + this.warnings.push({value: 'Data not available for revision range ' +
|
| + startRev + ':' + endRev + '.'});
|
| }
|
|
|
| const isNotZoomedIn = this.$.original.hidden;
|
| @@ -1635,6 +1751,8 @@ triaging functionality in the chart.
|
| }
|
| this.$.slider.startrev = orderedRevisions[0];
|
| this.$.slider.endrev = orderedRevisions[orderedRevisions.length - 1];
|
| + this.set('sliderStartRev', this.$.slider.startrev);
|
| + this.set('sliderEndRev', this.$.slider.endrev);
|
|
|
| // We keep a map of the ordered revision index to the [revision,
|
| // series index, data index] so that it's easy to show the right
|
| @@ -2151,9 +2269,17 @@ triaging functionality in the chart.
|
|
|
| // Un-hide and position the tooltip box.
|
| const top = flotData[flotSeriesIndex].yaxis.p2c(yValue);
|
| - const left = flotData[flotSeriesIndex].xaxis.p2c(xValue) +
|
| + let left = flotData[flotSeriesIndex].xaxis.p2c(xValue) +
|
| this.chartOptions.yaxis.labelWidth;
|
| this.$.tooltip.openAtPosition(top, left);
|
| +
|
| + // The tooltip doesn't need to be positioned exactly on the point, but
|
| + // the vertical line does. Flot's xaxis.p2c() above seems to be off by
|
| + // a few pixels for some reason.
|
| + left += 6.5;
|
| + this.$.vline.style.display = 'block';
|
| + this.$.vline.style.left = left + 'px';
|
| + this.set('vlinePointId', pointId);
|
| },
|
|
|
| /**
|
| @@ -2662,6 +2788,12 @@ triaging functionality in the chart.
|
| drop: 'onDrop',
|
| dragover: 'allowDrop',
|
| populateTestPicker: 'populateTestPicker_',
|
| + chartstatechanged: 'onChartStateChanged_',
|
| + },
|
| +
|
| + onChartStateChanged_(event) {
|
| + this.buildRelatedTabs_();
|
| + this.onSelectedRelatedTabIndexChange_();
|
| },
|
|
|
| populateTestPicker_(event) {
|
| @@ -2706,6 +2838,88 @@ triaging functionality in the chart.
|
| const url = window.location.origin + '/report?' + queryParts.join('&');
|
| this.fire('openReportPage', {url});
|
| },
|
| +
|
| + buildRelatedTabs_() {
|
| + if (uri.getParameter('3405') === null) return;
|
| +
|
| + const sourceTestPaths = [];
|
| + for (const seriesGroup of this.seriesGroupList) {
|
| + for (const test of seriesGroup.tests) {
|
| + if (!test.selected) continue;
|
| + // `test.path` is set in the addSeriesGroup2() path.
|
| + // This method needs to handle the old SeriesGroup.path/test.name
|
| + // form for the addSeriesGroup() path.
|
| + const testpath = test.path || seriesGroup.path + '/' + test.name;
|
| + sourceTestPaths.push({
|
| + testpath,
|
| + color: GENERATOR.colorForKey(testpath).toString(),
|
| + });
|
| + }
|
| + }
|
| +
|
| + const relatedTabs = d.buildRelatedTimeseries(sourceTestPaths);
|
| +
|
| + // The last tab is actually a button to unset the tab to hide the
|
| + // sparklines.
|
| + relatedTabs.push({
|
| + name: String.fromCharCode(8212),
|
| + sparklines: [],
|
| + });
|
| +
|
| + this.set('relatedTabs', relatedTabs);
|
| + },
|
| +
|
| + getSparklineChartOptions() {
|
| + return {
|
| + crosshair: {
|
| + },
|
| + grid: {
|
| + borderWidth: 0,
|
| + },
|
| + xaxis: {
|
| + ticks: [],
|
| + },
|
| + yaxis: {
|
| + ticks: [],
|
| + max: parseFloat(uri.getParameter('slyamax') || 0) ||
|
| + this.chartOptions.yaxis.max,
|
| + min: Number.MAX_VALUE,
|
| + },
|
| + selection: {
|
| + },
|
| + };
|
| + },
|
| +
|
| + showRelatedTabs_(relatedTabs) {
|
| + return relatedTabs.length > 1;
|
| + },
|
| +
|
| + onSelectedRelatedTabIndexChange_() {
|
| + // The last tab is actually a button to unset the tab to hide the
|
| + // sparklines.
|
| + if (this.selectedRelatedTabIndex === this.relatedTabs.length - 1) {
|
| + this.set('selectedRelatedTabIndex', -1);
|
| + }
|
| +
|
| + // It seems that setting this from one Array directly to another
|
| + // doesn't always make Polymer clear the old chart-sparklines and
|
| + // build new ones, so explicitly clear this to force it to clear the
|
| + // old chart-sparklines.
|
| + this.set('selectedRelatedTab', []);
|
| +
|
| + if (this.selectedRelatedTabIndex < 0 ||
|
| + this.selectedRelatedTabIndex >= this.relatedTabs.length) {
|
| + return;
|
| + }
|
| +
|
| + // Wait for Polymer to clear out the sparklines in response to
|
| + // clearing the selectedRelatedTab.
|
| + // TODO(#3841): Use dom-change or observeNodes instead of async().
|
| + this.async(() => {
|
| + this.set('selectedRelatedTab',
|
| + this.relatedTabs[this.selectedRelatedTabIndex].sparklines);
|
| + });
|
| + },
|
| });
|
| })();
|
| </script>
|
|
|