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
@@ -11,10 +11,9 @@ You can use any `type` value supported by Chart.js, including `bar`, `line`, `ho
11
11
  Here's an example of a horizontal bar chart:
12
12
 
13
13
  ```yaml
14
- measure:
15
- key: flight
16
- dimensions:
17
- - carrier
14
+ measure: flight
15
+ dimensions:
16
+ - carrier
18
17
  chart:
19
18
  type: horizontalBar
20
19
  options:
@@ -37,11 +36,10 @@ You can use any `options` that are supported by Chart.js.
37
36
  Here's an example of a chart with Chart.js options:
38
37
 
39
38
  ```yaml
40
- measure:
41
- key: flight
42
- dimensions:
43
- - origin_market
44
- - carrier
39
+ measure: flight
40
+ dimensions:
41
+ - origin_market
42
+ - carrier
45
43
  chart:
46
44
  type: horizontalBar
47
45
  options:
@@ -66,12 +64,11 @@ You can use any `datasets` options that are supported by Chart.js.
66
64
  Here's an example of a chart with `datasets` options:
67
65
 
68
66
  ```yaml
69
- measure:
70
- key: flight
71
- dimensions:
72
- - flight_at
73
- - key: carrier
74
- limit: 3
67
+ measure: flight
68
+ dimensions:
69
+ - flight_at
70
+ - key: carrier
71
+ limit: 3
75
72
  chart:
76
73
  type: line
77
74
  datasets:
@@ -7,13 +7,12 @@ A filter is like a SQL `WHERE`: it filters the results to only include results t
7
7
  For example, if the `Flight` model has a `delay` column that's an integer, the chart below will show only flights that have a delay of greater than 15 minutes:
8
8
 
9
9
  ```yaml
10
- measure:
11
- key: flight
12
- filters:
13
- - key: delay
14
- criteria:
15
- operator: '>'
16
- value: 15
10
+ measure: flight
11
+ filters:
12
+ - key: delay
13
+ criteria:
14
+ operator: '>'
15
+ value: 15
17
16
  dimensions:
18
17
  - carrier
19
18
  ```
@@ -22,13 +21,12 @@ dimensions:
22
21
  You can also create form controls that the user can use to filter the chart:
23
22
 
24
23
  ```yaml
25
- measure:
26
- key: flight
27
- filters:
28
- - carrier
29
- - carrier_name
30
- - is_on_time
31
- - flight_at
24
+ measure: flight
25
+ filters:
26
+ - carrier
27
+ - carrier_name
28
+ - is_on_time
29
+ - flight_at
32
30
  dimensions:
33
31
  - flight_at
34
32
  - carrier
@@ -55,12 +53,11 @@ In `app/views/my_view.html.haml`, you can use ReportsKit's form helpers to creat
55
53
  Boolean filters can be used on any `boolean` columns, or you can define your own boolean filter (see [Custom Filters](#custom-filters)).
56
54
 
57
55
  ```yaml
58
- measure:
59
- key: flight
60
- filters:
61
- - key: is_on_time
62
- criteria:
63
- value: true
56
+ measure: flight
57
+ filters:
58
+ - key: is_on_time
59
+ criteria:
60
+ value: true
64
61
  dimensions:
65
62
  - carrier
66
63
  ```
@@ -71,30 +68,30 @@ dimensions:
71
68
  Datetime filters can be used on any `datetime` or `timestamp` columns, or you can define your own datetime filter (see [Custom Filters](#custom-filters)).
72
69
 
73
70
  ```yaml
74
- measure:
75
- key: flight
76
- filters:
77
- - key: flight_at
78
- criteria:
79
- operator: between
80
- value: Oct 1, 2016 - Jan 1, 2017
71
+ measure: flight
72
+ filters:
73
+ - key: flight_at
74
+ criteria:
75
+ operator: between
76
+ value: -3M - now
81
77
  dimensions:
82
78
  - carrier
83
79
  ```
84
80
  [<img src="images/flights_with_configured_datetime.png?raw=true" width="500" />](images/flights_with_configured_datetime.png?raw=true)
85
81
 
82
+ The `value` in the example above is shorthand for relative time; it represents the time range of `(3.months.ago..Time.zone.now)`. To see the other supported time durations (e.g. `w` for week, `d` for day), see [RelativeTime](https://github.com/tombenner/reports_kit/blob/master/lib/reports_kit/relative_time.rb).
83
+
86
84
  ##### Number
87
85
 
88
86
  Number filters can be used on any `integer`, `float`, or `decimal` columns, or you can define your own number filter (see [Custom Filters](#custom-filters)).
89
87
 
90
88
  ```yaml
