split 4.0.1 → 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -3
  3. data/.rubocop.yml +2 -5
  4. data/CHANGELOG.md +23 -0
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +2 -1
  7. data/README.md +4 -2
  8. data/Rakefile +4 -5
  9. data/gemfiles/5.2.gemfile +1 -3
  10. data/gemfiles/6.0.gemfile +1 -3
  11. data/gemfiles/6.1.gemfile +1 -3
  12. data/gemfiles/7.0.gemfile +2 -3
  13. data/lib/split/algorithms/block_randomization.rb +5 -6
  14. data/lib/split/algorithms/whiplash.rb +16 -18
  15. data/lib/split/algorithms.rb +22 -0
  16. data/lib/split/alternative.rb +21 -22
  17. data/lib/split/cache.rb +0 -1
  18. data/lib/split/combined_experiments_helper.rb +4 -4
  19. data/lib/split/configuration.rb +83 -84
  20. data/lib/split/dashboard/helpers.rb +6 -7
  21. data/lib/split/dashboard/pagination_helpers.rb +53 -54
  22. data/lib/split/dashboard/public/style.css +5 -2
  23. data/lib/split/dashboard/views/index.erb +19 -4
  24. data/lib/split/dashboard.rb +29 -23
  25. data/lib/split/encapsulated_helper.rb +4 -6
  26. data/lib/split/experiment.rb +84 -88
  27. data/lib/split/experiment_catalog.rb +6 -5
  28. data/lib/split/extensions/string.rb +1 -1
  29. data/lib/split/goals_collection.rb +8 -10
  30. data/lib/split/helper.rb +19 -19
  31. data/lib/split/metric.rb +4 -5
  32. data/lib/split/persistence/cookie_adapter.rb +44 -47
  33. data/lib/split/persistence/dual_adapter.rb +7 -8
  34. data/lib/split/persistence/redis_adapter.rb +2 -3
  35. data/lib/split/persistence/session_adapter.rb +0 -2
  36. data/lib/split/persistence.rb +4 -4
  37. data/lib/split/redis_interface.rb +1 -2
  38. data/lib/split/trial.rb +23 -24
  39. data/lib/split/user.rb +12 -13
  40. data/lib/split/version.rb +1 -1
  41. data/lib/split/zscore.rb +1 -3
  42. data/lib/split.rb +26 -25
  43. data/spec/algorithms/block_randomization_spec.rb +6 -5
  44. data/spec/algorithms/weighted_sample_spec.rb +6 -5
  45. data/spec/algorithms/whiplash_spec.rb +4 -5
  46. data/spec/alternative_spec.rb +35 -36
  47. data/spec/cache_spec.rb +15 -19
  48. data/spec/combined_experiments_helper_spec.rb +18 -17
  49. data/spec/configuration_spec.rb +32 -38
  50. data/spec/dashboard/pagination_helpers_spec.rb +69 -67
  51. data/spec/dashboard/paginator_spec.rb +10 -9
  52. data/spec/dashboard_helpers_spec.rb +19 -18
  53. data/spec/dashboard_spec.rb +67 -35
  54. data/spec/encapsulated_helper_spec.rb +12 -14
  55. data/spec/experiment_catalog_spec.rb +14 -13
  56. data/spec/experiment_spec.rb +121 -123
  57. data/spec/goals_collection_spec.rb +17 -15
  58. data/spec/helper_spec.rb +379 -382
  59. data/spec/metric_spec.rb +14 -14
  60. data/spec/persistence/cookie_adapter_spec.rb +23 -8
  61. data/spec/persistence/dual_adapter_spec.rb +71 -71
  62. data/spec/persistence/redis_adapter_spec.rb +25 -26
  63. data/spec/persistence/session_adapter_spec.rb +2 -3
  64. data/spec/persistence_spec.rb +1 -2
  65. data/spec/redis_interface_spec.rb +16 -14
  66. data/spec/spec_helper.rb +15 -13
  67. data/spec/split_spec.rb +11 -11
  68. data/spec/support/cookies_mock.rb +1 -2
  69. data/spec/trial_spec.rb +61 -60
  70. data/spec/user_spec.rb +36 -36
  71. data/split.gemspec +20 -20
  72. metadata +7 -10
  73. data/.rubocop_todo.yml +0 -226
  74. data/Appraisals +0 -19
  75. data/gemfiles/5.0.gemfile +0 -9
  76. data/gemfiles/5.1.gemfile +0 -9
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
- require 'spec_helper'
3
- require 'rack/test'
4
- require 'split/dashboard'
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 '/my_experiment' do
13
- ab_test(params[:experiment], 'blue', 'red')
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: 'testmetric', experiments: [experiment, experiment_with_goals])
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('Start')
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('Reset Data')
63
- expect(last_response.body).not_to include('Metrics:')
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('Metrics:testmetric')
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('Start')
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('Reset Data')
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 => 'red' })
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}" => 'red' })
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 = 'red' }
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('Reopen Experiment')
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('Reopen Experiment')
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 = 'red' }
157
+ before { experiment.winner = "red" }
157
158
 
158
- it 'redirects' do
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 = 'blue'
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 = 'blue'
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}", :alternative => 'red'
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('red')
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('<small>Unknown</small>')
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
- require 'spec_helper'
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, 'This method is not really defined'
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(lambda { ab_test('link_color', 'blue', 'red') }).not_to raise_error
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('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
28
-
29
28
  it "works inside ERB" do
30
- require 'erb'
31
- template = ERB.new(<<-ERB.split(/\s+/s).map(&:strip).join(' '), nil, "%")
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 'is passed in shim' do
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('link_color', 'blue', 'red') }.not_to raise_error
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
- require 'spec_helper'
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('xyz', '1', '2', '3') }.not_to raise_error
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('xyz', ['1', '2', '3']) }.not_to raise_error
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('xyz', 1, 2, 3) }.to raise_error(ArgumentError)
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('xyz', :a, :b, :c) }.to raise_error(ArgumentError)
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({'link_color' => ["purchase", "refund"]}, 'blue', 'red') }
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({'link_color' => "purchase"}, 'blue', 'red') }
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('my_exp', 'control me').control.to_s).to eq('control me')
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 '.find' do
40
+ describe ".find" do
40
41
  it "should return an existing experiment" do
41
- experiment = Split::Experiment.new('basket_text', alternatives: ['blue', 'red', 'green'])
42
+ experiment = Split::Experiment.new("basket_text", alternatives: ["blue", "red", "green"])
42
43
  experiment.save
43
44
 
44
- experiment = subject.find('basket_text')
45
+ experiment = subject.find("basket_text")
45
46
 
46
- expect(experiment.name).to eq('basket_text')
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('non_existent_experiment')).to be_nil
51
+ expect(subject.find("non_existent_experiment")).to be_nil
51
52
  end
52
53
  end
53
54
  end