mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-21 15:54:22 +01:00
Statistics about hours available for machine reservations and tickets available for training reservations, now handle custom filtering on date and type
This commit is contained in:
parent
9edf723373
commit
8119f54e4c
@ -3,10 +3,12 @@
|
|||||||
## next release
|
## next release
|
||||||
- Mask new notifications alerts when more than 3
|
- Mask new notifications alerts when more than 3
|
||||||
- Added an asterisk on group select in admin's member form
|
- Added an asterisk on group select in admin's member form
|
||||||
|
- Statistics custom aggregations can handle custom filtering
|
||||||
|
- Statistics about hours available for machine reservations and tickets available for training reservations, now handle custom filtering on date and type
|
||||||
- Fix a bug: display more than 15 unread notifications (number on the bell icon & full list)
|
- Fix a bug: display more than 15 unread notifications (number on the bell icon & full list)
|
||||||
- Fix a bug: in invoice configuration panel, VAT amount and total excl. taxes are inverted
|
- Fix a bug: in invoice configuration panel, VAT amount and total excl. taxes are inverted
|
||||||
- Fix a bug: unable to compute user's age when they were born on february 29th and current year is not a leap year
|
- Fix a bug: unable to compute user's age when they were born on february 29th and current year is not a leap year
|
||||||
- Fix a bug: wrong statistics about hours available for reservation. Fix requires user action (1)
|
- Fix a bug: wrong statistics about hours available for machines reservation. Fix requires user action (1)
|
||||||
- [TODO DEPLOY] remove possible value `application/` in `ALLOWED_MIME_TYPES` list, in environment variable
|
- [TODO DEPLOY] remove possible value `application/` in `ALLOWED_MIME_TYPES` list, in environment variable
|
||||||
- [TODO DEPLOY] `rails runner StatisticCustomAggregation.destroy_all`, then `rake db:seed`, then `rake fablab:es_build_availabilities_index` (1)
|
- [TODO DEPLOY] `rails runner StatisticCustomAggregation.destroy_all`, then `rake db:seed`, then `rake fablab:es_build_availabilities_index` (1)
|
||||||
|
|
||||||
|
@ -363,6 +363,7 @@ Application.Controllers.controller "GraphsController", ["$scope", "$state", "$ro
|
|||||||
"type": esType
|
"type": esType
|
||||||
"searchType": "count"
|
"searchType": "count"
|
||||||
"stat-type": statType
|
"stat-type": statType
|
||||||
|
"custom-query": ''
|
||||||
"start-date": moment($scope.datePickerStart.selected).format()
|
"start-date": moment($scope.datePickerStart.selected).format()
|
||||||
"end-date": moment($scope.datePickerEnd.selected).format()
|
"end-date": moment($scope.datePickerEnd.selected).format()
|
||||||
"body": buildElasticAggregationsQuery(statType, $scope.display.interval, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected))
|
"body": buildElasticAggregationsQuery(statType, $scope.display.interval, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected))
|
||||||
|
@ -380,6 +380,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
|||||||
"size": RESULTS_PER_PAGE
|
"size": RESULTS_PER_PAGE
|
||||||
"scroll": ES_SCROLL_TIME+'m'
|
"scroll": ES_SCROLL_TIME+'m'
|
||||||
"stat-type": type
|
"stat-type": type
|
||||||
|
"custom-query": if custom then JSON.stringify(Object.assign({exclude: custom.exclude}, buildElasticCustomCriterion(custom))) else ''
|
||||||
"start-date": moment($scope.datePickerStart.selected).format()
|
"start-date": moment($scope.datePickerStart.selected).format()
|
||||||
"end-date": moment($scope.datePickerEnd.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)
|
"body": buildElasticDataQuery(type, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting)
|
||||||
@ -427,15 +428,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
|||||||
"lte": ageMax
|
"lte": ageMax
|
||||||
# optional criterion
|
# optional criterion
|
||||||
if custom
|
if custom
|
||||||
criterion = {
|
criterion = buildElasticCustomCriterion(custom)
|
||||||
"match" : {}
|
|
||||||
}
|
|
||||||
switch $scope.getCustomValueInputType($scope.customFilter.criterion)
|
|
||||||
when 'input_date' then criterion.match[custom.key] = moment(custom.value).format('YYYY-MM-DD')
|
|
||||||
when 'input_select' then criterion.match[custom.key] = custom.value.key
|
|
||||||
when 'input_list' then criterion.match[custom.key+".name"] = custom.value
|
|
||||||
else criterion.match[custom.key] = custom.value
|
|
||||||
|
|
||||||
if (custom.exclude)
|
if (custom.exclude)
|
||||||
q = "query": {
|
q = "query": {
|
||||||
"filtered": {
|
"filtered": {
|
||||||
@ -470,6 +463,27 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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
|
||||||
|
# @returns {{match:{*}}|string}
|
||||||
|
##
|
||||||
|
buildElasticCustomCriterion = (custom) ->
|
||||||
|
if (custom)
|
||||||
|
criterion = {
|
||||||
|
"match" : {}
|
||||||
|
}
|
||||||
|
switch $scope.getCustomValueInputType($scope.customFilter.criterion)
|
||||||
|
when 'input_date' then criterion.match[custom.key] = moment(custom.value).format('YYYY-MM-DD')
|
||||||
|
when 'input_select' then criterion.match[custom.key] = custom.value.key
|
||||||
|
when 'input_list' then criterion.match[custom.key+".name"] = custom.value
|
||||||
|
else criterion.match[custom.key] = custom.value
|
||||||
|
criterion
|
||||||
|
else
|
||||||
|
''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Parse the provided criteria array and return the corresponding elasticSearch syntax
|
# Parse the provided criteria array and return the corresponding elasticSearch syntax
|
||||||
# @param criteria {Array} array of {key_to_sort:order}
|
# @param criteria {Array} array of {key_to_sort:order}
|
||||||
|
@ -13,6 +13,7 @@ class API::StatisticsController < API::ApiController
|
|||||||
|
|
||||||
# remove additional parameters
|
# remove additional parameters
|
||||||
statistic_type = request.query_parameters.delete('stat-type')
|
statistic_type = request.query_parameters.delete('stat-type')
|
||||||
|
custom_query = request.query_parameters.delete('custom-query')
|
||||||
start_date = request.query_parameters.delete('start-date')
|
start_date = request.query_parameters.delete('start-date')
|
||||||
end_date = request.query_parameters.delete('end-date')
|
end_date = request.query_parameters.delete('end-date')
|
||||||
|
|
||||||
@ -21,15 +22,7 @@ class API::StatisticsController < API::ApiController
|
|||||||
results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response
|
results = Stats::#{path.classify}.search(query, request.query_parameters.symbolize_keys).response
|
||||||
|
|
||||||
# run additional custom aggregations, if any
|
# run additional custom aggregations, if any
|
||||||
if statistic_type and start_date and end_date
|
CustomAggregationService.new.("#{path}", statistic_type, start_date, end_date, custom_query, results)
|
||||||
stat_index = StatisticIndex.find_by(es_type_key: "#{path}")
|
|
||||||
stat_type = StatisticType.where(statistic_index_id: stat_index.id, key: statistic_type).first
|
|
||||||
client = Elasticsearch::Model.client
|
|
||||||
stat_type.statistic_custom_aggregations.each do |custom|
|
|
||||||
c_res = client.search index: custom.es_index, type:custom.es_type, body:sprintf(custom.query, {aggs_name: custom.field, start_date: start_date, end_date: end_date})
|
|
||||||
results['aggregations'][custom.field] = c_res['aggregations'][custom.field]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# return result
|
# return result
|
||||||
render json: results
|
render json: results
|
||||||
|
@ -38,6 +38,7 @@ class Availability < ActiveRecord::Base
|
|||||||
settings do
|
settings do
|
||||||
mappings dynamic: 'true' do
|
mappings dynamic: 'true' do
|
||||||
indexes 'available_type', analyzer: 'simple'
|
indexes 'available_type', analyzer: 'simple'
|
||||||
|
indexes 'subType', index: 'not_analyzed'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -97,8 +98,15 @@ class Availability < ActiveRecord::Base
|
|||||||
def as_indexed_json
|
def as_indexed_json
|
||||||
json = JSON.parse(to_json)
|
json = JSON.parse(to_json)
|
||||||
json['hours_duration'] = (end_at - start_at) / (60 * 60)
|
json['hours_duration'] = (end_at - start_at) / (60 * 60)
|
||||||
json['machines'] = machines_availabilities.map{|ma| ma.machine.friendly_id}
|
if available_type == 'machines'
|
||||||
json['bookable_hours'] = json['hours_duration'] * json['machines'].length
|
json['subType'] = machines_availabilities.map{|ma| ma.machine.friendly_id}
|
||||||
|
elsif available_type == 'training'
|
||||||
|
json['subType'] = trainings_availabilities.map{|ta| ta.training.friendly_id}
|
||||||
|
elsif available_type == 'event'
|
||||||
|
json['subType'] = [event.category.friendly_id]
|
||||||
|
end
|
||||||
|
json['bookable_hours'] = json['hours_duration'] * json['subType'].length
|
||||||
|
json['date'] = start_at.to_date
|
||||||
json.to_json
|
json.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
45
app/services/custom_aggregation_service.rb
Normal file
45
app/services/custom_aggregation_service.rb
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
require 'json'
|
||||||
|
|
||||||
|
class CustomAggregationService
|
||||||
|
|
||||||
|
##
|
||||||
|
# Run any additional custom aggregations related to the given statistic type, if any
|
||||||
|
##
|
||||||
|
def call(statistic_index, statistic_type, start_date, end_date, custom_query, results)
|
||||||
|
if statistic_type and start_date and end_date
|
||||||
|
stat_index = StatisticIndex.find_by(es_type_key: statistic_index)
|
||||||
|
stat_type = StatisticType.find_by(statistic_index_id: stat_index.id, key: statistic_type)
|
||||||
|
client = Elasticsearch::Model.client
|
||||||
|
stat_type.statistic_custom_aggregations.each do |custom|
|
||||||
|
|
||||||
|
query = sprintf(custom.query, {aggs_name: custom.field, start_date: start_date, end_date: end_date})
|
||||||
|
|
||||||
|
if custom_query and !custom_query.empty?
|
||||||
|
# Here, a custom query was provided with the original request (eg: filter by subtype)
|
||||||
|
# so we try to apply this custom filter to the current custom aggregation.
|
||||||
|
#
|
||||||
|
# The requested model mapping (ie. found in StatisticCustomAggregation.es_index > es_type) must have defined
|
||||||
|
# these fields in the indexed json, otherwise the returned value will probably not be what is expected.
|
||||||
|
#
|
||||||
|
# As an implementation exemple, you can take a look at Availability (indexed as fablab/availabilities)
|
||||||
|
# and witch will run custom filters on the fields 'date' and 'subType'. Other custom filters will return 0
|
||||||
|
# as they are not relevant with this kind of custom aggregation.
|
||||||
|
query = JSON.parse(query)
|
||||||
|
custom_query = JSON.parse(custom_query)
|
||||||
|
|
||||||
|
exclude = custom_query.delete('exclude')
|
||||||
|
if exclude
|
||||||
|
query = {query: { filtered: { query: query['query'], filter: { not: { term: custom_query['match'] } } } }, aggregations: query['aggregations'], size: query['size']}
|
||||||
|
else
|
||||||
|
query['query']['bool']['must'].push(custom_query)
|
||||||
|
end
|
||||||
|
query = query.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
c_res = client.search(index: custom.es_index, type: custom.es_type, body: query)
|
||||||
|
results['aggregations'][custom.field] = c_res['aggregations'][custom.field]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
results
|
||||||
|
end
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user