91
- measure:
92
- key: flight
93
- filters:
94
- - key: delay
95
- criteria:
96
- operator: '>'
97
- value: 15
89
+ measure: flight
90
+ filters:
91
+ - key: delay
92
+ criteria:
93
+ operator: '>'
94
+ value: 15
98
95
  dimensions:
99
96
  - carrier
100
97
  ```
@@ -105,13 +102,12 @@ dimensions:
105
102
  String filters can be used on any `string` or `text` columns, or you can define your own number filter (see [Custom Filters](#custom-filters)).
106
103
 
107
104
  ```yaml
108
- measure:
109
- key: flight
110
- filters:
111
- - key: carrier_name
112
- criteria:
113
- operator: contains
114
- value: airlines
105
+ measure: flight
106
+ filters:
107
+ - key: carrier_name
108
+ criteria:
109
+ operator: contains
110
+ value: airlines
115
111
  dimensions:
116
112
  - carrier
117
113
  ```
@@ -134,12 +130,11 @@ end
134
130
  We can then use the `was_delayed` filter:
135
131
 
136
132
  ```yaml
137
- measure:
138
- key: flight
139
- filters:
140
- - key: was_delayed
141
- criteria:
142
- value: true
133
+ measure: flight
134
+ filters:
135
+ - key: was_delayed
136
+ criteria:
137
+ value: true
143
138
  dimensions:
144
139
  - carrier
145
140
  ```
@@ -154,10 +149,9 @@ Most charting libraries don't provide interactive form controls, but ReportsKit
154
149
  Check boxes can be used with filters that have a `boolean` type.
155
150
 
156
151
  ```yaml
157
- measure:
158
- key: flight
159
- filters:
160
- - is_on_time
152
+ measure: flight
153
+ filters:
154
+ - is_on_time
161
155
  dimensions:
162
156
  - flight_at
163
157
  - carrier
@@ -176,10 +170,9 @@ dimensions:
176
170
  Date ranges can be used with filters that have a `datetime` type.
177
171
 
178
172
  ```yaml
179
- measure:
180
- key: flight
181
- filters:
182
- - flight_at
173
+ measure: flight
174
+ filters:
175
+ - flight_at
183
176
  dimensions:
184
177
  - flight_at
185
178
  - carrier
@@ -193,10 +186,9 @@ dimensions:
193
186
  ##### Multi-Autocomplete
194
187
 
195
188
  ```yaml
196
- measure:
197
- key: flight
198
- filters:
199
- - carrier
189
+ measure: flight
190
+ filters:
191
+ - carrier
200
192
  dimensions:
201
193
  - flight_at
202
194
  - carrier
@@ -210,10 +202,9 @@ dimensions:
210
202
  ##### String Filter
211
203
 
212
204
  ```yaml
213
- measure:
214
- key: flight
215
- filters:
216
- - carrier_name
205
+ measure: flight
206
+ filters:
207
+ - carrier_name
217
208
  dimensions:
218
209
  - flight_at
219
210
  - carrier
@@ -5,9 +5,8 @@ The measure is what is being counted (or aggregated in another way). You can use
5
5
  For example, say we have a `Flight` model with a `flight_at` datetime column. We can chart the number of flights over time:
6
6
 
7
7
  ```yaml
