moses-vanity 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/.autotest +22 -0
  2. data/.gitignore +7 -0
  3. data/.rvmrc +3 -0
  4. data/.travis.yml +13 -0
  5. data/CHANGELOG +374 -0
  6. data/Gemfile +28 -0
  7. data/MIT-LICENSE +21 -0
  8. data/README.rdoc +108 -0
  9. data/Rakefile +189 -0
  10. data/bin/vanity +16 -0
  11. data/doc/_config.yml +2 -0
  12. data/doc/_layouts/_header.html +34 -0
  13. data/doc/_layouts/page.html +47 -0
  14. data/doc/_metrics.textile +12 -0
  15. data/doc/ab_testing.textile +210 -0
  16. data/doc/configuring.textile +45 -0
  17. data/doc/contributing.textile +93 -0
  18. data/doc/credits.textile +23 -0
  19. data/doc/css/page.css +83 -0
  20. data/doc/css/print.css +43 -0
  21. data/doc/css/syntax.css +7 -0
  22. data/doc/email.textile +129 -0
  23. data/doc/experimental.textile +31 -0
  24. data/doc/faq.textile +8 -0
  25. data/doc/identity.textile +43 -0
  26. data/doc/images/ab_in_dashboard.png +0 -0
  27. data/doc/images/clear_winner.png +0 -0
  28. data/doc/images/price_options.png +0 -0
  29. data/doc/images/sidebar_test.png +0 -0
  30. data/doc/images/signup_metric.png +0 -0
  31. data/doc/images/vanity.png +0 -0
  32. data/doc/index.textile +91 -0
  33. data/doc/metrics.textile +231 -0
  34. data/doc/rails.textile +89 -0
  35. data/doc/site.js +27 -0
  36. data/generators/templates/vanity_migration.rb +53 -0
  37. data/generators/vanity_generator.rb +8 -0
  38. data/lib/generators/templates/vanity_migration.rb +53 -0
  39. data/lib/generators/vanity_generator.rb +15 -0
  40. data/lib/vanity.rb +36 -0
  41. data/lib/vanity/adapters/abstract_adapter.rb +140 -0
  42. data/lib/vanity/adapters/active_record_adapter.rb +248 -0
  43. data/lib/vanity/adapters/mock_adapter.rb +157 -0
  44. data/lib/vanity/adapters/mongodb_adapter.rb +178 -0
  45. data/lib/vanity/adapters/redis_adapter.rb +160 -0
  46. data/lib/vanity/backport.rb +26 -0
  47. data/lib/vanity/commands/list.rb +21 -0
  48. data/lib/vanity/commands/report.rb +64 -0
  49. data/lib/vanity/commands/upgrade.rb +34 -0
  50. data/lib/vanity/experiment/ab_test.rb +507 -0
  51. data/lib/vanity/experiment/base.rb +214 -0
  52. data/lib/vanity/frameworks.rb +16 -0
  53. data/lib/vanity/frameworks/rails.rb +318 -0
  54. data/lib/vanity/helpers.rb +66 -0
  55. data/lib/vanity/images/x.gif +0 -0
  56. data/lib/vanity/metric/active_record.rb +85 -0
  57. data/lib/vanity/metric/base.rb +244 -0
  58. data/lib/vanity/metric/google_analytics.rb +83 -0
  59. data/lib/vanity/metric/remote.rb +53 -0
  60. data/lib/vanity/playground.rb +396 -0
  61. data/lib/vanity/templates/_ab_test.erb +28 -0
  62. data/lib/vanity/templates/_experiment.erb +5 -0
  63. data/lib/vanity/templates/_experiments.erb +7 -0
  64. data/lib/vanity/templates/_metric.erb +14 -0
  65. data/lib/vanity/templates/_metrics.erb +13 -0
  66. data/lib/vanity/templates/_report.erb +27 -0
  67. data/lib/vanity/templates/_vanity.js.erb +20 -0
  68. data/lib/vanity/templates/flot.min.js +1 -0
  69. data/lib/vanity/templates/jquery.min.js +19 -0
  70. data/lib/vanity/templates/vanity.css +26 -0
  71. data/lib/vanity/templates/vanity.js +82 -0
  72. data/lib/vanity/version.rb +11 -0
  73. data/test/adapters/redis_adapter_test.rb +17 -0
  74. data/test/experiment/ab_test.rb +771 -0
  75. data/test/experiment/base_test.rb +150 -0
  76. data/test/experiments/age_and_zipcode.rb +19 -0
  77. data/test/experiments/metrics/cheers.rb +3 -0
  78. data/test/experiments/metrics/signups.rb +2 -0
  79. data/test/experiments/metrics/yawns.rb +3 -0
  80. data/test/experiments/null_abc.rb +5 -0
  81. data/test/metric/active_record_test.rb +277 -0
  82. data/test/metric/base_test.rb +293 -0
  83. data/test/metric/google_analytics_test.rb +104 -0
  84. data/test/metric/remote_test.rb +109 -0
  85. data/test/myapp/app/controllers/application_controller.rb +2 -0
  86. data/test/myapp/app/controllers/main_controller.rb +7 -0
  87. data/test/myapp/config/boot.rb +110 -0
  88. data/test/myapp/config/environment.rb +10 -0
  89. data/test/myapp/config/environments/production.rb +0 -0
  90. data/test/myapp/config/routes.rb +3 -0
  91. data/test/passenger_test.rb +43 -0
  92. data/test/playground_test.rb +26 -0
  93. data/test/rails_dashboard_test.rb +37 -0
  94. data/test/rails_helper_test.rb +36 -0
  95. data/test/rails_test.rb +389 -0
  96. data/test/test_helper.rb +145 -0
  97. data/vanity.gemspec +26 -0
  98. metadata +202 -0
