From 38c00391fc8ae2ea6bbd35ddb44276f441b54739 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 4 Jul 2016 17:15:37 +0200 Subject: [PATCH 01/24] basic UI and API for exporting stats to excel --- ...tatistics.coffee => statistics.coffee.erb} | 81 ++++++++++++++++++- .../admin/statistics/export.html.erb | 68 ++++++++++++++++ .../templates/admin/statistics/index.html.erb | 1 + app/controllers/api/statistics_controller.rb | 10 +++ config/locales/app.admin.en.yml | 4 + config/locales/app.admin.fr.yml | 4 + config/routes.rb | 1 + 7 files changed, 166 insertions(+), 3 deletions(-) rename app/assets/javascripts/controllers/admin/{statistics.coffee => statistics.coffee.erb} (87%) create mode 100644 app/assets/templates/admin/statistics/export.html.erb diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee b/app/assets/javascripts/controllers/admin/statistics.coffee.erb similarity index 87% rename from app/assets/javascripts/controllers/admin/statistics.coffee rename to app/assets/javascripts/controllers/admin/statistics.coffee.erb index 454b9961f..60925e364 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -1,7 +1,9 @@ 'use strict' -Application.Controllers.controller "StatisticsController", ["$scope", "$state", "$rootScope", "Statistics", "es", "Member", '_t', 'membersPromise', 'statisticsPromise' -, ($scope, $state, $rootScope, Statistics, es, Member, _t, membersPromise, statisticsPromise) -> + + +Application.Controllers.controller "StatisticsController", ["$scope", "$state", "$rootScope", '$uibModal', "Statistics", "es", "Member", '_t', 'membersPromise', 'statisticsPromise' +, ($scope, $state, $rootScope, $uibModal, Statistics, es, Member, _t, membersPromise, statisticsPromise) -> @@ -271,6 +273,27 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", + ## + # Run the current elastic query on the server and return the result as an Excel file + ## + $scope.exportToExcel = -> + options = + templateUrl: '<%= asset_path "admin/statistics/export.html" %>' + size: 'sm' + controller: 'ExportStatisticsController' + resolve: + dates: -> + start: $scope.datePickerStart.selected + end: $scope.datePickerEnd.selected + + $uibModal.open options + .result['finally'](null).then (info)-> + console.info(info) + , (reason)-> + console.error(reason) + + + ### PRIVATE SCOPE ### ## @@ -429,7 +452,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", "field": "age" "total_stat": "sum": - "field": "sta" + "field": "stat" } q @@ -490,3 +513,55 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", initialize() ] + + + +Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$uibModalInstance', 'dates', ($scope, $uibModalInstance, dates) -> + + # bindings for date range + $scope.dates = dates + + + $scope.export = + type: 'current' + + ## datePicker parameters for interval beginning + $scope.exportStart = + format: Fablab.uibDateFormat + opened: false # default: datePicker is not shown + minDate: null + maxDate: moment().subtract(1, 'day').toDate() + options: + startingDay: Fablab.weekStartingDay + + ## datePicker parameters for interval ending + $scope.exportEnd = + format: Fablab.uibDateFormat + opened: false # default: datePicker is not shown + minDate: null + maxDate: moment().subtract(1, 'day').toDate() + options: + startingDay: Fablab.weekStartingDay + + ## + # Callback to open the datepicker (interval start) + # @param $event {Object} jQuery event object + ## + $scope.toggleStartDatePicker = ($event) -> + $scope.exportStart.opened = !$scope.exportStart.opened + + + + ## + # Callback to open the datepicker (interval end) + # @param $event {Object} jQuery event object + ## + $scope.toggleEndDatePicker = ($event) -> + $scope.exportEnd.opened = !$scope.exportEnd.opened + + + $scope.ok = (info) -> + $uibModalInstance.close( info ) + $scope.cancel = -> + $uibModalInstance.dismiss('cancel') +] \ No newline at end of file diff --git a/app/assets/templates/admin/statistics/export.html.erb b/app/assets/templates/admin/statistics/export.html.erb new file mode 100644 index 000000000..85aa18683 --- /dev/null +++ b/app/assets/templates/admin/statistics/export.html.erb @@ -0,0 +1,68 @@ + + + diff --git a/app/assets/templates/admin/statistics/index.html.erb b/app/assets/templates/admin/statistics/index.html.erb index 9ffe47fba..39a104fb7 100644 --- a/app/assets/templates/admin/statistics/index.html.erb +++ b/app/assets/templates/admin/statistics/index.html.erb @@ -12,6 +12,7 @@
+ {{ 'evolution' | translate }}
diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb index 7dca1b347..0ceb57504 100644 --- a/app/controllers/api/statistics_controller.rb +++ b/app/controllers/api/statistics_controller.rb @@ -14,6 +14,16 @@ class API::StatisticsController < API::ApiController results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response render json: results end + + def export_#{path} + authorize :statistic, :#{path}? + query = MultiJson.load(request.body.read) + results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response + respond_to do |format| + format.html + format.xls + end + end } end diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 5c668f460..1093f28dc 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -389,6 +389,10 @@ en: unknown: "Unknown" user_id: "User ID" display_more_results: "Display more results" + export_statistics_to_excel: "Export statistics to Excel" + export_all_statistics: "Export all statistics" + export_the_current_search_results: "Export the current search results" + export: "Export" stats_graphs: diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index ce66544db..1372d6b27 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -389,6 +389,10 @@ fr: unknown: "Inconnu" user_id: "ID Utilisateur" display_more_results: "Afficher plus de résultats" + export_statistics_to_excel: "Exporter les statistiques vers Excel" + export_all_statistics: "Exporter toutes les statistiques" + export_the_current_search_results: "Exporter les résultats de la recherche courante" + export: "Exporter" stats_graphs: diff --git a/config/routes.rb b/config/routes.rb index 1226d6dbb..89ffae482 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -127,6 +127,7 @@ Rails.application.routes.draw do %w(account event machine project subscription training user).each do |path| post "/stats/#{path}/_search", to: "api/statistics##{path}" + post "/stats/#{path}/export", to: "api/statistics#export_#{path}" end post '_search/scroll', to: "api/statistics#scroll" From b26bbd18ef7e200e1862ff295cfb6de9b3d080cb Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 5 Jul 2016 12:21:55 +0200 Subject: [PATCH 02/24] API accessing service & conforming server-side access points --- .../controllers/admin/statistics.coffee.erb | 72 +++++++++++++++---- app/assets/javascripts/services/export.coffee | 11 +++ app/controllers/api/statistics_controller.rb | 10 +-- config/routes.rb | 1 + 4 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 app/assets/javascripts/services/export.coffee diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee.erb b/app/assets/javascripts/controllers/admin/statistics.coffee.erb index 60925e364..d9b5f058f 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee.erb +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -2,8 +2,8 @@ -Application.Controllers.controller "StatisticsController", ["$scope", "$state", "$rootScope", '$uibModal', "Statistics", "es", "Member", '_t', 'membersPromise', 'statisticsPromise' -, ($scope, $state, $rootScope, $uibModal, Statistics, es, Member, _t, membersPromise, statisticsPromise) -> +Application.Controllers.controller "StatisticsController", ["$scope", "$state", "$rootScope", '$uibModal', "Export", "es", "Member", '_t', 'membersPromise', 'statisticsPromise' +, ($scope, $state, $rootScope, $uibModal, Export, es, Member, _t, membersPromise, statisticsPromise) -> @@ -288,9 +288,24 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", $uibModal.open options .result['finally'](null).then (info)-> - console.info(info) - , (reason)-> - console.error(reason) + # export requested + if info.type == 'current' + custom = buildCustomFilterQuery() + Export.stats $scope.selectedIndex.es_type_key, + buildElasticDataQuery($scope.type.active.key, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) + else if info.type == 'global' + Export.stats 'global', + "query": + "bool": + "must": [ + { + "range": + "date": + "gte": moment(info.dates.start).format() + "lte": moment(info.dates.end).format() + } + ] + @@ -331,12 +346,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", $scope.sumStat = 0 $scope.totalHits = null $scope.searchDate = new Date() - custom = null - if $scope.customFilter.criterion and $scope.customFilter.criterion.key and $scope.customFilter.value - custom = {} - custom.key = $scope.customFilter.criterion.key - custom.value = $scope.customFilter.value - custom.exclude = $scope.customFilter.exclude + custom = buildCustomFilterQuery() queryElasticStats $scope.selectedIndex.es_type_key, $scope.type.active.key, custom, (res, err)-> if (err) console.error("[statisticsController::refreshStats] Unable to refresh due to "+err) @@ -508,6 +518,20 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", + ## + # Build and return an object according to the custom filter set by the user, used to request elasticsearch + # @return {Object|null} + ## + buildCustomFilterQuery = -> + custom = null + if $scope.customFilter.criterion and $scope.customFilter.criterion.key and $scope.customFilter.value + custom = {} + custom.key = $scope.customFilter.criterion.key + custom.value = $scope.customFilter.value + custom.exclude = $scope.customFilter.exclude + custom + + # init the controller (call at the end !) initialize() @@ -518,10 +542,10 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$uibModalInstance', 'dates', ($scope, $uibModalInstance, dates) -> - # bindings for date range + ## Bindings for date range $scope.dates = dates - + ## Binding of the export type (global / current) $scope.export = type: 'current' @@ -543,6 +567,8 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u options: startingDay: Fablab.weekStartingDay + + ## # Callback to open the datepicker (interval start) # @param $event {Object} jQuery event object @@ -560,8 +586,24 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u $scope.exportEnd.opened = !$scope.exportEnd.opened - $scope.ok = (info) -> - $uibModalInstance.close( info ) + + ## + # Callback to close the modal, telling the caller to start the export with the selected parameters + ## + $scope.ok = -> + info = + type: $scope.export.type + + if info.type == 'global' + info.dates = $scope.dates + + $uibModalInstance.close(info) + + + + ## + # Callback to cancel the export and close the modal + ## $scope.cancel = -> $uibModalInstance.dismiss('cancel') ] \ No newline at end of file diff --git a/app/assets/javascripts/services/export.coffee b/app/assets/javascripts/services/export.coffee new file mode 100644 index 000000000..5f7d0483d --- /dev/null +++ b/app/assets/javascripts/services/export.coffee @@ -0,0 +1,11 @@ +'use strict' + +Application.Services.factory 'Export', ["$http", ($http)-> + stats: (scope, query) -> + $http.post('/stats/'+scope+'/export', query).then((res) -> + console.log(res) + , (err) -> + console.error(err) + ) + +] diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb index 0ceb57504..bf85952dc 100644 --- a/app/controllers/api/statistics_controller.rb +++ b/app/controllers/api/statistics_controller.rb @@ -19,14 +19,16 @@ class API::StatisticsController < API::ApiController authorize :statistic, :#{path}? query = MultiJson.load(request.body.read) results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response - respond_to do |format| - format.html - format.xls - end + render xls: results end } end + def export_global + # query all stats with range arguments + render xls: [] + end + def scroll authorize :statistic, :scroll? diff --git a/config/routes.rb b/config/routes.rb index 89ffae482..b96d8c9ed 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -129,6 +129,7 @@ Rails.application.routes.draw do post "/stats/#{path}/_search", to: "api/statistics##{path}" post "/stats/#{path}/export", to: "api/statistics#export_#{path}" end + post '/stats/global/export', to: "api/statistics#export_global" post '_search/scroll', to: "api/statistics#scroll" match '/project_collaborator/:valid_token', to: 'api/projects#collaborator_valid', via: :get From 4dcab27af283d03223ce790e909f7c02fab3f234 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 5 Jul 2016 16:13:11 +0200 Subject: [PATCH 03/24] API generate valid xlsx files --- CHANGELOG.md | 3 +++ Gemfile | 5 +++++ Gemfile.lock | 12 ++++++++++++ app/controllers/api/statistics_controller.rb | 4 ++-- .../api/statistics/export_subscription.xlsx.axlsx | 6 ++++++ config/initializers/mime_types.rb | 3 ++- 6 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 app/views/api/statistics/export_subscription.xlsx.axlsx diff --git a/CHANGELOG.md b/CHANGELOG.md index e493ecec1..2d759d4eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog Fab Manager +## next release +- [TODO DEPLOY] `bundle install` + ## v2.3.0 2016 June 28 - Public API with access management and online documentation diff --git a/Gemfile b/Gemfile index 90f60d23e..b0250b9a7 100644 --- a/Gemfile +++ b/Gemfile @@ -144,3 +144,8 @@ gem 'openlab_ruby' gem 'api-pagination' gem 'has_secure_token' gem 'apipie-rails' + +# XLS files generation +gem 'rubyzip', '~> 1.1.0' +gem 'axlsx', '2.1.0.pre' +gem 'axlsx_rails' diff --git a/Gemfile.lock b/Gemfile.lock index 348501c84..b80aa12bb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -54,6 +54,13 @@ GEM descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) + axlsx (2.1.0.pre) + htmlentities (~> 4.3.1) + nokogiri (>= 1.4.1) + rubyzip (~> 1.1.7) + axlsx_rails (0.4.0) + axlsx (>= 2.0.1) + rails (>= 3.1) bcrypt (3.1.10) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) @@ -174,6 +181,7 @@ GEM highline (1.7.1) hike (1.2.3) hitimes (1.2.2) + htmlentities (4.3.4) http (0.6.4) http_parser.rb (~> 0.6.0) http-cookie (1.0.2) @@ -325,6 +333,7 @@ GEM netrc (~> 0.7) rolify (4.0.0) ruby-progressbar (1.7.5) + rubyzip (1.1.7) rufus-scheduler (3.0.9) tzinfo rvm-capistrano (1.5.6) @@ -440,6 +449,8 @@ DEPENDENCIES api-pagination apipie-rails awesome_print + axlsx (= 2.1.0.pre) + axlsx_rails bootstrap-sass byebug capistrano @@ -488,6 +499,7 @@ DEPENDENCIES recurrence responders (~> 2.0) rolify + rubyzip (~> 1.1.0) rvm-capistrano sass-rails (= 5.0.1) sdoc (~> 0.4.0) diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb index bf85952dc..aaf908cd0 100644 --- a/app/controllers/api/statistics_controller.rb +++ b/app/controllers/api/statistics_controller.rb @@ -18,8 +18,8 @@ class API::StatisticsController < API::ApiController def export_#{path} authorize :statistic, :#{path}? query = MultiJson.load(request.body.read) - results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response - render xls: results + @results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response + render xlsx: 'export_#{path}.xlsx', filename: "#{path}.xlsx" end } end diff --git a/app/views/api/statistics/export_subscription.xlsx.axlsx b/app/views/api/statistics/export_subscription.xlsx.axlsx new file mode 100644 index 000000000..ba1e7fa69 --- /dev/null +++ b/app/views/api/statistics/export_subscription.xlsx.axlsx @@ -0,0 +1,6 @@ +wb = xlsx_package.workbook +wb.add_worksheet(name: "Abonnements") do |sheet| + @results[:hits][:hits].each do |hit| + sheet.add_row [hit[:_source].date, hit[:_source].userId, hit[:_source].gender, hit[:_source].age, hit[:_source].planId, hit[:_source].groupName, hit[:_source].ca] + end +end \ No newline at end of file diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index bb3a62d1c..669623a3c 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -4,4 +4,5 @@ # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone -Mime::Type.register "application/vnd.ms-excel", :xls \ No newline at end of file +Mime::Type.register "application/vnd.ms-excel", :xls +Mime::Type.register "application/xlsx", :xlsx \ No newline at end of file From d307f91983034d36301bc53bcf5a76b7b6541d52 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 5 Jul 2016 17:23:14 +0200 Subject: [PATCH 04/24] use an hidden form to post export data --- .../javascripts/controllers/admin/statistics.coffee.erb | 9 ++++++--- app/assets/templates/admin/statistics/index.html.erb | 4 ++++ config/initializers/mime_types.rb | 3 +-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee.erb b/app/assets/javascripts/controllers/admin/statistics.coffee.erb index d9b5f058f..32c06e1d4 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee.erb +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -291,10 +291,12 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", # export requested if info.type == 'current' custom = buildCustomFilterQuery() - Export.stats $scope.selectedIndex.es_type_key, - buildElasticDataQuery($scope.type.active.key, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) + $scope.exportUrl = '/stats/'+$scope.selectedIndex.es_type_key+'/export' + $scope.exportQuery = buildElasticDataQuery($scope.type.active.key, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) + angular.element('#export-stats').submit() else if info.type == 'global' - Export.stats 'global', + $scope.exportUrl = '/stats/global/export' + $scope.exportQuery = "query": "bool": "must": [ @@ -305,6 +307,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", "lte": moment(info.dates.end).format() } ] + angular.element('#export-stats').submit() diff --git a/app/assets/templates/admin/statistics/index.html.erb b/app/assets/templates/admin/statistics/index.html.erb index 39a104fb7..2d6696d84 100644 --- a/app/assets/templates/admin/statistics/index.html.erb +++ b/app/assets/templates/admin/statistics/index.html.erb @@ -289,3 +289,7 @@ +
+ +
+ diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 669623a3c..bb3a62d1c 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -4,5 +4,4 @@ # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone -Mime::Type.register "application/vnd.ms-excel", :xls -Mime::Type.register "application/xlsx", :xlsx \ No newline at end of file +Mime::Type.register "application/vnd.ms-excel", :xls \ No newline at end of file From e8b1c99d2ae8e2b31f493040cb620fdb38df7499 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 6 Jul 2016 15:53:09 +0200 Subject: [PATCH 05/24] Alleluia git add . Downloading xls from front is working --- .../controllers/admin/statistics.coffee.erb | 63 ++++++++++++------- app/assets/javascripts/services/export.coffee | 7 +-- .../admin/statistics/export.html.erb | 13 ++-- .../templates/admin/statistics/index.html.erb | 5 -- app/controllers/api/statistics_controller.rb | 2 +- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee.erb b/app/assets/javascripts/controllers/admin/statistics.coffee.erb index 32c06e1d4..25e57300a 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee.erb +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -285,29 +285,15 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", dates: -> start: $scope.datePickerStart.selected end: $scope.datePickerEnd.selected + query: -> + custom = buildCustomFilterQuery() + buildElasticDataQuery($scope.type.active.key, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) + key: -> + key: $scope.selectedIndex.es_type_key $uibModal.open options .result['finally'](null).then (info)-> - # export requested - if info.type == 'current' - custom = buildCustomFilterQuery() - $scope.exportUrl = '/stats/'+$scope.selectedIndex.es_type_key+'/export' - $scope.exportQuery = buildElasticDataQuery($scope.type.active.key, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) - angular.element('#export-stats').submit() - else if info.type == 'global' - $scope.exportUrl = '/stats/global/export' - $scope.exportQuery = - "query": - "bool": - "must": [ - { - "range": - "date": - "gte": moment(info.dates.start).format() - "lte": moment(info.dates.end).format() - } - ] - angular.element('#export-stats').submit() + console.log(info) @@ -543,11 +529,25 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", -Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$uibModalInstance', 'dates', ($scope, $uibModalInstance, dates) -> +Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$uibModalInstance', 'dates', 'query', 'key', 'CSRF' +, ($scope, $uibModalInstance, dates, query, key, CSRF) -> + + CSRF.setMetaTags() ## Bindings for date range $scope.dates = dates + ## Body of the query to export + $scope.query = JSON.stringify(query) + + ## API URL where the form will be posted + $scope.actionUrl = '/stats/'+key.key+'/export' + + ## Form action on the above URL + $scope.method = "post" + + $scope.csrfToken = angular.element('meta[name="csrf-token"]')[0].content + ## Binding of the export type (global / current) $scope.export = type: 'current' @@ -590,10 +590,29 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u + $scope.setRequest = -> + if $scope.export.type == 'global' + $scope.actionUrl = '/stats/global/export' + $scope.query = JSON.stringify(query) + else + $scope.actionUrl = '/stats/'+key.key+'/export' + $scope.query = JSON.stringify( + "query": + "bool": + "must": [ + { + "range": + "date": + "gte": moment(info.dates.start).format() + "lte": moment(info.dates.end).format() + } + ] + ) + ## # Callback to close the modal, telling the caller to start the export with the selected parameters ## - $scope.ok = -> + $scope.exportData = -> info = type: $scope.export.type diff --git a/app/assets/javascripts/services/export.coffee b/app/assets/javascripts/services/export.coffee index 5f7d0483d..2c8d6e4b1 100644 --- a/app/assets/javascripts/services/export.coffee +++ b/app/assets/javascripts/services/export.coffee @@ -2,10 +2,5 @@ Application.Services.factory 'Export', ["$http", ($http)-> stats: (scope, query) -> - $http.post('/stats/'+scope+'/export', query).then((res) -> - console.log(res) - , (err) -> - console.error(err) - ) - + $http.post('/stats/'+scope+'/export', query) ] diff --git a/app/assets/templates/admin/statistics/export.html.erb b/app/assets/templates/admin/statistics/export.html.erb index 85aa18683..b5dc51da1 100644 --- a/app/assets/templates/admin/statistics/export.html.erb +++ b/app/assets/templates/admin/statistics/export.html.erb @@ -5,7 +5,7 @@ diff --git a/app/assets/templates/admin/statistics/index.html.erb b/app/assets/templates/admin/statistics/index.html.erb index 2d6696d84..06a58f754 100644 --- a/app/assets/templates/admin/statistics/index.html.erb +++ b/app/assets/templates/admin/statistics/index.html.erb @@ -288,8 +288,3 @@ - -
- -
- diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb index aaf908cd0..79c1a0666 100644 --- a/app/controllers/api/statistics_controller.rb +++ b/app/controllers/api/statistics_controller.rb @@ -17,7 +17,7 @@ class API::StatisticsController < API::ApiController def export_#{path} authorize :statistic, :#{path}? - query = MultiJson.load(request.body.read) + query = MultiJson.load(params[:body]) @results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response render xlsx: 'export_#{path}.xlsx', filename: "#{path}.xlsx" end From b0f7c634ea05cc04e97044577f7c60e88af05d09 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 6 Jul 2016 19:00:22 +0200 Subject: [PATCH 06/24] subscriptions export with all rows --- app/controllers/api/statistics_controller.rb | 9 ++++++++- .../api/statistics/export_subscription.xlsx.axlsx | 11 +++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb index 79c1a0666..693d8613d 100644 --- a/app/controllers/api/statistics_controller.rb +++ b/app/controllers/api/statistics_controller.rb @@ -18,7 +18,13 @@ class API::StatisticsController < API::ApiController def export_#{path} authorize :statistic, :#{path}? query = MultiJson.load(params[:body]) - @results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response + @results = Elasticsearch::Model.client.search({index: 'stats', type: '#{path}', scroll: '30s', body: query}) + scroll_id = @results['_scroll_id'] + while @results['hits']['hits'].size != @results['hits']['total'] + scroll_res = Elasticsearch::Model.client.scroll(scroll: '30s', scroll_id: scroll_id) + @results['hits']['hits'].concat(scroll_res['hits']['hits']) + scroll_id = scroll_res['_scroll_id'] + end render xlsx: 'export_#{path}.xlsx', filename: "#{path}.xlsx" end } @@ -26,6 +32,7 @@ class API::StatisticsController < API::ApiController def export_global # query all stats with range arguments + Elasticsearch::Model.client.search render xls: [] end diff --git a/app/views/api/statistics/export_subscription.xlsx.axlsx b/app/views/api/statistics/export_subscription.xlsx.axlsx index ba1e7fa69..1a9ac787f 100644 --- a/app/views/api/statistics/export_subscription.xlsx.axlsx +++ b/app/views/api/statistics/export_subscription.xlsx.axlsx @@ -1,6 +1,13 @@ wb = xlsx_package.workbook + +price = wb.styles.add_style :format_code => '' + wb.add_worksheet(name: "Abonnements") do |sheet| - @results[:hits][:hits].each do |hit| - sheet.add_row [hit[:_source].date, hit[:_source].userId, hit[:_source].gender, hit[:_source].age, hit[:_source].planId, hit[:_source].groupName, hit[:_source].ca] + sheet.add_row ['Entrées', @results['hits']['total']] + sheet.add_row ["Chiffre d'affaires", @results['aggregations']['total_ca']['value']] + sheet.add_row ['Âge moyen', @results['aggregations']['average_age']['value']] + sheet.add_row ['Date', 'ID Utilisateur', 'Genre', 'Âge', 'Type', 'Groupe', "Chiffre d'affaire"] + @results['hits']['hits'].each do |hit| + sheet.add_row [hit['_source']['date'], hit['_source']['userId'], hit['_source']['gender'], hit['_source']['age'], hit['_source']['planId'], hit['_source']['groupName'], hit['_source']['ca']] end end \ No newline at end of file From eec004d1ba73255c0a52ad861c5d5668835ea61e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 6 Jul 2016 19:04:04 +0200 Subject: [PATCH 07/24] cancel button does not trigger the generation --- app/assets/templates/admin/statistics/export.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/templates/admin/statistics/export.html.erb b/app/assets/templates/admin/statistics/export.html.erb index b5dc51da1..403d517d0 100644 --- a/app/assets/templates/admin/statistics/export.html.erb +++ b/app/assets/templates/admin/statistics/export.html.erb @@ -63,11 +63,11 @@ From 8e8bd3f9ebbb6b0c777628b4be24899501c06a2d Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 7 Jul 2016 15:31:22 +0200 Subject: [PATCH 08/24] full subscriptions export --- README.md | 5 ++++ .../controllers/admin/statistics.coffee.erb | 8 +++++- .../admin/statistics/export.html.erb | 2 +- app/controllers/api/statistics_controller.rb | 4 +++ app/helpers/application_helper.rb | 11 ++++++++ .../statistics/export_subscription.xlsx.axlsx | 27 ++++++++++++++----- config/application.yml.default | 1 + config/locales/fr.yml | 21 ++++++++++++++- config/secrets.yml | 4 +++ 9 files changed, 73 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 94f47b3e9..6a0a3b075 100644 --- a/README.md +++ b/README.md @@ -531,6 +531,11 @@ See https://angular-ui.github.io/bootstrap/#uibdateparser-s-format-codes for a l **BEWARE**: years format with less than 4 digits will result in problems because the system won't be able to distinct dates with the same less significant digits, eg. 50 could mean 1950 or 2050. + EXCEL_DATE_FORMAT + +Date format for dates shown in exported Excel files (eg. statistics) +See https://support.microsoft.com/en-us/kb/264372 for a list a available formats. + #### Applying changes diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee.erb b/app/assets/javascripts/controllers/admin/statistics.coffee.erb index 25e57300a..51b6bddd5 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee.erb +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -274,7 +274,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", ## - # Run the current elastic query on the server and return the result as an Excel file + # Open a modal dialog asking the user for details about exporting the statistics tables to an excel file ## $scope.exportToExcel = -> options = @@ -532,6 +532,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$uibModalInstance', 'dates', 'query', 'key', 'CSRF' , ($scope, $uibModalInstance, dates, query, key, CSRF) -> + ## Retrieve Anti-CSRF tokens from cookies CSRF.setMetaTags() ## Bindings for date range @@ -546,6 +547,7 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u ## Form action on the above URL $scope.method = "post" + ## Anti-CSRF token to inject into the download form $scope.csrfToken = angular.element('meta[name="csrf-token"]')[0].content ## Binding of the export type (global / current) @@ -590,6 +592,10 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u + ## + # Callback when exchanging the export type between 'global' and 'current view' + # Adjust the query and the requesting url according to this type. + ## $scope.setRequest = -> if $scope.export.type == 'global' $scope.actionUrl = '/stats/global/export' diff --git a/app/assets/templates/admin/statistics/export.html.erb b/app/assets/templates/admin/statistics/export.html.erb index 403d517d0..4cebbbef3 100644 --- a/app/assets/templates/admin/statistics/export.html.erb +++ b/app/assets/templates/admin/statistics/export.html.erb @@ -67,7 +67,7 @@ - + diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb index 693d8613d..5efc45d60 100644 --- a/app/controllers/api/statistics_controller.rb +++ b/app/controllers/api/statistics_controller.rb @@ -25,6 +25,10 @@ class API::StatisticsController < API::ApiController @results['hits']['hits'].concat(scroll_res['hits']['hits']) scroll_id = scroll_res['_scroll_id'] end + ids = @results['hits']['hits'].map { |u| u['_source']['userId'] } + @users = User.includes(:profile).where(:id => ids) + type_key = query['query']['bool']['must'][0]['term']['type'].to_s + @subtypes = StatisticType.find_by(key: type_key, statistic_index_id: StatisticIndex.find_by(es_type_key: '#{path}').id).statistic_sub_types render xlsx: 'export_#{path}.xlsx', filename: "#{path}.xlsx" end } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c504024bc..3d26f5936 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -52,6 +52,17 @@ module ApplicationHelper if (bool) then return :true else return :false end end + def get_item(array, id, key = nil) + array.each do |i| + if key.nil? + return i if i.id == id + else + return i if i[key] == id + end + + end + end + private ## inspired by gems/actionview-4.2.5/lib/action_view/helpers/translation_helper.rb diff --git a/app/views/api/statistics/export_subscription.xlsx.axlsx b/app/views/api/statistics/export_subscription.xlsx.axlsx index 1a9ac787f..1393f27fd 100644 --- a/app/views/api/statistics/export_subscription.xlsx.axlsx +++ b/app/views/api/statistics/export_subscription.xlsx.axlsx @@ -1,13 +1,26 @@ wb = xlsx_package.workbook -price = wb.styles.add_style :format_code => '' +bold = wb.styles.add_style :b => true +date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_format -wb.add_worksheet(name: "Abonnements") do |sheet| - sheet.add_row ['Entrées', @results['hits']['total']] - sheet.add_row ["Chiffre d'affaires", @results['aggregations']['total_ca']['value']] - sheet.add_row ['Âge moyen', @results['aggregations']['average_age']['value']] - sheet.add_row ['Date', 'ID Utilisateur', 'Genre', 'Âge', 'Type', 'Groupe', "Chiffre d'affaire"] +wb.add_worksheet(name: t('export.subscriptions')) do |sheet| + sheet.add_row [t('export.entries'), @results['hits']['total']], :style => [bold, nil], :types => [:string, :float] + sheet.add_row [t('export.revenue'), @results['aggregations']['total_ca']['value']], :style => [bold, nil], :types => [:string, :float] + sheet.add_row [t('export.average_age'), @results['aggregations']['average_age']['value']], :style => [bold, nil], :types => [:string, :float] + sheet.add_row [] + sheet.add_row [t('export.date'), t('export.user'), t('export.email'), t('export.phone'), t('export.gender'), t('export.age'), t('export.type'), t('export.group'), t('export.revenue')], :style => bold @results['hits']['hits'].each do |hit| - sheet.add_row [hit['_source']['date'], hit['_source']['userId'], hit['_source']['gender'], hit['_source']['age'], hit['_source']['planId'], hit['_source']['groupName'], hit['_source']['ca']] + user = get_item(@users, hit['_source']['userId']) + sheet.add_row [ + Date::strptime(hit['_source']['date'],'%Y-%m-%d'), + user.profile.full_name, + user.email, user.profile.phone, + t("export.#{hit['_source']['gender']}"), + hit['_source']['age'], + get_item(@subtypes, hit['_source']['subType'], 'key').label, + hit['_source']['groupName'], + hit['_source']['ca'] + ], :style => [date, nil, nil, nil, nil, nil, nil, nil], + :types => [:date, :string, :string, :string, :string, :integer, :string, :string, :float] end end \ No newline at end of file diff --git a/config/application.yml.default b/config/application.yml.default index 4facf7172..d041ecc51 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -47,6 +47,7 @@ TIME_ZONE: 'Paris' WEEK_STARTING_DAY: 'monday' D3_DATE_FORMAT: '%d/%m/%y' UIB_DATE_FORMAT: 'dd/MM/yyyy' +EXCEL_DATE_FORMAT: 'dd/mm/yyyy' OPENLAB_APP_SECRET: OPENLAB_APP_ID: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index f34204b90..8c535a11e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -255,4 +255,23 @@ fr: course: "Stage" workshop: "Atelier" account_creation: "Création de compte" - project_publication: "Publication de projet" \ No newline at end of file + project_publication: "Publication de projet" + + export: + # export des statistiques au format excel + subscriptions: "Abonnements" + entries: "Entrées" + revenue_: "Chiffre d'affaires" + average_age: "Âge moyen" + total: "Total" + date: "Date" + user: "Utilisateur" + email: "Courriel" + phone: "Téléphone" + gender: "Genre" + age: "Âge" + type: "Type" + group: "Groupe" + revenue: "Chiffre d'affaires" + male: "Homme" + female: "Femme" \ No newline at end of file diff --git a/config/secrets.yml b/config/secrets.yml index 4425cc5fc..7703aea4e 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -21,6 +21,7 @@ development: week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> # .dump is needed as the value may start by a '%', see https://github.com/tenderlove/psych/issues/75 uib_date_format: <%= ENV["UIB_DATE_FORMAT"] %> + excel_date_format: <%= ENV["EXCEL_DATE_FORMAT"]%> rails_locale: <%= ENV["RAILS_LOCALE"] %> moment_locale: <%= ENV["MOMENT_LOCALE"] %> summernote_locale: <%= ENV["SUMMERNOTE_LOCALE"] %> @@ -45,6 +46,7 @@ test: week_starting_day: monday d3_date_format: '%d/%m/%y' uib_date_format: dd/MM/yyyy + excel_date_format: dd/mm/yyyy rails_locale: en moment_locale: en summernote_locale: en-US @@ -76,6 +78,7 @@ staging: week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> uib_date_format: <%= ENV["UIB_DATE_FORMAT"] %> + excel_date_format: <%= ENV["EXCEL_DATE_FORMAT"]%> rails_locale: <%= ENV["RAILS_LOCALE"] %> moment_locale: <%= ENV["MOMENT_LOCALE"] %> summernote_locale: <%= ENV["SUMMERNOTE_LOCALE"] %> @@ -109,6 +112,7 @@ production: week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %> d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %> uib_date_format: <%= ENV["UIB_DATE_FORMAT"] %> + excel_date_format: <%= ENV["EXCEL_DATE_FORMAT"]%> rails_locale: <%= ENV["RAILS_LOCALE"] %> moment_locale: <%= ENV["MOMENT_LOCALE"] %> summernote_locale: <%= ENV["SUMMERNOTE_LOCALE"] %> From 022db4e4865a853ba7cc823c80ead247c45cb8a7 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 7 Jul 2016 16:26:25 +0200 Subject: [PATCH 09/24] export all stats indices to excel --- app/controllers/api/statistics_controller.rb | 12 +++- app/helpers/application_helper.rb | 2 +- .../api/statistics/export_current.xlsx.axlsx | 61 +++++++++++++++++++ .../statistics/export_subscription.xlsx.axlsx | 26 -------- config/locales/en.yml | 19 +++++- config/locales/fr.yml | 4 +- 6 files changed, 91 insertions(+), 33 deletions(-) create mode 100644 app/views/api/statistics/export_current.xlsx.axlsx delete mode 100644 app/views/api/statistics/export_subscription.xlsx.axlsx diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb index 5efc45d60..00ddf863e 100644 --- a/app/controllers/api/statistics_controller.rb +++ b/app/controllers/api/statistics_controller.rb @@ -17,7 +17,9 @@ class API::StatisticsController < API::ApiController def export_#{path} authorize :statistic, :#{path}? + query = MultiJson.load(params[:body]) + @results = Elasticsearch::Model.client.search({index: 'stats', type: '#{path}', scroll: '30s', body: query}) scroll_id = @results['_scroll_id'] while @results['hits']['hits'].size != @results['hits']['total'] @@ -25,11 +27,17 @@ class API::StatisticsController < API::ApiController @results['hits']['hits'].concat(scroll_res['hits']['hits']) scroll_id = scroll_res['_scroll_id'] end + ids = @results['hits']['hits'].map { |u| u['_source']['userId'] } @users = User.includes(:profile).where(:id => ids) + type_key = query['query']['bool']['must'][0]['term']['type'].to_s - @subtypes = StatisticType.find_by(key: type_key, statistic_index_id: StatisticIndex.find_by(es_type_key: '#{path}').id).statistic_sub_types - render xlsx: 'export_#{path}.xlsx', filename: "#{path}.xlsx" + @index = StatisticIndex.find_by(es_type_key: '#{path}') + @type = StatisticType.find_by(key: type_key, statistic_index_id: @index.id) + @subtypes = @type.statistic_sub_types + @fields = @index.statistic_fields + + render xlsx: 'export_current.xlsx', filename: "#{path}.xlsx" end } end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3d26f5936..d0ee68424 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -59,8 +59,8 @@ module ApplicationHelper else return i if i[key] == id end - end + nil end diff --git a/app/views/api/statistics/export_current.xlsx.axlsx b/app/views/api/statistics/export_current.xlsx.axlsx new file mode 100644 index 000000000..4c176572d --- /dev/null +++ b/app/views/api/statistics/export_current.xlsx.axlsx @@ -0,0 +1,61 @@ +wb = xlsx_package.workbook + +bold = wb.styles.add_style :b => true +date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_format + +wb.add_worksheet(name: @index.label) do |sheet| + ## heading stats for the current page + sheet.add_row [t('export.entries'), @results['hits']['total']], :style => [bold, nil], :types => [:string, :integer] + if @index.ca + sheet.add_row [t('export.revenue'), @results['aggregations']['total_ca']['value']], :style => [bold, nil], :types => [:string, :float] + end + sheet.add_row [t('export.average_age'), @results['aggregations']['average_age']['value']], :style => [bold, nil], :types => [:string, :float] + unless @type.simple + sheet.add_row ["#{t('export.total')} #{@type.label}", @results['aggregations']['total_stat']['value']], :style => [bold, nil], :types => [:string, :integer] + end + sheet.add_row [] + + ## data table + # heading labels + columns = [t('export.date'), t('export.user'), t('export.email'), t('export.phone'), t('export.gender'), t('export.age'), t('export.type')] + columns.push @type.label unless @type.simple + @fields.each do |f| + columns.push f.label + end + columns.push t('export.revenue') if @index.ca + sheet.add_row columns, :style => bold + + # data rows + @results['hits']['hits'].each do |hit| + user = get_item(@users, hit['_source']['userId']) + subtype = get_item(@subtypes, hit['_source']['subType'], 'key') + data = [ + Date::strptime(hit['_source']['date'],'%Y-%m-%d'), + user.profile.full_name, + user.email, + user.profile.phone, + t("export.#{hit['_source']['gender']}"), + hit['_source']['age'], + subtype.nil? ? "" : subtype.label + ] + styles = [date, nil, nil, nil, nil, nil, nil] + types = [:date, :string, :string, :string, :string, :integer, :string] + unless @type.simple + data.push hit['_source']['stat'] + styles.push nil + types.push :string + end + @fields.each do |f| + data.push hit['_source'][f.key] + styles.push nil + types.push :string + end + if @index.ca + data.push hit['_source']['ca'] + styles.push nil + types.push :float + end + + sheet.add_row data, :style => styles, :types => types + end +end \ No newline at end of file diff --git a/app/views/api/statistics/export_subscription.xlsx.axlsx b/app/views/api/statistics/export_subscription.xlsx.axlsx deleted file mode 100644 index 1393f27fd..000000000 --- a/app/views/api/statistics/export_subscription.xlsx.axlsx +++ /dev/null @@ -1,26 +0,0 @@ -wb = xlsx_package.workbook - -bold = wb.styles.add_style :b => true -date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_format - -wb.add_worksheet(name: t('export.subscriptions')) do |sheet| - sheet.add_row [t('export.entries'), @results['hits']['total']], :style => [bold, nil], :types => [:string, :float] - sheet.add_row [t('export.revenue'), @results['aggregations']['total_ca']['value']], :style => [bold, nil], :types => [:string, :float] - sheet.add_row [t('export.average_age'), @results['aggregations']['average_age']['value']], :style => [bold, nil], :types => [:string, :float] - sheet.add_row [] - sheet.add_row [t('export.date'), t('export.user'), t('export.email'), t('export.phone'), t('export.gender'), t('export.age'), t('export.type'), t('export.group'), t('export.revenue')], :style => bold - @results['hits']['hits'].each do |hit| - user = get_item(@users, hit['_source']['userId']) - sheet.add_row [ - Date::strptime(hit['_source']['date'],'%Y-%m-%d'), - user.profile.full_name, - user.email, user.profile.phone, - t("export.#{hit['_source']['gender']}"), - hit['_source']['age'], - get_item(@subtypes, hit['_source']['subType'], 'key').label, - hit['_source']['groupName'], - hit['_source']['ca'] - ], :style => [date, nil, nil, nil, nil, nil, nil, nil], - :types => [:date, :string, :string, :string, :string, :integer, :string, :string, :float] - end -end \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 4bbb517dc..937b8e004 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -251,4 +251,21 @@ en: bookings: "Bookings" hours_number: "Hours number" tickets_number: "Tickets number" - revenue: "Revenue" \ No newline at end of file + revenue: "Revenue" + + export: + # statistics exports to the excel file format + entries: "Entries" + revenue: "Revenue" + average_age: "Average Age" + total: "Total" + date: "Date" + user: "User" + email: "Email" + phone: "Phone" + gender: "Gender" + age: "Age" + type: "Type" + revenue: "Revenue" + male: "Man" + female: "Woman" \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 8c535a11e..ca377da60 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -259,9 +259,8 @@ fr: export: # export des statistiques au format excel - subscriptions: "Abonnements" entries: "Entrées" - revenue_: "Chiffre d'affaires" + revenue: "Chiffre d'affaires" average_age: "Âge moyen" total: "Total" date: "Date" @@ -271,7 +270,6 @@ fr: gender: "Genre" age: "Âge" type: "Type" - group: "Groupe" revenue: "Chiffre d'affaires" male: "Homme" female: "Femme" \ No newline at end of file From 0f7f2c256ab6ac50a0a8a7eda5fb61c760f7ad26 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 7 Jul 2016 16:34:28 +0200 Subject: [PATCH 10/24] [export] handle data type in custom fields --- .../api/statistics/export_current.xlsx.axlsx | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/app/views/api/statistics/export_current.xlsx.axlsx b/app/views/api/statistics/export_current.xlsx.axlsx index 4c176572d..89c49b66b 100644 --- a/app/views/api/statistics/export_current.xlsx.axlsx +++ b/app/views/api/statistics/export_current.xlsx.axlsx @@ -46,9 +46,26 @@ wb.add_worksheet(name: @index.label) do |sheet| types.push :string end @fields.each do |f| - data.push hit['_source'][f.key] - styles.push nil - types.push :string + field_data = hit['_source'][f.key] + case f.data_type + when 'date' + data.push Date::strptime(field_data,'%Y-%m-%d') + styles.push date + types.push :date + when 'list' + data.push field_data.map{|e| e['name'] }.join(', ') + styles.push nil + types.push :string + when 'number' + data.push field_data + styles.push nil + types.push :float + else + data.push field_data + styles.push nil + types.push :string + end + end if @index.ca data.push hit['_source']['ca'] From 8e968f781359f6a599918d9f37ca7e9d03df3ead Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 7 Jul 2016 16:57:23 +0200 Subject: [PATCH 11/24] fix type key transmission --- .../controllers/admin/statistics.coffee.erb | 17 +++++++++++------ .../templates/admin/statistics/export.html.erb | 1 + app/controllers/api/statistics_controller.rb | 2 +- .../api/statistics/export_current.xlsx.axlsx | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee.erb b/app/assets/javascripts/controllers/admin/statistics.coffee.erb index 51b6bddd5..93057dbb0 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee.erb +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -288,8 +288,10 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", query: -> custom = buildCustomFilterQuery() buildElasticDataQuery($scope.type.active.key, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) - key: -> + index: -> key: $scope.selectedIndex.es_type_key + type: -> + key: $scope.type.active.key $uibModal.open options .result['finally'](null).then (info)-> @@ -529,8 +531,8 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", -Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$uibModalInstance', 'dates', 'query', 'key', 'CSRF' -, ($scope, $uibModalInstance, dates, query, key, CSRF) -> +Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$uibModalInstance', 'dates', 'query', 'index', 'type', 'CSRF' +, ($scope, $uibModalInstance, dates, query, index, type, CSRF) -> ## Retrieve Anti-CSRF tokens from cookies CSRF.setMetaTags() @@ -542,7 +544,10 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u $scope.query = JSON.stringify(query) ## API URL where the form will be posted - $scope.actionUrl = '/stats/'+key.key+'/export' + $scope.actionUrl = '/stats/'+index.key+'/export' + + ## Key of the current search' statistic type + $scope.typeKey = type.key ## Form action on the above URL $scope.method = "post" @@ -601,7 +606,7 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u $scope.actionUrl = '/stats/global/export' $scope.query = JSON.stringify(query) else - $scope.actionUrl = '/stats/'+key.key+'/export' + $scope.actionUrl = '/stats/'+index.key+'/export' $scope.query = JSON.stringify( "query": "bool": @@ -616,7 +621,7 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u ) ## - # Callback to close the modal, telling the caller to start the export with the selected parameters + # Callback to close the modal, telling the caller what is exported ## $scope.exportData = -> info = diff --git a/app/assets/templates/admin/statistics/export.html.erb b/app/assets/templates/admin/statistics/export.html.erb index 4cebbbef3..a028612f7 100644 --- a/app/assets/templates/admin/statistics/export.html.erb +++ b/app/assets/templates/admin/statistics/export.html.erb @@ -66,6 +66,7 @@
+
diff --git a/app/controllers/api/statistics_controller.rb b/app/controllers/api/statistics_controller.rb index 00ddf863e..0aade905d 100644 --- a/app/controllers/api/statistics_controller.rb +++ b/app/controllers/api/statistics_controller.rb @@ -19,6 +19,7 @@ class API::StatisticsController < API::ApiController authorize :statistic, :#{path}? query = MultiJson.load(params[:body]) + type_key = params[:type_key] @results = Elasticsearch::Model.client.search({index: 'stats', type: '#{path}', scroll: '30s', body: query}) scroll_id = @results['_scroll_id'] @@ -31,7 +32,6 @@ class API::StatisticsController < API::ApiController ids = @results['hits']['hits'].map { |u| u['_source']['userId'] } @users = User.includes(:profile).where(:id => ids) - type_key = query['query']['bool']['must'][0]['term']['type'].to_s @index = StatisticIndex.find_by(es_type_key: '#{path}') @type = StatisticType.find_by(key: type_key, statistic_index_id: @index.id) @subtypes = @type.statistic_sub_types diff --git a/app/views/api/statistics/export_current.xlsx.axlsx b/app/views/api/statistics/export_current.xlsx.axlsx index 89c49b66b..eaddd9866 100644 --- a/app/views/api/statistics/export_current.xlsx.axlsx +++ b/app/views/api/statistics/export_current.xlsx.axlsx @@ -49,7 +49,7 @@ wb.add_worksheet(name: @index.label) do |sheet| field_data = hit['_source'][f.key] case f.data_type when 'date' - data.push Date::strptime(field_data,'%Y-%m-%d') + data.push Date::strptime(field_data, '%Y-%m-%d') styles.push date types.push :date when 'list' From 569cc1b73764849f089a53c29185cfd528d48989 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 7 Jul 2016 17:15:54 +0200 Subject: [PATCH 12/24] use theme primary in export header --- app/views/api/statistics/export_current.xlsx.axlsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/api/statistics/export_current.xlsx.axlsx b/app/views/api/statistics/export_current.xlsx.axlsx index eaddd9866..81fbdf2bb 100644 --- a/app/views/api/statistics/export_current.xlsx.axlsx +++ b/app/views/api/statistics/export_current.xlsx.axlsx @@ -1,6 +1,7 @@ wb = xlsx_package.workbook bold = wb.styles.add_style :b => true +header = wb.styles.add_style :b => true, :bg_color => Stylesheet.primary.upcase.gsub('#', 'FF'), :fg_color => 'FFFFFFFF' date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_format wb.add_worksheet(name: @index.label) do |sheet| @@ -23,7 +24,7 @@ wb.add_worksheet(name: @index.label) do |sheet| columns.push f.label end columns.push t('export.revenue') if @index.ca - sheet.add_row columns, :style => bold + sheet.add_row columns, :style => header # data rows @results['hits']['hits'].each do |hit| From d84238b33111910e10b277f528790cef356ed90c Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 12 Jul 2016 12:03:38 +0200 Subject: [PATCH 13/24] statistics: global export to excel --- .../controllers/admin/statistics.coffee.erb | 12 +-- .../admin/statistics/export.html.erb | 2 + app/controllers/api/statistics_controller.rb | 22 ++++- app/policies/statistic_policy.rb | 3 +- .../api/statistics/export_global.xlsx.axlsx | 84 +++++++++++++++++++ 5 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 app/views/api/statistics/export_global.xlsx.axlsx diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee.erb b/app/assets/javascripts/controllers/admin/statistics.coffee.erb index 93057dbb0..046d0ee62 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee.erb +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -604,9 +604,6 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u $scope.setRequest = -> if $scope.export.type == 'global' $scope.actionUrl = '/stats/global/export' - $scope.query = JSON.stringify(query) - else - $scope.actionUrl = '/stats/'+index.key+'/export' $scope.query = JSON.stringify( "query": "bool": @@ -614,11 +611,16 @@ Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$u { "range": "date": - "gte": moment(info.dates.start).format() - "lte": moment(info.dates.end).format() + "gte": moment($scope.dates.start).format() + "lte": moment($scope.dates.end).format() } ] ) + else + $scope.actionUrl = '/stats/'+index.key+'/export' + $scope.query = JSON.stringify(query) + + ## # Callback to close the modal, telling the caller what is exported diff --git a/app/assets/templates/admin/statistics/export.html.erb b/app/assets/templates/admin/statistics/export.html.erb index a028612f7..fb577b2a0 100644 --- a/app/assets/templates/admin/statistics/export.html.erb +++ b/app/assets/templates/admin/statistics/export.html.erb @@ -24,6 +24,7 @@ show-button-bar="false" placeholder="{{ 'start' | translate }}" ng-click="toggleStartDatePicker($event)" + ng-change="setRequest()" required="required"/> From 3413c3040b6ba996502f598970496c74a62b4ecf Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 12 Jul 2016 17:46:19 +0200 Subject: [PATCH 20/24] [ongoing] members export full infos --- app/controllers/api/members_controller.rb | 2 +- .../api/members/export_members.xlsx.axlsx | 50 +++++++++++++++---- config/locales/en.yml | 11 ++++ config/locales/fr.yml | 11 ++++ 4 files changed, 63 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/members_controller.rb b/app/controllers/api/members_controller.rb index 0fd7dc5fd..2164ed7db 100644 --- a/app/controllers/api/members_controller.rb +++ b/app/controllers/api/members_controller.rb @@ -106,7 +106,7 @@ class API::MembersController < API::ApiController def export_members authorize :export - @members = User.with_role(:member).includes(:group, :subscriptions, :profile) + @members = User.with_role(:member).includes(:group, :trainings, :tags, :invoices, :projects, :subscriptions => [:plan], :profile => [:address]) render xlsx: 'export_members.xlsx', filename: "export_members.xlsx" end diff --git a/app/views/api/members/export_members.xlsx.axlsx b/app/views/api/members/export_members.xlsx.axlsx index b69c46c5a..2ce8740ca 100644 --- a/app/views/api/members/export_members.xlsx.axlsx +++ b/app/views/api/members/export_members.xlsx.axlsx @@ -8,27 +8,57 @@ wb.add_worksheet(name: t('export_members.members')) do |sheet| ## data table # heading labels columns = [t('export_members.id'), t('export_members.surname'), t('export_members.first_name'), t('export_members.email'), - t('export_members.gender'), t('export_members.age'), t('export_members.phone'), t('export_members.group'), - t('export_members.subscription'), t('export_members.subscription_end_date'), t('export_members.validated_trainings')] + t('export_members.gender'), t('export_members.age'), t('export_members.address'), t('export_members.phone'), + t('export_members.website'), t('export_members.job'), t('export_members.interests'), + t('export_members.cad_software_mastered'), t('export_members.group'), t('export_members.subscription'), + t('export_members.subscription_end_date'), t('export_members.validated_trainings'), t('export_members.tags'), + t('export_members.number_of_invoices'), t('export_members.projects'), t('export_members.facebook'), + t('export_members.twitter'), t('export_members.echo_sciences')] sheet.add_row columns, :style => header # data rows @members.each do |member| data = [ - member.id, - member.profile.last_name, - member.profile.first_name, - member.email, + member.id, member.profile.last_name, member.profile.first_name, member.email, member.profile.gender ? t('export_members.man') : t('export_members.woman'), member.profile.age, - member.profile.phone, + member.profile.address ? member.profile.address.address : '', + member.profile.phone, member.profile.website, member.profile.job, + member.profile.interest, member.profile.software_mastered, member.group.name, (member.subscription and member.subscription.expired_at > Time.now) ? member.subscription.plan.name : t('export_members.without_subscriptions'), (member.subscription and member.subscription.expired_at > Time.now) ? member.subscription.expired_at.to_date : nil, - member.trainings.map(&:name).join("\n") + member.trainings.map(&:name).join("\n"), member.tags.map(&:name).join("\n"), + member.invoices.size, + member.projects.map(&:name).join("\n"), + member.profile.facebook || '', member.profile.twitter || '', member.profile.echosciences || '' ] - styles = [nil, nil, nil, nil, nil, nil, nil, nil, nil, date, nil] - types = [:integer, :string, :string, :string, :string, :integer, :string, :string, :string, :date, :string] + styles = [nil, nil, nil, + nil, + nil, + nil, + nil, nil, nil, + nil, nil, + nil, + nil, + date, + nil, nil, + nil, + nil, + nil, nil, nil] + types = [:integer, :string, :string, + :string, + :string, + :integer, + :string, :string, :string, + :string, :string, + :string, + :string, + :date, + :string, :string, + :integer, + :string, + :string, :string, :string] sheet.add_row data, :style => styles, :types => types end diff --git a/config/locales/en.yml b/config/locales/en.yml index e0b435d05..1cc61304b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -120,11 +120,22 @@ en: email: "E-mail" gender: "Gender" age: "Age" + address: "Address" phone: "Phone" + website: "Website" + job: "Job" + interests: "Interests" + cad_software_mastered: "CAD Softwares mastered" group: "Group" subscription: "Subscription" subscription_end_date: "Subscription end date" validated_trainings: "Validated trainings" + tags: "Tags" + number_of_invoices: "Number of invoices" + projects: "Projects" + facebook: "Facebook" + twitter: "Twitter" + echo_sciences: "Echosciences" man: "Man" woman: "Woman" without_subscriptions: "Without subscriptions" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d4d1d01fd..0e24a374c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -120,11 +120,22 @@ fr: email: "Courriel" gender: "Genre" age: "Âge" + address: "Adresse" phone: "Tel." + website: "Site web" + job: "Profession" + interests: "Centres d'intérêts" + cad_software_mastered: "Logiciels de conception maîtrisés" group: "Groupe" subscription: "Abonnement" subscription_end_date: "Date de fin de l'abonnement" validated_trainings: "Formations validées" + tags: "Étiquettes" + number_of_invoices: "Nombre de factures" + projects: "Projets" + facebook: "Facebook" + twitter: "Twitter" + echo_sciences: "Echosciences" man: "Homme" woman: "Femme" without_subscriptions: "Sans Abonnement" From cbeb78fd7ebeff56a70a0d3c2524de1ccf7d30f4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 13 Jul 2016 09:20:52 +0200 Subject: [PATCH 21/24] fix members full export --- .../api/members/export_members.xlsx.axlsx | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/app/views/api/members/export_members.xlsx.axlsx b/app/views/api/members/export_members.xlsx.axlsx index 2ce8740ca..6d3fe0060 100644 --- a/app/views/api/members/export_members.xlsx.axlsx +++ b/app/views/api/members/export_members.xlsx.axlsx @@ -19,46 +19,34 @@ wb.add_worksheet(name: t('export_members.members')) do |sheet| # data rows @members.each do |member| data = [ - member.id, member.profile.last_name, member.profile.first_name, member.email, - member.profile.gender ? t('export_members.man') : t('export_members.woman'), - member.profile.age, - member.profile.address ? member.profile.address.address : '', - member.profile.phone, member.profile.website, member.profile.job, - member.profile.interest, member.profile.software_mastered, - member.group.name, + member.id, member.profile.last_name, member.profile.first_name, + member.email, member.profile.gender ? t('export_members.man') : t('export_members.woman'), member.profile.age, + member.profile.address ? member.profile.address.address : '', member.profile.phone, member.profile.website, + member.profile.job, member.profile.interest, member.profile.software_mastered, member.group.name, (member.subscription and member.subscription.expired_at > Time.now) ? member.subscription.plan.name : t('export_members.without_subscriptions'), (member.subscription and member.subscription.expired_at > Time.now) ? member.subscription.expired_at.to_date : nil, - member.trainings.map(&:name).join("\n"), member.tags.map(&:name).join("\n"), - member.invoices.size, - member.projects.map(&:name).join("\n"), - member.profile.facebook || '', member.profile.twitter || '', member.profile.echosciences || '' + member.trainings.map(&:name).join("\n"), member.tags.map(&:name).join("\n"), member.invoices.size, + member.projects.map(&:name).join("\n"), member.profile.facebook || '', member.profile.twitter || '', + member.profile.echosciences || '' ] styles = [nil, nil, nil, - nil, - nil, - nil, nil, nil, nil, - nil, nil, - nil, + nil, nil, nil, + nil, nil, nil, nil, nil, date, - nil, nil, - nil, - nil, - nil, nil, nil] + nil, nil, nil, + nil, nil, nil, + nil] types = [:integer, :string, :string, - :string, - :string, - :integer, + :string, :string, :integer, :string, :string, :string, - :string, :string, - :string, + :string, :string, :string, :string, :string, :date, - :string, :string, - :integer, - :string, - :string, :string, :string] + :string, :string, :integer, + :string, :string, :string, + :string] sheet.add_row data, :style => styles, :types => types end From ad19bfbd794c61c902697c2235718de55485c42e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 13 Jul 2016 10:19:43 +0200 Subject: [PATCH 22/24] fix custom filter CA=0 --- app/assets/javascripts/app.js.erb | 4 ++++ .../javascripts/controllers/admin/statistics.coffee.erb | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app.js.erb b/app/assets/javascripts/app.js.erb index ae0ab8122..f25bfa3e6 100644 --- a/app/assets/javascripts/app.js.erb +++ b/app/assets/javascripts/app.js.erb @@ -128,3 +128,7 @@ config(['$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfig }]).constant('angularMomentConfig', { timezone: Fablab.timezone }); + +angular.isUndefinedOrNull = function(val) { + return angular.isUndefined(val) || val === null +}; diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee.erb b/app/assets/javascripts/controllers/admin/statistics.coffee.erb index 046d0ee62..f1ad1a3ff 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee.erb +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -515,7 +515,9 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", ## buildCustomFilterQuery = -> custom = null - if $scope.customFilter.criterion and $scope.customFilter.criterion.key and $scope.customFilter.value + if !angular.isUndefinedOrNull($scope.customFilter.criterion) and + !angular.isUndefinedOrNull($scope.customFilter.criterion.key) and + !angular.isUndefinedOrNull($scope.customFilter.value) custom = {} custom.key = $scope.customFilter.criterion.key custom.value = $scope.customFilter.value From c863975e9c6f0152442f107acfe11ee68a6b4f49 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 13 Jul 2016 11:12:16 +0200 Subject: [PATCH 23/24] statistics tables: sort by date --- app/assets/javascripts/controllers/admin/statistics.coffee.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/controllers/admin/statistics.coffee.erb b/app/assets/javascripts/controllers/admin/statistics.coffee.erb index f1ad1a3ff..d5fdf0a1b 100644 --- a/app/assets/javascripts/controllers/admin/statistics.coffee.erb +++ b/app/assets/javascripts/controllers/admin/statistics.coffee.erb @@ -58,6 +58,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", ## default: results are not sorted $scope.sorting = ca: 'none' + date: 'desc' ## active tab will be set here $scope.selectedIndex = null @@ -150,6 +151,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state", $scope.customFilter.value = null $scope.customFilter.exclude = false $scope.sorting.ca = 'none' + $scope.sorting.date = 'desc' buildCustomFiltersList() refreshStats() From e765267bb5d40af8998ea1257728e990171ca032 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 13 Jul 2016 18:09:32 +0200 Subject: [PATCH 24/24] fix export for missing users --- app/views/api/statistics/export_current.xlsx.axlsx | 6 +++--- app/views/api/statistics/export_global.xlsx.axlsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/api/statistics/export_current.xlsx.axlsx b/app/views/api/statistics/export_current.xlsx.axlsx index 81fbdf2bb..76c676854 100644 --- a/app/views/api/statistics/export_current.xlsx.axlsx +++ b/app/views/api/statistics/export_current.xlsx.axlsx @@ -32,9 +32,9 @@ wb.add_worksheet(name: @index.label) do |sheet| subtype = get_item(@subtypes, hit['_source']['subType'], 'key') data = [ Date::strptime(hit['_source']['date'],'%Y-%m-%d'), - user.profile.full_name, - user.email, - user.profile.phone, + (user ? user.profile.full_name : "ID #{hit['_source']['userId']}"), + (user ? user.email : ''), + (user ? user.profile.phone : ''), t("export.#{hit['_source']['gender']}"), hit['_source']['age'], subtype.nil? ? "" : subtype.label diff --git a/app/views/api/statistics/export_global.xlsx.axlsx b/app/views/api/statistics/export_global.xlsx.axlsx index 572acb8b7..dd00a277f 100644 --- a/app/views/api/statistics/export_global.xlsx.axlsx +++ b/app/views/api/statistics/export_global.xlsx.axlsx @@ -30,9 +30,9 @@ date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_ # start to fill data and associated styles and data-types data = [ Date::strptime(hit['_source']['date'],'%Y-%m-%d'), - user.profile.full_name, - user.email, - user.profile.phone, + (user ? user.profile.full_name : "ID #{hit['_source']['userId']}"), + (user ? user.email : ''), + (user ? user.profile.phone : ''), t("export.#{hit['_source']['gender']}"), hit['_source']['age'], subtype.nil? ? "" : subtype.label