Build fix. It looks like the code here is racy.
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / components / chart-pane-base.js
1
2 class ChartPaneBase extends ComponentBase {
3
4     constructor(name)
5     {
6         super(name);
7
8         this._errorMessage = null;
9         this._platformId = null;
10         this._metricId = null;
11         this._platform = null;
12         this._metric = null;
13         this._disableSampling = false;
14         this._showOutliers = false;
15         this._openRepository = null;
16
17         this._overviewChart = null;
18         this._mainChart = null;
19         this._mainChartStatus = null;
20         this._commitLogViewer = null;
21         this._tasksForAnnotations = null;
22         this._renderedAnnotations = false;
23     }
24
25     configure(platformId, metricId)
26     {
27         var result = ChartStyles.resolveConfiguration(platformId, metricId);
28         this._errorMessage = result.error;
29         this._platformId = platformId;
30         this._metricId = metricId;
31         this._platform = result.platform;
32         this._metric = result.metric;
33
34         this._overviewChart = null;
35         this._mainChart = null;
36         this._mainChartStatus = null;
37
38         this._commitLogViewer = this.content().querySelector('commit-log-viewer').component();
39
40         if (result.error)
41             return;
42
43         var formatter = result.metric.makeFormatter(4);
44
45         this._overviewChart = new InteractiveTimeSeriesChart(this._createSourceList(false), ChartStyles.overviewChartOptions(formatter));
46         this._overviewChart.listenToAction('selectionChange', this._overviewSelectionDidChange.bind(this));
47         this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart);
48
49         this._mainChart = new InteractiveTimeSeriesChart(this._createSourceList(true), ChartStyles.mainChartOptions(formatter));
50         this._mainChart.listenToAction('dataChange', () => this._didFetchData());
51         this._mainChart.listenToAction('indicatorChange', this._indicatorDidChange.bind(this));
52         this._mainChart.listenToAction('selectionChange', this._mainSelectionDidChange.bind(this));
53         this._mainChart.listenToAction('zoom', this._mainSelectionDidZoom.bind(this));
54         this._mainChart.listenToAction('annotationClick', this._openAnalysisTask.bind(this));
55         this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
56
57         this._revisionRange = new ChartRevisionRange(this._mainChart);
58
59         this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart);
60         this._mainChartStatus.setCurrentRepository(this._openRepository);
61         this._mainChartStatus.listenToAction('openRepository', this.openNewRepository.bind(this));
62         this.renderReplace(this.content().querySelector('.chart-pane-details'), this._mainChartStatus);
63
64         this.content().querySelector('.chart-pane').addEventListener('keydown', this._keyup.bind(this));
65
66         this.fetchAnalysisTasks(false);
67     }
68
69     isSamplingEnabled() { return !this._disableSampling; }
70     setSamplingEnabled(enabled)
71     {
72         this._disableSampling = !enabled;
73         this._updateSourceList();
74     }
75
76     isShowingOutliers() { return this._showOutliers; }
77     setShowOutliers(show)
78     {
79         this._showOutliers = !!show;
80         this._updateSourceList();
81     }
82
83     _createSourceList(isMainChart)
84     {
85         return ChartStyles.createSourceList(this._platform, this._metric, this._disableSampling, this._showOutliers, isMainChart);
86     }
87
88     _updateSourceList()
89     {
90         this._mainChart.setSourceList(this._createSourceList(true));
91         this._overviewChart.setSourceList(this._createSourceList(false));
92     }
93
94     fetchAnalysisTasks(noCache)
95     {
96         // FIXME: we need to update the annotation bars when the change type of tasks change.
97         var self = this;
98         AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId, noCache).then(function (tasks) {
99             self._tasksForAnnotations = tasks;
100             self._renderedAnnotations = false;
101             self.enqueueToRender();
102         });
103     }
104
105     // FIXME: We should have a mechanism to get notified whenever the set of annotations change.
106     didUpdateAnnotations()
107     {
108         this._renderedAnnotations = false;
109         this.enqueueToRender();
110     }
111
112     platformId() { return this._platformId; }
113     metricId() { return this._metricId; }
114
115     setOverviewDomain(startTime, endTime)
116     {
117         if (this._overviewChart)
118             this._overviewChart.setDomain(startTime, endTime);
119     }
120
121     setMainDomain(startTime, endTime)
122     {
123         if (this._mainChart)
124             this._mainChart.setDomain(startTime, endTime);
125     }
126
127     setMainSelection(selection)
128     {
129         if (this._mainChart)
130             this._mainChart.setSelection(selection);
131     }
132
133     setOpenRepository(repository)
134     {
135         this._openRepository = repository;
136         if (this._mainChartStatus)
137             this._mainChartStatus.setCurrentRepository(repository);
138         this._updateCommitLogViewer();
139     }
140
141     _overviewSelectionDidChange(domain, didEndDrag) { }
142
143     _mainSelectionDidChange(selection, didEndDrag)
144     {
145         this._updateCommitLogViewer();
146     }
147
148     _mainSelectionDidZoom(selection)
149     {
150         this._overviewChart.setSelection(selection, this);
151         this._mainChart.setSelection(null);
152         this.enqueueToRender();
153     }
154
155     _indicatorDidChange(indicatorID, isLocked)
156     {
157         this._updateCommitLogViewer();
158     }
159
160     _didFetchData()
161     {
162         this._updateCommitLogViewer();
163     }
164
165     _updateCommitLogViewer()
166     {
167         const range = this._revisionRange.rangeForRepository(this._openRepository);
168         this._commitLogViewer.view(this._openRepository, range.from, range.to);
169         this.enqueueToRender();
170     }
171
172     _openAnalysisTask(annotation)
173     {
174         var router = this.router();
175         console.assert(router);
176         window.open(router.url(`analysis/task/${annotation.task.id()}`), '_blank');
177     }
178
179     router() { return null; }
180
181     openNewRepository(repository)
182     {
183         this.content().querySelector('.chart-pane').focus();
184         this.setOpenRepository(repository);
185     }
186
187     _keyup(event)
188     {
189         switch (event.keyCode) {
190         case 37: // Left
191             if (!this._mainChart.moveLockedIndicatorWithNotification(false))
192                 return;
193             break;
194         case 39: // Right
195             if (!this._mainChart.moveLockedIndicatorWithNotification(true))
196                 return;
197             break;
198         case 38: // Up
199             if (!this._moveOpenRepository(false))
200                 return;
201             break;
202         case 40: // Down
203             if (!this._moveOpenRepository(true))
204                 return;
205             break;
206         default:
207             return;
208         }
209
210         this.enqueueToRender();
211
212         event.preventDefault();
213         event.stopPropagation();
214     }
215
216     _moveOpenRepository(forward)
217     {
218         const openRepository = this._openRepository;
219         if (!openRepository)
220             return false;
221
222         const revisionList = this._revisionRange.revisionList();
223         if (!revisionList)
224             return false;
225
226         const currentIndex = revisionList.findIndex((info) => info.repository == openRepository);
227         console.assert(currentIndex >= 0);
228
229         const newIndex = currentIndex + (forward ? 1 : -1);
230         if (newIndex < 0 || newIndex >= revisionList.length)
231             return false;
232
233         this.openNewRepository(revisionList[newIndex].repository);
234
235         return true;
236     }
237
238     render()
239     {
240         Instrumentation.startMeasuringTime('ChartPane', 'render');
241
242         super.render();
243
244         if (this._overviewChart)
245             this._overviewChart.enqueueToRender();
246
247         if (this._mainChart)
248             this._mainChart.enqueueToRender();
249
250         if (this._errorMessage) {
251             this.renderReplace(this.content().querySelector('.chart-pane-main'), this._errorMessage);
252             return;
253         }
254
255         this._renderAnnotations();
256
257         if (this._mainChartStatus)
258             this._mainChartStatus.enqueueToRender();
259
260         var body = this.content().querySelector('.chart-pane-body');
261         if (this._openRepository)
262             body.classList.add('has-second-sidebar');
263         else
264             body.classList.remove('has-second-sidebar');
265
266         Instrumentation.endMeasuringTime('ChartPane', 'render');
267     }
268
269     _renderAnnotations()
270     {
271         if (!this._tasksForAnnotations || this._renderedAnnotations)
272             return;
273         this._renderedAnnotations = true;
274
275         var annotations = this._tasksForAnnotations.map(function (task) {
276             var fillStyle = '#fc6';
277             switch (task.changeType()) {
278             case 'inconclusive':
279                 fillStyle = '#fcc';
280                 break;
281             case 'progression':
282                 fillStyle = '#39f';
283                 break;
284             case 'regression':
285                 fillStyle = '#c60';
286                 break;
287             case 'unchanged':
288                 fillStyle = '#ccc';
289                 break;
290             }
291
292             return {
293                 task: task,
294                 startTime: task.startTime(),
295                 endTime: task.endTime(),
296                 label: task.label(),
297                 fillStyle: fillStyle,
298             };
299         });
300         this._mainChart.setAnnotations(annotations);
301     }
302
303     static htmlTemplate()
304     {
305         return `
306             <section class="chart-pane" tabindex="0">
307                 ${this.paneHeaderTemplate()}
308                 <div class="chart-pane-body">
309                     <div class="chart-pane-main"></div>
310                     <div class="chart-pane-sidebar">
311                         <div class="chart-pane-overview"></div>
312                         <div class="chart-pane-details"></div>
313                     </div>
314                     <div class="chart-pane-second-sidebar">
315                         <commit-log-viewer></commit-log-viewer>
316                     </div>
317                 </div>
318             </section>
319             ${this.paneFooterTemplate()}
320         `;
321     }
322
323     static paneHeaderTemplate() { return ''; }
324     static paneFooterTemplate() { return ''; }
325
326     static cssTemplate()
327     {
328         return Toolbar.cssTemplate() + `
329             .chart-pane {
330                 padding: 0rem;
331                 height: 18rem;
332                 outline: none;
333             }
334
335             .chart-pane:focus .chart-pane-header {
336                 background: rgba(204, 153, 51, 0.1);
337             }
338
339             .chart-pane-body {
340                 position: relative;
341                 width: 100%;
342                 height: 100%;
343             }
344
345             .chart-pane-main {
346                 padding-right: 20rem;
347                 height: 100%;
348                 margin: 0;
349                 vertical-align: middle;
350                 text-align: center;
351             }
352
353             .has-second-sidebar .chart-pane-main {
354                 padding-right: 40rem;
355             }
356
357             .chart-pane-main > * {
358                 width: 100%;
359                 height: 100%;
360             }
361
362             .chart-pane-sidebar,
363             .chart-pane-second-sidebar {
364                 position: absolute;
365                 right: 0;
366                 top: 0;
367                 width: 0;
368                 border-left: solid 1px #ccc;
369                 height: 100%;
370             }
371
372             :not(.has-second-sidebar) > .chart-pane-second-sidebar {
373                 border-left: 0;
374             }
375
376             .chart-pane-sidebar {
377                 width: 20rem;
378             }
379
380             .has-second-sidebar .chart-pane-sidebar {
381                 right: 20rem;
382             }
383
384             .has-second-sidebar .chart-pane-second-sidebar {
385                 width: 20rem;
386             }
387
388             .chart-pane-overview {
389                 width: 100%;
390                 height: 5rem;
391                 border-bottom: solid 1px #ccc;
392             }
393
394             .chart-pane-overview > * {
395                 display: block;
396                 width: 100%;
397                 height: 100%;
398             }
399
400             .chart-pane-details {
401                 position: relative;
402                 display: block;
403                 height: calc(100% - 5.5rem - 2px);
404                 overflow-y: scroll;
405                 padding-top: 0.5rem;
406             }
407         `;
408     }
409
410 }