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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +14 -0
- data/README.md +436 -0
- data/Rakefile +23 -0
- data/lib/active_reporter.rb +26 -0
- data/lib/active_reporter/aggregator.rb +9 -0
- data/lib/active_reporter/aggregator/array.rb +14 -0
- data/lib/active_reporter/aggregator/average.rb +9 -0
- data/lib/active_reporter/aggregator/base.rb +73 -0
- data/lib/active_reporter/aggregator/count.rb +23 -0
- data/lib/active_reporter/aggregator/count_if.rb +23 -0
- data/lib/active_reporter/aggregator/max.rb +9 -0
- data/lib/active_reporter/aggregator/min.rb +9 -0
- data/lib/active_reporter/aggregator/ratio.rb +23 -0
- data/lib/active_reporter/aggregator/sum.rb +13 -0
- data/lib/active_reporter/calculator.rb +2 -0
- data/lib/active_reporter/calculator/base.rb +19 -0
- data/lib/active_reporter/calculator/ratio.rb +9 -0
- data/lib/active_reporter/dimension.rb +8 -0
- data/lib/active_reporter/dimension/base.rb +150 -0
- data/lib/active_reporter/dimension/bin.rb +123 -0
- data/lib/active_reporter/dimension/bin/set.rb +162 -0
- data/lib/active_reporter/dimension/bin/table.rb +43 -0
- data/lib/active_reporter/dimension/category.rb +29 -0
- data/lib/active_reporter/dimension/enum.rb +32 -0
- data/lib/active_reporter/dimension/number.rb +51 -0
- data/lib/active_reporter/dimension/time.rb +93 -0
- data/lib/active_reporter/evaluator.rb +2 -0
- data/lib/active_reporter/evaluator/base.rb +17 -0
- data/lib/active_reporter/evaluator/block.rb +15 -0
- data/lib/active_reporter/inflector.rb +8 -0
- data/lib/active_reporter/invalid_params_error.rb +4 -0
- data/lib/active_reporter/report.rb +102 -0
- data/lib/active_reporter/report/aggregation.rb +297 -0
- data/lib/active_reporter/report/definition.rb +195 -0
- data/lib/active_reporter/report/metrics.rb +75 -0
- data/lib/active_reporter/report/validation.rb +106 -0
- data/lib/active_reporter/serializer.rb +7 -0
- data/lib/active_reporter/serializer/base.rb +103 -0
- data/lib/active_reporter/serializer/csv.rb +22 -0
- data/lib/active_reporter/serializer/form_field.rb +134 -0
- data/lib/active_reporter/serializer/hash_table.rb +12 -0
- data/lib/active_reporter/serializer/highcharts.rb +200 -0
- data/lib/active_reporter/serializer/nested_hash.rb +11 -0
- data/lib/active_reporter/serializer/table.rb +21 -0
- data/lib/active_reporter/tracker.rb +2 -0
- data/lib/active_reporter/tracker/base.rb +15 -0
- data/lib/active_reporter/tracker/delta.rb +9 -0
- data/lib/active_reporter/version.rb +3 -0
- data/lib/tasks/active_reporter_tasks.rake +4 -0
- data/spec/acceptance/data_spec.rb +381 -0
- data/spec/active_reporter/aggregator_spec.rb +102 -0
- data/spec/active_reporter/dimension/base_spec.rb +102 -0
- data/spec/active_reporter/dimension/bin/set_spec.rb +83 -0
- data/spec/active_reporter/dimension/bin/table_spec.rb +47 -0
- data/spec/active_reporter/dimension/bin_spec.rb +77 -0
- data/spec/active_reporter/dimension/category_spec.rb +60 -0
- data/spec/active_reporter/dimension/enum_spec.rb +94 -0
- data/spec/active_reporter/dimension/number_spec.rb +71 -0
- data/spec/active_reporter/dimension/time_spec.rb +61 -0
- data/spec/active_reporter/report_spec.rb +597 -0
- data/spec/active_reporter/serializer/hash_table_spec.rb +45 -0
- data/spec/active_reporter/serializer/highcharts_spec.rb +113 -0
- data/spec/active_reporter/serializer/table_spec.rb +62 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +26 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/site_controller.rb +11 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/author.rb +4 -0
- data/spec/dummy/app/models/comment.rb +4 -0
- data/spec/dummy/app/models/data_builder.rb +112 -0
- data/spec/dummy/app/models/post.rb +6 -0
- data/spec/dummy/app/models/post_report.rb +14 -0
- data/spec/dummy/app/views/layouts/application.html.erb +17 -0
- data/spec/dummy/app/views/site/report.html.erb +73 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +57 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/migrate/20150714202319_add_dummy_models.rb +25 -0
- data/spec/dummy/db/schema.rb +43 -0
- data/spec/dummy/db/seeds.rb +1 -0
- data/spec/dummy/log/test.log +37033 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/factories.rb +29 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/float.rb +8 -0
- 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,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
|