diff --git a/.travis.yml b/.travis.yml index ed0474ae..7989fd61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,9 @@ language: node_js node_js: - "5.6.0" before_install: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" + - export CHROME_BIN=/usr/bin/google-chrome + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start before_script: # - npm install -g gulp - node_modules/.bin/bower install -f @@ -11,4 +12,8 @@ script: - node_modules/.bin/gulp build:dist - node_modules/.bin/gulp test addons: - chrome: stable + apt: + sources: + - google-chrome + packages: + - google-chrome-stable diff --git a/bower.json b/bower.json index 374dc6cf..fd79c603 100755 --- a/bower.json +++ b/bower.json @@ -44,17 +44,10 @@ "angular-translate": "~2.14.0", "angular-translate-interpolation-messageformat": "~2.14.0", "angular-sanitize": "~1.6.0", - "highcharts": "^5.0.11", "color": "^3.0.4" }, - "overrides": { - "highcharts": { - "main": "highstock.js" - } - }, "devDependencies": { "angular-mocks": "~1.6.0", - "angular-scenario": "~1.6.0", "angular-cookies": "~1.6.0", "AngularDevise": "angular-devise#~1.3.0", "angular-ui-router": "~0.3.1", diff --git a/karma-min.conf.js b/karma-min.conf.js index 301ba1e0..b5b8bf69 100644 --- a/karma-min.conf.js +++ b/karma-min.conf.js @@ -2,7 +2,7 @@ // Generated on Thu Aug 21 2014 10:24:39 GMT+0200 (CEST) module.exports = function(config) { - config.set({ + var configuration = { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', @@ -46,7 +46,6 @@ module.exports = function(config) { 'bower_components/highcharts/highstock.js', 'bower_components/color/one-color-all.js', 'bower_components/angular-mocks/angular-mocks.js', - 'bower_components/angular-scenario/angular-scenario.js', 'bower_components/angular-cookies/angular-cookies.js', 'bower_components/AngularDevise/lib/devise.js', 'bower_components/angular-ui-router/release/angular-ui-router.js', @@ -99,9 +98,21 @@ module.exports = function(config) { // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], + customLaunchers: { + Chrome_travis_ci: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false - }); + }; + + if (process.env.TRAVIS) { + configuration.browsers = ['Chrome_travis_ci']; + } + + config.set(configuration); }; diff --git a/karma.conf.js b/karma.conf.js index 6308fed1..7756e81f 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -2,7 +2,7 @@ // Generated on Thu Aug 21 2014 10:24:39 GMT+0200 (CEST) module.exports = function(config) { - config.set({ + var configuration = { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', @@ -46,7 +46,6 @@ module.exports = function(config) { 'bower_components/highcharts/highstock.js', 'bower_components/color/one-color-all.js', 'bower_components/angular-mocks/angular-mocks.js', - 'bower_components/angular-scenario/angular-scenario.js', 'bower_components/angular-cookies/angular-cookies.js', 'bower_components/AngularDevise/lib/devise.js', 'bower_components/angular-ui-router/release/angular-ui-router.js', @@ -99,9 +98,21 @@ module.exports = function(config) { // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], + customLaunchers: { + Chrome_travis_ci: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false - }); + }; + + if (process.env.TRAVIS) { + configuration.browsers = ['Chrome_travis_ci']; + } + + config.set(configuration); }; diff --git a/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.directive.coffee b/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.directive.coffee new file mode 100644 index 00000000..a2619958 --- /dev/null +++ b/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.directive.coffee @@ -0,0 +1,88 @@ +# +# Component generated by Impac! Widget Generator! +# +module = angular.module('impac.components.widgets.sales-average-purchase-size', []) +module.controller('WidgetSalesAveragePurchaseSizeCtrl', ($scope, $q, $filter, ImpacWidgetsSvc, ImpacAssets, HighchartsFactory, ImpacUtilities) -> + + w = $scope.widget + $scope.isChartDisplayed = true + + # Define settings + # -------------------------------------- + $scope.orgDeferred = $q.defer(); + settingsPromises = [$scope.orgDeferred.promise] + + # Time management + todayUTC = moment().startOf('day').add(moment().utcOffset(), 'minutes') + + # Timestamps stored in the back-end are in UTC => the filter on the date must be UTC too + dateFilter = (timestamp) -> + pickedDate = moment.utc(timestamp) + if pickedDate <= todayUTC then "lte #{pickedDate.format('YYYY-MM-DD')}" else pickedDate.format('YYYY-MM-DD') + + # Unique identifier for the chart object in the DOM + $scope.chartId = -> + "averagePurchaseSizeChart-#{w.id}" + + # # == Sub-Components - Needed? ============================================================= + $scope.chartDeferred = $q.defer() + + # == Chart Events Callbacks ===================================================================== + # Sets the transactions list resources type and displays it + onClickBar = (event) -> + $scope.isChartDisplayed = false + + # == Directive Events Callbacks ===================================================================== + $scope.onButtonBack = () -> + $scope.isChartDisplayed = true + + # Widget specific methods + # -------------------------------------- + w.initContext = -> + $scope.isDataFound = w.content? + + w.format = -> + # Instantiate and render chart + options = + chartType: 'line' + chartOnClickCallbacks: [] + currency: w.metadata.currency + showToday: true + showLegend: true + + $scope.chart = new HighchartsFactory($scope.chartId(), w.content.chart, options) + + $scope.chart.formatters = -> + currency = @options.currency + xAxisLabels = + labels: + formatter: -> + if (this.chart.rangeSelector.options.selected >= 3) + moment.utc(this.value).format('MMM YYYY') + else + moment.utc(this.value).format('DD MMM') + yAxisLabels = + labels: + formatter: -> + $filter('mnoCurrency')(this.value, currency, false) + xAxis: angular.merge([w.content.chart.xAxis[0]], [xAxisLabels]) + yAxis: angular.merge([w.content.chart.yAxis[0]], [yAxisLabels]) + rangeSelector: + selected: 5 + + $scope.chart.render(w.content.chart, options) + + # Add events callbacks to chart object + $scope.chart.addSeriesEvent('click', onClickBar) + + # Notifies parent element that the chart is ready to be displayed + $scope.chartDeferred.notify($scope.chart) + + $scope.widgetDeferred.resolve(settingsPromises) +) +module.directive('widgetSalesAveragePurchaseSize', -> + return { + restrict: 'A', + controller: 'WidgetSalesAveragePurchaseSizeCtrl' + } +) diff --git a/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.less b/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.less new file mode 100644 index 00000000..1ed94963 --- /dev/null +++ b/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.less @@ -0,0 +1,20 @@ +.analytics .widget-item .content.sales-average-purchase-size { + .tall-widget(); + + .average-purchase-size-chart { + height: ~"calc(@{impac-big-widget-size} - 50px)"; + } + + .average-purchase-size-insight { + height: ~"calc(@{impac-big-widget-size} - 100px)"; + padding: 150px; + } + + .highcharts-y-primary { + fill: 'rgb(68, 208, 218)' + } + + .highcharts-y-secondary { + fill: 'rgb(208, 68, 218)' + } +} diff --git a/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.tmpl.html b/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.tmpl.html new file mode 100644 index 00000000..397fc24b --- /dev/null +++ b/src/components/widgets/sales-average-purchase-size/sales-average-purchase-size.tmpl.html @@ -0,0 +1,59 @@ +
+ +
+

