https://bugs.webkit.org/show_bug.cgi?id=154057
Reviewed by Chris Dumez.
Extracted ChartPaneBase out of ChartPane and added an instance of its new subclass, AnalysisTaskChartPane,
to the analysis task page. The main difference is that ChartPaneBase doesn't depend on the presence of
this._chartsPage unlike ChartPane. It also doesn't have the header with toolbar (to show breakdown, etc...).
* public/v3/components/base.js:
(ComponentBase.prototype._constructShadowTree): Call htmlTemplate() and cssTemplate() with the right "this".
* public/v3/components/chart-pane-base.js: Added.
(ChartPaneBase): Extracted from ChartPane.
(ChartPaneBase.prototype.configure): Extracted from the constructor. Separating this function allows the
component to be instantiated inside a HTML template.
(ChartPaneBase.prototype._fetchAnalysisTasks): Moved from ChartPane._fetchAnalysisTasks.
(ChartPaneBase.prototype.platformId): Ditto.
(ChartPaneBase.prototype.metricId): Ditto.
(ChartPaneBase.prototype.setOverviewDomain): Ditto.
(ChartPaneBase.prototype.setMainDomain): Ditto.
(ChartPaneBase.prototype._overviewSelectionDidChange): Extracted from the constructor. This is overridden in
ChartPane and unused in AnalysisTaskChartPane.
(ChartPaneBase.prototype._mainSelectionDidChange): Extracted from ChartPane._mainSelectionDidChange.
(ChartPaneBase.prototype._mainSelectionDidZoom): Extracted from ChartPane._mainSelectionDidZoom.
(ChartPaneBase.prototype._indicatorDidChange): Extracted from ChartPane._indicatorDidChange.
(ChartPaneBase.prototype._didFetchData): Moved from ChartPane._fetchAnalysisTasks.
(ChartPaneBase.prototype._openAnalysisTask): Ditto.
(ChartPaneBase.prototype._openCommitViewer): Ditto. Also fixed a bug that we don't show the spinner while
waiting for the data to be fetched by calling this.render() here.
(ChartPaneBase.prototype._keyup): Moved from ChartPane._keyup. Also fixed the bug that the revisions list
doesn't update by calling this.render() here.
(ChartPaneBase.prototype.render): Extracted from ChartPane.render.
(ChartPaneBase.htmlTemplate): Extracted from ChartPane.htmlTemplate.
(ChartPaneBase.paneHeaderTemplate): Added. This is overridden in ChartPane and unused in AnalysisTaskChartPane.
(ChartPaneBase.cssTemplate): Extracted from ChartPane.htmlTemplate.
* public/v3/components/chart-styles.js: Renamed from public/v3/pages/page-with-charts.js.
(PageWithCharts): Renamed from PageWithCharts since it no longer extends PageWithHeading.
(ChartStyles.createChartSourceList):
* public/v3/components/commit-log-viewer.js:
(CommitLogViewer.prototype.view): Set this._repository right away instead of waiting for the fetched data
so that spinner will be shown while the data is being fetched.
* public/v3/index.html:
* public/v3/pages/analysis-task-page.js:
(AnalysisTaskChartPane): Added extends ChartPaneBase.
(AnalysisTaskPage): Added. this._chartPane.
(AnalysisTaskPage.prototype._didFetchTask): Initialize this._chartPane with a domain.
(AnalysisTaskPage.prototype.render): Render this._chartPane.
(AnalysisTaskPage.htmlTemplate):
* public/v3/pages/chart-pane-status-view.js:
(ChartPaneStatusView): Removed the unused router from the argument list.
(ChartPaneStatusView.prototype.pointsRangeForAnalysis): Renamed from analyzeData() since it was ambiguous.
(ChartPaneStatusView.prototype.moveRepositoryWithNotification): Fixed the bug that we don't update the list
of the revisions here.
(ChartPaneStatusView.prototype.computeChartStatusLabels):
* public/v3/pages/chart-pane.js:
(ChartPane): Now extends ChartPaneBase.
(ChartPane.prototype._overviewSelectionDidChange): Extracted from the constructor.
(ChartPane.prototype._mainSelectionDidChange):
(ChartPane.prototype._mainSelectionDidZoom):
(ChartPane.prototype._indicatorDidChange):
(ChartPane.prototype.render):
(ChartPane.prototype._renderActionToolbar):
(ChartPane.paneHeaderTemplate): Extracted from htmlTemplate.
(ChartPane.cssTemplate):
(ChartPane.overviewOptions.selection.onchange): Deleted.
(ChartPane.prototype._fetchAnalysisTasks): Deleted.
(ChartPane.prototype.platformId): Deleted.
(ChartPane.prototype.metricId): Deleted.
(ChartPane.prototype.setOverviewDomain): Deleted.
(ChartPane.prototype.setMainDomain): Deleted.
(ChartPane.prototype._openCommitViewer): Deleted.
(ChartPane.prototype._didFetchData): Deleted.
(ChartPane.prototype._keyup): Deleted.
* public/v3/pages/charts-page.js:
(ChartsPage):
(ChartsPage.createDomainForAnalysisTask): Extracted by createDomainForAnalysisTask; used to set the domain
of the charts in the analysis task page.
(ChartsPage.createStateForAnalysisTask):
* public/v3/pages/dashboard-page.js:
(DashboardPage):
(DashboardPage.prototype._createChartForCell):
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@196387
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2016-02-09 Ryosuke Niwa <rniwa@webkit.org>
+
+ Analysis task page on v3 UI should show charts
+ https://bugs.webkit.org/show_bug.cgi?id=154057
+
+ Reviewed by Chris Dumez.
+
+ Extracted ChartPaneBase out of ChartPane and added an instance of its new subclass, AnalysisTaskChartPane,
+ to the analysis task page. The main difference is that ChartPaneBase doesn't depend on the presence of
+ this._chartsPage unlike ChartPane. It also doesn't have the header with toolbar (to show breakdown, etc...).
+
+ * public/v3/components/base.js:
+ (ComponentBase.prototype._constructShadowTree): Call htmlTemplate() and cssTemplate() with the right "this".
+
+ * public/v3/components/chart-pane-base.js: Added.
+ (ChartPaneBase): Extracted from ChartPane.
+ (ChartPaneBase.prototype.configure): Extracted from the constructor. Separating this function allows the
+ component to be instantiated inside a HTML template.
+ (ChartPaneBase.prototype._fetchAnalysisTasks): Moved from ChartPane._fetchAnalysisTasks.
+ (ChartPaneBase.prototype.platformId): Ditto.
+ (ChartPaneBase.prototype.metricId): Ditto.
+ (ChartPaneBase.prototype.setOverviewDomain): Ditto.
+ (ChartPaneBase.prototype.setMainDomain): Ditto.
+ (ChartPaneBase.prototype._overviewSelectionDidChange): Extracted from the constructor. This is overridden in
+ ChartPane and unused in AnalysisTaskChartPane.
+ (ChartPaneBase.prototype._mainSelectionDidChange): Extracted from ChartPane._mainSelectionDidChange.
+ (ChartPaneBase.prototype._mainSelectionDidZoom): Extracted from ChartPane._mainSelectionDidZoom.
+ (ChartPaneBase.prototype._indicatorDidChange): Extracted from ChartPane._indicatorDidChange.
+ (ChartPaneBase.prototype._didFetchData): Moved from ChartPane._fetchAnalysisTasks.
+ (ChartPaneBase.prototype._openAnalysisTask): Ditto.
+ (ChartPaneBase.prototype._openCommitViewer): Ditto. Also fixed a bug that we don't show the spinner while
+ waiting for the data to be fetched by calling this.render() here.
+ (ChartPaneBase.prototype._keyup): Moved from ChartPane._keyup. Also fixed the bug that the revisions list
+ doesn't update by calling this.render() here.
+ (ChartPaneBase.prototype.render): Extracted from ChartPane.render.
+ (ChartPaneBase.htmlTemplate): Extracted from ChartPane.htmlTemplate.
+ (ChartPaneBase.paneHeaderTemplate): Added. This is overridden in ChartPane and unused in AnalysisTaskChartPane.
+ (ChartPaneBase.cssTemplate): Extracted from ChartPane.htmlTemplate.
+
+ * public/v3/components/chart-styles.js: Renamed from public/v3/pages/page-with-charts.js.
+ (PageWithCharts): Renamed from PageWithCharts since it no longer extends PageWithHeading.
+ (ChartStyles.createChartSourceList):
+
+ * public/v3/components/commit-log-viewer.js:
+ (CommitLogViewer.prototype.view): Set this._repository right away instead of waiting for the fetched data
+ so that spinner will be shown while the data is being fetched.
+
+ * public/v3/index.html:
+
+ * public/v3/pages/analysis-task-page.js:
+ (AnalysisTaskChartPane): Added extends ChartPaneBase.
+ (AnalysisTaskPage): Added. this._chartPane.
+ (AnalysisTaskPage.prototype._didFetchTask): Initialize this._chartPane with a domain.
+ (AnalysisTaskPage.prototype.render): Render this._chartPane.
+ (AnalysisTaskPage.htmlTemplate):
+
+ * public/v3/pages/chart-pane-status-view.js:
+ (ChartPaneStatusView): Removed the unused router from the argument list.
+ (ChartPaneStatusView.prototype.pointsRangeForAnalysis): Renamed from analyzeData() since it was ambiguous.
+ (ChartPaneStatusView.prototype.moveRepositoryWithNotification): Fixed the bug that we don't update the list
+ of the revisions here.
+ (ChartPaneStatusView.prototype.computeChartStatusLabels):
+
+ * public/v3/pages/chart-pane.js:
+ (ChartPane): Now extends ChartPaneBase.
+ (ChartPane.prototype._overviewSelectionDidChange): Extracted from the constructor.
+ (ChartPane.prototype._mainSelectionDidChange):
+ (ChartPane.prototype._mainSelectionDidZoom):
+ (ChartPane.prototype._indicatorDidChange):
+ (ChartPane.prototype.render):
+ (ChartPane.prototype._renderActionToolbar):
+ (ChartPane.paneHeaderTemplate): Extracted from htmlTemplate.
+ (ChartPane.cssTemplate):
+ (ChartPane.overviewOptions.selection.onchange): Deleted.
+ (ChartPane.prototype._fetchAnalysisTasks): Deleted.
+ (ChartPane.prototype.platformId): Deleted.
+ (ChartPane.prototype.metricId): Deleted.
+ (ChartPane.prototype.setOverviewDomain): Deleted.
+ (ChartPane.prototype.setMainDomain): Deleted.
+ (ChartPane.prototype._openCommitViewer): Deleted.
+ (ChartPane.prototype._didFetchData): Deleted.
+ (ChartPane.prototype._keyup): Deleted.
+
+ * public/v3/pages/charts-page.js:
+ (ChartsPage):
+ (ChartsPage.createDomainForAnalysisTask): Extracted by createDomainForAnalysisTask; used to set the domain
+ of the charts in the analysis task page.
+ (ChartsPage.createStateForAnalysisTask):
+
+ * public/v3/pages/dashboard-page.js:
+ (DashboardPage):
+ (DashboardPage.prototype._createChartForCell):
+
2016-02-10 Ryosuke Niwa <rniwa@webkit.org>
Add the support for maintenance mode
if (htmlTemplate) {
var template = document.createElement('template');
- template.innerHTML = htmlTemplate();
+ template.innerHTML = newTarget.htmlTemplate();
shadow.appendChild(template.content.cloneNode(true));
this._recursivelyReplaceUnknownElementsByComponents(shadow);
}
if (cssTemplate) {
var style = document.createElement('style');
- style.textContent = cssTemplate();
+ style.textContent = newTarget.cssTemplate();
shadow.appendChild(style);
}
--- /dev/null
+
+class ChartPaneBase extends ComponentBase {
+
+ constructor(name)
+ {
+ super(name);
+
+ this._errorMessage = null;
+ this._platformId = null;
+ this._metricId = null;
+ this._platform = null;
+ this._metric = null;
+
+ this._overviewChart = null;
+ this._mainChart = null;
+ this._mainChartStatus = null;
+ this._commitLogViewer = null;
+ }
+
+ configure(platformId, metricId)
+ {
+ var result = ChartStyles.createChartSourceList(platformId, metricId);
+ this._errorMessage = result.error;
+ this._platformId = platformId;
+ this._metricId = metricId;
+ this._platform = result.platform;
+ this._metric = result.metric;
+
+ this._overviewChart = null;
+ this._mainChart = null;
+ this._mainChartStatus = null;
+
+ this._commitLogViewer = this.content().querySelector('commit-log-viewer').component();
+
+ if (result.error)
+ return;
+
+ var formatter = result.metric.makeFormatter(4);
+ var self = this;
+
+ var overviewOptions = ChartStyles.overviewChartOptions(formatter);
+ overviewOptions.selection.onchange = this._overviewSelectionDidChange.bind(this);
+
+ this._overviewChart = new InteractiveTimeSeriesChart(result.sourceList, overviewOptions);
+ this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart);
+
+ var mainOptions = ChartStyles.mainChartOptions(formatter);
+ mainOptions.indicator.onchange = this._indicatorDidChange.bind(this);
+ mainOptions.selection.onchange = this._mainSelectionDidChange.bind(this);
+ mainOptions.selection.onzoom = this._mainSelectionDidZoom.bind(this);
+ mainOptions.annotations.onclick = this._openAnalysisTask.bind(this);
+ mainOptions.ondata = this._didFetchData.bind(this);
+ this._mainChart = new InteractiveTimeSeriesChart(result.sourceList, mainOptions);
+ this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
+
+ this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart, this._openCommitViewer.bind(this));
+ this.renderReplace(this.content().querySelector('.chart-pane-details'), this._mainChartStatus);
+
+ this.content().querySelector('.chart-pane').addEventListener('keyup', this._keyup.bind(this));
+
+ this._fetchAnalysisTasks(platformId, metricId);
+ }
+
+ _fetchAnalysisTasks(platformId, metricId)
+ {
+ var self = this;
+ AnalysisTask.fetchByPlatformAndMetric(platformId, metricId).then(function (tasks) {
+ self._mainChart.setAnnotations(tasks.map(function (task) {
+ var fillStyle = '#fc6';
+ switch (task.changeType()) {
+ case 'inconclusive':
+ fillStyle = '#fcc';
+ case 'progression':
+ fillStyle = '#39f';
+ break;
+ case 'regression':
+ fillStyle = '#c60';
+ break;
+ case 'unchanged':
+ fillStyle = '#ccc';
+ break;
+ }
+
+ return {
+ task: task,
+ startTime: task.startTime(),
+ endTime: task.endTime(),
+ label: task.label(),
+ fillStyle: fillStyle,
+ };
+ }));
+ });
+ }
+
+ platformId() { return this._platformId; }
+ metricId() { return this._metricId; }
+
+ setOverviewDomain(startTime, endTime)
+ {
+ if (this._overviewChart)
+ this._overviewChart.setDomain(startTime, endTime);
+ }
+
+ setMainDomain(startTime, endTime)
+ {
+ if (this._mainChart)
+ this._mainChart.setDomain(startTime, endTime);
+ }
+
+ _overviewSelectionDidChange(domain, didEndDrag) { }
+
+ _mainSelectionDidChange(selection, didEndDrag)
+ {
+ this.render();
+ }
+
+ _mainSelectionDidZoom(selection)
+ {
+ this._overviewChart.setSelection(selection, this);
+ this._mainChart.setSelection(null);
+ this.render();
+ }
+
+ _indicatorDidChange(indicatorID, isLocked)
+ {
+ this._mainChartStatus.updateRevisionListWithNotification();
+ this.render();
+ }
+
+ _didFetchData()
+ {
+ this._mainChartStatus.updateRevisionListWithNotification();
+ this.render();
+ }
+
+ _openAnalysisTask()
+ { }
+
+ _openCommitViewer(repository, from, to)
+ {
+ this._mainChartStatus.setCurrentRepository(repository);
+ this._commitLogViewer.view(repository, from, to).then(this.render.bind(this));
+ this.render();
+ }
+
+ _keyup(event)
+ {
+ switch (event.keyCode) {
+ case 37: // Left
+ if (!this._mainChart.moveLockedIndicatorWithNotification(false))
+ return;
+ break;
+ case 39: // Right
+ if (!this._mainChart.moveLockedIndicatorWithNotification(true))
+ return;
+ break;
+ case 38: // Up
+ if (!this._mainChartStatus.moveRepositoryWithNotification(false))
+ return;
+ case 40: // Down
+ if (!this._mainChartStatus.moveRepositoryWithNotification(true))
+ return;
+ default:
+ return;
+ }
+
+ this.render();
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ render()
+ {
+ Instrumentation.startMeasuringTime('ChartPane', 'render');
+
+ super.render();
+
+ if (this._errorMessage) {
+ this.renderReplace(this.content().querySelector('.chart-pane-main'), this._errorMessage);
+ return;
+ }
+
+ if (this._mainChartStatus)
+ this._mainChartStatus.render();
+
+ var body = this.content().querySelector('.chart-pane-body');
+ if (this._commitLogViewer && this._commitLogViewer.currentRepository()) {
+ body.classList.add('has-second-sidebar');
+ this._commitLogViewer.render();
+ } else
+ body.classList.remove('has-second-sidebar');
+
+ Instrumentation.endMeasuringTime('ChartPane', 'render');
+ }
+
+ static htmlTemplate()
+ {
+ return `
+ <section class="chart-pane" tabindex="0">
+ ${this.paneHeaderTemplate()}
+ <div class="chart-pane-body">
+ <div class="chart-pane-main"></div>
+ <div class="chart-pane-sidebar">
+ <div class="chart-pane-overview"></div>
+ <div class="chart-pane-details"></div>
+ </div>
+ <div class="chart-pane-second-sidebar">
+ <commit-log-viewer></commit-log-viewer>
+ </div>
+ </div>
+ </section>
+ `;
+ }
+
+ static paneHeaderTemplate() { return ''; }
+
+ static cssTemplate()
+ {
+ return Toolbar.cssTemplate() + `
+ .chart-pane {
+ margin: 1rem;
+ margin-bottom: 2rem;
+ padding: 0rem;
+ height: 18rem;
+ outline: none;
+ }
+
+ .chart-pane:focus .chart-pane-header {
+ background: rgba(204, 153, 51, 0.1);
+ }
+
+ .chart-pane-body {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+
+ .chart-pane-main {
+ padding-right: 20rem;
+ height: 100%;
+ margin: 0;
+ vertical-align: middle;
+ text-align: center;
+ }
+
+ .has-second-sidebar .chart-pane-main {
+ padding-right: 40rem;
+ }
+
+ .chart-pane-main > * {
+ width: 100%;
+ height: 100%;
+ }
+
+ .chart-pane-sidebar,
+ .chart-pane-second-sidebar {
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 0;
+ border-left: solid 1px #ccc;
+ height: 100%;
+ }
+
+ :not(.has-second-sidebar) > .chart-pane-second-sidebar {
+ border-left: 0;
+ }
+
+ .chart-pane-sidebar {
+ width: 20rem;
+ }
+
+ .has-second-sidebar .chart-pane-sidebar {
+ right: 20rem;
+ }
+
+ .has-second-sidebar .chart-pane-second-sidebar {
+ width: 20rem;
+ }
+
+ .chart-pane-overview {
+ width: 100%;
+ height: 5rem;
+ border-bottom: solid 1px #ccc;
+ }
+
+ .chart-pane-overview > * {
+ display: block;
+ width: 100%;
+ height: 100%;
+ }
+
+ .chart-pane-details {
+ position: relative;
+ display: block;
+ height: calc(100% - 5.5rem - 2px);
+ overflow-y: scroll;
+ padding-top: 0.5rem;
+ }
+ `;
+ }
+
+}
+
+class ChartPaneBase extends ComponentBase {
+
+ constructor(name)
+ {
+ super(name);
+
+ this._errorMessage = null;
+ this._platformId = null;
+ this._metricId = null;
+ this._platform = null;
+ this._metric = null;
+
+ this._overviewChart = null;
+ this._mainChart = null;
+ this._mainChartStatus = null;
+ this._commitLogViewer = null;
+ }
+
+ configure(platformId, metricId)
+ {
+ var result = ChartStyles.createChartSourceList(platformId, metricId);
+ this._errorMessage = result.error;
+ this._platformId = platformId;
+ this._metricId = metricId;
+ this._platform = result.platform;
+ this._metric = result.metric;
+
+ this._overviewChart = null;
+ this._mainChart = null;
+ this._mainChartStatus = null;
+
+ this._commitLogViewer = this.content().querySelector('commit-log-viewer').component();
+
+ if (result.error)
+ return;
+
+ var formatter = result.metric.makeFormatter(4);
+ var self = this;
+
+ var overviewOptions = ChartStyles.overviewChartOptions(formatter);
+ overviewOptions.selection.onchange = this._overviewSelectionDidChange.bind(this);
+
+ this._overviewChart = new InteractiveTimeSeriesChart(result.sourceList, overviewOptions);
+ this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart);
+
+ var mainOptions = ChartStyles.mainChartOptions(formatter);
+ mainOptions.indicator.onchange = this._indicatorDidChange.bind(this);
+ mainOptions.selection.onchange = this._mainSelectionDidChange.bind(this);
+ mainOptions.selection.onzoom = this._mainSelectionDidZoom.bind(this);
+ mainOptions.annotations.onclick = this._openAnalysisTask.bind(this);
+ mainOptions.ondata = this._didFetchData.bind(this);
+ this._mainChart = new InteractiveTimeSeriesChart(result.sourceList, mainOptions);
+ this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
+
+ this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart, this._openCommitViewer.bind(this));
+ this.renderReplace(this.content().querySelector('.chart-pane-details'), this._mainChartStatus);
+
+ this.content().querySelector('.chart-pane').addEventListener('keyup', this._keyup.bind(this));
+
+ this._fetchAnalysisTasks(platformId, metricId);
+ }
+
+ _fetchAnalysisTasks(platformId, metricId)
+ {
+ var self = this;
+ AnalysisTask.fetchByPlatformAndMetric(platformId, metricId).then(function (tasks) {
+ self._mainChart.setAnnotations(tasks.map(function (task) {
+ var fillStyle = '#fc6';
+ switch (task.changeType()) {
+ case 'inconclusive':
+ fillStyle = '#fcc';
+ case 'progression':
+ fillStyle = '#39f';
+ break;
+ case 'regression':
+ fillStyle = '#c60';
+ break;
+ case 'unchanged':
+ fillStyle = '#ccc';
+ break;
+ }
+
+ return {
+ task: task,
+ startTime: task.startTime(),
+ endTime: task.endTime(),
+ label: task.label(),
+ fillStyle: fillStyle,
+ };
+ }));
+ });
+ }
+
+ platformId() { return this._platformId; }
+ metricId() { return this._metricId; }
+
+ setOverviewDomain(startTime, endTime)
+ {
+ if (this._overviewChart)
+ this._overviewChart.setDomain(startTime, endTime);
+ }
+
+ setMainDomain(startTime, endTime)
+ {
+ if (this._mainChart)
+ this._mainChart.setDomain(startTime, endTime);
+ }
+
+ _overviewSelectionDidChange(domain, didEndDrag) { }
+
+ _mainSelectionDidChange(selection, didEndDrag)
+ {
+ this.render();
+ }
+
+ _mainSelectionDidZoom(selection)
+ {
+ this._overviewChart.setSelection(selection, this);
+ this._mainChart.setSelection(null);
+ this.render();
+ }
+
+ _indicatorDidChange(indicatorID, isLocked)
+ {
+ this._mainChartStatus.updateRevisionListWithNotification();
+ this.render();
+ }
+
+ _didFetchData()
+ {
+ this._mainChartStatus.updateRevisionListWithNotification();
+ this.render();
+ }
+
+ _openAnalysisTask()
+ { }
+
+ _openCommitViewer(repository, from, to)
+ {
+ this._mainChartStatus.setCurrentRepository(repository);
+ this._commitLogViewer.view(repository, from, to).then(this.render.bind(this));
+ this.render();
+ }
+
+ _keyup(event)
+ {
+ switch (event.keyCode) {
+ case 37: // Left
+ if (!this._mainChart.moveLockedIndicatorWithNotification(false))
+ return;
+ break;
+ case 39: // Right
+ if (!this._mainChart.moveLockedIndicatorWithNotification(true))
+ return;
+ break;
+ case 38: // Up
+ if (!this._mainChartStatus.moveRepositoryWithNotification(false))
+ return;
+ case 40: // Down
+ if (!this._mainChartStatus.moveRepositoryWithNotification(true))
+ return;
+ default:
+ return;
+ }
+
+ this.render();
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ render()
+ {
+ Instrumentation.startMeasuringTime('ChartPane', 'render');
+
+ super.render();
+
+ if (this._errorMessage) {
+ this.renderReplace(this.content().querySelector('.chart-pane-main'), this._errorMessage);
+ return;
+ }
+
+ if (this._mainChartStatus) {
+ this._mainChartStatus.render();
+ this._renderActionToolbar();
+ }
+
+ var body = this.content().querySelector('.chart-pane-body');
+ if (this._commitLogViewer && this._commitLogViewer.currentRepository()) {
+ body.classList.add('has-second-sidebar');
+ this._commitLogViewer.render();
+ } else
+ body.classList.remove('has-second-sidebar');
+
+ Instrumentation.endMeasuringTime('ChartPane', 'render');
+ }
+
+ _renderActionToolbar() { }
+
+ static htmlTemplate()
+ {
+ return `
+ <section class="chart-pane" tabindex="0">
+ ${this.paneHeaderTemplate()}
+ <div class="chart-pane-body">
+ <div class="chart-pane-main"></div>
+ <div class="chart-pane-sidebar">
+ <div class="chart-pane-overview"></div>
+ <div class="chart-pane-details"></div>
+ </div>
+ <div class="chart-pane-second-sidebar">
+ <commit-log-viewer></commit-log-viewer>
+ </div>
+ </div>
+ </section>
+ `;
+ }
+
+ static paneHeaderTemplate() { return ''; }
+
+ static cssTemplate()
+ {
+ return Toolbar.cssTemplate() + `
+ .chart-pane {
+ margin: 1rem;
+ margin-bottom: 2rem;
+ padding: 0rem;
+ height: 18rem;
+ outline: none;
+ }
+
+ .chart-pane:focus .chart-pane-header {
+ background: rgba(204, 153, 51, 0.1);
+ }
+
+ .chart-pane-body {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+
+ .chart-pane-main {
+ padding-right: 20rem;
+ height: 100%;
+ margin: 0;
+ vertical-align: middle;
+ text-align: center;
+ }
+
+ .has-second-sidebar .chart-pane-main {
+ padding-right: 40rem;
+ }
+
+ .chart-pane-main > * {
+ width: 100%;
+ height: 100%;
+ }
+
+ .chart-pane-sidebar,
+ .chart-pane-second-sidebar {
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 0;
+ border-left: solid 1px #ccc;
+ height: 100%;
+ }
+
+ :not(.has-second-sidebar) > .chart-pane-second-sidebar {
+ border-left: 0;
+ }
+
+ .chart-pane-sidebar {
+ width: 20rem;
+ }
+
+ .has-second-sidebar .chart-pane-sidebar {
+ right: 20rem;
+ }
+
+ .has-second-sidebar .chart-pane-second-sidebar {
+ width: 20rem;
+ }
+
+ .chart-pane-overview {
+ width: 100%;
+ height: 5rem;
+ border-bottom: solid 1px #ccc;
+ }
+
+ .chart-pane-overview > * {
+ display: block;
+ width: 100%;
+ height: 100%;
+ }
+
+ .chart-pane-details {
+ position: relative;
+ display: block;
+ height: calc(100% - 5.5rem - 2px);
+ overflow-y: scroll;
+ padding-top: 0.5rem;
+ }
+ `;
+ }
+
+}
-class PageWithCharts extends PageWithHeading {
- constructor(name, toolbar)
- {
- super(name, toolbar);
- this._charts = [];
- }
-
+class ChartStyles {
static createChartSourceList(platformId, metricId)
{
var platform = Platform.findById(platformId);
var promise = CommitLog.fetchBetweenRevisions(repository, from || to, to);
+ this._repository = repository;
this._fetchingPromise = promise;
var self = this;
clearTimeout(spinnerTimer);
if (self._fetchingPromise != promise)
return;
- self._repository = repository;
self._fetchingPromise = null;
self._commits = commits;
});
<script src="components/results-table.js"></script>
<script src="components/analysis-results-viewer.js"></script>
<script src="components/test-group-results-table.js"></script>
+ <script src="components/chart-styles.js"></script>
+ <script src="components/chart-pane-base.js"></script>
<script src="pages/page.js"></script>
<script src="pages/page-router.js"></script>
<script src="pages/heading.js"></script>
<script src="pages/toolbar.js"></script>
<script src="pages/page-with-heading.js"></script>
- <script src="pages/page-with-charts.js"></script>
<script src="pages/domain-control-toolbar.js"></script>
<script src="pages/dashboard-toolbar.js"></script>
<script src="pages/dashboard-page.js"></script>
+class AnalysisTaskChartPane extends ChartPaneBase {
+ constructor() { super('analysis-task-chart-pane'); }
+}
+
+ComponentBase.defineElement('analysis-task-chart-pane', AnalysisTaskChartPane);
+
class AnalysisTaskPage extends PageWithHeading {
constructor()
{
this._endPoint = null;
this._errorMessage = null;
this._currentTestGroup = null;
+ this._chartPane = this.content().querySelector('analysis-task-chart-pane').component();
this._analysisResultsViewer = this.content().querySelector('analysis-results-viewer').component();
this._analysisResultsViewer.setTestGroupCallback(this._showTestGroup.bind(this));
this._testGroupResultsTable = this.content().querySelector('test-group-results-table').component();
this._analysisResultsViewer.setSmallerIsBetter(metric.isSmallerBetter());
this._testGroupResultsTable.setValueFormatter(formatter);
+ this._chartPane.configure(platform.id(), metric.id());
+
+ var domain = ChartsPage.createDomainForAnalysisTask(task);
+ this._chartPane.setOverviewDomain(domain[0], domain[1]);
+ this._chartPane.setMainDomain(domain[0], domain[1]);
+
this.render();
}
this.content().querySelector('.error-message').innerHTML +=
`<p>This page is read only for now. To schedule a new A/B testing job, use <a href="${v2URL}">v2 page</a>.</p>`;
+ this._chartPane.render();
+
if (this._task) {
this.renderReplace(this.content().querySelector('.analysis-task-name'), this._task.name());
var platform = this._task.platform();
<h2 class="analysis-task-name"></h2>
<h3 class="platform-metric-names"><a href=""></a></h3>
<p class="error-message"></p>
- <div class="overview-chart"></div>
+ <div class="overview-chart"><analysis-task-chart-pane></analysis-task-chart-pane></div>
<section class="analysis-results-view">
<analysis-results-viewer></analysis-results-viewer>
</section>
class ChartPaneStatusView extends ChartStatusView {
- constructor(metric, chart, router, revisionCallback)
+ constructor(metric, chart, revisionCallback)
{
super(metric, chart);
- this._router = router;
-
this._buildLabel = null;
this._buildUrl = null;
this._revisionList = [];
this._currentRepository = null;
this._revisionCallback = revisionCallback;
- this._analyzeData = null;
+ this._pointsRangeForAnalysis = null;
this._renderedRevisionList = null;
this._renderedRepository = null;
this._usedRevisionRange = null;
}
- analyzeData() { return this._analyzeData; }
+ pointsRangeForAnalysis() { return this._pointsRangeForAnalysis; }
render()
{
var info = this._revisionList[newIndex];
this.setCurrentRepository(info.repository);
+ this.updateRevisionListWithNotification();
}
updateRevisionListWithNotification()
this._buildInfo = null;
this._revisionList = [];
- this._analyzeData = null;
+ this._pointsRangeForAnalysis = null;
if (!currentPoint)
return;
this._buildInfo = currentMeasurement;
if (currentPoint && previousPoint && this._chart.currentSelection()) {
- this._analyzeData = {
+ this._pointsRangeForAnalysis = {
startPointId: previousPoint.id,
endPointId: currentPoint.id,
};
-class ChartPane extends ComponentBase {
+class ChartPane extends ChartPaneBase {
constructor(chartsPage, platformId, metricId)
{
super('chart-pane');
- this._chartsPage = chartsPage;
- this._platformId = platformId;
- this._metricId = metricId;
-
- var result = ChartsPage.createChartSourceList(platformId, metricId);
- this._errorMessage = result.error;
- this._platform = result.platform;
- this._metric = result.metric;
-
- this._overviewChart = null;
- this._mainChart = null;
- this._mainChartStatus = null;
- this._mainSelection = null;
this._mainChartIndicatorWasLocked = false;
- this._status = null;
- this._revisions = null;
-
+ this._chartsPage = chartsPage;
this._paneOpenedByClick = null;
- this._commitLogViewer = this.content().querySelector('commit-log-viewer').component();
this.content().querySelector('close-button').component().setCallback(chartsPage.closePane.bind(chartsPage, this));
- if (result.error)
- return;
-
- var formatter = result.metric.makeFormatter(4);
- var self = this;
-
- var overviewOptions = ChartsPage.overviewChartOptions(formatter);
- overviewOptions.selection.onchange = function (domain, didEndDrag) {
- self._chartsPage.setMainDomainFromOverviewSelection(domain, self, didEndDrag);
- }
-
- this._overviewChart = new InteractiveTimeSeriesChart(result.sourceList, overviewOptions);
- this.renderReplace(this.content().querySelector('.chart-pane-overview'), this._overviewChart);
-
- var mainOptions = ChartsPage.mainChartOptions(formatter);
- mainOptions.indicator.onchange = this._indicatorDidChange.bind(this);
- mainOptions.selection.onchange = this._mainSelectionDidChange.bind(this);
- mainOptions.selection.onzoom = this._mainSelectionDidZoom.bind(this);
- mainOptions.annotations.onclick = this._openAnalysisTask.bind(this);
- mainOptions.ondata = this._didFetchData.bind(this);
- this._mainChart = new InteractiveTimeSeriesChart(result.sourceList, mainOptions);
- this.renderReplace(this.content().querySelector('.chart-pane-main'), this._mainChart);
-
- this._mainChartStatus = new ChartPaneStatusView(result.metric, this._mainChart, chartsPage.router(), this._openCommitViewer.bind(this));
- this.renderReplace(this.content().querySelector('.chart-pane-details'), this._mainChartStatus);
-
- this.content().querySelector('.chart-pane').addEventListener('keyup', this._keyup.bind(this));
- this._fetchAnalysisTasks();
+ this.configure(platformId, metricId);
}
- _fetchAnalysisTasks()
- {
- var self = this;
- AnalysisTask.fetchByPlatformAndMetric(this._platformId, this._metricId).then(function (tasks) {
- self._mainChart.setAnnotations(tasks.map(function (task) {
- var fillStyle = '#fc6';
- switch (task.changeType()) {
- case 'inconclusive':
- fillStyle = '#fcc';
- case 'progression':
- fillStyle = '#39f';
- break;
- case 'regression':
- fillStyle = '#c60';
- break;
- case 'unchanged':
- fillStyle = '#ccc';
- break;
- }
-
- return {
- task: task,
- startTime: task.startTime(),
- endTime: task.endTime(),
- label: task.label(),
- fillStyle: fillStyle,
- };
- }));
- });
- }
-
- platformId() { return this._platformId; }
- metricId() { return this._metricId; }
-
serializeState()
{
var selection = this._mainChart ? this._mainChart.currentSelection() : null;
this._mainChart.setIndicator(null, false);
}
- setOverviewDomain(startTime, endTime)
- {
- if (this._overviewChart)
- this._overviewChart.setDomain(startTime, endTime);
- }
-
setOverviewSelection(selection)
{
if (this._overviewChart)
this._overviewChart.setSelection(selection);
}
- setMainDomain(startTime, endTime)
+ _overviewSelectionDidChange(domain, didEndDrag)
{
- if (this._mainChart)
- this._mainChart.setDomain(startTime, endTime);
+ super._overviewSelectionDidChange(domain, didEndDrag);
+ this._chartsPage.setMainDomainFromOverviewSelection(domain, this, didEndDrag);
}
_mainSelectionDidChange(selection, didEndDrag)
{
+ super._mainSelectionDidChange(selection, didEndDrag);
this._chartsPage.mainChartSelectionDidChange(this, didEndDrag);
- this.render();
}
_mainSelectionDidZoom(selection)
{
- this._overviewChart.setSelection(selection, this);
- this._mainChart.setSelection(null);
+ super._mainSelectionDidZoom(selection);
this._chartsPage.setMainDomainFromZoom(selection, this);
- this.render();
}
_openAnalysisTask(annotation)
_indicatorDidChange(indicatorID, isLocked)
{
- this._chartsPage.mainChartIndicatorDidChange(this, isLocked || this._mainChartIndicatorWasLocked);
this._mainChartIndicatorWasLocked = isLocked;
- this._mainChartStatus.updateRevisionListWithNotification();
- this.render();
- }
-
- _openCommitViewer(repository, from, to)
- {
- var self = this;
- this._commitLogViewer.view(repository, from, to).then(function () {
- self._mainChartStatus.setCurrentRepository(self._commitLogViewer.currentRepository());
- self.render();
- });
- }
-
- _didFetchData()
- {
- this._mainChartStatus.updateRevisionListWithNotification();
- this.render();
- }
-
- _keyup(event)
- {
- switch (event.keyCode) {
- case 37: // Left
- if (!this._mainChart.moveLockedIndicatorWithNotification(false))
- return;
- break;
- case 39: // Right
- if (!this._mainChart.moveLockedIndicatorWithNotification(true))
- return;
- break;
- case 38: // Up
- if (!this._mainChartStatus.moveRepositoryWithNotification(false))
- return;
- case 40: // Down
- if (!this._mainChartStatus.moveRepositoryWithNotification(true))
- return;
- default:
- return;
- }
-
- event.preventDefault();
- event.stopPropagation();
+ this._chartsPage.mainChartIndicatorDidChange(this, isLocked || this._mainChartIndicatorWasLocked);
+ super._indicatorDidChange(indicatorID, isLocked);
}
render()
{
- Instrumentation.startMeasuringTime('ChartPane', 'render');
-
- super.render();
-
if (this._platform && this._metric) {
var metric = this._metric;
var platform = this._platform;
metric.fullName() + ' on ' + platform.name());
}
- if (this._errorMessage) {
- this.renderReplace(this.content().querySelector('.chart-pane-main'), this._errorMessage);
- return;
- }
-
- if (this._mainChartStatus) {
- this._mainChartStatus.render();
- this._renderActionToolbar(this._mainChartStatus.analyzeData());
- }
-
- var body = this.content().querySelector('.chart-pane-body');
- if (this._commitLogViewer.currentRepository()) {
- body.classList.add('has-second-sidebar');
- this._commitLogViewer.render();
- } else
- body.classList.remove('has-second-sidebar');
+ if (this._mainChartStatus)
+ this._renderActionToolbar();
- Instrumentation.endMeasuringTime('ChartPane', 'render');
+ super.render();
}
- _renderActionToolbar(analyzeData)
+ _renderActionToolbar()
{
var actions = [];
var platform = this._platform;
}
var analyzePane = this.content().querySelector('.chart-pane-analyze-pane');
- if (analyzeData) {
+ var pointsRangeForAnalysis = this._mainChartStatus.pointsRangeForAnalysis();
+ if (pointsRangeForAnalysis) {
actions.push(element('li', {class: this._paneOpenedByClick == analyzePane ? 'selected' : ''},
this._makeAnchorToOpenPane(analyzePane, 'Analyze', false)));
var newWindow = window.open(router.url('analysis/task/create'), '_blank');
var name = analyzePane.querySelector('input').value;
- AnalysisTask.create(name, analyzeData.startPointId, analyzeData.endPointId).then(function (data) {
+ AnalysisTask.create(name, pointsRangeForAnalysis.startPointId, pointsRangeForAnalysis.endPointId).then(function (data) {
newWindow.location.href = router.url('analysis/task/' + data['taskId']);
// FIXME: Refetch the list of analysis tasks.
}, function (error) {
return anchor;
}
- static htmlTemplate()
+ static paneHeaderTemplate()
{
return `
- <section class="chart-pane" tabindex="0">
- <header class="chart-pane-header">
- <h2 class="chart-pane-title">-</h2>
- <nav class="chart-pane-actions">
- <ul>
- <li class="close"><close-button></close-button></li>
- </ul>
- <ul class="chart-pane-action-buttons buttoned-toolbar"></ul>
- <ul class="chart-pane-alternative-platforms" style="display:none"></ul>
- <form class="chart-pane-analyze-pane" style="display:none">
- <input type="text" required>
- <button>Create</button>
- </form>
- </nav>
- </header>
- <div class="chart-pane-body">
- <div class="chart-pane-main"></div>
- <div class="chart-pane-sidebar">
- <div class="chart-pane-overview"></div>
- <div class="chart-pane-details"></div>
- </div>
- <div class="chart-pane-second-sidebar">
- <commit-log-viewer></commit-log-viewer>
- </div>
- </div>
- </section>
-`;
+ <header class="chart-pane-header">
+ <h2 class="chart-pane-title">-</h2>
+ <nav class="chart-pane-actions">
+ <ul>
+ <li class="close"><close-button></close-button></li>
+ </ul>
+ <ul class="chart-pane-action-buttons buttoned-toolbar"></ul>
+ <ul class="chart-pane-alternative-platforms" style="display:none"></ul>
+ <form class="chart-pane-analyze-pane" style="display:none">
+ <input type="text" required>
+ <button>Create</button>
+ </form>
+ </nav>
+ </header>
+ `;
}
static cssTemplate()
{
- return Toolbar.cssTemplate() + `
+ return ChartPaneBase.cssTemplate() + `
.chart-pane {
- margin: 1rem;
- margin-bottom: 2rem;
- padding: 0rem;
- height: 18rem;
border: solid 1px #ccc;
border-radius: 0.5rem;
- outline: none;
}
- .chart-pane:focus .chart-pane-header {
- background: rgba(204, 153, 51, 0.1);
+ .chart-pane-body {
+ height: calc(100% - 2rem);
}
.chart-pane-header {
outline: none;
border: solid 1px #ccc;
}
-
- .chart-pane-body {
- position: relative;
- width: 100%;
- height: calc(100% - 2rem);
- }
-
- .chart-pane-main {
- padding-right: 20rem;
- height: 100%;
- margin: 0;
- vertical-align: middle;
- text-align: center;
- }
-
- .has-second-sidebar .chart-pane-main {
- padding-right: 40rem;
- }
-
- .chart-pane-main > * {
- width: 100%;
- height: 100%;
- }
-
- .chart-pane-sidebar,
- .chart-pane-second-sidebar {
- position: absolute;
- right: 0;
- top: 0;
- width: 0;
- border-left: solid 1px #ccc;
- height: 100%;
- }
-
- :not(.has-second-sidebar) > .chart-pane-second-sidebar {
- border-left: 0;
- }
-
- .chart-pane-sidebar {
- width: 20rem;
- }
-
- .has-second-sidebar .chart-pane-sidebar {
- right: 20rem;
- }
-
- .has-second-sidebar .chart-pane-second-sidebar {
- width: 20rem;
- }
-
- .chart-pane-overview {
- width: 100%;
- height: 5rem;
- border-bottom: solid 1px #ccc;
- }
-
- .chart-pane-overview > * {
- display: block;
- width: 100%;
- height: 100%;
- }
-
- .chart-pane-details {
- position: relative;
- display: block;
- height: calc(100% - 5.5rem - 2px);
- overflow-y: scroll;
- padding-top: 0.5rem;
- }
`;
}
}
-class ChartsPage extends PageWithCharts {
+class ChartsPage extends PageWithHeading {
constructor(toolbar)
{
console.assert(toolbar instanceof ChartsToolbar);
return state;
}
- static createStateForAnalysisTask(task)
+ static createDomainForAnalysisTask(task)
{
var diff = (task.endTime() - task.startTime()) * 0.1;
+ return [task.startTime() - diff, task.endTime() + diff];
+ }
+
+ static createStateForAnalysisTask(task)
+ {
+ var domain = this.createDomainForAnalysisTask(task);
var state = {
paneList: [[task.platform().id(), task.metric().id()]],
since: Math.round(task.startTime() - (Date.now() - task.startTime()) * 0.1),
- zoom: [task.startTime() - diff, task.endTime() + diff],
+ zoom: domain,
};
return state;
}
-class DashboardPage extends PageWithCharts {
+class DashboardPage extends PageWithHeading {
constructor(name, table, toolbar)
{
console.assert(toolbar instanceof DashboardToolbar);
super(name, toolbar);
this._table = table;
+ this._charts = [];
this._needsTableConstruction = true;
this._needsStatusUpdate = true;
this._statusViews = [];
if (!platformId || !metricId)
return '';
- var result = DashboardPage.createChartSourceList(platformId, metricId);
+ var result = ChartStyles.createChartSourceList(platformId, metricId);
if (result.error)
return result.error;
- var options = DashboardPage.dashboardOptions(result.metric.makeFormatter(3));
+ var options = ChartStyles.dashboardOptions(result.metric.makeFormatter(3));
options.ondata = this._fetchedData.bind(this);
var chart = new TimeSeriesChart(result.sourceList, options);
this._charts.push(chart);