reports_kit 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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