⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 201564 in webkit


Ignore:
Timestamp:
Jun 1, 2016, 12:55:38 PM (10 years ago)
Author:
rniwa@webkit.org
Message:

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.
Location:
trunk/Websites/perf.webkit.org
Files:
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/Websites/perf.webkit.org/ChangeLog

    r201337 r201564  
     12016-05-31  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        v3 UI should support marking and unmarking outliers as well as hiding them
     4        https://bugs.webkit.org/show_bug.cgi?id=158248
     5
     6        Rubber-stamped by Chris Dumez.
     7
     8        Added the support for marking and unmarking a sequence of points as outliers. Unlike v2, we now support marking
     9        multiple points as outliers in a single click. Also fixed a bug that outliers are never explicitly hidden in v3 UI.
     10
     11        This patch splits ChartStyles.createChartSourceList into two functions: resolveConfiguration and createSourceList
     12        to separate the work of resolving platform and metric IDs to their respective model objects, and creating a source
     13        list used by TimeSeriesChart to fetch measurement sets. createSourceList is called again when filtering options are
     14        changed.
     15
     16        It also adds noCache option to TimeSeriesChart's fetchMeasurementSets, MeasurementSet's fetchBetween and
     17        _fetchPrimaryCluster to update the measurement sets after marking or unmarking points as outliers. In addition, it
     18        fixes a bug that the annotation bars for analysis tasks are not updated in charts page after creating an analysis
     19        task by adding noCache option to ChartPaneBase's fetchAnalysisTasks, AnalysisTask's fetchByPlatformAndMetric and
     20        _fetchSubset.
     21
     22        Finally, this patch splits ChartPane._makeAnchorToOpenPane into _makePopoverActionItem, _makePopoverOpenOnHover and
     23        _setPopoverVisibility for clarity.
     24
     25        * public/v3/components/chart-pane-base.js:
     26        (ChartPaneBase): Added _disableSampling and _showOutliers as instance variables.
     27        (ChartPaneBase.prototype.configure):
     28        (ChartPaneBase.prototype.isSamplingEnabled): Added.
     29        (ChartPaneBase.prototype.setSamplingEnabled): Added. When a filtering option is updated, recreate the source list
     30        so that TimeSeriesChart.setSourceList can re-fetch the measurement set JSONs.
     31        (ChartPaneBase.prototype.isShowingOutliers): Added.
     32        (ChartPaneBase.prototype.setShowOutliers): Added. Ditto for calling _updateSourceList.
     33        (ChartPaneBase.prototype._updateSourceList): Added.
     34        (ChartPaneBase.prototype.fetchAnalysisTasks): Renamed from _fetchAnalysisTasks. Now takes noCache as an argument
     35        instead of platform and metric IDs since they're on instance variables.
     36
     37        * public/v3/components/chart-styles.js:
     38        (ChartStyles.resolveConfiguration): Renamed from createChartSourceList. Just resolves platform and metric IDs.
     39        (ChartStyles.createSourceList): Extracted from createChartSourceList since it needs to be called when a filtering
     40        option is changed as well as when ChartPaneBase.prototype.configure is called.
     41        (ChartStyles.baselineStyle): Now takes filtering options.
     42        (ChartStyles.targetStyle): Ditto.
     43        (ChartStyles.currentStyle): Ditto.
     44
     45        * public/v3/components/interactive-time-series-chart.js:
     46        (InteractiveTimeSeriesChart.prototype.currentPoint): Find the point in _fetchedTimeSeries when
     47        _sampledTimeSeriesData hasn't been computed yet as a fallback (e.g. when the chart hasn't been rendered yet).
     48        (InteractiveTimeSeriesChart.prototype.selectedPoints): Added.
     49        (InteractiveTimeSeriesChart.prototype.firstSelectedPoint): Added.
     50        (InteractiveTimeSeriesChart.prototype.lockedIndicator): Added. Returns the current point if it's locked.
     51
     52        * public/v3/components/time-series-chart.js:
     53        (TimeSeriesChart.prototype.setDomain):
     54        (TimeSeriesChart.prototype.setSourceList): Added. Re-create _fetchedTimeSeries when filtering options have changed.
     55        Don't re-fetch measurement set JSONs here since showing outliers can be done entirely in the front end.
     56        (TimeSeriesChart.prototype.fetchMeasurementSets): Extracted out of setDomain. Now takes noCache as an argument.
     57        ChartPane._markAsOutlier
     58        (TimeSeriesChart.prototype.firstSampledPointBetweenTime): Added.
     59
     60        * public/v3/models/analysis-task.js:
     61        (AnalysisTask.fetchByPlatformAndMetric): Added noCache as an argument.
     62        (AnalysisTask._fetchSubset): Ditto.
     63
     64        * public/v3/models/measurement-adaptor.js:
     65        (MeasurementAdaptor.prototype.isOutlier): Added.
     66        (MeasurementAdaptor.prototype.applyToAnalysisResults): Add markedOutlier as a property on each point.
     67
     68        * public/v3/models/measurement-cluster.js:
     69        (MeasurementCluster.prototype.addToSeries): Fixed the bug that filtering outliers was broken as _markedOutlierIndex
     70        is undefined here. Use MeasurementAdaptor's isOutlier instead.
     71
     72        * public/v3/models/measurement-set.js:
     73        (MeasurementSet.prototype.fetchBetween): Added noCache as an argument. Reset _primaryClusterPromise and _allFetches
     74        when noCache is true since we need to re-fetch the primary cluster as well as all secondary clusters now.
     75        (MeasurementSet.prototype._fetchPrimaryCluster): Added noCache as an argument. Directly invoke the JSON API at
     76        /api/measurement-set to re-generate all clusters' JSON files instead of first fetching the cached version.
     77        (MeasurementSet.prototype._fetchSecondaryCluster):
     78        (MeasurementSet.prototype._didFetchJSON): Removed a bogus assertion since this function is called on secondary
     79        clusters as well as primary clusters.
     80        (MeasurementSet.prototype._addFetchedCluster): Reimplemented this function using an insertion sort. Also remove the
     81        existing entry if the fetch cluster should replace it.
     82
     83        * public/v3/models/time-series.js:
     84        (TimeSeries.prototype.dataBetweenPoints): Removed the dead code to filter out outliers. This is done in addToSeries
     85        of MeasurementCluster instead.
     86
     87        * public/v3/pages/chart-pane.js:
     88        (ChartPane): Renamed pane to popover since it was confusing to have a pane inside a pane class. As such, renamed
     89        _paneOpenedByClick to _lockedPopover.
     90        (ChartPane.prototype.serializeState): Added the code to serialize filtering options in the serialized state URL.
     91        (ChartPane.prototype.updateFromSerializedState): Ditto for parsing.
     92        (ChartPane.prototype._analyzeRange): Extracted out of render(). Also fixed a bug that the charts page don't show
     93        the newly created analysis task by invoking fetchAnalysisTasks with noCache set to true.
     94        (ChartPane.prototype._markAsOutlier): Added.
     95        (ChartPane.prototype._renderActionToolbar): A bunch of changes due to pane -> popover rename. Also added a popover
     96        for filtering options.
     97        (ChartPane.prototype._makePopoverActionItem): Extracted from _makeAnchorToOpenPane.
     98        (ChartPane.prototype._makePopoverOpenOnHover): Ditto.
     99        (ChartPane.prototype._setPopoverVisibility): Ditto.
     100        (ChartPane.prototype._renderFilteringPopover): Added.
     101        (ChartPane.htmlTemplate): Added a popover for specifying filtering options. Also added .popover on each popover.
     102        (ChartPane.cssTemplate): Updated the style to make use of .popover.
     103
     104        * public/v3/pages/charts-page.js:
     105        (ChartsPage.prototype.graphOptionsDidChange): Added. Updates the URL state when a filtering option is modified.
     106
     107        * public/v3/pages/dashboard-page.js:
     108        (DashboardPage.prototype._createChartForCell):
     109
     110        * public/v3/pages/page-router.js:
     111        (PageRouter.prototype._serializeHashQueryValue): Serialize a set of strings as | separated tokens.
     112        (PageRouter.prototype._deserializeHashQueryValue): Rewrote the function as the serialized URL can no longer be
     113        parsed as a JSON as | separated tokens can't be converted into a valid JSON construct with a simple regex.
     114
     115        * unit-tests/measurement-set-tests.js: Added a test case for fetchBetween with noCache=true.
     116
    11172016-05-24  Ryosuke Niwa  <rniwa@webkit.org>
    2118
  • trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js

    r198598 r201564  
    1111        this._platform = null;
    1212        this._metric = null;
     13        this._disableSampling = false;
     14        this._showOutliers = false;
    1315
    1416        this._overviewChart = null;
     
    2123    configure(platformId, metricId)
    2224    {
    23         var result = ChartStyles.createChartSourceList(platformId, metricId);
     25        var result = ChartStyles.resolveConfiguration(platformId, metricId);
    2426        this._errorMessage = result.error;
    2527        this._platformId = platformId;
     
    4042        var self = this;
    4143
     44        var sourceList = ChartStyles.createSourceList(this._platform, this._metric, this._disableSampling, this._showOutliers);
     45
    4246        var overviewOptions = ChartStyles.overviewChartOptions(formatter);
    4347        overviewOptions.selection.onchange = this._overviewSelectionDidChange.bind(this);
    44 
    45         this._overviewChart = new InteractiveTimeSeriesChart(result.sourceList, overviewOptions);
     48        this._overviewChart = new InteractiveTimeSeriesChart(sourceList, overviewOptions);
    4649        this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart);
    4750
     
    5255        mainOptions.annotations.onclick = this._openAnalysisTask.bind(this);
    5356        mainOptions.ondata = this._didFetchData.bind(this);
    54         this._mainChart = new InteractiveTimeSeriesChart(result.sourceList, mainOptions);
     57        this._mainChart = new InteractiveTimeSeriesChart(sourceList, mainOptions);
    5558        this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
    5659
     
    6063        this.content().querySelector('.chart-pane').addEventListener('keyup', this._keyup.bind(this));
    6164
    62         this._fetchAnalysisTasks(platformId, metricId);
    63     }
    64 
    65     _fetchAnalysisTasks(platformId, metricId)
     65        this.fetchAnalysisTasks(false);
     66    }
     67
     68    isSamplingEnabled() { return !this._disableSampling; }
     69    setSamplingEnabled(enabled)
     70    {
     71        this._disableSampling = !enabled;
     72        this._updateSourceList();
     73    }
     74
     75    isShowingOutliers() { return this._showOutliers; }
     76    setShowOutliers(show)
     77    {
     78        this._showOutliers = !!show;
     79        this._updateSourceList();
     80    }
     81
     82    _updateSourceList()
     83    {
     84        var sourceList = ChartStyles.createSourceList(this._platform, this._metric, this._disableSampling, this._showOutliers);
     85        this._mainChart.setSourceList(sourceList);
     86        this._overviewChart.setSourceList(sourceList);
     87    }
     88
     89    fetchAnalysisTasks(noCache)
    6690    {
    6791        // FIXME: we need to update the annotation bars when the change type of tasks change.
    6892        var self = this;
    69         AnalysisTask.fetchByPlatformAndMetric(platformId, metricId).then(function (tasks) {
     93        AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId, noCache).then(function (tasks) {
    7094            self._tasksForAnnotations = tasks;
    7195            self.render();
  • trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js

    r196387 r201564  
    11
    22class ChartStyles {
    3     static createChartSourceList(platformId, metricId)
     3    static resolveConfiguration(platformId, metricId)
    44    {
    55        var platform = Platform.findById(platformId);
     
    1212            return {platform: platform, metric: metric, error: `No results on ${platform.name()}`};
    1313
    14         var measurementSet = MeasurementSet.findSet(platform.id(), metric.id(), lastModified);
    15         var sourceList = [
    16             this.baselineStyle(measurementSet, 'baseline'),
    17             this.targetStyle(measurementSet, 'target'),
    18             this.currentStyle(measurementSet, 'current'),
    19         ];
    20 
    2114        return {
    2215            platform: platform,
    2316            metric: metric,
    24             sourceList: sourceList,
    2517        };
    2618    }
    2719
    28     static baselineStyle(measurementSet)
     20    static createSourceList(platform, metric, disableSampling, includeOutlier)
     21    {
     22        console.assert(platform instanceof Platform);
     23        console.assert(metric instanceof Metric);
     24
     25        var lastModified = platform.lastModified(metric);
     26        console.assert(lastModified);
     27
     28        var measurementSet = MeasurementSet.findSet(platform.id(), metric.id(), lastModified);
     29        return [
     30            this.baselineStyle(measurementSet, disableSampling, includeOutlier),
     31            this.targetStyle(measurementSet, disableSampling, includeOutlier),
     32            this.currentStyle(measurementSet, disableSampling, includeOutlier),
     33        ];
     34    }
     35
     36    static baselineStyle(measurementSet, disableSampling, includeOutlier)
    2937    {
    3038        return {
    3139            measurementSet: measurementSet,
    3240            extendToFuture: true,
    33             sampleData: true,
     41            sampleData: !disableSampling,
     42            includeOutliers: includeOutlier,
    3443            type: 'baseline',
    3544            pointStyle: '#f33',
     
    4251    }
    4352
    44     static targetStyle(measurementSet)
     53    static targetStyle(measurementSet, disableSampling, includeOutlier)
    4554    {
    4655        return {
    4756            measurementSet: measurementSet,
    4857            extendToFuture: true,
    49             sampleData: true,
     58            sampleData: !disableSampling,
     59            includeOutliers: includeOutlier,
    5060            type: 'target',
    5161            pointStyle: '#33f',
     
    5868    }
    5969
    60     static currentStyle(measurementSet)
     70    static currentStyle(measurementSet, disableSampling, includeOutlier)
    6171    {
    6272        return {
    6373            measurementSet: measurementSet,
    64             sampleData: true,
     74            sampleData: !disableSampling,
     75            includeOutliers: includeOutlier,
    6576            type: 'current',
    6677            pointStyle: '#333',
  • trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js

    r198518 r201564  
    1919    currentPoint(diff)
    2020    {
    21         if (!this._sampledTimeSeriesData)
    22             return null;
    23 
    2421        var id = this._indicatorID;
    2522        if (!id)
    2623            return null;
     24
     25        if (!this._sampledTimeSeriesData) {
     26            this._ensureFetchedTimeSeries();
     27            for (var series of this._fetchedTimeSeries) {
     28                var point = series.findById(id);
     29                if (point)
     30                    return point;
     31            }
     32            return null;
     33        }
    2734
    2835        for (var data of this._sampledTimeSeriesData) {
     
    4047
    4148    currentSelection() { return this._selectionTimeRange; }
     49
     50    selectedPoints(type)
     51    {
     52        var selection = this._selectionTimeRange;
     53        return selection ? this.sampledDataBetween(type, selection[0], selection[1]) : null;
     54    }
     55
     56    firstSelectedPoint(type)
     57    {
     58        var selection = this._selectionTimeRange;
     59        return selection ? this.firstSampledPointBetweenTime(type, selection[0], selection[1]) : null;
     60    }
     61
     62    lockedIndicator() { return this._indicatorIsLocked ? this.currentPoint() : null; }
     63
    4264
    4365    setIndicator(id, shouldLock)
  • trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js

    r198464 r201564  
    6767        this._startTime = startTime;
    6868        this._endTime = endTime;
     69        this.fetchMeasurementSets(false);
     70    }
     71
     72    setSourceList(sourceList)
     73    {
     74        this._sourceList = sourceList;
     75        this.fetchMeasurementSets(false);
     76    }
     77
     78    fetchMeasurementSets(noCache)
     79    {
    6980        for (var source of this._sourceList) {
    7081            if (source.measurementSet)
    71                 source.measurementSet.fetchBetween(startTime, endTime, this._didFetchMeasurementSet.bind(this, source.measurementSet));
     82                source.measurementSet.fetchBetween(this._startTime, this._endTime, this._didFetchMeasurementSet.bind(this, source.measurementSet), noCache);
    7283        }
    7384        this._sampledTimeSeriesData = null;
     
    102113            return null;
    103114        return data.filter(function (point) { return startTime <= point.time && point.time <= endTime; });
     115    }
     116
     117    firstSampledPointBetweenTime(type, startTime, endTime)
     118    {
     119        var data = this.sampledTimeSeriesData(type);
     120        if (!data)
     121            return null;
     122        return data.find(function (point) { return startTime <= point.time && point.time <= endTime; });
    104123    }
    105124
  • trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js

    r198479 r201564  
    167167    }
    168168
    169     static fetchByPlatformAndMetric(platformId, metricId)
    170     {
    171         return this._fetchSubset({platform: platformId, metric: metricId}).then(function (data) {
     169    static fetchByPlatformAndMetric(platformId, metricId, noCache)
     170    {
     171        return this._fetchSubset({platform: platformId, metric: metricId}, noCache).then(function (data) {
    172172            return AnalysisTask.findByPlatformAndMetric(platformId, metricId);
    173173        });
     
    199199    }
    200200
    201     static _fetchSubset(params)
     201    static _fetchSubset(params, noCache)
    202202    {
    203203        if (this._fetchAllPromise)
    204204            return this._fetchAllPromise;
    205         return this.cachedFetch('../api/analysis-tasks', params).then(this._constructAnalysisTasksFromRawData.bind(this));
     205        return this.cachedFetch('../api/analysis-tasks', params, noCache).then(this._constructAnalysisTasksFromRawData.bind(this));
    206206    }
    207207
  • trunk/Websites/perf.webkit.org/public/v3/models/measurement-adaptor.js

    r198462 r201564  
    2929    }
    3030
     31    isOutlier(row)
     32    {
     33        return row[this._markedOutlierIndex];
     34    }
     35
    3136    applyToAnalysisResults(row)
    3237    {
     
    5156        return {
    5257            id: id,
     58            markedOutlier: row[this._markedOutlierIndex],
    5359            buildId: buildId,
    5460            metricId: null,
  • trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js

    r198462 r201564  
    2222            if (id in idMap)
    2323                return;
    24             if (row[self._markedOutlierIndex] && !includeOutliers)
     24            if (self._adaptor.isOutlier(row) && !includeOutliers)
    2525                return;
    2626
  • trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js

    r200449 r201564  
    5656    }
    5757
    58     fetchBetween(startTime, endTime, callback)
     58    fetchBetween(startTime, endTime, callback, noCache)
    5959    {
    60         if (!this._primaryClusterPromise)
    61             this._primaryClusterPromise = this._fetchPrimaryCluster();
     60        if (noCache) {
     61            this._primaryClusterPromise = null;
     62            this._allFetches = {};
     63        }
     64        if (!this._primaryClusterPromise || noCache)
     65            this._primaryClusterPromise = this._fetchPrimaryCluster(noCache);
    6266        var self = this;
    6367        this._primaryClusterPromise.catch(callback);
     
    8791    }
    8892
    89     _fetchPrimaryCluster() {
     93    _fetchPrimaryCluster(noCache)
     94    {
    9095        var self = this;
     96        if (noCache) {
     97            return RemoteAPI.getJSONWithStatus(self._constructUrl(false, null)).then(function (data) {
     98                self._didFetchJSON(true, data);
     99                self._allFetches[self._primaryClusterEndTime] = self._primaryClusterPromise;
     100            });
     101        }
     102
    91103        return RemoteAPI.getJSONWithStatus(self._constructUrl(true, null)).then(function (data) {
    92104            if (+data['lastModified'] < self._lastModified)
     
    103115    }
    104116
    105     _fetchSecondaryCluster(endTime) {
     117    _fetchSecondaryCluster(endTime)
     118    {
    106119        var self = this;
    107120        return RemoteAPI.getJSONWithStatus(self._constructUrl(true, endTime)).then(function (data) {
     
    112125    _didFetchJSON(isPrimaryCluster, response, clusterEndTime)
    113126    {
    114         console.assert(isPrimaryCluster);
    115 
    116127        if (isPrimaryCluster) {
    117128            this._primaryClusterEndTime = response['endTime'];
     
    127138    _addFetchedCluster(cluster)
    128139    {
     140        for (var clusterIndex = 0; clusterIndex < this._sortedClusters.length; clusterIndex++) {
     141            var startTime = this._sortedClusters[clusterIndex].startTime();
     142            if (cluster.startTime() <= startTime) {
     143                this._sortedClusters.splice(clusterIndex, startTime == cluster.startTime() ? 1 : 0, cluster);
     144                return;
     145            }
     146        }
    129147        this._sortedClusters.push(cluster);
    130         this._sortedClusters = this._sortedClusters.sort(function (c1, c2) {
    131             return c1.startTime() - c2.startTime();
    132         });
    133148    }
    134149
  • trunk/Websites/perf.webkit.org/public/v3/models/time-series.js

    r198462 r201564  
    6262    dataBetweenPoints(firstPoint, lastPoint)
    6363    {
    64         var data = this._data;
    65         var filteredData = [];
    66         for (var i = firstPoint.seriesIndex; i <= lastPoint.seriesIndex; i++) {
    67             if (!data[i].markedOutlier)
    68                 filteredData.push(data[i]);
    69         }
    70         return filteredData;
     64        console.assert(firstPoint.series == this);
     65        console.assert(lastPoint.series == this);
     66        return this._data.slice(firstPoint.seriesIndex, lastPoint.seriesIndex + 1);
    7167    }
    7268
  • trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js

    r201309 r201564  
    77        this._mainChartIndicatorWasLocked = false;
    88        this._chartsPage = chartsPage;
    9         this._paneOpenedByClick = null;
     9        this._lockedPopover = null;
    1010
    1111        this.content().querySelector('close-button').component().setCallback(chartsPage.closePane.bind(chartsPage, this));
     
    2525                state[2] = currentPoint.id;
    2626        }
     27
     28        var graphOptions = new Set;
     29        if (!this.isSamplingEnabled())
     30            graphOptions.add('noSampling');
     31        if (this.isShowingOutliers())
     32            graphOptions.add('showOutliers');
     33
     34        if (graphOptions.size)
     35            state[3] = graphOptions;
     36
    2737        return state;
    2838    }
     
    4151        } else
    4252            this._mainChart.setIndicator(null, false);
     53
     54        // FIXME: This forces sourceList to be set twice. First in configure inside the constructor then here.
     55        var graphOptions = state[3];
     56        if (graphOptions instanceof Set) {
     57            this.setSamplingEnabled(!graphOptions.has('nosampling'));
     58            this.setShowOutliers(graphOptions.has('showoutliers'));
     59        }
     60
     61        // FIXME: Show full y-axis when graphOptions is true to be compatible with v2 UI.
     62        // FIXME: state[4] specifies moving average in v2 UI
     63        // FIXME: state[5] specifies envelope in v2 UI
     64        // FIXME: state[6] specifies change detection algorithm in v2 UI
    4365    }
    4466
     
    91113    }
    92114
     115    _analyzeRange(pointsRangeForAnalysis)
     116    {
     117        var router = this._chartsPage.router();
     118        var newWindow = window.open(router.url('analysis/task/create'), '_blank');
     119
     120        var analyzePopover = this.content().querySelector('.chart-pane-analyze-popover');
     121        var name = analyzePopover.querySelector('input').value;
     122        var self = this;
     123        AnalysisTask.create(name, pointsRangeForAnalysis.startPointId, pointsRangeForAnalysis.endPointId).then(function (data) {
     124            newWindow.location.href = router.url('analysis/task/' + data['taskId']);
     125            self.fetchAnalysisTasks(true);
     126            // FIXME: Refetch the list of analysis tasks.
     127        }, function (error) {
     128            newWindow.location.href = router.url('analysis/task/create', {error: error});
     129        });
     130    }
     131
     132    _markAsOutlier(markAsOutlier, points)
     133    {
     134        var self = this;
     135        return Promise.all(points.map(function (point) {
     136            return PrivilegedAPI.sendRequest('update-run-status', {'run': point.id, 'markedOutlier': markAsOutlier});
     137        })).then(function () {
     138            self._mainChart.fetchMeasurementSets(true /* noCache */);
     139        }, function (error) {
     140            alert('Failed to update the outlier status: ' + error);
     141        }).catch();
     142    }
     143
    93144    render()
    94145    {
     
    123174        }
    124175
    125         var platformPane = this.content().querySelector('.chart-pane-alternative-platforms');
     176        var platformPopover = this.content().querySelector('.chart-pane-alternative-platforms');
    126177        var alternativePlatforms = this._chartsPage.alternatePlatforms(platform, metric);
    127178        if (alternativePlatforms.length) {
    128             this.renderReplace(platformPane, Platform.sortByName(alternativePlatforms).map(function (platform) {
     179            this.renderReplace(platformPopover, Platform.sortByName(alternativePlatforms).map(function (platform) {
    129180                return element('li', link(platform.label(), function () {
    130181                    self._chartsPage.insertPaneAfter(platform, metric, self);
     
    132183            }));
    133184
    134             actions.push(element('li', {class: this._paneOpenedByClick == platformPane ? 'selected' : ''},
    135                 this._makeAnchorToOpenPane(platformPane, 'Other Platforms', true)));
    136         } else {
    137             platformPane.style.display = 'none';
    138         }
    139 
    140         var analyzePane = this.content().querySelector('.chart-pane-analyze-pane');
     185            actions.push(this._makePopoverActionItem(platformPopover, 'Other Platforms', true));
     186        } else
     187            platformPopover.style.display = 'none';
     188
     189        var analyzePopover = this.content().querySelector('.chart-pane-analyze-popover');
    141190        var pointsRangeForAnalysis = this._mainChartStatus.pointsRangeForAnalysis();
    142191        if (pointsRangeForAnalysis) {
    143             actions.push(element('li', {class: this._paneOpenedByClick == analyzePane ? 'selected' : ''},
    144                 this._makeAnchorToOpenPane(analyzePane, 'Analyze', false)));
    145 
    146             var router = this._chartsPage.router();
    147             analyzePane.onsubmit = function (event) {
     192            actions.push(this._makePopoverActionItem(analyzePopover, 'Analyze', false));
     193            analyzePopover.onsubmit = function (event) {
    148194                event.preventDefault();
    149                 var newWindow = window.open(router.url('analysis/task/create'), '_blank');
    150 
    151                 var name = analyzePane.querySelector('input').value;
    152                 AnalysisTask.create(name, pointsRangeForAnalysis.startPointId, pointsRangeForAnalysis.endPointId).then(function (data) {
    153                     newWindow.location.href = router.url('analysis/task/' + data['taskId']);
    154                     // FIXME: Refetch the list of analysis tasks.
    155                 }, function (error) {
    156                     newWindow.location.href = router.url('analysis/task/create', {error: error});
    157                 });
     195                self._analyzeRange(pointsRangeForAnalysis);
    158196            }
    159197        } else {
    160             analyzePane.style.display = 'none';
    161             analyzePane.onsubmit = function (event) { event.preventDefault(); }
    162         }
    163 
    164         this._paneOpenedByClick = null;
     198            analyzePopover.style.display = 'none';
     199            analyzePopover.onsubmit = function (event) { event.preventDefault(); }
     200        }
     201
     202        var filteringOptions = this.content().querySelector('.chart-pane-filtering-options');
     203        actions.push(this._makePopoverActionItem(filteringOptions, 'Filtering', true));
     204
     205        this._renderFilteringPopover();
     206
     207        this._lockedPopover = null;
    165208        this.renderReplace(this.content().querySelector('.chart-pane-action-buttons'), actions);
    166209    }
    167210
    168     _makeAnchorToOpenPane(pane, label, shouldRespondToHover)
    169     {
    170         var anchor = null;
    171         var ignoreMouseLeave = false;
    172         var self = this;
    173         var setPaneVisibility = function (pane, shouldShow) {
    174             var anchor = pane.anchor;
    175             if (shouldShow) {
    176                 var width = anchor.offsetParent.offsetWidth;
    177                 pane.style.top = anchor.offsetTop + anchor.offsetHeight + 'px';
    178                 pane.style.right = (width - anchor.offsetLeft - anchor.offsetWidth) + 'px';
    179             }
    180             pane.style.display = shouldShow ? null : 'none';
    181             anchor.parentNode.className = shouldShow ? 'selected' : '';
    182             if (self._paneOpenedByClick == pane && !shouldShow)
    183                 self._paneOpenedByClick = null;
    184         }
    185 
    186         var attributes = {
    187             href: '#',
    188             onclick: function (event) {
    189                 event.preventDefault();
    190                 var shouldShowPane = pane.style.display == 'none';
    191                 if (shouldShowPane) {
    192                     if (self._paneOpenedByClick)
    193                         setPaneVisibility(self._paneOpenedByClick, false);
    194                     self._paneOpenedByClick = pane;
    195                 }
    196                 setPaneVisibility(pane, shouldShowPane);
    197             },
    198         };
    199         if (shouldRespondToHover) {
    200             var mouseIsInAnchor = false;
    201             var mouseIsInPane = false;
    202 
    203             attributes.onmouseenter = function () {
    204                 if (self._paneOpenedByClick)
    205                     return;
    206                 mouseIsInAnchor = true;
    207                 setPaneVisibility(pane, true);
    208             }
    209             attributes.onmouseleave = function () {
    210                 setTimeout(function () {
    211                     if (!mouseIsInPane)
    212                         setPaneVisibility(pane, false);
    213                 }, 0);
    214                 mouseIsInAnchor = false;               
    215             }
    216 
    217             pane.onmouseleave = function () {
    218                 setTimeout(function () {
    219                     if (!mouseIsInAnchor)
    220                         setPaneVisibility(pane, false);
    221                 }, 0);
    222                 mouseIsInPane = false;
    223             }
    224             pane.onmouseenter = function () {
    225                 mouseIsInPane = true;
    226             }
    227         }
    228 
    229         var anchor = ComponentBase.createElement('a', attributes, label);
    230         pane.anchor = anchor;
    231         return anchor;
     211    _makePopoverActionItem(popover, label, shouldRespondToHover)
     212    {
     213        var self = this;
     214        popover.anchor = ComponentBase.createLink(label, function () {
     215            var makeVisible = self._lockedPopover != popover;
     216            self._setPopoverVisibility(popover, makeVisible);
     217            if (makeVisible)
     218                self._lockedPopover = popover;
     219        });
     220        if (shouldRespondToHover)
     221            this._makePopoverOpenOnHover(popover);
     222
     223        return ComponentBase.createElement('li', {class: this._lockedPopover == popover ? 'selected' : ''}, popover.anchor);
     224    }
     225
     226    _makePopoverOpenOnHover(popover)
     227    {
     228        var mouseIsInAnchor = false;
     229        var mouseIsInPopover = false;
     230
     231        var self = this;
     232        var closeIfNeeded = function () {
     233            setTimeout(function () {
     234                if (self._lockedPopover != popover && !mouseIsInAnchor && !mouseIsInPopover)
     235                    self._setPopoverVisibility(popover, false);
     236            }, 0);
     237        }
     238
     239        popover.anchor.onmouseenter = function () {
     240            if (self._lockedPopover)
     241                return;
     242            mouseIsInAnchor = true;
     243            self._setPopoverVisibility(popover, true);
     244        }
     245        popover.anchor.onmouseleave = function () {
     246            mouseIsInAnchor = false;
     247            closeIfNeeded();         
     248        }
     249
     250        popover.onmouseenter = function () {
     251            mouseIsInPopover = true;
     252        }
     253        popover.onmouseleave = function () {
     254            mouseIsInPopover = false;
     255            closeIfNeeded();
     256        }
     257    }
     258
     259    _setPopoverVisibility(popover, visible)
     260    {
     261        var anchor = popover.anchor;
     262        if (visible) {
     263            var width = anchor.offsetParent.offsetWidth;
     264            popover.style.top = anchor.offsetTop + anchor.offsetHeight + 'px';
     265            popover.style.right = (width - anchor.offsetLeft - anchor.offsetWidth) + 'px';
     266        }
     267        popover.style.display = visible ? null : 'none';
     268        anchor.parentNode.className = visible ? 'selected' : '';
     269
     270        if (this._lockedPopover && this._lockedPopover != popover && visible)
     271            this._setPopoverVisibility(this._lockedPopover, false);
     272
     273        if (this._lockedPopover == popover && !visible)
     274            this._lockedPopover = null;
     275    }
     276
     277    _renderFilteringPopover()
     278    {
     279        var enableSampling = this.content().querySelector('.enable-sampling');
     280        enableSampling.checked = this.isSamplingEnabled();
     281        enableSampling.onchange = function () {
     282            self.setSamplingEnabled(enableSampling.checked);
     283            self._chartsPage.graphOptionsDidChange();
     284        }
     285
     286        var showOutliers = this.content().querySelector('.show-outliers');
     287        showOutliers.checked = this.isShowingOutliers();
     288        showOutliers.onchange = function () {
     289            self.setShowOutliers(showOutliers.checked);
     290            self._chartsPage.graphOptionsDidChange();
     291        }
     292
     293        var markAsOutlierButton = this.content().querySelector('.mark-as-outlier');
     294        var firstSelectedPoint = this._mainChart.lockedIndicator();
     295        if (!firstSelectedPoint)
     296            firstSelectedPoint = this._mainChart.firstSelectedPoint('current');
     297        var alreayMarkedAsOutlier = firstSelectedPoint && firstSelectedPoint.markedOutlier;
     298
     299        var self = this;
     300        markAsOutlierButton.textContent = (alreayMarkedAsOutlier ? 'Unmark' : 'Mark') + ' selected points as outlier';
     301        markAsOutlierButton.onclick = function () {
     302            var selectedPoints = [firstSelectedPoint];
     303            if (self._mainChart.currentSelection('current'))
     304                selectedPoints = self._mainChart.selectedPoints('current');
     305            self._markAsOutlier(!alreayMarkedAsOutlier, selectedPoints);
     306        }
     307        markAsOutlierButton.disabled = !firstSelectedPoint;
    232308    }
    233309
     
    242318                    </ul>
    243319                    <ul class="chart-pane-action-buttons buttoned-toolbar"></ul>
    244                     <ul class="chart-pane-alternative-platforms" style="display:none"></ul>
    245                     <form class="chart-pane-analyze-pane" style="display:none">
     320                    <ul class="chart-pane-alternative-platforms popover" style="display:none"></ul>
     321                    <form class="chart-pane-analyze-popover popover" style="display:none">
    246322                        <input type="text" required>
    247323                        <button>Create</button>
    248324                    </form>
     325                    <ul class="chart-pane-filtering-options popover" style="display:none">
     326                        <li><label><input type="checkbox" class="enable-sampling">Sampling</label></li>
     327                        <li><label><input type="checkbox" class="show-outliers">Show outliers</label></li>
     328                        <li><button class="mark-as-outlier">Mark selected points as outlier</button></li>
     329                    </ul>
    249330                </nav>
    250331            </header>
     
    310391            }
    311392
    312             .chart-pane-actions .chart-pane-alternative-platforms,
    313             .chart-pane-analyze-pane {
     393            .chart-pane-actions .popover {
    314394                position: absolute;
    315395                top: 0;
     
    326406            }
    327407
    328             .chart-pane-alternative-platforms li {
    329             }
    330 
    331             .chart-pane-alternative-platforms li a {
     408            .chart-pane-actions .popover li {
     409            }
     410
     411            .chart-pane-actions .popover li a {
    332412                display: block;
    333413                text-decoration: none;
     
    337417            }
    338418
    339             .chart-pane-alternative-platforms a:hover,
    340             .chart-pane-analyze-pane input:focus {
     419            .chart-pane-actions .popover a:hover,
     420            .chart-pane-actions .popover input:focus {
    341421                background: rgba(204, 153, 51, 0.1);
    342422            }
    343423
    344             .chart-pane-analyze-pane {
     424            .chart-pane-actions .chart-pane-analyze-popover {
    345425                padding: 0.5rem;
    346426            }
    347427
    348             .chart-pane-analyze-pane input {
     428            .chart-pane-actions .popover label {
     429                font-size: 0.9rem;
     430            }
     431
     432            .chart-pane-actions .popover input[type=text] {
    349433                font-size: 1rem;
    350434                width: 15rem;
  • trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js

    r199444 r201564  
    186186    }
    187187
     188    graphOptionsDidChange(pane)
     189    {
     190        this.scheduleUrlStateUpdate();
     191    }
     192
    188193    setOpenRepository(repository)
    189194    {
  • trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js

    r196387 r201564  
    126126            return '';
    127127
    128         var result = ChartStyles.createChartSourceList(platformId, metricId);
     128        var result = ChartStyles.resolveConfiguration(platformId, metricId);
    129129        if (result.error)
    130130            return result.error;
     
    132132        var options = ChartStyles.dashboardOptions(result.metric.makeFormatter(3));
    133133        options.ondata = this._fetchedData.bind(this);
    134         var chart = new TimeSeriesChart(result.sourceList, options);
     134        var chart = new TimeSeriesChart(ChartStyles.createSourceList(result.platform, result.metric, false, false), options);
    135135        this._charts.push(chart);
    136136
  • trunk/Websites/perf.webkit.org/public/v3/pages/page-router.js

    r201337 r201564  
    128128    _serializeHashQueryValue(value)
    129129    {
    130         if (!(value instanceof Array)) {
    131             console.assert(value === null || typeof(value) === 'number' || /[A-Za-z0-9]*/.test(value));
    132             return value === null ? 'null' : value;
     130        if (value instanceof Array) {
     131            var serializedItems = [];
     132            for (var item of value)
     133                serializedItems.push(this._serializeHashQueryValue(item));
     134            return '(' + serializedItems.join('-') + ')';
    133135        }
    134 
    135         var serializedItems = [];
    136         for (var item of value)
    137             serializedItems.push(this._serializeHashQueryValue(item));
    138         return '(' + serializedItems.join('-') + ')';
     136        if (value instanceof Set)
     137            return Array.from(value).sort().join('|');
     138        console.assert(value === null || value === undefined || typeof(value) === 'number' || /[0-9]*/.test(value));
     139        return value === null || value === undefined ? 'null' : value;
    139140    }
    140141
    141142    _deserializeHashQueryValue(value)
    142143    {
    143         var json = value.replace(/\(/g, '[').replace(/\)/g, ']').replace(/-/g, ',');
    144         try {
    145             return JSON.parse(json);
    146         } catch (error) {
    147 
    148             // Some applications don't linkify two consecutive closing parentheses: )).
    149             // Try fixing adding one extra parenthesis to see if that works.
    150             var missingClosingBrackets = this._countOccurrences(json, /\[/g) - this._countOccurrences(json, /\]/g);
    151             var fix = new Array(missingClosingBrackets).fill(']').join('');
    152             try {
    153                 return JSON.parse(json + fix);
    154             } catch (newError) { }
    155 
    156             return value;
     144        if (value.charAt(0) == '(') {
     145            var nestingLevel = 0;
     146            var end = 0;
     147            var start = 1;
     148            var result = [];
     149            for (var character of value) {
     150                if (character == '(')
     151                    nestingLevel++;
     152                else if (character == ')') {
     153                    nestingLevel--;
     154                    if (!nestingLevel)
     155                        break;
     156                } else if (nestingLevel == 1 && character == '-') {
     157                    result.push(this._deserializeHashQueryValue(value.substring(start, end)));
     158                    start = end + 1;
     159                }
     160                end++;
     161            }
     162            result.push(this._deserializeHashQueryValue(value.substring(start, end)));
     163            return result;
    157164        }
     165        if (value == 'true')
     166            return true;
     167        if (value == 'false')
     168            return true;
     169        if (value.match(/^[0-9\.]+$/))
     170            return parseFloat(value);
     171        if (value.match(/^[A-Za-z][A-Za-z0-9|]*$/))
     172            return new Set(value.toLowerCase().split('|'));
     173        return null;
    158174    }
    159175
  • trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js

    r200449 r201564  
    285285                assert.equal(requests.length, 2);
    286286                assert.equal(requests[1].url, '../api/measurement-set?platform=1&metric=1');
     287                done();
     288            }).catch(function (error) {
     289                done(error);
     290            });
     291        });
     292
     293        it('should request the uncached primary cluster when noCache is true', function (done) {
     294            var set = MeasurementSet.findSet(1, 1, 3000);
     295            var callCount = 0;
     296            set.fetchBetween(1000, 3000, function () {
     297                callCount++;
     298            });
     299            assert.equal(requests.length, 1);
     300            assert.equal(requests[0].url, '../data/measurement-set-1-1.json');
     301
     302            requests[0].resolve({
     303                'clusterStart': 1000,
     304                'clusterSize': 1000,
     305                'formatMap': [],
     306                'configurations': {current: []},
     307                'startTime': 2000,
     308                'endTime': 3000,
     309                'lastModified': 3000,
     310                'clusterCount': 2,
     311                'status': 'OK'});
     312
     313            var noCacheFetchCount = 0;
     314            waitForMeasurementSet().then(function () {
     315                assert.equal(callCount, 1);
     316                assert.equal(noCacheFetchCount, 0);
     317                assert.equal(set._sortedClusters.length, 1);
     318                assert.equal(requests.length, 2);
     319                assert.equal(requests[1].url, '../data/measurement-set-1-1-2000.json');
     320
     321                requests[1].resolve({
     322                    'clusterStart': 1000,
     323                    'clusterSize': 1000,
     324                    'formatMap': [],
     325                    'configurations': {current: []},
     326                    'startTime': 1000,
     327                    'endTime': 2000,
     328                    'lastModified': 3000,
     329                    'clusterCount': 2,
     330                    'status': 'OK'});
     331
     332                set.fetchBetween(1000, 3000, function () {
     333                    noCacheFetchCount++;
     334                }, true /* noCache */);
     335
     336                return waitForMeasurementSet();
     337            }).then(function () {
     338                assert.equal(callCount, 2);
     339                assert.equal(noCacheFetchCount, 0);
     340                assert.equal(set._sortedClusters.length, 2);
     341                assert.equal(requests.length, 3);
     342                assert.equal(requests[2].url, '../api/measurement-set?platform=1&metric=1');
     343
     344                requests[2].resolve({
     345                    'clusterStart': 1000,
     346                    'clusterSize': 1000,
     347                    'formatMap': [],
     348                    'configurations': {current: []},
     349                    'startTime': 2000,
     350                    'endTime': 3000,
     351                    'lastModified': 3000,
     352                    'clusterCount': 2,
     353                    'status': 'OK'});
     354
     355                return waitForMeasurementSet();
     356            }).then(function () {
     357                assert.equal(callCount, 2);
     358                assert.equal(noCacheFetchCount, 1);
     359                assert.equal(set._sortedClusters.length, 2);
     360                assert.equal(requests.length, 4);
     361                assert.equal(requests[3].url, '../data/measurement-set-1-1-2000.json');
     362
     363                requests[3].resolve({
     364                    'clusterStart': 1000,
     365                    'clusterSize': 1000,
     366                    'formatMap': [],
     367                    'configurations': {current: []},
     368                    'startTime': 1000,
     369                    'endTime': 2000,
     370                    'lastModified': 3000,
     371                    'clusterCount': 2,
     372                    'status': 'OK'});
     373
     374                return waitForMeasurementSet();
     375            }).then(function () {
     376                assert.equal(callCount, 2);
     377                assert.equal(noCacheFetchCount, 2);
     378                assert.equal(set._sortedClusters.length, 2);
     379                assert.equal(requests.length, 4);
     380
    287381                done();
    288382            }).catch(function (error) {
Note: See TracChangeset for help on using the changeset viewer.