active_reporter 0.5.8

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 (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +14 -0
  3. data/README.md +436 -0
  4. data/Rakefile +23 -0
  5. data/lib/active_reporter.rb +26 -0
  6. data/lib/active_reporter/aggregator.rb +9 -0
  7. data/lib/active_reporter/aggregator/array.rb +14 -0
  8. data/lib/active_reporter/aggregator/average.rb +9 -0
  9. data/lib/active_reporter/aggregator/base.rb +73 -0
  10. data/lib/active_reporter/aggregator/count.rb +23 -0
  11. data/lib/active_reporter/aggregator/count_if.rb +23 -0
  12. data/lib/active_reporter/aggregator/max.rb +9 -0
  13. data/lib/active_reporter/aggregator/min.rb +9 -0
  14. data/lib/active_reporter/aggregator/ratio.rb +23 -0
  15. data/lib/active_reporter/aggregator/sum.rb +13 -0
  16. data/lib/active_reporter/calculator.rb +2 -0
  17. data/lib/active_reporter/calculator/base.rb +19 -0
  18. data/lib/active_reporter/calculator/ratio.rb +9 -0
  19. data/lib/active_reporter/dimension.rb +8 -0
  20. data/lib/active_reporter/dimension/base.rb +150 -0
  21. data/lib/active_reporter/dimension/bin.rb +123 -0
  22. data/lib/active_reporter/dimension/bin/set.rb +162 -0
  23. data/lib/active_reporter/dimension/bin/table.rb +43 -0
  24. data/lib/active_reporter/dimension/category.rb +29 -0
  25. data/lib/active_reporter/dimension/enum.rb +32 -0
  26. data/lib/active_reporter/dimension/number.rb +51 -0
  27. data/lib/active_reporter/dimension/time.rb +93 -0
  28. data/lib/active_reporter/evaluator.rb +2 -0
  29. data/lib/active_reporter/evaluator/base.rb +17 -0
  30. data/lib/active_reporter/evaluator/block.rb +15 -0
  31. data/lib/active_reporter/inflector.rb +8 -0
  32. data/lib/active_reporter/invalid_params_error.rb +4 -0
  33. data/lib/active_reporter/report.rb +102 -0
  34. data/lib/active_reporter/report/aggregation.rb +297 -0
  35. data/lib/active_reporter/report/definition.rb +195 -0
  36. data/lib/active_reporter/report/metrics.rb +75 -0
  37. data/lib/active_reporter/report/validation.rb +106 -0
  38. data/lib/active_reporter/serializer.rb +7 -0
  39. data/lib/active_reporter/serializer/base.rb +103 -0
  40. data/lib/active_reporter/serializer/csv.rb +22 -0
  41. data/lib/active_reporter/serializer/form_field.rb +134 -0
  42. data/lib/active_reporter/serializer/hash_table.rb +12 -0
  43. data/lib/active_reporter/serializer/highcharts.rb +200 -0
  44. data/lib/active_reporter/serializer/nested_hash.rb +11 -0
  45. data/lib/active_reporter/serializer/table.rb +21 -0
  46. data/lib/active_reporter/tracker.rb +2 -0
  47. data/lib/active_reporter/tracker/base.rb +15 -0
  48. data/lib/active_reporter/tracker/delta.rb +9 -0
  49. data/lib/active_reporter/version.rb +3 -0
  50. data/lib/tasks/active_reporter_tasks.rake +4 -0
  51. data/spec/acceptance/data_spec.rb +381 -0
  52. data/spec/active_reporter/aggregator_spec.rb +102 -0
  53. data/spec/active_reporter/dimension/base_spec.rb +102 -0
  54. data/spec/active_reporter/dimension/bin/set_spec.rb +83 -0
  55. data/spec/active_reporter/dimension/bin/table_spec.rb +47 -0
  56. data/spec/active_reporter/dimension/bin_spec.rb +77 -0
  57. data/spec/active_reporter/dimension/category_spec.rb +60 -0
  58. data/spec/active_reporter/dimension/enum_spec.rb +94 -0
  59. data/spec/active_reporter/dimension/number_spec.rb +71 -0
  60. data/spec/active_reporter/dimension/time_spec.rb +61 -0
  61. data/spec/active_reporter/report_spec.rb +597 -0
  62. data/spec/active_reporter/serializer/hash_table_spec.rb +45 -0
  63. data/spec/active_reporter/serializer/highcharts_spec.rb +113 -0
  64. data/spec/active_reporter/serializer/table_spec.rb +62 -0
  65. data/spec/dummy/README.rdoc +28 -0
  66. data/spec/dummy/Rakefile +6 -0
  67. data/spec/dummy/app/assets/config/manifest.js +0 -0
  68. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  69. data/spec/dummy/app/assets/stylesheets/application.css +26 -0
  70. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  71. data/spec/dummy/app/controllers/site_controller.rb +11 -0
  72. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  73. data/spec/dummy/app/models/author.rb +4 -0
  74. data/spec/dummy/app/models/comment.rb +4 -0
  75. data/spec/dummy/app/models/data_builder.rb +112 -0
  76. data/spec/dummy/app/models/post.rb +6 -0
  77. data/spec/dummy/app/models/post_report.rb +14 -0
  78. data/spec/dummy/app/views/layouts/application.html.erb +17 -0
  79. data/spec/dummy/app/views/site/report.html.erb +73 -0
  80. data/spec/dummy/bin/bundle +3 -0
  81. data/spec/dummy/bin/rails +4 -0
  82. data/spec/dummy/bin/rake +4 -0
  83. data/spec/dummy/bin/setup +29 -0
  84. data/spec/dummy/config.ru +4 -0
  85. data/spec/dummy/config/application.rb +26 -0
  86. data/spec/dummy/config/boot.rb +5 -0
  87. data/spec/dummy/config/database.yml +22 -0
  88. data/spec/dummy/config/environment.rb +5 -0
  89. data/spec/dummy/config/environments/development.rb +41 -0
  90. data/spec/dummy/config/environments/production.rb +79 -0
  91. data/spec/dummy/config/environments/test.rb +42 -0
  92. data/spec/dummy/config/initializers/assets.rb +11 -0
  93. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  94. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  95. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  96. data/spec/dummy/config/initializers/inflections.rb +16 -0
  97. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  98. data/spec/dummy/config/initializers/session_store.rb +3 -0
  99. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  100. data/spec/dummy/config/locales/en.yml +23 -0
  101. data/spec/dummy/config/routes.rb +57 -0
  102. data/spec/dummy/config/secrets.yml +22 -0
  103. data/spec/dummy/db/migrate/20150714202319_add_dummy_models.rb +25 -0
  104. data/spec/dummy/db/schema.rb +43 -0
  105. data/spec/dummy/db/seeds.rb +1 -0
  106. data/spec/dummy/log/test.log +37033 -0
  107. data/spec/dummy/public/404.html +67 -0
  108. data/spec/dummy/public/422.html +67 -0
  109. data/spec/dummy/public/500.html +66 -0
  110. data/spec/dummy/public/favicon.ico +0 -0
  111. data/spec/factories/factories.rb +29 -0
  112. data/spec/spec_helper.rb +40 -0
  113. data/spec/support/float.rb +8 -0
  114. metadata +385 -0
@@ -0,0 +1,22 @@
1
+ require 'csv'
2
+
3
+ module ActiveReporter
4
+ module Serializer
5
+ class Csv < Table
6
+ def csv_text
7
+ CSV.generate do |csv|
8
+ csv << headers
9
+ each_row { |row| csv << row }
10
+ end
11
+ end
12
+
13
+ def save(filename = self.filename)
14
+ File.open(filename, 'w') { |f| f.write data }
15
+ end
16
+
17
+ def filename
18
+ "#{caption.parameterize}.csv"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,134 @@
1
+ module ActiveReporter
2
+ module Serializer
3
+ class FormField < Base
4
+ include ActionView::Helpers::FormTagHelper
5
+ include ActionView::Helpers::FormOptionsHelper
6
+
7
+ def html_fields
8
+ "<div class='#{wrapper_class}'>
9
+ #{axis_fields}
10
+ #{dimension_fields.join}
11
+ </div>".html_safe
12
+ end
13
+
14
+ def aggregator_options
15
+ @agg_opts ||= report.aggregators.map { |name, agg| [human_aggregator_label(agg), name] }
16
+ end
17
+
18
+ def dimension_options
19
+ @dim_opts ||= report.dimensions.map { |name, dim| [human_dimension_label(dim), name] }
20
+ end
21
+
22
+ def aggregator_field
23
+ select_tag("#{prefix}[aggregator]", options_for_select(aggregator_options, report.aggregators.keys.first))
24
+ end
25
+
26
+ def primary_grouper_field
27
+ select_tag("#{prefix}[groupers][0]", options_for_select(dimension_options, report.grouper_names[0]))
28
+ end
29
+
30
+ def secondary_grouper_field
31
+ select_tag("#{prefix}[groupers][1]", options_for_select([[nil, nil]]+dimension_options, report.grouper_names[1]))
32
+ end
33
+
34
+ def axis_fields
35
+ "<div class='#{axis_fields_class}'>
36
+ Show me #{aggregator_field}
37
+ by #{primary_grouper_field}
38
+ and #{secondary_grouper_field}
39
+ for
40
+ </div>".html_safe
41
+ end
42
+
43
+ def dimension_fields
44
+ report.dimensions.map { |name, dimension| field_for(dimension) }.compact
45
+ end
46
+
47
+ def field_for(dimension)
48
+ case dimension
49
+ when ActiveReporter::Dimension::Category then category_dimension_field(dimension)
50
+ when ActiveReporter::Dimension::Set then bin_dimension_field(dimension)
51
+ end
52
+ end
53
+
54
+ def category_dimension_field(dimension)
55
+ options = [[nil, nil]]
56
+
57
+ dimension.all_values.each do |value|
58
+ options << [human_dimension_value_label(dimension, value), value]
59
+ end
60
+
61
+ fields_for(dimension) do
62
+ select_tag("#{prefix_for(dimension)}[only]", options_for_select(options, dimension.filter_values.first))
63
+ end
64
+ end
65
+
66
+ def bin_dimension_field(dimension)
67
+ fields_for(dimension) do
68
+ fields = "#{bin_min_field(dimension)} to #{bin_max_field(dimension)}"
69
+ fields += " by #{bin_step_field(dimension)}" if dimension.grouping?
70
+ fields
71
+ end
72
+ end
73
+
74
+ def bin_min_field(dimension)
75
+ text_field_tag("#{prefix_for(dimension)}[only][min]", dimension.filter_min, placeholder: bin_min_placeholder(dimension))
76
+ end
77
+
78
+ def bin_max_field(dimension)
79
+ text_field_tag("#{prefix_for(dimension)}[only][max]", dimension.filter_max, placeholder: bin_max_placeholder(dimension))
80
+ end
81
+
82
+ def bin_step_field(dimension)
83
+ text_field_tag("#{prefix_for(dimension)}[bin_width]", dimension.params[:bin_width], placeholder: bin_step_placeholder(dimension))
84
+ end
85
+
86
+ def fields_for(dimension, &block)
87
+ "<fieldset class='#{dimension_fields_class(dimension)}'>
88
+ <legend>#{human_dimension_label(dimension)}</legend>
89
+ #{yield}
90
+ </fieldset>".html_safe
91
+ end
92
+
93
+ def wrapper_class
94
+ "active-reporter-fields active-reporter-fields--#{css_class(report.class.name)}"
95
+ end
96
+
97
+ def axis_fields_class
98
+ 'active-reporter-axis-fields'
99
+ end
100
+
101
+ def dimension_fields_class(dimension)
102
+ [
103
+ 'active-reporter-dimension-fields',
104
+ "active-reporter-dimension-fields--#{css_class(dimension.name)}",
105
+ "active-reporter-dimension-fields--#{css_class(dimension.class.name)}"
106
+ ].join(' ')
107
+ end
108
+
109
+ def bin_max_placeholder(dimension)
110
+ 'max'
111
+ end
112
+
113
+ def bin_min_placeholder(dimension)
114
+ 'min'
115
+ end
116
+
117
+ def bin_step_placeholder(dimension)
118
+ dimension.bin_width.inspect
119
+ end
120
+
121
+ def prefix
122
+ report.class.name.underscore
123
+ end
124
+
125
+ def prefix_for(dimension)
126
+ "#{prefix}[dimensions][#{dimension.name}]"
127
+ end
128
+
129
+ def css_class(s)
130
+ s.to_s.demodulize.underscore.dasherize
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveReporter
2
+ module Serializer
3
+ class HashTable < Base
4
+ def table
5
+ fields = (report.grouper_names + report.aggregators.keys)
6
+ titles = report.groupers.map(&method(:human_dimension_label)) + report.aggregators.collect { |k, v| human_aggregator_label({ k => v }) }
7
+
8
+ [fields.zip(titles).to_h] + report.hashed_data.collect { |row| row.map { |k,v| [k, (v.respond_to?(:min) ? v.min : v).to_s] }.to_h}
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,200 @@
1
+ module ActiveReporter
2
+ module Serializer
3
+ class Highcharts < Table
4
+ def colors
5
+ ['#7cb5ec', '#434348', '#90ed7d', '#f7a35c', '#8085e9', '#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1']
6
+ end
7
+
8
+ def color_hash
9
+ # ensure we consistently assign the same color to the same dimension-
10
+ # value pair
11
+ @color_hash ||= Hash.new do |h, key|
12
+ color_cycle = colors.cycle
13
+ h[key] = Hash.new do |hh, value|
14
+ hh[value] = color_cycle.next
15
+ end
16
+ end
17
+ end
18
+
19
+ def color_for(dimension, value)
20
+ # override this if the values of a particular dimension can take on
21
+ # meaningful colors
22
+ color_hash[dimension.name][value]
23
+ end
24
+
25
+ # def series
26
+ # case report.groupers.count
27
+ # when 2
28
+ # dim1, dim2 = report.groupers
29
+ # report.data.flat_map.with_index do |d2, i|
30
+ # d2[:values].map do |d1|
31
+ # {
32
+ # stack: human_dimension_value_label(dim2, d2[:key]),
33
+ # name: human_dimension_value_label(dim1, d1[:key]),
34
+ # (i == 0 ? :id : :linkedTo) => human_dimension_value_label(dim1, d1[:key]),
35
+ # color: color_for(dim1, d1[:key]),
36
+ # data: d1[:values].map do |k,v|
37
+ # {
38
+ # y: v.to_f,
39
+ # tooltip: tooltip_for(k => v, dim1 => d1),
40
+ # filters: filters_for(k => v, dim1 => d1)
41
+ # }
42
+ # end
43
+ # }
44
+ # end
45
+ # end
46
+ # when 1
47
+ # dim1 = report.groupers.first
48
+ # report.data.map do |d1|
49
+ # {
50
+ # name: human_dimension_value_label(dim1, d1[:key]),
51
+ # color: color_for(dim1, d1[:key]),
52
+ # data: d1[:values].map do |k,v|
53
+ # {
54
+ # y: v.to_f,
55
+ # tooltip: tooltip_for(k => v, dim1 => d1),
56
+ # filters: filters_for(k => v, dim1 => d1)
57
+ # }
58
+ # end
59
+ # }
60
+ # end
61
+ # else
62
+ # raise ActiveReporter::InvalidParamsError, "report must have <= 2 groupers"
63
+ # end
64
+ # end
65
+
66
+ def series
67
+ case report.groupers.count
68
+ when 3
69
+ dim1, dim2, dim3 = report.groupers
70
+ report.data.flat_map.with_index do |d3, i|
71
+ d3[:values].map { |d2| {
72
+ stack: human_dimension_value_label(dim3, d3[:key]),
73
+ name: human_dimension_value_label(dim2, d2[:key]),
74
+ (i == 0 ? :id : :linkedTo) => human_dimension_value_label(dim2, d2[:key]),
75
+ color: color_for(dim2, d2[:key]),
76
+ data: d2[:values].map { |d1| {
77
+ y: d1[:value].to_f,
78
+ tooltip: tooltip_for(dim1 => d1, dim2 => d2, dim3 => d3),
79
+ filters: filters_for(dim1 => d1, dim2 => d2, dim3 => d3)
80
+ }}
81
+ }}
82
+ end
83
+ when 2
84
+ dim1, dim2 = report.groupers
85
+ report.data.map { |d2| {
86
+ name: human_dimension_value_label(dim2, d2[:key]),
87
+ color: color_for(dim2, d2[:key]),
88
+ data: d2[:values].map { |d1| {
89
+ y: d1[:value].to_f,
90
+ tooltip: tooltip_for(dim1 => d1, dim2 => d2),
91
+ filters: filters_for(dim1 => d1, dim2 => d2)
92
+ }}
93
+ }}
94
+ when 1
95
+ dim1 = report.groupers.first
96
+ [{
97
+ name: human_aggregator_label(report.aggregators),
98
+ data: report.data.map { |d1| {
99
+ y: d1[:value].to_f,
100
+ tooltip: tooltip_for(dim1 => d1),
101
+ filters: filters_for(dim1 => d1)
102
+ }}
103
+ }]
104
+ else
105
+ raise ActiveReporter::InvalidParamsError, "report must have <= 3 groupers"
106
+ end
107
+ end
108
+
109
+ def tooltip_for(xes)
110
+ lines = []
111
+ xes.each do |dim, d|
112
+ lines << [
113
+ human_dimension_label(dim),
114
+ human_dimension_value_label(dim, d[:key])
115
+ ]
116
+ end
117
+ lines << [
118
+ human_aggregator_label(report.aggregators),
119
+ human_aggregator_value_label(report.aggregators, xes[report.groupers.first][:value])
120
+ ]
121
+ lines.map { |k, v| "<b>#{k}:</b> #{v}" }.join('<br/>')
122
+ end
123
+
124
+ def filters_for(xes)
125
+ xes.each_with_object({}) do |(dim, d), h|
126
+ h[dim.name] = d[:key]
127
+ end
128
+ end
129
+
130
+ def categories
131
+ dimension = report.groupers.first
132
+ dimension.group_values.map do |value|
133
+ human_dimension_value_label(dimension, value)
134
+ end
135
+ end
136
+
137
+ def chart_title
138
+ axis_summary
139
+ end
140
+
141
+ def chart_subtitle
142
+ filter_summary
143
+ end
144
+
145
+ def x_axis_title
146
+ human_dimension_label(report.groupers.first)
147
+ end
148
+
149
+ def y_axis_title
150
+ human_aggregator_label(report.aggregators)
151
+ end
152
+
153
+ def highcharts_options
154
+ {
155
+ chart: {
156
+ type: 'column'
157
+ },
158
+ colors: colors,
159
+ title: {
160
+ text: chart_title
161
+ },
162
+ subtitle: {
163
+ text: chart_subtitle
164
+ },
165
+ series: series,
166
+ xAxis: {
167
+ categories: categories,
168
+ title: {
169
+ text: x_axis_title
170
+ }
171
+ },
172
+ yAxis: {
173
+ allowDecimals: true,
174
+ title: {
175
+ text: y_axis_title
176
+ },
177
+ stackLabels: {
178
+ enabled: report.groupers.length >= 3,
179
+ format: '{stack}',
180
+ rotation: -45,
181
+ textAlign: 'left'
182
+ }
183
+ },
184
+ legend: {
185
+ enabled: report.groupers.length >= 2
186
+ },
187
+ tooltip: {},
188
+ plotOptions: {
189
+ series: {
190
+ events: {}
191
+ },
192
+ column: {
193
+ stacking: ('normal' if report.groupers.length >= 2)
194
+ }
195
+ }
196
+ }
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveReporter
2
+ module Serializer
3
+ class NestedHash < Base
4
+ def table
5
+ report.hashed_data.collect { |row| row.map { |k,v| [k, (v.respond_to?(:min) ? v.min : v).to_s] }.to_h }.collect do |row|
6
+ report.grouper_names.reverse.inject(row.slice(*report.aggregators.keys)) { |nested_row_data, group| { row[group] => nested_row_data } }
7
+ end.reduce({}, :merge)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveReporter
2
+ module Serializer
3
+ class Table < Base
4
+ def headers
5
+ report.groupers.map(&method(:human_dimension_label)) + [human_aggregator_label(report.aggregators)]
6
+ end
7
+
8
+ def each_row
9
+ return to_enum(__method__) unless block_given?
10
+
11
+ report.flat_data.each do |xes, y|
12
+ yield report.groupers.zip(xes).map { |d, v| human_dimension_value_label(d, v) } + [human_aggregator_value_label(report.aggregators, y)]
13
+ end
14
+ end
15
+
16
+ def caption
17
+ axis_summary
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_reporter/tracker/base'
2
+ require 'active_reporter/tracker/delta'
@@ -0,0 +1,15 @@
1
+ require 'active_reporter/aggregator/base'
2
+
3
+ module ActiveReporter
4
+ module Tracker
5
+ class Base < ActiveReporter::Aggregator::Base
6
+ def aggregator
7
+ opts[:aggregator] || name
8
+ end
9
+
10
+ def prior_aggregator
11
+ opts[:prior_aggregator] || aggregator
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveReporter
2
+ module Tracker
3
+ class Delta < ActiveReporter::Tracker::Base
4
+ def track(row, prior_row)
5
+ ((row[aggregator].to_f / prior_row[prior_aggregator].to_f) * 100) unless row.nil? || prior_row.nil? || prior_row[prior_aggregator].to_f == 0
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveReporter
2
+ VERSION = "0.5.8"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :active_reporter do
3
+ # # Task goes here
4
+ # end