reports_kit 0.2.0 → 0.3.0

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/.travis.yml +20 -0
  4. data/README.md +17 -19
  5. data/app/assets/javascripts/reports_kit/lib/_init.js +2 -1
  6. data/app/assets/javascripts/reports_kit/lib/chart.js +25 -16
  7. data/app/assets/javascripts/reports_kit/lib/report.js +42 -25
  8. data/app/assets/javascripts/reports_kit/lib/table.js +37 -2
  9. data/app/assets/stylesheets/reports_kit/reports.css.sass +3 -0
  10. data/docs/dimensions.md +26 -34
  11. data/docs/display_options.md +12 -15
  12. data/docs/filters.md +54 -63
  13. data/docs/measures.md +3 -4
  14. data/lib/reports_kit.rb +12 -10
  15. data/lib/reports_kit/base_controller.rb +1 -2
  16. data/lib/reports_kit/configuration.rb +19 -4
  17. data/lib/reports_kit/entity.rb +3 -0
  18. data/lib/reports_kit/helper.rb +17 -21
  19. data/lib/reports_kit/model_configuration.rb +1 -1
  20. data/lib/reports_kit/report_builder.rb +11 -11
  21. data/lib/reports_kit/reports/{abstract_measure.rb → abstract_series.rb} +1 -1
  22. data/lib/reports_kit/reports/{composite_measure.rb → composite_series.rb} +12 -8
  23. data/lib/reports_kit/reports/data/add_table_aggregations.rb +105 -0
  24. data/lib/reports_kit/reports/data/{composite_aggregation.rb → aggregate_composite.rb} +28 -27
  25. data/lib/reports_kit/reports/data/{one_dimension.rb → aggregate_one_dimension.rb} +9 -7
  26. data/lib/reports_kit/reports/data/{two_dimensions.rb → aggregate_two_dimensions.rb} +9 -8
  27. data/lib/reports_kit/reports/data/chart_options.rb +6 -11
  28. data/lib/reports_kit/reports/data/format_one_dimension.rb +56 -36
  29. data/lib/reports_kit/reports/data/format_table.rb +65 -0
  30. data/lib/reports_kit/reports/data/format_two_dimensions.rb +10 -8
  31. data/lib/reports_kit/reports/data/generate.rb +51 -28
  32. data/lib/reports_kit/reports/data/generate_for_properties.rb +52 -30
  33. data/lib/reports_kit/reports/data/populate_one_dimension.rb +30 -12
  34. data/lib/reports_kit/reports/data/populate_two_dimensions.rb +31 -31
  35. data/lib/reports_kit/reports/data/utils.rb +28 -24
  36. data/lib/reports_kit/reports/dimension.rb +4 -0
  37. data/lib/reports_kit/reports/{dimension_with_measure.rb → dimension_with_series.rb} +8 -9
  38. data/lib/reports_kit/reports/filter.rb +4 -0
  39. data/lib/reports_kit/reports/filter_types/base.rb +4 -3
  40. data/lib/reports_kit/reports/filter_types/boolean.rb +4 -4
  41. data/lib/reports_kit/reports/filter_types/datetime.rb +13 -3
  42. data/lib/reports_kit/reports/filter_types/number.rb +5 -5
  43. data/lib/reports_kit/reports/{filter_with_measure.rb → filter_with_series.rb} +7 -7
  44. data/lib/reports_kit/reports/generate_autocomplete_results.rb +2 -2
  45. data/lib/reports_kit/reports/inferrable_configuration.rb +6 -6
  46. data/lib/reports_kit/reports/{measure.rb → series.rb} +28 -15
  47. data/lib/reports_kit/reports_controller.rb +25 -5
  48. data/lib/reports_kit/value.rb +3 -0
  49. data/lib/reports_kit/version.rb +1 -1
  50. data/spec/fixtures/generate_inputs.yml +116 -63
  51. data/spec/fixtures/generate_outputs.yml +64 -0
  52. data/spec/reports_kit/report_builder_spec.rb +10 -12
  53. data/spec/reports_kit/reports/data/generate_spec.rb +559 -140
  54. data/spec/reports_kit/reports/{dimension_with_measure_spec.rb → dimension_with_series_spec.rb} +5 -7
  55. data/spec/reports_kit/reports/{filter_with_measure_spec.rb → filter_with_series_spec.rb} +3 -3
  56. data/spec/spec_helper.rb +5 -5
  57. data/spec/support/config.rb +31 -1
  58. data/spec/support/helpers.rb +6 -2
  59. metadata +17 -14
  60. data/lib/reports_kit/reports/data/entity.rb +0 -7
  61. data/lib/reports_kit/reports/data/value.rb +0 -7
