vanity 1.8.4 → 1.9.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +3 -2
- data/CHANGELOG +12 -0
- data/Gemfile +6 -3
- data/Gemfile.lock +12 -10
- data/README.rdoc +45 -16
- data/Rakefile +14 -9
- data/doc/_layouts/page.html +4 -6
- data/doc/ab_testing.textile +1 -1
- data/doc/configuring.textile +2 -4
- data/doc/email.textile +1 -3
- data/doc/index.textile +3 -63
- data/doc/rails.textile +34 -8
- data/gemfiles/rails3.gemfile +12 -3
- data/gemfiles/rails3.gemfile.lock +37 -11
- data/gemfiles/rails31.gemfile +12 -3
- data/gemfiles/rails31.gemfile.lock +37 -11
- data/gemfiles/rails32.gemfile +12 -3
- data/gemfiles/rails32.gemfile.lock +37 -11
- data/gemfiles/rails4.gemfile +12 -3
- data/gemfiles/rails4.gemfile.lock +37 -11
- data/lib/vanity/adapters/abstract_adapter.rb +4 -0
- data/lib/vanity/adapters/active_record_adapter.rb +18 -10
- data/lib/vanity/adapters/mock_adapter.rb +8 -4
- data/lib/vanity/adapters/mongodb_adapter.rb +11 -7
- data/lib/vanity/adapters/redis_adapter.rb +88 -37
- data/lib/vanity/commands/report.rb +9 -9
- data/lib/vanity/experiment/ab_test.rb +120 -101
- data/lib/vanity/experiment/alternative.rb +21 -21
- data/lib/vanity/experiment/base.rb +5 -5
- data/lib/vanity/experiment/bayesian_bandit_score.rb +51 -51
- data/lib/vanity/experiment/definition.rb +10 -10
- data/lib/vanity/frameworks/rails.rb +39 -36
- data/lib/vanity/helpers.rb +6 -4
- data/lib/vanity/metric/active_record.rb +1 -1
- data/lib/vanity/metric/base.rb +23 -24
- data/lib/vanity/metric/google_analytics.rb +5 -5
- data/lib/vanity/playground.rb +118 -24
- data/lib/vanity/templates/_report.erb +20 -6
- data/lib/vanity/templates/vanity.css +2 -0
- data/lib/vanity/version.rb +1 -1
- data/test/adapters/redis_adapter_test.rb +106 -1
- data/test/dummy/config/database.yml +21 -4
- data/test/dummy/config/routes.rb +1 -1
- data/test/experiment/ab_test.rb +93 -13
- data/test/metric/active_record_test.rb +9 -4
- data/test/passenger_test.rb +43 -42
- data/test/playground_test.rb +50 -1
- data/test/rails_dashboard_test.rb +38 -1
- data/test/rails_helper_test.rb +5 -0
- data/test/rails_test.rb +66 -15
- data/test/test_helper.rb +24 -2
- data/vanity.gemspec +0 -2
- metadata +45 -57
@@ -5,15 +5,14 @@ GIT
|
|
5
5
|
fastthread (1.0.7)
|
6
6
|
|
7
7
|
PATH
|
8
|
-
remote:
|
8
|
+
remote: ..
|
9
9
|
specs:
|
10
|
-
vanity (1.8.
|
11
|
-
redis (>= 2.1)
|
12
|
-
redis-namespace (>= 1.1.0)
|
10
|
+
vanity (1.8.3)
|
13
11
|
|
14
12
|
GEM
|
15
13
|
remote: https://rubygems.org/
|
16
14
|
specs:
|
15
|
+
RedCloth (4.2.9)
|
17
16
|
SystemTimer (1.2.3)
|
18
17
|
actionmailer (3.2.1)
|
19
18
|
actionpack (= 3.2.1)
|
@@ -43,30 +42,48 @@ GEM
|
|
43
42
|
i18n (~> 0.6)
|
44
43
|
multi_json (~> 1.0)
|
45
44
|
addressable (2.2.7)
|
46
|
-
|
45
|
+
albino (1.3.3)
|
46
|
+
posix-spawn (>= 0.3.6)
|
47
|
+
appraisal (1.0.0.beta2)
|
47
48
|
bundler
|
48
49
|
rake
|
50
|
+
thor (>= 0.14.0)
|
49
51
|
arel (3.0.2)
|
50
52
|
backports (3.3.5)
|
51
53
|
bson (1.6.0)
|
52
54
|
bson_ext (1.6.0)
|
53
55
|
bson (= 1.6.0)
|
54
56
|
builder (3.0.0)
|
57
|
+
classifier (1.3.3)
|
58
|
+
fast-stemmer (>= 1.0.0)
|
55
59
|
crack (0.3.1)
|
56
60
|
daemon_controller (1.0.0)
|
61
|
+
directory_watcher (1.4.1)
|
57
62
|
erubis (2.7.0)
|
63
|
+
fast-stemmer (1.0.0)
|
58
64
|
garb (0.9.1)
|
59
65
|
activesupport (>= 2.2.0)
|
60
66
|
crack (>= 0.1.6)
|
61
67
|
hike (1.2.1)
|
62
68
|
i18n (0.6.0)
|
63
69
|
integration (0.1.0)
|
70
|
+
jekyll (0.11.2)
|
71
|
+
albino (~> 1.3)
|
72
|
+
classifier (~> 1.3)
|
73
|
+
directory_watcher (~> 1.1)
|
74
|
+
kramdown (~> 0.13)
|
75
|
+
liquid (~> 2.3)
|
76
|
+
maruku (~> 0.5)
|
64
77
|
journey (1.0.3)
|
65
78
|
json (1.6.5)
|
79
|
+
kramdown (0.13.5)
|
80
|
+
liquid (2.3.0)
|
66
81
|
mail (2.4.1)
|
67
82
|
i18n (>= 0.4.0)
|
68
83
|
mime-types (~> 1.16)
|
69
84
|
treetop (~> 1.4.8)
|
85
|
+
maruku (0.6.0)
|
86
|
+
syntax (>= 1.0.0)
|
70
87
|
metaclass (0.0.1)
|
71
88
|
mime-types (1.17.2)
|
72
89
|
mocha (0.10.5)
|
@@ -80,8 +97,8 @@ GEM
|
|
80
97
|
fastthread (>= 1.0.1)
|
81
98
|
rack
|
82
99
|
rake (>= 0.8.1)
|
83
|
-
pg (0.13.2)
|
84
100
|
polyglot (0.3.3)
|
101
|
+
posix-spawn (0.3.6)
|
85
102
|
rack (1.4.1)
|
86
103
|
rack-cache (1.1)
|
87
104
|
rack (>= 0.4)
|
@@ -107,9 +124,9 @@ GEM
|
|
107
124
|
rake (0.9.2.2)
|
108
125
|
rdoc (3.12)
|
109
126
|
json (~> 1.4)
|
110
|
-
redis (3.
|
111
|
-
redis-namespace (1.
|
112
|
-
redis (~> 3.0
|
127
|
+
redis (3.0.6)
|
128
|
+
redis-namespace (1.3.2)
|
129
|
+
redis (~> 3.0.4)
|
113
130
|
rubystats (0.2.3)
|
114
131
|
shoulda (3.0.1)
|
115
132
|
shoulda-context (~> 1.0.0)
|
@@ -120,6 +137,8 @@ GEM
|
|
120
137
|
hike (~> 1.2)
|
121
138
|
rack (~> 1.0)
|
122
139
|
tilt (~> 1.1, != 1.3.0)
|
140
|
+
sqlite3 (1.3.8)
|
141
|
+
syntax (1.0.0)
|
123
142
|
thor (0.14.6)
|
124
143
|
tilt (1.3.3)
|
125
144
|
timecop (0.3.5)
|
@@ -130,27 +149,34 @@ GEM
|
|
130
149
|
webmock (1.8.0)
|
131
150
|
addressable (>= 2.2.7)
|
132
151
|
crack (>= 0.1.7)
|
152
|
+
yard (0.7.5)
|
133
153
|
|
134
154
|
PLATFORMS
|
135
155
|
ruby
|
136
156
|
|
137
157
|
DEPENDENCIES
|
158
|
+
RedCloth
|
138
159
|
SystemTimer (= 1.2.3)
|
139
|
-
appraisal
|
160
|
+
appraisal (>= 1.0.0.beta2)
|
140
161
|
backports
|
141
162
|
bson_ext
|
142
163
|
fastthread!
|
143
164
|
garb
|
144
165
|
integration
|
166
|
+
jekyll
|
145
167
|
mocha
|
146
168
|
mongo
|
147
169
|
mysql
|
148
170
|
passenger (~> 3.0)
|
149
|
-
pg
|
150
171
|
rack
|
151
172
|
rails (= 3.2.1)
|
173
|
+
rake
|
174
|
+
redis (>= 2.1)
|
175
|
+
redis-namespace (>= 1.1.0)
|
152
176
|
rubystats
|
153
177
|
shoulda
|
178
|
+
sqlite3
|
154
179
|
timecop
|
155
180
|
vanity!
|
156
181
|
webmock
|
182
|
+
yard
|
data/gemfiles/rails4.gemfile
CHANGED
@@ -3,16 +3,17 @@
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
5
|
gem "rack"
|
6
|
+
gem "redis", ">= 2.1"
|
7
|
+
gem "redis-namespace", ">= 1.1.0"
|
6
8
|
gem "bson_ext"
|
7
9
|
gem "mongo"
|
8
10
|
gem "mysql"
|
9
|
-
gem "
|
11
|
+
gem "sqlite3"
|
10
12
|
gem "backports", :platforms=>:mri_18
|
11
13
|
gem "integration"
|
12
14
|
gem "rubystats"
|
13
15
|
gem "garb"
|
14
16
|
gem "SystemTimer", "1.2.3", :platforms=>:mri_18
|
15
|
-
gem "appraisal"
|
16
17
|
gem "mocha", :require=>false
|
17
18
|
gem "shoulda", :require=>false
|
18
19
|
gem "timecop", :require=>false
|
@@ -21,4 +22,12 @@ gem "rails", "4.0.0"
|
|
21
22
|
gem "fastthread", :git=>"git://github.com/zoltankiss/fastthread.git", :platforms=>:mri_20
|
22
23
|
gem "passenger", "~>3.0"
|
23
24
|
|
24
|
-
|
25
|
+
group :development do
|
26
|
+
gem "appraisal", ">= 1.0.0.beta2"
|
27
|
+
gem "jekyll"
|
28
|
+
gem "rake"
|
29
|
+
gem "RedCloth"
|
30
|
+
gem "yard"
|
31
|
+
end
|
32
|
+
|
33
|
+
gemspec :path=>".././"
|
@@ -5,15 +5,14 @@ GIT
|
|
5
5
|
fastthread (1.0.7)
|
6
6
|
|
7
7
|
PATH
|
8
|
-
remote:
|
8
|
+
remote: ..
|
9
9
|
specs:
|
10
|
-
vanity (1.8.
|
11
|
-
redis (>= 2.1)
|
12
|
-
redis-namespace (>= 1.1.0)
|
10
|
+
vanity (1.8.3)
|
13
11
|
|
14
12
|
GEM
|
15
13
|
remote: https://rubygems.org/
|
16
14
|
specs:
|
15
|
+
RedCloth (4.2.9)
|
17
16
|
SystemTimer (1.2.3)
|
18
17
|
actionmailer (4.0.0)
|
19
18
|
actionpack (= 4.0.0)
|
@@ -40,9 +39,12 @@ GEM
|
|
40
39
|
thread_safe (~> 0.1)
|
41
40
|
tzinfo (~> 0.3.37)
|
42
41
|
addressable (2.3.5)
|
43
|
-
|
42
|
+
albino (1.3.3)
|
43
|
+
posix-spawn (>= 0.3.6)
|
44
|
+
appraisal (1.0.0.beta2)
|
44
45
|
bundler
|
45
46
|
rake
|
47
|
+
thor (>= 0.14.0)
|
46
48
|
arel (4.0.1)
|
47
49
|
atomic (1.1.14)
|
48
50
|
backports (3.3.5)
|
@@ -50,19 +52,34 @@ GEM
|
|
50
52
|
bson_ext (1.9.2)
|
51
53
|
bson (~> 1.9.2)
|
52
54
|
builder (3.1.4)
|
55
|
+
classifier (1.3.3)
|
56
|
+
fast-stemmer (>= 1.0.0)
|
53
57
|
crack (0.4.1)
|
54
58
|
safe_yaml (~> 0.9.0)
|
55
59
|
daemon_controller (1.1.7)
|
60
|
+
directory_watcher (1.4.1)
|
56
61
|
erubis (2.7.0)
|
62
|
+
fast-stemmer (1.0.0)
|
57
63
|
garb (0.9.1)
|
58
64
|
activesupport (>= 2.2.0)
|
59
65
|
crack (>= 0.1.6)
|
60
66
|
hike (1.2.3)
|
61
67
|
i18n (0.6.5)
|
62
68
|
integration (0.1.0)
|
69
|
+
jekyll (0.11.2)
|
70
|
+
albino (~> 1.3)
|
71
|
+
classifier (~> 1.3)
|
72
|
+
directory_watcher (~> 1.1)
|
73
|
+
kramdown (~> 0.13)
|
74
|
+
liquid (~> 2.3)
|
75
|
+
maruku (~> 0.5)
|
76
|
+
kramdown (0.13.5)
|
77
|
+
liquid (2.3.0)
|
63
78
|
mail (2.5.4)
|
64
79
|
mime-types (~> 1.16)
|
65
80
|
treetop (~> 1.4.8)
|
81
|
+
maruku (0.6.0)
|
82
|
+
syntax (>= 1.0.0)
|
66
83
|
metaclass (0.0.1)
|
67
84
|
mime-types (1.25)
|
68
85
|
minitest (4.7.5)
|
@@ -77,8 +94,8 @@ GEM
|
|
77
94
|
fastthread (>= 1.0.1)
|
78
95
|
rack
|
79
96
|
rake (>= 0.8.1)
|
80
|
-
pg (0.17.0)
|
81
97
|
polyglot (0.3.3)
|
98
|
+
posix-spawn (0.3.6)
|
82
99
|
rack (1.5.2)
|
83
100
|
rack-test (0.6.2)
|
84
101
|
rack (>= 1.0)
|
@@ -96,9 +113,9 @@ GEM
|
|
96
113
|
rake (>= 0.8.7)
|
97
114
|
thor (>= 0.18.1, < 2.0)
|
98
115
|
rake (10.1.0)
|
99
|
-
redis (3.
|
100
|
-
redis-namespace (1.
|
101
|
-
redis (~> 3.0
|
116
|
+
redis (3.0.6)
|
117
|
+
redis-namespace (1.3.2)
|
118
|
+
redis (~> 3.0.4)
|
102
119
|
rubystats (0.2.3)
|
103
120
|
safe_yaml (0.9.7)
|
104
121
|
shoulda (3.5.0)
|
@@ -116,6 +133,8 @@ GEM
|
|
116
133
|
actionpack (>= 3.0)
|
117
134
|
activesupport (>= 3.0)
|
118
135
|
sprockets (~> 2.8)
|
136
|
+
sqlite3 (1.3.8)
|
137
|
+
syntax (1.0.0)
|
119
138
|
thor (0.18.1)
|
120
139
|
thread_safe (0.1.3)
|
121
140
|
atomic
|
@@ -128,27 +147,34 @@ GEM
|
|
128
147
|
webmock (1.15.2)
|
129
148
|
addressable (>= 2.2.7)
|
130
149
|
crack (>= 0.3.2)
|
150
|
+
yard (0.7.5)
|
131
151
|
|
132
152
|
PLATFORMS
|
133
153
|
ruby
|
134
154
|
|
135
155
|
DEPENDENCIES
|
156
|
+
RedCloth
|
136
157
|
SystemTimer (= 1.2.3)
|
137
|
-
appraisal
|
158
|
+
appraisal (>= 1.0.0.beta2)
|
138
159
|
backports
|
139
160
|
bson_ext
|
140
161
|
fastthread!
|
141
162
|
garb
|
142
163
|
integration
|
164
|
+
jekyll
|
143
165
|
mocha
|
144
166
|
mongo
|
145
167
|
mysql
|
146
168
|
passenger (~> 3.0)
|
147
|
-
pg
|
148
169
|
rack
|
149
170
|
rails (= 4.0.0)
|
171
|
+
rake
|
172
|
+
redis (>= 2.1)
|
173
|
+
redis-namespace (>= 1.1.0)
|
150
174
|
rubystats
|
151
175
|
shoulda
|
176
|
+
sqlite3
|
152
177
|
timecop
|
153
178
|
vanity!
|
154
179
|
webmock
|
180
|
+
yard
|
@@ -66,6 +66,10 @@ module Vanity
|
|
66
66
|
|
67
67
|
# -- Experiments --
|
68
68
|
|
69
|
+
def experiment_persisted?(experiment)
|
70
|
+
fail "Not implemented"
|
71
|
+
end
|
72
|
+
|
69
73
|
# Store when experiment was created (do not write over existing value).
|
70
74
|
def set_experiment_created_at(experiment, time)
|
71
75
|
fail "Not implemented"
|
@@ -27,8 +27,7 @@ module Vanity
|
|
27
27
|
# Metric model
|
28
28
|
class VanityMetric < VanityRecord
|
29
29
|
self.table_name = :vanity_metrics
|
30
|
-
has_many :vanity_metric_values
|
31
|
-
:class_name => 'Vanity::Adapters::ActiveRecordAdapter::VanityMetricValue'
|
30
|
+
has_many :vanity_metric_values
|
32
31
|
|
33
32
|
def self.retrieve(metric)
|
34
33
|
find_or_create_by_metric_id(metric.to_s)
|
@@ -40,15 +39,13 @@ module Vanity
|
|
40
39
|
attr_accessible :date, :index, :value if needs_attr_accessible?
|
41
40
|
|
42
41
|
self.table_name = :vanity_metric_values
|
43
|
-
belongs_to :vanity_metric
|
44
|
-
:class_name => 'Vanity::Adapters::ActiveRecordAdapter::VanityMetric'
|
42
|
+
belongs_to :vanity_metric
|
45
43
|
end
|
46
44
|
|
47
45
|
# Experiment model
|
48
46
|
class VanityExperiment < VanityRecord
|
49
47
|
self.table_name = :vanity_experiments
|
50
|
-
has_many :vanity_conversions, :dependent => :destroy
|
51
|
-
:class_name => 'Vanity::Adapters::ActiveRecordAdapter::VanityConversion'
|
48
|
+
has_many :vanity_conversions, :dependent => :destroy
|
52
49
|
attr_accessible :experiment_id if needs_attr_accessible?
|
53
50
|
|
54
51
|
# Finds or creates the experiment
|
@@ -65,8 +62,7 @@ module Vanity
|
|
65
62
|
# Conversion model
|
66
63
|
class VanityConversion < VanityRecord
|
67
64
|
self.table_name = :vanity_conversions
|
68
|
-
belongs_to :vanity_experiment
|
69
|
-
:class_name => 'Vanity::Adapters::ActiveRecordAdapter::VanityExperiment'
|
65
|
+
belongs_to :vanity_experiment
|
70
66
|
end
|
71
67
|
|
72
68
|
# Participant model
|
@@ -90,6 +86,8 @@ module Vanity
|
|
90
86
|
end
|
91
87
|
|
92
88
|
def initialize(options)
|
89
|
+
|
90
|
+
|
93
91
|
@options = options.inject({}) { |h,kv| h[kv.first.to_s] = kv.last ; h }
|
94
92
|
if @options["active_record_adapter"] && (@options["active_record_adapter"] != "default")
|
95
93
|
@options["adapter"] = @options["active_record_adapter"]
|
@@ -115,6 +113,9 @@ module Vanity
|
|
115
113
|
end
|
116
114
|
end
|
117
115
|
|
116
|
+
|
117
|
+
# -- Metrics --
|
118
|
+
|
118
119
|
def get_metric_last_update_at(metric)
|
119
120
|
record = VanityMetric.find_by_metric_id(metric.to_s)
|
120
121
|
record && record.updated_at
|
@@ -158,6 +159,13 @@ module Vanity
|
|
158
159
|
record && record.destroy
|
159
160
|
end
|
160
161
|
|
162
|
+
|
163
|
+
# -- Experiments --
|
164
|
+
|
165
|
+
def experiment_persisted?(experiment)
|
166
|
+
VanityExperiment.find_by_experiment_id(experiment.to_s).present?
|
167
|
+
end
|
168
|
+
|
161
169
|
# Store when experiment was created (do not write over existing value).
|
162
170
|
def set_experiment_created_at(experiment, time)
|
163
171
|
record = VanityExperiment.find_by_experiment_id(experiment.to_s) ||
|
@@ -226,8 +234,8 @@ module Vanity
|
|
226
234
|
|
227
235
|
# Determines if a participant already has seen this alternative in this experiment.
|
228
236
|
def ab_seen(experiment, identity, alternative)
|
229
|
-
|
230
|
-
|
237
|
+
participant = VanityParticipant.retrieve(experiment, identity, false)
|
238
|
+
participant && participant.seen == alternative.id
|
231
239
|
end
|
232
240
|
|
233
241
|
# Returns the participant's seen alternative in this experiment, if it exists
|
@@ -44,7 +44,7 @@ module Vanity
|
|
44
44
|
|
45
45
|
|
46
46
|
# -- Metrics --
|
47
|
-
|
47
|
+
|
48
48
|
def get_metric_last_update_at(metric)
|
49
49
|
@metrics[metric] && @metrics[metric][:last_update_at]
|
50
50
|
end
|
@@ -66,10 +66,14 @@ module Vanity
|
|
66
66
|
def destroy_metric(metric)
|
67
67
|
@metrics.delete metric
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
|
71
71
|
# -- Experiments --
|
72
|
-
|
72
|
+
|
73
|
+
def experiment_persisted?(experiment)
|
74
|
+
!!@experiments[experiment]
|
75
|
+
end
|
76
|
+
|
73
77
|
def set_experiment_created_at(experiment, time)
|
74
78
|
@experiments[experiment] ||= {}
|
75
79
|
@experiments[experiment][:created_at] ||= time
|
@@ -133,7 +137,7 @@ module Vanity
|
|
133
137
|
if implicit
|
134
138
|
alt[:participants] << identity
|
135
139
|
else
|
136
|
-
|
140
|
+
participating = alt[:participants].include?(identity)
|
137
141
|
end
|
138
142
|
alt[:converted] << identity if implicit || participating
|
139
143
|
alt[:conversions] += count
|
@@ -73,10 +73,10 @@ module Vanity
|
|
73
73
|
@experiments.drop
|
74
74
|
@participants.drop
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
|
78
78
|
# -- Metrics --
|
79
|
-
|
79
|
+
|
80
80
|
def get_metric_last_update_at(metric)
|
81
81
|
record = @metrics.find_one(:_id=>metric)
|
82
82
|
record && record["last_update_at"]
|
@@ -99,12 +99,16 @@ module Vanity
|
|
99
99
|
def destroy_metric(metric)
|
100
100
|
@metrics.remove :_id=>metric
|
101
101
|
end
|
102
|
-
|
102
|
+
|
103
103
|
|
104
104
|
# -- Experiments --
|
105
|
-
|
105
|
+
|
106
|
+
def experiment_persisted?(experiment)
|
107
|
+
!!@experiments.find_one({ :_id=>experiment })
|
108
|
+
end
|
109
|
+
|
106
110
|
def set_experiment_created_at(experiment, time)
|
107
|
-
@experiments.insert
|
111
|
+
@experiments.insert(:_id=>experiment, :created_at=>time)
|
108
112
|
end
|
109
113
|
|
110
114
|
def get_experiment_created_at(experiment)
|
@@ -152,8 +156,8 @@ module Vanity
|
|
152
156
|
|
153
157
|
# Determines if a participant already has seen this alternative in this experiment.
|
154
158
|
def ab_seen(experiment, identity, alternative)
|
155
|
-
|
156
|
-
|
159
|
+
participant = @participants.find_one({ :experiment=>experiment, :identity=>identity }, { :fields=>[:seen] })
|
160
|
+
participant && participant["seen"].first == alternative.id
|
157
161
|
end
|
158
162
|
|
159
163
|
# Returns the participant's seen alternative in this experiment, if it exists
|