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
|