@@ -1,6 +1,6 @@
1
1
  module ReportsKit
2
2
  module Reports
3
- class FilterWithMeasure
3
+ class FilterWithSeries
4
4
  CONFIGURATION_STRATEGIES_FILTER_TYPE_CLASSES = {
5
5
  association: FilterTypes::Records,
6
6
  boolean: FilterTypes::Boolean,
@@ -15,19 +15,19 @@ module ReportsKit
15
15
  string: FilterTypes::String
16
16
  }
17
17
 
18
- attr_accessor :filter, :measure, :configuration
18
+ attr_accessor :filter, :series, :configuration
19
19
 
20
- delegate :key, :properties, :label, to: :filter
20
+ delegate :key, :expression, :properties, :label, to: :filter
21
21
  delegate :configured_by_association?, :configured_by_column?, :configured_by_model?, :configured_by_time?,
22
22
  :settings_from_model, :configuration_strategy, :instance_class, :column_type, :column,
23
23
  to: :configuration
24
24
 
25
- def initialize(filter:, measure:)
25
+ def initialize(filter:, series:)
26
26
  self.filter = filter
27
- self.measure = measure
27
+ self.series = series
28
28
  self.configuration = InferrableConfiguration.new(self, :filters)
29
29
 
30
- self.properties[:criteria] = filter_type.default_criteria unless self.properties[:criteria]
30
+ properties[:criteria] = filter_type.default_criteria unless properties[:criteria]
31
31
  end
32
32
 
33
33
  def normalized_properties
@@ -64,7 +64,7 @@ module ReportsKit
64
64
  end
65
65
 
66
66
  def filter_type
67
- type_klass.new(settings, properties)
67
+ type_klass.new(settings, properties, primary_dimension: series.dimensions.first)
68
68
  end
69
69
 
70
70
  def filter_type_class_from_model
@@ -41,8 +41,8 @@ module ReportsKit
41
41
 
42
42
  def model
43
43
  @model ||= begin
44
- measure = Measure.new(measure_key, context_record: context_record)
45
- filter = FilterWithMeasure.new(filter: Filter.new(filter_key), measure: measure)
44
+ series = Series.new(measure_key, context_record: context_record)
45
+ filter = FilterWithSeries.new(filter: Filter.new(filter_key), series: series)
46
46
  filter.instance_class
47
47
  end
48
48
  end
@@ -11,7 +11,7 @@ module ReportsKit
11
11
 
12
12
  attr_accessor :inferrable, :inferrable_type
13
13
 
14
- delegate :key, :measure, to: :inferrable
14
+ delegate :key, :expression, :series, to: :inferrable
15
15
 
16
16
  def initialize(inferrable, inferrable_type)
17
17
  self.inferrable = inferrable
@@ -57,7 +57,7 @@ module ReportsKit
57
57
  end
58
58
 
59
59
  def reflection
60
- model_class.reflect_on_association(key.to_sym)
60
+ model_class.reflect_on_association(expression.to_sym)
61
61
  end
62
62
 
63
63
  def instance_class
@@ -71,7 +71,7 @@ module ReportsKit
71
71
  end
72
72
 
73
73
  def inferred_settings
74
- return { column: "#{model_class.table_name}.#{key}" } if configured_by_column?
74
+ return { column: "#{model_class.table_name}.#{expression}" } if configured_by_column?
75
75
  if configured_by_association?
76
76
  return inferred_settings_from_belongs_to_or_has_one if inferred_settings_from_belongs_to_or_has_one
77
77
  return inferred_settings_from_has_many if inferred_settings_from_has_many
@@ -115,7 +115,7 @@ module ReportsKit
115
115
  end
116
116
 
117
117
  def column_type
118
- column_type = model_class.columns_hash[key.to_s].try(:type)
118
+ column_type = model_class.columns_hash[expression.to_s].try(:type)
119
119
  return column_type if SUPPORTED_COLUMN_TYPES.include?(column_type)
120
120
  end
121
121
 
@@ -125,8 +125,8 @@ module ReportsKit
125
125
  end
126
126
 
127
127
  def model_class
128
- return unless measure
129
- measure.model_class
128
+ return unless series
129
+ series.model_class
130
130
  end
131
131
  end
132
132
  end
