dash_creator 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +170 -0
- data/Rakefile +36 -0
- data/app/assets/config/dash_creator_manifest.js +2 -0
- data/app/assets/javascripts/dash_creator/application.js +24 -0
- data/app/assets/javascripts/dash_creator/chart.js +2 -0
- data/app/assets/javascripts/dash_creator/dashboard.js +2 -0
- data/app/assets/javascripts/dash_creator/dashboard_object.js +2 -0
- data/app/assets/javascripts/dash_creator/filter.js +2 -0
- data/app/assets/javascripts/dash_creator/libs/Chart.js +12269 -0
- data/app/assets/javascripts/dash_creator/libs/bootstrap.js +3535 -0
- data/app/assets/javascripts/dash_creator/libs/chartCreator.js +1582 -0
- data/app/assets/javascripts/dash_creator/libs/dashboardCreator.js +531 -0
- data/app/assets/javascripts/dash_creator/libs/daterangepicker.js +1626 -0
- data/app/assets/javascripts/dash_creator/libs/filterCreator.js +733 -0
- data/app/assets/javascripts/dash_creator/libs/jquery-ui.js +18706 -0
- data/app/assets/javascripts/dash_creator/libs/jquery.minicolors.js +1108 -0
- data/app/assets/javascripts/dash_creator/libs/moment.js +4301 -0
- data/app/assets/javascripts/dash_creator/libs/tether.js +1811 -0
- data/app/assets/javascripts/dash_creator/user.js +2 -0
- data/app/assets/stylesheets/dash_creator/application.css +16 -0
- data/app/assets/stylesheets/dash_creator/chart.css +4 -0
- data/app/assets/stylesheets/dash_creator/dashboard.css +4 -0
- data/app/assets/stylesheets/dash_creator/dashboard_object.css +4 -0
- data/app/assets/stylesheets/dash_creator/filter.css +4 -0
- data/app/assets/stylesheets/dash_creator/fonts/FontAwesome.otf +0 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.eot +0 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.svg +2671 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.ttf +0 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.woff +0 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.woff2 +0 -0
- data/app/assets/stylesheets/dash_creator/libs/font-awesome.css +2199 -0
- data/app/assets/stylesheets/dash_creator/libs/jquery.minicolors.css +319 -0
- data/app/assets/stylesheets/dash_creator/libs/jquery.minicolors.png +0 -0
- data/app/assets/stylesheets/dash_creator/libs/style.css +13714 -0
- data/app/assets/stylesheets/dash_creator/user.css +4 -0
- data/app/controllers/dash_creator/application_controller.rb +3 -0
- data/app/controllers/dash_creator/chart_controller.rb +81 -0
- data/app/controllers/dash_creator/dashboard_controller.rb +37 -0
- data/app/controllers/dash_creator/dashboard_object_controller.rb +34 -0
- data/app/controllers/dash_creator/filter_controller.rb +60 -0
- data/app/controllers/dash_creator/user_controller.rb +82 -0
- data/app/helpers/dash_creator/application_helper.rb +4 -0
- data/app/helpers/dash_creator/chart_helper.rb +829 -0
- data/app/helpers/dash_creator/dashboard_helper.rb +4 -0
- data/app/helpers/dash_creator/dashboard_object_helper.rb +4 -0
- data/app/helpers/dash_creator/filter_helper.rb +237 -0
- data/app/helpers/dash_creator/user_helper.rb +4 -0
- data/app/jobs/dash_creator/application_job.rb +4 -0
- data/app/mailers/dash_creator/application_mailer.rb +6 -0
- data/app/models/dash_creator/application_record.rb +5 -0
- data/app/models/dash_creator/chart.rb +21 -0
- data/app/models/dash_creator/dashboard.rb +13 -0
- data/app/models/dash_creator/dashboard_object.rb +7 -0
- data/app/models/dash_creator/filter.rb +22 -0
- data/app/views/dash_creator/chart/_chart_creator.html.erb +230 -0
- data/app/views/dash_creator/chart/_modals.html.erb +74 -0
- data/app/views/dash_creator/chart/_plot_chart.html.erb +45 -0
- data/app/views/dash_creator/chart/create_chart.js.erb +53 -0
- data/app/views/dash_creator/dashboard/_dashboard_creator.html.erb +182 -0
- data/app/views/dash_creator/dashboard/_modals.html.erb +74 -0
- data/app/views/dash_creator/dashboard_object/_chart.html.erb +9 -0
- data/app/views/dash_creator/dashboard_object/_stat.html.erb +24 -0
- data/app/views/dash_creator/dashboard_object/_table.html.erb +16 -0
- data/app/views/dash_creator/filter/_filtering_card.html.erb +132 -0
- data/app/views/dash_creator/filter/_modals.html.erb +51 -0
- data/app/views/dash_creator/filter/_result_tables.html.erb +55 -0
- data/app/views/dash_creator/filter/apply_filtering.js.erb +46 -0
- data/app/views/dash_creator/filter/create_stat.js.erb +20 -0
- data/app/views/dash_creator/layouts/application.html.erb +64 -0
- data/app/views/dash_creator/layouts/menu/_left_menu.html.erb +12 -0
- data/app/views/dash_creator/user/_section_card.html.erb +17 -0
- data/app/views/dash_creator/user/creator.html.erb +11 -0
- data/app/views/dash_creator/user/dashboard.html.erb +10 -0
- data/config/initializers/dash_creator.rb +0 -0
- data/config/routes.rb +30 -0
- data/lib/dash_creator.rb +87 -0
- data/lib/dash_creator/acts_as_dash_creator.rb +16 -0
- data/lib/dash_creator/acts_as_dashboard_object.rb +19 -0
- data/lib/dash_creator/engine.rb +11 -0
- data/lib/dash_creator/version.rb +3 -0
- data/lib/generators/dash_creator/install/install_generator.rb +70 -0
- data/lib/generators/dash_creator/install/templates/_chart.html.erb +9 -0
- data/lib/generators/dash_creator/install/templates/_section_card.html.erb +17 -0
- data/lib/generators/dash_creator/install/templates/_stat.html.erb +24 -0
- data/lib/generators/dash_creator/install/templates/_table.html.erb +16 -0
- data/lib/generators/dash_creator/install/templates/add_indexes_to_dash_creator_tables.rb +15 -0
- data/lib/generators/dash_creator/install/templates/create_dash_creator_charts.rb +25 -0
- data/lib/generators/dash_creator/install/templates/create_dash_creator_dashboard_objects.rb +36 -0
- data/lib/generators/dash_creator/install/templates/create_dash_creator_dashboards.rb +28 -0
- data/lib/generators/dash_creator/install/templates/create_dash_creator_filters.rb +22 -0
- data/lib/generators/dash_creator/install/templates/dashboard.html.erb +10 -0
- data/lib/generators/dash_creator/install/templates/initializer.rb +48 -0
- data/lib/tasks/dash_creator_tasks.rake +4 -0
- metadata +196 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
require_dependency "dash_creator/application_controller"
|
2
|
+
|
3
|
+
module DashCreator
|
4
|
+
class ChartController < ApplicationController
|
5
|
+
skip_before_action :verify_authenticity_token
|
6
|
+
|
7
|
+
def create_chart
|
8
|
+
return render json: {models: nil} if params[:filters].nil?
|
9
|
+
|
10
|
+
# Remove useless filters from data
|
11
|
+
chart_data = params
|
12
|
+
chart_data[:filters] = chart_data[:filters]
|
13
|
+
.to_unsafe_h
|
14
|
+
.select { |key, value| chart_data[:y_data].include?(key.to_s) || (key.to_s == chart_data[:x_data][:attribute].classify && chart_data[:x_data][:from_filter] == 'true' ) }
|
15
|
+
|
16
|
+
render 'dash_creator/chart/create_chart', locals: {chart_data: chart_data, filters_data: nil, id: 0}
|
17
|
+
end
|
18
|
+
|
19
|
+
def save_chart
|
20
|
+
return render json: {models: nil} if params[:filters].nil?
|
21
|
+
|
22
|
+
# Remove useless filters from data
|
23
|
+
chart_data = params
|
24
|
+
chart_data[:filters] = chart_data[:filters]
|
25
|
+
.to_unsafe_h
|
26
|
+
.select { |key, value| chart_data[:y_data].include?(key.to_s) || (key.to_s == chart_data[:x_data][:attribute].classify && chart_data[:x_data][:from_filter] == 'true' ) }
|
27
|
+
|
28
|
+
# Create chart from data
|
29
|
+
user = user_for_dash_creator
|
30
|
+
chart = DashCreator::Chart.all.where(user_id: user.id).create(name: params[:chart_name], data: chart_data)
|
31
|
+
|
32
|
+
render json: {chart_id: chart.id}
|
33
|
+
end
|
34
|
+
|
35
|
+
def edit_chart
|
36
|
+
return render json: {models: nil} if params[:filters].nil?
|
37
|
+
|
38
|
+
# Remove useless filters from data
|
39
|
+
chart_data = params
|
40
|
+
chart_data[:filters] = chart_data[:filters]
|
41
|
+
.to_unsafe_h
|
42
|
+
.select { |key, value| chart_data[:y_data].include?(key.to_s) || (key.to_s == chart_data[:x_data][:attribute].classify && chart_data[:x_data][:from_filter] == 'true' ) }
|
43
|
+
|
44
|
+
# Edit chart from data
|
45
|
+
user = user_for_dash_creator
|
46
|
+
chart = DashCreator::Chart.all.where(user_id: user.id).find(params[:chart_id])
|
47
|
+
chart.update_attribute(:data, chart_data)
|
48
|
+
|
49
|
+
render json: {chart_id: chart.id}
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_chart
|
53
|
+
user = user_for_dash_creator
|
54
|
+
id = params[:chart_id]
|
55
|
+
chart = DashCreator::Chart.all.where(user_id: user.id).find(id)
|
56
|
+
refresh = params.key?('refresh') ? params['refresh'] : 'false'
|
57
|
+
|
58
|
+
filters_data = chart.data['filters']
|
59
|
+
|
60
|
+
chart_data = chart.data
|
61
|
+
chart_data['refresh'] = refresh
|
62
|
+
chart_data.delete('chart_id')
|
63
|
+
chart_data.delete('chart_name')
|
64
|
+
|
65
|
+
render 'dash_creator/chart/create_chart', locals: {chart_data: chart.data, filters_data: filters_data, id: id}
|
66
|
+
end
|
67
|
+
|
68
|
+
def delete_charts
|
69
|
+
user = user_for_dash_creator
|
70
|
+
DashCreator::Chart.all.where(user_id: user.id, id: params[:charts_ids]).destroy_all
|
71
|
+
end
|
72
|
+
|
73
|
+
def pluck_labels
|
74
|
+
model = params[:model].safe_constantize
|
75
|
+
attribute = params[:attribute]
|
76
|
+
limit = params[:limit].empty? ? 30 : params[:limit].to_i
|
77
|
+
|
78
|
+
render json: {labels: model.pluck(:"#{attribute}").uniq.take(limit)}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_dependency "dash_creator/application_controller"
|
2
|
+
|
3
|
+
module DashCreator
|
4
|
+
class DashboardController < ApplicationController
|
5
|
+
skip_before_action :verify_authenticity_token
|
6
|
+
|
7
|
+
def get_dashboard
|
8
|
+
# user = user_for_dash_creator
|
9
|
+
# dashboard = DashCreator::Dashboard.all.where(user_id: user.id).find(params[:dashboard_id])
|
10
|
+
dashboard = DashCreator::Dashboard.all.find(params[:dashboard_id])
|
11
|
+
|
12
|
+
render json: dashboard.options
|
13
|
+
end
|
14
|
+
|
15
|
+
def save_dashboard
|
16
|
+
user = user_for_dash_creator
|
17
|
+
dashboard = DashCreator::Dashboard.all.where(user_id: user.id).create(name: params[:dashboard_name], options: params[:options])
|
18
|
+
|
19
|
+
render json: {dashboard_id: dashboard.id}
|
20
|
+
end
|
21
|
+
|
22
|
+
def edit_dashboard
|
23
|
+
# user = user_for_dash_creator
|
24
|
+
# dashboard = DashCreator::Dashboard.all.where(user_id: user.id).find(params[:dashboard_id])
|
25
|
+
dashboard = DashCreator::Dashboard.all.find(params[:dashboard_id])
|
26
|
+
dashboard.update_attribute(:options, params[:options])
|
27
|
+
|
28
|
+
render json: {dashboard_id: dashboard.id}
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_dashboards
|
32
|
+
# user = user_for_dash_creator
|
33
|
+
# DashCreator::Dashboard.all.where(user_id: user.id, id: params[:dashboards_ids]).destroy_all
|
34
|
+
DashCreator::Dashboard.all.where(id: params[:dashboards_ids]).destroy_all
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_dependency "dash_creator/application_controller"
|
2
|
+
|
3
|
+
module DashCreator
|
4
|
+
class DashboardObjectController < ApplicationController
|
5
|
+
skip_before_action :verify_authenticity_token
|
6
|
+
|
7
|
+
def get_model_objects
|
8
|
+
object = DashboardObject.find_by_code(params['object_code'])
|
9
|
+
|
10
|
+
model_objects = object.related_model.classify.safe_constantize.all
|
11
|
+
|
12
|
+
model_objects_json = model_objects.map{ |o| {id: o.id, name: o.name} }
|
13
|
+
|
14
|
+
render json: {model_objects: model_objects_json}
|
15
|
+
end
|
16
|
+
|
17
|
+
def save_object
|
18
|
+
object = DashboardObject.create(name: params[:name], options: params[:options])
|
19
|
+
|
20
|
+
render json: {object_id: object.id}
|
21
|
+
end
|
22
|
+
|
23
|
+
def edit_object
|
24
|
+
object = DashboardObject.find_by_code(params['object_code'])
|
25
|
+
object.update_attribute(:options, params[:options])
|
26
|
+
|
27
|
+
render json: {object_id: object.id}
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_object
|
31
|
+
DashboardObject.find(params[:object_id]).destroy
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_dependency "dash_creator/application_controller"
|
2
|
+
|
3
|
+
module DashCreator
|
4
|
+
class FilterController < ApplicationController
|
5
|
+
skip_before_action :verify_authenticity_token
|
6
|
+
|
7
|
+
def get_filter
|
8
|
+
user = user_for_dash_creator
|
9
|
+
filter = DashCreator::Filter.all.where(user_id: user.id).find(params[:filter_id])
|
10
|
+
|
11
|
+
render json: filter.options
|
12
|
+
end
|
13
|
+
|
14
|
+
def save_filter
|
15
|
+
user = user_for_dash_creator
|
16
|
+
filter = DashCreator::Filter.all.where(user_id: user.id).create(name: params[:filter_name], options: params[:filters])
|
17
|
+
|
18
|
+
render json: {filter_id: filter.id}
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_filters
|
22
|
+
user = user_for_dash_creator
|
23
|
+
DashCreator::Filter.all.where(user_id: user.id, id: params[:filters_ids]).destroy_all
|
24
|
+
end
|
25
|
+
|
26
|
+
def filtering_result
|
27
|
+
return render json: {models: nil} if params[:filters].nil?
|
28
|
+
|
29
|
+
numeric_types = Numeric.descendants.map{ |n| n.name.underscore }
|
30
|
+
@starting_types = numeric_types + %w(string date datetime)
|
31
|
+
|
32
|
+
render 'dash_creator/filter/apply_filtering', locals: {data: params['filters'], id: params['id']}
|
33
|
+
end
|
34
|
+
|
35
|
+
def download_csv
|
36
|
+
model = params[:model]
|
37
|
+
columns = params[:columns]
|
38
|
+
objects = model.safe_constantize.find(params[:ids])
|
39
|
+
|
40
|
+
file = CSV.generate do |csv|
|
41
|
+
csv << columns
|
42
|
+
objects.each do |o|
|
43
|
+
csv << o.attributes.values_at(*columns)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
send_data file,
|
48
|
+
type: 'text/csv; charset=utf-8; header=present',
|
49
|
+
disposition: "attachment; filename=#{model}.csv"
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_stat
|
53
|
+
id = params[:filter_id]
|
54
|
+
filters_data = DashCreator::Filter.find(id).options
|
55
|
+
filters_data['refresh'] = true unless params[:refresh].nil?
|
56
|
+
|
57
|
+
render 'dash_creator/filter/create_stat', locals: {filters_data: filters_data, id: id}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_dependency "dash_creator/application_controller"
|
2
|
+
|
3
|
+
module DashCreator
|
4
|
+
class UserController < ApplicationController
|
5
|
+
##### Creator for filters, charts & dashboards #####
|
6
|
+
|
7
|
+
def creator
|
8
|
+
user = user_for_dash_creator
|
9
|
+
|
10
|
+
Rails.application.eager_load!
|
11
|
+
models = ApplicationRecord.descendants.select{ |m| !DashCreator.except_models.include?(m.name) }
|
12
|
+
|
13
|
+
@models = models.map{ |m| [m.name, m.name] }
|
14
|
+
|
15
|
+
@filters = DashCreator::Filter.all.where(user_id: user.id).all.map { |f| [f.name, f.id] }
|
16
|
+
@models_data = models_data
|
17
|
+
@charts = DashCreator::Chart.all.where(user_id: user.id).all.map { |c| [c.name, c.id] }
|
18
|
+
@dashboards = DashCreator::Dashboard.all.where(user_id: user.id).all.map { |d| [d.name, d.id] }
|
19
|
+
|
20
|
+
@dashboard_objects = DashboardObject.all
|
21
|
+
@model_objects = {}
|
22
|
+
@dashboard_objects.each do |o|
|
23
|
+
if o.related_model != ''
|
24
|
+
model = o.related_model.safe_constantize
|
25
|
+
model_objects = (model.column_names.include? 'user_id') ? model.where(user: user) : model.all
|
26
|
+
@model_objects[o.code] = model_objects.map{ |mo| {id: mo.id, name: mo.name} }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
render :creator, layout: DashCreator.layout_path
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
##### Render a dashboard #####
|
35
|
+
|
36
|
+
def dashboard
|
37
|
+
user = user_for_dash_creator
|
38
|
+
@dashboard = DashCreator::Dashboard.all.where(user_id: user.id).find(params[:dashboard_id])
|
39
|
+
@dashboards = DashCreator::Dashboard.all.where(user_id: user.id).map { |d| [d.name, d.id] }
|
40
|
+
|
41
|
+
render :dashboard, layout: DashCreator.layout_path
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
private
|
46
|
+
def models_data
|
47
|
+
Rails.application.eager_load!
|
48
|
+
models = ApplicationRecord.descendants.select{ |m| !DashCreator.except_models.include?(m.name.gsub('::', '')) }
|
49
|
+
|
50
|
+
numeric_types = Numeric.descendants.map{ |n| n.name.underscore }
|
51
|
+
|
52
|
+
models_data = {}
|
53
|
+
models.each do |m|
|
54
|
+
belongs_to_models = m.reflect_on_all_associations(:belongs_to)
|
55
|
+
.map(&:name)
|
56
|
+
.map{ |x| x.to_s << '_id' }
|
57
|
+
.map{ |x| DashCreator.columns_aliases[x].nil? ? x : DashCreator.columns_aliases[x] }
|
58
|
+
|
59
|
+
models_data[m.name] = []
|
60
|
+
m.columns_hash.each do |k,v|
|
61
|
+
if belongs_to_models.include?(k)
|
62
|
+
models_data[m.name].push(k + '-ref')
|
63
|
+
else
|
64
|
+
type = v.type.to_s
|
65
|
+
type = 'numeric' if numeric_types.include?(type)
|
66
|
+
type = 'text' if type == 'string'
|
67
|
+
type = 'datetime' if type == 'date'
|
68
|
+
models_data[m.name].push(k + '-' + type) unless DashCreator.except_attributes.include?(k)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
has_many_models = m.reflect_on_all_associations(:has_many)
|
73
|
+
.map(&:name)
|
74
|
+
.map{ |x| x.to_s }
|
75
|
+
.select{ |k| !DashCreator.except_models.include?(k.classify) }
|
76
|
+
has_many_models.each { |h| models_data[m.name].push(h.singularize + '-has') }
|
77
|
+
end
|
78
|
+
|
79
|
+
models_data
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,829 @@
|
|
1
|
+
module DashCreator
|
2
|
+
module ChartHelper
|
3
|
+
def plot_chart(id, type, plot_data, options = {})
|
4
|
+
default_options = {
|
5
|
+
tooltips: {
|
6
|
+
mode: 'nearest',
|
7
|
+
intersect: false
|
8
|
+
},
|
9
|
+
responsive: true
|
10
|
+
}
|
11
|
+
options = options.merge(default_options)
|
12
|
+
render 'dash_creator/chart/plot_chart', {id: id, type: type, data: plot_data, options: options}
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_update_button(id, type, text)
|
16
|
+
button_tag text, id: "btn-#{type}-#{id}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_chart_script(id, type, action_path, action_params = {})
|
20
|
+
render 'dash_creator/chart/update_chart', {id: id, type: type, action_path: action_path, action_params: action_params}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Data formatting (hash)
|
24
|
+
# :labels is array of labels
|
25
|
+
# :datasets_labels is array of datasets labels
|
26
|
+
# :datasets is array of arrays containing the datasets data
|
27
|
+
# for a mixed chart, :types is array of datasets types
|
28
|
+
# :style contains chart styling
|
29
|
+
def plot_type_1_chart(id, type, data, options = {})
|
30
|
+
begin
|
31
|
+
raise ArgumentError, 'datasets_key_missing' unless data.key?('datasets')
|
32
|
+
raise ArgumentError, 'labels_key_missing' unless data.key?('labels')
|
33
|
+
raise ArgumentError, 'types_key_missing' if (type == 'mixed' && !data.key?('types'))
|
34
|
+
|
35
|
+
plot_data = type_1_data_to_plot(data)
|
36
|
+
rescue Exception => e
|
37
|
+
return render html: create_plot_error_message(id, type, e.message).to_s
|
38
|
+
end
|
39
|
+
plot_data_with_options = type_1_add_style(plot_data, data['style'])
|
40
|
+
plot_data_with_options[:options] = plot_data_with_options[:options].merge(options)
|
41
|
+
|
42
|
+
plot_chart(id, type, plot_data_with_options[:plot_data], plot_data_with_options[:options])
|
43
|
+
end
|
44
|
+
|
45
|
+
def type_1_data_to_plot(data)
|
46
|
+
datasets = []
|
47
|
+
0.upto(data['datasets'].length-1) do |i|
|
48
|
+
set = {data: data['datasets'][i]}
|
49
|
+
unless data['labels'][i].nil?
|
50
|
+
set[:label] = data['datasets_labels'][i] unless data['datasets_labels'].nil?
|
51
|
+
end
|
52
|
+
set[:type] = data['types'][i]
|
53
|
+
datasets.push(set)
|
54
|
+
end
|
55
|
+
|
56
|
+
{labels: data['labels'], datasets: datasets}
|
57
|
+
end
|
58
|
+
|
59
|
+
def type_1_add_style(plot_data, style_options)
|
60
|
+
colors = style_options['colors'].nil? ? nil : set_transparency(style_options['colors'], style_options['transparencies'])
|
61
|
+
|
62
|
+
0.upto(plot_data[:datasets].length-1) do |i|
|
63
|
+
set = plot_data[:datasets][i]
|
64
|
+
set[:backgroundColor] = colors[i] unless colors.nil? || colors[i].nil?
|
65
|
+
set[:borderColor] = style_options['colors'][i] unless style_options['colors'].nil? || style_options['colors'][i].nil?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Initialize options
|
69
|
+
options = {}
|
70
|
+
scales = options['scales'] = {}
|
71
|
+
scales['xAxes'] = [{}]
|
72
|
+
scales['yAxes'] = [{}]
|
73
|
+
scales['xAxes'][0]['gridLines'] = {}
|
74
|
+
scales['xAxes'][0]['ticks'] = {}
|
75
|
+
scales['yAxes'][0]['gridLines'] = {}
|
76
|
+
scales['yAxes'][0]['ticks'] = {}
|
77
|
+
|
78
|
+
# Grid options
|
79
|
+
grid_options = style_options['grid']
|
80
|
+
scales['xAxes'][0]['gridLines']['display'] = false if grid_options['x'] == 'false'
|
81
|
+
scales['yAxes'][0]['gridLines']['display'] = false if grid_options['y'] == 'false'
|
82
|
+
|
83
|
+
# Y Axis options
|
84
|
+
y_axis_options = style_options['y-axis']
|
85
|
+
scales['yAxes'][0]['ticks']['min'] = y_axis_options['min'].to_i if y_axis_options['min'] != ''
|
86
|
+
scales['yAxes'][0]['ticks']['max'] = y_axis_options['max'].to_i if y_axis_options['max'] != ''
|
87
|
+
scales['yAxes'][0]['ticks']['stepSize'] = y_axis_options['step'].to_i if y_axis_options['step'] != ''
|
88
|
+
|
89
|
+
# Stacked
|
90
|
+
if style_options['stacked'] == 'true'
|
91
|
+
scales['xAxes'][0]['stacked'] = true
|
92
|
+
scales['yAxes'][0]['stacked'] = true
|
93
|
+
end
|
94
|
+
|
95
|
+
# Labeling step
|
96
|
+
scales['xAxes'][0]['ticks']['callback'] = style_options['labeling-step'] if style_options['labeling-step'] != ''
|
97
|
+
|
98
|
+
# Legend
|
99
|
+
legend_options = style_options['legend']
|
100
|
+
legend = options['legend'] = {}
|
101
|
+
legend['display'] = false if legend_options['display'] == 'false'
|
102
|
+
legend['position'] = legend_options['position']
|
103
|
+
|
104
|
+
# Title
|
105
|
+
options['title'] = {display: true, text: style_options['title']} if style_options['title'] != ''
|
106
|
+
|
107
|
+
{plot_data: plot_data, options: options}
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
# Data formatting (hash)
|
113
|
+
# :labels is array of labels
|
114
|
+
# :datasets is array of arrays containing the datasets data
|
115
|
+
# :style contains chart styling
|
116
|
+
def plot_type_2_chart(id, type, data, options = {})
|
117
|
+
begin
|
118
|
+
raise ArgumentError, 'datasets_key_missing' unless data.key?('datasets')
|
119
|
+
raise ArgumentError, 'labels_key_missing' unless data.key?('labels')
|
120
|
+
|
121
|
+
plot_data = type_2_data_to_plot(data)
|
122
|
+
rescue Exception => e
|
123
|
+
return render html: create_plot_error_message(id, type, e.message).to_s
|
124
|
+
end
|
125
|
+
plot_data_with_options = type_2_add_style(plot_data, data['style'])
|
126
|
+
plot_data_with_options = plot_data_with_options.merge(options)
|
127
|
+
|
128
|
+
plot_chart(id, type, plot_data_with_options[:plot_data], plot_data_with_options[:options])
|
129
|
+
end
|
130
|
+
|
131
|
+
def type_2_data_to_plot(data)
|
132
|
+
datasets = []
|
133
|
+
0.upto(data['datasets'].length-1) do |i|
|
134
|
+
set = {
|
135
|
+
data: data['datasets'][i]
|
136
|
+
}
|
137
|
+
datasets.push(set)
|
138
|
+
end
|
139
|
+
|
140
|
+
{labels: data['labels'], datasets: datasets}
|
141
|
+
end
|
142
|
+
|
143
|
+
def type_2_add_style(plot_data, style_options)
|
144
|
+
0.upto(plot_data[:datasets].length-1) do |i|
|
145
|
+
set = plot_data[:datasets][i]
|
146
|
+
set[:backgroundColor] = set_transparency(style_options['colors'], style_options['transparencies']) unless style_options['colors'].nil?
|
147
|
+
end
|
148
|
+
|
149
|
+
options = {}
|
150
|
+
|
151
|
+
legend_options = style_options['legend']
|
152
|
+
legend = options['legend'] = {}
|
153
|
+
legend['display'] = false if legend_options['display'] == 'false'
|
154
|
+
legend['position'] = legend_options['position']
|
155
|
+
|
156
|
+
options['title'] = {display: true, text: style_options['title']} if style_options['title'] != ''
|
157
|
+
|
158
|
+
{plot_data: plot_data, options: options}
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
# Type 1 charts & mixed bar/line
|
163
|
+
def plot_line_chart(id, data, options = {})
|
164
|
+
plot_type_1_chart(id, 'line', data, options)
|
165
|
+
end
|
166
|
+
|
167
|
+
def plot_bar_chart(id, data, options = {})
|
168
|
+
plot_type_1_chart(id, 'bar', data, options)
|
169
|
+
end
|
170
|
+
|
171
|
+
def plot_mixed_chart(id, data, options = {})
|
172
|
+
plot_type_1_chart(id, 'mixed', data, options)
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
# Type 2 charts
|
177
|
+
def plot_pie_chart(id, data, options = {})
|
178
|
+
plot_type_2_chart(id, 'pie', data, options)
|
179
|
+
end
|
180
|
+
|
181
|
+
def plot_doughnut_chart(id, data, options = {})
|
182
|
+
plot_type_2_chart(id, 'doughnut', data, options)
|
183
|
+
end
|
184
|
+
|
185
|
+
def plot_polar_chart(id, data, options = {})
|
186
|
+
plot_type_2_chart(id, 'polarArea', data, options)
|
187
|
+
end
|
188
|
+
|
189
|
+
def plot_radar_chart(id, data, options = {})
|
190
|
+
plot_type_2_chart(id, 'radar', data, options)
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
# From rgb to rgba with given colors and transparencies
|
195
|
+
def set_transparency(colors, transparencies)
|
196
|
+
colors_with_transparency = []
|
197
|
+
0.upto(colors.length - 1).each { |i| colors_with_transparency.push('rgba' + colors[i][3..-2] + ', ' + (1-(transparencies[i].to_f/100)).to_s + ')') }
|
198
|
+
colors_with_transparency
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns plot failing error message
|
202
|
+
def create_plot_error_message(id, type, error_key)
|
203
|
+
str = error_key + " for chart-#{type}-#{id}"
|
204
|
+
str
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# Data processing for charts
|
209
|
+
# Handle differently regarding x_data type
|
210
|
+
def chart_processing(data)
|
211
|
+
ActiveRecord::Base.connection.execute("SET temp_buffers = '4GB';")
|
212
|
+
case data['x_data']['type']
|
213
|
+
when 'datetime'
|
214
|
+
hash = date_chart_processing(data)
|
215
|
+
|
216
|
+
when 'numeric'
|
217
|
+
hash = numeric_chart_processing(data)
|
218
|
+
|
219
|
+
when 'boolean'
|
220
|
+
hash = boolean_chart_processing(data)
|
221
|
+
|
222
|
+
when 'text'
|
223
|
+
hash = text_chart_processing(data)
|
224
|
+
|
225
|
+
when 'has'
|
226
|
+
hash = has_model_chart_processing(data)
|
227
|
+
|
228
|
+
when 'ref'
|
229
|
+
hash = ref_model_chart_processing(data)
|
230
|
+
|
231
|
+
else
|
232
|
+
hash = {}
|
233
|
+
end
|
234
|
+
ActiveRecord::Base.connection.execute("SET temp_buffers = '8MB';")
|
235
|
+
hash = hash.merge({
|
236
|
+
datasets_labels: data['datasets_labels'],
|
237
|
+
types: data['types']
|
238
|
+
})
|
239
|
+
hash
|
240
|
+
end
|
241
|
+
|
242
|
+
def get_start_and_end_dates(date_info)
|
243
|
+
date_range_type = date_info['date_range_type']
|
244
|
+
|
245
|
+
case date_range_type
|
246
|
+
when 'Today'
|
247
|
+
start_date = DateTime.now.beginning_of_day
|
248
|
+
end_date = DateTime.now.beginning_of_day + 1.days
|
249
|
+
|
250
|
+
when 'Yesterday'
|
251
|
+
start_date = DateTime.now.beginning_of_day - 1.days
|
252
|
+
end_date = DateTime.now.beginning_of_day
|
253
|
+
|
254
|
+
when 'Last 7 Days'
|
255
|
+
start_date = DateTime.now.beginning_of_day - 6.days
|
256
|
+
end_date = DateTime.now.beginning_of_day + 1.days
|
257
|
+
|
258
|
+
when 'This Week'
|
259
|
+
start_date = DateTime.now.beginning_of_week
|
260
|
+
end_date = DateTime.now.beginning_of_week + 7.days
|
261
|
+
|
262
|
+
when 'Last Week'
|
263
|
+
start_date = DateTime.now.beginning_of_week - 7.days
|
264
|
+
end_date = DateTime.now.beginning_of_week
|
265
|
+
|
266
|
+
when 'Last 30 Days'
|
267
|
+
start_date = DateTime.now.beginning_of_day - 29.days
|
268
|
+
end_date = DateTime.now.beginning_of_day + 1.days
|
269
|
+
|
270
|
+
when 'This Month'
|
271
|
+
start_date = DateTime.now.beginning_of_month
|
272
|
+
end_date = DateTime.now.beginning_of_month + 30.days
|
273
|
+
|
274
|
+
when 'Last Month'
|
275
|
+
start_date = DateTime.now.beginning_of_month - 30.days
|
276
|
+
end_date = DateTime.now.beginning_of_month
|
277
|
+
|
278
|
+
when 'Last 365 Days'
|
279
|
+
start_date = DateTime.now.beginning_of_day - 364.days
|
280
|
+
end_date = DateTime.now.beginning_of_day + 1.days
|
281
|
+
|
282
|
+
when 'This Year'
|
283
|
+
start_date = DateTime.now.beginning_of_year
|
284
|
+
end_date = DateTime.now.beginning_of_year + 365.days
|
285
|
+
|
286
|
+
when 'Last Year'
|
287
|
+
start_date = DateTime.now.beginning_of_year - 365.days
|
288
|
+
end_date = DateTime.now.beginning_of_year
|
289
|
+
|
290
|
+
else
|
291
|
+
start_date = DateTime.strptime(date_info['start'], '%b %d, %Y').beginning_of_day
|
292
|
+
end_date = DateTime.strptime(date_info['end'], '%b %d, %Y').beginning_of_day + 1.days
|
293
|
+
end
|
294
|
+
|
295
|
+
[start_date, end_date]
|
296
|
+
end
|
297
|
+
|
298
|
+
# Process chart data for a x timeline
|
299
|
+
def date_chart_processing(data)
|
300
|
+
date_info = data['x_data']
|
301
|
+
y_data = data['y_data']
|
302
|
+
|
303
|
+
sub_attribute = date_info['sub_attribute']
|
304
|
+
unless sub_attribute.nil?
|
305
|
+
return sub_has_date_chart_processing(data) if date_info['sub_attribute_from'] == 'has'
|
306
|
+
return sub_ref_date_chart_processing(data)
|
307
|
+
end
|
308
|
+
|
309
|
+
# Gather all info
|
310
|
+
attribute = date_info['attribute']
|
311
|
+
format = date_info.key?('format') ? date_info['format'] : '%d.%m.%Y'
|
312
|
+
end_date = get_start_and_end_dates(date_info)[1]
|
313
|
+
labels = get_date_labels(date_info)
|
314
|
+
|
315
|
+
datasets = []
|
316
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
317
|
+
0.upto(labels.length-1).each do |i|
|
318
|
+
date = labels[i]
|
319
|
+
next_date = i == (labels.length - 1) ? end_date : labels[i+1]
|
320
|
+
0.upto(y_data.length-1).each do |k|
|
321
|
+
count = y_data[k].where("#{attribute}": date..next_date).count
|
322
|
+
datasets[k].push(count)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
labels = labels.map{ |d| d.strftime(format) }
|
327
|
+
|
328
|
+
{labels: labels, datasets: datasets}
|
329
|
+
end
|
330
|
+
|
331
|
+
def sub_has_date_chart_processing(data)
|
332
|
+
date_info = data['x_data']
|
333
|
+
y_data = data['y_data']
|
334
|
+
|
335
|
+
# Gather all info
|
336
|
+
# has_models = date_info['attribute'].pluralize
|
337
|
+
model_string = y_data.first.first.class.name.underscore
|
338
|
+
attribute_model = date_info['attribute'].classify.safe_constantize
|
339
|
+
sub_attribute = date_info['sub_attribute']
|
340
|
+
|
341
|
+
format = date_info.key?('format') ? date_info['format'] : '%d.%m.%Y'
|
342
|
+
end_date = get_start_and_end_dates(date_info)[1]
|
343
|
+
labels = get_date_labels(date_info)
|
344
|
+
|
345
|
+
datasets = []
|
346
|
+
model_ids = []
|
347
|
+
0.upto(y_data.length-1).each do |i|
|
348
|
+
datasets.push([])
|
349
|
+
model_ids.push(y_data[i].pluck(:id).uniq)
|
350
|
+
end
|
351
|
+
|
352
|
+
0.upto(labels.length-1).each do |i|
|
353
|
+
date = labels[i]
|
354
|
+
next_date = i == (labels.length - 1) ? end_date : labels[i+1]
|
355
|
+
0.upto(y_data.length-1).each do |k|
|
356
|
+
# data = y_data[k].joins("LEFT JOIN #{has_models} ON #{has_models}.#{model_string}_id = #{model_string.pluralize}.id")
|
357
|
+
# .group("#{model_string.pluralize}.id")
|
358
|
+
# .having("#{has_models}.#{sub_attribute}": date..next_date)
|
359
|
+
|
360
|
+
# total_count = 0
|
361
|
+
# data.count.each { |key, count| total_count += count }
|
362
|
+
# datasets[k].push(total_count)
|
363
|
+
|
364
|
+
filtered_model_ids = attribute_model.where("#{sub_attribute}": date..next_date).pluck("#{model_string}_id")
|
365
|
+
|
366
|
+
datasets[i].push(count_values_in_common(model_ids[i], filtered_model_ids))
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
labels = labels.map { |d| d.strftime(format) }
|
371
|
+
|
372
|
+
{labels: labels, datasets: datasets}
|
373
|
+
end
|
374
|
+
|
375
|
+
def sub_ref_date_chart_processing(data)
|
376
|
+
date_info = data['x_data']
|
377
|
+
y_data = data['y_data']
|
378
|
+
|
379
|
+
# Gather all info
|
380
|
+
ref_model = date_info['attribute'][0..-4]
|
381
|
+
sub_attribute = date_info['sub_attribute']
|
382
|
+
|
383
|
+
format = date_info.key?('format') ? date_info['format'] : '%d.%m.%Y'
|
384
|
+
end_date = get_start_and_end_dates(date_info)[1]
|
385
|
+
labels = get_date_labels(date_info)
|
386
|
+
|
387
|
+
datasets = []
|
388
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
389
|
+
0.upto(labels.length-1).each do |i|
|
390
|
+
date = labels[i]
|
391
|
+
next_date = i == (labels.length - 1) ? end_date : labels[i+1]
|
392
|
+
0.upto(y_data.length-1).each do |k|
|
393
|
+
count = y_data[k].joins(:"#{ref_model}")
|
394
|
+
.where("#{ref_model.pluralize}.#{sub_attribute}": date..next_date)
|
395
|
+
.count
|
396
|
+
|
397
|
+
datasets[k].push(count)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
labels = labels.map { |d| d.strftime(format) }
|
402
|
+
|
403
|
+
{labels: labels, datasets: datasets}
|
404
|
+
end
|
405
|
+
|
406
|
+
def numeric_chart_processing(data)
|
407
|
+
x_data = data['x_data']
|
408
|
+
y_data = data['y_data']
|
409
|
+
|
410
|
+
attribute = x_data['attribute']
|
411
|
+
|
412
|
+
sub_attribute = x_data['sub_attribute']
|
413
|
+
unless sub_attribute.nil?
|
414
|
+
return sub_has_numeric_chart_processing(data) if x_data['sub_attribute_from'] == 'has'
|
415
|
+
return sub_ref_numeric_chart_processing(data)
|
416
|
+
end
|
417
|
+
|
418
|
+
datasets = []
|
419
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
420
|
+
labels = x_data['labels-corres'].blank? ? x_data['labels'] : x_data['labels-corres']
|
421
|
+
|
422
|
+
x_data['labels'].each do |l|
|
423
|
+
0.upto(y_data.length-1).each do |i|
|
424
|
+
split = l.split('-')
|
425
|
+
low = l[-1] == '+' ? l[0..-2] : split[0]
|
426
|
+
high = split.length == 2 ? split[1] : low
|
427
|
+
data = y_data[i].where("#{attribute}" + ' >= ?', low)
|
428
|
+
data = data.where("#{attribute}" + ' <= ?', high) unless l[-1] == '+'
|
429
|
+
|
430
|
+
datasets[i].push(data.count)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
{labels: labels, datasets: datasets}
|
435
|
+
end
|
436
|
+
|
437
|
+
def sub_has_numeric_chart_processing(data)
|
438
|
+
x_data = data['x_data']
|
439
|
+
y_data = data['y_data']
|
440
|
+
|
441
|
+
model_string = y_data.first.first.class.name.underscore
|
442
|
+
attribute_model = x_data['attribute'].classify.safe_constantize
|
443
|
+
sub_attribute = x_data['sub_attribute']
|
444
|
+
|
445
|
+
datasets = []
|
446
|
+
model_ids = []
|
447
|
+
0.upto(y_data.length-1).each do |i|
|
448
|
+
datasets.push([])
|
449
|
+
model_ids.push(y_data[i].pluck(:id).uniq)
|
450
|
+
end
|
451
|
+
|
452
|
+
labels = x_data['labels-corres'].blank? ? x_data['labels'] : x_data['labels-corres']
|
453
|
+
|
454
|
+
x_data['labels'].each do |l|
|
455
|
+
0.upto(y_data.length-1).each do |i|
|
456
|
+
split = l.split('-')
|
457
|
+
low = l[-1] == '+' ? l[0..-2] : split[0]
|
458
|
+
high = split.length == 2 ? split[1] : low
|
459
|
+
|
460
|
+
if l[-1] == '+'
|
461
|
+
filtered_model_ids = attribute_model.where("#{sub_attribute} >= ?", low).pluck("#{model_string}_id")
|
462
|
+
else
|
463
|
+
filtered_model_ids = attribute_model.where("#{sub_attribute}": low..high).pluck("#{model_string}_id")
|
464
|
+
end
|
465
|
+
|
466
|
+
datasets[i].push(count_values_in_common(model_ids[i], filtered_model_ids))
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
{labels: labels, datasets: datasets}
|
471
|
+
end
|
472
|
+
|
473
|
+
def sub_ref_numeric_chart_processing(data)
|
474
|
+
x_data = data['x_data']
|
475
|
+
y_data = data['y_data']
|
476
|
+
|
477
|
+
ref_model = x_data['attribute'][0..-4]
|
478
|
+
sub_attribute = x_data['sub_attribute']
|
479
|
+
|
480
|
+
datasets = []
|
481
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
482
|
+
labels = x_data['labels-corres'].blank? ? x_data['labels'] : x_data['labels-corres']
|
483
|
+
|
484
|
+
x_data['labels'].each do |l|
|
485
|
+
0.upto(y_data.length-1).each do |i|
|
486
|
+
split = l.split('-')
|
487
|
+
low = l[-1] == '+' ? l[0..-2] : split[0]
|
488
|
+
high = split.length == 2 ? split[1] : low
|
489
|
+
|
490
|
+
if l[-1] == '+'
|
491
|
+
data = y_data[i].joins(:"#{ref_model}").where("#{ref_model.pluralize}.#{sub_attribute} >= ?", low)
|
492
|
+
else
|
493
|
+
data = y_data[i].joins(:"#{ref_model}").where("#{ref_model.pluralize}.#{sub_attribute}": low..high)
|
494
|
+
end
|
495
|
+
|
496
|
+
datasets[i].push(data.count)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
{labels: labels, datasets: datasets}
|
501
|
+
end
|
502
|
+
|
503
|
+
def boolean_chart_processing(data)
|
504
|
+
x_data = data['x_data']
|
505
|
+
y_data = data['y_data']
|
506
|
+
|
507
|
+
attribute = x_data['attribute']
|
508
|
+
|
509
|
+
sub_attribute = x_data['sub_attribute']
|
510
|
+
unless sub_attribute.nil?
|
511
|
+
return sub_has_boolean_chart_processing(data) if x_data['sub_attribute_from'] == 'has'
|
512
|
+
return sub_ref_boolean_chart_processing(data)
|
513
|
+
end
|
514
|
+
|
515
|
+
datasets = []
|
516
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
517
|
+
labels = x_data['labels-corres'].blank? ? x_data['labels'] : x_data['labels-corres']
|
518
|
+
|
519
|
+
x_data['labels'].each do |l|
|
520
|
+
0.upto(y_data.length-1).each do |i|
|
521
|
+
data = y_data[i].where("#{attribute}": true_string?(l))
|
522
|
+
|
523
|
+
datasets[i].push(data.count)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
{labels: labels, datasets: datasets}
|
528
|
+
end
|
529
|
+
|
530
|
+
def sub_has_boolean_chart_processing(data)
|
531
|
+
x_data = data['x_data']
|
532
|
+
y_data = data['y_data']
|
533
|
+
|
534
|
+
model_string = y_data.first.first.class.name.underscore
|
535
|
+
attribute_model = x_data['attribute'].classify.safe_constantize
|
536
|
+
sub_attribute = x_data['sub_attribute']
|
537
|
+
|
538
|
+
datasets = []
|
539
|
+
model_ids = []
|
540
|
+
0.upto(y_data.length-1).each do |i|
|
541
|
+
datasets.push([])
|
542
|
+
model_ids.push(y_data[i].pluck(:id).uniq)
|
543
|
+
end
|
544
|
+
|
545
|
+
labels = x_data['labels-corres'].blank? ? x_data['labels'] : x_data['labels-corres']
|
546
|
+
|
547
|
+
x_data['labels'].each do |l|
|
548
|
+
0.upto(y_data.length-1).each do |i|
|
549
|
+
filtered_model_ids = attribute_model.where("#{sub_attribute}": true_string?(l)).pluck("#{model_string}_id")
|
550
|
+
|
551
|
+
datasets[i].push(count_values_in_common(model_ids[i], filtered_model_ids))
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
{labels: labels, datasets: datasets}
|
556
|
+
end
|
557
|
+
|
558
|
+
def sub_ref_boolean_chart_processing(data)
|
559
|
+
x_data = data['x_data']
|
560
|
+
y_data = data['y_data']
|
561
|
+
|
562
|
+
ref_model = x_data['attribute'][0..-4]
|
563
|
+
sub_attribute = x_data['sub_attribute']
|
564
|
+
|
565
|
+
datasets = []
|
566
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
567
|
+
labels = x_data['labels-corres'].blank? ? x_data['labels'] : x_data['labels-corres']
|
568
|
+
|
569
|
+
x_data['labels'].each do |l|
|
570
|
+
0.upto(y_data.length-1).each do |i|
|
571
|
+
data = y_data[i].joins(:"#{ref_model}").where("#{ref_model.pluralize}.#{sub_attribute}": true_string?(l))
|
572
|
+
|
573
|
+
datasets[i].push(data.count)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
{labels: labels, datasets: datasets}
|
578
|
+
end
|
579
|
+
|
580
|
+
def text_chart_processing(data)
|
581
|
+
x_data = data['x_data']
|
582
|
+
y_data = data['y_data']
|
583
|
+
|
584
|
+
sub_attribute = x_data['sub_attribute']
|
585
|
+
unless sub_attribute.nil?
|
586
|
+
return sub_has_text_chart_processing(data) if x_data['sub_attribute_from'] == 'has'
|
587
|
+
return sub_ref_text_chart_processing(data)
|
588
|
+
end
|
589
|
+
|
590
|
+
attribute = x_data['attribute']
|
591
|
+
|
592
|
+
datasets = []
|
593
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
594
|
+
|
595
|
+
labels = get_or_pluck_labels(x_data, y_data)
|
596
|
+
|
597
|
+
labels.each do |l|
|
598
|
+
0.upto(y_data.length-1).each do |i|
|
599
|
+
datasets[i].push(y_data[i].where("#{attribute}": l).count)
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
{labels: labels, datasets: datasets}
|
604
|
+
end
|
605
|
+
|
606
|
+
def sub_has_text_chart_processing(data)
|
607
|
+
x_data = data['x_data']
|
608
|
+
y_data = data['y_data']
|
609
|
+
|
610
|
+
model_string = y_data.first.first.class.name.underscore
|
611
|
+
attribute_model = x_data['attribute'].classify.safe_constantize
|
612
|
+
sub_attribute = x_data['sub_attribute']
|
613
|
+
|
614
|
+
datasets = []
|
615
|
+
model_ids = []
|
616
|
+
0.upto(y_data.length-1).each do |i|
|
617
|
+
datasets.push([])
|
618
|
+
model_ids.push(y_data[i].pluck(:id).uniq)
|
619
|
+
end
|
620
|
+
|
621
|
+
labels = get_or_pluck_labels(x_data, y_data)
|
622
|
+
|
623
|
+
labels.each do |l|
|
624
|
+
0.upto(y_data.length-1).each do |i|
|
625
|
+
filtered_model_ids = attribute_model.where("#{sub_attribute}": l).pluck("#{model_string}_id")
|
626
|
+
|
627
|
+
datasets[i].push(count_values_in_common(model_ids[i], filtered_model_ids))
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
{labels: labels, datasets: datasets}
|
632
|
+
end
|
633
|
+
|
634
|
+
def sub_ref_text_chart_processing(data)
|
635
|
+
x_data = data['x_data']
|
636
|
+
y_data = data['y_data']
|
637
|
+
|
638
|
+
ref_model = x_data['attribute'][0..-4]
|
639
|
+
sub_attribute = x_data['sub_attribute']
|
640
|
+
|
641
|
+
datasets = []
|
642
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
643
|
+
|
644
|
+
labels = get_or_pluck_labels(x_data, y_data)
|
645
|
+
|
646
|
+
labels.each do |l|
|
647
|
+
0.upto(y_data.length-1).each do |i|
|
648
|
+
datasets[i].push(y_data[i].joins(:"#{ref_model}").where("#{ref_model.pluralize}.#{sub_attribute} = '#{l}'").count)
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
{labels: labels, datasets: datasets}
|
653
|
+
end
|
654
|
+
|
655
|
+
# Has is necessarily a count : if a sub-attribute is given, then label_type becomes the one of the sub attribute and cannot be has
|
656
|
+
def has_model_chart_processing(data)
|
657
|
+
x_data = data['x_data']
|
658
|
+
y_data = data['y_data']
|
659
|
+
|
660
|
+
has_models = x_data['attribute'].pluralize
|
661
|
+
model_string = y_data.first.first.class.name.underscore
|
662
|
+
# attribute_model = x_data['attribute'].classify.safe_constantize
|
663
|
+
|
664
|
+
datasets = []
|
665
|
+
# model_ids = []
|
666
|
+
0.upto(y_data.length-1).each do |i|
|
667
|
+
datasets.push([])
|
668
|
+
# model_ids.push(y_data[i].pluck(:id).uniq)
|
669
|
+
end
|
670
|
+
# filtered_model_ids = attribute_model.pluck("#{model_string}_id")
|
671
|
+
# count_hash = count_hash(filtered_model_ids)
|
672
|
+
|
673
|
+
labels = x_data['labels']
|
674
|
+
labels.each do |l|
|
675
|
+
0.upto(y_data.length-1).each do |i|
|
676
|
+
split = l.split('-')
|
677
|
+
low = l[-1] == '+' ? l[0..-2] : split[0]
|
678
|
+
high = split.length == 2 ? split[1] : low
|
679
|
+
|
680
|
+
if l[-1] == '+'
|
681
|
+
data = y_data[i].joins("LEFT JOIN #{has_models} ON #{has_models}.#{model_string}_id = #{model_string.pluralize}.id")
|
682
|
+
.group("#{model_string.pluralize}.id")
|
683
|
+
.having("count(#{has_models}.id) >= ?", low)
|
684
|
+
# count = count_hash.select{ |k, v| v >= low<to_i }.count
|
685
|
+
else
|
686
|
+
data = y_data[i].joins("LEFT JOIN #{has_models} ON #{has_models}.#{model_string}_id = #{model_string.pluralize}.id")
|
687
|
+
.group("#{model_string.pluralize}.id")
|
688
|
+
.having("count(#{has_models}.id) BETWEEN ? AND ?", low, high)
|
689
|
+
# count = count_hash.select{ |k, v| v >= low.to_i && v <= high.to_i }.count
|
690
|
+
end
|
691
|
+
|
692
|
+
# datasets[i].push(count)
|
693
|
+
sum = 0
|
694
|
+
data.count.each { |key, count| sum += count }
|
695
|
+
datasets[i].push(sum)
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
{labels: labels, datasets: datasets}
|
700
|
+
end
|
701
|
+
|
702
|
+
def ref_model_chart_processing(data)
|
703
|
+
x_data = data['x_data']
|
704
|
+
y_data = data['y_data']
|
705
|
+
|
706
|
+
ref_model = x_data['attribute']
|
707
|
+
|
708
|
+
datasets = []
|
709
|
+
0.upto(y_data.length-1).each {datasets.push([])}
|
710
|
+
|
711
|
+
labels = ['Having', 'Not having']
|
712
|
+
0.upto(y_data.length-1).each do |i|
|
713
|
+
tot_count = y_data[i].count
|
714
|
+
having_count = y_data[i].where.not("#{ref_model}": nil).count
|
715
|
+
datasets[0].push(having_count).push(tot_count - having_count)
|
716
|
+
end
|
717
|
+
|
718
|
+
{labels: labels, datasets: datasets}
|
719
|
+
end
|
720
|
+
|
721
|
+
|
722
|
+
# Returns the date group size regarding date_info
|
723
|
+
def date_label_group_size(date_info)
|
724
|
+
case date_info['period']
|
725
|
+
when 'day'
|
726
|
+
1
|
727
|
+
when 'week'
|
728
|
+
7
|
729
|
+
else
|
730
|
+
30
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
def get_date_labels(date_info)
|
735
|
+
limit_dates = get_start_and_end_dates(date_info)
|
736
|
+
start_date = limit_dates[0]
|
737
|
+
end_date = limit_dates[1]
|
738
|
+
period_length = (end_date - start_date).to_i
|
739
|
+
group_size = date_label_group_size(date_info)
|
740
|
+
|
741
|
+
labels = []
|
742
|
+
(0..(period_length-1)).step(group_size).each do |i|
|
743
|
+
date = (start_date + i.days)
|
744
|
+
labels.push(date)
|
745
|
+
end
|
746
|
+
labels
|
747
|
+
end
|
748
|
+
|
749
|
+
def get_or_pluck_labels(x_data, y_data)
|
750
|
+
case x_data['label_type']
|
751
|
+
when 'text-auto'
|
752
|
+
sub_attribute = x_data['sub_attribute']
|
753
|
+
pluck_from = sub_attribute.nil? ? y_data.first : x_data['attribute'][0..-4].classify.safe_constantize
|
754
|
+
to_pluck = sub_attribute.nil? ? x_data['attribute'] : sub_attribute
|
755
|
+
|
756
|
+
labels = pluck_from.pluck(:"#{to_pluck}").uniq
|
757
|
+
|
758
|
+
when 'text'
|
759
|
+
labels = x_data['labels']
|
760
|
+
end
|
761
|
+
labels
|
762
|
+
end
|
763
|
+
|
764
|
+
def count_values_in_common(model_ids, filtered_model_ids)
|
765
|
+
values_in_common = (model_ids & filtered_model_ids)
|
766
|
+
|
767
|
+
count_hash = count_hash(filtered_model_ids)
|
768
|
+
|
769
|
+
count = 0
|
770
|
+
values_in_common.each { |v| count += count_hash[v] }
|
771
|
+
|
772
|
+
count
|
773
|
+
end
|
774
|
+
|
775
|
+
def count_hash(array)
|
776
|
+
array.group_by(&:itself).map { |k,v| [k, v.count] }.to_h
|
777
|
+
end
|
778
|
+
|
779
|
+
include FilterHelper
|
780
|
+
def chart_processed_data_from_redis(chart_data)
|
781
|
+
redis_data = nil
|
782
|
+
style = chart_data.delete('style')
|
783
|
+
redis_chart_data = encode_chart_data(chart_data)
|
784
|
+
|
785
|
+
unless chart_data['refresh'] == 'true' || DashCreator.redis_store_variable.nil?
|
786
|
+
redis_data = DashCreator.redis_store_variable.get(redis_chart_data)
|
787
|
+
end
|
788
|
+
|
789
|
+
unless redis_data.nil?
|
790
|
+
processed_data = JSON.parse(redis_data)
|
791
|
+
else
|
792
|
+
chart_data_with_records = prepare_data(chart_data)
|
793
|
+
processed_data = chart_processing(chart_data_with_records).deep_stringify_keys
|
794
|
+
processed_data['last_updated'] = DateTime.now.strftime('%d/%m/%Y - %T')
|
795
|
+
|
796
|
+
# Add chart data to redis
|
797
|
+
DashCreator.redis_store_variable.set(redis_chart_data, processed_data.to_json) unless DashCreator.redis_store_variable.nil?
|
798
|
+
end
|
799
|
+
|
800
|
+
processed_data['style'] = style
|
801
|
+
processed_data['style'] = processed_data['style'].to_unsafe_h unless processed_data['style'].is_a?(Hash)
|
802
|
+
|
803
|
+
processed_data
|
804
|
+
end
|
805
|
+
|
806
|
+
def encode_chart_data(chart_data)
|
807
|
+
# Prepare data to encode
|
808
|
+
encoded_chart_data = chart_data.clone
|
809
|
+
encoded_chart_data = encoded_chart_data.to_unsafe_h unless encoded_chart_data.is_a?(Hash)
|
810
|
+
encoded_chart_data.delete('controller')
|
811
|
+
encoded_chart_data.delete('action')
|
812
|
+
encoded_chart_data.delete('refresh')
|
813
|
+
|
814
|
+
# Encode
|
815
|
+
hash_hash(encoded_chart_data)
|
816
|
+
end
|
817
|
+
|
818
|
+
# This helper function is used in create_chart.js.erb to hash param hash to store it in Redis
|
819
|
+
def hash_hash(h)
|
820
|
+
require 'digest/sha1'
|
821
|
+
str = recursive_flatten(h).sort.join
|
822
|
+
Digest::SHA1.hexdigest(str)
|
823
|
+
end
|
824
|
+
|
825
|
+
def recursive_flatten(h)
|
826
|
+
h.flatten(-1).map{|el| el.is_a?(Hash) ? recursive_flatten(el) : el}.flatten
|
827
|
+
end
|
828
|
+
end
|
829
|
+
end
|