mikeg-vanity 1.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.
- data/CHANGELOG +153 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +83 -0
- data/bin/vanity +53 -0
- data/lib/vanity.rb +38 -0
- data/lib/vanity/backport.rb +43 -0
- data/lib/vanity/commands.rb +2 -0
- data/lib/vanity/commands/list.rb +21 -0
- data/lib/vanity/commands/report.rb +60 -0
- data/lib/vanity/experiment/ab_test.rb +477 -0
- data/lib/vanity/experiment/base.rb +212 -0
- data/lib/vanity/helpers.rb +59 -0
- data/lib/vanity/metric/active_record.rb +77 -0
- data/lib/vanity/metric/base.rb +221 -0
- data/lib/vanity/metric/google_analytics.rb +70 -0
- data/lib/vanity/mock_redis.rb +76 -0
- data/lib/vanity/playground.rb +197 -0
- data/lib/vanity/rails.rb +22 -0
- data/lib/vanity/rails/dashboard.rb +24 -0
- data/lib/vanity/rails/helpers.rb +158 -0
- data/lib/vanity/rails/testing.rb +11 -0
- data/lib/vanity/templates/_ab_test.erb +26 -0
- data/lib/vanity/templates/_experiment.erb +5 -0
- data/lib/vanity/templates/_experiments.erb +7 -0
- data/lib/vanity/templates/_metric.erb +14 -0
- data/lib/vanity/templates/_metrics.erb +13 -0
- data/lib/vanity/templates/_report.erb +27 -0
- data/lib/vanity/templates/flot.min.js +1 -0
- data/lib/vanity/templates/jquery.min.js +19 -0
- data/lib/vanity/templates/vanity.css +26 -0
- data/lib/vanity/templates/vanity.js +82 -0
- data/test/ab_test_test.rb +656 -0
- data/test/experiment_test.rb +136 -0
- data/test/experiments/age_and_zipcode.rb +19 -0
- data/test/experiments/metrics/cheers.rb +3 -0
- data/test/experiments/metrics/signups.rb +2 -0
- data/test/experiments/metrics/yawns.rb +3 -0
- data/test/experiments/null_abc.rb +5 -0
- data/test/metric_test.rb +518 -0
- data/test/playground_test.rb +10 -0
- data/test/rails_test.rb +104 -0
- data/test/test_helper.rb +135 -0
- data/vanity.gemspec +18 -0
- data/vendor/redis-rb/LICENSE +20 -0
- data/vendor/redis-rb/README.markdown +36 -0
- data/vendor/redis-rb/Rakefile +62 -0
- data/vendor/redis-rb/bench.rb +44 -0
- data/vendor/redis-rb/benchmarking/suite.rb +24 -0
- data/vendor/redis-rb/benchmarking/worker.rb +71 -0
- data/vendor/redis-rb/bin/distredis +33 -0
- data/vendor/redis-rb/examples/basic.rb +16 -0
- data/vendor/redis-rb/examples/incr-decr.rb +18 -0
- data/vendor/redis-rb/examples/list.rb +26 -0
- data/vendor/redis-rb/examples/sets.rb +36 -0
- data/vendor/redis-rb/lib/dist_redis.rb +124 -0
- data/vendor/redis-rb/lib/hash_ring.rb +128 -0
- data/vendor/redis-rb/lib/pipeline.rb +21 -0
- data/vendor/redis-rb/lib/redis.rb +370 -0
- data/vendor/redis-rb/lib/redis/raketasks.rb +1 -0
- data/vendor/redis-rb/profile.rb +22 -0
- data/vendor/redis-rb/redis-rb.gemspec +30 -0
- data/vendor/redis-rb/spec/redis_spec.rb +637 -0
- data/vendor/redis-rb/spec/spec_helper.rb +4 -0
- data/vendor/redis-rb/speed.rb +16 -0
- data/vendor/redis-rb/tasks/redis.tasks.rb +140 -0
- metadata +125 -0
@@ -0,0 +1,136 @@
|
|
1
|
+
require "test/test_helper"
|
2
|
+
|
3
|
+
class ExperimentTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
metric "Happiness"
|
8
|
+
end
|
9
|
+
|
10
|
+
# -- Defining experiment --
|
11
|
+
|
12
|
+
def test_can_access_experiment_by_id
|
13
|
+
exp = new_ab_test(:ice_cream_flavor) { metrics :happiness }
|
14
|
+
assert_equal exp, experiment(:ice_cream_flavor)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_fail_when_defining_same_experiment_twice
|
18
|
+
File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
|
19
|
+
f.write <<-RUBY
|
20
|
+
ab_test "Ice Cream Flavor" do
|
21
|
+
metrics :happiness
|
22
|
+
end
|
23
|
+
ab_test "Ice Cream Flavor" do
|
24
|
+
metrics :happiness
|
25
|
+
end
|
26
|
+
RUBY
|
27
|
+
end
|
28
|
+
assert_raises NameError do
|
29
|
+
experiment(:ice_cream_flavor)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_uses_playground_namespace_for_experiment
|
34
|
+
new_ab_test(:ice_cream_flavor) { metrics :happiness }
|
35
|
+
assert_equal "vanity:#{Vanity::Version::MAJOR}:ice_cream_flavor", experiment(:ice_cream_flavor).send(:key)
|
36
|
+
assert_equal "vanity:#{Vanity::Version::MAJOR}:ice_cream_flavor:participants", experiment(:ice_cream_flavor).send(:key, "participants")
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# -- Loading experiments --
|
41
|
+
|
42
|
+
def test_fails_if_cannot_load_named_experiment
|
43
|
+
assert_raises NameError do
|
44
|
+
experiment(:ice_cream_flavor)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_loading_experiment
|
49
|
+
File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
|
50
|
+
f.write <<-RUBY
|
51
|
+
ab_test "Ice Cream Flavor" do
|
52
|
+
def xmts
|
53
|
+
"x"
|
54
|
+
end
|
55
|
+
metrics :happiness
|
56
|
+
end
|
57
|
+
RUBY
|
58
|
+
end
|
59
|
+
assert_equal "x", experiment(:ice_cream_flavor).xmts
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_fails_if_error_loading_experiment
|
63
|
+
File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
|
64
|
+
f.write "fail 'yawn!'"
|
65
|
+
end
|
66
|
+
assert_raises NameError do
|
67
|
+
experiment(:ice_cream_flavor)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_complains_if_not_defined_where_expected
|
72
|
+
File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
|
73
|
+
f.write ""
|
74
|
+
end
|
75
|
+
assert_raises NameError do
|
76
|
+
experiment(:ice_cream_flavor)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_reloading_experiments
|
81
|
+
new_ab_test(:ab) { metrics :happiness }
|
82
|
+
new_ab_test(:cd) { metrics :happiness }
|
83
|
+
assert 2, Vanity.playground.experiments.size
|
84
|
+
Vanity.playground.reload!
|
85
|
+
assert Vanity.playground.experiments.empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# -- Attributes --
|
90
|
+
|
91
|
+
def test_experiment_mapping_name_to_id
|
92
|
+
experiment = new_ab_test("Ice Cream Flavor/Tastes") { metrics :happiness }
|
93
|
+
assert_equal "Ice Cream Flavor/Tastes", experiment.name
|
94
|
+
assert_equal :ice_cream_flavor_tastes, experiment.id
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_saving_experiment_after_definition
|
98
|
+
File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
|
99
|
+
f.write <<-RUBY
|
100
|
+
ab_test "Ice Cream Flavor" do
|
101
|
+
metrics :happiness
|
102
|
+
expects(:save)
|
103
|
+
end
|
104
|
+
RUBY
|
105
|
+
end
|
106
|
+
Vanity.playground.experiment(:ice_cream_flavor)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_experiment_has_created_timestamp
|
110
|
+
new_ab_test(:ice_cream_flavor) { metrics :happiness }
|
111
|
+
assert_instance_of Time, experiment(:ice_cream_flavor).created_at
|
112
|
+
assert_in_delta experiment(:ice_cream_flavor).created_at.to_i, Time.now.to_i, 1
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_experiment_keeps_created_timestamp_across_definitions
|
116
|
+
past = Date.today - 1
|
117
|
+
Timecop.travel past do
|
118
|
+
new_ab_test(:ice_cream_flavor) { metrics :happiness }
|
119
|
+
assert_equal past.to_time.to_i, experiment(:ice_cream_flavor).created_at.to_i
|
120
|
+
end
|
121
|
+
|
122
|
+
new_playground
|
123
|
+
metric :happiness
|
124
|
+
new_ab_test(:ice_cream_flavor) { metrics :happiness }
|
125
|
+
assert_equal past.to_time.to_i, experiment(:ice_cream_flavor).created_at.to_i
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_experiment_has_description
|
129
|
+
new_ab_test :ice_cream_flavor do
|
130
|
+
description "Because 31 is not enough ..."
|
131
|
+
metrics :happiness
|
132
|
+
end
|
133
|
+
assert_equal "Because 31 is not enough ...", experiment(:ice_cream_flavor).description
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
ab_test "Age and Zipcode" do
|
2
|
+
description <<-TEXT
|
3
|
+
Testing new registration form that asks for age and zipcode. Option A presents
|
4
|
+
the existing form, and option B adds age and zipcode fields.
|
5
|
+
|
6
|
+
We know option B will convert less, but higher quality leads. If we lose less
|
7
|
+
than 20% conversions, we're going to switch to option B.
|
8
|
+
TEXT
|
9
|
+
metrics :signups
|
10
|
+
|
11
|
+
complete_if do
|
12
|
+
alternatives.all? { |alt| alt.participants > 100 }
|
13
|
+
end
|
14
|
+
outcome_is do
|
15
|
+
one_field = alternative(false)
|
16
|
+
three_fields = alternative(true)
|
17
|
+
three_fields.conversion_rate >= 0.8 * one_field.conversion_rate ? three_fields : one_field
|
18
|
+
end
|
19
|
+
end
|
data/test/metric_test.rb
ADDED
@@ -0,0 +1,518 @@
|
|
1
|
+
require "test/test_helper"
|
2
|
+
|
3
|
+
class Sky < ActiveRecord::Base
|
4
|
+
connection.drop_table :skies if table_exists?
|
5
|
+
connection.create_table :skies do |t|
|
6
|
+
t.integer :height
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
named_scope :high, lambda { { :conditions=>"height >= 4" } }
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
context "Metric" do
|
15
|
+
|
16
|
+
# -- Via the playground --
|
17
|
+
|
18
|
+
context "playground" do
|
19
|
+
|
20
|
+
test "knows all loaded metrics" do
|
21
|
+
metric "Yawns/sec", "Cheers/sec"
|
22
|
+
assert Vanity.playground.metrics.keys.include?(:yawns_sec)
|
23
|
+
assert Vanity.playground.metrics.keys.include?(:cheers_sec)
|
24
|
+
end
|
25
|
+
|
26
|
+
test "loads metric definitions" do
|
27
|
+
File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
|
28
|
+
f.write <<-RUBY
|
29
|
+
metric "Yawns/sec" do
|
30
|
+
def xmts
|
31
|
+
"x"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
RUBY
|
35
|
+
end
|
36
|
+
assert_equal "x", Vanity.playground.metric(:yawns_sec).xmts
|
37
|
+
end
|
38
|
+
|
39
|
+
test "bubbles up loaded metrics" do
|
40
|
+
File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
|
41
|
+
f.write "fail 'yawn!'"
|
42
|
+
end
|
43
|
+
assert_raises NameError do
|
44
|
+
Vanity.playground.metric(:yawns_sec)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
test "map identifier from file name" do
|
49
|
+
File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
|
50
|
+
f.write <<-RUBY
|
51
|
+
metric "yawns/hour" do
|
52
|
+
end
|
53
|
+
RUBY
|
54
|
+
end
|
55
|
+
assert Vanity.playground.metric(:yawns_sec)
|
56
|
+
end
|
57
|
+
|
58
|
+
test "fails tracking unknown metric" do
|
59
|
+
assert_raises NameError do
|
60
|
+
Vanity.playground.track! :yawns_sec
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
test "reloading metrics" do
|
65
|
+
metric "Yawns/sec", "Cheers/sec"
|
66
|
+
Vanity.playground.metric(:yawns_sec)
|
67
|
+
Vanity.playground.metric(:cheers_sec)
|
68
|
+
assert 2, Vanity.playground.metrics.size
|
69
|
+
metrics = Vanity.playground.metrics.values
|
70
|
+
Vanity.playground.reload!
|
71
|
+
assert 2, Vanity.playground.metrics.size
|
72
|
+
assert_not_equal metrics, Vanity.playground.metrics.values
|
73
|
+
end
|
74
|
+
|
75
|
+
test "ignores undefined metrics in database" do
|
76
|
+
metric "Yawns/sec"
|
77
|
+
Vanity.playground.reload!
|
78
|
+
assert Vanity.playground.metrics.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# -- Tracking --
|
85
|
+
|
86
|
+
context "tracking" do
|
87
|
+
test "can count" do
|
88
|
+
metric "Yawns/sec", "Cheers/sec"
|
89
|
+
4.times { Vanity.playground.track! :yawns_sec }
|
90
|
+
2.times { Vanity.playground.track! :cheers_sec }
|
91
|
+
yawns = Vanity.playground.metric(:yawns_sec).values(today, today).first
|
92
|
+
cheers = Vanity.playground.metric(:cheers_sec).values(today, today).first
|
93
|
+
assert yawns = 2 * cheers
|
94
|
+
end
|
95
|
+
|
96
|
+
test "can tell the time" do
|
97
|
+
metric "Yawns/sec"
|
98
|
+
Timecop.travel(today - 4) { 4.times { Vanity.playground.track! :yawns_sec } }
|
99
|
+
Timecop.travel(today - 2) { 2.times { Vanity.playground.track! :yawns_sec } }
|
100
|
+
1.times { Vanity.playground.track! :yawns_sec }
|
101
|
+
boredom = Vanity.playground.metric(:yawns_sec).values(today - 5, today)
|
102
|
+
assert_equal [0,4,0,2,0,1], boredom
|
103
|
+
end
|
104
|
+
|
105
|
+
test "with no value" do
|
106
|
+
metric "Yawns/sec", "Cheers/sec", "Looks"
|
107
|
+
Vanity.playground.track! :yawns_sec, 0
|
108
|
+
Vanity.playground.track! :cheers_sec, -1
|
109
|
+
Vanity.playground.track! :looks, 10
|
110
|
+
assert_equal 0, Vanity.playground.metric(:yawns_sec).values(today, today).sum
|
111
|
+
assert_equal 0, Vanity.playground.metric(:cheers_sec).values(today, today).sum
|
112
|
+
assert_equal 10, Vanity.playground.metric(:looks).values(today, today).sum
|
113
|
+
end
|
114
|
+
|
115
|
+
test "with count" do
|
116
|
+
metric "Yawns/sec"
|
117
|
+
Timecop.travel(today - 4) { Vanity.playground.track! :yawns_sec, 4 }
|
118
|
+
Timecop.travel(today - 2) { Vanity.playground.track! :yawns_sec, 2 }
|
119
|
+
Vanity.playground.track! :yawns_sec
|
120
|
+
boredom = Vanity.playground.metric(:yawns_sec).values(today - 5, today)
|
121
|
+
assert_equal [0,4,0,2,0,1], boredom
|
122
|
+
end
|
123
|
+
|
124
|
+
test "runs hook" do
|
125
|
+
metric "Many Happy Returns"
|
126
|
+
total = 0
|
127
|
+
Vanity.playground.metric(:many_happy_returns).hook do |metric_id, timestamp, count|
|
128
|
+
assert_equal :many_happy_returns, metric_id
|
129
|
+
assert_in_delta Time.now.to_i, timestamp.to_i, 1
|
130
|
+
total += count
|
131
|
+
end
|
132
|
+
Vanity.playground.track! :many_happy_returns, 6
|
133
|
+
assert_equal 6, total
|
134
|
+
end
|
135
|
+
|
136
|
+
test "runs multiple hooks" do
|
137
|
+
metric "Many Happy Returns"
|
138
|
+
returns = 0
|
139
|
+
Vanity.playground.metric(:many_happy_returns).hook { returns += 1 }
|
140
|
+
Vanity.playground.metric(:many_happy_returns).hook { returns += 1 }
|
141
|
+
Vanity.playground.metric(:many_happy_returns).hook { returns += 1 }
|
142
|
+
Vanity.playground.track! :many_happy_returns
|
143
|
+
assert_equal 3, returns
|
144
|
+
end
|
145
|
+
|
146
|
+
test "destroy wipes metrics" do
|
147
|
+
metric "Many Happy Returns"
|
148
|
+
Vanity.playground.track! :many_happy_returns, 3
|
149
|
+
assert_equal [3], Vanity.playground.metric(:many_happy_returns).values(today, today)
|
150
|
+
Vanity.playground.metric(:many_happy_returns).destroy!
|
151
|
+
assert_equal [0], Vanity.playground.metric(:many_happy_returns).values(today, today)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
# -- Metric name --
|
157
|
+
|
158
|
+
context "name" do
|
159
|
+
test "can be whatever" do
|
160
|
+
File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
|
161
|
+
f.write <<-RUBY
|
162
|
+
metric "Yawns per second" do
|
163
|
+
end
|
164
|
+
RUBY
|
165
|
+
end
|
166
|
+
assert_equal "Yawns per second", Vanity.playground.metric(:yawns_sec).name
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
# -- Description helper --
|
172
|
+
|
173
|
+
context "description" do
|
174
|
+
test "metric with description" do
|
175
|
+
File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
|
176
|
+
f.write <<-RUBY
|
177
|
+
metric "Yawns/sec" do
|
178
|
+
description "Am I that boring?"
|
179
|
+
end
|
180
|
+
RUBY
|
181
|
+
end
|
182
|
+
assert_equal "Am I that boring?", Vanity::Metric.description(Vanity.playground.metric(:yawns_sec))
|
183
|
+
end
|
184
|
+
|
185
|
+
test "metric without description" do
|
186
|
+
File.open "tmp/experiments/metrics/yawns_sec.rb", "w" do |f|
|
187
|
+
f.write <<-RUBY
|
188
|
+
metric "Yawns/sec" do
|
189
|
+
end
|
190
|
+
RUBY
|
191
|
+
end
|
192
|
+
assert_nil Vanity::Metric.description(Vanity.playground.metric(:yawns_sec))
|
193
|
+
end
|
194
|
+
|
195
|
+
test "metric with no method description" do
|
196
|
+
metric = Object.new
|
197
|
+
assert_nil Vanity::Metric.description(metric)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# -- Metric bounds --
|
203
|
+
|
204
|
+
context "bounds" do
|
205
|
+
test "metric with bounds" do
|
206
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
207
|
+
f.write <<-RUBY
|
208
|
+
metric "Sky is limit" do
|
209
|
+
def bounds
|
210
|
+
[6,12]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
RUBY
|
214
|
+
end
|
215
|
+
assert_equal [6,12], Vanity::Metric.bounds(Vanity.playground.metric(:sky_is_limit))
|
216
|
+
end
|
217
|
+
|
218
|
+
test "metric without bounds" do
|
219
|
+
metric "Sky is limit"
|
220
|
+
assert_equal [nil, nil], Vanity::Metric.bounds(Vanity.playground.metric(:sky_is_limit))
|
221
|
+
end
|
222
|
+
|
223
|
+
test "metric with no method bounds" do
|
224
|
+
metric = Object.new
|
225
|
+
assert_equal [nil, nil], Vanity::Metric.bounds(metric)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
# -- Timestamp --
|
231
|
+
|
232
|
+
context "created_at" do
|
233
|
+
test "for new metric" do
|
234
|
+
metric "Coolness"
|
235
|
+
metric = Vanity.playground.metric(:coolness)
|
236
|
+
assert_instance_of Time, metric.created_at
|
237
|
+
assert_in_delta metric.created_at.to_i, Time.now.to_i, 1
|
238
|
+
end
|
239
|
+
|
240
|
+
test "across restarts" do
|
241
|
+
past = Date.today - 1
|
242
|
+
Timecop.travel past do
|
243
|
+
metric "Coolness"
|
244
|
+
coolness = Vanity.playground.metric(:coolness)
|
245
|
+
assert_in_delta coolness.created_at.to_i, past.to_time.to_i, 1
|
246
|
+
end
|
247
|
+
|
248
|
+
new_playground
|
249
|
+
metric "Coolness"
|
250
|
+
new_cool = Vanity.playground.metric(:coolness)
|
251
|
+
assert_in_delta new_cool.created_at.to_i, past.to_time.to_i, 1
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
# -- Data --
|
257
|
+
|
258
|
+
context "data" do
|
259
|
+
test "explicit dates" do
|
260
|
+
metric "Yawns/sec"
|
261
|
+
Timecop.travel(today - 4) { Vanity.playground.track! :yawns_sec, 4 }
|
262
|
+
Timecop.travel(today - 2) { Vanity.playground.track! :yawns_sec, 2 }
|
263
|
+
Vanity.playground.track! :yawns_sec
|
264
|
+
boredom = Vanity::Metric.data(Vanity.playground.metric(:yawns_sec), Date.today - 5, Date.today)
|
265
|
+
assert_equal [[today - 5, 0], [today - 4, 4], [today - 3, 0], [today - 2, 2], [today - 1, 0], [today, 1]], boredom
|
266
|
+
end
|
267
|
+
|
268
|
+
test "start date only" do
|
269
|
+
metric "Yawns/sec"
|
270
|
+
Timecop.travel(today - 4) { Vanity.playground.track! :yawns_sec, 4 }
|
271
|
+
Timecop.travel(today - 2) { Vanity.playground.track! :yawns_sec, 2 }
|
272
|
+
Vanity.playground.track! :yawns_sec
|
273
|
+
boredom = Vanity::Metric.data(Vanity.playground.metric(:yawns_sec), Date.today - 4)
|
274
|
+
assert_equal [[today - 4, 4], [today - 3, 0], [today - 2, 2], [today - 1, 0], [today, 1]], boredom
|
275
|
+
end
|
276
|
+
|
277
|
+
test "start date and duration" do
|
278
|
+
metric "Yawns/sec"
|
279
|
+
Timecop.travel(today - 4) { Vanity.playground.track! :yawns_sec, 4 }
|
280
|
+
Timecop.travel(today - 2) { Vanity.playground.track! :yawns_sec, 2 }
|
281
|
+
Vanity.playground.track! :yawns_sec
|
282
|
+
boredom = Vanity::Metric.data(Vanity.playground.metric(:yawns_sec), 5)
|
283
|
+
assert_equal [[today - 4, 4], [today - 3, 0], [today - 2, 2], [today - 1, 0], [today, 1]], boredom
|
284
|
+
end
|
285
|
+
|
286
|
+
test "no data" do
|
287
|
+
metric "Yawns/sec"
|
288
|
+
boredom = Vanity::Metric.data(Vanity.playground.metric(:yawns_sec))
|
289
|
+
assert_equal 90, boredom.size
|
290
|
+
assert_equal [today - 89, 0], boredom.first
|
291
|
+
assert_equal [today, 0], boredom.last
|
292
|
+
end
|
293
|
+
|
294
|
+
test "using custom values method" do
|
295
|
+
File.open "tmp/experiments/metrics/hours_in_day.rb", "w" do |f|
|
296
|
+
f.write <<-RUBY
|
297
|
+
metric "Hours in day" do
|
298
|
+
def values(from, to)
|
299
|
+
(from..to).map { |d| 24 }
|
300
|
+
end
|
301
|
+
end
|
302
|
+
RUBY
|
303
|
+
end
|
304
|
+
data = Vanity::Metric.data(Vanity.playground.metric(:hours_in_day))
|
305
|
+
assert_equal [24] * 90, data.map(&:last)
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
# -- ActiveRecord --
|
312
|
+
|
313
|
+
context "ActiveRecord" do
|
314
|
+
|
315
|
+
test "record count" do
|
316
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
317
|
+
f.write <<-RUBY
|
318
|
+
metric "Sky is limit" do
|
319
|
+
model Sky
|
320
|
+
end
|
321
|
+
RUBY
|
322
|
+
end
|
323
|
+
Vanity.playground.metrics
|
324
|
+
Sky.create!
|
325
|
+
assert_equal 1, Sky.count
|
326
|
+
assert_equal 1, Vanity::Metric.data(metric(:sky_is_limit)).last.last
|
327
|
+
end
|
328
|
+
|
329
|
+
test "record sum" do
|
330
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
331
|
+
f.write <<-RUBY
|
332
|
+
metric "Sky is limit" do
|
333
|
+
model Sky, :sum=>:height
|
334
|
+
end
|
335
|
+
RUBY
|
336
|
+
end
|
337
|
+
Vanity.playground.metrics
|
338
|
+
Sky.create! :height=>4
|
339
|
+
Sky.create! :height=>2
|
340
|
+
assert_equal 6, Vanity::Metric.data(metric(:sky_is_limit)).last.last
|
341
|
+
end
|
342
|
+
|
343
|
+
test "record average" do
|
344
|
+
Sky.aggregates
|
345
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
346
|
+
f.write <<-RUBY
|
347
|
+
metric "Sky is limit" do
|
348
|
+
model Sky, :average=>:height
|
349
|
+
end
|
350
|
+
RUBY
|
351
|
+
end
|
352
|
+
Vanity.playground.metrics
|
353
|
+
Sky.create! :height=>4
|
354
|
+
Sky.create! :height=>2
|
355
|
+
assert_equal 3, Vanity::Metric.data(metric(:sky_is_limit)).last.last
|
356
|
+
end
|
357
|
+
|
358
|
+
test "record minimum" do
|
359
|
+
Sky.aggregates
|
360
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
361
|
+
f.write <<-RUBY
|
362
|
+
metric "Sky is limit" do
|
363
|
+
model Sky, :minimum=>:height
|
364
|
+
end
|
365
|
+
RUBY
|
366
|
+
end
|
367
|
+
Vanity.playground.metrics
|
368
|
+
Sky.create! :height=>4
|
369
|
+
Sky.create! :height=>2
|
370
|
+
assert_equal 2, Vanity::Metric.data(metric(:sky_is_limit)).last.last
|
371
|
+
end
|
372
|
+
|
373
|
+
test "record maximum" do
|
374
|
+
Sky.aggregates
|
375
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
376
|
+
f.write <<-RUBY
|
377
|
+
metric "Sky is limit" do
|
378
|
+
model Sky, :maximum=>:height
|
379
|
+
end
|
380
|
+
RUBY
|
381
|
+
end
|
382
|
+
Vanity.playground.metrics
|
383
|
+
Sky.create! :height=>4
|
384
|
+
Sky.create! :height=>2
|
385
|
+
assert_equal 4, Vanity::Metric.data(metric(:sky_is_limit)).last.last
|
386
|
+
end
|
387
|
+
|
388
|
+
test "with conditions" do
|
389
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
390
|
+
f.write <<-RUBY
|
391
|
+
metric "Sky is limit" do
|
392
|
+
model Sky, :sum=>:height, :conditions=>["height > 4"]
|
393
|
+
end
|
394
|
+
RUBY
|
395
|
+
end
|
396
|
+
Vanity.playground.metrics
|
397
|
+
high_skies = 0
|
398
|
+
metric(:sky_is_limit).hook do |metric_id, timestamp, height|
|
399
|
+
assert height > 4
|
400
|
+
high_skies += height
|
401
|
+
end
|
402
|
+
[nil,5,3,6].each do |height|
|
403
|
+
Sky.create! :height=>height
|
404
|
+
end
|
405
|
+
assert_equal 11, Vanity::Metric.data(metric(:sky_is_limit)).sum(&:last)
|
406
|
+
assert_equal 11, high_skies
|
407
|
+
end
|
408
|
+
|
409
|
+
test "with scope" do
|
410
|
+
Sky.aggregates
|
411
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
412
|
+
f.write <<-RUBY
|
413
|
+
metric "Sky is limit" do
|
414
|
+
model Sky.high
|
415
|
+
end
|
416
|
+
RUBY
|
417
|
+
end
|
418
|
+
Vanity.playground.metrics
|
419
|
+
total = 0
|
420
|
+
metric(:sky_is_limit).hook do |metric_id, timestamp, count|
|
421
|
+
total += count
|
422
|
+
end
|
423
|
+
Sky.create! :height=>4
|
424
|
+
Sky.create! :height=>2
|
425
|
+
assert_equal 1, Vanity::Metric.data(metric(:sky_is_limit)).last.last
|
426
|
+
assert_equal 1, total
|
427
|
+
end
|
428
|
+
|
429
|
+
test "hooks" do
|
430
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
431
|
+
f.write <<-RUBY
|
432
|
+
metric "Sky is limit" do
|
433
|
+
model Sky, :sum=>:height
|
434
|
+
end
|
435
|
+
RUBY
|
436
|
+
end
|
437
|
+
Vanity.playground.metrics
|
438
|
+
total = 0
|
439
|
+
metric(:sky_is_limit).hook do |metric_id, timestamp, count|
|
440
|
+
assert_equal :sky_is_limit, metric_id
|
441
|
+
assert_in_delta Time.now.to_i, timestamp.to_i, 1
|
442
|
+
total += count
|
443
|
+
end
|
444
|
+
Sky.create! :height=>4
|
445
|
+
assert_equal 4, total
|
446
|
+
end
|
447
|
+
|
448
|
+
test "after_create not after_save" do
|
449
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
450
|
+
f.write <<-RUBY
|
451
|
+
metric "Sky is limit" do
|
452
|
+
model Sky
|
453
|
+
end
|
454
|
+
RUBY
|
455
|
+
end
|
456
|
+
Vanity.playground.metrics
|
457
|
+
once = nil
|
458
|
+
metric(:sky_is_limit).hook do
|
459
|
+
fail "Metric tracked twice" if once
|
460
|
+
once = true
|
461
|
+
end
|
462
|
+
Sky.create!
|
463
|
+
Sky.last.update_attributes :height=>4
|
464
|
+
end
|
465
|
+
|
466
|
+
test "with after_save" do
|
467
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
468
|
+
f.write <<-RUBY
|
469
|
+
metric "Sky is limit" do
|
470
|
+
model Sky, :conditions=>["height > 3"]
|
471
|
+
Sky.after_save { |sky| track! if sky.height_changed? && sky.height > 3 }
|
472
|
+
end
|
473
|
+
RUBY
|
474
|
+
end
|
475
|
+
Vanity.playground.metrics
|
476
|
+
times = 0
|
477
|
+
metric(:sky_is_limit).hook do
|
478
|
+
times += 1
|
479
|
+
end
|
480
|
+
Sky.create!
|
481
|
+
(1..5).each do |height|
|
482
|
+
Sky.last.update_attributes! :height=>height
|
483
|
+
end
|
484
|
+
assert_equal 2, times
|
485
|
+
end
|
486
|
+
|
487
|
+
test "do it youself" do
|
488
|
+
File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
|
489
|
+
f.write <<-RUBY
|
490
|
+
metric "Sky is limit" do
|
491
|
+
Sky.after_save { |sky| track! if sky.height_changed? && sky.height > 3 }
|
492
|
+
end
|
493
|
+
RUBY
|
494
|
+
end
|
495
|
+
Vanity.playground.metrics
|
496
|
+
(1..5).each do |height|
|
497
|
+
Sky.create! :height=>height
|
498
|
+
end
|
499
|
+
Sky.first.update_attributes! :height=>4
|
500
|
+
assert_equal 3, Vanity::Metric.data(metric(:sky_is_limit)).last.last
|
501
|
+
end
|
502
|
+
|
503
|
+
end
|
504
|
+
|
505
|
+
|
506
|
+
# -- Helper methods --
|
507
|
+
|
508
|
+
def today
|
509
|
+
@today ||= Date.today
|
510
|
+
end
|
511
|
+
|
512
|
+
teardown do
|
513
|
+
Sky.delete_all
|
514
|
+
Sky.after_create.clear
|
515
|
+
Sky.after_save.clear
|
516
|
+
end
|
517
|
+
|
518
|
+
end
|