@@ -1,13 +1,17 @@
1
1
  module ReportsKit
2
2
  module Reports
3
- class Measure < AbstractMeasure
3
+ class Series < AbstractSeries
4
+ VALID_KEYS = [:measure, :dimensions, :filters, :limit, :report_options]
5
+
4
6
  attr_accessor :properties, :dimensions, :filters, :context_record
5
7
 
6
8
  def initialize(properties, context_record: nil)
7
- properties = properties.dup
8
- properties = { key: properties } if properties.is_a?(String)
9
+ properties = { measure: properties } if properties.is_a?(String)
10
+ properties = properties.deep_symbolize_keys.dup
11
+ measure_properties = properties[:measure]
12
+ properties[:measure] = measure_properties
13
+ properties[:measure] = { key: properties[:measure] } if properties[:measure].is_a?(String)
9
14
  raise ArgumentError.new("Measure properties must be a String or Hash, not a #{properties.class.name}: #{properties.inspect}") unless properties.is_a?(Hash)
10
- properties = properties.deep_symbolize_keys
11
15
 
12
16
  dimension_hashes = properties[:dimensions] || []
13
17
  dimension_hashes = dimension_hashes.values if dimension_hashes.is_a?(Hash) && dimension_hashes.key?(:'0')
@@ -15,17 +19,25 @@ module ReportsKit
15
19
  filter_hashes = filter_hashes.values if filter_hashes.is_a?(Hash) && filter_hashes.key?(:'0')
16
20
 
17
21
  self.properties = properties
18
- self.dimensions = dimension_hashes.map { |dimension_hash| DimensionWithMeasure.new(dimension: Dimension.new(dimension_hash), measure: self) }
19
- self.filters = filter_hashes.map { |filter_hash| FilterWithMeasure.new(filter: Filter.new(filter_hash), measure: self) }
22
+ self.dimensions = dimension_hashes.map { |dimension_hash| DimensionWithSeries.new(dimension: Dimension.new(dimension_hash), series: self) }
23
+ self.filters = filter_hashes.map { |filter_hash| FilterWithSeries.new(filter: Filter.new(filter_hash), series: self) }
20
24
  self.context_record = context_record
21
25
  end
22
26
 
23
27
  def key
24
- properties[:key].underscore
28
+ properties[:measure][:key].underscore
25
29
  end
26
30
 
27
31
  def label
28
- properties[:name].presence || key.pluralize.titleize
32
+ properties[:measure][:name].presence || key.pluralize.titleize
33
+ end
34
+
35
+ def limit
36
+ properties[:limit]
37
+ end
38
+
39
+ def edit_relation_method
40
+ ReportsKit.configuration.custom_method(properties[:report_options].try(:[], :edit_relation_method))
29
41
  end
30
42
 
31
43
  def relation_name
@@ -47,7 +59,7 @@ module ReportsKit
47
59
  end
48
60
 
49
61
  def aggregation_key
50
- properties[:aggregation]
62
+ properties[:measure][:aggregation]
51
63
  end
52
64
 
53
65
  def aggregation_config
@@ -82,14 +94,15 @@ module ReportsKit
82
94
  end
83
95
 
84
96
  def self.new_from_properties!(properties, context_record:)
85
- measure_hashes = [properties[:measure]].compact + Array(properties[:measures])
86
- raise ArgumentError.new('At least one measure must be configured') if measure_hashes.blank?
97
+ series_hashes = properties[:series].presence || properties.slice(*Series::VALID_KEYS)
98
+ series_hashes = [series_hashes] if series_hashes.is_a?(Hash)
99
+ raise ArgumentError.new('At least one series must be configured') if series_hashes.blank?
87
100
 
88
- measure_hashes.map do |measure_hash|
89
- if measure_hash[:composite_operator].present?
90
- CompositeMeasure.new(measure_hash)
101
+ series_hashes.map do |series_hash|
102
+ if series_hash[:composite_operator].present?
103
+ CompositeSeries.new(series_hash)
91
104
  else
92
- new(measure_hash, context_record: context_record)
105
+ new(series_hash, context_record: context_record)
93
106
  end
94
107
  end
95
108
  end
@@ -3,35 +3,55 @@ require 'spreadsheet'
3
3
 
4
4
  module ReportsKit
5
5
  class ReportsController < ReportsKit::BaseController
6
+ VALID_PARAMS_PROPERTIES_KEYS = [:ui_filters]
7
+
6
8
  def index
7
9
  respond_to do |format|
