1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-11 22:24:21 +01:00
fab-manager/app/assets/javascripts/controllers/admin/statistics.js.erb

788 lines
26 KiB
Plaintext
Raw Normal View History

/* eslint-disable
no-constant-condition,
no-return-assign,
no-undef,
standard/no-callback-literal,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
2018-11-21 11:08:53 +01:00
'use strict';
2016-03-23 18:39:41 +01:00
2020-02-25 11:06:03 +01:00
Application.Controllers.controller('StatisticsController', ['$scope', '$state', '$rootScope', '$uibModal', 'es', 'Member', '_t', 'membersPromise', 'statisticsPromise', 'uiTourService',
function ($scope, $state, $rootScope, $uibModal, es, Member, _t, membersPromise, statisticsPromise, uiTourService) {
/* PRIVATE STATIC CONSTANTS */
2016-06-20 17:13:39 +02:00
2018-11-19 16:17:49 +01:00
// search window size
2018-11-21 11:08:53 +01:00
const RESULTS_PER_PAGE = 20;
2016-06-20 17:13:39 +02:00
2018-11-19 16:17:49 +01:00
// keep search context for (delay in minutes) ...
2018-11-21 11:08:53 +01:00
const ES_SCROLL_TIME = 1;
2016-06-20 17:13:39 +02:00
/* PUBLIC SCOPE */
2016-06-20 17:13:39 +02:00
2018-11-19 16:17:49 +01:00
// ui-view transitions optimization: if true, the stats will never be refreshed
2018-11-21 11:08:53 +01:00
$scope.preventRefresh = false;
2016-06-20 17:13:39 +02:00
2018-11-19 16:17:49 +01:00
// statistics structure in elasticSearch
2018-11-21 11:08:53 +01:00
$scope.statistics = statisticsPromise;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// fablab users list
2018-11-21 11:08:53 +01:00
$scope.members = membersPromise;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// statistics data recovered from elasticSearch
2018-11-21 11:08:53 +01:00
$scope.data = null;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// when did the search was triggered
2018-11-21 11:08:53 +01:00
$scope.searchDate = null;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// id of the elastic search context
2018-11-21 11:08:53 +01:00
$scope.scrollId = null;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// total number of results for the current query
2018-11-21 11:08:53 +01:00
$scope.totalHits = null;
2016-06-20 17:13:39 +02:00
2018-11-19 16:17:49 +01:00
// configuration of the widget allowing to pick the ages range
$scope.agePicker = {
show: false,
start: null,
end: null
2018-11-21 11:08:53 +01:00
};
2016-06-20 17:13:39 +02:00
2018-11-19 16:17:49 +01:00
// total CA for the current view
2018-11-21 11:08:53 +01:00
$scope.sumCA = 0;
2016-06-20 17:13:39 +02:00
2018-11-19 16:17:49 +01:00
// average users' age for the current view
2018-11-21 11:08:53 +01:00
$scope.averageAge = 0;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// total of the stat field for non simple types
2018-11-21 11:08:53 +01:00
$scope.sumStat = 0;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// Results of custom aggregations for the current type
2018-11-21 11:08:53 +01:00
$scope.customAggs = {};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// default: results are not sorted
$scope.sorting = {
ca: 'none',
date: 'desc'
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
// active tab will be set here
2018-11-21 11:08:53 +01:00
$scope.selectedIndex = null;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// type filter binding
$scope.type = {
selected: null,
active: null
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
// selected custom filter
$scope.customFilter = {
show: false,
criterion: {},
value: null,
exclude: false,
datePicker: {
format: Fablab.uibDateFormat,
opened: false, // default: datePicker is not shown
minDate: null,
maxDate: moment().toDate(),
options: {
startingDay: Fablab.weekStartingDay
}
}
2018-11-21 11:08:53 +01:00
};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// available custom filters
2018-11-21 11:08:53 +01:00
$scope.filters = [];
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// default: we do not open the datepicker menu
$scope.datePicker =
2018-11-21 11:08:53 +01:00
{ show: false };
2018-11-19 16:17:49 +01:00
// datePicker parameters for interval beginning
$scope.datePickerStart = {
format: Fablab.uibDateFormat,
opened: false, // default: datePicker is not shown
minDate: null,
maxDate: moment().subtract(1, 'day').toDate(),
selected: moment().utc().subtract(1, 'months').subtract(1, 'day').startOf('day').toDate(),
options: {
startingDay: Fablab.weekStartingDay
}
2018-11-21 11:08:53 +01:00
};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// datePicker parameters for interval ending
$scope.datePickerEnd = {
format: Fablab.uibDateFormat,
opened: false, // default: datePicker is not shown
minDate: null,
maxDate: moment().subtract(1, 'day').toDate(),
selected: moment().subtract(1, 'day').endOf('day').toDate(),
options: {
startingDay: Fablab.weekStartingDay
}
2018-11-21 11:08:53 +01:00
};
2016-03-23 18:39:41 +01:00
2019-12-17 18:06:56 +01:00
/**
* Return a localized name for the given field
*/
$scope.customFieldName = function (field) {
return _t(`app.admin.statistics.${field}`);
}
2018-11-19 16:17:49 +01:00
/**
* Callback to open the datepicker (interval start)
* @param $event {Object} jQuery event object
*/
2018-11-21 11:08:53 +01:00
$scope.toggleStartDatePicker = function ($event) { toggleDatePicker($event, $scope.datePickerStart); };
2018-11-19 16:17:49 +01:00
/**
* Callback to open the datepicker (interval end)
* @param $event {Object} jQuery event object
*/
2018-11-21 11:08:53 +01:00
$scope.toggleEndDatePicker = function ($event) { toggleDatePicker($event, $scope.datePickerEnd); };
2018-11-19 16:17:49 +01:00
/**
* Callback to open the datepicker (custom filter)
* @param $event {Object} jQuery event object
*/
2018-11-21 11:08:53 +01:00
$scope.toggleCustomDatePicker = function ($event) { toggleDatePicker($event, $scope.customFilter.datePicker); };
2018-11-19 16:17:49 +01:00
/**
* Callback called when the active tab is changed.
* recover the current tab and store its value in $scope.selectedIndex
* @param tab {Object} elasticsearch statistic structure (from statistic_indices table)
*/
$scope.setActiveTab = function (tab) {
2018-11-21 11:08:53 +01:00
$scope.selectedIndex = tab;
$scope.type.selected = tab.types[0];
$scope.type.active = $scope.type.selected;
$scope.customFilter.criterion = {};
$scope.customFilter.value = null;
$scope.customFilter.exclude = false;
$scope.sorting.ca = 'none';
$scope.sorting.date = 'desc';
buildCustomFiltersList();
return refreshStats();
};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
/**
* Returns true if the provided tab must be hidden due to some global or local configuration
* @param tab {Object} elasticsearch statistic structure (from statistic_indices table)
*/
$scope.hiddenTab = function (tab) {
if (tab.table) {
if ((tab.es_type_key === 'subscription') && !$rootScope.modules.plans) {
2018-11-21 11:08:53 +01:00
return true;
} else return (tab.es_type_key === 'space') && !$rootScope.modules.spaces;
} else {
2018-11-21 11:08:53 +01:00
return true;
}
2018-11-21 11:08:53 +01:00
};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
/**
* Callback to validate the filters and send a new request to elastic
*/
$scope.validateFilterChange = function () {
2018-11-21 11:08:53 +01:00
$scope.agePicker.show = false;
$scope.customFilter.show = false;
$scope.type.active = $scope.type.selected;
buildCustomFiltersList();
return refreshStats();
};
2018-11-19 16:17:49 +01:00
/**
* Callback to validate the dates range and refresh the data from elastic
*/
$scope.validateDateChange = function () {
2018-11-21 11:08:53 +01:00
$scope.datePicker.show = false;
return refreshStats();
};
2018-11-19 16:17:49 +01:00
/**
* Parse the given date and return a user-friendly string
* @param date {Date} JS date or ant moment.js compatible date string
*/
2018-11-21 11:08:53 +01:00
$scope.formatDate = function (date) { return moment(date).format('LL'); };
2018-11-19 16:17:49 +01:00
/**
* Parse the sex and return a user-friendly string
* @param sex {string} 'male' | 'female'
*/
$scope.formatSex = function (sex) {
if (sex === 'male') {
2019-12-17 18:06:56 +01:00
return _t('app.admin.statistics.man');
}
if (sex === 'female') {
2019-12-17 18:06:56 +01:00
return _t('app.admin.statistics.woman');
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Retrieve the label for the given subtype in the current type
* @param key {string} statistic subtype key
*/
$scope.formatSubtype = function (key) {
2018-11-21 11:08:53 +01:00
let label = '';
angular.forEach($scope.type.active.subtypes, function (subtype) {
if (subtype.key === key) {
2018-11-21 11:08:53 +01:00
return label = subtype.label;
}
2018-11-21 11:08:53 +01:00
});
return label;
};
2018-11-19 16:17:49 +01:00
/**
* Helper usable in ng-switch to determine the input type to display for custom filter value
* @param filter {Object} custom filter criterion
*/
$scope.getCustomValueInputType = function (filter) {
if (filter && filter.values) {
if (typeof (filter.values[0]) === 'string') {
2018-11-21 11:08:53 +01:00
return filter.values[0];
} else if (typeof (filter.values[0] === 'object')) {
2018-11-21 11:08:53 +01:00
return 'input_select';
}
} else {
2018-11-21 11:08:53 +01:00
return 'input_text';
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Change the sorting order and refresh the results to match the new order
* @param filter {Object} any filter
*/
$scope.toggleSorting = function (filter) {
switch ($scope.sorting[filter]) {
2018-11-21 11:08:53 +01:00
case 'none': $scope.sorting[filter] = 'asc'; break;
case 'asc': $scope.sorting[filter] = 'desc'; break;
case 'desc': $scope.sorting[filter] = 'none'; break;
}
2018-11-21 11:08:53 +01:00
return refreshStats();
};
2018-11-19 16:17:49 +01:00
/**
* Return the user's name from his given ID
* @param id {number} user ID
*/
$scope.getUserNameFromId = function (id) {
2018-11-21 11:08:53 +01:00
const name = $scope.members[id];
return (name || `ID ${id}`);
};
2018-11-19 16:17:49 +01:00
/**
* Run a scroll query to elasticsearch to append the next packet of results to those displayed.
* If the ES search context has expired when the user ask for more results, we re-run the whole query.
*/
$scope.showMoreResults = function () {
// if all results were retrieved, do nothing
if ($scope.data.length >= $scope.totalHits) {
2018-11-21 11:08:53 +01:00
return;
}
if (moment($scope.searchDate).add(ES_SCROLL_TIME, 'minutes').isBefore(moment())) {
// elastic search context has expired, so we run again the whole query
2018-11-21 11:08:53 +01:00
return refreshStats();
} else {
return es.scroll({
'scroll': ES_SCROLL_TIME + 'm',
'body': { scrollId: $scope.scrollId }
}
, function (error, response) {
if (error) {
2018-11-21 11:08:53 +01:00
return console.error(`Error: something unexpected occurred during elasticSearch scroll query: ${error}`);
} else {
2018-11-21 11:08:53 +01:00
$scope.scrollId = response._scroll_id;
return $scope.data = $scope.data.concat(response.hits.hits);
}
2018-11-21 11:08:53 +01:00
});
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Open a modal dialog asking the user for details about exporting the statistics tables to an excel file
*/
$scope.exportToExcel = function () {
const options = {
templateUrl: '<%= asset_path "admin/statistics/export.html" %>',
size: 'sm',
controller: 'ExportStatisticsController',
resolve: {
dates () {
return {
start: $scope.datePickerStart.selected,
end: $scope.datePickerEnd.selected
2018-11-21 11:08:53 +01:00
};
},
query () {
2018-11-21 11:08:53 +01:00
const custom = buildCustomFilterQuery();
return buildElasticDataQuery($scope.type.active.key, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting);
},
index () {
2018-11-21 11:08:53 +01:00
return { key: $scope.selectedIndex.es_type_key };
},
type () {
2018-11-21 11:08:53 +01:00
return { key: $scope.type.active.key };
}
}
2018-11-21 11:08:53 +01:00
};
return $uibModal.open(options)
2018-11-21 11:08:53 +01:00
.result['finally'](null).then(function (info) { console.log(info); });
};
2020-02-25 11:06:03 +01:00
/**
* Setup the feature-tour for the admin/statistics page.
* This is intended as a contextual help (when pressing F1)
*/
$scope.setupStatisticsTour = function () {
// get the tour defined by the ui-tour directive
const uitour = uiTourService.getTourByName('statistics');
uitour.createStep({
selector: 'body',
stepId: 'welcome',
order: 0,
title: _t('app.admin.tour.statistics.welcome.title'),
content: _t('app.admin.tour.statistics.welcome.content'),
placement: 'bottom',
orphan: true
});
uitour.createStep({
selector: '.heading .export-button',
stepId: 'export',
order: 1,
title: _t('app.admin.tour.statistics.export.title'),
content: _t('app.admin.tour.statistics.export.content'),
placement: 'bottom'
});
uitour.createStep({
selector: '.heading .charts-button',
stepId: 'trending',
order: 2,
title: _t('app.admin.tour.statistics.trending.title'),
content: _t('app.admin.tour.statistics.trending.content'),
placement: 'left'
2020-02-25 11:06:03 +01:00
});
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
order: 3,
title: _t('app.admin.tour.conclusion.title'),
content: _t('app.admin.tour.conclusion.content'),
placement: 'bottom',
orphan: true
});
// on tour end, save the status in database
uitour.on('ended', function () {
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('statistics') < 0) {
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'statistics' }, function (res) {
$scope.currentUser.profile.tours = res.tours;
});
}
});
// if the user has never seen the tour, show him now
2020-03-02 11:17:28 +01:00
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('statistics') < 0) {
2020-02-25 11:06:03 +01:00
uitour.start();
}
}
/* PRIVATE SCOPE */
2018-11-19 16:17:49 +01:00
/**
* Kind of constructor: these actions will be realized first when the controller is loaded
*/
2018-11-20 12:26:06 +01:00
const initialize = function () {
// workaround for angular-bootstrap::tabs behavior: on tab deletion, another tab will be selected
// which will cause every tabs to reload, one by one, when the view is closed
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
if ((fromState.name === 'app.admin.statistics') && (Object.keys(fromParams).length === 0)) {
2018-11-21 11:08:53 +01:00
return $scope.preventRefresh = true;
}
2018-11-21 11:08:53 +01:00
});
};
2018-11-19 16:17:49 +01:00
/**
* Generic function to toggle a bootstrap datePicker
* @param $event {Object} jQuery event object
* @param datePicker {Object} settings object of the concerned datepicker. Must have an 'opened' property
*/
2020-02-25 11:06:03 +01:00
const toggleDatePicker = function ($event, datePicker) {
2018-11-21 11:08:53 +01:00
$event.preventDefault();
$event.stopPropagation();
return datePicker.opened = !datePicker.opened;
};
2018-11-19 16:17:49 +01:00
/**
* Force update the statistics table, querying elasticSearch according to the current config values
*/
2020-02-25 11:06:03 +01:00
const refreshStats = function () {
if ($scope.selectedIndex && !$scope.preventRefresh && $scope.type.active) {
2018-11-21 11:08:53 +01:00
$scope.data = [];
$scope.sumCA = 0;
$scope.averageAge = 0;
$scope.sumStat = 0;
$scope.customAggs = {};
$scope.totalHits = null;
$scope.searchDate = new Date();
let custom = buildCustomFilterQuery();
return queryElasticStats($scope.selectedIndex.es_type_key, $scope.type.active.key, custom, function (res, err) {
if (err) {
2018-11-21 11:08:53 +01:00
return console.error(`[statisticsController::refreshStats] Unable to refresh due to ${err}`);
} else {
2018-11-21 11:08:53 +01:00
$scope.data = res.hits.hits;
$scope.totalHits = res.hits.total;
$scope.sumCA = res.aggregations.total_ca.value;
$scope.averageAge = Math.round(res.aggregations.average_age.value * 100) / 100;
$scope.sumStat = res.aggregations.total_stat.value;
$scope.scrollId = res._scroll_id;
2018-11-20 12:26:06 +01:00
return (function () {
2018-11-21 11:08:53 +01:00
const result = [];
for (custom of Array.from($scope.type.active.custom_aggregations)) {
2018-11-21 11:08:53 +01:00
result.push($scope.customAggs[custom.field] = res.aggregations[custom.field].value);
}
2018-11-21 11:08:53 +01:00
return result;
})();
}
2018-11-21 11:08:53 +01:00
});
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
2019-12-17 18:06:56 +01:00
* Run the elasticSearch query to retrieve the /stats/type aggregations
2018-11-19 16:17:49 +01:00
* @param index {String} elasticSearch document type (account|event|machine|project|subscription|training)
* @param type {String} statistics type (month|year|booking|hour|user|project)
* @param custom {{key:{string}, value:{string}}|null} custom filter property or null to disable this filter
* @param callback {function} function be to run after results were retrieved, it will receive
* two parameters : results {Object}, error {String} (if any)
*/
2020-02-25 11:06:03 +01:00
const queryElasticStats = function (index, type, custom, callback) {
// handle invalid callback
if (typeof (callback) !== 'function') {
2018-11-21 11:08:53 +01:00
console.error('[statisticsController::queryElasticStats] Error: invalid callback provided');
return;
}
// run query
return es.search({
'index': 'stats',
'type': index,
'size': RESULTS_PER_PAGE,
'scroll': ES_SCROLL_TIME + 'm',
'stat-type': type,
'custom-query': custom ? JSON.stringify(Object.assign({ exclude: custom.exclude }, buildElasticCustomCriterion(custom))) : '',
'start-date': moment($scope.datePickerStart.selected).format(),
'end-date': moment($scope.datePickerEnd.selected).format(),
'body': buildElasticDataQuery(type, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting)
}
, function (error, response) {
if (error) {
2018-11-21 11:08:53 +01:00
return callback({}, `Error: something unexpected occurred during elasticSearch query: ${error}`);
} else {
2018-11-21 11:08:53 +01:00
return callback(response);
}
2018-11-21 11:08:53 +01:00
});
};
2018-11-19 16:17:49 +01:00
/**
* Build an object representing the content of the REST-JSON query to elasticSearch,
* based on the provided parameters for row data recovering.
* @param type {String} statistics type (month|year|booking|hour|user|project)
* @param custom {{key:{string}, value:{string}}|null} custom filter property or null to disable this filter
* @param ageMin {Number|null} filter by age: range lower value OR null to do not filter
* @param ageMax {Number|null} filter by age: range higher value OR null to do not filter
* @param intervalBegin {moment} statitics interval beginning (moment.js type)
* @param intervalEnd {moment} statitics interval ending (moment.js type)
* @param sortings {Array|null} elasticSearch criteria for sorting the results
*/
2020-02-25 11:06:03 +01:00
const buildElasticDataQuery = function (type, custom, ageMin, ageMax, intervalBegin, intervalEnd, sortings) {
const q = {
'query': {
'bool': {
'must': [
{
'term': {
'type': type
}
},
{
'range': {
'date': {
'gte': intervalBegin.format(),
'lte': intervalEnd.format()
}
}
}
]
}
}
2018-11-21 11:08:53 +01:00
};
// optional date range
if ((typeof ageMin === 'number') && (typeof ageMax === 'number')) {
q.query.bool.must.push({
'range': {
'age': {
'gte': ageMin,
'lte': ageMax
}
}
2018-11-21 11:08:53 +01:00
});
}
// optional criterion
if (custom) {
2018-11-21 11:08:53 +01:00
const criterion = buildElasticCustomCriterion(custom);
if (custom.exclude) {
q.query.bool.must_not = [
{ 'term': criterion.match }
2018-11-21 11:08:53 +01:00
];
} else {
2018-11-21 11:08:53 +01:00
q.query.bool.must.push(criterion);
}
}
2016-03-23 18:39:41 +01:00
if (sortings) {
2018-11-21 11:08:53 +01:00
q['sort'] = buildElasticSortCriteria(sortings);
}
2016-03-23 18:39:41 +01:00
// aggregations (avg age & CA sum)
q['aggs'] = {
'total_ca': {
'sum': {
'field': 'ca'
}
},
'average_age': {
'avg': {
'field': 'age'
}
},
'total_stat': {
'sum': {
'field': 'stat'
}
}
2018-11-21 11:08:53 +01:00
};
return q;
};
2016-06-20 17:13:39 +02:00
2018-11-19 16:17:49 +01:00
/**
* Build the elasticSearch query DSL to match the selected cutom filter
* @param custom {Object} if custom is empty or undefined, an empty string will be returned
2018-11-20 12:26:06 +01:00
* @returns {{match:*}|string}
2018-11-19 16:17:49 +01:00
*/
2020-02-25 11:06:03 +01:00
const buildElasticCustomCriterion = function (custom) {
if (custom) {
const criterion = {
'match': {}
2018-11-21 11:08:53 +01:00
};
switch ($scope.getCustomValueInputType($scope.customFilter.criterion)) {
2018-11-21 11:08:53 +01:00
case 'input_date': criterion.match[custom.key] = moment(custom.value).format('YYYY-MM-DD'); break;
case 'input_select': criterion.match[custom.key] = custom.value.key; break;
case 'input_list': criterion.match[custom.key + '.name'] = custom.value; break;
default: criterion.match[custom.key] = custom.value;
}
2018-11-21 11:08:53 +01:00
return criterion;
} else {
2018-11-21 11:08:53 +01:00
return '';
}
2018-11-21 11:08:53 +01:00
};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
/**
* Parse the provided criteria array and return the corresponding elasticSearch syntax
* @param criteria {Array} array of {key_to_sort:order}
*/
2020-02-25 11:06:03 +01:00
const buildElasticSortCriteria = function (criteria) {
2018-11-21 11:08:53 +01:00
const crits = [];
angular.forEach(criteria, function (value, key) {
if ((typeof value !== 'undefined') && (value !== null) && (value !== 'none')) {
2018-11-21 11:08:53 +01:00
const c = {};
c[key] = { 'order': value };
return crits.push(c);
}
2018-11-21 11:08:53 +01:00
});
return crits;
};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
/**
2019-12-17 18:06:56 +01:00
* Fulfill the list of available options in the custom filter panel. The list will be based on common
2018-11-19 16:17:49 +01:00
* properties and on index-specific properties (additional_fields)
*/
2020-02-25 11:06:03 +01:00
const buildCustomFiltersList = function () {
2018-11-20 12:26:06 +01:00
$scope.filters = [
2019-12-17 18:06:56 +01:00
{ key: 'date', label: _t('app.admin.statistics.date'), values: ['input_date'] },
{ key: 'userId', label: _t('app.admin.statistics.user_id'), values: ['input_number'] },
{ key: 'gender', label: _t('app.admin.statistics.gender'), values: [{ key: 'male', label: _t('app.admin.statistics.man') }, { key: 'female', label: _t('app.admin.statistics.woman') }] },
{ key: 'age', label: _t('app.admin.statistics.age'), values: ['input_number'] },
{ key: 'ca', label: _t('app.admin.statistics.revenue'), values: ['input_number'] }
2018-11-21 11:08:53 +01:00
];
// if no plans were created, there's no types for statisticIndex=subscriptions
if ($scope.type.active) {
$scope.filters.splice(4, 0, { key: 'subType', label: _t('app.admin.statistics.type'), values: $scope.type.active.subtypes })
if (!$scope.type.active.simple) {
const f = { key: 'stat', label: $scope.type.active.label, values: ['input_number'] };
$scope.filters.push(f);
}
}
2016-03-23 18:39:41 +01:00
return angular.forEach($scope.selectedIndex.additional_fields, function (field) {
2018-11-21 11:08:53 +01:00
const filter = { key: field.key, label: field.label, values: [] };
switch (field.data_type) {
2018-11-21 11:08:53 +01:00
case 'index': filter.values.push('input_number'); break;
case 'number': filter.values.push('input_number'); break;
case 'date': filter.values.push('input_date'); break;
case 'list': filter.values.push('input_list'); break;
default: filter.values.push('input_text');
}
2018-11-21 11:08:53 +01:00
return $scope.filters.push(filter);
});
};
2018-11-19 16:17:49 +01:00
/**
* Build and return an object according to the custom filter set by the user, used to request elasticsearch
* @return {Object|null}
*/
2020-02-25 11:06:03 +01:00
const buildCustomFilterQuery = function () {
2018-11-21 11:08:53 +01:00
let custom = null;
if (!angular.isUndefinedOrNull($scope.customFilter.criterion) &&
!angular.isUndefinedOrNull($scope.customFilter.criterion.key) &&
!angular.isUndefinedOrNull($scope.customFilter.value)) {
2018-11-21 11:08:53 +01:00
custom = {};
custom.key = $scope.customFilter.criterion.key;
custom.value = $scope.customFilter.value;
custom.exclude = $scope.customFilter.exclude;
}
2018-11-21 11:08:53 +01:00
return custom;
};
// init the controller (call at the end !)
2018-11-21 11:08:53 +01:00
return initialize();
}
2016-03-23 18:39:41 +01:00
2018-11-21 11:08:53 +01:00
]);
2016-03-23 18:39:41 +01:00
Application.Controllers.controller('ExportStatisticsController', [ '$scope', '$uibModalInstance', 'Export', 'dates', 'query', 'index', 'type', 'CSRF', 'growl', '_t',
function ($scope, $uibModalInstance, Export, dates, query, index, type, CSRF, growl, _t) {
2018-11-19 16:17:49 +01:00
// Retrieve Anti-CSRF tokens from cookies
2018-11-21 11:08:53 +01:00
CSRF.setMetaTags();
2018-11-19 16:17:49 +01:00
// Bindings for date range
2018-11-21 11:08:53 +01:00
$scope.dates = dates;
2018-11-19 16:17:49 +01:00
// Body of the query to export
2018-11-21 11:08:53 +01:00
$scope.query = JSON.stringify(query);
2018-11-19 16:17:49 +01:00
// API URL where the form will be posted
2018-11-21 11:08:53 +01:00
$scope.actionUrl = `/stats/${index.key}/export`;
2018-11-19 16:17:49 +01:00
// Key of the current search' statistic type
2018-11-21 11:08:53 +01:00
$scope.typeKey = type.key;
2018-11-19 16:17:49 +01:00
// Form action on the above URL
2018-11-21 11:08:53 +01:00
$scope.method = 'post';
2018-11-19 16:17:49 +01:00
// Anti-CSRF token to inject into the download form
2018-11-21 11:08:53 +01:00
$scope.csrfToken = angular.element('meta[name="csrf-token"]')[0].content;
2018-11-19 16:17:49 +01:00
// Binding of the export type (global / current)
$scope.export =
2018-11-21 11:08:53 +01:00
{ type: 'current' };
2016-07-07 16:57:23 +02:00
2018-11-19 16:17:49 +01:00
// 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
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
// 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
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Callback to open the datepicker (interval start)
* @param $event {Object} jQuery event object
*/
2018-11-21 11:08:53 +01:00
$scope.toggleStartDatePicker = function ($event) { $scope.exportStart.opened = !$scope.exportStart.opened; };
2018-11-19 16:17:49 +01:00
/**
* Callback to open the datepicker (interval end)
* @param $event {Object} jQuery event object
*/
2018-11-21 11:08:53 +01:00
$scope.toggleEndDatePicker = function ($event) { $scope.exportEnd.opened = !$scope.exportEnd.opened; };
2018-11-19 16:17:49 +01:00
/**
* Callback when exchanging the export type between 'global' and 'current view'
* Adjust the query and the requesting url according to this type.
*/
$scope.setRequest = function () {
if ($scope.export.type === 'global') {
2018-11-21 11:08:53 +01:00
$scope.actionUrl = '/stats/global/export';
return $scope.query = JSON.stringify({
'query': {
'bool': {
'must': [
{
'range': {
'date': {
'gte': moment($scope.dates.start).format(),
'lte': moment($scope.dates.end).format()
}
}
}
]
}
}
2018-11-21 11:08:53 +01:00
});
} else {
2018-11-21 11:08:53 +01:00
$scope.actionUrl = `/stats/${index.key}/export`;
$scope.query = JSON.stringify(query);
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Callback to close the modal, telling the caller what is exported
*/
$scope.exportData = function () {
2018-11-21 11:08:53 +01:00
const statusQry = { category: 'statistics', type: $scope.export.type, query: $scope.query };
if ($scope.export.type !== 'global') {
2018-11-21 11:08:53 +01:00
statusQry['type'] = index.key;
statusQry['key'] = type.key;
}
Export.status(statusQry).then(function (res) {
if (!res.data.exists) {
2019-12-17 18:06:56 +01:00
return growl.success(_t('app.admin.statistics.export_is_running_you_ll_be_notified_when_its_ready'));
}
2018-11-21 11:08:53 +01:00
});
2018-11-21 11:08:53 +01:00
return $uibModalInstance.close(statusQry);
};
2018-11-19 16:17:49 +01:00
/**
* Callback to cancel the export and close the modal
*/
2018-11-21 11:08:53 +01:00
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
}
2018-11-21 11:08:53 +01:00
]);