reports_kits 0.7.5 → 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/.rubocop.yml +85 -0
- data/.travis.yml +21 -0
- data/Appraisals +27 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +35 -0
- data/Rakefile +2 -0
- data/app/assets/javascripts/reports_kits/application.js +14 -0
- data/app/assets/javascripts/reports_kits/lib/_init.js +9 -0
- data/app/assets/javascripts/reports_kits/lib/chart.js +73 -0
- data/app/assets/javascripts/reports_kits/lib/report.js +135 -0
- data/app/assets/javascripts/reports_kits/lib/table.js +87 -0
- data/app/assets/javascripts/reports_kits/vendor/chart.js +12269 -0
- data/app/assets/javascripts/reports_kits/vendor/daterangepicker.js +1627 -0
- data/app/assets/javascripts/reports_kits/vendor/jquery.tablesorter.min.js +4 -0
- data/app/assets/javascripts/reports_kits/vendor/moment.js +4040 -0
- data/app/assets/javascripts/reports_kits/vendor/select2.full.js +6436 -0
- data/app/assets/stylesheets/reports_kits/application.css.scss +3 -0
- data/app/assets/stylesheets/reports_kits/reports.css.sass +33 -0
- data/app/assets/stylesheets/reports_kits/select2_overrides.css.sass +7 -0
- data/app/assets/stylesheets/reports_kits/vendor/daterangepicker.css +269 -0
- data/app/assets/stylesheets/reports_kits/vendor/select2-bootstrap.css +721 -0
- data/app/assets/stylesheets/reports_kits/vendor/select2.css +484 -0
- data/config/initializers/mime_types.rb +1 -0
- data/config/routes.rb +10 -0
- data/docs/images/demo.gif +0 -0
- data/docs/images/users_by_created_at.png +0 -0
- data/gemfiles/mysql.gemfile +7 -0
- data/gemfiles/mysql.gemfile.lock +167 -0
- data/gemfiles/postgresql.gemfile +7 -0
- data/gemfiles/postgresql.gemfile.lock +165 -0
- data/gemfiles/postgresql_rails_5.1.4.gemfile +8 -0
- data/gemfiles/postgresql_rails_5.1.4.gemfile.lock +168 -0
- data/gemfiles/rails_4_mysql.gemfile +8 -0
- data/gemfiles/rails_4_mysql.gemfile.lock +165 -0
- data/gemfiles/rails_4_postgresql.gemfile +8 -0
- data/gemfiles/rails_4_postgresql.gemfile.lock +163 -0
- data/gemfiles/rails_5.1.4_postgresql.gemfile +8 -0
- data/gemfiles/rails_5.1.4_postgresql.gemfile.lock +169 -0
- data/gemfiles/rails_5_mysql.gemfile +8 -0
- data/gemfiles/rails_5_mysql.gemfile.lock +171 -0
- data/gemfiles/rails_5_postgresql.gemfile +8 -0
- data/gemfiles/rails_5_postgresql.gemfile.lock +169 -0
- data/lib/reports_kits/base_controller.rb +17 -0
- data/lib/reports_kits/cache.rb +37 -0
- data/lib/reports_kits/configuration.rb +50 -0
- data/lib/reports_kits/engine.rb +21 -0
- data/lib/reports_kits/entity.rb +3 -0
- data/lib/reports_kits/filters_controller.rb +11 -0
- data/lib/reports_kits/form_builder.rb +66 -0
- data/lib/reports_kits/helper.rb +24 -0
- data/lib/reports_kits/model.rb +16 -0
- data/lib/reports_kits/model_configuration.rb +28 -0
- data/lib/reports_kits/normalized_params.rb +16 -0
- data/lib/reports_kits/order.rb +33 -0
- data/lib/reports_kits/relative_time.rb +42 -0
- data/lib/reports_kits/report_builder.rb +88 -0
- data/lib/reports_kits/reports/abstract_series.rb +9 -0
- data/lib/reports_kits/reports/adapters/mysql.rb +26 -0
- data/lib/reports_kits/reports/adapters/postgresql.rb +26 -0
- data/lib/reports_kits/reports/composite_series.rb +48 -0
- data/lib/reports_kits/reports/contextual_filter.rb +19 -0
- data/lib/reports_kits/reports/data/add_table_aggregations.rb +105 -0
- data/lib/reports_kits/reports/data/aggregate_composite.rb +97 -0
- data/lib/reports_kits/reports/data/aggregate_one_dimension.rb +39 -0
- data/lib/reports_kits/reports/data/aggregate_two_dimensions.rb +39 -0
- data/lib/reports_kits/reports/data/chart_data_for_data_method.rb +62 -0
- data/lib/reports_kits/reports/data/chart_options.rb +155 -0
- data/lib/reports_kits/reports/data/format_one_dimension.rb +140 -0
- data/lib/reports_kits/reports/data/format_table.rb +63 -0
- data/lib/reports_kits/reports/data/format_two_dimensions.rb +143 -0
- data/lib/reports_kits/reports/data/generate.rb +156 -0
- data/lib/reports_kits/reports/data/generate_for_properties.rb +97 -0
- data/lib/reports_kits/reports/data/normalize_properties.rb +62 -0
- data/lib/reports_kits/reports/data/populate_one_dimension.rb +54 -0
- data/lib/reports_kits/reports/data/populate_two_dimensions.rb +104 -0
- data/lib/reports_kits/reports/data/utils.rb +178 -0
- data/lib/reports_kits/reports/dimension.rb +27 -0
- data/lib/reports_kits/reports/dimension_with_series.rb +144 -0
- data/lib/reports_kits/reports/filter.rb +29 -0
- data/lib/reports_kits/reports/filter_types/base.rb +48 -0
- data/lib/reports_kits/reports/filter_types/boolean.rb +47 -0
- data/lib/reports_kits/reports/filter_types/datetime.rb +51 -0
- data/lib/reports_kits/reports/filter_types/number.rb +30 -0
- data/lib/reports_kits/reports/filter_types/records.rb +26 -0
- data/lib/reports_kits/reports/filter_types/string.rb +38 -0
- data/lib/reports_kits/reports/filter_with_series.rb +97 -0
- data/lib/reports_kits/reports/generate_autocomplete_method_results.rb +29 -0
- data/lib/reports_kits/reports/generate_autocomplete_results.rb +57 -0
- data/lib/reports_kits/reports/inferrable_configuration.rb +116 -0
- data/lib/reports_kits/reports/model_settings.rb +30 -0
- data/lib/reports_kits/reports/properties.rb +10 -0
- data/lib/reports_kits/reports/properties_to_filter.rb +40 -0
- data/lib/reports_kits/reports/series.rb +121 -0
- data/lib/reports_kits/reports_controller.rb +65 -0
- data/lib/reports_kits/utils.rb +11 -0
- data/lib/reports_kits/value.rb +3 -0
- data/lib/reports_kits/version.rb +3 -0
- data/lib/reports_kits.rb +79 -0
- data/reports_kits.gemspec +26 -0
- data/spec/factories/issue_factory.rb +4 -0
- data/spec/factories/issues_label_factory.rb +4 -0
- data/spec/factories/label_factory.rb +4 -0
- data/spec/factories/pro_repo_factory.rb +5 -0
- data/spec/factories/repo_factory.rb +5 -0
- data/spec/factories/tag_factory.rb +4 -0
- data/spec/fixtures/generate_inputs.yml +254 -0
- data/spec/fixtures/generate_outputs.yml +1216 -0
- data/spec/reports_kit/form_builder_spec.rb +26 -0
- data/spec/reports_kit/relative_time_spec.rb +29 -0
- data/spec/reports_kit/reports/data/generate/contextual_filters_spec.rb +60 -0
- data/spec/reports_kit/reports/data/generate_spec.rb +1371 -0
- data/spec/reports_kit/reports/data/normalize_properties_spec.rb +196 -0
- data/spec/reports_kit/reports/dimension_with_series_spec.rb +67 -0
- data/spec/reports_kit/reports/filter_with_series_spec.rb +39 -0
- data/spec/reports_kit/reports/generate_autocomplete_results_spec.rb +69 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/support/config.rb +41 -0
- data/spec/support/example_data_methods.rb +25 -0
- data/spec/support/factory_girl.rb +5 -0
- data/spec/support/helpers.rb +25 -0
- data/spec/support/models/issue.rb +14 -0
- data/spec/support/models/issues_label.rb +4 -0
- data/spec/support/models/label.rb +5 -0
- data/spec/support/models/pro/repo.rb +5 -0
- data/spec/support/models/pro/special_issue.rb +4 -0
- data/spec/support/models/repo.rb +13 -0
- data/spec/support/models/tag.rb +4 -0
- data/spec/support/schema.rb +39 -0
- metadata +134 -4
@@ -0,0 +1,39 @@
|
|
1
|
+
module ReportsKits
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class AggregateOneDimension
|
5
|
+
attr_accessor :series, :dimension
|
6
|
+
|
7
|
+
def initialize(series)
|
8
|
+
self.series = series
|
9
|
+
self.dimension = series.dimensions[0]
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform
|
13
|
+
dimension_keys_values
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def dimension_keys_values
|
19
|
+
relation = series.filtered_relation
|
20
|
+
relation = relation.group(dimension.group_expression)
|
21
|
+
relation = relation.joins(dimension.joins) if dimension.joins
|
22
|
+
relation = relation.limit(dimension.dimension_instances_limit) if dimension.dimension_instances_limit
|
23
|
+
relation = relation.order(order)
|
24
|
+
relation = series.edit_relation_method.call(relation) if series.edit_relation_method
|
25
|
+
dimension_keys_values = relation.distinct.public_send(*series.aggregate_function)
|
26
|
+
dimension_keys_values = Utils.populate_sparse_hash(dimension_keys_values, dimension: dimension)
|
27
|
+
dimension_keys_values.delete(nil)
|
28
|
+
dimension_keys_values.delete('')
|
29
|
+
dimension_keys_values = dimension_keys_values.take(series.limit) if series.limit
|
30
|
+
Hash[dimension_keys_values]
|
31
|
+
end
|
32
|
+
|
33
|
+
def order
|
34
|
+
dimension.configured_by_time? ? '2' : '1 DESC'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ReportsKits
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class AggregateTwoDimensions
|
5
|
+
attr_accessor :series, :dimension, :second_dimension
|
6
|
+
|
7
|
+
def initialize(series)
|
8
|
+
self.series = series
|
9
|
+
self.dimension = series.dimensions[0]
|
10
|
+
self.second_dimension = series.dimensions[1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform
|
14
|
+
dimension_keys_values
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def dimension_keys_values
|
20
|
+
relation = series.filtered_relation
|
21
|
+
relation = relation.group(dimension.group_expression, second_dimension.group_expression)
|
22
|
+
relation = relation.joins(dimension.joins) if dimension.joins
|
23
|
+
relation = relation.joins(second_dimension.joins) if second_dimension.joins
|
24
|
+
relation = relation.order(order)
|
25
|
+
relation = series.edit_relation_method.call(relation) if series.edit_relation_method
|
26
|
+
dimension_keys_values = relation.distinct.public_send(*series.aggregate_function)
|
27
|
+
dimension_keys_values = Utils.populate_sparse_hash(dimension_keys_values, dimension: dimension)
|
28
|
+
dimension_keys_values.delete(nil)
|
29
|
+
dimension_keys_values.delete('')
|
30
|
+
Hash[dimension_keys_values]
|
31
|
+
end
|
32
|
+
|
33
|
+
def order
|
34
|
+
dimension.configured_by_time? ? '2' : '1 DESC'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ReportsKits
|
2
|
+
class ChartDataForDataMethod
|
3
|
+
attr_accessor :properties
|
4
|
+
|
5
|
+
def initialize(properties)
|
6
|
+
self.properties = properties
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform
|
10
|
+
klass, method_name = ReportsKits::Utils.string_to_class_method(properties[:data_method], 'data_method')
|
11
|
+
raw_data = klass.public_send(method_name, properties)
|
12
|
+
{
|
13
|
+
raw_data: raw_data,
|
14
|
+
formatted_data: format_returned_data(raw_data)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def format_returned_data(returned_data)
|
21
|
+
return [] if returned_data.blank?
|
22
|
+
returned_data = returned_data.to_a
|
23
|
+
|
24
|
+
first_key = returned_data.first.first
|
25
|
+
if first_key.is_a?(Array) && first_key.length == 2
|
26
|
+
format_two_dimensional_returned_data(returned_data)
|
27
|
+
else
|
28
|
+
{
|
29
|
+
labels: returned_data.map(&:first),
|
30
|
+
datasets: [
|
31
|
+
{
|
32
|
+
data: returned_data.map(&:last)
|
33
|
+
}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def format_two_dimensional_returned_data(returned_data)
|
40
|
+
primary_keys_secondary_keys_values = {}
|
41
|
+
secondary_keys_primary_keys_values = {}
|
42
|
+
secondary_keys = []
|
43
|
+
returned_data.each do |(primary_key, secondary_key), value|
|
44
|
+
secondary_keys_primary_keys_values[secondary_key] ||= {}
|
45
|
+
secondary_keys_primary_keys_values[secondary_key][primary_key] = value
|
46
|
+
primary_keys_secondary_keys_values[primary_key] ||= {}
|
47
|
+
primary_keys_secondary_keys_values[primary_key][secondary_key] = value
|
48
|
+
end
|
49
|
+
primary_keys = primary_keys_secondary_keys_values.keys
|
50
|
+
datasets = secondary_keys_primary_keys_values.map do |secondary_key, primary_keys_values|
|
51
|
+
{
|
52
|
+
label: secondary_key,
|
53
|
+
data: primary_keys.map { |primary_key| primary_keys_values[primary_key] || 0 }
|
54
|
+
}
|
55
|
+
end
|
56
|
+
{
|
57
|
+
labels: primary_keys,
|
58
|
+
datasets: datasets
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module ReportsKits
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class ChartOptions
|
5
|
+
DEFAULT_COLORS = %w(
|
6
|
+
#1f77b4
|
7
|
+
#aec7e8
|
8
|
+
#ff7f0e
|
9
|
+
#ffbb78
|
10
|
+
#2ca02c
|
11
|
+
#98df8a
|
12
|
+
#d62728
|
13
|
+
#ff9896
|
14
|
+
#9467bd
|
15
|
+
#c5b0d5
|
16
|
+
#8c564b
|
17
|
+
#c49c94
|
18
|
+
#e377c2
|
19
|
+
#f7b6d2
|
20
|
+
#7f7f7f
|
21
|
+
#c7c7c7
|
22
|
+
#bcbd22
|
23
|
+
#dbdb8d
|
24
|
+
#17becf
|
25
|
+
#9edae5
|
26
|
+
).freeze
|
27
|
+
DEFAULT_OPTIONS = {
|
28
|
+
scales: {
|
29
|
+
xAxes: [{
|
30
|
+
gridLines: {
|
31
|
+
display: false
|
32
|
+
},
|
33
|
+
barPercentage: 0.9,
|
34
|
+
categoryPercentage: 0.9
|
35
|
+
}],
|
36
|
+
yAxes: [{
|
37
|
+
ticks: {
|
38
|
+
beginAtZero: true
|
39
|
+
}
|
40
|
+
}]
|
41
|
+
},
|
42
|
+
legend: {
|
43
|
+
labels: {
|
44
|
+
usePointStyle: true
|
45
|
+
}
|
46
|
+
},
|
47
|
+
maintainAspectRatio: false,
|
48
|
+
tooltips: {
|
49
|
+
xPadding: 8,
|
50
|
+
yPadding: 7
|
51
|
+
}
|
52
|
+
}.freeze
|
53
|
+
|
54
|
+
attr_accessor :data, :options, :chart_options, :inferred_options, :dataset_options, :type
|
55
|
+
|
56
|
+
def initialize(data, options:, inferred_options: {})
|
57
|
+
self.data = data
|
58
|
+
self.options = options.try(:except, :options) || {}
|
59
|
+
self.chart_options = options.try(:[], :options) || {}
|
60
|
+
self.dataset_options = options.try(:[], :datasets)
|
61
|
+
self.type = options.try(:[], :type) || 'bar'
|
62
|
+
|
63
|
+
self.options = inferred_options.deep_merge(self.options) if inferred_options.present?
|
64
|
+
end
|
65
|
+
|
66
|
+
def perform
|
67
|
+
set_colors
|
68
|
+
set_chart_options
|
69
|
+
set_dataset_options
|
70
|
+
set_type
|
71
|
+
data
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def set_colors
|
77
|
+
if donut_or_pie_chart?
|
78
|
+
set_record_scoped_colors
|
79
|
+
else
|
80
|
+
set_dataset_scoped_colors
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_record_scoped_colors
|
85
|
+
data[:chart_data][:datasets] = data[:chart_data][:datasets].map do |dataset|
|
86
|
+
length = dataset[:data].length
|
87
|
+
dataset[:backgroundColor] = DEFAULT_COLORS * (length.to_f / DEFAULT_COLORS.length).ceil
|
88
|
+
dataset
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_dataset_scoped_colors
|
93
|
+
data[:chart_data][:datasets] = data[:chart_data][:datasets].map.with_index do |dataset, index|
|
94
|
+
color = DEFAULT_COLORS[index % DEFAULT_COLORS.length]
|
95
|
+
dataset[:backgroundColor] = color
|
96
|
+
dataset[:borderColor] = color
|
97
|
+
dataset
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def default_options
|
102
|
+
@default_options ||= begin
|
103
|
+
return {} if donut_or_pie_chart?
|
104
|
+
|
105
|
+
default_options = DEFAULT_OPTIONS.deep_dup
|
106
|
+
|
107
|
+
x_axis_label = options[:x_axis_label]
|
108
|
+
if x_axis_label
|
109
|
+
default_options[:scales] ||= {}
|
110
|
+
default_options[:scales][:xAxes] ||= []
|
111
|
+
default_options[:scales][:xAxes][0] ||= {}
|
112
|
+
default_options[:scales][:xAxes][0][:scaleLabel] ||= {}
|
113
|
+
default_options[:scales][:xAxes][0][:scaleLabel][:display] ||= true
|
114
|
+
default_options[:scales][:xAxes][0][:scaleLabel][:labelString] ||= x_axis_label
|
115
|
+
end
|
116
|
+
|
117
|
+
y_axis_label = options[:y_axis_label]
|
118
|
+
if y_axis_label
|
119
|
+
default_options[:scales] ||= {}
|
120
|
+
default_options[:scales][:yAxes] ||= []
|
121
|
+
default_options[:scales][:yAxes][0] ||= {}
|
122
|
+
default_options[:scales][:yAxes][0][:scaleLabel] ||= {}
|
123
|
+
default_options[:scales][:yAxes][0][:scaleLabel][:display] ||= true
|
124
|
+
default_options[:scales][:yAxes][0][:scaleLabel][:labelString] ||= y_axis_label
|
125
|
+
end
|
126
|
+
|
127
|
+
default_options
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def set_chart_options
|
132
|
+
merged_options = default_options
|
133
|
+
merged_options = merged_options.deep_merge(chart_options) if chart_options
|
134
|
+
data[:chart_data][:options] = merged_options
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_dataset_options
|
138
|
+
return if data[:chart_data][:datasets].blank? || dataset_options.blank?
|
139
|
+
data[:chart_data][:datasets] = data[:chart_data][:datasets].map do |dataset|
|
140
|
+
dataset.merge(dataset_options)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def set_type
|
145
|
+
return if type.blank?
|
146
|
+
data[:type] = type
|
147
|
+
end
|
148
|
+
|
149
|
+
def donut_or_pie_chart?
|
150
|
+
type.in?(%w(donut pie))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module ReportsKits
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class FormatOneDimension
|
5
|
+
attr_accessor :serieses_results, :serieses, :order, :limit
|
6
|
+
|
7
|
+
def initialize(serieses_results, order:, limit:)
|
8
|
+
self.serieses_results = serieses_results
|
9
|
+
self.serieses = serieses_results.keys
|
10
|
+
self.order = order
|
11
|
+
self.limit = limit
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
{
|
16
|
+
entities: entities,
|
17
|
+
datasets: datasets
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def entities
|
24
|
+
sorted_dimension_keys.map do |key|
|
25
|
+
Utils.dimension_key_to_entity(key, primary_dimension_with_series, dimension_ids_dimension_instances)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def datasets
|
30
|
+
sorted_serieses_results.map do |series, result|
|
31
|
+
values = result.values.map do |raw_value|
|
32
|
+
Utils.raw_value_to_value(raw_value, series.value_format_method)
|
33
|
+
end
|
34
|
+
{
|
35
|
+
entity: series,
|
36
|
+
values: values
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def dimension_summaries
|
42
|
+
@dimension_summaries ||= raw_dimension_keys.map do |dimension_key|
|
43
|
+
label = Utils.dimension_key_to_label(dimension_key, primary_dimension_with_series, dimension_ids_dimension_instances)
|
44
|
+
next if label.blank?
|
45
|
+
[dimension_key, label]
|
46
|
+
end.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
def sorted_dimension_keys
|
50
|
+
sorted_serieses_results.first.last.keys
|
51
|
+
end
|
52
|
+
|
53
|
+
def dimension_keys_sorted_by_label
|
54
|
+
dimension_summaries.sort_by { |key, label| label.is_a?(String) ? label.downcase : label }.map(&:first)
|
55
|
+
end
|
56
|
+
|
57
|
+
def dimension_keys
|
58
|
+
dimension_summaries.map(&:first)
|
59
|
+
end
|
60
|
+
|
61
|
+
def raw_dimension_keys
|
62
|
+
serieses_results.first.last.keys
|
63
|
+
end
|
64
|
+
|
65
|
+
def dimension_ids_dimension_instances
|
66
|
+
@dimension_ids_dimension_instances ||= begin
|
67
|
+
dimension_ids = raw_dimension_keys
|
68
|
+
Utils.dimension_to_dimension_ids_dimension_instances(primary_dimension_with_series, dimension_ids)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def primary_dimension_with_series
|
73
|
+
@primary_dimension_with_series ||= DimensionWithSeries.new(dimension: primary_series.dimensions.first, series: primary_series)
|
74
|
+
end
|
75
|
+
|
76
|
+
def primary_series
|
77
|
+
serieses.first
|
78
|
+
end
|
79
|
+
|
80
|
+
def sorted_serieses_results
|
81
|
+
@sorted_serieses_results ||= begin
|
82
|
+
if order.relation == 'dimension1' && order.field == 'label'
|
83
|
+
sorted_serieses_results = serieses_results.map do |series, dimension_keys_values|
|
84
|
+
dimension_keys_values = filter_dimension_keys_values(dimension_keys_values)
|
85
|
+
sorted_dimension_keys_values = dimension_keys_values.sort_by { |key, _| dimension_keys_sorted_by_label.index(key) }
|
86
|
+
sorted_dimension_keys_values = sorted_dimension_keys_values.reverse if order.direction == 'desc'
|
87
|
+
[series, Hash[sorted_dimension_keys_values]]
|
88
|
+
end
|
89
|
+
elsif (order.relation == 'dimension1' && order.field.nil?) || (order.relation == 0)
|
90
|
+
sorted_serieses_results = serieses_results.map do |series, dimension_keys_values|
|
91
|
+
dimension_keys_values = filter_dimension_keys_values(dimension_keys_values)
|
92
|
+
sorted_dimension_keys_values = dimension_keys_values.sort_by(&:first)
|
93
|
+
sorted_dimension_keys_values = sorted_dimension_keys_values.reverse if order.direction == 'desc'
|
94
|
+
[series, Hash[sorted_dimension_keys_values]]
|
95
|
+
end
|
96
|
+
elsif order.relation.is_a?(Fixnum)
|
97
|
+
series_index = order.relation - 1
|
98
|
+
raise ArgumentError.new("Invalid order column: #{order.relation}") unless series_index.in?((0..(serieses_results.length - 1)))
|
99
|
+
dimension_keys_values = serieses_results.values.to_a[series_index]
|
100
|
+
sorted_dimension_keys = dimension_keys_values.sort_by(&:last).map(&:first)
|
101
|
+
sorted_dimension_keys = sorted_dimension_keys.reverse if order.direction == 'desc'
|
102
|
+
sorted_serieses_results = serieses_results.map do |series, dimension_keys_values|
|
103
|
+
dimension_keys_values = filter_dimension_keys_values(dimension_keys_values)
|
104
|
+
dimension_keys_values = dimension_keys_values.sort_by { |dimension_key, _| sorted_dimension_keys.index(dimension_key) }
|
105
|
+
[series, Hash[dimension_keys_values]]
|
106
|
+
end
|
107
|
+
elsif order.relation == 'count'
|
108
|
+
dimension_keys_sums = Hash.new(0)
|
109
|
+
serieses_results.values.each do |dimension_keys_values|
|
110
|
+
dimension_keys_values = filter_dimension_keys_values(dimension_keys_values)
|
111
|
+
dimension_keys_values.each do |dimension_key, value|
|
112
|
+
dimension_keys_sums[dimension_key] += value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
sorted_dimension_keys = dimension_keys_sums.sort_by(&:last).map(&:first)
|
116
|
+
sorted_dimension_keys = sorted_dimension_keys.reverse if order.direction == 'desc'
|
117
|
+
sorted_serieses_results = serieses_results.map do |series, dimension_keys_values|
|
118
|
+
dimension_keys_values = filter_dimension_keys_values(dimension_keys_values)
|
119
|
+
dimension_keys_values = dimension_keys_values.sort_by { |dimension_key, _| sorted_dimension_keys.index(dimension_key) }
|
120
|
+
[series, Hash[dimension_keys_values]]
|
121
|
+
end
|
122
|
+
else
|
123
|
+
sorted_serieses_results = serieses_results
|
124
|
+
end
|
125
|
+
if limit
|
126
|
+
sorted_serieses_results = sorted_serieses_results.map do |series, results|
|
127
|
+
[series, Hash[results.to_a.take(limit)]]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
Hash[sorted_serieses_results]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def filter_dimension_keys_values(dimension_keys_values)
|
135
|
+
dimension_keys_values.select { |key, values| dimension_keys.include?(key) }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module ReportsKits
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class FormatTable
|
5
|
+
attr_accessor :data, :format, :first_column_label, :report_options
|
6
|
+
|
7
|
+
def initialize(data, format:, first_column_label:, report_options:)
|
8
|
+
self.data = data
|
9
|
+
self.format = format
|
10
|
+
self.first_column_label = first_column_label
|
11
|
+
self.report_options = report_options || {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
table_data
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def table_data
|
21
|
+
data_rows_with_labels = data_rows.map.with_index do |data_row, index|
|
22
|
+
label = format_string(data[:labels][index])
|
23
|
+
[label] + data_row
|
24
|
+
end
|
25
|
+
[column_names] + data_rows_with_labels
|
26
|
+
end
|
27
|
+
|
28
|
+
def column_names
|
29
|
+
column_names_column_values[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
def column_values
|
33
|
+
column_names_column_values[1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def data_rows
|
37
|
+
@data_rows ||= column_values.transpose
|
38
|
+
end
|
39
|
+
|
40
|
+
def column_names_column_values
|
41
|
+
@column_names_column_values ||= begin
|
42
|
+
column_names = [format_string(first_column_label)]
|
43
|
+
column_values = []
|
44
|
+
data[:datasets].each do |dataset|
|
45
|
+
column_names << format_string(dataset[:label])
|
46
|
+
column_values << dataset[:data]
|
47
|
+
end
|
48
|
+
[column_names, column_values]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def format_string(string)
|
53
|
+
return string unless string && strip_html_tags?
|
54
|
+
ActionView::Base.full_sanitizer.sanitize(string)
|
55
|
+
end
|
56
|
+
|
57
|
+
def strip_html_tags?
|
58
|
+
format == 'csv'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module ReportsKits
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class FormatTwoDimensions
|
5
|
+
attr_accessor :series, :dimension, :second_dimension, :dimension_keys_values, :order, :limit
|
6
|
+
|
7
|
+
def initialize(series, dimension_keys_values, order:, limit:)
|
8
|
+
self.series = series
|
9
|
+
self.dimension = series.dimensions[0]
|
10
|
+
self.second_dimension = series.dimensions[1]
|
11
|
+
self.dimension_keys_values = dimension_keys_values
|
12
|
+
self.order = order
|
13
|
+
self.limit = limit
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
{
|
18
|
+
entities: entities,
|
19
|
+
datasets: datasets
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def entities
|
26
|
+
sorted_primary_keys.map do |primary_key|
|
27
|
+
Utils.dimension_key_to_entity(primary_key, dimension, dimension_ids_dimension_instances)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def datasets
|
32
|
+
secondary_keys_values = sorted_secondary_keys.map do |secondary_key|
|
33
|
+
raw_values = sorted_primary_keys.map do |primary_key|
|
34
|
+
primary_keys_secondary_keys_values[primary_key][secondary_key]
|
35
|
+
end
|
36
|
+
values = raw_values.map do |raw_value|
|
37
|
+
Utils.raw_value_to_value(raw_value, series.value_format_method)
|
38
|
+
end
|
39
|
+
[secondary_key, values]
|
40
|
+
end
|
41
|
+
secondary_keys_values.map do |secondary_key, values|
|
42
|
+
next if secondary_key.blank?
|
43
|
+
{
|
44
|
+
entity: Utils.dimension_key_to_entity(secondary_key, second_dimension, second_dimension_ids_dimension_instances),
|
45
|
+
values: values
|
46
|
+
}
|
47
|
+
end.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
def sorted_primary_keys_secondary_keys_values
|
51
|
+
@sorted_primary_keys_secondary_keys_values ||= begin
|
52
|
+
if order.relation == 'dimension1' && order.field == 'label'
|
53
|
+
primary_keys_secondary_keys_values
|
54
|
+
sorted_primary_keys_secondary_keys_values = primary_keys_secondary_keys_values.sort_by do |primary_key, _|
|
55
|
+
primary_dimension_keys_sorted_by_label.index(primary_key)
|
56
|
+
end
|
57
|
+
elsif order.relation == 'dimension1' && order.field.nil?
|
58
|
+
sorted_primary_keys_secondary_keys_values = primary_keys_secondary_keys_values.sort_by do |primary_key, _|
|
59
|
+
primary_key
|
60
|
+
end
|
61
|
+
elsif order.relation == 'count'
|
62
|
+
primary_keys_sums = Hash.new(0)
|
63
|
+
primary_keys_secondary_keys_values.each do |primary_key, secondary_keys_values|
|
64
|
+
primary_keys_sums[primary_key] += secondary_keys_values.values.sum
|
65
|
+
end
|
66
|
+
sorted_primary_keys = primary_keys_sums.sort_by(&:last).map(&:first)
|
67
|
+
sorted_primary_keys_secondary_keys_values = primary_keys_secondary_keys_values.sort_by do |primary_key, _|
|
68
|
+
sorted_primary_keys.index(primary_key)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
dimension_keys_values
|
72
|
+
end
|
73
|
+
sorted_primary_keys_secondary_keys_values = sorted_primary_keys_secondary_keys_values.reverse if order.direction == 'desc'
|
74
|
+
Hash[sorted_primary_keys_secondary_keys_values]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def primary_keys_secondary_keys_values
|
79
|
+
@primary_keys_secondary_keys_values ||= begin
|
80
|
+
primary_keys_secondary_keys_values = {}
|
81
|
+
dimension_keys_values.each do |(primary_key, secondary_key), value|
|
82
|
+
primary_key = primary_key.to_date if primary_key.is_a?(Time)
|
83
|
+
secondary_key = secondary_key.to_date if secondary_key.is_a?(Time)
|
84
|
+
primary_keys_secondary_keys_values[primary_key] ||= {}
|
85
|
+
primary_keys_secondary_keys_values[primary_key][secondary_key] = value
|
86
|
+
end
|
87
|
+
primary_keys_secondary_keys_values
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def dimension_ids_dimension_instances
|
92
|
+
@dimension_ids_dimension_instances ||= begin
|
93
|
+
Utils.dimension_to_dimension_ids_dimension_instances(dimension, primary_keys)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def second_dimension_ids_dimension_instances
|
98
|
+
@second_dimension_ids_dimension_instances ||= begin
|
99
|
+
Utils.dimension_to_dimension_ids_dimension_instances(second_dimension, secondary_keys)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def sorted_primary_keys
|
104
|
+
@sorted_primary_keys ||= begin
|
105
|
+
keys = sorted_primary_keys_secondary_keys_values.keys
|
106
|
+
limit = dimension.dimension_instances_limit
|
107
|
+
keys = keys.first(limit) if limit
|
108
|
+
keys
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def sorted_secondary_keys
|
113
|
+
@sorted_secondary_keys ||= begin
|
114
|
+
keys = sorted_primary_keys_secondary_keys_values.values.first.keys
|
115
|
+
limit = second_dimension.dimension_instances_limit
|
116
|
+
keys = keys.first(limit) if limit
|
117
|
+
keys
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def primary_summaries
|
122
|
+
primary_keys.map do |key|
|
123
|
+
label = Utils.dimension_key_to_label(key, dimension, dimension_ids_dimension_instances)
|
124
|
+
next if label.blank?
|
125
|
+
[key, label]
|
126
|
+
end.compact
|
127
|
+
end
|
128
|
+
|
129
|
+
def primary_dimension_keys_sorted_by_label
|
130
|
+
@primary_dimension_keys_sorted_by_label ||= primary_summaries.sort_by { |key, label| label.is_a?(String) ? label.downcase : label }.map(&:first)
|
131
|
+
end
|
132
|
+
|
133
|
+
def primary_keys
|
134
|
+
@primary_keys ||= dimension_keys_values.keys.map(&:first).uniq
|
135
|
+
end
|
136
|
+
|
137
|
+
def secondary_keys
|
138
|
+
@secondary_keys ||= dimension_keys_values.keys.map(&:last).uniq
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|