8
- measure:
9
- key: flight
10
- dimensions:
11
- - flight_at
8
+ measure: flight
9
+ dimensions:
10
+ - flight_at
12
11
  ```
13
12
  [<img src="images/flights_by_flight_at.png?raw=true" width="500" />](images/flights_by_flight_at.png?raw=true)
@@ -4,6 +4,7 @@ require 'reports_kit/base_controller'
4
4
  require 'reports_kit/cache'
5
5
  require 'reports_kit/configuration'
6
6
  require 'reports_kit/engine'
7
+ require 'reports_kit/entity'
7
8
  require 'reports_kit/helper'
8
9
  require 'reports_kit/model'
9
10
  require 'reports_kit/model_configuration'
@@ -12,24 +13,25 @@ require 'reports_kit/relative_time'
12
13
  require 'reports_kit/report_builder'
13
14
  require 'reports_kit/resources_controller'
14
15
  require 'reports_kit/reports_controller'
16
+ require 'reports_kit/value'
15
17
  require 'reports_kit/version'
16
18
 
17
19
  require 'reports_kit/reports/adapters/mysql'
18
20
  require 'reports_kit/reports/adapters/postgresql'
19
21
 
22
+ require 'reports_kit/reports/data/add_table_aggregations'
23
+ require 'reports_kit/reports/data/aggregate_composite'
24
+ require 'reports_kit/reports/data/aggregate_one_dimension'
25
+ require 'reports_kit/reports/data/aggregate_two_dimensions'
20
26
  require 'reports_kit/reports/data/chart_options'
21
- require 'reports_kit/reports/data/composite_aggregation'
22
- require 'reports_kit/reports/data/entity'
23
27
  require 'reports_kit/reports/data/format_one_dimension'
28
+ require 'reports_kit/reports/data/format_table'
24
29
  require 'reports_kit/reports/data/format_two_dimensions'
25
30
  require 'reports_kit/reports/data/generate'
26
31
  require 'reports_kit/reports/data/generate_for_properties'
27
- require 'reports_kit/reports/data/one_dimension'
28
32
  require 'reports_kit/reports/data/populate_one_dimension'
29
33
  require 'reports_kit/reports/data/populate_two_dimensions'
30
- require 'reports_kit/reports/data/two_dimensions'
31
34
  require 'reports_kit/reports/data/utils'
32
- require 'reports_kit/reports/data/value'
33
35
 
34
36
  require 'reports_kit/reports/filter_types/base'
35
37
  require 'reports_kit/reports/filter_types/boolean'
@@ -38,15 +40,15 @@ require 'reports_kit/reports/filter_types/number'
38
40
  require 'reports_kit/reports/filter_types/records'
39
41
  require 'reports_kit/reports/filter_types/string'
40
42
 
41
- require 'reports_kit/reports/abstract_measure'
42
- require 'reports_kit/reports/composite_measure'
43
+ require 'reports_kit/reports/abstract_series'
44
+ require 'reports_kit/reports/composite_series'
43
45
  require 'reports_kit/reports/dimension'
44
- require 'reports_kit/reports/dimension_with_measure'
46
+ require 'reports_kit/reports/dimension_with_series'
45
47
  require 'reports_kit/reports/filter'
46
- require 'reports_kit/reports/filter_with_measure'
48
+ require 'reports_kit/reports/filter_with_series'
47
49
  require 'reports_kit/reports/generate_autocomplete_results'
48
50
  require 'reports_kit/reports/inferrable_configuration'
49
- require 'reports_kit/reports/measure'
51
+ require 'reports_kit/reports/series'
50
52
 
51
53
  module ReportsKit
52
54
  def self.configure
@@ -1,7 +1,6 @@
1
1
  module ReportsKit
2
2
  class BaseController < ActionController::Base
3
- private
4
-
3
+ # This is intentionally public to allow external code to access it
5
4
  def context_record
6
5
  context_record_method = ReportsKit.configuration.context_record_method
7
6
  return unless context_record_method
@@ -1,22 +1,37 @@
1
1
  module ReportsKit
2
2
  class Configuration
3
- attr_accessor :cache_duration, :cache_store,
4
- :context_params_method, :context_record_method, :custom_methods, :first_day_of_week
3
+ attr_accessor :cache_duration, :cache_store, :context_record_method, :custom_methods, :default_dimension_limit,
4
+ :default_properties, :first_day_of_week, :properties_method, :report_filename_method, :use_concurrent_queries
5
+
6
+ DEFAULT_PROPERTIES_METHOD = lambda do |env|
7
+ path = Rails.root.join('config', 'reports_kit', 'reports', "#{report_key}.yml")
8
+ YAML.load_file(path)
9
+ end
5
10
 
6
11
  def initialize
7
12
  self.cache_duration = 5.minutes
8
13
  self.cache_store = nil
9
- self.context_params_method = nil
10
14
  self.context_record_method = nil
11
15
  self.custom_methods = {}
16
+ self.default_dimension_limit = 30
17
+ self.default_properties = nil
12
18
  self.first_day_of_week = :sunday
19
+ self.properties_method = DEFAULT_PROPERTIES_METHOD
20
+ self.report_filename_method = nil
21
+ self.use_concurrent_queries = false
13
22
  end
14
23
 
15
24
  def custom_method(method_name)
16
25
  return if method_name.blank?
17
- method = custom_methods[method_name.to_sym]
26
+ method = evaluated_custom_methods[method_name.to_sym]
18
27
  raise ArgumentError.new("A method named '#{method_name}' is not defined") unless method
19
28
  method
20
29
  end
30
+
31
+ def evaluated_custom_methods
32
+ return custom_methods if custom_methods.is_a?(Hash)
33
+ return custom_methods.call if custom_methods.is_a?(Proc)
34
+ raise ArgumentError.new("Invalid type for custom_methods configuration: #{custom_methods.class}")
35
+ end
21
36
  end
22
37
  end
@@ -0,0 +1,3 @@
1
+ module ReportsKit
2
+ Entity = Struct.new(:key, :label, :instance)
3
+ end
@@ -5,15 +5,16 @@ module ReportsKit
5
5
  'export_xls' => :export_xls_element
6
6
  }
7
7
 
8
- def render_report(properties, &block)
9
- raise ArgumentError.new('`properties` must be a Hash or String') if properties.blank?
10
- if properties.is_a?(String)
11
- path = Rails.root.join('config', 'reports_kit', 'reports', "#{properties}.yml")
12
- properties = YAML.load_file(path)
13
- end
8
+ def render_report(report_params, context_params: {}, actions: %w(export_csv export_xls), js_report_class: 'Report', &block)
9
+ report_params = { key: report_params } if report_params.is_a?(String)
10
+ additional_params = { context_params: context_params, report_params: report_params }
11
+ params.merge!(additional_params)
12
+ properties = instance_eval(&ReportsKit.configuration.properties_method)
13
+ properties = properties.deep_symbolize_keys
14
14
  builder = ReportsKit::ReportBuilder.new(properties, additional_params: additional_params)
15
15
  path = reports_kit.reports_kit_reports_path({ format: 'json' }.merge(additional_params))
16
- content_tag :div, nil, class: 'reports_kit_report form-inline', data: { properties: builder.properties, path: path } do
16
+ data = { properties: properties.slice(:format), path: path, report_class: js_report_class }
17
+ content_tag :div, nil, class: 'reports_kit_report form-inline', data: data do
17
18
  elements = []
18
19
  if block_given?
19
20
  elements << form_tag(path, method: 'get', class: 'reports_kit_report_form') do
@@ -21,7 +22,7 @@ module ReportsKit
21
22
  end
22
23
  end
23
24
  elements << content_tag(:div, nil, class: 'reports_kit_visualization')
24
- action_elements = action_elements_for_properties(properties)
25
+ action_elements = generate_action_elements(actions, additional_params)
25
26
  if action_elements
26
27
  elements << content_tag(:div, nil, class: 'reports_kit_actions') do
27
28
  action_elements.map { |element| concat(element) }
@@ -33,25 +34,20 @@ module ReportsKit
33
34
 
34
35
  private
35
36
 
36
- def additional_params
37
- @additional_params ||= begin
38
- context_params_method = ReportsKit.configuration.context_params_method
39
- return {} unless context_params_method
40
- context_params = instance_eval(&context_params_method)
41
- { context_params: context_params }
42
- end
37
+ def report_key
38
+ params[:report_params][:key]
43
39
  end
44
40
 
45
- def action_elements_for_properties(properties)
46
- return if properties['actions'].blank?
47
- properties['actions'].map do |action|
41
+ def generate_action_elements(actions, additional_params)
42
+ return if actions.blank?
43
+ actions.map do |action|
48
44
  element_method = ACTION_KEYS_METHODS[action]
49
45
  raise ArgumentError.new("Invalid action: #{action}") unless element_method
50
- send(element_method)
46
+ send(element_method, additional_params)
51
47
  end
52
48
  end
53
49
 
54
- def export_csv_element
50
+ def export_csv_element(additional_params)
55
51
  data = {
56
52
  role: 'reports_kit_export_button',
57
53
  path: reports_kit.reports_kit_reports_path({ format: 'csv' }.merge(additional_params))
@@ -59,7 +55,7 @@ module ReportsKit
59
55
  link_to('Download CSV', '#', class: 'btn btn-primary', data: data)
60
56
  end
61
57
 
62
- def export_xls_element
58
+ def export_xls_element(additional_params)
63
59
  data = {
64
60
  role: 'reports_kit_export_button',
65
61
  path: reports_kit.reports_kit_reports_path({ format: 'xls' }.merge(additional_params))
@@ -9,7 +9,7 @@ module ReportsKit
9
9
  self.autocomplete_scopes = []
10
10
  end
11
11
 
12
- def aggregation(key, expression, properties={})
12
+ def aggregation(key, expression, properties = {})
13
13
  aggregations << { key: key.to_s, expression: expression }.merge(properties).symbolize_keys
14
14
  end
15
15