split 0.6.5 → 0.6.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/CHANGELOG.mdown +16 -0
- data/README.mdown +9 -0
- data/lib/split/algorithms/whiplash.rb +1 -1
- data/lib/split/configuration.rb +41 -17
- data/lib/split/dashboard/views/_experiment.erb +10 -4
- data/lib/split/dashboard.rb +9 -2
- data/lib/split/encapsulated_helper.rb +58 -0
- data/lib/split/engine.rb +5 -3
- data/lib/split/experiment.rb +10 -1
- data/lib/split/extensions/string.rb +1 -1
- data/lib/split/helper.rb +6 -3
- data/lib/split/version.rb +1 -1
- data/lib/split.rb +1 -0
- data/spec/algorithms/weighted_sample_spec.rb +1 -1
- data/spec/algorithms/whiplash_spec.rb +2 -2
- data/spec/configuration_spec.rb +3 -3
- data/spec/dashboard_spec.rb +25 -26
- data/spec/experiment_spec.rb +8 -1
- data/spec/helper_spec.rb +24 -7
- metadata +3 -2
data/.travis.yml
CHANGED
data/CHANGELOG.mdown
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
## 0.6.6 (October 15th, 2013)
|
2
|
+
|
3
|
+
Features:
|
4
|
+
|
5
|
+
- Sort experiments on Dashboard so "active" ones without a winner appear first (@swrobel, #204)
|
6
|
+
- Starting tests manually (@duksis, #209)
|
7
|
+
|
8
|
+
Bugfixes:
|
9
|
+
|
10
|
+
- Only trigger completion callback with valid Trial (@segfaultAX, #208)
|
11
|
+
- Fixed bug with `resettable` when using `normalize_experiments` (@jonashuckestein, #213)
|
12
|
+
|
13
|
+
Misc:
|
14
|
+
|
15
|
+
- Added more bots to filter list (@lbeder, #214, #215, #216)
|
16
|
+
|
1
17
|
## 0.6.5 (August 23, 2013)
|
2
18
|
|
3
19
|
Features:
|
data/README.mdown
CHANGED
@@ -165,6 +165,13 @@ If you have an experiment called `button_color` with alternatives called `red` a
|
|
165
165
|
|
166
166
|
will always have red buttons. This won't be stored in your session or count towards to results, unless you set the `store_override` configuration option.
|
167
167
|
|
168
|
+
### Starting experiments manually
|
169
|
+
|
170
|
+
By default new AB tests will be active right after deployment. In case you would like to start new test a while after
|
171
|
+
the deploy, you can do it by setting the `start_manually` configuration option to `true`.
|
172
|
+
|
173
|
+
After choosing this option tests won't be started right after deploy, but after pressing the `Start` button in Split admin dashboard.
|
174
|
+
|
168
175
|
### Reset after completion
|
169
176
|
|
170
177
|
When a user completes a test their session is reset so that they may start the test again in the future.
|
@@ -336,6 +343,8 @@ Split.configure do |config|
|
|
336
343
|
config.allow_multiple_experiments = true
|
337
344
|
config.enabled = true
|
338
345
|
config.persistence = Split::Persistence::SessionAdapter
|
346
|
+
#config.start_manually = false ## new test will have to be started manually from the admin panel. default false
|
347
|
+
config.include_rails_helper = true
|
339
348
|
end
|
340
349
|
```
|
341
350
|
|
data/lib/split/configuration.rb
CHANGED
@@ -12,59 +12,78 @@ module Split
|
|
12
12
|
attr_accessor :persistence
|
13
13
|
attr_accessor :algorithm
|
14
14
|
attr_accessor :store_override
|
15
|
+
attr_accessor :start_manually
|
15
16
|
attr_accessor :on_trial_choose
|
16
17
|
attr_accessor :on_trial_complete
|
17
18
|
attr_accessor :on_experiment_reset
|
18
19
|
attr_accessor :on_experiment_delete
|
20
|
+
attr_accessor :include_rails_helper
|
19
21
|
|
20
22
|
attr_reader :experiments
|
21
23
|
|
22
24
|
def bots
|
23
25
|
@bots ||= {
|
24
26
|
# Indexers
|
25
|
-
|
27
|
+
'AdsBot-Google' => 'Google Adwords',
|
26
28
|
'Baidu' => 'Chinese search engine',
|
29
|
+
'Baiduspider' => 'Chinese search engine',
|
30
|
+
'bingbot' => 'Microsoft bing bot',
|
31
|
+
'Butterfly' => 'Topsy Labs',
|
27
32
|
'Gigabot' => 'Gigabot spider',
|
28
33
|
'Googlebot' => 'Google spider',
|
34
|
+
'MJ12bot' => 'Majestic-12 spider',
|
29
35
|
'msnbot' => 'Microsoft bot',
|
30
|
-
'bingbot' => 'Microsoft bing bot',
|
31
36
|
'rogerbot' => 'SeoMoz spider',
|
37
|
+
'PaperLiBot' => 'PaperLi is another content curation service',
|
32
38
|
'Slurp' => 'Yahoo spider',
|
33
39
|
'Sogou' => 'Chinese search engine',
|
34
|
-
|
40
|
+
'spider' => 'generic web spider',
|
41
|
+
'UnwindFetchor' => 'Gnip crawler',
|
35
42
|
'WordPress' => 'WordPress spider',
|
36
|
-
'ZIBB' => 'ZIBB spider',
|
37
43
|
'YandexBot' => 'Yandex spider',
|
44
|
+
'ZIBB' => 'ZIBB spider',
|
45
|
+
|
38
46
|
# HTTP libraries
|
39
47
|
'Apache-HttpClient' => 'Java http library',
|
40
48
|
'AppEngine-Google' => 'Google App Engine',
|
41
|
-
|
49
|
+
'curl' => 'curl unix CLI http client',
|
42
50
|
'ColdFusion' => 'ColdFusion http library',
|
43
|
-
|
44
|
-
|
51
|
+
'EventMachine HttpClient' => 'Ruby http library',
|
52
|
+
'Go http package' => 'Go http library',
|
45
53
|
'Java' => 'Generic Java http library',
|
46
54
|
'libwww-perl' => 'Perl client-server library loved by script kids',
|
47
55
|
'lwp-trivial' => 'Another Perl library loved by script kids',
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
56
|
+
'Python-urllib' => 'Python http library',
|
57
|
+
'PycURL' => 'Python http library',
|
58
|
+
'Test Certificate Info' => 'C http library?',
|
59
|
+
'Wget' => 'wget unix CLI http client',
|
60
|
+
|
52
61
|
# URL expanders / previewers
|
53
62
|
'awe.sm' => 'Awe.sm URL expander',
|
54
|
-
|
55
|
-
|
63
|
+
'bitlybot' => 'bit.ly bot',
|
64
|
+
'bot@linkfluence.net' => 'Linkfluence bot',
|
65
|
+
'facebookexternalhit' => 'facebook bot',
|
66
|
+
'Feedfetcher-Google' => 'Google Feedfetcher',
|
67
|
+
'https://developers.google.com/+/web/snippet' => 'Google+ Snippet Fetcher',
|
56
68
|
'LongURL' => 'URL expander service',
|
69
|
+
'NING' => 'NING - Yet Another Twitter Swarmer',
|
70
|
+
'redditbot' => 'Reddit Bot',
|
71
|
+
'ShortLinkTranslate' => 'Link shortener',
|
72
|
+
'TweetmemeBot' => 'TweetMeMe Crawler',
|
57
73
|
'Twitterbot' => 'Twitter URL expander',
|
58
74
|
'UnwindFetch' => 'Gnip URL expander',
|
75
|
+
'vkShare' => 'VKontake Sharer',
|
76
|
+
|
59
77
|
# Uptime monitoring
|
60
78
|
'check_http' => 'Nagios monitor',
|
61
79
|
'NewRelicPinger' => 'NewRelic monitor',
|
62
80
|
'Panopta' => 'Monitoring service',
|
63
|
-
|
81
|
+
'Pingdom' => 'Pingdom monitoring',
|
64
82
|
'SiteUptime' => 'Site monitoring services',
|
83
|
+
|
65
84
|
# ???
|
66
|
-
|
67
|
-
|
85
|
+
'DigitalPersona Fingerprint Software' => 'HP Fingerprint scanner',
|
86
|
+
'ShowyouBot' => 'Showyou iOS app spider',
|
68
87
|
'ZyBorg' => 'Zyborg? Hmmm....',
|
69
88
|
}
|
70
89
|
end
|
@@ -117,6 +136,10 @@ module Split
|
|
117
136
|
if goals = value_for(settings, :goals)
|
118
137
|
experiment_config[experiment_name.to_sym][:goals] = goals
|
119
138
|
end
|
139
|
+
|
140
|
+
if (resettable = value_for(settings, :resettable)) != nil
|
141
|
+
experiment_config[experiment_name.to_sym][:resettable] = resettable
|
142
|
+
end
|
120
143
|
end
|
121
144
|
|
122
145
|
experiment_config
|
@@ -171,13 +194,14 @@ module Split
|
|
171
194
|
@experiments = {}
|
172
195
|
@persistence = Split::Persistence::SessionAdapter
|
173
196
|
@algorithm = Split::Algorithms::WeightedSample
|
197
|
+
@include_rails_helper = true
|
174
198
|
end
|
175
199
|
|
176
200
|
private
|
177
201
|
|
178
202
|
def value_for(hash, key)
|
179
203
|
if hash.kind_of?(Hash)
|
180
|
-
hash[key.to_s]
|
204
|
+
hash.has_key?(key.to_s) ? hash[key.to_s] : hash[key.to_sym]
|
181
205
|
end
|
182
206
|
end
|
183
207
|
|
@@ -14,9 +14,15 @@
|
|
14
14
|
<% if goal.nil? %>
|
15
15
|
<div class='inline-controls'>
|
16
16
|
<small><%= experiment.start_time ? experiment.start_time.strftime('%Y-%m-%d') : 'Unknown' %></small>
|
17
|
-
|
18
|
-
<
|
19
|
-
|
17
|
+
<% if experiment.start_time %>
|
18
|
+
<form action="<%= url "/reset/#{experiment.name}" %>" method='post' onclick="return confirmReset()">
|
19
|
+
<input type="submit" value="Reset Data">
|
20
|
+
</form>
|
21
|
+
<% else%>
|
22
|
+
<form action="<%= url "/start/#{experiment.name}" %>" method='post'>
|
23
|
+
<input type="submit" value="Start">
|
24
|
+
</form>
|
25
|
+
<% end %>
|
20
26
|
<form action="<%= url "/#{experiment.name}" %>" method='post' onclick="return confirmDelete()">
|
21
27
|
<input type="hidden" name="_method" value="delete"/>
|
22
28
|
<input type="submit" value="Delete" class="red">
|
@@ -95,4 +101,4 @@
|
|
95
101
|
<td>N/A</td>
|
96
102
|
</tr>
|
97
103
|
</table>
|
98
|
-
</div>
|
104
|
+
</div>
|
data/lib/split/dashboard.rb
CHANGED
@@ -15,7 +15,8 @@ module Split
|
|
15
15
|
helpers Split::DashboardHelpers
|
16
16
|
|
17
17
|
get '/' do
|
18
|
-
|
18
|
+
# Display experiments without a winner at the top of the dashboard
|
19
|
+
@experiments = Split::Experiment.all_active_first
|
19
20
|
# Display Rails Environment mode (or Rack version if not using Rails)
|
20
21
|
if Object.const_defined?('Rails')
|
21
22
|
@current_env = Rails.env.titlecase
|
@@ -32,6 +33,12 @@ module Split
|
|
32
33
|
redirect url('/')
|
33
34
|
end
|
34
35
|
|
36
|
+
post '/start/:experiment' do
|
37
|
+
@experiment = Split::Experiment.find(params[:experiment])
|
38
|
+
@experiment.start
|
39
|
+
redirect url('/')
|
40
|
+
end
|
41
|
+
|
35
42
|
post '/reset/:experiment' do
|
36
43
|
@experiment = Split::Experiment.find(params[:experiment])
|
37
44
|
@experiment.reset
|
@@ -44,4 +51,4 @@ module Split
|
|
44
51
|
redirect url('/')
|
45
52
|
end
|
46
53
|
end
|
47
|
-
end
|
54
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Split's helper exposes all kinds of methods we don't want to
|
2
|
+
# mix into our model classes.
|
3
|
+
#
|
4
|
+
# This module exposes only two methods
|
5
|
+
# - ab_test and
|
6
|
+
# - ab_test_finished
|
7
|
+
# that can safely be mixed into any class.
|
8
|
+
#
|
9
|
+
# Passes the instance of the class that it's mixed into to the
|
10
|
+
# Split persistence adapter as context.
|
11
|
+
#
|
12
|
+
module Split
|
13
|
+
module EncapsulatedHelper
|
14
|
+
|
15
|
+
class ContextShim
|
16
|
+
include Split::Helper
|
17
|
+
def initialize(context, original_params)
|
18
|
+
@context = context
|
19
|
+
@_params = original_params
|
20
|
+
end
|
21
|
+
def ab_user
|
22
|
+
@ab_user ||= Split::Persistence.adapter.new(@context)
|
23
|
+
end
|
24
|
+
def params
|
25
|
+
@_params
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def ab_test(*arguments)
|
30
|
+
ret = split_context_shim.ab_test(*arguments)
|
31
|
+
# TODO there must be a better way to pass a block straight
|
32
|
+
# through to the original ab_test
|
33
|
+
if block_given?
|
34
|
+
if defined?(capture) # a block in a rails view
|
35
|
+
block = Proc.new { yield(ret) }
|
36
|
+
concat(capture(ret, &block))
|
37
|
+
false
|
38
|
+
else
|
39
|
+
yield(ret)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
ret
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def ab_test_finished(*arguments)
|
47
|
+
split_context_shim.finished *arguments
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# instantiate and memoize a context shim in case of multiple ab_test* calls
|
53
|
+
def split_context_shim
|
54
|
+
_params = defined?(params) ? params : {}
|
55
|
+
@split_context_shim ||= ContextShim.new(self, _params)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/split/engine.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
module Split
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
initializer "split" do |app|
|
4
|
-
|
5
|
-
|
4
|
+
if Split.configuration.include_rails_helper
|
5
|
+
ActionController::Base.send :include, Split::Helper
|
6
|
+
ActionController::Base.helper Split::Helper
|
7
|
+
end
|
6
8
|
end
|
7
9
|
end
|
8
|
-
end
|
10
|
+
end
|
data/lib/split/experiment.rb
CHANGED
@@ -41,6 +41,11 @@ module Split
|
|
41
41
|
Split.redis.smembers(:experiments).map {|e| find(e)}
|
42
42
|
end
|
43
43
|
|
44
|
+
# Return experiments without a winner (considered "active") first
|
45
|
+
def self.all_active_first
|
46
|
+
all.sort_by{|e| e.winner ? 1 : 0} # sort_by hack since true/false isn't sortable
|
47
|
+
end
|
48
|
+
|
44
49
|
def self.find(name)
|
45
50
|
if Split.redis.exists(name)
|
46
51
|
obj = self.new name
|
@@ -65,7 +70,7 @@ module Split
|
|
65
70
|
|
66
71
|
if new_record?
|
67
72
|
Split.redis.sadd(:experiments, name)
|
68
|
-
|
73
|
+
start unless Split.configuration.start_manually
|
69
74
|
@alternatives.reverse.each {|a| Split.redis.lpush(name, a.name)}
|
70
75
|
@goals.reverse.each {|a| Split.redis.lpush(goals_key, a)} unless @goals.nil?
|
71
76
|
else
|
@@ -155,6 +160,10 @@ module Split
|
|
155
160
|
Split.redis.hdel(:experiment_winner, name)
|
156
161
|
end
|
157
162
|
|
163
|
+
def start
|
164
|
+
Split.redis.hset(:experiment_start_times, @name, Time.now.to_i)
|
165
|
+
end
|
166
|
+
|
158
167
|
def start_time
|
159
168
|
t = Split.redis.hget(:experiment_start_times, @name)
|
160
169
|
if t
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class String
|
2
|
-
# Constatntize is often provided by ActiveSupport, but ActiveSupport is not a dependency of Split.
|
2
|
+
# Constatntize is often provided by ActiveSupport, but ActiveSupport is not a dependency of Split.
|
3
3
|
unless method_defined?(:constantize)
|
4
4
|
def constantize
|
5
5
|
names = self.split('::')
|
data/lib/split/helper.rb
CHANGED
@@ -62,8 +62,7 @@ module Split
|
|
62
62
|
else
|
63
63
|
alternative_name = ab_user[experiment.key]
|
64
64
|
trial = Trial.new(:experiment => experiment, :alternative => alternative_name, :goals => options[:goals])
|
65
|
-
trial.complete!
|
66
|
-
call_trial_complete_hook(trial)
|
65
|
+
call_trial_complete_hook(trial) if trial.complete!
|
67
66
|
|
68
67
|
if should_reset
|
69
68
|
reset!(experiment)
|
@@ -173,7 +172,7 @@ module Split
|
|
173
172
|
ret = experiment.winner.name
|
174
173
|
else
|
175
174
|
clean_old_versions(experiment)
|
176
|
-
if exclude_visitor? || not_allowed_to_test?(experiment.key)
|
175
|
+
if exclude_visitor? || not_allowed_to_test?(experiment.key) || not_started?(experiment)
|
177
176
|
ret = experiment.control.name
|
178
177
|
else
|
179
178
|
if ab_user[experiment.key]
|
@@ -189,6 +188,10 @@ module Split
|
|
189
188
|
ret
|
190
189
|
end
|
191
190
|
|
191
|
+
def not_started?(experiment)
|
192
|
+
experiment.start_time.nil?
|
193
|
+
end
|
194
|
+
|
192
195
|
def call_trial_choose_hook(trial)
|
193
196
|
send(Split.configuration.on_trial_choose, trial) if Split.configuration.on_trial_choose
|
194
197
|
end
|
data/lib/split/version.rb
CHANGED
data/lib/split.rb
CHANGED
@@ -10,7 +10,7 @@ describe Split::Algorithms::WeightedSample do
|
|
10
10
|
experiment = Split::Experiment.find_or_create('link_color', {'blue' => 100}, {'red' => 0 })
|
11
11
|
Split::Algorithms::WeightedSample.choose_alternative(experiment).name.should == 'blue'
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
it "should return one of the results" do
|
15
15
|
experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
|
16
16
|
['red', 'blue'].should include Split::Algorithms::WeightedSample.choose_alternative(experiment).name
|
@@ -11,7 +11,7 @@ describe Split::Algorithms::Whiplash do
|
|
11
11
|
experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
|
12
12
|
['red', 'blue'].should include Split::Algorithms::Whiplash.choose_alternative(experiment).name
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
it "should guess floats" do
|
16
16
|
Split::Algorithms::Whiplash.send(:arm_guess, 0, 0).class.should == Float
|
17
17
|
Split::Algorithms::Whiplash.send(:arm_guess, 1, 0).class.should == Float
|
@@ -19,5 +19,5 @@ describe Split::Algorithms::Whiplash do
|
|
19
19
|
Split::Algorithms::Whiplash.send(:arm_guess, 1000, 5).class.should == Float
|
20
20
|
Split::Algorithms::Whiplash.send(:arm_guess, 10, -2).class.should == Float
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -86,7 +86,7 @@ describe Split::Configuration do
|
|
86
86
|
end
|
87
87
|
|
88
88
|
it 'should normalize experiments' do
|
89
|
-
@config.normalized_experiments.should == {:my_experiment=>{:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
|
89
|
+
@config.normalized_experiments.should == {:my_experiment=>{:resettable=>false,:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -112,7 +112,7 @@ describe Split::Configuration do
|
|
112
112
|
end
|
113
113
|
|
114
114
|
it "should normalize experiments" do
|
115
|
-
@config.normalized_experiments.should == {:my_experiment=>{:alternatives=>[{"Control Opt"=>0.67},
|
115
|
+
@config.normalized_experiments.should == {:my_experiment=>{:resettable=>false,:alternatives=>[{"Control Opt"=>0.67},
|
116
116
|
[{"Alt One"=>0.1}, {"Alt Two"=>0.23}]]}, :another_experiment=>{:alternatives=>["a", ["b"]]}}
|
117
117
|
end
|
118
118
|
|
@@ -139,7 +139,7 @@ describe Split::Configuration do
|
|
139
139
|
end
|
140
140
|
|
141
141
|
it "should normalize experiments" do
|
142
|
-
@config.normalized_experiments.should == {:my_experiment=>{:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
|
142
|
+
@config.normalized_experiments.should == {:my_experiment=>{:resettable=>false,:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
|
143
143
|
end
|
144
144
|
end
|
145
145
|
|
data/spec/dashboard_spec.rb
CHANGED
@@ -5,39 +5,43 @@ require 'split/dashboard'
|
|
5
5
|
describe Split::Dashboard do
|
6
6
|
include Rack::Test::Methods
|
7
7
|
|
8
|
-
|
9
|
-
Split::
|
10
|
-
|
8
|
+
def app
|
9
|
+
@app ||= Split::Dashboard
|
10
|
+
end
|
11
11
|
|
12
12
|
def link(color)
|
13
|
-
Split::Alternative.new(color,
|
13
|
+
Split::Alternative.new(color, experiment.name)
|
14
14
|
end
|
15
15
|
|
16
|
-
let(:
|
17
|
-
|
16
|
+
let(:experiment) {
|
17
|
+
Split::Experiment.find_or_create('link_color', 'blue', 'red')
|
18
18
|
}
|
19
19
|
|
20
|
-
let(:
|
21
|
-
|
22
|
-
}
|
23
|
-
|
24
|
-
def app
|
25
|
-
@app ||= Split::Dashboard
|
26
|
-
end
|
20
|
+
let(:red_link) { link("red") }
|
21
|
+
let(:blue_link) { link("blue") }
|
27
22
|
|
28
23
|
it "should respond to /" do
|
29
24
|
get '/'
|
30
25
|
last_response.should be_ok
|
31
26
|
end
|
32
27
|
|
33
|
-
it "should
|
34
|
-
|
28
|
+
it "should start experiment" do
|
29
|
+
Split.configuration.start_manually = true
|
30
|
+
experiment
|
31
|
+
get '/'
|
32
|
+
last_response.body.should include('Start')
|
35
33
|
|
34
|
+
post "/start/#{experiment.name}"
|
35
|
+
get '/'
|
36
|
+
last_response.body.should include('Reset Data')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should reset an experiment" do
|
36
40
|
red_link.participant_count = 5
|
37
41
|
blue_link.participant_count = 7
|
38
42
|
experiment.winner = 'blue'
|
39
43
|
|
40
|
-
post
|
44
|
+
post "/reset/#{experiment.name}"
|
41
45
|
|
42
46
|
last_response.should be_redirect
|
43
47
|
|
@@ -50,17 +54,14 @@ describe Split::Dashboard do
|
|
50
54
|
end
|
51
55
|
|
52
56
|
it "should delete an experiment" do
|
53
|
-
experiment
|
54
|
-
delete '/link_color'
|
57
|
+
delete "/#{experiment.name}"
|
55
58
|
last_response.should be_redirect
|
56
|
-
Split::Experiment.find(
|
59
|
+
Split::Experiment.find(experiment.name).should be_nil
|
57
60
|
end
|
58
61
|
|
59
62
|
it "should mark an alternative as the winner" do
|
60
|
-
experiment = link_color
|
61
63
|
experiment.winner.should be_nil
|
62
|
-
|
63
|
-
post '/link_color', :alternative => 'red'
|
64
|
+
post "/#{experiment.name}", :alternative => 'red'
|
64
65
|
|
65
66
|
last_response.should be_redirect
|
66
67
|
experiment.winner.name.should eql('red')
|
@@ -69,7 +70,7 @@ describe Split::Dashboard do
|
|
69
70
|
it "should display the start date" do
|
70
71
|
experiment_start_time = Time.parse('2011-07-07')
|
71
72
|
Time.stub(:now => experiment_start_time)
|
72
|
-
experiment
|
73
|
+
experiment
|
73
74
|
|
74
75
|
get '/'
|
75
76
|
|
@@ -79,12 +80,10 @@ describe Split::Dashboard do
|
|
79
80
|
it "should handle experiments without a start date" do
|
80
81
|
experiment_start_time = Time.parse('2011-07-07')
|
81
82
|
Time.stub(:now => experiment_start_time)
|
82
|
-
experiment = link_color
|
83
|
-
|
84
83
|
Split.redis.hdel(:experiment_start_times, experiment.name)
|
85
84
|
|
86
85
|
get '/'
|
87
86
|
|
88
87
|
last_response.body.should include('<small>Unknown</small>')
|
89
88
|
end
|
90
|
-
end
|
89
|
+
end
|
data/spec/experiment_spec.rb
CHANGED
@@ -51,6 +51,13 @@ describe Split::Experiment do
|
|
51
51
|
Split::Experiment.find('basket_text').start_time.should == experiment_start_time
|
52
52
|
end
|
53
53
|
|
54
|
+
it "should not save the start time to redis when start_manually is enabled" do
|
55
|
+
Split.configuration.stub(:start_manually => true)
|
56
|
+
experiment.save
|
57
|
+
|
58
|
+
Split::Experiment.find('basket_text').start_time.should be_nil
|
59
|
+
end
|
60
|
+
|
54
61
|
it "should save the selected algorithm to redis" do
|
55
62
|
experiment_algorithm = Split::Algorithms::Whiplash
|
56
63
|
experiment.algorithm = experiment_algorithm
|
@@ -148,7 +155,7 @@ describe Split::Experiment do
|
|
148
155
|
e.should == experiment
|
149
156
|
e.algorithm.should == Split::Algorithms::Whiplash
|
150
157
|
end
|
151
|
-
|
158
|
+
|
152
159
|
it "should persist a new experiment in redis, that does not exist in the configuration file" do
|
153
160
|
experiment = Split::Experiment.new('foobar', :alternatives => ['tra', 'la'], :algorithm => Split::Algorithms::Whiplash)
|
154
161
|
experiment.save
|
data/spec/helper_spec.rb
CHANGED
@@ -71,6 +71,15 @@ describe Split::Helper do
|
|
71
71
|
}.should_not change { e.participant_count }
|
72
72
|
end
|
73
73
|
|
74
|
+
it 'should not increment the counter for an not started experiment' do
|
75
|
+
Split.configuration.stub(:start_manually => true)
|
76
|
+
e = Split::Experiment.find_or_create('button_size', 'small', 'big')
|
77
|
+
lambda {
|
78
|
+
a = ab_test('button_size', 'small', 'big')
|
79
|
+
a.should eq('small')
|
80
|
+
}.should_not change { e.participant_count }
|
81
|
+
end
|
82
|
+
|
74
83
|
it "should return the given alternative for an existing user" do
|
75
84
|
alternative = ab_test('link_color', 'blue', 'red')
|
76
85
|
repeat_alternative = ab_test('link_color', 'blue', 'red')
|
@@ -261,6 +270,12 @@ describe Split::Helper do
|
|
261
270
|
self.should_receive(:some_method)
|
262
271
|
finished(@experiment_name)
|
263
272
|
end
|
273
|
+
|
274
|
+
it "should not call the method without alternative" do
|
275
|
+
ab_user[@experiment.key] = nil
|
276
|
+
self.should_not_receive(:some_method)
|
277
|
+
finished(@experiment_name)
|
278
|
+
end
|
264
279
|
end
|
265
280
|
|
266
281
|
end
|
@@ -885,13 +900,15 @@ describe Split::Helper do
|
|
885
900
|
end
|
886
901
|
|
887
902
|
it "should increment the counter for the specified-goal completed alternative" do
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
903
|
+
lambda {
|
904
|
+
lambda {
|
905
|
+
finished({"link_color" => ["purchase"]})
|
906
|
+
}.should_not change {
|
907
|
+
Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
|
908
|
+
}
|
909
|
+
}.should change {
|
910
|
+
Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
|
911
|
+
}.by(1)
|
895
912
|
end
|
896
913
|
end
|
897
914
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: split
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-10-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -194,6 +194,7 @@ files:
|
|
194
194
|
- lib/split/dashboard/views/_experiment_with_goal_header.erb
|
195
195
|
- lib/split/dashboard/views/index.erb
|
196
196
|
- lib/split/dashboard/views/layout.erb
|
197
|
+
- lib/split/encapsulated_helper.rb
|
197
198
|
- lib/split/engine.rb
|
198
199
|
- lib/split/exceptions.rb
|
199
200
|
- lib/split/experiment.rb
|