<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[201564] trunk/Websites/perf.webkit.org</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/201564">201564</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2016-06-01 12:55:38 -0700 (Wed, 01 Jun 2016)</dd>
</dl>
<h3>Log Message</h3>
<pre>v3 UI should support marking and unmarking outliers as well as hiding them
https://bugs.webkit.org/show_bug.cgi?id=158248
Rubber-stamped by Chris Dumez.
Added the support for marking and unmarking a sequence of points as outliers. Unlike v2, we now support marking
multiple points as outliers in a single click. Also fixed a bug that outliers are never explicitly hidden in v3 UI.
This patch splits ChartStyles.createChartSourceList into two functions: resolveConfiguration and createSourceList
to separate the work of resolving platform and metric IDs to their respective model objects, and creating a source
list used by TimeSeriesChart to fetch measurement sets. createSourceList is called again when filtering options are
changed.
It also adds noCache option to TimeSeriesChart's fetchMeasurementSets, MeasurementSet's fetchBetween and
_fetchPrimaryCluster to update the measurement sets after marking or unmarking points as outliers. In addition, it
fixes a bug that the annotation bars for analysis tasks are not updated in charts page after creating an analysis
task by adding noCache option to ChartPaneBase's fetchAnalysisTasks, AnalysisTask's fetchByPlatformAndMetric and
_fetchSubset.
Finally, this patch splits ChartPane._makeAnchorToOpenPane into _makePopoverActionItem, _makePopoverOpenOnHover and
_setPopoverVisibility for clarity.
* public/v3/components/chart-pane-base.js:
(ChartPaneBase): Added _disableSampling and _showOutliers as instance variables.
(ChartPaneBase.prototype.configure):
(ChartPaneBase.prototype.isSamplingEnabled): Added.
(ChartPaneBase.prototype.setSamplingEnabled): Added. When a filtering option is updated, recreate the source list
so that TimeSeriesChart.setSourceList can re-fetch the measurement set JSONs.
(ChartPaneBase.prototype.isShowingOutliers): Added.
(ChartPaneBase.prototype.setShowOutliers): Added. Ditto for calling _updateSourceList.
(ChartPaneBase.prototype._updateSourceList): Added.
(ChartPaneBase.prototype.fetchAnalysisTasks): Renamed from _fetchAnalysisTasks. Now takes noCache as an argument
instead of platform and metric IDs since they're on instance variables.
* public/v3/components/chart-styles.js:
(ChartStyles.resolveConfiguration): Renamed from createChartSourceList. Just resolves platform and metric IDs.
(ChartStyles.createSourceList): Extracted from createChartSourceList since it needs to be called when a filtering
option is changed as well as when ChartPaneBase.prototype.configure is called.
(ChartStyles.baselineStyle): Now takes filtering options.
(ChartStyles.targetStyle): Ditto.
(ChartStyles.currentStyle): Ditto.
* public/v3/components/interactive-time-series-chart.js:
(InteractiveTimeSeriesChart.prototype.currentPoint): Find the point in _fetchedTimeSeries when
_sampledTimeSeriesData hasn't been computed yet as a fallback (e.g. when the chart hasn't been rendered yet).
(InteractiveTimeSeriesChart.prototype.selectedPoints): Added.
(InteractiveTimeSeriesChart.prototype.firstSelectedPoint): Added.
(InteractiveTimeSeriesChart.prototype.lockedIndicator): Added. Returns the current point if it's locked.
* public/v3/components/time-series-chart.js:
(TimeSeriesChart.prototype.setDomain):
(TimeSeriesChart.prototype.setSourceList): Added. Re-create _fetchedTimeSeries when filtering options have changed.
Don't re-fetch measurement set JSONs here since showing outliers can be done entirely in the front end.
(TimeSeriesChart.prototype.fetchMeasurementSets): Extracted out of setDomain. Now takes noCache as an argument.
ChartPane._markAsOutlier
(TimeSeriesChart.prototype.firstSampledPointBetweenTime): Added.
* public/v3/models/analysis-task.js:
(AnalysisTask.fetchByPlatformAndMetric): Added noCache as an argument.
(AnalysisTask._fetchSubset): Ditto.
* public/v3/models/measurement-adaptor.js:
(MeasurementAdaptor.prototype.isOutlier): Added.
(MeasurementAdaptor.prototype.applyToAnalysisResults): Add markedOutlier as a property on each point.
* public/v3/models/measurement-cluster.js:
(MeasurementCluster.prototype.addToSeries): Fixed the bug that filtering outliers was broken as _markedOutlierIndex
is undefined here. Use MeasurementAdaptor's isOutlier instead.
* public/v3/models/measurement-set.js:
(MeasurementSet.prototype.fetchBetween): Added noCache as an argument. Reset _primaryClusterPromise and _allFetches
when noCache is true since we need to re-fetch the primary cluster as well as all secondary clusters now.
(MeasurementSet.prototype._fetchPrimaryCluster): Added noCache as an argument. Directly invoke the JSON API at
/api/measurement-set to re-generate all clusters' JSON files instead of first fetching the cached version.
(MeasurementSet.prototype._fetchSecondaryCluster):
(MeasurementSet.prototype._didFetchJSON): Removed a bogus assertion since this function is called on secondary
clusters as well as primary clusters.
(MeasurementSet.prototype._addFetchedCluster): Reimplemented this function using an insertion sort. Also remove the
existing entry if the fetch cluster should replace it.
* public/v3/models/time-series.js:
(TimeSeries.prototype.dataBetweenPoints): Removed the dead code to filter out outliers. This is done in addToSeries
of MeasurementCluster instead.
* public/v3/pages/chart-pane.js:
(ChartPane): Renamed pane to popover since it was confusing to have a pane inside a pane class. As such, renamed
_paneOpenedByClick to _lockedPopover.
(ChartPane.prototype.serializeState): Added the code to serialize filtering options in the serialized state URL.
(ChartPane.prototype.updateFromSerializedState): Ditto for parsing.
(ChartPane.prototype._analyzeRange): Extracted out of render(). Also fixed a bug that the charts page don't show
the newly created analysis task by invoking fetchAnalysisTasks with noCache set to true.
(ChartPane.prototype._markAsOutlier): Added.
(ChartPane.prototype._renderActionToolbar): A bunch of changes due to pane -> popover rename. Also added a popover
for filtering options.
(ChartPane.prototype._makePopoverActionItem): Extracted from _makeAnchorToOpenPane.
(ChartPane.prototype._makePopoverOpenOnHover): Ditto.
(ChartPane.prototype._setPopoverVisibility): Ditto.
(ChartPane.prototype._renderFilteringPopover): Added.
(ChartPane.htmlTemplate): Added a popover for specifying filtering options. Also added .popover on each popover.
(ChartPane.cssTemplate): Updated the style to make use of .popover.
* public/v3/pages/charts-page.js:
(ChartsPage.prototype.graphOptionsDidChange): Added. Updates the URL state when a filtering option is modified.
* public/v3/pages/dashboard-page.js:
(DashboardPage.prototype._createChartForCell):
* public/v3/pages/page-router.js:
(PageRouter.prototype._serializeHashQueryValue): Serialize a set of strings as | separated tokens.
(PageRouter.prototype._deserializeHashQueryValue): Rewrote the function as the serialized URL can no longer be
parsed as a JSON as | separated tokens can't be converted into a valid JSON construct with a simple regex.
* unit-tests/measurement-set-tests.js: Added a test case for fetchBetween with noCache=true.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentschartpanebasejs">trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentschartstylesjs">trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentsinteractivetimeserieschartjs">trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3componentstimeserieschartjs">trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsanalysistaskjs">trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsmeasurementadaptorjs">trunk/Websites/perf.webkit.org/public/v3/models/measurement-adaptor.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsmeasurementclusterjs">trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsmeasurementsetjs">trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelstimeseriesjs">trunk/Websites/perf.webkit.org/public/v3/models/time-series.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pageschartpanejs">trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pageschartspagejs">trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pagesdashboardpagejs">trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3pagespagerouterjs">trunk/Websites/perf.webkit.org/public/v3/pages/page-router.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgunittestsmeasurementsettestsjs">trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -1,3 +1,119 @@
</span><ins>+2016-05-31 Ryosuke Niwa <rniwa@webkit.org>
+
+ v3 UI should support marking and unmarking outliers as well as hiding them
+ https://bugs.webkit.org/show_bug.cgi?id=158248
+
+ Rubber-stamped by Chris Dumez.
+
+ Added the support for marking and unmarking a sequence of points as outliers. Unlike v2, we now support marking
+ multiple points as outliers in a single click. Also fixed a bug that outliers are never explicitly hidden in v3 UI.
+
+ This patch splits ChartStyles.createChartSourceList into two functions: resolveConfiguration and createSourceList
+ to separate the work of resolving platform and metric IDs to their respective model objects, and creating a source
+ list used by TimeSeriesChart to fetch measurement sets. createSourceList is called again when filtering options are
+ changed.
+
+ It also adds noCache option to TimeSeriesChart's fetchMeasurementSets, MeasurementSet's fetchBetween and
+ _fetchPrimaryCluster to update the measurement sets after marking or unmarking points as outliers. In addition, it
+ fixes a bug that the annotation bars for analysis tasks are not updated in charts page after creating an analysis
+ task by adding noCache option to ChartPaneBase's fetchAnalysisTasks, AnalysisTask's fetchByPlatformAndMetric and
+ _fetchSubset.
+
+ Finally, this patch splits ChartPane._makeAnchorToOpenPane into _makePopoverActionItem, _makePopoverOpenOnHover and
+ _setPopoverVisibility for clarity.
+
+ * public/v3/components/chart-pane-base.js:
+ (ChartPaneBase): Added _disableSampling and _showOutliers as instance variables.
+ (ChartPaneBase.prototype.configure):
+ (ChartPaneBase.prototype.isSamplingEnabled): Added.
+ (ChartPaneBase.prototype.setSamplingEnabled): Added. When a filtering option is updated, recreate the source list
+ so that TimeSeriesChart.setSourceList can re-fetch the measurement set JSONs.
+ (ChartPaneBase.prototype.isShowingOutliers): Added.
+ (ChartPaneBase.prototype.setShowOutliers): Added. Ditto for calling _updateSourceList.
+ (ChartPaneBase.prototype._updateSourceList): Added.
+ (ChartPaneBase.prototype.fetchAnalysisTasks): Renamed from _fetchAnalysisTasks. Now takes noCache as an argument
+ instead of platform and metric IDs since they're on instance variables.
+
+ * public/v3/components/chart-styles.js:
+ (ChartStyles.resolveConfiguration): Renamed from createChartSourceList. Just resolves platform and metric IDs.
+ (ChartStyles.createSourceList): Extracted from createChartSourceList since it needs to be called when a filtering
+ option is changed as well as when ChartPaneBase.prototype.configure is called.
+ (ChartStyles.baselineStyle): Now takes filtering options.
+ (ChartStyles.targetStyle): Ditto.
+ (ChartStyles.currentStyle): Ditto.
+
+ * public/v3/components/interactive-time-series-chart.js:
+ (InteractiveTimeSeriesChart.prototype.currentPoint): Find the point in _fetchedTimeSeries when
+ _sampledTimeSeriesData hasn't been computed yet as a fallback (e.g. when the chart hasn't been rendered yet).
+ (InteractiveTimeSeriesChart.prototype.selectedPoints): Added.
+ (InteractiveTimeSeriesChart.prototype.firstSelectedPoint): Added.
+ (InteractiveTimeSeriesChart.prototype.lockedIndicator): Added. Returns the current point if it's locked.
+
+ * public/v3/components/time-series-chart.js:
+ (TimeSeriesChart.prototype.setDomain):
+ (TimeSeriesChart.prototype.setSourceList): Added. Re-create _fetchedTimeSeries when filtering options have changed.
+ Don't re-fetch measurement set JSONs here since showing outliers can be done entirely in the front end.
+ (TimeSeriesChart.prototype.fetchMeasurementSets): Extracted out of setDomain. Now takes noCache as an argument.
+ ChartPane._markAsOutlier
+ (TimeSeriesChart.prototype.firstSampledPointBetweenTime): Added.
+
+ * public/v3/models/analysis-task.js:
+ (AnalysisTask.fetchByPlatformAndMetric): Added noCache as an argument.
+ (AnalysisTask._fetchSubset): Ditto.
+
+ * public/v3/models/measurement-adaptor.js:
+ (MeasurementAdaptor.prototype.isOutlier): Added.
+ (MeasurementAdaptor.prototype.applyToAnalysisResults): Add markedOutlier as a property on each point.
+
+ * public/v3/models/measurement-cluster.js:
+ (MeasurementCluster.prototype.addToSeries): Fixed the bug that filtering outliers was broken as _markedOutlierIndex
+ is undefined here. Use MeasurementAdaptor's isOutlier instead.
+
+ * public/v3/models/measurement-set.js:
+ (MeasurementSet.prototype.fetchBetween): Added noCache as an argument. Reset _primaryClusterPromise and _allFetches
+ when noCache is true since we need to re-fetch the primary cluster as well as all secondary clusters now.
+ (MeasurementSet.prototype._fetchPrimaryCluster): Added noCache as an argument. Directly invoke the JSON API at
+ /api/measurement-set to re-generate all clusters' JSON files instead of first fetching the cached version.
+ (MeasurementSet.prototype._fetchSecondaryCluster):
+ (MeasurementSet.prototype._didFetchJSON): Removed a bogus assertion since this function is called on secondary
+ clusters as well as primary clusters.
+ (MeasurementSet.prototype._addFetchedCluster): Reimplemented this function using an insertion sort. Also remove the
+ existing entry if the fetch cluster should replace it.
+
+ * public/v3/models/time-series.js:
+ (TimeSeries.prototype.dataBetweenPoints): Removed the dead code to filter out outliers. This is done in addToSeries
+ of MeasurementCluster instead.
+
+ * public/v3/pages/chart-pane.js:
+ (ChartPane): Renamed pane to popover since it was confusing to have a pane inside a pane class. As such, renamed
+ _paneOpenedByClick to _lockedPopover.
+ (ChartPane.prototype.serializeState): Added the code to serialize filtering options in the serialized state URL.
+ (ChartPane.prototype.updateFromSerializedState): Ditto for parsing.
+ (ChartPane.prototype._analyzeRange): Extracted out of render(). Also fixed a bug that the charts page don't show
+ the newly created analysis task by invoking fetchAnalysisTasks with noCache set to true.
+ (ChartPane.prototype._markAsOutlier): Added.
+ (ChartPane.prototype._renderActionToolbar): A bunch of changes due to pane -> popover rename. Also added a popover
+ for filtering options.
+ (ChartPane.prototype._makePopoverActionItem): Extracted from _makeAnchorToOpenPane.
+ (ChartPane.prototype._makePopoverOpenOnHover): Ditto.
+ (ChartPane.prototype._setPopoverVisibility): Ditto.
+ (ChartPane.prototype._renderFilteringPopover): Added.
+ (ChartPane.htmlTemplate): Added a popover for specifying filtering options. Also added .popover on each popover.
+ (ChartPane.cssTemplate): Updated the style to make use of .popover.
+
+ * public/v3/pages/charts-page.js:
+ (ChartsPage.prototype.graphOptionsDidChange): Added. Updates the URL state when a filtering option is modified.
+
+ * public/v3/pages/dashboard-page.js:
+ (DashboardPage.prototype._createChartForCell):
+
+ * public/v3/pages/page-router.js:
+ (PageRouter.prototype._serializeHashQueryValue): Serialize a set of strings as | separated tokens.
+ (PageRouter.prototype._deserializeHashQueryValue): Rewrote the function as the serialized URL can no longer be
+ parsed as a JSON as | separated tokens can't be converted into a valid JSON construct with a simple regex.
+
+ * unit-tests/measurement-set-tests.js: Added a test case for fetchBetween with noCache=true.
+
</ins><span class="cx"> 2016-05-24 Ryosuke Niwa <rniwa@webkit.org>
</span><span class="cx">
</span><span class="cx"> Another build fix after r201307.
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentschartpanebasejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -10,6 +10,8 @@
</span><span class="cx"> this._metricId = null;
</span><span class="cx"> this._platform = null;
</span><span class="cx"> this._metric = null;
</span><ins>+ this._disableSampling = false;
+ this._showOutliers = false;
</ins><span class="cx">
</span><span class="cx"> this._overviewChart = null;
</span><span class="cx"> this._mainChart = null;
</span><span class="lines">@@ -20,7 +22,7 @@
</span><span class="cx">
</span><span class="cx"> configure(platformId, metricId)
</span><span class="cx"> {
</span><del>- var result = ChartStyles.createChartSourceList(platformId, metricId);
</del><ins>+ var result = ChartStyles.resolveConfiguration(platformId, metricId);
</ins><span class="cx"> this._errorMessage = result.error;
</span><span class="cx"> this._platformId = platformId;
</span><span class="cx"> this._metricId = metricId;
</span><span class="lines">@@ -39,10 +41,11 @@
</span><span class="cx"> var formatter = result.metric.makeFormatter(4);
</span><span class="cx"> var self = this;
</span><span class="cx">
</span><ins>+ var sourceList = ChartStyles.createSourceList(this._platform, this._metric, this._disableSampling, this._showOutliers);
+
</ins><span class="cx"> var overviewOptions = ChartStyles.overviewChartOptions(formatter);
</span><span class="cx"> overviewOptions.selection.onchange = this._overviewSelectionDidChange.bind(this);
</span><del>-
- this._overviewChart = new InteractiveTimeSeriesChart(result.sourceList, overviewOptions);
</del><ins>+ this._overviewChart = new InteractiveTimeSeriesChart(sourceList, overviewOptions);
</ins><span class="cx"> this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart);
</span><span class="cx">
</span><span class="cx"> var mainOptions = ChartStyles.mainChartOptions(formatter);
</span><span class="lines">@@ -51,7 +54,7 @@
</span><span class="cx"> mainOptions.selection.onzoom = this._mainSelectionDidZoom.bind(this);
</span><span class="cx"> mainOptions.annotations.onclick = this._openAnalysisTask.bind(this);
</span><span class="cx"> mainOptions.ondata = this._didFetchData.bind(this);
</span><del>- this._mainChart = new InteractiveTimeSeriesChart(result.sourceList, mainOptions);
</del><ins>+ this._mainChart = new InteractiveTimeSeriesChart(sourceList, mainOptions);
</ins><span class="cx"> this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
</span><span class="cx">
</span><span class="cx"> this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart, this._requestOpeningCommitViewer.bind(this));
</span><span class="lines">@@ -59,14 +62,35 @@
</span><span class="cx">
</span><span class="cx"> this.content().querySelector('.chart-pane').addEventListener('keyup', this._keyup.bind(this));
</span><span class="cx">
</span><del>- this._fetchAnalysisTasks(platformId, metricId);
</del><ins>+ this.fetchAnalysisTasks(false);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- _fetchAnalysisTasks(platformId, metricId)
</del><ins>+ isSamplingEnabled() { return !this._disableSampling; }
+ setSamplingEnabled(enabled)
</ins><span class="cx"> {
</span><ins>+ this._disableSampling = !enabled;
+ this._updateSourceList();
+ }
+
+ isShowingOutliers() { return this._showOutliers; }
+ setShowOutliers(show)
+ {
+ this._showOutliers = !!show;
+ this._updateSourceList();
+ }
+
+ _updateSourceList()
+ {
+ var sourceList = ChartStyles.createSourceList(this._platform, this._metric, this._disableSampling, this._showOutliers);
+ this._mainChart.setSourceList(sourceList);
+ this._overviewChart.setSourceList(sourceList);
+ }
+
+ fetchAnalysisTasks(noCache)
+ {
</ins><span class="cx"> // FIXME: we need to update the annotation bars when the change type of tasks change.
</span><span class="cx"> var self = this;
</span><del>- AnalysisTask.fetchByPlatformAndMetric(platformId, metricId).then(function (tasks) {
</del><ins>+ AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId, noCache).then(function (tasks) {
</ins><span class="cx"> self._tasksForAnnotations = tasks;
</span><span class="cx"> self.render();
</span><span class="cx"> });
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentschartstylesjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -1,6 +1,6 @@
</span><span class="cx">
</span><span class="cx"> class ChartStyles {
</span><del>- static createChartSourceList(platformId, metricId)
</del><ins>+ static resolveConfiguration(platformId, metricId)
</ins><span class="cx"> {
</span><span class="cx"> var platform = Platform.findById(platformId);
</span><span class="cx"> var metric = Metric.findById(metricId);
</span><span class="lines">@@ -11,26 +11,35 @@
</span><span class="cx"> if (!lastModified)
</span><span class="cx"> return {platform: platform, metric: metric, error: `No results on ${platform.name()}`};
</span><span class="cx">
</span><del>- var measurementSet = MeasurementSet.findSet(platform.id(), metric.id(), lastModified);
- var sourceList = [
- this.baselineStyle(measurementSet, 'baseline'),
- this.targetStyle(measurementSet, 'target'),
- this.currentStyle(measurementSet, 'current'),
- ];
-
</del><span class="cx"> return {
</span><span class="cx"> platform: platform,
</span><span class="cx"> metric: metric,
</span><del>- sourceList: sourceList,
</del><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx">
</span><del>- static baselineStyle(measurementSet)
</del><ins>+ static createSourceList(platform, metric, disableSampling, includeOutlier)
</ins><span class="cx"> {
</span><ins>+ console.assert(platform instanceof Platform);
+ console.assert(metric instanceof Metric);
+
+ var lastModified = platform.lastModified(metric);
+ console.assert(lastModified);
+
+ var measurementSet = MeasurementSet.findSet(platform.id(), metric.id(), lastModified);
+ return [
+ this.baselineStyle(measurementSet, disableSampling, includeOutlier),
+ this.targetStyle(measurementSet, disableSampling, includeOutlier),
+ this.currentStyle(measurementSet, disableSampling, includeOutlier),
+ ];
+ }
+
+ static baselineStyle(measurementSet, disableSampling, includeOutlier)
+ {
</ins><span class="cx"> return {
</span><span class="cx"> measurementSet: measurementSet,
</span><span class="cx"> extendToFuture: true,
</span><del>- sampleData: true,
</del><ins>+ sampleData: !disableSampling,
+ includeOutliers: includeOutlier,
</ins><span class="cx"> type: 'baseline',
</span><span class="cx"> pointStyle: '#f33',
</span><span class="cx"> pointRadius: 2,
</span><span class="lines">@@ -41,12 +50,13 @@
</span><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx">
</span><del>- static targetStyle(measurementSet)
</del><ins>+ static targetStyle(measurementSet, disableSampling, includeOutlier)
</ins><span class="cx"> {
</span><span class="cx"> return {
</span><span class="cx"> measurementSet: measurementSet,
</span><span class="cx"> extendToFuture: true,
</span><del>- sampleData: true,
</del><ins>+ sampleData: !disableSampling,
+ includeOutliers: includeOutlier,
</ins><span class="cx"> type: 'target',
</span><span class="cx"> pointStyle: '#33f',
</span><span class="cx"> pointRadius: 2,
</span><span class="lines">@@ -57,11 +67,12 @@
</span><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx">
</span><del>- static currentStyle(measurementSet)
</del><ins>+ static currentStyle(measurementSet, disableSampling, includeOutlier)
</ins><span class="cx"> {
</span><span class="cx"> return {
</span><span class="cx"> measurementSet: measurementSet,
</span><del>- sampleData: true,
</del><ins>+ sampleData: !disableSampling,
+ includeOutliers: includeOutlier,
</ins><span class="cx"> type: 'current',
</span><span class="cx"> pointStyle: '#333',
</span><span class="cx"> pointRadius: 2,
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentsinteractivetimeserieschartjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -18,13 +18,20 @@
</span><span class="cx">
</span><span class="cx"> currentPoint(diff)
</span><span class="cx"> {
</span><del>- if (!this._sampledTimeSeriesData)
- return null;
-
</del><span class="cx"> var id = this._indicatorID;
</span><span class="cx"> if (!id)
</span><span class="cx"> return null;
</span><span class="cx">
</span><ins>+ if (!this._sampledTimeSeriesData) {
+ this._ensureFetchedTimeSeries();
+ for (var series of this._fetchedTimeSeries) {
+ var point = series.findById(id);
+ if (point)
+ return point;
+ }
+ return null;
+ }
+
</ins><span class="cx"> for (var data of this._sampledTimeSeriesData) {
</span><span class="cx"> if (!data)
</span><span class="cx"> continue;
</span><span class="lines">@@ -40,6 +47,21 @@
</span><span class="cx">
</span><span class="cx"> currentSelection() { return this._selectionTimeRange; }
</span><span class="cx">
</span><ins>+ selectedPoints(type)
+ {
+ var selection = this._selectionTimeRange;
+ return selection ? this.sampledDataBetween(type, selection[0], selection[1]) : null;
+ }
+
+ firstSelectedPoint(type)
+ {
+ var selection = this._selectionTimeRange;
+ return selection ? this.firstSampledPointBetweenTime(type, selection[0], selection[1]) : null;
+ }
+
+ lockedIndicator() { return this._indicatorIsLocked ? this.currentPoint() : null; }
+
+
</ins><span class="cx"> setIndicator(id, shouldLock)
</span><span class="cx"> {
</span><span class="cx"> var selectionDidChange = !!this._sampledTimeSeriesData;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3componentstimeserieschartjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -66,9 +66,20 @@
</span><span class="cx"> console.assert(startTime < endTime, 'startTime must be before endTime');
</span><span class="cx"> this._startTime = startTime;
</span><span class="cx"> this._endTime = endTime;
</span><ins>+ this.fetchMeasurementSets(false);
+ }
+
+ setSourceList(sourceList)
+ {
+ this._sourceList = sourceList;
+ this.fetchMeasurementSets(false);
+ }
+
+ fetchMeasurementSets(noCache)
+ {
</ins><span class="cx"> for (var source of this._sourceList) {
</span><span class="cx"> if (source.measurementSet)
</span><del>- source.measurementSet.fetchBetween(startTime, endTime, this._didFetchMeasurementSet.bind(this, source.measurementSet));
</del><ins>+ source.measurementSet.fetchBetween(this._startTime, this._endTime, this._didFetchMeasurementSet.bind(this, source.measurementSet), noCache);
</ins><span class="cx"> }
</span><span class="cx"> this._sampledTimeSeriesData = null;
</span><span class="cx"> this._valueRangeCache = null;
</span><span class="lines">@@ -103,6 +114,14 @@
</span><span class="cx"> return data.filter(function (point) { return startTime <= point.time && point.time <= endTime; });
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ firstSampledPointBetweenTime(type, startTime, endTime)
+ {
+ var data = this.sampledTimeSeriesData(type);
+ if (!data)
+ return null;
+ return data.find(function (point) { return startTime <= point.time && point.time <= endTime; });
+ }
+
</ins><span class="cx"> setAnnotations(annotations)
</span><span class="cx"> {
</span><span class="cx"> this._annotations = annotations;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsanalysistaskjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -166,9 +166,9 @@
</span><span class="cx"> return this._fetchSubset({buildRequest: id}).then(function (tasks) { return tasks[0]; });
</span><span class="cx"> }
</span><span class="cx">
</span><del>- static fetchByPlatformAndMetric(platformId, metricId)
</del><ins>+ static fetchByPlatformAndMetric(platformId, metricId, noCache)
</ins><span class="cx"> {
</span><del>- return this._fetchSubset({platform: platformId, metric: metricId}).then(function (data) {
</del><ins>+ return this._fetchSubset({platform: platformId, metric: metricId}, noCache).then(function (data) {
</ins><span class="cx"> return AnalysisTask.findByPlatformAndMetric(platformId, metricId);
</span><span class="cx"> });
</span><span class="cx"> }
</span><span class="lines">@@ -198,11 +198,11 @@
</span><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><del>- static _fetchSubset(params)
</del><ins>+ static _fetchSubset(params, noCache)
</ins><span class="cx"> {
</span><span class="cx"> if (this._fetchAllPromise)
</span><span class="cx"> return this._fetchAllPromise;
</span><del>- return this.cachedFetch('../api/analysis-tasks', params).then(this._constructAnalysisTasksFromRawData.bind(this));
</del><ins>+ return this.cachedFetch('../api/analysis-tasks', params, noCache).then(this._constructAnalysisTasksFromRawData.bind(this));
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> static fetchAll()
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsmeasurementadaptorjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/measurement-adaptor.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/measurement-adaptor.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/models/measurement-adaptor.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -28,6 +28,11 @@
</span><span class="cx"> return row[this._idIndex];
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ isOutlier(row)
+ {
+ return row[this._markedOutlierIndex];
+ }
+
</ins><span class="cx"> applyToAnalysisResults(row)
</span><span class="cx"> {
</span><span class="cx"> var adaptedRow = this.applyTo(row);
</span><span class="lines">@@ -50,6 +55,7 @@
</span><span class="cx"> var self = this;
</span><span class="cx"> return {
</span><span class="cx"> id: id,
</span><ins>+ markedOutlier: row[this._markedOutlierIndex],
</ins><span class="cx"> buildId: buildId,
</span><span class="cx"> metricId: null,
</span><span class="cx"> configType: null,
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsmeasurementclusterjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -21,7 +21,7 @@
</span><span class="cx"> var id = self._adaptor.extractId(row);
</span><span class="cx"> if (id in idMap)
</span><span class="cx"> return;
</span><del>- if (row[self._markedOutlierIndex] && !includeOutliers)
</del><ins>+ if (self._adaptor.isOutlier(row) && !includeOutliers)
</ins><span class="cx"> return;
</span><span class="cx">
</span><span class="cx"> idMap[id] = true;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsmeasurementsetjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -55,10 +55,14 @@
</span><span class="cx"> return clusters;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- fetchBetween(startTime, endTime, callback)
</del><ins>+ fetchBetween(startTime, endTime, callback, noCache)
</ins><span class="cx"> {
</span><del>- if (!this._primaryClusterPromise)
- this._primaryClusterPromise = this._fetchPrimaryCluster();
</del><ins>+ if (noCache) {
+ this._primaryClusterPromise = null;
+ this._allFetches = {};
+ }
+ if (!this._primaryClusterPromise || noCache)
+ this._primaryClusterPromise = this._fetchPrimaryCluster(noCache);
</ins><span class="cx"> var self = this;
</span><span class="cx"> this._primaryClusterPromise.catch(callback);
</span><span class="cx"> return this._primaryClusterPromise.then(function () {
</span><span class="lines">@@ -86,8 +90,16 @@
</span><span class="cx"> return url;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- _fetchPrimaryCluster() {
</del><ins>+ _fetchPrimaryCluster(noCache)
+ {
</ins><span class="cx"> var self = this;
</span><ins>+ if (noCache) {
+ return RemoteAPI.getJSONWithStatus(self._constructUrl(false, null)).then(function (data) {
+ self._didFetchJSON(true, data);
+ self._allFetches[self._primaryClusterEndTime] = self._primaryClusterPromise;
+ });
+ }
+
</ins><span class="cx"> return RemoteAPI.getJSONWithStatus(self._constructUrl(true, null)).then(function (data) {
</span><span class="cx"> if (+data['lastModified'] < self._lastModified)
</span><span class="cx"> return RemoteAPI.getJSONWithStatus(self._constructUrl(false, null));
</span><span class="lines">@@ -102,7 +114,8 @@
</span><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><del>- _fetchSecondaryCluster(endTime) {
</del><ins>+ _fetchSecondaryCluster(endTime)
+ {
</ins><span class="cx"> var self = this;
</span><span class="cx"> return RemoteAPI.getJSONWithStatus(self._constructUrl(true, endTime)).then(function (data) {
</span><span class="cx"> self._didFetchJSON(false, data);
</span><span class="lines">@@ -111,8 +124,6 @@
</span><span class="cx">
</span><span class="cx"> _didFetchJSON(isPrimaryCluster, response, clusterEndTime)
</span><span class="cx"> {
</span><del>- console.assert(isPrimaryCluster);
-
</del><span class="cx"> if (isPrimaryCluster) {
</span><span class="cx"> this._primaryClusterEndTime = response['endTime'];
</span><span class="cx"> this._clusterCount = response['clusterCount'];
</span><span class="lines">@@ -126,10 +137,14 @@
</span><span class="cx">
</span><span class="cx"> _addFetchedCluster(cluster)
</span><span class="cx"> {
</span><ins>+ for (var clusterIndex = 0; clusterIndex < this._sortedClusters.length; clusterIndex++) {
+ var startTime = this._sortedClusters[clusterIndex].startTime();
+ if (cluster.startTime() <= startTime) {
+ this._sortedClusters.splice(clusterIndex, startTime == cluster.startTime() ? 1 : 0, cluster);
+ return;
+ }
+ }
</ins><span class="cx"> this._sortedClusters.push(cluster);
</span><del>- this._sortedClusters = this._sortedClusters.sort(function (c1, c2) {
- return c1.startTime() - c2.startTime();
- });
</del><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> hasFetchedRange(startTime, endTime)
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelstimeseriesjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/time-series.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/time-series.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/models/time-series.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -61,13 +61,9 @@
</span><span class="cx">
</span><span class="cx"> dataBetweenPoints(firstPoint, lastPoint)
</span><span class="cx"> {
</span><del>- var data = this._data;
- var filteredData = [];
- for (var i = firstPoint.seriesIndex; i <= lastPoint.seriesIndex; i++) {
- if (!data[i].markedOutlier)
- filteredData.push(data[i]);
- }
- return filteredData;
</del><ins>+ console.assert(firstPoint.series == this);
+ console.assert(lastPoint.series == this);
+ return this._data.slice(firstPoint.seriesIndex, lastPoint.seriesIndex + 1);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> };
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pageschartpanejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -6,7 +6,7 @@
</span><span class="cx">
</span><span class="cx"> this._mainChartIndicatorWasLocked = false;
</span><span class="cx"> this._chartsPage = chartsPage;
</span><del>- this._paneOpenedByClick = null;
</del><ins>+ this._lockedPopover = null;
</ins><span class="cx">
</span><span class="cx"> this.content().querySelector('close-button').component().setCallback(chartsPage.closePane.bind(chartsPage, this));
</span><span class="cx">
</span><span class="lines">@@ -24,6 +24,16 @@
</span><span class="cx"> else if (this._mainChartIndicatorWasLocked && currentPoint)
</span><span class="cx"> state[2] = currentPoint.id;
</span><span class="cx"> }
</span><ins>+
+ var graphOptions = new Set;
+ if (!this.isSamplingEnabled())
+ graphOptions.add('noSampling');
+ if (this.isShowingOutliers())
+ graphOptions.add('showOutliers');
+
+ if (graphOptions.size)
+ state[3] = graphOptions;
+
</ins><span class="cx"> return state;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -40,6 +50,18 @@
</span><span class="cx"> this._mainChartIndicatorWasLocked = true;
</span><span class="cx"> } else
</span><span class="cx"> this._mainChart.setIndicator(null, false);
</span><ins>+
+ // FIXME: This forces sourceList to be set twice. First in configure inside the constructor then here.
+ var graphOptions = state[3];
+ if (graphOptions instanceof Set) {
+ this.setSamplingEnabled(!graphOptions.has('nosampling'));
+ this.setShowOutliers(graphOptions.has('showoutliers'));
+ }
+
+ // FIXME: Show full y-axis when graphOptions is true to be compatible with v2 UI.
+ // FIXME: state[4] specifies moving average in v2 UI
+ // FIXME: state[5] specifies envelope in v2 UI
+ // FIXME: state[6] specifies change detection algorithm in v2 UI
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> setOverviewSelection(selection)
</span><span class="lines">@@ -90,6 +112,35 @@
</span><span class="cx"> super._indicatorDidChange(indicatorID, isLocked);
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ _analyzeRange(pointsRangeForAnalysis)
+ {
+ var router = this._chartsPage.router();
+ var newWindow = window.open(router.url('analysis/task/create'), '_blank');
+
+ var analyzePopover = this.content().querySelector('.chart-pane-analyze-popover');
+ var name = analyzePopover.querySelector('input').value;
+ var self = this;
+ AnalysisTask.create(name, pointsRangeForAnalysis.startPointId, pointsRangeForAnalysis.endPointId).then(function (data) {
+ newWindow.location.href = router.url('analysis/task/' + data['taskId']);
+ self.fetchAnalysisTasks(true);
+ // FIXME: Refetch the list of analysis tasks.
+ }, function (error) {
+ newWindow.location.href = router.url('analysis/task/create', {error: error});
+ });
+ }
+
+ _markAsOutlier(markAsOutlier, points)
+ {
+ var self = this;
+ return Promise.all(points.map(function (point) {
+ return PrivilegedAPI.sendRequest('update-run-status', {'run': point.id, 'markedOutlier': markAsOutlier});
+ })).then(function () {
+ self._mainChart.fetchMeasurementSets(true /* noCache */);
+ }, function (error) {
+ alert('Failed to update the outlier status: ' + error);
+ }).catch();
+ }
+
</ins><span class="cx"> render()
</span><span class="cx"> {
</span><span class="cx"> if (this._platform && this._metric) {
</span><span class="lines">@@ -122,115 +173,140 @@
</span><span class="cx"> })));
</span><span class="cx"> }
</span><span class="cx">
</span><del>- var platformPane = this.content().querySelector('.chart-pane-alternative-platforms');
</del><ins>+ var platformPopover = this.content().querySelector('.chart-pane-alternative-platforms');
</ins><span class="cx"> var alternativePlatforms = this._chartsPage.alternatePlatforms(platform, metric);
</span><span class="cx"> if (alternativePlatforms.length) {
</span><del>- this.renderReplace(platformPane, Platform.sortByName(alternativePlatforms).map(function (platform) {
</del><ins>+ this.renderReplace(platformPopover, Platform.sortByName(alternativePlatforms).map(function (platform) {
</ins><span class="cx"> return element('li', link(platform.label(), function () {
</span><span class="cx"> self._chartsPage.insertPaneAfter(platform, metric, self);
</span><span class="cx"> }));
</span><span class="cx"> }));
</span><span class="cx">
</span><del>- actions.push(element('li', {class: this._paneOpenedByClick == platformPane ? 'selected' : ''},
- this._makeAnchorToOpenPane(platformPane, 'Other Platforms', true)));
- } else {
- platformPane.style.display = 'none';
- }
</del><ins>+ actions.push(this._makePopoverActionItem(platformPopover, 'Other Platforms', true));
+ } else
+ platformPopover.style.display = 'none';
</ins><span class="cx">
</span><del>- var analyzePane = this.content().querySelector('.chart-pane-analyze-pane');
</del><ins>+ var analyzePopover = this.content().querySelector('.chart-pane-analyze-popover');
</ins><span class="cx"> var pointsRangeForAnalysis = this._mainChartStatus.pointsRangeForAnalysis();
</span><span class="cx"> if (pointsRangeForAnalysis) {
</span><del>- actions.push(element('li', {class: this._paneOpenedByClick == analyzePane ? 'selected' : ''},
- this._makeAnchorToOpenPane(analyzePane, 'Analyze', false)));
-
- var router = this._chartsPage.router();
- analyzePane.onsubmit = function (event) {
</del><ins>+ actions.push(this._makePopoverActionItem(analyzePopover, 'Analyze', false));
+ analyzePopover.onsubmit = function (event) {
</ins><span class="cx"> event.preventDefault();
</span><del>- var newWindow = window.open(router.url('analysis/task/create'), '_blank');
-
- var name = analyzePane.querySelector('input').value;
- AnalysisTask.create(name, pointsRangeForAnalysis.startPointId, pointsRangeForAnalysis.endPointId).then(function (data) {
- newWindow.location.href = router.url('analysis/task/' + data['taskId']);
- // FIXME: Refetch the list of analysis tasks.
- }, function (error) {
- newWindow.location.href = router.url('analysis/task/create', {error: error});
- });
</del><ins>+ self._analyzeRange(pointsRangeForAnalysis);
</ins><span class="cx"> }
</span><span class="cx"> } else {
</span><del>- analyzePane.style.display = 'none';
- analyzePane.onsubmit = function (event) { event.preventDefault(); }
</del><ins>+ analyzePopover.style.display = 'none';
+ analyzePopover.onsubmit = function (event) { event.preventDefault(); }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- this._paneOpenedByClick = null;
</del><ins>+ var filteringOptions = this.content().querySelector('.chart-pane-filtering-options');
+ actions.push(this._makePopoverActionItem(filteringOptions, 'Filtering', true));
+
+ this._renderFilteringPopover();
+
+ this._lockedPopover = null;
</ins><span class="cx"> this.renderReplace(this.content().querySelector('.chart-pane-action-buttons'), actions);
</span><span class="cx"> }
</span><span class="cx">
</span><del>- _makeAnchorToOpenPane(pane, label, shouldRespondToHover)
</del><ins>+ _makePopoverActionItem(popover, label, shouldRespondToHover)
</ins><span class="cx"> {
</span><del>- var anchor = null;
- var ignoreMouseLeave = false;
</del><span class="cx"> var self = this;
</span><del>- var setPaneVisibility = function (pane, shouldShow) {
- var anchor = pane.anchor;
- if (shouldShow) {
- var width = anchor.offsetParent.offsetWidth;
- pane.style.top = anchor.offsetTop + anchor.offsetHeight + 'px';
- pane.style.right = (width - anchor.offsetLeft - anchor.offsetWidth) + 'px';
- }
- pane.style.display = shouldShow ? null : 'none';
- anchor.parentNode.className = shouldShow ? 'selected' : '';
- if (self._paneOpenedByClick == pane && !shouldShow)
- self._paneOpenedByClick = null;
</del><ins>+ popover.anchor = ComponentBase.createLink(label, function () {
+ var makeVisible = self._lockedPopover != popover;
+ self._setPopoverVisibility(popover, makeVisible);
+ if (makeVisible)
+ self._lockedPopover = popover;
+ });
+ if (shouldRespondToHover)
+ this._makePopoverOpenOnHover(popover);
+
+ return ComponentBase.createElement('li', {class: this._lockedPopover == popover ? 'selected' : ''}, popover.anchor);
+ }
+
+ _makePopoverOpenOnHover(popover)
+ {
+ var mouseIsInAnchor = false;
+ var mouseIsInPopover = false;
+
+ var self = this;
+ var closeIfNeeded = function () {
+ setTimeout(function () {
+ if (self._lockedPopover != popover && !mouseIsInAnchor && !mouseIsInPopover)
+ self._setPopoverVisibility(popover, false);
+ }, 0);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- var attributes = {
- href: '#',
- onclick: function (event) {
- event.preventDefault();
- var shouldShowPane = pane.style.display == 'none';
- if (shouldShowPane) {
- if (self._paneOpenedByClick)
- setPaneVisibility(self._paneOpenedByClick, false);
- self._paneOpenedByClick = pane;
- }
- setPaneVisibility(pane, shouldShowPane);
- },
- };
- if (shouldRespondToHover) {
- var mouseIsInAnchor = false;
- var mouseIsInPane = false;
</del><ins>+ popover.anchor.onmouseenter = function () {
+ if (self._lockedPopover)
+ return;
+ mouseIsInAnchor = true;
+ self._setPopoverVisibility(popover, true);
+ }
+ popover.anchor.onmouseleave = function () {
+ mouseIsInAnchor = false;
+ closeIfNeeded();
+ }
</ins><span class="cx">
</span><del>- attributes.onmouseenter = function () {
- if (self._paneOpenedByClick)
- return;
- mouseIsInAnchor = true;
- setPaneVisibility(pane, true);
- }
- attributes.onmouseleave = function () {
- setTimeout(function () {
- if (!mouseIsInPane)
- setPaneVisibility(pane, false);
- }, 0);
- mouseIsInAnchor = false;
- }
</del><ins>+ popover.onmouseenter = function () {
+ mouseIsInPopover = true;
+ }
+ popover.onmouseleave = function () {
+ mouseIsInPopover = false;
+ closeIfNeeded();
+ }
+ }
</ins><span class="cx">
</span><del>- pane.onmouseleave = function () {
- setTimeout(function () {
- if (!mouseIsInAnchor)
- setPaneVisibility(pane, false);
- }, 0);
- mouseIsInPane = false;
- }
- pane.onmouseenter = function () {
- mouseIsInPane = true;
- }
</del><ins>+ _setPopoverVisibility(popover, visible)
+ {
+ var anchor = popover.anchor;
+ if (visible) {
+ var width = anchor.offsetParent.offsetWidth;
+ popover.style.top = anchor.offsetTop + anchor.offsetHeight + 'px';
+ popover.style.right = (width - anchor.offsetLeft - anchor.offsetWidth) + 'px';
</ins><span class="cx"> }
</span><ins>+ popover.style.display = visible ? null : 'none';
+ anchor.parentNode.className = visible ? 'selected' : '';
</ins><span class="cx">
</span><del>- var anchor = ComponentBase.createElement('a', attributes, label);
- pane.anchor = anchor;
- return anchor;
</del><ins>+ if (this._lockedPopover && this._lockedPopover != popover && visible)
+ this._setPopoverVisibility(this._lockedPopover, false);
+
+ if (this._lockedPopover == popover && !visible)
+ this._lockedPopover = null;
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+ _renderFilteringPopover()
+ {
+ var enableSampling = this.content().querySelector('.enable-sampling');
+ enableSampling.checked = this.isSamplingEnabled();
+ enableSampling.onchange = function () {
+ self.setSamplingEnabled(enableSampling.checked);
+ self._chartsPage.graphOptionsDidChange();
+ }
+
+ var showOutliers = this.content().querySelector('.show-outliers');
+ showOutliers.checked = this.isShowingOutliers();
+ showOutliers.onchange = function () {
+ self.setShowOutliers(showOutliers.checked);
+ self._chartsPage.graphOptionsDidChange();
+ }
+
+ var markAsOutlierButton = this.content().querySelector('.mark-as-outlier');
+ var firstSelectedPoint = this._mainChart.lockedIndicator();
+ if (!firstSelectedPoint)
+ firstSelectedPoint = this._mainChart.firstSelectedPoint('current');
+ var alreayMarkedAsOutlier = firstSelectedPoint && firstSelectedPoint.markedOutlier;
+
+ var self = this;
+ markAsOutlierButton.textContent = (alreayMarkedAsOutlier ? 'Unmark' : 'Mark') + ' selected points as outlier';
+ markAsOutlierButton.onclick = function () {
+ var selectedPoints = [firstSelectedPoint];
+ if (self._mainChart.currentSelection('current'))
+ selectedPoints = self._mainChart.selectedPoints('current');
+ self._markAsOutlier(!alreayMarkedAsOutlier, selectedPoints);
+ }
+ markAsOutlierButton.disabled = !firstSelectedPoint;
+ }
+
</ins><span class="cx"> static paneHeaderTemplate()
</span><span class="cx"> {
</span><span class="cx"> return `
</span><span class="lines">@@ -241,11 +317,16 @@
</span><span class="cx"> <li class="close"><close-button></close-button></li>
</span><span class="cx"> </ul>
</span><span class="cx"> <ul class="chart-pane-action-buttons buttoned-toolbar"></ul>
</span><del>- <ul class="chart-pane-alternative-platforms" style="display:none"></ul>
- <form class="chart-pane-analyze-pane" style="display:none">
</del><ins>+ <ul class="chart-pane-alternative-platforms popover" style="display:none"></ul>
+ <form class="chart-pane-analyze-popover popover" style="display:none">
</ins><span class="cx"> <input type="text" required>
</span><span class="cx"> <button>Create</button>
</span><span class="cx"> </form>
</span><ins>+ <ul class="chart-pane-filtering-options popover" style="display:none">
+ <li><label><input type="checkbox" class="enable-sampling">Sampling</label></li>
+ <li><label><input type="checkbox" class="show-outliers">Show outliers</label></li>
+ <li><button class="mark-as-outlier">Mark selected points as outlier</button></li>
+ </ul>
</ins><span class="cx"> </nav>
</span><span class="cx"> </header>
</span><span class="cx"> `;
</span><span class="lines">@@ -309,8 +390,7 @@
</span><span class="cx"> line-height: 0.9rem;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- .chart-pane-actions .chart-pane-alternative-platforms,
- .chart-pane-analyze-pane {
</del><ins>+ .chart-pane-actions .popover {
</ins><span class="cx"> position: absolute;
</span><span class="cx"> top: 0;
</span><span class="cx"> right: 0;
</span><span class="lines">@@ -325,10 +405,10 @@
</span><span class="cx"> margin-right: -0.2rem;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- .chart-pane-alternative-platforms li {
</del><ins>+ .chart-pane-actions .popover li {
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- .chart-pane-alternative-platforms li a {
</del><ins>+ .chart-pane-actions .popover li a {
</ins><span class="cx"> display: block;
</span><span class="cx"> text-decoration: none;
</span><span class="cx"> color: inherit;
</span><span class="lines">@@ -336,16 +416,20 @@
</span><span class="cx"> padding: 0.2rem 0.5rem;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- .chart-pane-alternative-platforms a:hover,
- .chart-pane-analyze-pane input:focus {
</del><ins>+ .chart-pane-actions .popover a:hover,
+ .chart-pane-actions .popover input:focus {
</ins><span class="cx"> background: rgba(204, 153, 51, 0.1);
</span><span class="cx"> }
</span><span class="cx">
</span><del>- .chart-pane-analyze-pane {
</del><ins>+ .chart-pane-actions .chart-pane-analyze-popover {
</ins><span class="cx"> padding: 0.5rem;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- .chart-pane-analyze-pane input {
</del><ins>+ .chart-pane-actions .popover label {
+ font-size: 0.9rem;
+ }
+
+ .chart-pane-actions .popover input[type=text] {
</ins><span class="cx"> font-size: 1rem;
</span><span class="cx"> width: 15rem;
</span><span class="cx"> outline: none;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pageschartspagejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -185,6 +185,11 @@
</span><span class="cx"> this.scheduleUrlStateUpdate();
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ graphOptionsDidChange(pane)
+ {
+ this.scheduleUrlStateUpdate();
+ }
+
</ins><span class="cx"> setOpenRepository(repository)
</span><span class="cx"> {
</span><span class="cx"> this._currentRepositoryId = repository ? repository.id() : null;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pagesdashboardpagejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -125,13 +125,13 @@
</span><span class="cx"> if (!platformId || !metricId)
</span><span class="cx"> return '';
</span><span class="cx">
</span><del>- var result = ChartStyles.createChartSourceList(platformId, metricId);
</del><ins>+ var result = ChartStyles.resolveConfiguration(platformId, metricId);
</ins><span class="cx"> if (result.error)
</span><span class="cx"> return result.error;
</span><span class="cx">
</span><span class="cx"> var options = ChartStyles.dashboardOptions(result.metric.makeFormatter(3));
</span><span class="cx"> options.ondata = this._fetchedData.bind(this);
</span><del>- var chart = new TimeSeriesChart(result.sourceList, options);
</del><ins>+ var chart = new TimeSeriesChart(ChartStyles.createSourceList(result.platform, result.metric, false, false), options);
</ins><span class="cx"> this._charts.push(chart);
</span><span class="cx">
</span><span class="cx"> var statusView = new ChartStatusView(result.metric, chart);
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3pagespagerouterjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/pages/page-router.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/pages/page-router.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/page-router.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -127,34 +127,50 @@
</span><span class="cx">
</span><span class="cx"> _serializeHashQueryValue(value)
</span><span class="cx"> {
</span><del>- if (!(value instanceof Array)) {
- console.assert(value === null || typeof(value) === 'number' || /[A-Za-z0-9]*/.test(value));
- return value === null ? 'null' : value;
</del><ins>+ if (value instanceof Array) {
+ var serializedItems = [];
+ for (var item of value)
+ serializedItems.push(this._serializeHashQueryValue(item));
+ return '(' + serializedItems.join('-') + ')';
</ins><span class="cx"> }
</span><del>-
- var serializedItems = [];
- for (var item of value)
- serializedItems.push(this._serializeHashQueryValue(item));
- return '(' + serializedItems.join('-') + ')';
</del><ins>+ if (value instanceof Set)
+ return Array.from(value).sort().join('|');
+ console.assert(value === null || value === undefined || typeof(value) === 'number' || /[0-9]*/.test(value));
+ return value === null || value === undefined ? 'null' : value;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> _deserializeHashQueryValue(value)
</span><span class="cx"> {
</span><del>- var json = value.replace(/\(/g, '[').replace(/\)/g, ']').replace(/-/g, ',');
- try {
- return JSON.parse(json);
- } catch (error) {
-
- // Some applications don't linkify two consecutive closing parentheses: )).
- // Try fixing adding one extra parenthesis to see if that works.
- var missingClosingBrackets = this._countOccurrences(json, /\[/g) - this._countOccurrences(json, /\]/g);
- var fix = new Array(missingClosingBrackets).fill(']').join('');
- try {
- return JSON.parse(json + fix);
- } catch (newError) { }
-
- return value;
</del><ins>+ if (value.charAt(0) == '(') {
+ var nestingLevel = 0;
+ var end = 0;
+ var start = 1;
+ var result = [];
+ for (var character of value) {
+ if (character == '(')
+ nestingLevel++;
+ else if (character == ')') {
+ nestingLevel--;
+ if (!nestingLevel)
+ break;
+ } else if (nestingLevel == 1 && character == '-') {
+ result.push(this._deserializeHashQueryValue(value.substring(start, end)));
+ start = end + 1;
+ }
+ end++;
+ }
+ result.push(this._deserializeHashQueryValue(value.substring(start, end)));
+ return result;
</ins><span class="cx"> }
</span><ins>+ if (value == 'true')
+ return true;
+ if (value == 'false')
+ return true;
+ if (value.match(/^[0-9\.]+$/))
+ return parseFloat(value);
+ if (value.match(/^[A-Za-z][A-Za-z0-9|]*$/))
+ return new Set(value.toLowerCase().split('|'));
+ return null;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> _countOccurrences(string, regex)
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgunittestsmeasurementsettestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js (201563 => 201564)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js        2016-06-01 19:47:19 UTC (rev 201563)
+++ trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js        2016-06-01 19:55:38 UTC (rev 201564)
</span><span class="lines">@@ -290,6 +290,100 @@
</span><span class="cx"> });
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+ it('should request the uncached primary cluster when noCache is true', function (done) {
+ var set = MeasurementSet.findSet(1, 1, 3000);
+ var callCount = 0;
+ set.fetchBetween(1000, 3000, function () {
+ callCount++;
+ });
+ assert.equal(requests.length, 1);
+ assert.equal(requests[0].url, '../data/measurement-set-1-1.json');
+
+ requests[0].resolve({
+ 'clusterStart': 1000,
+ 'clusterSize': 1000,
+ 'formatMap': [],
+ 'configurations': {current: []},
+ 'startTime': 2000,
+ 'endTime': 3000,
+ 'lastModified': 3000,
+ 'clusterCount': 2,
+ 'status': 'OK'});
+
+ var noCacheFetchCount = 0;
+ waitForMeasurementSet().then(function () {
+ assert.equal(callCount, 1);
+ assert.equal(noCacheFetchCount, 0);
+ assert.equal(set._sortedClusters.length, 1);
+ assert.equal(requests.length, 2);
+ assert.equal(requests[1].url, '../data/measurement-set-1-1-2000.json');
+
+ requests[1].resolve({
+ 'clusterStart': 1000,
+ 'clusterSize': 1000,
+ 'formatMap': [],
+ 'configurations': {current: []},
+ 'startTime': 1000,
+ 'endTime': 2000,
+ 'lastModified': 3000,
+ 'clusterCount': 2,
+ 'status': 'OK'});
+
+ set.fetchBetween(1000, 3000, function () {
+ noCacheFetchCount++;
+ }, true /* noCache */);
+
+ return waitForMeasurementSet();
+ }).then(function () {
+ assert.equal(callCount, 2);
+ assert.equal(noCacheFetchCount, 0);
+ assert.equal(set._sortedClusters.length, 2);
+ assert.equal(requests.length, 3);
+ assert.equal(requests[2].url, '../api/measurement-set?platform=1&metric=1');
+
+ requests[2].resolve({
+ 'clusterStart': 1000,
+ 'clusterSize': 1000,
+ 'formatMap': [],
+ 'configurations': {current: []},
+ 'startTime': 2000,
+ 'endTime': 3000,
+ 'lastModified': 3000,
+ 'clusterCount': 2,
+ 'status': 'OK'});
+
+ return waitForMeasurementSet();
+ }).then(function () {
+ assert.equal(callCount, 2);
+ assert.equal(noCacheFetchCount, 1);
+ assert.equal(set._sortedClusters.length, 2);
+ assert.equal(requests.length, 4);
+ assert.equal(requests[3].url, '../data/measurement-set-1-1-2000.json');
+
+ requests[3].resolve({
+ 'clusterStart': 1000,
+ 'clusterSize': 1000,
+ 'formatMap': [],
+ 'configurations': {current: []},
+ 'startTime': 1000,
+ 'endTime': 2000,
+ 'lastModified': 3000,
+ 'clusterCount': 2,
+ 'status': 'OK'});
+
+ return waitForMeasurementSet();
+ }).then(function () {
+ assert.equal(callCount, 2);
+ assert.equal(noCacheFetchCount, 2);
+ assert.equal(set._sortedClusters.length, 2);
+ assert.equal(requests.length, 4);
+
+ done();
+ }).catch(function (error) {
+ done(error);
+ });
+ });
+
</ins><span class="cx"> it('should not request the primary cluster twice when multiple clients request it but should invoke all callbacks', function (done) {
</span><span class="cx"> var set = MeasurementSet.findSet(1, 1, 3000);
</span><span class="cx"> var callCount = 0;
</span></span></pre>
</div>
</div>
</body>
</html>