reports_kits 0.7.5 → 0.7.7

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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.rubocop.yml +85 -0
  4. data/.travis.yml +21 -0
  5. data/Appraisals +27 -0
  6. data/Gemfile +3 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +35 -0
  9. data/Rakefile +2 -0
  10. data/app/assets/javascripts/reports_kits/application.js +14 -0
  11. data/app/assets/javascripts/reports_kits/lib/_init.js +9 -0
  12. data/app/assets/javascripts/reports_kits/lib/chart.js +73 -0
  13. data/app/assets/javascripts/reports_kits/lib/report.js +135 -0
  14. data/app/assets/javascripts/reports_kits/lib/table.js +87 -0
  15. data/app/assets/javascripts/reports_kits/vendor/chart.js +12269 -0
  16. data/app/assets/javascripts/reports_kits/vendor/daterangepicker.js +1627 -0
  17. data/app/assets/javascripts/reports_kits/vendor/jquery.tablesorter.min.js +4 -0
  18. data/app/assets/javascripts/reports_kits/vendor/moment.js +4040 -0
  19. data/app/assets/javascripts/reports_kits/vendor/select2.full.js +6436 -0
  20. data/app/assets/stylesheets/reports_kits/application.css.scss +3 -0
  21. data/app/assets/stylesheets/reports_kits/reports.css.sass +33 -0
  22. data/app/assets/stylesheets/reports_kits/select2_overrides.css.sass +7 -0
  23. data/app/assets/stylesheets/reports_kits/vendor/daterangepicker.css +269 -0
  24. data/app/assets/stylesheets/reports_kits/vendor/select2-bootstrap.css +721 -0
  25. data/app/assets/stylesheets/reports_kits/vendor/select2.css +484 -0
  26. data/config/initializers/mime_types.rb +1 -0
  27. data/config/routes.rb +10 -0
  28. data/docs/images/demo.gif +0 -0
  29. data/docs/images/users_by_created_at.png +0 -0
  30. data/gemfiles/mysql.gemfile +7 -0
  31. data/gemfiles/mysql.gemfile.lock +167 -0
  32. data/gemfiles/postgresql.gemfile +7 -0
  33. data/gemfiles/postgresql.gemfile.lock +165 -0
  34. data/gemfiles/postgresql_rails_5.1.4.gemfile +8 -0
  35. data/gemfiles/postgresql_rails_5.1.4.gemfile.lock +168 -0
  36. data/gemfiles/rails_4_mysql.gemfile +8 -0
  37. data/gemfiles/rails_4_mysql.gemfile.lock +165 -0
  38. data/gemfiles/rails_4_postgresql.gemfile +8 -0
  39. data/gemfiles/rails_4_postgresql.gemfile.lock +163 -0
  40. data/gemfiles/rails_5.1.4_postgresql.gemfile +8 -0
  41. data/gemfiles/rails_5.1.4_postgresql.gemfile.lock +169 -0
  42. data/gemfiles/rails_5_mysql.gemfile +8 -0
  43. data/gemfiles/rails_5_mysql.gemfile.lock +171 -0
  44. data/gemfiles/rails_5_postgresql.gemfile +8 -0
  45. data/gemfiles/rails_5_postgresql.gemfile.lock +169 -0
  46. data/lib/reports_kits/base_controller.rb +17 -0
  47. data/lib/reports_kits/cache.rb +37 -0
  48. data/lib/reports_kits/configuration.rb +50 -0
  49. data/lib/reports_kits/engine.rb +21 -0
  50. data/lib/reports_kits/entity.rb +3 -0
  51. data/lib/reports_kits/filters_controller.rb +11 -0
  52. data/lib/reports_kits/form_builder.rb +66 -0
  53. data/lib/reports_kits/helper.rb +24 -0
  54. data/lib/reports_kits/model.rb +16 -0
  55. data/lib/reports_kits/model_configuration.rb +28 -0
  56. data/lib/reports_kits/normalized_params.rb +16 -0
  57. data/lib/reports_kits/order.rb +33 -0
  58. data/lib/reports_kits/relative_time.rb +42 -0
  59. data/lib/reports_kits/report_builder.rb +88 -0
  60. data/lib/reports_kits/reports/abstract_series.rb +9 -0
  61. data/lib/reports_kits/reports/adapters/mysql.rb +26 -0
  62. data/lib/reports_kits/reports/adapters/postgresql.rb +26 -0
  63. data/lib/reports_kits/reports/composite_series.rb +48 -0
  64. data/lib/reports_kits/reports/contextual_filter.rb +19 -0
  65. data/lib/reports_kits/reports/data/add_table_aggregations.rb +105 -0
  66. data/lib/reports_kits/reports/data/aggregate_composite.rb +97 -0
  67. data/lib/reports_kits/reports/data/aggregate_one_dimension.rb +39 -0
  68. data/lib/reports_kits/reports/data/aggregate_two_dimensions.rb +39 -0
  69. data/lib/reports_kits/reports/data/chart_data_for_data_method.rb +62 -0
  70. data/lib/reports_kits/reports/data/chart_options.rb +155 -0
  71. data/lib/reports_kits/reports/data/format_one_dimension.rb +140 -0
  72. data/lib/reports_kits/reports/data/format_table.rb +63 -0
  73. data/lib/reports_kits/reports/data/format_two_dimensions.rb +143 -0
  74. data/lib/reports_kits/reports/data/generate.rb +156 -0
  75. data/lib/reports_kits/reports/data/generate_for_properties.rb +97 -0
  76. data/lib/reports_kits/reports/data/normalize_properties.rb +62 -0
  77. data/lib/reports_kits/reports/data/populate_one_dimension.rb +54 -0
  78. data/lib/reports_kits/reports/data/populate_two_dimensions.rb +104 -0
  79. data/lib/reports_kits/reports/data/utils.rb +178 -0
  80. data/lib/reports_kits/reports/dimension.rb +27 -0
  81. data/lib/reports_kits/reports/dimension_with_series.rb +144 -0
  82. data/lib/reports_kits/reports/filter.rb +29 -0
  83. data/lib/reports_kits/reports/filter_types/base.rb +48 -0
  84. data/lib/reports_kits/reports/filter_types/boolean.rb +47 -0
  85. data/lib/reports_kits/reports/filter_types/datetime.rb +51 -0
  86. data/lib/reports_kits/reports/filter_types/number.rb +30 -0
  87. data/lib/reports_kits/reports/filter_types/records.rb +26 -0
  88. data/lib/reports_kits/reports/filter_types/string.rb +38 -0
  89. data/lib/reports_kits/reports/filter_with_series.rb +97 -0
  90. data/lib/reports_kits/reports/generate_autocomplete_method_results.rb +29 -0
  91. data/lib/reports_kits/reports/generate_autocomplete_results.rb +57 -0
  92. data/lib/reports_kits/reports/inferrable_configuration.rb +116 -0
  93. data/lib/reports_kits/reports/model_settings.rb +30 -0
  94. data/lib/reports_kits/reports/properties.rb +10 -0
  95. data/lib/reports_kits/reports/properties_to_filter.rb +40 -0
  96. data/lib/reports_kits/reports/series.rb +121 -0
  97. data/lib/reports_kits/reports_controller.rb +65 -0
  98. data/lib/reports_kits/utils.rb +11 -0
  99. data/lib/reports_kits/value.rb +3 -0
  100. data/lib/reports_kits/version.rb +3 -0
  101. data/lib/reports_kits.rb +79 -0
  102. data/reports_kits.gemspec +26 -0
  103. data/spec/factories/issue_factory.rb +4 -0
  104. data/spec/factories/issues_label_factory.rb +4 -0
  105. data/spec/factories/label_factory.rb +4 -0
  106. data/spec/factories/pro_repo_factory.rb +5 -0
  107. data/spec/factories/repo_factory.rb +5 -0
  108. data/spec/factories/tag_factory.rb +4 -0
  109. data/spec/fixtures/generate_inputs.yml +254 -0
  110. data/spec/fixtures/generate_outputs.yml +1216 -0
  111. data/spec/reports_kit/form_builder_spec.rb +26 -0
  112. data/spec/reports_kit/relative_time_spec.rb +29 -0
  113. data/spec/reports_kit/reports/data/generate/contextual_filters_spec.rb +60 -0
  114. data/spec/reports_kit/reports/data/generate_spec.rb +1371 -0
  115. data/spec/reports_kit/reports/data/normalize_properties_spec.rb +196 -0
  116. data/spec/reports_kit/reports/dimension_with_series_spec.rb +67 -0
  117. data/spec/reports_kit/reports/filter_with_series_spec.rb +39 -0
  118. data/spec/reports_kit/reports/generate_autocomplete_results_spec.rb +69 -0
  119. data/spec/spec_helper.rb +77 -0
  120. data/spec/support/config.rb +41 -0
  121. data/spec/support/example_data_methods.rb +25 -0
  122. data/spec/support/factory_girl.rb +5 -0
  123. data/spec/support/helpers.rb +25 -0
  124. data/spec/support/models/issue.rb +14 -0
  125. data/spec/support/models/issues_label.rb +4 -0
  126. data/spec/support/models/label.rb +5 -0
  127. data/spec/support/models/pro/repo.rb +5 -0
  128. data/spec/support/models/pro/special_issue.rb +4 -0
  129. data/spec/support/models/repo.rb +13 -0
  130. data/spec/support/models/tag.rb +4 -0
  131. data/spec/support/schema.rb +39 -0
  132. 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