split 0.6.5 → 0.6.6
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/.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
|