split 4.0.1 → 4.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +6 -3
- data/.rubocop.yml +2 -5
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +2 -1
- data/README.md +4 -2
- data/Rakefile +4 -5
- data/gemfiles/5.2.gemfile +1 -3
- data/gemfiles/6.0.gemfile +1 -3
- data/gemfiles/6.1.gemfile +1 -3
- data/gemfiles/7.0.gemfile +2 -3
- data/lib/split/algorithms/block_randomization.rb +5 -6
- data/lib/split/algorithms/whiplash.rb +16 -18
- data/lib/split/algorithms.rb +22 -0
- data/lib/split/alternative.rb +21 -22
- data/lib/split/cache.rb +0 -1
- data/lib/split/combined_experiments_helper.rb +4 -4
- data/lib/split/configuration.rb +83 -84
- data/lib/split/dashboard/helpers.rb +6 -7
- data/lib/split/dashboard/pagination_helpers.rb +53 -54
- data/lib/split/dashboard/public/style.css +5 -2
- data/lib/split/dashboard/views/index.erb +19 -4
- data/lib/split/dashboard.rb +29 -23
- data/lib/split/encapsulated_helper.rb +4 -6
- data/lib/split/experiment.rb +84 -88
- data/lib/split/experiment_catalog.rb +6 -5
- data/lib/split/extensions/string.rb +1 -1
- data/lib/split/goals_collection.rb +8 -10
- data/lib/split/helper.rb +19 -19
- data/lib/split/metric.rb +4 -5
- data/lib/split/persistence/cookie_adapter.rb +44 -47
- data/lib/split/persistence/dual_adapter.rb +7 -8
- data/lib/split/persistence/redis_adapter.rb +2 -3
- data/lib/split/persistence/session_adapter.rb +0 -2
- data/lib/split/persistence.rb +4 -4
- data/lib/split/redis_interface.rb +1 -2
- data/lib/split/trial.rb +23 -24
- data/lib/split/user.rb +12 -13
- data/lib/split/version.rb +1 -1
- data/lib/split/zscore.rb +1 -3
- data/lib/split.rb +26 -25
- data/spec/algorithms/block_randomization_spec.rb +6 -5
- data/spec/algorithms/weighted_sample_spec.rb +6 -5
- data/spec/algorithms/whiplash_spec.rb +4 -5
- data/spec/alternative_spec.rb +35 -36
- data/spec/cache_spec.rb +15 -19
- data/spec/combined_experiments_helper_spec.rb +18 -17
- data/spec/configuration_spec.rb +32 -38
- data/spec/dashboard/pagination_helpers_spec.rb +69 -67
- data/spec/dashboard/paginator_spec.rb +10 -9
- data/spec/dashboard_helpers_spec.rb +19 -18
- data/spec/dashboard_spec.rb +67 -35
- data/spec/encapsulated_helper_spec.rb +12 -14
- data/spec/experiment_catalog_spec.rb +14 -13
- data/spec/experiment_spec.rb +121 -123
- data/spec/goals_collection_spec.rb +17 -15
- data/spec/helper_spec.rb +379 -382
- data/spec/metric_spec.rb +14 -14
- data/spec/persistence/cookie_adapter_spec.rb +23 -8
- data/spec/persistence/dual_adapter_spec.rb +71 -71
- data/spec/persistence/redis_adapter_spec.rb +25 -26
- data/spec/persistence/session_adapter_spec.rb +2 -3
- data/spec/persistence_spec.rb +1 -2
- data/spec/redis_interface_spec.rb +16 -14
- data/spec/spec_helper.rb +15 -13
- data/spec/split_spec.rb +11 -11
- data/spec/support/cookies_mock.rb +1 -2
- data/spec/trial_spec.rb +61 -60
- data/spec/user_spec.rb +36 -36
- data/split.gemspec +20 -20
- metadata +7 -10
- data/.rubocop_todo.yml +0 -226
- data/Appraisals +0 -19
- data/gemfiles/5.0.gemfile +0 -9
- data/gemfiles/5.1.gemfile +0 -9
data/spec/dashboard_spec.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "rack/test"
|
5
|
+
require "split/dashboard"
|
5
6
|
|
6
7
|
describe Split::Dashboard do
|
7
8
|
include Rack::Test::Methods
|
@@ -9,8 +10,8 @@ describe Split::Dashboard do
|
|
9
10
|
class TestDashboard < Split::Dashboard
|
10
11
|
include Split::Helper
|
11
12
|
|
12
|
-
get
|
13
|
-
ab_test(params[:experiment],
|
13
|
+
get "/my_experiment" do
|
14
|
+
ab_test(params[:experiment], "blue", "red")
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
@@ -27,11 +28,11 @@ describe Split::Dashboard do
|
|
27
28
|
}
|
28
29
|
|
29
30
|
let(:experiment_with_goals) {
|
30
|
-
Split::ExperimentCatalog.find_or_create({"link_color" => ["goal_1", "goal_2"]}, "blue", "red")
|
31
|
+
Split::ExperimentCatalog.find_or_create({ "link_color" => ["goal_1", "goal_2"] }, "blue", "red")
|
31
32
|
}
|
32
33
|
|
33
34
|
let(:metric) {
|
34
|
-
Split::Metric.find_or_create(name:
|
35
|
+
Split::Metric.find_or_create(name: "testmetric", experiments: [experiment, experiment_with_goals])
|
35
36
|
}
|
36
37
|
|
37
38
|
let(:red_link) { link("red") }
|
@@ -42,7 +43,7 @@ describe Split::Dashboard do
|
|
42
43
|
end
|
43
44
|
|
44
45
|
it "should respond to /" do
|
45
|
-
get
|
46
|
+
get "/"
|
46
47
|
expect(last_response).to be_ok
|
47
48
|
end
|
48
49
|
|
@@ -54,33 +55,33 @@ describe Split::Dashboard do
|
|
54
55
|
context "experiment without goals" do
|
55
56
|
it "should display a Start button" do
|
56
57
|
experiment
|
57
|
-
get
|
58
|
-
expect(last_response.body).to include(
|
58
|
+
get "/"
|
59
|
+
expect(last_response.body).to include("Start")
|
59
60
|
|
60
61
|
post "/start?experiment=#{experiment.name}"
|
61
|
-
get
|
62
|
-
expect(last_response.body).to include(
|
63
|
-
expect(last_response.body).not_to include(
|
62
|
+
get "/"
|
63
|
+
expect(last_response.body).to include("Reset Data")
|
64
|
+
expect(last_response.body).not_to include("Metrics:")
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
67
68
|
context "experiment with metrics" do
|
68
69
|
it "should display the names of associated metrics" do
|
69
70
|
metric
|
70
|
-
get
|
71
|
-
expect(last_response.body).to include(
|
71
|
+
get "/"
|
72
|
+
expect(last_response.body).to include("Metrics:testmetric")
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
75
76
|
context "with goals" do
|
76
77
|
it "should display a Start button" do
|
77
78
|
experiment_with_goals
|
78
|
-
get
|
79
|
-
expect(last_response.body).to include(
|
79
|
+
get "/"
|
80
|
+
expect(last_response.body).to include("Start")
|
80
81
|
|
81
82
|
post "/start?experiment=#{experiment.name}"
|
82
|
-
get
|
83
|
-
expect(last_response.body).to include(
|
83
|
+
get "/"
|
84
|
+
expect(last_response.body).to include("Reset Data")
|
84
85
|
end
|
85
86
|
end
|
86
87
|
end
|
@@ -88,7 +89,7 @@ describe Split::Dashboard do
|
|
88
89
|
describe "force alternative" do
|
89
90
|
context "initial version" do
|
90
91
|
let!(:user) do
|
91
|
-
Split::User.new(@app, { experiment.name =>
|
92
|
+
Split::User.new(@app, { experiment.name => "red" })
|
92
93
|
end
|
93
94
|
|
94
95
|
before do
|
@@ -115,7 +116,7 @@ describe Split::Dashboard do
|
|
115
116
|
context "incremented version" do
|
116
117
|
let!(:user) do
|
117
118
|
experiment.increment_version
|
118
|
-
Split::User.new(@app, { "#{experiment.name}:#{experiment.version}" =>
|
119
|
+
Split::User.new(@app, { "#{experiment.name}:#{experiment.version}" => "red" })
|
119
120
|
end
|
120
121
|
|
121
122
|
before do
|
@@ -134,28 +135,28 @@ describe Split::Dashboard do
|
|
134
135
|
|
135
136
|
describe "index page" do
|
136
137
|
context "with winner" do
|
137
|
-
before { experiment.winner =
|
138
|
+
before { experiment.winner = "red" }
|
138
139
|
|
139
140
|
it "displays `Reopen Experiment` button" do
|
140
|
-
get
|
141
|
+
get "/"
|
141
142
|
|
142
|
-
expect(last_response.body).to include(
|
143
|
+
expect(last_response.body).to include("Reopen Experiment")
|
143
144
|
end
|
144
145
|
end
|
145
146
|
|
146
147
|
context "without winner" do
|
147
148
|
it "should not display `Reopen Experiment` button" do
|
148
|
-
get
|
149
|
+
get "/"
|
149
150
|
|
150
|
-
expect(last_response.body).to_not include(
|
151
|
+
expect(last_response.body).to_not include("Reopen Experiment")
|
151
152
|
end
|
152
153
|
end
|
153
154
|
end
|
154
155
|
|
155
156
|
describe "reopen experiment" do
|
156
|
-
before { experiment.winner =
|
157
|
+
before { experiment.winner = "red" }
|
157
158
|
|
158
|
-
it
|
159
|
+
it "redirects" do
|
159
160
|
post "/reopen?experiment=#{experiment.name}"
|
160
161
|
|
161
162
|
expect(last_response).to be_redirect
|
@@ -170,7 +171,7 @@ describe Split::Dashboard do
|
|
170
171
|
it "keeps existing stats" do
|
171
172
|
red_link.participant_count = 5
|
172
173
|
blue_link.participant_count = 7
|
173
|
-
experiment.winner =
|
174
|
+
experiment.winner = "blue"
|
174
175
|
|
175
176
|
post "/reopen?experiment=#{experiment.name}"
|
176
177
|
|
@@ -201,10 +202,41 @@ describe Split::Dashboard do
|
|
201
202
|
end
|
202
203
|
end
|
203
204
|
|
205
|
+
describe "initialize experiment" do
|
206
|
+
before do
|
207
|
+
Split.configuration.experiments = {
|
208
|
+
my_experiment: {
|
209
|
+
alternatives: [ "control", "alternative" ],
|
210
|
+
}
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
it "initializes the experiment when the experiment is given" do
|
215
|
+
expect(Split::ExperimentCatalog.find("my_experiment")).to be nil
|
216
|
+
|
217
|
+
post "/initialize_experiment", { experiment: "my_experiment" }
|
218
|
+
|
219
|
+
experiment = Split::ExperimentCatalog.find("my_experiment")
|
220
|
+
expect(experiment).to be_a(Split::Experiment)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "does not attempt to intialize the experiment when empty experiment is given" do
|
224
|
+
post "/initialize_experiment", { experiment: "" }
|
225
|
+
|
226
|
+
expect(Split::ExperimentCatalog).to_not receive(:find_or_create)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "does not attempt to intialize the experiment when no experiment is given" do
|
230
|
+
post "/initialize_experiment"
|
231
|
+
|
232
|
+
expect(Split::ExperimentCatalog).to_not receive(:find_or_create)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
204
236
|
it "should reset an experiment" do
|
205
237
|
red_link.participant_count = 5
|
206
238
|
blue_link.participant_count = 7
|
207
|
-
experiment.winner =
|
239
|
+
experiment.winner = "blue"
|
208
240
|
|
209
241
|
post "/reset?experiment=#{experiment.name}"
|
210
242
|
|
@@ -226,16 +258,16 @@ describe Split::Dashboard do
|
|
226
258
|
|
227
259
|
it "should mark an alternative as the winner" do
|
228
260
|
expect(experiment.winner).to be_nil
|
229
|
-
post "/experiment?experiment=#{experiment.name}", :
|
261
|
+
post "/experiment?experiment=#{experiment.name}", alternative: "red"
|
230
262
|
|
231
263
|
expect(last_response).to be_redirect
|
232
|
-
expect(experiment.winner.name).to eq(
|
264
|
+
expect(experiment.winner.name).to eq("red")
|
233
265
|
end
|
234
266
|
|
235
267
|
it "should display the start date" do
|
236
268
|
experiment.start
|
237
269
|
|
238
|
-
get
|
270
|
+
get "/"
|
239
271
|
|
240
272
|
expect(last_response.body).to include("<small>#{experiment.start_time.strftime('%Y-%m-%d')}</small>")
|
241
273
|
end
|
@@ -243,8 +275,8 @@ describe Split::Dashboard do
|
|
243
275
|
it "should handle experiments without a start date" do
|
244
276
|
Split.redis.hdel(:experiment_start_times, experiment.name)
|
245
277
|
|
246
|
-
get
|
278
|
+
get "/"
|
247
279
|
|
248
|
-
expect(last_response.body).to include(
|
280
|
+
expect(last_response.body).to include("<small>Unknown</small>")
|
249
281
|
end
|
250
282
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "spec_helper"
|
3
4
|
|
4
5
|
describe Split::EncapsulatedHelper do
|
5
6
|
include Split::EncapsulatedHelper
|
6
7
|
|
7
|
-
|
8
8
|
def params
|
9
|
-
raise NoMethodError,
|
9
|
+
raise NoMethodError, "This method is not really defined"
|
10
10
|
end
|
11
11
|
|
12
12
|
describe "ab_test" do
|
@@ -16,37 +16,35 @@ describe Split::EncapsulatedHelper do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should not raise an error when params raises an error" do
|
19
|
-
expect{ params }.to raise_error(NoMethodError)
|
20
|
-
expect
|
19
|
+
expect { params }.to raise_error(NoMethodError)
|
20
|
+
expect { ab_test("link_color", "blue", "red") }.not_to raise_error
|
21
21
|
end
|
22
22
|
|
23
23
|
it "calls the block with selected alternative" do
|
24
|
-
expect{|block| ab_test(
|
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
|
28
|
-
|
29
28
|
it "works inside ERB" do
|
30
|
-
require
|
31
|
-
template = ERB.new(<<-ERB.split(/\s+/s).map(&:strip).join(
|
29
|
+
require "erb"
|
30
|
+
template = ERB.new(<<-ERB.split(/\s+/s).map(&:strip).join(" "), nil, "%")
|
32
31
|
foo <% ab_test(:foo, '1', '2') do |alt, meta| %>
|
33
32
|
static <%= alt %>
|
34
33
|
<% end %>
|
35
34
|
ERB
|
36
35
|
expect(template.result(binding)).to match(/foo static \d/)
|
37
36
|
end
|
38
|
-
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
42
40
|
describe "context" do
|
43
|
-
it
|
44
|
-
ctx = Class.new{
|
41
|
+
it "is passed in shim" do
|
42
|
+
ctx = Class.new {
|
45
43
|
include Split::EncapsulatedHelper
|
46
44
|
public :session
|
47
45
|
}.new
|
48
|
-
expect(ctx).to receive(:session){{}}
|
49
|
-
expect{ ctx.ab_test(
|
46
|
+
expect(ctx).to receive(:session) { {} }
|
47
|
+
expect { ctx.ab_test("link_color", "blue", "red") }.not_to raise_error
|
50
48
|
end
|
51
49
|
end
|
52
50
|
end
|
@@ -1,53 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "spec_helper"
|
3
4
|
|
4
5
|
describe Split::ExperimentCatalog do
|
5
6
|
subject { Split::ExperimentCatalog }
|
6
7
|
|
7
8
|
describe ".find_or_create" do
|
8
9
|
it "should not raise an error when passed strings for alternatives" do
|
9
|
-
expect { subject.find_or_create(
|
10
|
+
expect { subject.find_or_create("xyz", "1", "2", "3") }.not_to raise_error
|
10
11
|
end
|
11
12
|
|
12
13
|
it "should not raise an error when passed an array for alternatives" do
|
13
|
-
expect { subject.find_or_create(
|
14
|
+
expect { subject.find_or_create("xyz", ["1", "2", "3"]) }.not_to raise_error
|
14
15
|
end
|
15
16
|
|
16
17
|
it "should raise the appropriate error when passed integers for alternatives" do
|
17
|
-
expect { subject.find_or_create(
|
18
|
+
expect { subject.find_or_create("xyz", 1, 2, 3) }.to raise_error(ArgumentError)
|
18
19
|
end
|
19
20
|
|
20
21
|
it "should raise the appropriate error when passed symbols for alternatives" do
|
21
|
-
expect { subject.find_or_create(
|
22
|
+
expect { subject.find_or_create("xyz", :a, :b, :c) }.to raise_error(ArgumentError)
|
22
23
|
end
|
23
24
|
|
24
25
|
it "should not raise error when passed an array for goals" do
|
25
|
-
expect { subject.find_or_create({
|
26
|
+
expect { subject.find_or_create({ "link_color" => ["purchase", "refund"] }, "blue", "red") }
|
26
27
|
.not_to raise_error
|
27
28
|
end
|
28
29
|
|
29
30
|
it "should not raise error when passed just one goal" do
|
30
|
-
expect { subject.find_or_create({
|
31
|
+
expect { subject.find_or_create({ "link_color" => "purchase" }, "blue", "red") }
|
31
32
|
.not_to raise_error
|
32
33
|
end
|
33
34
|
|
34
35
|
it "constructs a new experiment" do
|
35
|
-
expect(subject.find_or_create(
|
36
|
+
expect(subject.find_or_create("my_exp", "control me").control.to_s).to eq("control me")
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
|
-
describe
|
40
|
+
describe ".find" do
|
40
41
|
it "should return an existing experiment" do
|
41
|
-
experiment = Split::Experiment.new(
|
42
|
+
experiment = Split::Experiment.new("basket_text", alternatives: ["blue", "red", "green"])
|
42
43
|
experiment.save
|
43
44
|
|
44
|
-
experiment = subject.find(
|
45
|
+
experiment = subject.find("basket_text")
|
45
46
|
|
46
|
-
expect(experiment.name).to eq(
|
47
|
+
expect(experiment.name).to eq("basket_text")
|
47
48
|
end
|
48
49
|
|
49
50
|
it "should return nil if experiment not exist" do
|
50
|
-
expect(subject.find(
|
51
|
+
expect(subject.find("non_existent_experiment")).to be_nil
|
51
52
|
end
|
52
53
|
end
|
53
54
|
end
|