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