Widget settings

+ +
+ + +
+ + +
+
+ + + +
+ + + +
+ +
+ +
+ + + +
+ + +
+

Partner report:

+ +

The purchase size grew by 0.98% compared to last month. (Not real data)

+

In your Industry, in April your customer spend an average + 20.5%

+ +

The report found top store strategies on the horizon include:

+

----------------------------------------------------------------

+

40% OFFER SPACE FOR IN-STORE DEMOS AND EVENTS

+

39% INCREASE STAFFING

+

35% OFFER CLICK-AND-COLLECT SERVICES

+

35% INTRODUCE AUGMENTED REALITY AND/OR VIRTUAL REALITY TECHNOLOGY

+ +

+

+ +
+ + +
+
+
diff --git a/src/components/widgets/sales-average-visit/sales-average-visit.directive.coffee b/src/components/widgets/sales-average-visit/sales-average-visit.directive.coffee new file mode 100644 index 00000000..9e2ad993 --- /dev/null +++ b/src/components/widgets/sales-average-visit/sales-average-visit.directive.coffee @@ -0,0 +1,80 @@ +# +# Component generated by Impac! Widget Generator! +# +module = angular.module('impac.components.widgets.sales-average-visit', []) +module.controller('WidgetSalesAverageVisitCtrl', ($scope, $q, $filter, ImpacWidgetsSvc, ImpacAssets, HighchartsFactory, BoltResources) -> + + w = $scope.widget + + # Define settings + # -------------------------------------- + $scope.orgDeferred = $q.defer(); + settingsPromises = [$scope.orgDeferred.promise] + + # Time management + todayUTC = moment().startOf('day').add(moment().utcOffset(), 'minutes') + + # Timestamps stored in the back-end are in UTC => the filter on the date must be UTC too + dateFilter = (timestamp) -> + pickedDate = moment.utc(timestamp) + if pickedDate <= todayUTC then "lte #{pickedDate.format('YYYY-MM-DD')}" else pickedDate.format('YYYY-MM-DD') + + # Unique identifier for the chart object in the DOM + $scope.chartId = -> + "averageVisitChart-#{w.id}" + + $scope.chartDeferred = $q.defer() + # Widget specific methods + # -------------------------------------- + w.initContext = -> + $scope.isDataFound = w.content? + + w.format = -> + + # Instantiate and render chart + options = + chartType: 'line' + chartOnClickCallbacks: [] + currency: w.metadata.currency + showToday: true + showLegend: true + zoomType: false + + $scope.chart = new HighchartsFactory($scope.chartId(), w.content.chart, options) + + $scope.chart.formatters = -> + currency = @options.currency + xAxisLabels = + labels: + formatter: -> + if (this.chart.rangeSelector.options.selected >= 3) + moment.utc(this.value).format('MMM YYYY') + else + moment.utc(this.value).format('DD MMM') + yAxisLabels = + labels: + formatter: -> + $filter('mnoCurrency')(this.value, currency, false) + leftYAxis = w.content.chart.yAxis[0] + rightYAxis = angular.merge(w.content.chart.yAxis[1], yAxisLabels) + + xAxis: angular.merge([w.content.chart.xAxis[0]], [xAxisLabels]) + yAxis: [leftYAxis, rightYAxis] + rangeSelector: + selected: 5 + + $scope.chart.render(w.content.chart, options) + + # Notifies parent element that the chart is ready to be displayed + $scope.chartDeferred.notify($scope.chart) + + # Widget is ready: can trigger the "wait for settings to be ready" + # -------------------------------------- + $scope.widgetDeferred.resolve(settingsPromises) +) +module.directive('widgetSalesAverageVisit', -> + return { + restrict: 'A', + controller: 'WidgetSalesAverageVisitCtrl' + } +) diff --git a/src/components/widgets/sales-average-visit/sales-average-visit.less b/src/components/widgets/sales-average-visit/sales-average-visit.less new file mode 100644 index 00000000..45f7e063 --- /dev/null +++ b/src/components/widgets/sales-average-visit/sales-average-visit.less @@ -0,0 +1,15 @@ +.analytics .widget-item .content.sales-average-visit { + .tall-widget(); + + .average-visit-chart { + height: ~"calc(@{impac-big-widget-size} - 50px)"; + } + + .highcharts-y-primary { + fill: 'rgb(68, 208, 218)' + } + + .highcharts-y-secondary { + fill: 'rgb(208, 68, 218)' + } +} diff --git a/src/components/widgets/sales-average-visit/sales-average-visit.tmpl.html b/src/components/widgets/sales-average-visit/sales-average-visit.tmpl.html new file mode 100644 index 00000000..7dc648ca --- /dev/null +++ b/src/components/widgets/sales-average-visit/sales-average-visit.tmpl.html @@ -0,0 +1,31 @@ + +
+ +
+

