Skip to content

Commit 482e700

Browse files
authored
Merge pull request #615 from robin-phung/master
Introduce enable/disable experiment cohorting
2 parents 261013c + 0dcc853 commit 482e700

File tree

9 files changed

+159
-7
lines changed

9 files changed

+159
-7
lines changed

lib/split/dashboard.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ class Dashboard < Sinatra::Base
6565
redirect url('/')
6666
end
6767

68+
post '/update_cohorting' do
69+
@experiment = Split::ExperimentCatalog.find(params[:experiment])
70+
case params[:cohorting_action].downcase
71+
when "enable"
72+
@experiment.enable_cohorting
73+
when "disable"
74+
@experiment.disable_cohorting
75+
end
76+
redirect url('/')
77+
end
78+
6879
delete '/experiment' do
6980
@experiment = Split::ExperimentCatalog.find(params[:experiment])
7081
@experiment.delete

lib/split/dashboard/public/dashboard.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,13 @@ function confirmReopen() {
2222
var agree = confirm("This will reopen the experiment. Are you sure?");
2323
return agree ? true : false;
2424
}
25+
26+
function confirmEnableCohorting(){
27+
var agree = confirm("This will enable the cohorting of the experiment. Are you sure?");
28+
return agree ? true : false;
29+
}
30+
31+
function confirmDisableCohorting(){
32+
var agree = confirm("This will disable the cohorting of the experiment. Note: Existing participants will continue to receive their alternative and may continue to convert. Are you sure?");
33+
return agree ? true : false;
34+
}

lib/split/dashboard/public/style.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,8 @@ a.button.green:focus, button.green:focus, input[type="submit"].green:focus {
326326
display: inline-block;
327327
padding: 5px;
328328
}
329+
330+
.divider {
331+
display: inline-block;
332+
margin-left: 10px;
333+
}

lib/split/dashboard/views/_controls.erb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,20 @@
22
<form action="<%= url "/reopen?experiment=#{experiment.name}" %>" method='post' onclick="return confirmReopen()">
33
<input type="submit" value="Reopen Experiment">
44
</form>
5+
<% else %>
6+
<% if experiment.cohorting_disabled? %>
7+
<form action="<%= url "/update_cohorting?experiment=#{experiment.name}" %>" method='post' onclick="return confirmEnableCohorting()">
8+
<input type="hidden" name="cohorting_action" value="enable">
9+
<input type="submit" value="Enable Cohorting" class="green">
10+
</form>
11+
<% else %>
12+
<form action="<%= url "/update_cohorting?experiment=#{experiment.name}" %>" method='post' onclick="return confirmDisableCohorting()">
13+
<input type="hidden" name="cohorting_action" value="disable">
14+
<input type="submit" value="Disable Cohorting" class="red">
15+
</form>
16+
<% end %>
517
<% end %>
18+
<span class="divider">|</span>
619
<% if experiment.start_time %>
720
<form action="<%= url "/reset?experiment=#{experiment.name}" %>" method='post' onclick="return confirmReset()">
821
<input type="submit" value="Reset Data">

lib/split/experiment.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ def delete
235235
end
236236
reset_winner
237237
redis.srem(:experiments, name)
238+
remove_experiment_cohorting
238239
remove_experiment_configuration
239240
Split.configuration.on_experiment_delete.call(self)
240241
increment_version
@@ -387,6 +388,23 @@ def jstring(goal = nil)
387388
js_id.gsub('/', '--')
388389
end
389390

391+
def cohorting_disabled?
392+
@cohorting_disabled ||= begin
393+
value = redis.hget(experiment_config_key, :cohorting)
394+
value.nil? ? false : value.downcase == "true"
395+
end
396+
end
397+
398+
def disable_cohorting
399+
@cohorting_disabled = true
400+
redis.hset(experiment_config_key, :cohorting, true)
401+
end
402+
403+
def enable_cohorting
404+
@cohorting_disabled = false
405+
redis.hset(experiment_config_key, :cohorting, false)
406+
end
407+
390408
protected
391409

392410
def experiment_config_key
@@ -468,5 +486,10 @@ def experiment_configuration_has_changed?
468486
def goals_collection
469487
Split::GoalsCollection.new(@name, @goals)
470488
end
489+
490+
def remove_experiment_cohorting
491+
@cohorting_disabled = false
492+
redis.hdel(experiment_config_key, :cohorting)
493+
end
471494
end
472495
end

lib/split/trial.rb

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def choose!(context = nil)
5353
# Only run the process once
5454
return alternative if @alternative_choosen
5555