8
10
  format.json do
9
11
  render json: { data: report_data }
10
12
  end
11
13
  format.csv do
12
- properties[:format] = 'table'
14
+ properties[:format] = 'csv'
13
15
  csv = CSV.generate do |csv|
14
16
  report_data[:table_data].each do |row|
15
17
  csv << row
16
18
  end
17
19
  end
18
- send_data csv, filename: "Report.csv"
20
+ send_data csv, filename: "#{report_filename}.csv"
19
21
  end
20
22
  format.xls do
21
- properties[:format] = 'table'
22
- send_data xls_string, filename: 'Report.xls', type: 'application/vnd.ms-excel'
23
+ properties[:format] = 'csv'
24
+ send_data xls_string, filename: "#{report_filename}.xls", type: 'application/vnd.ms-excel'
23
25
  end
24
26
  end
25
27
  end
26
28
 
27
29
  private
28
30
 
31
+ def report_filename
32
+ report_filename_method = ReportsKit.configuration.report_filename_method
33
+ return 'Report' unless report_filename_method
34
+ instance_eval(&report_filename_method)
35
+ end
36
+
29
37
  def report_data
30
38
  Reports::Data::Generate.new(properties, context_record: context_record).perform
31
39
  end
32
40
 
41
+ def report_key
42
+ params[:report_params][:key]
43
+ end
44
+
33
45
  def properties
34
- @properties ||= ActiveSupport::JSON.decode(params[:properties])
46
+ @properties ||= begin
47
+ properties_method = ReportsKit.configuration.properties_method
48
+ properties = instance_eval(&properties_method)
49
+ properties.merge(params_properties).deep_symbolize_keys
50
+ end
51
+ end
52
+
53
+ def params_properties
54
+ @params_properties ||= ActiveSupport::JSON.decode(params[:properties]).with_indifferent_access.slice(*VALID_PARAMS_PROPERTIES_KEYS)
35
55
  end
36
56
 
37
57
  def xls_string
@@ -0,0 +1,3 @@
1
+ module ReportsKit
2
+ Value = Struct.new(:raw, :formatted)
3
+ end
@@ -1,3 +1,3 @@
1
1
  module ReportsKit
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -1,164 +1,217 @@
1
- - measure:
2
- key: issue
1
+ - series:
2
+ measure: issue
3
3
  dimensions:
4
4
  - repo
5
- - measure:
6
- key: issue
5
+ - series:
6
+ measure: issue
7
7
  dimensions:
8
8
  - repo
9
9
  chart:
10
10
  options:
11
11
  foo: bar
12
- - measure:
13
- key: issue
12
+ - series:
13
+ measure: issue
14
14
  dimensions:
15
15
  - repo
16
16
  chart:
17
17
  foo: bar
18
- - measure:
19
- key: issue
18
+ - series:
19
+ measure: issue
20
20
  dimensions:
21
21
  - repo
22
22
  chart:
23
23
  type: pie
24
- - measure:
25
- key: issue
24
+ - series:
25
+ measure: issue
26
26
  dimensions:
27
27
  - key: repo
28
- - measure:
29
- key: issue
28
+ - series:
29
+ measure: issue
30
30
  dimensions:
31
31
  - key: opened_at
32
- - measure:
33
- key: issue
32
+ - series:
33
+ measure: issue
34
34
  dimensions:
35
35
  - key: opened_at
36
36
  - key: repo
37
- - measure:
38
- key: issue
37
+ - series:
38
+ measure: issue
39
39
  filters:
40
40
  - key: locked
41
41
  - key: title
42
42
  - key: opened_at
43
43
  dimensions:
44
44
  - key: repo
45
- - measures:
46
- - key: issue
45
+ - series:
46
+ - measure: issue
47
47
  dimensions:
48
48
  - repo
49
- - key: label
49
+ - measure: label
50
50
  dimensions:
51
51
  - repo
52
- - measures:
53
- - key: issue
52
+ - series:
53
+ - measure: issue
54
54
  dimensions:
55
55
  - repo
56
- - key: label
56
+ - measure: label
57
57
  dimensions:
58
58
  - repo
59
- - key: tag
59
+ - measure: tag
60
60
  dimensions:
61
61
  - repo
62
- - measures:
63
- - key: issue
64
- name: Repo Issues
62
+ - series:
63
+ - measure:
64
+ key: issue
65
+ name: Repo Issues
65
66
  dimensions:
66
67
  - repo