Widget settings

+ +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
diff --git a/src/components/widgets/sales-customers-engagement/sales-customers-engagement.directive.coffee b/src/components/widgets/sales-customers-engagement/sales-customers-engagement.directive.coffee new file mode 100644 index 00000000..2ceb0002 --- /dev/null +++ b/src/components/widgets/sales-customers-engagement/sales-customers-engagement.directive.coffee @@ -0,0 +1,80 @@ +module = angular.module('impac.components.widgets.sales-customers-engagement', []) +module.controller('WidgetSalesCustomersEngagementCtrl', ($scope, $q, $filter, ImpacWidgetsSvc, ImpacAssets, HighchartsFactory, ImpacUtilities) -> + + w = $scope.widget + $scope.isChartDisplayed = true + + # Define settings + # -------------------------------------- + $scope.orgDeferred = $q.defer(); + settingsPromises = [$scope.orgDeferred.promise] + + # Time management + todayUTC = moment().startOf('day').add(moment().utcOffset(), 'minutes') + + # Timestamps stored in the back-end are in UTC => the filter on the date must be UTC too + dateFilter = (timestamp) -> + pickedDate = moment.utc(timestamp) + if pickedDate <= todayUTC then "lte #{pickedDate.format('YYYY-MM-DD')}" else pickedDate.format('YYYY-MM-DD') + + # Unique identifier for the chart object in the DOM + $scope.chartId = -> + "averagePurchaseSizeChart-#{w.id}" + + # # == Sub-Components - Needed? ============================================================= + $scope.chartDeferred = $q.defer() + + # Widget specific methods + # -------------------------------------- + w.initContext = -> + $scope.isDataFound = w.content? + + w.format = -> + # Instantiate and render chart + options = + chartType: 'line' + chartOnClickCallbacks: [] + currency: w.metadata.currency + showToday: true + showLegend: true + + $scope.chart = new HighchartsFactory($scope.chartId(), w.content.chart, options) + + $scope.chart.formatters = -> + currency = @options.currency + xAxisLabels = + labels: + formatter: -> + if (this.chart.rangeSelector.options.selected >= 3) + moment.utc(this.value).format('MMM YYYY') + else + moment.utc(this.value).format('DD MMM') + yAxisLabels = + labels: + formatter: -> + "#{this.value} %" + xAxis: angular.merge([w.content.chart.xAxis[0]], [xAxisLabels]) + yAxis: angular.merge([w.content.chart.yAxis[0]], [yAxisLabels]) + rangeSelector: + selected: 3 + tooltip: + shared: false + backgroundColor: '#FBF7E6' + formatter: -> + date = moment.utc(this.x).format('Do MMM YYYY') + name = this.series.name + "#{date}
#{name}: #{this.y}%" + + $scope.chart.render(w.content.chart, options) + + # Notifies parent element that the chart is ready to be displayed + $scope.chartDeferred.notify($scope.chart) + + $scope.widgetDeferred.resolve(settingsPromises) +) +module.directive('widgetSalesCustomersEngagement', -> + return { + restrict: 'A', + controller: 'WidgetSalesCustomersEngagementCtrl' + } +) diff --git a/src/components/widgets/sales-customers-engagement/sales-customers-engagement.less b/src/components/widgets/sales-customers-engagement/sales-customers-engagement.less new file mode 100644 index 00000000..42392ff8 --- /dev/null +++ b/src/components/widgets/sales-customers-engagement/sales-customers-engagement.less @@ -0,0 +1,8 @@ +.analytics .widget-item .content.sales-customers-engagement { + .tall-widget(); + + .customers-engagement-chart { + height: ~"calc(@{impac-big-widget-size} - 50px)"; + } + +} diff --git a/src/components/widgets/sales-customers-engagement/sales-customers-engagement.tmpl.html b/src/components/widgets/sales-customers-engagement/sales-customers-engagement.tmpl.html new file mode 100644 index 00000000..d47059f2 --- /dev/null +++ b/src/components/widgets/sales-customers-engagement/sales-customers-engagement.tmpl.html @@ -0,0 +1,30 @@ + +
+ +
+

Widget settings

+ +
+ + +
+ + +
+
+ + +
+ +
+ +
+ +
+ + +
+
+
diff --git a/src/impac-angular.module.js b/src/impac-angular.module.js index 6347dbe6..596a8731 100644 --- a/src/impac-angular.module.js +++ b/src/impac-angular.module.js @@ -92,7 +92,10 @@ angular.module('impac.components.widgets', 'impac.components.widgets.sales-summary', 'impac.components.widgets.sales-top-opportunities', 'impac.components.widgets.sales-top-customers', - 'impac.components.widgets.sales-new-vs-existing-customers' + 'impac.components.widgets.sales-new-vs-existing-customers', + 'impac.components.widgets.sales-average-purchase-size', + 'impac.components.widgets.sales-average-visit', + 'impac.components.widgets.sales-customers-engagement' ] ); angular.module('impac.components.widgets-settings', @@ -155,6 +158,7 @@ angular.module('impac.services', 'impac.services.assets', 'impac.services.chart-formatter', 'impac.services.highcharts-factory', + 'impac.services.highcharts-theme', 'impac.services.message-bus', 'impac.services.utilities', 'impac.services.main', diff --git a/src/services/dashboards/dashboards.spec.js b/src/services/dashboards/dashboards.spec.js index 8927c9be..9d42c10d 100644 --- a/src/services/dashboards/dashboards.spec.js +++ b/src/services/dashboards/dashboards.spec.js @@ -153,21 +153,20 @@ describe('<> ImpacDashboardsSvc', function () { currentDashboard: {} }; - spyOn(svc, 'setWidgetsTemplates').and.returnValue(true); spyOn(svc, 'initializeActiveTabs').and.returnValue(true); spyOn(svc.callbacks.dashboardChanged, 'notify').and.callThrough(); }); // Helpers --- - function sharedBehaviorForSetDependingAttributes() { - it('stores the widgets templates list in the service', function() { - expect(svc.setWidgetsTemplates).toHaveBeenCalled(); - }); + // function sharedBehaviorForSetDependingAttributes() { + // it('stores the widgets templates list in the service', function() { + // expect(svc.setWidgetsTemplates).toHaveBeenCalled(); + // }); - it('initializes the tabs status', function() { - expect(svc.initializeActiveTabs).toHaveBeenCalled(); - }); - }; + // it('initializes the tabs status', function() { + // expect(svc.initializeActiveTabs).toHaveBeenCalled(); + // }); + // }; function sharedBehaviorForSetDefaultCurrentDashboard(id) { describe('.setDefaultCurrentDashboard', function() { @@ -180,8 +179,8 @@ describe('<> ImpacDashboardsSvc', function () { expect(svc.config.currentDashboard).toEqual({id: 1, name: 'dash1'}); }); - it('sets the depending attributes', function() { - sharedBehaviorForSetDependingAttributes(); + it('initializes the tabs status', function() { + expect(svc.initializeActiveTabs).toHaveBeenCalled(); }); it('notifies the dashboardChanged callback', function() { @@ -217,8 +216,8 @@ describe('<> ImpacDashboardsSvc', function () { expect(svc.config.currentDashboard).toEqual({id: 2, name: 'dash2'}); }); - it('sets the depending attributes', function() { - sharedBehaviorForSetDependingAttributes(); + it('initializes the tabs status', function() { + expect(svc.initializeActiveTabs).toHaveBeenCalled(); }); it('notifies the dashboardChanged callback', function() { @@ -292,7 +291,7 @@ describe('<> ImpacDashboardsSvc', function () { svc.setWidgetsTemplates(array); expect(svc.config.widgetsTemplates).toEqual([{ endpoint: 'accounts/balance' }]); }); - + describe('when :array is not defined', function() { sharedBehaviorForKeepOriginal(); }); diff --git a/src/services/highcarts-theme/highcharts-theme.svc.coffee b/src/services/highcarts-theme/highcharts-theme.svc.coffee new file mode 100644 index 00000000..7ec5a750 --- /dev/null +++ b/src/services/highcarts-theme/highcharts-theme.svc.coffee @@ -0,0 +1,32 @@ +angular +.module('impac.services.highcharts-theme', []) +.service('HighchartsThemeService', (ImpacTheming) -> + + Highcharts.theme = + colors: ImpacTheming.get().chartColors.array + chart: + backgroundColor: null + title: style: + fontSize: '16px' + fontWeight: 'bold' + textTransform: 'uppercase' + tooltip: + borderWidth: 0 + backgroundColor: 'rgba(219,219,216,0.8)' + shadow: true + legend: itemStyle: + fontWeight: 'bold' + fontSize: '13px' + xAxis: + labels: style: fontSize: '10px' + yAxis: + minorTickInterval: 'auto' + title: style: textTransform: 'uppercase' + labels: style: fontSize: '12px' + plotOptions: candlestick: lineColor: '#404048' + background2: '#F0F0EA' + + # Apply the theme + Highcharts.setOptions Highcharts.theme + +) diff --git a/src/services/highcharts-factory/highcharts-factory.svc.coffee b/src/services/highcharts-factory/highcharts-factory.svc.coffee index 30387801..cd872742 100644 --- a/src/services/highcharts-factory/highcharts-factory.svc.coffee +++ b/src/services/highcharts-factory/highcharts-factory.svc.coffee @@ -4,7 +4,7 @@ angular templates = line: Object.freeze - get: (series = [], options = {})-> + get: (series = [], options = {}, xAxis = [], yAxis = [], plotOptions = {})-> zoomingOptions = _.get(options, 'withZooming') xAxisOptions = if zoomingOptions? { @@ -16,8 +16,9 @@ angular chart: type: 'line' - zoomType: 'x' + zoomType: _.get(options, 'zoomType', 'x') spacingTop: 20 + spacingRight: 60 events: click: (event)-> _.each(_.get(options, 'chartOnClickCallbacks', []), (cb)-> cb(event)) title: null @@ -25,15 +26,19 @@ angular enabled: false legend: enabled: _.get(options, 'showLegend', true) + itemMarginTop: _.get(options,'itemMarginTop', 5) + itemMarginBottom: _.get(options,'itemMarginBottom', 5) layout: 'vertical' align: 'left' verticalAlign: 'middle' - xAxis: xAxisOptions - yAxis: - title: null - startOnTick: true - minPadding: 0 + xAxis: xAxis + yAxis: yAxis series: series + navigator: + series: + dashStyle: _.get(options, 'dashStyle','Dash') + type: _.get(options, 'navigatorType', 'line') + plotOptions: plotOptions rangeSelector: buttons: [ { type: 'month', count: 4, text: 'def.' }, @@ -45,6 +50,7 @@ angular ] selected: (if _.get(xAxisOptions, 'min') then null else 0) + todayUTC = moment().startOf('day').add(moment().utcOffset(), 'minutes') class Chart @@ -63,7 +69,7 @@ angular return @ template: -> - @_template.get(@data.series, @options) + @_template.get(@data.series, @options, @data.xAxis, @data.yAxis, @data.plotOptions) formatters: -> currency = @options.currency