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.
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