67
- - key: label
68
+ - measure: label
68
69
  dimensions:
69
70
  - repo
70
- - measures:
71
- - key: issue
72
- name: Repo Issues
71
+ - series:
72
+ - measure:
73
+ key: issue
74
+ name: Repo Issues
73
75
  dimensions:
74
76
  - key: repo
75
77
  label:
76
- - key: label
78
+ - measure: label
77
79
  dimensions:
78
80
  - repo
79
81
  format: table
80
- - measure:
81
- key: label
82
+ - series:
83
+ - measure:
84
+ key: issue
85
+ name: Repo Issues
86
+ dimensions:
87
+ - key: repo
88
+ label:
89
+ - measure: label
90
+ dimensions:
91
+ - repo
92
+ format: table
93
+ report_options:
94
+ aggregations:
95
+ - from: columns
96
+ operator: sum
97
+ label: Total
98
+ - series:
99
+ - measure:
100
+ key: issue
101
+ name: Repo Issues
102
+ dimensions:
103
+ - key: repo
104
+ label:
105
+ - measure: label
106
+ dimensions:
107
+ - repo
108
+ format: table
109
+ report_options:
110
+ aggregations:
111
+ - from: rows
112
+ operator: sum
113
+ label: Total
114
+ - series:
115
+ - measure:
116
+ key: issue
117
+ name: Repo Issues
118
+ dimensions:
119
+ - key: repo
120
+ label:
121
+ - measure: label
122
+ dimensions:
123
+ - repo
124
+ format: table
125
+ report_options:
126
+ aggregations:
127
+ - from: columns
128
+ operator: sum
129
+ label: Total
130
+ - from: rows
131
+ operator: sum
132
+ label: Total
133
+ - series:
134
+ measure: label
82
135
  dimensions:
83
136
  - repo
84
137
  order: dimension1.label
85
- - measure:
86
- key: label
138
+ - series:
139
+ measure: label
87
140
  dimensions:
88
141
  - repo
89
142
  order: dimension1.label desc
90
- - measure:
91
- key: label
143
+ - series:
144
+ measure: label
92
145
  dimensions:
93
146
  - repo
94
147
  order: count
95
- - measure:
96
- key: label
148
+ - series:
149
+ measure: label
97
150
  dimensions:
98
151
  - repo
99
152
  order: count desc
100
- - measure:
101
- key: issue
153
+ - series:
154
+ measure: issue
102
155
  dimensions:
103
156
  - opened_at
104
157
  order: dimension1
105
- - measure:
106
- key: issue
158
+ - series:
159
+ measure: issue
107
160
  dimensions:
108
161
  - opened_at
109
162
  order: dimension1 desc
110
- - measure:
111
- key: label
163
+ - series:
164
+ measure: label
112
165
  dimensions:
113
166
  - repo
114
167
  order: 0
115
- - measure:
116
- key: label
168
+ - series:
169
+ measure: label
117
170
  dimensions:
118
171
  - repo
119
172
  order: 0 desc
120
- - measure:
121
- key: label
173
+ - series:
174
+ measure: label
122
175
  dimensions:
123
176
  - repo
124
177
  order: 1
125
- - measure:
126
- key: label
178
+ - series:
179
+ measure: label
127
180
  dimensions:
128
181
  - repo
129
182
  order: 1 desc
130
- - measure:
131
- key: issue
183
+ - series:
184
+ measure: issue
132
185
  dimensions:
133
186
  - key: opened_at
134
187
  - key: repo
135
188
  order: dimension1
136
- - measure:
137
- key: issue
189
+ - series:
190
+ measure: issue
138
191
  dimensions:
139
192
  - key: opened_at
140
193
  - key: repo
141
194
  order: dimension1 desc
142
- - measure:
143
- key: label
195
+ - series:
196
+ measure: label
144
197
  dimensions:
145
198
  - key: repo
146
199
  - key: created_at
147
200
  order: dimension1.label
148
- - measure:
149
- key: label
201
+ - series:
202
+ measure: label
150
203
  dimensions:
151
204
  - key: repo
152
205
  - key: created_at
153
206
  order: dimension1.label desc
154
- - measure:
155
- key: issue
207
+ - series:
208
+ measure: issue
156
209
  dimensions:
157
210
  - key: opened_at
158
211
  - key: repo
159
212
  order: count
160
- - measure:
161
- key: issue
213
+ - series:
214
+ measure: issue
162
215
  dimensions:
163
216
  - key: opened_at
164
217
  - key: repo