56+
new_participant = @user[@experiment.key].nil?
5657
if override_is_alternative?
5758
self.alternative = @options[:override]
5859
if should_store_alternative? && !@user[@experiment.key]
@@ -70,19 +71,25 @@ def choose!(context = nil)
7071
else
7172
self.alternative = @user[@experiment.key]
7273
if alternative.nil?
73-
self.alternative = @experiment.next_alternative
74+
if @experiment.cohorting_disabled?
75+
self.alternative = @experiment.control
76+
else
77+
self.alternative = @experiment.next_alternative
7478

75-
# Increment the number of participants since we are actually choosing a new alternative
76-
self.alternative.increment_participation
79+
# Increment the number of participants since we are actually choosing a new alternative
80+
self.alternative.increment_participation
7781

78-
run_callback context, Split.configuration.on_trial_choose
82+
run_callback context, Split.configuration.on_trial_choose
83+
end
7984
end
8085
end
8186
end
8287

83-
@user[@experiment.key] = alternative.name if !@experiment.has_winner? && should_store_alternative?
88+
new_participant_and_cohorting_disabled = new_participant && @experiment.cohorting_disabled?
89+
90+
@user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || new_participant_and_cohorting_disabled
8491
@alternative_choosen = true
85-
run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled?
92+
run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || new_participant_and_cohorting_disabled
8693
alternative
8794
end
8895

spec/dashboard_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,28 @@ def link(color)
161161
end
162162
end
163163

164+
describe "update cohorting" do
165+
it "calls enable of cohorting when action is enable" do
166+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "enable" }
167+
168+
expect(experiment.cohorting_disabled?).to eq false
169+
end
170+
171+
it "calls disable of cohorting when action is disable" do
172+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" }
173+
174+
expect(experiment.cohorting_disabled?).to eq true
175+
end
176+
177+
it "calls neither enable or disable cohorting when passed invalid action" do
178+
previous_value = experiment.cohorting_disabled?
179+
180+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "other" }
181+
182+
expect(experiment.cohorting_disabled?).to eq previous_value
183+
end
184+
end
185+
164186
it "should reset an experiment" do
165187
red_link.participant_count = 5
166188
blue_link.participant_count = 7

spec/experiment_spec.rb

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,14 @@ def alternative(color)
223223
experiment.delete
224224
expect(experiment.start_time).to be_nil
225225
end
226-
end
227226

227+
it "should default cohorting back to false" do
228+
experiment.disable_cohorting
229+
expect(experiment.cohorting_disabled?).to eq(true)
230+
experiment.delete
231+
expect(experiment.cohorting_disabled?).to eq(false)
232+
end
233+
end
228234

229235
describe 'winner' do
230236
it "should have no winner initially" do
@@ -392,6 +398,34 @@ def alternative(color)
392398
end
393399
end
394400

401+
describe "#cohorting_disabled?" do
402+
it "returns false when nothing has been configured" do
403+
expect(experiment.cohorting_disabled?).to eq false
404+
end
405+
406+
it "returns true when enable_cohorting is performed" do
407+
experiment.enable_cohorting
408+
expect(experiment.cohorting_disabled?).to eq false
409+
end
410+
411+
it "returns false when nothing has been configured" do
412+
experiment.disable_cohorting
413+
expect(experiment.cohorting_disabled?).to eq true
414+
end
415+
end
416+
417+
describe "#disable_cohorting" do
418+
it "saves a new key in redis" do
419+
expect(experiment.disable_cohorting).to eq true
420+
end
421+
end
422+
423+
describe "#enable_cohorting" do
424+
it "saves a new key in redis" do
425+
expect(experiment.enable_cohorting).to eq true
426+
end
427+
end
428+
395429
describe 'changing an existing experiment' do
396430
def same_but_different_alternative
397431
Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'yellow', 'orange')

spec/trial_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,33 @@ def expect_alternative(trial, alternative_name)
198198
expect(trial.alternative.name).to_not be_empty
199199
Split.configuration.on_trial_choose = nil
200200
end
201+
202+
it "assigns user to an alternative" do
203+
trial.choose! context
204+
205+
expect(alternatives).to include(user[experiment.name])
206+
end
207+
208+
context "when cohorting is disabled" do
209+
before(:each) { allow(experiment).to receive(:cohorting_disabled?).and_return(true) }
210+
211+
it "picks the control and does not run on_trial callbacks" do
212+
Split.configuration.on_trial = :on_trial_callback
213+
214+
expect(experiment).to_not receive(:next_alternative)
215+
expect(context).not_to receive(:on_trial_callback)
216+
expect_alternative(trial, 'basket')
217+
218+
Split.configuration.enabled = true
219+
Split.configuration.on_trial = nil
220+
end
221+
222+
it "user is not assigned an alternative" do
223+
trial.choose! context
224+
225+
expect(user[experiment]).to eq(nil)
226+
end
227+
end
201228
end
202229
end
203230

0 commit comments

Comments
 (0)