Changeset 201564 in webkit
- Timestamp:
- Jun 1, 2016, 12:55:38 PM (10 years ago)
- Location:
- trunk/Websites/perf.webkit.org
- Files:
-
- 15 edited
-
ChangeLog (modified) (1 diff)
-
public/v3/components/chart-pane-base.js (modified) (5 diffs)
-
public/v3/components/chart-styles.js (modified) (4 diffs)
-
public/v3/components/interactive-time-series-chart.js (modified) (2 diffs)
-
public/v3/components/time-series-chart.js (modified) (2 diffs)
-
public/v3/models/analysis-task.js (modified) (2 diffs)
-
public/v3/models/measurement-adaptor.js (modified) (2 diffs)
-
public/v3/models/measurement-cluster.js (modified) (1 diff)
-
public/v3/models/measurement-set.js (modified) (5 diffs)
-
public/v3/models/time-series.js (modified) (1 diff)
-
public/v3/pages/chart-pane.js (modified) (10 diffs)
-
public/v3/pages/charts-page.js (modified) (1 diff)
-
public/v3/pages/dashboard-page.js (modified) (2 diffs)
-
public/v3/pages/page-router.js (modified) (1 diff)
-
unit-tests/measurement-set-tests.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Websites/perf.webkit.org/ChangeLog
r201337 r201564 1 2016-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 1 117 2016-05-24 Ryosuke Niwa <rniwa@webkit.org> 2 118 -
trunk/Websites/perf.webkit.org/public/v3/components/chart-pane-base.js
r198598 r201564 11 11 this._platform = null; 12 12 this._metric = null; 13 this._disableSampling = false; 14 this._showOutliers = false; 13 15 14 16 this._overviewChart = null; … … 21 23 configure(platformId, metricId) 22 24 { 23 var result = ChartStyles. createChartSourceList(platformId, metricId);25 var result = ChartStyles.resolveConfiguration(platformId, metricId); 24 26 this._errorMessage = result.error; 25 27 this._platformId = platformId; … … 40 42 var self = this; 41 43 44 var sourceList = ChartStyles.createSourceList(this._platform, this._metric, this._disableSampling, this._showOutliers); 45 42 46 var overviewOptions = ChartStyles.overviewChartOptions(formatter); 43 47 overviewOptions.selection.onchange = this._overviewSelectionDidChange.bind(this); 44 45 this._overviewChart = new InteractiveTimeSeriesChart(result.sourceList, overviewOptions); 48 this._overviewChart = new InteractiveTimeSeriesChart(sourceList, overviewOptions); 46 49 this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart); 47 50 … … 52 55 mainOptions.annotations.onclick = this._openAnalysisTask.bind(this); 53 56 mainOptions.ondata = this._didFetchData.bind(this); 54 this._mainChart = new InteractiveTimeSeriesChart( result.sourceList, mainOptions);57 this._mainChart = new InteractiveTimeSeriesChart(sourceList, mainOptions); 55 58 this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart); 56 59 … … 60 63 this.content().querySelector('.chart-pane').addEventListener('keyup', this._keyup.bind(this)); 61 64 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) 66 90 { 67 91 // FIXME: we need to update the annotation bars when the change type of tasks change. 68 92 var self = this; 69 AnalysisTask.fetchByPlatformAndMetric( platformId, metricId).then(function (tasks) {93 AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId, noCache).then(function (tasks) { 70 94 self._tasksForAnnotations = tasks; 71 95 self.render(); -
trunk/Websites/perf.webkit.org/public/v3/components/chart-styles.js
r196387 r201564 1 1 2 2 class ChartStyles { 3 static createChartSourceList(platformId, metricId)3 static resolveConfiguration(platformId, metricId) 4 4 { 5 5 var platform = Platform.findById(platformId); … … 12 12 return {platform: platform, metric: metric, error: `No results on ${platform.name()}`}; 13 13 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 21 14 return { 22 15 platform: platform, 23 16 metric: metric, 24 sourceList: sourceList,25 17 }; 26 18 } 27 19 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) 29 37 { 30 38 return { 31 39 measurementSet: measurementSet, 32 40 extendToFuture: true, 33 sampleData: true, 41 sampleData: !disableSampling, 42 includeOutliers: includeOutlier, 34 43 type: 'baseline', 35 44 pointStyle: '#f33', … … 42 51 } 43 52 44 static targetStyle(measurementSet )53 static targetStyle(measurementSet, disableSampling, includeOutlier) 45 54 { 46 55 return { 47 56 measurementSet: measurementSet, 48 57 extendToFuture: true, 49 sampleData: true, 58 sampleData: !disableSampling, 59 includeOutliers: includeOutlier, 50 60 type: 'target', 51 61 pointStyle: '#33f', … … 58 68 } 59 69 60 static currentStyle(measurementSet )70 static currentStyle(measurementSet, disableSampling, includeOutlier) 61 71 { 62 72 return { 63 73 measurementSet: measurementSet, 64 sampleData: true, 74 sampleData: !disableSampling, 75 includeOutliers: includeOutlier, 65 76 type: 'current', 66 77 pointStyle: '#333', -
trunk/Websites/perf.webkit.org/public/v3/components/interactive-time-series-chart.js
r198518 r201564 19 19 currentPoint(diff) 20 20 { 21 if (!this._sampledTimeSeriesData)22 return null;23 24 21 var id = this._indicatorID; 25 22 if (!id) 26 23 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 } 27 34 28 35 for (var data of this._sampledTimeSeriesData) { … … 40 47 41 48 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 42 64 43 65 setIndicator(id, shouldLock) -
trunk/Websites/perf.webkit.org/public/v3/components/time-series-chart.js
r198464 r201564 67 67 this._startTime = startTime; 68 68 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 { 69 80 for (var source of this._sourceList) { 70 81 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); 72 83 } 73 84 this._sampledTimeSeriesData = null; … … 102 113 return null; 103 114 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; }); 104 123 } 105 124 -
trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js
r198479 r201564 167 167 } 168 168 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) { 172 172 return AnalysisTask.findByPlatformAndMetric(platformId, metricId); 173 173 }); … … 199 199 } 200 200 201 static _fetchSubset(params )201 static _fetchSubset(params, noCache) 202 202 { 203 203 if (this._fetchAllPromise) 204 204 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)); 206 206 } 207 207 -
trunk/Websites/perf.webkit.org/public/v3/models/measurement-adaptor.js
r198462 r201564 29 29 } 30 30 31 isOutlier(row) 32 { 33 return row[this._markedOutlierIndex]; 34 } 35 31 36 applyToAnalysisResults(row) 32 37 { … … 51 56 return { 52 57 id: id, 58 markedOutlier: row[this._markedOutlierIndex], 53 59 buildId: buildId, 54 60 metricId: null, -
trunk/Websites/perf.webkit.org/public/v3/models/measurement-cluster.js
r198462 r201564 22 22 if (id in idMap) 23 23 return; 24 if ( row[self._markedOutlierIndex]&& !includeOutliers)24 if (self._adaptor.isOutlier(row) && !includeOutliers) 25 25 return; 26 26 -
trunk/Websites/perf.webkit.org/public/v3/models/measurement-set.js
r200449 r201564 56 56 } 57 57 58 fetchBetween(startTime, endTime, callback )58 fetchBetween(startTime, endTime, callback, noCache) 59 59 { 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); 62 66 var self = this; 63 67 this._primaryClusterPromise.catch(callback); … … 87 91 } 88 92 89 _fetchPrimaryCluster() { 93 _fetchPrimaryCluster(noCache) 94 { 90 95 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 91 103 return RemoteAPI.getJSONWithStatus(self._constructUrl(true, null)).then(function (data) { 92 104 if (+data['lastModified'] < self._lastModified) … … 103 115 } 104 116 105 _fetchSecondaryCluster(endTime) { 117 _fetchSecondaryCluster(endTime) 118 { 106 119 var self = this; 107 120 return RemoteAPI.getJSONWithStatus(self._constructUrl(true, endTime)).then(function (data) { … … 112 125 _didFetchJSON(isPrimaryCluster, response, clusterEndTime) 113 126 { 114 console.assert(isPrimaryCluster);115 116 127 if (isPrimaryCluster) { 117 128 this._primaryClusterEndTime = response['endTime']; … … 127 138 _addFetchedCluster(cluster) 128 139 { 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 } 129 147 this._sortedClusters.push(cluster); 130 this._sortedClusters = this._sortedClusters.sort(function (c1, c2) {131 return c1.startTime() - c2.startTime();132 });133 148 } 134 149 -
trunk/Websites/perf.webkit.org/public/v3/models/time-series.js
r198462 r201564 62 62 dataBetweenPoints(firstPoint, lastPoint) 63 63 { 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); 71 67 } 72 68 -
trunk/Websites/perf.webkit.org/public/v3/pages/chart-pane.js
r201309 r201564 7 7 this._mainChartIndicatorWasLocked = false; 8 8 this._chartsPage = chartsPage; 9 this._ paneOpenedByClick= null;9 this._lockedPopover = null; 10 10 11 11 this.content().querySelector('close-button').component().setCallback(chartsPage.closePane.bind(chartsPage, this)); … … 25 25 state[2] = currentPoint.id; 26 26 } 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 27 37 return state; 28 38 } … … 41 51 } else 42 52 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 43 65 } 44 66 … … 91 113 } 92 114 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 93 144 render() 94 145 { … … 123 174 } 124 175 125 var platformP ane= this.content().querySelector('.chart-pane-alternative-platforms');176 var platformPopover = this.content().querySelector('.chart-pane-alternative-platforms'); 126 177 var alternativePlatforms = this._chartsPage.alternatePlatforms(platform, metric); 127 178 if (alternativePlatforms.length) { 128 this.renderReplace(platformP ane, Platform.sortByName(alternativePlatforms).map(function (platform) {179 this.renderReplace(platformPopover, Platform.sortByName(alternativePlatforms).map(function (platform) { 129 180 return element('li', link(platform.label(), function () { 130 181 self._chartsPage.insertPaneAfter(platform, metric, self); … … 132 183 })); 133 184 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'); 141 190 var pointsRangeForAnalysis = this._mainChartStatus.pointsRangeForAnalysis(); 142 191 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) { 148 194 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); 158 196 } 159 197 } 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; 165 208 this.renderReplace(this.content().querySelector('.chart-pane-action-buttons'), actions); 166 209 } 167 210 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; 232 308 } 233 309 … … 242 318 </ul> 243 319 <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-p ane" 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"> 246 322 <input type="text" required> 247 323 <button>Create</button> 248 324 </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> 249 330 </nav> 250 331 </header> … … 310 391 } 311 392 312 .chart-pane-actions .chart-pane-alternative-platforms, 313 .chart-pane-analyze-pane { 393 .chart-pane-actions .popover { 314 394 position: absolute; 315 395 top: 0; … … 326 406 } 327 407 328 .chart-pane-a lternative-platformsli {329 } 330 331 .chart-pane-a lternative-platformsli a {408 .chart-pane-actions .popover li { 409 } 410 411 .chart-pane-actions .popover li a { 332 412 display: block; 333 413 text-decoration: none; … … 337 417 } 338 418 339 .chart-pane-a lternative-platformsa:hover,340 .chart-pane-a nalyze-paneinput:focus {419 .chart-pane-actions .popover a:hover, 420 .chart-pane-actions .popover input:focus { 341 421 background: rgba(204, 153, 51, 0.1); 342 422 } 343 423 344 .chart-pane-a nalyze-pane{424 .chart-pane-actions .chart-pane-analyze-popover { 345 425 padding: 0.5rem; 346 426 } 347 427 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] { 349 433 font-size: 1rem; 350 434 width: 15rem; -
trunk/Websites/perf.webkit.org/public/v3/pages/charts-page.js
r199444 r201564 186 186 } 187 187 188 graphOptionsDidChange(pane) 189 { 190 this.scheduleUrlStateUpdate(); 191 } 192 188 193 setOpenRepository(repository) 189 194 { -
trunk/Websites/perf.webkit.org/public/v3/pages/dashboard-page.js
r196387 r201564 126 126 return ''; 127 127 128 var result = ChartStyles. createChartSourceList(platformId, metricId);128 var result = ChartStyles.resolveConfiguration(platformId, metricId); 129 129 if (result.error) 130 130 return result.error; … … 132 132 var options = ChartStyles.dashboardOptions(result.metric.makeFormatter(3)); 133 133 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); 135 135 this._charts.push(chart); 136 136 -
trunk/Websites/perf.webkit.org/public/v3/pages/page-router.js
r201337 r201564 128 128 _serializeHashQueryValue(value) 129 129 { 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('-') + ')'; 133 135 } 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; 139 140 } 140 141 141 142 _deserializeHashQueryValue(value) 142 143 { 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; 157 164 } 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; 158 174 } 159 175 -
trunk/Websites/perf.webkit.org/unit-tests/measurement-set-tests.js
r200449 r201564 285 285 assert.equal(requests.length, 2); 286 286 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 287 381 done(); 288 382 }).catch(function (error) {
Note:
See TracChangeset
for help on using the changeset viewer.