split 3.3.0 → 4.0.1
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.
- checksums.yaml +4 -4
- data/.eslintrc +1 -1
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +71 -0
- data/.rspec +1 -0
- data/.rubocop.yml +71 -1044
- data/.rubocop_todo.yml +226 -0
- data/Appraisals +4 -0
- data/CHANGELOG.md +116 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/Gemfile +2 -0
- data/README.md +63 -26
- data/Rakefile +2 -0
- data/gemfiles/{4.2.gemfile → 6.0.gemfile} +1 -1
- data/gemfiles/6.1.gemfile +9 -0
- data/gemfiles/7.0.gemfile +9 -0
- data/lib/split/algorithms/block_randomization.rb +2 -0
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +3 -2
- data/lib/split/alternative.rb +4 -3
- data/lib/split/cache.rb +28 -0
- data/lib/split/combined_experiments_helper.rb +3 -2
- data/lib/split/configuration.rb +17 -14
- data/lib/split/dashboard/helpers.rb +3 -2
- data/lib/split/dashboard/pagination_helpers.rb +4 -4
- data/lib/split/dashboard/paginator.rb +1 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +5 -0
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/dashboard.rb +19 -1
- data/lib/split/encapsulated_helper.rb +3 -2
- data/lib/split/engine.rb +7 -4
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +98 -65
- data/lib/split/experiment_catalog.rb +1 -3
- data/lib/split/extensions/string.rb +1 -0
- data/lib/split/goals_collection.rb +2 -0
- data/lib/split/helper.rb +30 -10
- data/lib/split/metric.rb +2 -1
- data/lib/split/persistence/cookie_adapter.rb +6 -1
- data/lib/split/persistence/dual_adapter.rb +54 -12
- data/lib/split/persistence/redis_adapter.rb +5 -0
- data/lib/split/persistence/session_adapter.rb +1 -0
- data/lib/split/persistence.rb +4 -2
- data/lib/split/redis_interface.rb +9 -28
- data/lib/split/trial.rb +25 -17
- data/lib/split/user.rb +20 -4
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +1 -0
- data/lib/split.rb +16 -3
- data/spec/alternative_spec.rb +1 -1
- data/spec/cache_spec.rb +88 -0
- data/spec/configuration_spec.rb +17 -15
- data/spec/dashboard/pagination_helpers_spec.rb +3 -1
- data/spec/dashboard_helpers_spec.rb +2 -2
- data/spec/dashboard_spec.rb +78 -17
- data/spec/encapsulated_helper_spec.rb +2 -2
- data/spec/experiment_spec.rb +116 -12
- data/spec/goals_collection_spec.rb +1 -1
- data/spec/helper_spec.rb +191 -112
- data/spec/persistence/cookie_adapter_spec.rb +1 -1
- data/spec/persistence/dual_adapter_spec.rb +160 -68
- data/spec/persistence/redis_adapter_spec.rb +9 -0
- data/spec/redis_interface_spec.rb +0 -69
- data/spec/spec_helper.rb +5 -6
- data/spec/trial_spec.rb +65 -19
- data/spec/user_spec.rb +45 -3
- data/split.gemspec +9 -9
- metadata +38 -29
- data/.travis.yml +0 -53
data/lib/split/trial.rb
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Split
|
|
3
4
|
class Trial
|
|
5
|
+
attr_accessor :goals
|
|
4
6
|
attr_accessor :experiment
|
|
5
|
-
|
|
7
|
+
attr_writer :metadata
|
|
6
8
|
|
|
7
9
|
def initialize(attrs = {})
|
|
8
10
|
self.experiment = attrs.delete(:experiment)
|
|
9
11
|
self.alternative = attrs.delete(:alternative)
|
|
10
12
|
self.metadata = attrs.delete(:metadata)
|
|
13
|
+
self.goals = attrs.delete(:goals) || []
|
|
11
14
|
|
|
12
15
|
@user = attrs.delete(:user)
|
|
13
16
|
@options = attrs
|
|
14
17
|
|
|
15
|
-
@
|
|
18
|
+
@alternative_chosen = false
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def metadata
|
|
@@ -33,7 +36,7 @@ module Split
|
|
|
33
36
|
end
|
|
34
37
|
end
|
|
35
38
|
|
|
36
|
-
def complete!(
|
|
39
|
+
def complete!(context = nil)
|
|
37
40
|
if alternative
|
|
38
41
|
if Array(goals).empty?
|
|
39
42
|
alternative.increment_completion
|
|
@@ -51,8 +54,9 @@ module Split
|
|
|
51
54
|
def choose!(context = nil)
|
|
52
55
|
@user.cleanup_old_experiments!
|
|
53
56
|
# Only run the process once
|
|
54
|
-
return alternative if @
|
|
57
|
+
return alternative if @alternative_chosen
|
|
55
58
|
|
|
59
|
+
new_participant = @user[@experiment.key].nil?
|
|
56
60
|
if override_is_alternative?
|
|
57
61
|
self.alternative = @options[:override]
|
|
58
62
|
if should_store_alternative? && !@user[@experiment.key]
|
|
@@ -68,23 +72,27 @@ module Split
|
|
|
68
72
|
if exclude_user?
|
|
69
73
|
self.alternative = @experiment.control
|
|
70
74
|
else
|
|
71
|
-
|
|
72
|
-
if
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
self.alternative = @user[@experiment.key]
|
|
76
|
+
if alternative.nil?
|
|
77
|
+
if @experiment.cohorting_disabled?
|
|
78
|
+
self.alternative = @experiment.control
|
|
79
|
+
else
|
|
80
|
+
self.alternative = @experiment.next_alternative
|
|
81
|
+
|
|
82
|
+
# Increment the number of participants since we are actually choosing a new alternative
|
|
83
|
+
self.alternative.increment_participation
|
|
84
|
+
|
|
85
|
+
run_callback context, Split.configuration.on_trial_choose
|
|
86
|
+
end
|
|
81
87
|
end
|
|
82
88
|
end
|
|
83
89
|
end
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
new_participant_and_cohorting_disabled = new_participant && @experiment.cohorting_disabled?
|
|
92
|
+
|
|
93
|
+
@user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || new_participant_and_cohorting_disabled
|
|
94
|
+
@alternative_chosen = true
|
|
95
|
+
run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || new_participant_and_cohorting_disabled
|
|
88
96
|
alternative
|
|
89
97
|
end
|
|
90
98
|
|
data/lib/split/user.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'forwardable'
|
|
2
4
|
|
|
3
5
|
module Split
|
|
@@ -6,11 +8,13 @@ module Split
|
|
|
6
8
|
def_delegators :@user, :keys, :[], :[]=, :delete
|
|
7
9
|
attr_reader :user
|
|
8
10
|
|
|
9
|
-
def initialize(context, adapter=nil)
|
|
11
|
+
def initialize(context, adapter = nil)
|
|
10
12
|
@user = adapter || Split::Persistence.adapter.new(context)
|
|
13
|
+
@cleaned_up = false
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def cleanup_old_experiments!
|
|
17
|
+
return if @cleaned_up
|
|
14
18
|
keys_without_finished(user.keys).each do |key|
|
|
15
19
|
experiment = ExperimentCatalog.find key_without_version(key)
|
|
16
20
|
if experiment.nil? || experiment.has_winner? || experiment.start_time.nil?
|
|
@@ -18,12 +22,14 @@ module Split
|
|
|
18
22
|
user.delete Experiment.finished_key(key)
|
|
19
23
|
end
|
|
20
24
|
end
|
|
25
|
+
@cleaned_up = true
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
def max_experiments_reached?(experiment_key)
|
|
24
29
|
if Split.configuration.allow_multiple_experiments == 'control'
|
|
25
30
|
experiments = active_experiments
|
|
26
|
-
|
|
31
|
+
experiment_key_without_version = key_without_version(experiment_key)
|
|
32
|
+
count_control = experiments.count {|k, v| k == experiment_key_without_version || v == 'control'}
|
|
27
33
|
experiments.size > count_control
|
|
28
34
|
else
|
|
29
35
|
!Split.configuration.allow_multiple_experiments &&
|
|
@@ -32,13 +38,13 @@ module Split
|
|
|
32
38
|
end
|
|
33
39
|
|
|
34
40
|
def cleanup_old_versions!(experiment)
|
|
35
|
-
keys = user.keys.select { |k| k
|
|
41
|
+
keys = user.keys.select { |k| key_without_version(k) == experiment.name }
|
|
36
42
|
keys_without_experiment(keys, experiment.key).each { |key| user.delete(key) }
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
def active_experiments
|
|
40
46
|
experiment_pairs = {}
|
|
41
|
-
user.keys.each do |key|
|
|
47
|
+
keys_without_finished(user.keys).each do |key|
|
|
42
48
|
Metric.possible_experiments(key_without_version(key)).each do |experiment|
|
|
43
49
|
if !experiment.has_winner?
|
|
44
50
|
experiment_pairs[key_without_version(key)] = user[key]
|
|
@@ -48,6 +54,16 @@ module Split
|
|
|
48
54
|
experiment_pairs
|
|
49
55
|
end
|
|
50
56
|
|
|
57
|
+
def self.find(user_id, adapter)
|
|
58
|
+
adapter = adapter.is_a?(Symbol) ? Split::Persistence::ADAPTERS[adapter] : adapter
|
|
59
|
+
|
|
60
|
+
if adapter.respond_to?(:find)
|
|
61
|
+
User.new(nil, adapter.find(user_id))
|
|
62
|
+
else
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
51
67
|
private
|
|
52
68
|
|
|
53
69
|
def keys_without_experiment(keys, experiment_key)
|
data/lib/split/version.rb
CHANGED
data/lib/split/zscore.rb
CHANGED
data/lib/split.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'redis'
|
|
3
4
|
|
|
4
5
|
require 'split/algorithms/block_randomization'
|
|
5
6
|
require 'split/algorithms/weighted_sample'
|
|
6
7
|
require 'split/algorithms/whiplash'
|
|
7
8
|
require 'split/alternative'
|
|
9
|
+
require 'split/cache'
|
|
8
10
|
require 'split/configuration'
|
|
9
11
|
require 'split/encapsulated_helper'
|
|
10
12
|
require 'split/exceptions'
|
|
@@ -35,9 +37,9 @@ module Split
|
|
|
35
37
|
# `Redis::DistRedis`, or `Redis::Namespace`.
|
|
36
38
|
def redis=(server)
|
|
37
39
|
@redis = if server.is_a?(String)
|
|
38
|
-
Redis.new(:
|
|
40
|
+
Redis.new(url: server)
|
|
39
41
|
elsif server.is_a?(Hash)
|
|
40
|
-
Redis.new(server
|
|
42
|
+
Redis.new(server)
|
|
41
43
|
elsif server.respond_to?(:smembers)
|
|
42
44
|
server
|
|
43
45
|
else
|
|
@@ -64,6 +66,17 @@ module Split
|
|
|
64
66
|
self.configuration ||= Configuration.new
|
|
65
67
|
yield(configuration)
|
|
66
68
|
end
|
|
69
|
+
|
|
70
|
+
def cache(namespace, key, &block)
|
|
71
|
+
Split::Cache.fetch(namespace, key, &block)
|
|
72
|
+
end
|
|
67
73
|
end
|
|
68
74
|
|
|
69
|
-
|
|
75
|
+
# Check to see if being run in a Rails application. If so, wait until before_initialize to run configuration so Gems that create ENV variables have the chance to initialize first.
|
|
76
|
+
if defined?(::Rails)
|
|
77
|
+
class Split::Railtie < Rails::Railtie
|
|
78
|
+
config.before_initialize { Split.configure {} }
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
Split.configure {}
|
|
82
|
+
end
|
data/spec/alternative_spec.rb
CHANGED
|
@@ -126,7 +126,7 @@ describe Split::Alternative do
|
|
|
126
126
|
|
|
127
127
|
it "should save to redis" do
|
|
128
128
|
alternative.save
|
|
129
|
-
expect(Split.redis.exists('basket_text:Basket')).to be true
|
|
129
|
+
expect(Split.redis.exists?('basket_text:Basket')).to be true
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
it "should increment participation count" do
|
data/spec/cache_spec.rb
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Split::Cache do
|
|
5
|
+
|
|
6
|
+
let(:namespace) { :test_namespace }
|
|
7
|
+
let(:key) { :test_key }
|
|
8
|
+
let(:now) { 1606189017 }
|
|
9
|
+
|
|
10
|
+
before { allow(Time).to receive(:now).and_return(now) }
|
|
11
|
+
|
|
12
|
+
describe 'clear' do
|
|
13
|
+
|
|
14
|
+
before { Split.configuration.cache = true }
|
|
15
|
+
|
|
16
|
+
it 'clears the cache' do
|
|
17
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
|
18
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
19
|
+
Split::Cache.clear
|
|
20
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe 'clear_key' do
|
|
25
|
+
before { Split.configuration.cache = true }
|
|
26
|
+
|
|
27
|
+
it 'clears the cache' do
|
|
28
|
+
expect(Time).to receive(:now).and_return(now).exactly(3).times
|
|
29
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
|
30
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
|
31
|
+
Split::Cache.clear_key(:key1)
|
|
32
|
+
|
|
33
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
|
34
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe 'fetch' do
|
|
39
|
+
|
|
40
|
+
subject { Split::Cache.fetch(namespace, key) { Time.now } }
|
|
41
|
+
|
|
42
|
+
context 'when cache disabled' do
|
|
43
|
+
|
|
44
|
+
before { Split.configuration.cache = false }
|
|
45
|
+
|
|
46
|
+
it 'returns the yield' do
|
|
47
|
+
expect(subject).to eql(now)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'yields every time' do
|
|
51
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
|
52
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
53
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context 'when cache enabled' do
|
|
58
|
+
|
|
59
|
+
before { Split.configuration.cache = true }
|
|
60
|
+
|
|
61
|
+
it 'returns the yield' do
|
|
62
|
+
expect(subject).to eql(now)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'yields once' do
|
|
66
|
+
expect(Time).to receive(:now).and_return(now).once
|
|
67
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
68
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'honors namespace' do
|
|
72
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
|
73
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
|
74
|
+
|
|
75
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
|
76
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'honors key' do
|
|
80
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
|
81
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
|
82
|
+
|
|
83
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
|
84
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/spec/configuration_spec.rb
CHANGED
|
@@ -212,23 +212,12 @@ describe Split::Configuration do
|
|
|
212
212
|
expect(@config.normalized_experiments).to eq({:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}})
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
-
context 'redis_url configuration [DEPRECATED]' do
|
|
216
|
-
it 'should warn on set and assign to #redis' do
|
|
217
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
|
218
|
-
@config.redis_url = 'example_url'
|
|
219
|
-
expect(@config.redis).to eq('example_url')
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
it 'should warn on get and return #redis' do
|
|
223
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
|
224
|
-
@config.redis = 'example_url'
|
|
225
|
-
expect(@config.redis_url).to eq('example_url')
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
215
|
context "redis configuration" do
|
|
230
216
|
it "should default to local redis server" do
|
|
231
|
-
|
|
217
|
+
old_redis_url = ENV['REDIS_URL']
|
|
218
|
+
ENV.delete('REDIS_URL')
|
|
219
|
+
expect(Split::Configuration.new.redis).to eq("redis://localhost:6379")
|
|
220
|
+
ENV['REDIS_URL'] = old_redis_url
|
|
232
221
|
end
|
|
233
222
|
|
|
234
223
|
it "should allow for redis url to be configured" do
|
|
@@ -238,8 +227,10 @@ describe Split::Configuration do
|
|
|
238
227
|
|
|
239
228
|
context "provided REDIS_URL environment variable" do
|
|
240
229
|
it "should use the ENV variable" do
|
|
230
|
+
old_redis_url = ENV['REDIS_URL']
|
|
241
231
|
ENV['REDIS_URL'] = "env_redis_url"
|
|
242
232
|
expect(Split::Configuration.new.redis).to eq("env_redis_url")
|
|
233
|
+
ENV['REDIS_URL'] = old_redis_url
|
|
243
234
|
end
|
|
244
235
|
end
|
|
245
236
|
end
|
|
@@ -255,4 +246,15 @@ describe Split::Configuration do
|
|
|
255
246
|
end
|
|
256
247
|
end
|
|
257
248
|
|
|
249
|
+
context "persistence cookie domain" do
|
|
250
|
+
it "should default to nil" do
|
|
251
|
+
expect(@config.persistence_cookie_domain).to eq(nil)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "should allow the persistence cookie domain to be configured" do
|
|
255
|
+
@config.persistence_cookie_domain = '.acme.com'
|
|
256
|
+
expect(@config.persistence_cookie_domain).to eq('.acme.com')
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
258
260
|
end
|
|
@@ -10,7 +10,9 @@ describe Split::DashboardPaginationHelpers do
|
|
|
10
10
|
context 'when params empty' do
|
|
11
11
|
let(:params) { Hash[] }
|
|
12
12
|
|
|
13
|
-
it 'returns 10' do
|
|
13
|
+
it 'returns the default (10)' do
|
|
14
|
+
default_per_page = Split.configuration.dashboard_pagination_default_per_page
|
|
15
|
+
expect(pagination_per).to eql default_per_page
|
|
14
16
|
expect(pagination_per).to eql 10
|
|
15
17
|
end
|
|
16
18
|
end
|
|
@@ -27,11 +27,11 @@ describe Split::DashboardHelpers do
|
|
|
27
27
|
|
|
28
28
|
describe '#round' do
|
|
29
29
|
it 'can round number strings' do
|
|
30
|
-
expect(round('3.1415')).to eq BigDecimal
|
|
30
|
+
expect(round('3.1415')).to eq BigDecimal('3.14')
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
it 'can round number strings for precsion' do
|
|
34
|
-
expect(round('3.1415', 1)).to eq BigDecimal
|
|
34
|
+
expect(round('3.1415', 1)).to eq BigDecimal('3.1')
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
it 'can handle invalid number strings' do
|
data/spec/dashboard_spec.rb
CHANGED
|
@@ -6,8 +6,16 @@ require 'split/dashboard'
|
|
|
6
6
|
describe Split::Dashboard do
|
|
7
7
|
include Rack::Test::Methods
|
|
8
8
|
|
|
9
|
+
class TestDashboard < Split::Dashboard
|
|
10
|
+
include Split::Helper
|
|
11
|
+
|
|
12
|
+
get '/my_experiment' do
|
|
13
|
+
ab_test(params[:experiment], 'blue', 'red')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
9
17
|
def app
|
|
10
|
-
@app ||=
|
|
18
|
+
@app ||= TestDashboard
|
|
11
19
|
end
|
|
12
20
|
|
|
13
21
|
def link(color)
|
|
@@ -29,6 +37,10 @@ describe Split::Dashboard do
|
|
|
29
37
|
let(:red_link) { link("red") }
|
|
30
38
|
let(:blue_link) { link("blue") }
|
|
31
39
|
|
|
40
|
+
before(:each) do
|
|
41
|
+
Split.configuration.beta_probability_simulations = 1
|
|
42
|
+
end
|
|
43
|
+
|
|
32
44
|
it "should respond to /" do
|
|
33
45
|
get '/'
|
|
34
46
|
expect(last_response).to be_ok
|
|
@@ -74,17 +86,49 @@ describe Split::Dashboard do
|
|
|
74
86
|
end
|
|
75
87
|
|
|
76
88
|
describe "force alternative" do
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
context "initial version" do
|
|
90
|
+
let!(:user) do
|
|
91
|
+
Split::User.new(@app, { experiment.name => 'red' })
|
|
92
|
+
end
|
|
80
93
|
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
before do
|
|
95
|
+
allow(Split::User).to receive(:new).and_return(user)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "should set current user's alternative" do
|
|
99
|
+
blue_link.participant_count = 7
|
|
100
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
|
101
|
+
|
|
102
|
+
get "/my_experiment?experiment=#{experiment.name}"
|
|
103
|
+
expect(last_response.body).to include("blue")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "should not modify an existing user" do
|
|
107
|
+
blue_link.participant_count = 7
|
|
108
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
|
109
|
+
|
|
110
|
+
expect(user[experiment.key]).to eq("red")
|
|
111
|
+
expect(blue_link.participant_count).to eq(7)
|
|
112
|
+
end
|
|
83
113
|
end
|
|
84
114
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
115
|
+
context "incremented version" do
|
|
116
|
+
let!(:user) do
|
|
117
|
+
experiment.increment_version
|
|
118
|
+
Split::User.new(@app, { "#{experiment.name}:#{experiment.version}" => 'red' })
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
before do
|
|
122
|
+
allow(Split::User).to receive(:new).and_return(user)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "should set current user's alternative" do
|
|
126
|
+
blue_link.participant_count = 7
|
|
127
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
|
128
|
+
|
|
129
|
+
get "/my_experiment?experiment=#{experiment.name}"
|
|
130
|
+
expect(last_response.body).to include("blue")
|
|
131
|
+
end
|
|
88
132
|
end
|
|
89
133
|
end
|
|
90
134
|
|
|
@@ -120,7 +164,7 @@ describe Split::Dashboard do
|
|
|
120
164
|
it "removes winner" do
|
|
121
165
|
post "/reopen?experiment=#{experiment.name}"
|
|
122
166
|
|
|
123
|
-
expect(experiment).to_not have_winner
|
|
167
|
+
expect(Split::ExperimentCatalog.find(experiment.name)).to_not have_winner
|
|
124
168
|
end
|
|
125
169
|
|
|
126
170
|
it "keeps existing stats" do
|
|
@@ -135,6 +179,28 @@ describe Split::Dashboard do
|
|
|
135
179
|
end
|
|
136
180
|
end
|
|
137
181
|
|
|
182
|
+
describe "update cohorting" do
|
|
183
|
+
it "calls enable of cohorting when action is enable" do
|
|
184
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "enable" }
|
|
185
|
+
|
|
186
|
+
expect(experiment.cohorting_disabled?).to eq false
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "calls disable of cohorting when action is disable" do
|
|
190
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" }
|
|
191
|
+
|
|
192
|
+
expect(experiment.cohorting_disabled?).to eq true
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "calls neither enable or disable cohorting when passed invalid action" do
|
|
196
|
+
previous_value = experiment.cohorting_disabled?
|
|
197
|
+
|
|
198
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "other" }
|
|
199
|
+
|
|
200
|
+
expect(experiment.cohorting_disabled?).to eq previous_value
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
138
204
|
it "should reset an experiment" do
|
|
139
205
|
red_link.participant_count = 5
|
|
140
206
|
blue_link.participant_count = 7
|
|
@@ -167,19 +233,14 @@ describe Split::Dashboard do
|
|
|
167
233
|
end
|
|
168
234
|
|
|
169
235
|
it "should display the start date" do
|
|
170
|
-
|
|
171
|
-
expect(Time).to receive(:now).at_least(:once).and_return(experiment_start_time)
|
|
172
|
-
experiment
|
|
236
|
+
experiment.start
|
|
173
237
|
|
|
174
238
|
get '/'
|
|
175
239
|
|
|
176
|
-
expect(last_response.body).to include(
|
|
240
|
+
expect(last_response.body).to include("<small>#{experiment.start_time.strftime('%Y-%m-%d')}</small>")
|
|
177
241
|
end
|
|
178
242
|
|
|
179
243
|
it "should handle experiments without a start date" do
|
|
180
|
-
experiment_start_time = Time.parse('2011-07-07')
|
|
181
|
-
expect(Time).to receive(:now).at_least(:once).and_return(experiment_start_time)
|
|
182
|
-
|
|
183
244
|
Split.redis.hdel(:experiment_start_times, experiment.name)
|
|
184
245
|
|
|
185
246
|
get '/'
|
|
@@ -21,7 +21,7 @@ describe Split::EncapsulatedHelper do
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
it "calls the block with selected alternative" do
|
|
24
|
-
expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red',
|
|
24
|
+
expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', {})
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
context "inside a view" do
|
|
@@ -33,7 +33,7 @@ describe Split::EncapsulatedHelper do
|
|
|
33
33
|
static <%= alt %>
|
|
34
34
|
<% end %>
|
|
35
35
|
ERB
|
|
36
|
-
expect(template.result(binding)).to match
|
|
36
|
+
expect(template.result(binding)).to match(/foo static \d/)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
end
|