@@ -0,0 +1,150 @@
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
+
34
+ # -- Loading experiments --
35
+
36
+ def test_fails_if_cannot_load_named_experiment
37
+ assert_raises NameError do
38
+ experiment(:ice_cream_flavor)
39
+ end
40
+ end
41
+
42
+ def test_loading_experiment
43
+ File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
44
+ f.write <<-RUBY
45
+ ab_test "Ice Cream Flavor" do
46
+ def xmts
47
+ "x"
48
+ end
49
+ metrics :happiness
50
+ end
51
+ RUBY
52
+ end
53
+ assert_equal "x", experiment(:ice_cream_flavor).xmts
54
+ end
55
+
56
+ def test_fails_if_error_loading_experiment
57
+ File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
58
+ f.write "fail 'yawn!'"
59
+ end
60
+ assert_raises NameError do
61
+ experiment(:ice_cream_flavor)
62
+ end
63
+ end
64
+
65
+ def test_complains_if_not_defined_where_expected
66
+ File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
67
+ f.write ""
68
+ end
69
+ assert_raises NameError do
70
+ experiment(:ice_cream_flavor)
71
+ end
72
+ end
73
+
74
+ def test_reloading_experiments
75
+ new_ab_test(:ab) { metrics :happiness }
76
+ new_ab_test(:cd) { metrics :happiness }
77
+ assert_equal 2, Vanity.playground.experiments.size
78
+ Vanity.playground.reload!
79
+ assert Vanity.playground.experiments.empty?
80
+ end
81
+
82
+
83
+ # -- Attributes --
84
+
85
+ def test_experiment_mapping_name_to_id
86
+ experiment = new_ab_test("Ice Cream Flavor/Tastes") { metrics :happiness }
87
+ assert_equal "Ice Cream Flavor/Tastes", experiment.name
88
+ assert_equal :ice_cream_flavor_tastes, experiment.id
89
+ end
90
+
91
+ def test_saving_experiment_after_definition
92
+ File.open "tmp/experiments/ice_cream_flavor.rb", "w" do |f|
93
+ f.write <<-RUBY
94
+ ab_test "Ice Cream Flavor" do
95
+ metrics :happiness
96
+ expects(:save)
97
+ end
98
+ RUBY
99
+ end
100
+ Vanity.playground.experiment(:ice_cream_flavor)
101
+ end
102
+
103
+ def test_experiment_has_created_timestamp
104
+ new_ab_test(:ice_cream_flavor) { metrics :happiness }
105
+ assert_instance_of Time, experiment(:ice_cream_flavor).created_at
106
+ assert_in_delta experiment(:ice_cream_flavor).created_at.to_i, Time.now.to_i, 1
107
+ end
108
+
109
+ def test_experiment_keeps_created_timestamp_across_definitions
110
+ past = Date.today - 1
111
+ Timecop.freeze past do
112
+ new_ab_test(:ice_cream_flavor) { metrics :happiness }
113
+ assert_equal past.to_time.to_i, experiment(:ice_cream_flavor).created_at.to_i
114
+ end
115
+
116
+ new_playground
117
+ metric :happiness
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
+ def test_experiment_has_description
123
+ new_ab_test :ice_cream_flavor do
124
+ description "Because 31 is not enough ..."
125
+ metrics :happiness
126
+ end
127
+ assert_equal "Because 31 is not enough ...", experiment(:ice_cream_flavor).description
128
+ end
129
+
130
+ def test_experiment_stores_nothing_when_collection_disabled
131
+ not_collecting!
132
+ new_ab_test(:ice_cream_flavor) { metrics :happiness }
133
+ experiment(:ice_cream_flavor).complete!
134
+ end
135
+
136
+ # -- completion -- #
137
+
138
+ # check_completion is called by derived classes, but since it's
139
+ # part of the base_test I'm testing it here.
140
+ def test_error_in_check_completion
141
+ new_ab_test(:ab) { metrics :happiness }
142
+ e = experiment(:ab)
143
+ e.complete_if { true }
144
+ e.stubs(:complete!).raises(RuntimeError, "A forced error")
145
+ e.expects(:warn)
146
+ e.stubs(:identity).returns(:b)
147
+ e.track!(:a, Time.now, 10)
148
+ end
149
+
150
+ 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
@@ -0,0 +1,3 @@
1
+ metric "Cheers" do
2
+ description "They love us, don't they?"
3
+ end
@@ -0,0 +1,2 @@
1
+ metric "Signups" do
2
+ end
@@ -0,0 +1,3 @@
1
+ metric "Yawns" do
2
+ description "How many yawns/sec can our video-sharing micro-blogging social network elicit?"
3
+ end
@@ -0,0 +1,5 @@
1
+ ab_test "Null/ABC" do
2
+ description "Testing three alternatives"
3
+ alternatives nil, :red, :green, :blue
4
+ metrics :signups
5
+ end
@@ -0,0 +1,277 @@
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 "ActiveRecord Metric" do
15
+
16
+ test "record count" do
17
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
18
+ f.write <<-RUBY
19
+ metric "Sky is limit" do
20
+ model Sky
21
+ end
22
+ RUBY
23
+ end
24
+ Vanity.playground.metrics
25
+ Sky.create!
26
+ assert_equal 1, Sky.count
27
+ assert_equal 1, Vanity::Metric.data(metric(:sky_is_limit)).last.last
28
+ end
29
+
30
+ test "record sum" do
31
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
32
+ f.write <<-RUBY
33
+ metric "Sky is limit" do
34
+ model Sky, :sum=>:height
35
+ end
36
+ RUBY
37
+ end
38
+ Vanity.playground.metrics
39
+ Sky.create! :height=>4
40
+ Sky.create! :height=>2
41
+ assert_equal 6, Vanity::Metric.data(metric(:sky_is_limit)).last.last
42
+ end
43
+
44
+ test "record average" do
45
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
46
+ f.write <<-RUBY
47
+ metric "Sky is limit" do
48
+ model Sky, :average=>:height
49
+ end
50
+ RUBY
51
+ end
52
+ Vanity.playground.metrics
53
+ Sky.create! :height=>8
54
+ Sky.create! :height=>2
55
+ assert_equal 5, Vanity::Metric.data(metric(:sky_is_limit)).last.last
56
+ end
57
+
58
+ test "record minimum" do
59
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
60
+ f.write <<-RUBY
61
+ metric "Sky is limit" do
62
+ model Sky, :minimum=>:height
63
+ end
64
+ RUBY
65
+ end
66
+ Vanity.playground.metrics
67
+ Sky.create! :height=>4
68
+ Sky.create! :height=>2
69
+ assert_equal 2, Vanity::Metric.data(metric(:sky_is_limit)).last.last
70
+ end
71
+
72
+ test "record maximum" do
73
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
74
+ f.write <<-RUBY
75
+ metric "Sky is limit" do
76
+ model Sky, :maximum=>:height
77
+ end
78
+ RUBY
79
+ end
80
+ Vanity.playground.metrics
81
+ Sky.create! :height=>4
82
+ Sky.create! :height=>2
83
+ assert_equal 4, Vanity::Metric.data(metric(:sky_is_limit)).last.last
84
+ end
85
+
86
+ test "with conditions" do
87
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
88
+ f.write <<-RUBY
89
+ metric "Sky is limit" do
90
+ model Sky, :sum=>:height, :conditions=>["height > 4"]
91
+ end
92
+ RUBY
93
+ end
94
+ Vanity.playground.metrics
95
+ high_skies = 0
96
+ metric(:sky_is_limit).hook do |metric_id, timestamp, height|
97
+ assert height > 4
98
+ high_skies += height
99
+ end
100
+ [nil,5,3,6].each do |height|
101
+ Sky.create! :height=>height
102
+ end
103
+ assert_equal 11, Vanity::Metric.data(metric(:sky_is_limit)).sum(&:last)
104
+ assert_equal 11, high_skies
105
+ end
106
+
107
+ test "with scope" do
108
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
109
+ f.write <<-RUBY
110
+ metric "Sky is limit" do
111
+ model Sky.high
112
+ end
113
+ RUBY
114
+ end
115
+ Vanity.playground.metrics
116
+ total = 0
117
+ metric(:sky_is_limit).hook do |metric_id, timestamp, count|
118
+ total += count
119
+ end
120
+ Sky.create! :height=>4
121
+ Sky.create! :height=>2
122
+ assert_equal 1, Vanity::Metric.data(metric(:sky_is_limit)).last.last
123
+ assert_equal 1, total
124
+ end
125
+
126
+ test "with timestamp" do
127
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
128
+ f.write <<-RUBY
129
+ metric "Sky is limit" do
130
+ model Sky, :timestamp => :created_at
131
+ end
132
+ RUBY
133
+ end
134
+ Vanity.playground.metrics
135
+ Sky.create!
136
+ assert_equal 1, Sky.count
137
+ assert_equal 1, Vanity::Metric.data(metric(:sky_is_limit)).last.last
138
+ end
139
+
140
+ test "with timestamp and table" do
141
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
142
+ f.write <<-RUBY
143
+ metric "Sky is limit" do
144
+ model Sky, :timestamp => 'skies.created_at'
145
+ end
146
+ RUBY
147
+ end
148
+ Vanity.playground.metrics
149
+ Sky.create!
150
+ assert_equal 1, Sky.count
151
+ assert_equal 1, Vanity::Metric.data(metric(:sky_is_limit)).last.last
152
+ end
153
+
154
+ test "hooks" do
155
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
156
+ f.write <<-RUBY
157
+ metric "Sky is limit" do
158
+ model Sky, :sum=>:height
159
+ end
160
+ RUBY
161
+ end
162
+ Vanity.playground.metrics
163
+ total = 0
164
+ metric(:sky_is_limit).hook do |metric_id, timestamp, count|
165
+ assert_equal :sky_is_limit, metric_id
166
+ assert_in_delta Time.now.to_i, timestamp.to_i, 1
167
+ total += count
168
+ end
169
+ Sky.create! :height=>4
170
+ assert_equal 4, total
171
+ end
172
+
173
+ test "no hooks when metrics disabled" do
174
+ not_collecting!
175
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
176
+ f.write <<-RUBY
177
+ metric "Sky is limit" do
178
+ model Sky, :sum=>:height
179
+ end
180
+ RUBY
181
+ end
182
+ Vanity.playground.metrics
183
+ total = 0
184
+ metric(:sky_is_limit).hook do |metric_id, timestamp, count|
185
+ total += count
186
+ end
187
+ Sky.create! :height=>4
188
+ assert_equal 0, total
189
+ end
190
+
191
+ test "after_create not after_save" do
192
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
193
+ f.write <<-RUBY
194
+ metric "Sky is limit" do
195
+ model Sky
196
+ end
197
+ RUBY
198
+ end
199
+ Vanity.playground.metrics
200
+ once = nil
201
+ metric(:sky_is_limit).hook do
202
+ fail "Metric tracked twice" if once
203
+ once = true
204
+ end
205
+ Sky.create!
206
+ Sky.last.update_attributes :height=>4
207
+ end
208
+
209
+ test "with after_save" do
210
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
211
+ f.write <<-RUBY
212
+ metric "Sky is limit" do
213
+ model Sky, :conditions=>["height > 3"]
214
+ Sky.after_save { |sky| track! if sky.height_changed? && sky.height > 3 }
215
+ end
216
+ RUBY
217
+ end
218
+ Vanity.playground.metrics
219
+ times = 0
220
+ metric(:sky_is_limit).hook do
221
+ times += 1
222
+ end
223
+ Sky.create!
224
+ (1..5).each do |height|
225
+ Sky.last.update_attributes! :height=>height
226
+ end
227
+ assert_equal 2, times
228
+ end
229
+
230
+ test "do it youself" do
231
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
232
+ f.write <<-RUBY
233
+ metric "Sky is limit" do
234
+ Sky.after_save { |sky| track! if sky.height_changed? && sky.height > 3 }
235
+ end
236
+ RUBY
237
+ end
238
+ Vanity.playground.metrics
239
+ (1..5).each do |height|
240
+ Sky.create! :height=>height
241
+ end
242
+ Sky.first.update_attributes! :height=>4
243
+ assert_equal 3, Vanity::Metric.data(metric(:sky_is_limit)).last.last
244
+ end
245
+
246
+ test "last update for new metric" do
247
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
248
+ f.write <<-RUBY
249
+ metric "Sky is limit" do
250
+ model Sky
251
+ end
252
+ RUBY
253
+ end
254
+ assert_nil metric(:sky_is_limit).last_update_at
255
+ end
256
+
257
+ test "last update with records" do
258
+ File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
259
+ f.write <<-RUBY
260
+ metric "Sky is limit" do
261
+ model Sky
262
+ end
263
+ RUBY
264
+ end
265
+ Sky.create! :height=>1
266
+ Timecop.freeze Time.now + 1.day do
267
+ Sky.create! :height=>1
268
+ end
269
+ assert_in_delta metric(:sky_is_limit).last_update_at.to_i, (Time.now + 1.day).to_i, 1
270
+ end
271
+
272
+ teardown do
273
+ Sky.delete_all
274
+ Sky.after_create.clear
275
+ Sky.after_save.clear
276
+ end
277
+ end