split 4.0.1 → 4.0.2
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/.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
|