Mercurial > hg > thermostat-ng > web-client
changeset 176:6c3ce29d353f
Convert multicharts module to component
Chart component split from multicharts view component
Reviewed-by: jkang
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-August/024755.html
author | Andrew Azores <aazores@redhat.com> |
---|---|
date | Wed, 30 Aug 2017 14:21:56 -0400 |
parents | e10e61e34baf |
children | 97e433c06ff8 |
files | src/app/components/multichart/chart.controller.js src/app/components/multichart/chart.controller.spec.js src/app/components/multichart/chart/chart.component.js src/app/components/multichart/chart/chart.controller.js src/app/components/multichart/chart/chart.controller.spec.js src/app/components/multichart/chart/chart.html src/app/components/multichart/chart/en.locale.yaml src/app/components/multichart/en.locale.yaml src/app/components/multichart/multichart.component.js src/app/components/multichart/multichart.controller.js src/app/components/multichart/multichart.controller.spec.js src/app/components/multichart/multichart.html src/app/components/multichart/multichart.module.js src/app/components/multichart/multichart.routing.js src/app/components/multichart/multichart.routing.spec.js |
diffstat | 15 files changed, 742 insertions(+), 734 deletions(-) [+] |
line wrap: on
line diff
--- a/src/app/components/multichart/chart.controller.js Thu Aug 31 08:40:56 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,184 +0,0 @@ -/** - * Copyright 2012-2017 Red Hat, Inc. - * - * Thermostat is distributed under the GNU General Public License, - * version 2 or any later version (with a special exception described - * below, commonly known as the "Classpath Exception"). - * - * A copy of GNU General Public License (GPL) is included in this - * distribution, in the file COPYING. - * - * Linking Thermostat code with other modules is making a combined work - * based on Thermostat. Thus, the terms and conditions of the GPL - * cover the whole combination. - * - * As a special exception, the copyright holders of Thermostat give you - * permission to link this code with independent modules to produce an - * executable, regardless of the license terms of these independent - * modules, and to copy and distribute the resulting executable under - * terms of your choice, provided that you also meet, for each linked - * independent module, the terms and conditions of the license of that - * module. An independent module is a module which is not derived from - * or based on Thermostat code. If you modify Thermostat, you may - * extend this exception to your version of the software, but you are - * not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - */ - -import 'c3'; -import services from 'shared/services/services.module.js'; -import filters from 'shared/filters/filters.module.js'; - -class MultiChartController { - constructor (multichartService, $scope, $interval, dateFilter, DATE_FORMAT, $translate) { - this.svc = multichartService; - this.scope = $scope; - this.interval = $interval; - this.dateFilter = dateFilter; - this.dateFormat = DATE_FORMAT; - this.translate = $translate; - this.chart = $scope.$parent.chart; - - this.initializeChartData(); - - this.scope.$on('$destroy', () => this.stop()); - - this.scope.refreshRate = '2000'; - this.scope.dataAgeLimit = '60000'; - - this.scope.$watch('refreshRate', (cur, prev) => this.setRefreshRate(cur)); - this.scope.$watch('dataAgeLimit', () => this.trimData()); - - this.refresh = $interval(() => this.update(), parseInt(this.scope.refreshRate)); - this.update(); - } - - update () { - this.svc.getData(this.chart).then(data => { - let keys = Object.keys(data); - if (keys.length === 0) { - return; - } - - this.chartData.xData.push(Date.now()); - keys.forEach(prop => { - if (this.chartData.hasOwnProperty(prop)) { - this.chartData[prop].push(data[prop][1]); - } else { - this.chartData[prop] = data[prop]; - } - }); - this.chartConfig.data.axes = this.svc.getAxesForChart(this.chart); - - this.trimData(); - }, angular.noop); - } - - stop () { - if (angular.isDefined(this.refresh)) { - this.interval.cancel(this.refresh); - delete this.refresh; - } - } - - trimData () { - if (!angular.isDefined(this.chartData)) { - return; - } - let now = Date.now(); - let oldestLimit = now - parseInt(this.scope.dataAgeLimit); - - while (true) { - if (this.chartData.xData.length <= 2) { - break; - } - let oldest = this.chartData.xData[1]; - if (oldest < oldestLimit) { - Object.keys(this.chartData).forEach(key => { - this.chartData[key].splice(1, 1); - }); - } else { - break; - } - } - } - - initializeChartData () { - this.translate([ - 'multicharts.chart.X_AXIS_LABEL', - 'multicharts.chart.X_AXIS_DATA_TYPE' - ]).then(translations => { - let self = this; - this.chartConfig = { - chartId: 'chart-' + this.chart, - axis: { - x: { - label: translations['multicharts.chart.X_AXIS_LABEL'], - type: 'timeseries', - localtime: false, - tick: { - format: timestamp => this.dateFilter(timestamp, this.dateFormat.time.medium), - count: 5 - } - }, - y: { - tick: { - format: d => d - } - }, - y2: { - get show () { - return self.svc.countServicesForChart(self.chart) > 1; - } - } - }, - tooltip: { - format: { - title: x => x, - value: y => y - } - } - }; - this.chartData = { - xData: [translations['multicharts.chart.X_AXIS_DATA_TYPE']] - }; - }); - } - - removeChart () { - this.svc.removeChart(this.chart); - } - - setRefreshRate (val) { - this.stop(); - if (val > 0) { - this.refresh = this.interval(() => this.update(), val); - } - } - - rename (to) { - if (!to) { - return; - } - to = to.trim(); - if (!this.isValid(to)) { - return; - } - this.svc.rename(this.chart, to); - } - - isValid (chartName) { - // TODO: this needs to accept letters outside of the English alphabet - return chartName.search(/^[\w-]+$/) > -1; - } -} - -export default angular - .module('multichartChartController', [ - 'patternfly', - 'patternfly.charts', - services, - filters - ]) - .controller('MultiChartChartController', MultiChartController) - .name;
--- a/src/app/components/multichart/chart.controller.spec.js Thu Aug 31 08:40:56 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,373 +0,0 @@ -/** - * Copyright 2012-2017 Red Hat, Inc. - * - * Thermostat is distributed under the GNU General Public License, - * version 2 or any later version (with a special exception described - * below, commonly known as the "Classpath Exception"). - * - * A copy of GNU General Public License (GPL) is included in this - * distribution, in the file COPYING. - * - * Linking Thermostat code with other modules is making a combined work - * based on Thermostat. Thus, the terms and conditions of the GPL - * cover the whole combination. - * - * As a special exception, the copyright holders of Thermostat give you - * permission to link this code with independent modules to produce an - * executable, regardless of the license terms of these independent - * modules, and to copy and distribute the resulting executable under - * terms of your choice, provided that you also meet, for each linked - * independent module, the terms and conditions of the license of that - * module. An independent module is a module which is not derived from - * or based on Thermostat code. If you modify Thermostat, you may - * extend this exception to your version of the software, but you are - * not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - */ - -import controllerModule from './chart.controller.js'; - -describe('multichart chartController', () => { - - let ctrl, svc, scope, interval, dateFilter, translate; - beforeEach(() => { - angular.mock.module(controllerModule); - angular.mock.inject($controller => { - 'ngInject'; - - let getDataPromise = sinon.spy(); - let getData = sinon.stub().returns({ - then: getDataPromise - }); - svc = { - getData: getData, - getDataPromise: getDataPromise, - getAxesForChart: sinon.stub().returns(['y']), - countServicesForChart: sinon.stub().returns(2), - removeChart: sinon.spy(), - rename: sinon.spy() - }; - scope = { - $parent: { - chart: 'foo-chart' - }, - $on: sinon.spy(), - $watch: sinon.spy() - }; - interval = sinon.stub().returns('interval-sentinel'); - interval.cancel = sinon.spy(); - dateFilter = sinon.stub().returns('dateFilter-sentinel'); - translate = sinon.stub().returns({ - then: sinon.stub().yields({ - 'multicharts.chart.X_AXIS_LABEL': 'timestamp', - 'multicharts.chart.X_AXIS_DATA_TYPE': 'timestamp' - }) - }); - - ctrl = $controller('MultiChartChartController', { - multichartService: svc, - $scope: scope, - $interval: interval, - dateFilter: dateFilter, - DATE_FORMAT: { time: { medium: 'medium-time' } }, - $translate: translate - }); - ctrl.chartConfig.data = {}; - }); - }); - - it('should exist', () => { - should.exist(ctrl); - }); - - it('should stop on destroy', () => { - scope.$on.should.be.calledWith('$destroy'); - scope.$on.callCount.should.equal(1); - let fn = scope.$on.args[0][1]; - fn.should.be.a.Function(); - - interval.cancel.should.not.be.called(); - fn(); - interval.cancel.should.be.calledOnce(); - interval.cancel.should.be.calledWith('interval-sentinel'); - }); - - it('should do nothing if stopped when already stopped', () => { - interval.cancel.should.not.be.called(); - ctrl.stop(); - interval.cancel.should.be.calledOnce(); - ctrl.stop(); - interval.cancel.should.be.calledOnce(); - }); - - it('should initialize refreshRate', () => { - scope.should.have.ownProperty('refreshRate'); - scope.refreshRate.should.equal('2000'); - }); - - it('should initialize dataAgeLimit', () => { - scope.should.have.ownProperty('dataAgeLimit'); - scope.dataAgeLimit.should.equal('60000'); - }); - - it('should begin updating at refreshRate period', () => { - svc.getData.should.be.calledOnce(); - interval.should.be.calledOnce(); - interval.should.be.calledWithMatch(sinon.match.func, sinon.match(2000)); - let fn = interval.args[0][0]; - fn(); - svc.getData.should.be.calledTwice(); - }); - - it('should watch refreshRate changes', () => { - interval.cancel.should.not.be.called(); - scope.$watch.should.be.calledWithMatch(sinon.match('refreshRate'), sinon.match.func); - let fn = scope.$watch.withArgs('refreshRate').args[0][1]; - fn.should.be.a.Function(); - interval.returns('foo-sentinel'); - fn(5); - interval.cancel.should.be.calledOnce(); - ctrl.refresh.should.equal('foo-sentinel'); - interval.should.be.calledWithMatch(sinon.match.func, sinon.match(5)); - }); - - it('should watch dataAgeLimit changes', () => { - let spy = sinon.spy(ctrl, 'trimData'); - scope.$watch.should.be.calledWithMatch(sinon.match('dataAgeLimit'), sinon.match.func); - let fn = scope.$watch.withArgs('dataAgeLimit').args[0][1]; - fn.should.be.a.Function(); - fn(); - spy.should.be.calledOnce(); - spy.restore(); - }); - - describe('update', () => { - let fn; - beforeEach(() => { - fn = svc.getDataPromise.args[0][0]; - fn.should.be.a.Function(); - }); - - it('should pass chart ID', () => { - ctrl.update(); - svc.getData.should.be.calledWith(scope.$parent.chart); - }); - - it('should do nothing if data is empty', () => { - ctrl.chartData.xData.length.should.equal(1); - fn({}); - ctrl.chartData.xData.length.should.equal(1); - }); - - it('should append new data', () => { - ctrl.chartData.xData.length.should.equal(1); - ctrl.should.not.have.ownProperty('yData'); - fn({ - yData: ['someMetric', 100] - }); - ctrl.chartData.xData.length.should.equal(2); - ctrl.chartData.should.have.ownProperty('yData'); - ctrl.chartData.yData.length.should.equal(2); - }); - - it('should append data for existing metric', () => { - ctrl.chartData.xData.length.should.equal(1); - ctrl.should.not.have.ownProperty('yData'); - fn({ - yData: ['someMetric', 100] - }); - ctrl.chartData.xData.length.should.equal(2); - ctrl.chartData.should.have.ownProperty('yData'); - ctrl.chartData.yData.length.should.equal(2); - fn({ - yData: ['someMetric', 200] - }); - ctrl.chartData.xData.length.should.equal(3); - ctrl.chartData.yData.length.should.equal(3); - }); - }); - - describe('trimData', () => { - let dateNowStub; - beforeEach(() => { - dateNowStub = sinon.stub(Date, 'now'); - }); - - afterEach(() => { - Date.now.restore(); - }); - - it('should do nothing if chartData undefined', () => { - delete ctrl.chartData; - ctrl.trimData(); - should(ctrl.chartData).be.undefined(); - }); - - it('should do nothing if no samples', () => { - ctrl.trimData(); - ctrl.chartData.should.eql({ - xData: ['timestamp'] - }); - }); - - it('should not trim data if only one sample', () => { - scope.dataAgeLimit = 10; - let updateFn = svc.getDataPromise.args[0][0]; - - dateNowStub.returns(100); - updateFn({ - yData: ['foo', 500], - yData1: ['bar', 5000] - }); - - dateNowStub.returns(200); - ctrl.trimData(); - - ctrl.chartData.should.eql({ - xData: ['timestamp', 100], - yData: ['foo', 500], - yData1: ['bar', 5000] - }); - }); - - it('should trim old data', () => { - scope.dataAgeLimit = 200; - let updateFn = svc.getDataPromise.args[0][0]; - - dateNowStub.returns(100); - updateFn({ - yData: ['foo', 500], - yData1: ['bar', 5000] - }); - - dateNowStub.returns(200); - updateFn({ - yData: ['foo', 600], - yData1: ['bar', 6000] - }); - - dateNowStub.returns(300); - updateFn({ - yData: ['foo', 700], - yData1: ['bar', 7000] - }); - - dateNowStub.returns(350); - - ctrl.chartData.should.eql({ - xData: ['timestamp', 100, 200, 300], - yData: ['foo', 500, 600, 700], - yData1: ['bar', 5000, 6000, 7000] - }); - - ctrl.trimData(); - - ctrl.chartData.should.eql({ - xData: ['timestamp', 200, 300], - yData: ['foo', 600, 700], - yData1: ['bar', 6000, 7000] - }); - }); - }); - - describe('initializeChartData', () => { - it('should set chartId', () => { - ctrl.chartConfig.chartId.should.equal('chart-foo-chart'); - }); - - it('should format x-axis ticks using dateFilter', () => { - dateFilter.should.not.be.called(); - let fmtFn = ctrl.chartConfig.axis.x.tick.format; - fmtFn(100).should.equal('dateFilter-sentinel'); - dateFilter.should.be.calledOnce(); - dateFilter.should.be.calledWith(100, 'medium-time'); - }); - - it('should format y-axis ticks with identity function', () => { - let fmtFn = ctrl.chartConfig.axis.y.tick.format; - fmtFn(1).should.equal(1); - fmtFn('foo').should.equal('foo'); - }); - - it('should show y2 axis if suggested by service', () => { - svc.countServicesForChart.should.not.be.called(); - let res = ctrl.chartConfig.axis.y2.show; - res.should.equal(true); - svc.countServicesForChart.should.be.calledOnce(); - svc.countServicesForChart.should.be.calledWith('foo-chart'); - }); - - it('should format tooltips', () => { - let titleFmt = ctrl.chartConfig.tooltip.format.title; - let valueFmt = ctrl.chartConfig.tooltip.format.value; - - titleFmt('foo').should.equal('foo'); - titleFmt(100).should.equal(100); - - valueFmt('foo').should.equal('foo'); - valueFmt(100).should.equal(100); - }); - }); - - describe('removeChart', () => { - it('should delegate to service', () => { - svc.removeChart.should.not.be.called(); - ctrl.removeChart(); - svc.removeChart.should.be.calledOnce(); - svc.removeChart.should.be.calledWith('foo-chart'); - }); - }); - - describe('setRefreshRate', () => { - it('should stop previous refreshes', () => { - interval.cancel.should.not.be.called(); - ctrl.setRefreshRate(5); - interval.cancel.should.be.calledOnce(); - }); - - it('should set the new update interval', () => { - interval.should.be.calledOnce(); - ctrl.setRefreshRate(5); - interval.should.be.calledTwice(); - interval.secondCall.should.be.calledWith(sinon.match.func, 5); - }); - - it('should perform updates at each interval', () => { - ctrl.setRefreshRate(5); - svc.getData.should.be.calledOnce(); - let fn = interval.secondCall.args[0]; - fn(); - svc.getData.should.be.calledTwice(); - }); - - it('should cancel if given non-positive value', () => { - interval.cancel.should.not.be.called(); - svc.getData.should.be.calledOnce(); - ctrl.setRefreshRate(-1); - interval.cancel.should.be.calledOnce(); - svc.getData.should.be.calledOnce(); - }); - }); - - describe('rename', () => { - it('should delegate to service', () => { - svc.rename.should.not.be.called(); - ctrl.rename('newname'); - svc.rename.should.be.calledOnce(); - svc.rename.should.be.calledWith('foo-chart', 'newname'); - }); - - it('should do nothing if chart name is empty', () => { - svc.rename.should.not.be.called(); - ctrl.rename(''); - svc.rename.should.not.be.called(); - }); - - it('should do nothing if chart name is invalid', () => { - svc.rename.should.not.be.called(); - ctrl.rename('with space'); - svc.rename.should.not.be.called(); - }); - }); - -});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/multichart/chart/chart.component.js Wed Aug 30 14:21:56 2017 -0400 @@ -0,0 +1,39 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +import controller from './chart.controller.js'; + +export default angular + .module('multichartChartComponent', [controller]) + .component('multichartChart', { + bindings: { + chart: '<' + }, + controller: 'MultichartChartController', + template: require('./chart.html') + }) + .name;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/multichart/chart/chart.controller.js Wed Aug 30 14:21:56 2017 -0400 @@ -0,0 +1,193 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +import 'c3'; +import services from 'shared/services/services.module.js'; +import filters from 'shared/filters/filters.module.js'; + +class MultichartChartController { + constructor (multichartService, $scope, $interval, dateFilter, DATE_FORMAT, $translate) { + this.svc = multichartService; + this.interval = $interval; + this.dateFilter = dateFilter; + this.dateFormat = DATE_FORMAT; + this.translate = $translate; + this.chart = $scope.$parent.chart; + + this.initializeChartData(); + + $scope.$on('$destroy', () => this.stop()); + + this._refreshRate = 2000; + this._dataAgeLimit = 60000; + + this.refresh = $interval(() => this.update(), this._refreshRate); + this.update(); + } + + update () { + this.svc.getData(this.chart).then(data => { + let keys = Object.keys(data); + if (keys.length === 0) { + return; + } + + this.chartData.xData.push(Date.now()); + keys.forEach(prop => { + if (this.chartData.hasOwnProperty(prop)) { + this.chartData[prop].push(data[prop][1]); + } else { + this.chartData[prop] = data[prop]; + } + }); + this.chartConfig.data.axes = this.svc.getAxesForChart(this.chart); + + this.trimData(); + }, angular.noop); + } + + stop () { + if (angular.isDefined(this.refresh)) { + this.interval.cancel(this.refresh); + delete this.refresh; + } + } + + trimData () { + if (!angular.isDefined(this.chartData)) { + return; + } + let now = Date.now(); + let oldestLimit = now - this._dataAgeLimit; + + while (true) { + if (this.chartData.xData.length <= 2) { + break; + } + let oldest = this.chartData.xData[1]; + if (oldest < oldestLimit) { + Object.keys(this.chartData).forEach(key => { + this.chartData[key].splice(1, 1); + }); + } else { + break; + } + } + } + + initializeChartData () { + this.translate([ + 'multicharts.chart.X_AXIS_LABEL', + 'multicharts.chart.X_AXIS_DATA_TYPE' + ]).then(translations => { + let self = this; + this.chartConfig = { + chartId: 'chart-' + this.chart, + axis: { + x: { + label: translations['multicharts.chart.X_AXIS_LABEL'], + type: 'timeseries', + localtime: false, + tick: { + format: timestamp => this.dateFilter(timestamp, this.dateFormat.time.medium), + count: 5 + } + }, + y: { + tick: { + format: d => d + } + }, + y2: { + get show () { + return self.svc.countServicesForChart(self.chart) > 1; + } + } + }, + tooltip: { + format: { + title: x => x, + value: y => y + } + } + }; + this.chartData = { + xData: [translations['multicharts.chart.X_AXIS_DATA_TYPE']] + }; + }); + } + + removeChart () { + this.svc.removeChart(this.chart); + } + + get refreshRate () { + return this._refreshRate.toString(); + } + + set refreshRate (val) { + this.stop(); + if (val > 0) { + this.refresh = this.interval(() => this.update(), val); + } + } + + get dataAgeLimit () { + return this._dataAgeLimit.toString(); + } + + set dataAgeLimit (val) { + this._dataAgeLimit = parseInt(val); + this.trimData(); + } + + rename (to) { + if (!to) { + return; + } + to = to.trim(); + if (!this.isValid(to)) { + return; + } + this.svc.rename(this.chart, to); + } + + isValid (chartName) { + // TODO: this needs to accept letters outside of the English alphabet + return chartName.search(/^[\w-]+$/) > -1; + } +} + +export default angular + .module('multichartChartController', [ + 'patternfly', + 'patternfly.charts', + services, + filters + ]) + .controller('MultichartChartController', MultichartChartController) + .name;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/multichart/chart/chart.controller.spec.js Wed Aug 30 14:21:56 2017 -0400 @@ -0,0 +1,349 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +import controllerModule from './chart.controller.js'; + +describe('multichart chartController', () => { + + let ctrl, svc, scope, interval, dateFilter, translate; + beforeEach(() => { + angular.mock.module(controllerModule); + angular.mock.inject($controller => { + 'ngInject'; + + let getDataPromise = sinon.spy(); + let getData = sinon.stub().returns({ + then: getDataPromise + }); + svc = { + getData: getData, + getDataPromise: getDataPromise, + getAxesForChart: sinon.stub().returns(['y']), + countServicesForChart: sinon.stub().returns(2), + removeChart: sinon.spy(), + rename: sinon.spy() + }; + scope = { + $parent: { + chart: 'foo-chart' + }, + $on: sinon.spy(), + $watch: sinon.spy() + }; + interval = sinon.stub().returns('interval-sentinel'); + interval.cancel = sinon.spy(); + dateFilter = sinon.stub().returns('dateFilter-sentinel'); + translate = sinon.stub().returns({ + then: sinon.stub().yields({ + 'multicharts.chart.X_AXIS_LABEL': 'timestamp', + 'multicharts.chart.X_AXIS_DATA_TYPE': 'timestamp' + }) + }); + + ctrl = $controller('MultichartChartController', { + multichartService: svc, + $scope: scope, + $interval: interval, + dateFilter: dateFilter, + DATE_FORMAT: { time: { medium: 'medium-time' } }, + $translate: translate + }); + ctrl.chartConfig.data = {}; + }); + }); + + it('should exist', () => { + should.exist(ctrl); + }); + + it('should stop on destroy', () => { + scope.$on.should.be.calledWith('$destroy'); + scope.$on.callCount.should.equal(1); + let fn = scope.$on.args[0][1]; + fn.should.be.a.Function(); + + interval.cancel.should.not.be.called(); + fn(); + interval.cancel.should.be.calledOnce(); + interval.cancel.should.be.calledWith('interval-sentinel'); + }); + + it('should do nothing if stopped when already stopped', () => { + interval.cancel.should.not.be.called(); + ctrl.stop(); + interval.cancel.should.be.calledOnce(); + ctrl.stop(); + interval.cancel.should.be.calledOnce(); + }); + + it('should initialize refreshRate', () => { + ctrl.refreshRate.should.equal('2000'); + }); + + it('should initialize dataAgeLimit', () => { + ctrl.dataAgeLimit.should.equal('60000'); + }); + + it('should begin updating at refreshRate period', () => { + svc.getData.should.be.calledOnce(); + interval.should.be.calledOnce(); + interval.should.be.calledWithMatch(sinon.match.func, sinon.match(2000)); + let fn = interval.args[0][0]; + fn(); + svc.getData.should.be.calledTwice(); + }); + + describe('update', () => { + let fn; + beforeEach(() => { + fn = svc.getDataPromise.args[0][0]; + fn.should.be.a.Function(); + }); + + it('should pass chart ID', () => { + ctrl.update(); + svc.getData.should.be.calledWith(scope.$parent.chart); + }); + + it('should do nothing if data is empty', () => { + ctrl.chartData.xData.length.should.equal(1); + fn({}); + ctrl.chartData.xData.length.should.equal(1); + }); + + it('should append new data', () => { + ctrl.chartData.xData.length.should.equal(1); + ctrl.should.not.have.ownProperty('yData'); + fn({ + yData: ['someMetric', 100] + }); + ctrl.chartData.xData.length.should.equal(2); + ctrl.chartData.should.have.ownProperty('yData'); + ctrl.chartData.yData.length.should.equal(2); + }); + + it('should append data for existing metric', () => { + ctrl.chartData.xData.length.should.equal(1); + ctrl.should.not.have.ownProperty('yData'); + fn({ + yData: ['someMetric', 100] + }); + ctrl.chartData.xData.length.should.equal(2); + ctrl.chartData.should.have.ownProperty('yData'); + ctrl.chartData.yData.length.should.equal(2); + fn({ + yData: ['someMetric', 200] + }); + ctrl.chartData.xData.length.should.equal(3); + ctrl.chartData.yData.length.should.equal(3); + }); + }); + + describe('trimData', () => { + let dateNowStub; + beforeEach(() => { + dateNowStub = sinon.stub(Date, 'now'); + }); + + afterEach(() => { + Date.now.restore(); + }); + + it('should do nothing if chartData undefined', () => { + delete ctrl.chartData; + ctrl.trimData(); + should(ctrl.chartData).be.undefined(); + }); + + it('should do nothing if no samples', () => { + ctrl.trimData(); + ctrl.chartData.should.eql({ + xData: ['timestamp'] + }); + }); + + it('should not trim data if only one sample', () => { + ctrl.dataAgeLimit = 10; + let updateFn = svc.getDataPromise.args[0][0]; + + dateNowStub.returns(100); + updateFn({ + yData: ['foo', 500], + yData1: ['bar', 5000] + }); + + dateNowStub.returns(200); + ctrl.trimData(); + + ctrl.chartData.should.eql({ + xData: ['timestamp', 100], + yData: ['foo', 500], + yData1: ['bar', 5000] + }); + }); + + it('should trim old data', () => { + ctrl.dataAgeLimit = 200; + let updateFn = svc.getDataPromise.args[0][0]; + + dateNowStub.returns(100); + updateFn({ + yData: ['foo', 500], + yData1: ['bar', 5000] + }); + + dateNowStub.returns(200); + updateFn({ + yData: ['foo', 600], + yData1: ['bar', 6000] + }); + + dateNowStub.returns(300); + updateFn({ + yData: ['foo', 700], + yData1: ['bar', 7000] + }); + + dateNowStub.returns(350); + + ctrl.chartData.should.eql({ + xData: ['timestamp', 100, 200, 300], + yData: ['foo', 500, 600, 700], + yData1: ['bar', 5000, 6000, 7000] + }); + + ctrl.trimData(); + + ctrl.chartData.should.eql({ + xData: ['timestamp', 200, 300], + yData: ['foo', 600, 700], + yData1: ['bar', 6000, 7000] + }); + }); + }); + + describe('initializeChartData', () => { + it('should set chartId', () => { + ctrl.chartConfig.chartId.should.equal('chart-foo-chart'); + }); + + it('should format x-axis ticks using dateFilter', () => { + dateFilter.should.not.be.called(); + let fmtFn = ctrl.chartConfig.axis.x.tick.format; + fmtFn(100).should.equal('dateFilter-sentinel'); + dateFilter.should.be.calledOnce(); + dateFilter.should.be.calledWith(100, 'medium-time'); + }); + + it('should format y-axis ticks with identity function', () => { + let fmtFn = ctrl.chartConfig.axis.y.tick.format; + fmtFn(1).should.equal(1); + fmtFn('foo').should.equal('foo'); + }); + + it('should show y2 axis if suggested by service', () => { + svc.countServicesForChart.should.not.be.called(); + let res = ctrl.chartConfig.axis.y2.show; + res.should.equal(true); + svc.countServicesForChart.should.be.calledOnce(); + svc.countServicesForChart.should.be.calledWith('foo-chart'); + }); + + it('should format tooltips', () => { + let titleFmt = ctrl.chartConfig.tooltip.format.title; + let valueFmt = ctrl.chartConfig.tooltip.format.value; + + titleFmt('foo').should.equal('foo'); + titleFmt(100).should.equal(100); + + valueFmt('foo').should.equal('foo'); + valueFmt(100).should.equal(100); + }); + }); + + describe('removeChart', () => { + it('should delegate to service', () => { + svc.removeChart.should.not.be.called(); + ctrl.removeChart(); + svc.removeChart.should.be.calledOnce(); + svc.removeChart.should.be.calledWith('foo-chart'); + }); + }); + + describe('#set refreshRate ()', () => { + it('should stop previous refreshes', () => { + interval.cancel.should.not.be.called(); + ctrl.refreshRate = 5; + interval.cancel.should.be.calledOnce(); + }); + + it('should set the new update interval', () => { + interval.should.be.calledOnce(); + ctrl.refreshRate = 5; + interval.should.be.calledTwice(); + interval.secondCall.should.be.calledWith(sinon.match.func, 5); + }); + + it('should perform updates at each interval', () => { + ctrl.refreshRate = 5; + svc.getData.should.be.calledOnce(); + let fn = interval.secondCall.args[0]; + fn(); + svc.getData.should.be.calledTwice(); + }); + + it('should cancel if given non-positive value', () => { + interval.cancel.should.not.be.called(); + svc.getData.should.be.calledOnce(); + ctrl.refreshRate = -1; + interval.cancel.should.be.calledOnce(); + svc.getData.should.be.calledOnce(); + }); + }); + + describe('rename', () => { + it('should delegate to service', () => { + svc.rename.should.not.be.called(); + ctrl.rename('newname'); + svc.rename.should.be.calledOnce(); + svc.rename.should.be.calledWith('foo-chart', 'newname'); + }); + + it('should do nothing if chart name is empty', () => { + svc.rename.should.not.be.called(); + ctrl.rename(''); + svc.rename.should.not.be.called(); + }); + + it('should do nothing if chart name is invalid', () => { + svc.rename.should.not.be.called(); + ctrl.rename('with space'); + svc.rename.should.not.be.called(); + }); + }); + +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/multichart/chart/chart.html Wed Aug 30 14:21:56 2017 -0400 @@ -0,0 +1,69 @@ +<div class="card-pf card-pf-view"> + <div class="card-pf-heading"> + <label class="card-pf-title">{{$ctrl.chart}}</label> + </div> + + <div class="row" style="margin-top:2vh"> + + <!-- Metric Controls: Refresh Rate --> + <div class="col-xs-12 col-md-3"> + <label for="refreshCombo" class="label label-info" translate>multicharts.chart.refresh.LABEL</label> + <select name="refreshCombo" class="combobox form-control" ng-model="$ctrl.refreshRate"> + <option value="-1" translate>multicharts.chart.refresh.DISABLED</option> + <option value="1000" translate="multicharts.chart.refresh.SECONDS" translate-values="{ SECONDS: 1 }" translate-interpolation="messageformat"></option> + <option value="2000" selected translate="multicharts.chart.refresh.SECONDS" translate-values="{ SECONDS: 2, DEFAULT: true }" translate-interpolation="messageformat"></option> + <option value="5000" translate="multicharts.chart.refresh.SECONDS" translate-values="{ SECONDS: 5 }" translate-interpolation="messageformat"></option> + <option value="10000" translate="multicharts.chart.refresh.SECONDS" translate-values="{ SECONDS: 10 }" translate-interpolation="messageformat"></option> + <option value="30000" translate="multicharts.chart.refresh.SECONDS" translate-values="{ SECONDS: 30 }" translate-interpolation="messageformat"></option> + <option value="60000" translate="multicharts.chart.refresh.MINUTES" translate-values="{ MINUTES: 1 }" translate-interpolation="messageformat"></option> + <option value="300000" translate="multicharts.chart.refresh.MINUTES" translate-values="{ MINUTES: 5 }" translate-interpolation="messageformat"></option> + <option value="600000" translate="multicharts.chart.refresh.MINUTES" translate-values="{ MINUTES: 10 }" translate-interpolation="messageformat"></option> + </select> + </div> + + <!-- Metric Controls: Max Data Age --> + <div class="col-xs-12 col-md-3"> + <label for="dataAgeCombo" class="label label-info" translate>multicharts.chart.refresh.LABEL</label> + <select name="dataAgeCombo" class="combobox form-control" ng-model="$ctrl.dataAgeLimit"> + <option value="10000" translate="multicharts.chart.dataAge.SECONDS" translate-values="{ SECONDS: 10 }" translate-interpolation="messageformat"></option> + <option value="30000" translate="multicharts.chart.dataAge.SECONDS" translate-values="{ SECONDS: 30 }" translate-interpolation="messageformat"></option> + <option value="60000" selected translate="multicharts.chart.dataAge.MINUTES" translate-values="{ MINUTES: 1, DEFAULT: true}" translate-interpolation="messageformat"></option> + <option value="300000" translate="multicharts.chart.dataAge.MINUTES" translate-values="{ MINUTES: 5 }" translate-interpolation="messageformat"></option> + <option value="600000" translate="multicharts.chart.dataAge.MINUTES" translate-values="{ MINUTES: 10 }" translate-interpolation="messageformat"></option> + <option value="900000" translate="multicharts.chart.dataAge.MINUTES" translate-values="{ MINUTES: 15 }" translate-interpolation="messageformat"></option> + <option value="1800000" translate="multicharts.chart.dataAge.MINUTES" translate-values="{ MINUTES: 30 }" translate-interpolation="messageformat"></option> + <option value="3600000" translate="multicharts.chart.dataAge.HOURS" translate-values="{ HOURS: 1 }" translate-interpolation="messageformat"></option> + </select> + </div> + + <div class="dropdown dropdown-kebab-pf pull-right" style="margin-right:2vw"> + <button class="btn btn-link dropdown-toggle" type="button" id="{{$ctrl.chart}}-kebab" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> + <span class="fa fa-ellipsis-v"></span> + </button> + <ul class="dropdown-menu" aria-labelledby="{{$ctrl.chart}}-kebab"> + <li> + <form name="renameForm" class="input-group" ng-submit="$ctrl.rename(chartRename)"> + <input type="text" class="form-control" translate-attr="{ placeholder: 'multicharts.chart.rename.PLACEHOLDER' }" ng-model="chartRename"/> + <input type="submit" translate-attr="{ value: 'multicharts.chart.rename.SUBMIT_LABEL' }"/> + </form> + </li> + <li role="separator" class="divider"></li> + <li> + <a ng-click="$ctrl.removeChart(chart)" translate>multicharts.chart.DELETE_LABEL</a> + </li> + </ul> + </div> + + </div> + + <div class="card-pf-body" ng-if="$ctrl.chartConfig"> + <pf-line-chart id="chart-{{$ctrl.chart}}" config="$ctrl.chartConfig" + chart-data="$ctrl.chartData" + show-x-axis="true" + show-y-axis="true" + ></pf-line-chart> + + + </div> + +</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/multichart/chart/en.locale.yaml Wed Aug 30 14:21:56 2017 -0400 @@ -0,0 +1,23 @@ +multicharts: + + chart: + + X_AXIS_LABEL: timestamp + X_AXIS_DATA_TYPE: timestamp + DELETE_LABEL: Delete + + refresh: + LABEL: Refresh Rate + DISABLED: Disabled + SECONDS: '{SECONDS, plural, =0{0 Seconds} one{1 Second} other{# Seconds}}{DEFAULT, select, true{ (Default)} other{}}' + MINUTES: '{MINUTES, plural, =0{0 Minutes} one{1 Minute} other{# Minutes}}{DEFAULT, select, true{ (Default)} other{}}' + + dataAge: + LABEL: Max Data Age + SECONDS: '{SECONDS, plural, =0{0 Seconds} one{1 Second} other{# Seconds}}{DEFAULT, select, true{ (Default)} other{}}' + MINUTES: '{MINUTES, plural, =0{0 Minutes} one{1 Minute} other{# Minutes}}{DEFAULT, select, true{ (Default)} other{}}' + HOURS: '{HOURS, plural, =0{0 Hours} one{1 Hour} other{# Hours}}{DEFAULT, select, true{ (Default)} other{}}' + + rename: + PLACEHOLDER: '@:multicharts.form.PLACEHOLDER' + SUBMIT_LABEL: Rename Chart
--- a/src/app/components/multichart/en.locale.yaml Thu Aug 31 08:40:56 2017 -0400 +++ b/src/app/components/multichart/en.locale.yaml Wed Aug 30 14:21:56 2017 -0400 @@ -8,24 +8,3 @@ NAME: Name PLACEHOLDER: Enter new chart name SUBMIT_LABEL: Create Chart - - refresh: - DISABLED: Disabled - SECONDS: '{SECONDS, plural, =0{0 Seconds} one{1 Second} other{# Seconds}}{DEFAULT, select, true{ (Default)} other{}}' - MINUTES: '{MINUTES, plural, =0{0 Minutes} one{1 Minute} other{# Minutes}}{DEFAULT, select, true{ (Default)} other{}}' - - dataAge: - SECONDS: '{SECONDS, plural, =0{0 Seconds} one{1 Second} other{# Seconds}}{DEFAULT, select, true{ (Default)} other{}}' - MINUTES: '{MINUTES, plural, =0{0 Minutes} one{1 Minute} other{# Minutes}}{DEFAULT, select, true{ (Default)} other{}}' - HOURS: '{HOURS, plural, =0{0 Hours} one{1 Hour} other{# Hours}}{DEFAULT, select, true{ (Default)} other{}}' - - chart: - X_AXIS_LABEL: timestamp - X_AXIS_DATA_TYPE: timestamp - REFRESH_RATE_LABEL: Refresh Rate - MAX_DATA_AGE_LABEL: Max Data Age - DELETE_LABEL: Delete - - rename: - PLACEHOLDER: '@:multicharts.form.PLACEHOLDER' - SUBMIT_LABEL: Rename Chart
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/multichart/multichart.component.js Wed Aug 30 14:21:56 2017 -0400 @@ -0,0 +1,40 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +import controller from './multichart.controller.js'; +import chartComponent from './chart/chart.component.js'; + +export default angular + .module('multichartComponent', [ + controller, + chartComponent + ]) + .component('multichart', { + controller: 'MultichartController', + template: require('./multichart.html') + }) + .name;
--- a/src/app/components/multichart/multichart.controller.js Thu Aug 31 08:40:56 2017 -0400 +++ b/src/app/components/multichart/multichart.controller.js Wed Aug 30 14:21:56 2017 -0400 @@ -34,8 +34,8 @@ this.svc = multichartService; this.showErr = false; - $translate('multicharts.ERR_TITLE').then(s => this.scope.errTitle = s); - $translate('multicharts.ERR_MESSAGE').then(s => this.scope.errMessage = s); + $translate('multicharts.ERR_TITLE').then(s => this.errTitle = s); + $translate('multicharts.ERR_MESSAGE').then(s => this.errMessage = s); } createChart (chartName) { @@ -49,7 +49,7 @@ } this.showErr = false; this.svc.addChart(chartName); - this.scope.newChartName = ''; + this.newChartName = ''; let form = this.scope.newChartForm; form.$setPristine(); form.$setUntouched();
--- a/src/app/components/multichart/multichart.controller.spec.js Thu Aug 31 08:40:56 2017 -0400 +++ b/src/app/components/multichart/multichart.controller.spec.js Wed Aug 30 14:21:56 2017 -0400 @@ -79,7 +79,7 @@ ctrl.createChart('foo'); svc.addChart.should.be.calledOnce(); svc.addChart.should.be.calledWith('foo'); - scope.newChartName.should.equal(''); + ctrl.newChartName.should.equal(''); scope.newChartForm.$setUntouched.should.be.calledOnce(); scope.newChartForm.$setPristine.should.be.calledOnce(); });
--- a/src/app/components/multichart/multichart.html Thu Aug 31 08:40:56 2017 -0400 +++ b/src/app/components/multichart/multichart.html Wed Aug 30 14:21:56 2017 -0400 @@ -4,97 +4,29 @@ <li><a ui-serf="multichart" translate>multicharts.BREADCRUMB</a></li> </ol> - <customizable-error-message ng-show="ctrl.showErr" dismissible="true" err-title="errTitle" - err-message="errMessage"/> - - <div class="row"> - <div class="col-xs-12 col-md-4"> - <form name="newChartForm" class="input-group" ng-submit="ctrl.createChart(newChartName)"> - <span class="input-group-addon" translate>multicharts.form.NAME</span> - <input type="text" class="form-control" translate-attr="{ placeholder: 'multicharts.form.PLACEHOLDER' }" ng-model="newChartName"/> - <input type="submit" translate-attr="{ value: 'multicharts.form.SUBMIT_LABEL' }"/> - </form> - </div> - </div> - - <div class="row row-cards-pf"> - <div class="container-fluid container-cards-pf"> - - <div ng-repeat="chart in ctrl.chartNames"> - <div class="col-xs-12 col-md-10"> - <div ng-controller="MultiChartChartController as chartCtrl" class="card-pf card-pf-view"> - <div class="card-pf-heading"> - <label class="card-pf-title">{{chart}}</label> - </div> - - <div class="row" style="margin-top:2vh"> - - <!-- Metric Controls: Refresh Rate --> - <div class="col-xs-12 col-md-3"> - <label for="refreshCombo" class="label label-info" translate>multicharts.chart.REFRESH_RATE_LABEL</label> - <select name="refreshCombo" class="combobox form-control" ng-model="refreshRate"> - <option value="-1" translate>multicharts.refresh.DISABLED</option> - <option value="1000" translate="multicharts.refresh.SECONDS" translate-values="{ SECONDS: 1 }" translate-interpolation="messageformat"></option> - <option value="2000" selected translate="multicharts.refresh.SECONDS" translate-values="{ SECONDS: 2, DEFAULT: true }" translate-interpolation="messageformat"></option> - <option value="5000" translate="multicharts.refresh.SECONDS" translate-values="{ SECONDS: 5 }" translate-interpolation="messageformat"></option> - <option value="10000" translate="multicharts.refresh.SECONDS" translate-values="{ SECONDS: 10 }" translate-interpolation="messageformat"></option> - <option value="30000" translate="multicharts.refresh.SECONDS" translate-values="{ SECONDS: 30 }" translate-interpolation="messageformat"></option> - <option value="60000" translate="multicharts.refresh.MINUTES" translate-values="{ MINUTES: 1 }" translate-interpolation="messageformat"></option> - <option value="300000" translate="multicharts.refresh.MINUTES" translate-values="{ MINUTES: 5 }" translate-interpolation="messageformat"></option> - <option value="600000" translate="multicharts.refresh.MINUTES" translate-values="{ MINUTES: 10 }" translate-interpolation="messageformat"></option> - </select> - </div> + <customizable-error-message ng-show="$ctrl.showErr" dismissible="true" err-title="$ctrl.errTitle" + err-message="$ctrl.errMessage"/> - <!-- Metric Controls: Max Data Age --> - <div class="col-xs-12 col-md-3"> - <label for="dataAgeCombo" class="label label-info" translate>multicharts.chart.MAX_DATA_AGE_LABEL</label> - <select name="dataAgeCombo" class="combobox form-control" ng-model="dataAgeLimit"> - <option value="10000" translate="multicharts.dataAge.SECONDS" translate-values="{ SECONDS: 10 }" translate-interpolation="messageformat"></option> - <option value="30000" translate="multicharts.dataAge.SECONDS" translate-values="{ SECONDS: 30 }" translate-interpolation="messageformat"></option> - <option value="60000" selected translate="multicharts.dataAge.MINUTES" translate-values="{ MINUTES: 1, DEFAULT: true}" translate-interpolation="messageformat"></option> - <option value="300000" translate="multicharts.dataAge.MINUTES" translate-values="{ MINUTES: 5 }" translate-interpolation="messageformat"></option> - <option value="600000" translate="multicharts.dataAge.MINUTES" translate-values="{ MINUTES: 10 }" translate-interpolation="messageformat"></option> - <option value="900000" translate="multicharts.dataAge.MINUTES" translate-values="{ MINUTES: 15 }" translate-interpolation="messageformat"></option> - <option value="1800000" translate="multicharts.dataAge.MINUTES" translate-values="{ MINUTES: 30 }" translate-interpolation="messageformat"></option> - <option value="3600000" translate="multicharts.dataAge.HOURS" translate-values="{ HOURS: 1 }" translate-interpolation="messageformat"></option> - </select> - </div> + <div class="row"> + <div class="col-xs-12 col-md-4"> + <form name="newChartForm" class="input-group" ng-submit="$ctrl.createChart(newChartName)"> + <span class="input-group-addon" translate>multicharts.form.NAME</span> + <input type="text" class="form-control" translate-attr="{ placeholder: 'multicharts.form.PLACEHOLDER' }" ng-model="newChartName"/> + <input type="submit" translate-attr="{ value: 'multicharts.form.SUBMIT_LABEL' }"/> + </form> + </div> + </div> - <div class="dropdown dropdown-kebab-pf pull-right" style="margin-right:2vw"> - <button class="btn btn-link dropdown-toggle" type="button" id="{{chart}}-kebab" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> - <span class="fa fa-ellipsis-v"></span> - </button> - <ul class="dropdown-menu" aria-labelledby="{{chart}}-kebab"> - <li> - <form name="renameForm" class="input-group" ng-submit="chartCtrl.rename(chartRename)"> - <input type="text" class="form-control" translate-attr="{ placeholder: 'multicharts.chart.rename.PLACEHOLDER' }" ng-model="chartRename"/> - <input type="submit" translate-attr="{ value: 'multicharts.chart.rename.SUBMIT_LABEL' }"/> - </form> - </li> - <li role="separator" class="divider"></li> - <li> - <a ng-click="chartCtrl.removeChart(chart)" translate>multicharts.chart.DELETE_LABEL</a> - </li> - </ul> - </div> + <div class="row row-cards-pf"> + <div class="container-fluid container-cards-pf"> - </div> - - <div class="card-pf-body" ng-if="chartCtrl.chartConfig"> - <pf-line-chart id="chart-{{chart}}" config="chartCtrl.chartConfig" - chart-data="chartCtrl.chartData" - show-x-axis="true" - show-y-axis="true" - ></pf-line-chart> - - - </div> - + <div ng-repeat="chart in $ctrl.chartNames"> + <div class="col-xs-12 col-md-10"> + <multichart-chart></multichart-chart> </div> </div> + </div> - </div> - </div> </div>
--- a/src/app/components/multichart/multichart.module.js Thu Aug 31 08:40:56 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -/** - * Copyright 2012-2017 Red Hat, Inc. - * - * Thermostat is distributed under the GNU General Public License, - * version 2 or any later version (with a special exception described - * below, commonly known as the "Classpath Exception"). - * - * A copy of GNU General Public License (GPL) is included in this - * distribution, in the file COPYING. - * - * Linking Thermostat code with other modules is making a combined work - * based on Thermostat. Thus, the terms and conditions of the GPL - * cover the whole combination. - * - * As a special exception, the copyright holders of Thermostat give you - * permission to link this code with independent modules to produce an - * executable, regardless of the license terms of these independent - * modules, and to copy and distribute the resulting executable under - * terms of your choice, provided that you also meet, for each linked - * independent module, the terms and conditions of the license of that - * module. An independent module is a module which is not derived from - * or based on Thermostat code. If you modify Thermostat, you may - * extend this exception to your version of the software, but you are - * not obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - */ - -import controller from './multichart.controller.js'; -import chartController from './chart.controller.js'; - -export default angular - .module('multichartModule', [ - controller, - chartController - ]) - .name;
--- a/src/app/components/multichart/multichart.routing.js Thu Aug 31 08:40:56 2017 -0400 +++ b/src/app/components/multichart/multichart.routing.js Wed Aug 30 14:21:56 2017 -0400 @@ -30,20 +30,13 @@ $stateProvider.state('multichart', { url: '/multichart', - templateProvider: $q => { - 'ngInject'; - return $q(resolve => - require.ensure([], () => resolve(require('./multichart.html')) - ) - ); - }, - controller: 'MultichartController as ctrl', + component: 'multichart', resolve: { - loadMultichart: ($q, $ocLazyLoad) => { + lazyLoad: ($q, $ocLazyLoad) => { 'ngInject'; return $q(resolve => { - require.ensure(['./multichart.module.js'], () => { - let module = require('./multichart.module.js'); + require.ensure(['./multichart.component.js'], () => { + let module = require('./multichart.component.js'); $ocLazyLoad.load({ name: module.default }); resolve(module); });
--- a/src/app/components/multichart/multichart.routing.spec.js Thu Aug 31 08:40:56 2017 -0400 +++ b/src/app/components/multichart/multichart.routing.spec.js Wed Aug 30 14:21:56 2017 -0400 @@ -55,24 +55,8 @@ args[1].url.should.equal('/multichart'); }); - it('template provider should return multichart.html', done => { - let providerFn = args[1].templateProvider[1]; - providerFn.should.be.a.Function(); - providerFn(q); - q.should.be.calledOnce(); - - let deferred = q.args[0][0]; - deferred.should.be.a.Function(); - - let resolve = sinon.stub().callsFake(val => { - val.should.equal(require('./multichart.html')); - done(); - }); - deferred(resolve); - }); - - it('resolve should load multichart module', done => { - let resolveFn = args[1].resolve.loadMultichart[2]; + it('resolve should load multichart component', done => { + let resolveFn = args[1].resolve.lazyLoad[2]; resolveFn.should.be.a.Function(); resolveFn(q, ocLazyLoad); q.should.be.calledOnce(); @@ -81,8 +65,8 @@ deferred.should.be.a.Function(); let resolve = sinon.stub().callsFake(val => { - ocLazyLoad.load.should.be.calledWith({ name: require('./multichart.module.js').default}); - val.should.equal(require('./multichart.module.js')); + ocLazyLoad.load.should.be.calledWith({ name: require('./multichart.component.js').default}); + val.should.equal(require('./multichart.component.js')); done(); }); deferred(resolve);