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
@@ -7,11 +7,11 @@ module Split
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def url(*path_parts)
|
10
|
-
[ path_prefix, path_parts ].join("/").squeeze(
|
10
|
+
[ path_prefix, path_parts ].join("/").squeeze("/")
|
11
11
|
end
|
12
12
|
|
13
13
|
def path_prefix
|
14
|
-
request.env[
|
14
|
+
request.env["SCRIPT_NAME"]
|
15
15
|
end
|
16
16
|
|
17
17
|
def number_to_percentage(number, precision = 2)
|
@@ -32,15 +32,14 @@ module Split
|
|
32
32
|
z = round(z_score.to_s.to_f, 3).abs
|
33
33
|
|
34
34
|
if z >= 2.58
|
35
|
-
|
35
|
+
"99% confidence"
|
36
36
|
elsif z >= 1.96
|
37
|
-
|
37
|
+
"95% confidence"
|
38
38
|
elsif z >= 1.65
|
39
|
-
|
39
|
+
"90% confidence"
|
40
40
|
else
|
41
|
-
|
41
|
+
"Insufficient confidence"
|
42
42
|
end
|
43
|
-
|
44
43
|
end
|
45
44
|
end
|
46
45
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "split/dashboard/paginator"
|
4
4
|
|
5
5
|
module Split
|
6
6
|
module DashboardPaginationHelpers
|
@@ -30,58 +30,57 @@ module Split
|
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
33
|
+
def show_first_page_tag?
|
34
|
+
page_number > 2
|
35
|
+
end
|
36
|
+
|
37
|
+
def first_page_tag
|
38
|
+
%Q(<a href="#{url.chop}?page=1&per=#{pagination_per}">1</a>)
|
39
|
+
end
|
40
|
+
|
41
|
+
def show_first_ellipsis_tag?
|
42
|
+
page_number >= 4
|
43
|
+
end
|
44
|
+
|
45
|
+
def ellipsis_tag
|
46
|
+
"<span>...</span>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def show_prev_page_tag?
|
50
|
+
page_number > 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def prev_page_tag
|
54
|
+
%Q(<a href="#{url.chop}?page=#{page_number - 1}&per=#{pagination_per}">#{page_number - 1}</a>)
|
55
|
+
end
|
56
|
+
|
57
|
+
def current_page_tag
|
58
|
+
"<span><b>#{page_number}</b></span>"
|
59
|
+
end
|
60
|
+
|
61
|
+
def show_next_page_tag?(collection)
|
62
|
+
(page_number * pagination_per) < collection.count
|
63
|
+
end
|
64
|
+
|
65
|
+
def next_page_tag
|
66
|
+
%Q(<a href="#{url.chop}?page=#{page_number + 1}&per=#{pagination_per}">#{page_number + 1}</a>)
|
67
|
+
end
|
68
|
+
|
69
|
+
def show_last_ellipsis_tag?(collection)
|
70
|
+
(total_pages(collection) - page_number) >= 3
|
71
|
+
end
|
72
|
+
|
73
|
+
def total_pages(collection)
|
74
|
+
collection.count / pagination_per + ((collection.count % pagination_per).zero? ? 0 : 1)
|
75
|
+
end
|
76
|
+
|
77
|
+
def show_last_page_tag?(collection)
|
78
|
+
page_number < (total_pages(collection) - 1)
|
79
|
+
end
|
80
|
+
|
81
|
+
def last_page_tag(collection)
|
82
|
+
total = total_pages(collection)
|
83
|
+
%Q(<a href="#{url.chop}?page=#{total}&per=#{pagination_per}">#{total}</a>)
|
84
|
+
end
|
86
85
|
end
|
87
86
|
end
|
@@ -258,7 +258,7 @@ body {
|
|
258
258
|
color: #408C48;
|
259
259
|
}
|
260
260
|
|
261
|
-
a.button, button, input[type="submit"] {
|
261
|
+
.experiment a.button, .experiment button, .experiment input[type="submit"] {
|
262
262
|
padding: 4px 10px;
|
263
263
|
overflow: hidden;
|
264
264
|
background: #d8dae0;
|
@@ -312,10 +312,13 @@ a.button.green:focus, button.green:focus, input[type="submit"].green:focus {
|
|
312
312
|
background:#768E7A;
|
313
313
|
}
|
314
314
|
|
315
|
-
|
315
|
+
.dashboard-controls input, .dashboard-controls select {
|
316
316
|
padding: 10px;
|
317
317
|
}
|
318
318
|
|
319
|
+
.dashboard-controls-bottom {
|
320
|
+
margin-top: 10px;
|
321
|
+
}
|
319
322
|
|
320
323
|
.pagination {
|
321
324
|
text-align: center;
|
@@ -1,10 +1,12 @@
|
|
1
1
|
<% if @experiments.any? %>
|
2
2
|
<p class="intro">The list below contains all the registered experiments along with the number of test participants, completed and conversion rate currently in the system.</p>
|
3
3
|
|
4
|
-
<
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
<div class="dashboard-controls">
|
5
|
+
<input type="text" placeholder="Begin typing to filter" id="filter" />
|
6
|
+
<input type="button" id="toggle-completed" value="Hide completed" />
|
7
|
+
<input type="button" id="toggle-active" value="Hide active" />
|
8
|
+
<input type="button" id="clear-filter" value="Clear filters" />
|
9
|
+
</div>
|
8
10
|
|
9
11
|
<% paginated(@experiments).each do |experiment| %>
|
10
12
|
<% if experiment.goals.empty? %>
|
@@ -24,3 +26,16 @@
|
|
24
26
|
<p class="intro">No experiments have started yet, you need to define them in your code and introduce them to your users.</p>
|
25
27
|
<p class="intro">Check out the <a href='https://github.com/splitrb/split#readme'>Readme</a> for more help getting started.</p>
|
26
28
|
<% end %>
|
29
|
+
|
30
|
+
<div class="dashboard-controls dashboard-controls-bottom">
|
31
|
+
<form action="<%= url "/initialize_experiment" %>" method='post'>
|
32
|
+
<label>Add unregistered experiment: </label>
|
33
|
+
<select name="experiment" id="experiment-select">
|
34
|
+
<option selected disabled>experiment</option>
|
35
|
+
<% @unintialized_experiments.sort.each do |experiment_name| %>
|
36
|
+
<option value="<%= experiment_name %>"><%= experiment_name %></option>
|
37
|
+
<% end %>
|
38
|
+
</select>
|
39
|
+
<input type="submit" id="register-experiment-btn" value="register experiment"/>
|
40
|
+
</form>
|
41
|
+
<div>
|
data/lib/split/dashboard.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require "sinatra/base"
|
4
|
+
require "split"
|
5
|
+
require "bigdecimal"
|
6
|
+
require "split/dashboard/helpers"
|
7
|
+
require "split/dashboard/pagination_helpers"
|
8
8
|
|
9
9
|
module Split
|
10
10
|
class Dashboard < Sinatra::Base
|
@@ -18,14 +18,15 @@ module Split
|
|
18
18
|
helpers Split::DashboardHelpers
|
19
19
|
helpers Split::DashboardPaginationHelpers
|
20
20
|
|
21
|
-
get
|
21
|
+
get "/" do
|
22
22
|
# Display experiments without a winner at the top of the dashboard
|
23
23
|
@experiments = Split::ExperimentCatalog.all_active_first
|
24
|
+
@unintialized_experiments = Split.configuration.experiments.keys - @experiments.map(&:name)
|
24
25
|
|
25
26
|
@metrics = Split::Metric.all
|
26
27
|
|
27
28
|
# Display Rails Environment mode (or Rack version if not using Rails)
|
28
|
-
if Object.const_defined?(
|
29
|
+
if Object.const_defined?("Rails") && Rails.respond_to?(:env)
|
29
30
|
@current_env = Rails.env.titlecase
|
30
31
|
else
|
31
32
|
@current_env = "Rack: #{Rack.version}"
|
@@ -33,43 +34,48 @@ module Split
|
|
33
34
|
erb :index
|
34
35
|
end
|
35
36
|
|
36
|
-
post
|
37
|
+
post "/initialize_experiment" do
|
38
|
+
Split::ExperimentCatalog.find_or_create(params[:experiment]) unless params[:experiment].nil? || params[:experiment].empty?
|
39
|
+
redirect url("/")
|
40
|
+
end
|
41
|
+
|
42
|
+
post "/force_alternative" do
|
37
43
|
experiment = Split::ExperimentCatalog.find(params[:experiment])
|
38
44
|
alternative = Split::Alternative.new(params[:alternative], experiment.name)
|
39
45
|
|
40
|
-
cookies = JSON.parse(request.cookies[
|
46
|
+
cookies = JSON.parse(request.cookies["split_override"]) rescue {}
|
41
47
|
cookies[experiment.name] = alternative.name
|
42
|
-
response.set_cookie(
|
48
|
+
response.set_cookie("split_override", { value: cookies.to_json, path: "/" })
|
43
49
|
|
44
|
-
redirect url(
|
50
|
+
redirect url("/")
|
45
51
|
end
|
46
52
|
|
47
|
-
post
|
53
|
+
post "/experiment" do
|
48
54
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
49
55
|
@alternative = Split::Alternative.new(params[:alternative], params[:experiment])
|
50
56
|
@experiment.winner = @alternative.name
|
51
|
-
redirect url(
|
57
|
+
redirect url("/")
|
52
58
|
end
|
53
59
|
|
54
|
-
post
|
60
|
+
post "/start" do
|
55
61
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
56
62
|
@experiment.start
|
57
|
-
redirect url(
|
63
|
+
redirect url("/")
|
58
64
|
end
|
59
65
|
|
60
|
-
post
|
66
|
+
post "/reset" do
|
61
67
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
62
68
|
@experiment.reset
|
63
|
-
redirect url(
|
69
|
+
redirect url("/")
|
64
70
|
end
|
65
71
|
|
66
|
-
post
|
72
|
+
post "/reopen" do
|
67
73
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
68
74
|
@experiment.reset_winner
|
69
|
-
redirect url(
|
75
|
+
redirect url("/")
|
70
76
|
end
|
71
77
|
|
72
|
-
post
|
78
|
+
post "/update_cohorting" do
|
73
79
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
74
80
|
case params[:cohorting_action].downcase
|
75
81
|
when "enable"
|
@@ -77,13 +83,13 @@ module Split
|
|
77
83
|
when "disable"
|
78
84
|
@experiment.disable_cohorting
|
79
85
|
end
|
80
|
-
redirect url(
|
86
|
+
redirect url("/")
|
81
87
|
end
|
82
88
|
|
83
|
-
delete
|
89
|
+
delete "/experiment" do
|
84
90
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
85
91
|
@experiment.delete
|
86
|
-
redirect url(
|
92
|
+
redirect url("/")
|
87
93
|
end
|
88
94
|
end
|
89
95
|
end
|
@@ -15,7 +15,6 @@ require "split/helper"
|
|
15
15
|
#
|
16
16
|
module Split
|
17
17
|
module EncapsulatedHelper
|
18
|
-
|
19
18
|
class ContextShim
|
20
19
|
include Split::Helper
|
21
20
|
public :ab_test, :ab_finished
|
@@ -34,10 +33,9 @@ module Split
|
|
34
33
|
end
|
35
34
|
|
36
35
|
private
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
36
|
+
# instantiate and memoize a context shim in case of multiple ab_test* calls
|
37
|
+
def split_context_shim
|
38
|
+
@split_context_shim ||= ContextShim.new(self)
|
39
|
+
end
|
42
40
|
end
|
43
41
|
end
|
data/lib/split/experiment.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rubystats'
|
4
|
-
|
5
3
|
module Split
|
6
4
|
class Experiment
|
7
5
|
attr_accessor :name
|
@@ -13,7 +11,7 @@ module Split
|
|
13
11
|
attr_reader :resettable
|
14
12
|
|
15
13
|
DEFAULT_OPTIONS = {
|
16
|
-
:
|
14
|
+
resettable: true
|
17
15
|
}
|
18
16
|
|
19
17
|
def self.find(name)
|
@@ -52,7 +50,7 @@ module Split
|
|
52
50
|
|
53
51
|
if alts.length == 1
|
54
52
|
if alts[0].is_a? Hash
|
55
|
-
alts = alts[0].map{|k, v| {k => v} }
|
53
|
+
alts = alts[0].map { |k, v| { k => v } }
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
@@ -87,7 +85,7 @@ module Split
|
|
87
85
|
persist_experiment_configuration
|
88
86
|
end
|
89
87
|
|
90
|
-
redis.hmset(experiment_config_key, :resettable, resettable,
|
88
|
+
redis.hmset(experiment_config_key, :resettable, resettable.to_s,
|
91
89
|
:algorithm, algorithm.to_s)
|
92
90
|
self
|
93
91
|
end
|
@@ -96,7 +94,7 @@ module Split
|
|
96
94
|
if @alternatives.empty? && Split.configuration.experiment_for(@name).nil?
|
97
95
|
raise ExperimentNotFound.new("Experiment #{@name} not found")
|
98
96
|
end
|
99
|
-
@alternatives.each {|a| a.validate! }
|
97
|
+
@alternatives.each { |a| a.validate! }
|
100
98
|
goals_collection.validate!
|
101
99
|
end
|
102
100
|
|
@@ -109,7 +107,7 @@ module Split
|
|
109
107
|
end
|
110
108
|
|
111
109
|
def [](name)
|
112
|
-
alternatives.find{|a| a.name == name}
|
110
|
+
alternatives.find { |a| a.name == name }
|
113
111
|
end
|
114
112
|
|
115
113
|
def algorithm
|
@@ -121,7 +119,7 @@ module Split
|
|
121
119
|
end
|
122
120
|
|
123
121
|
def resettable=(resettable)
|
124
|
-
@resettable = resettable.is_a?(String) ? resettable ==
|
122
|
+
@resettable = resettable.is_a?(String) ? resettable == "true" : resettable
|
125
123
|
end
|
126
124
|
|
127
125
|
def alternatives=(alts)
|
@@ -157,7 +155,7 @@ module Split
|
|
157
155
|
end
|
158
156
|
|
159
157
|
def participant_count
|
160
|
-
alternatives.inject(0){|sum, a| sum + a.participant_count}
|
158
|
+
alternatives.inject(0) { |sum, a| sum + a.participant_count }
|
161
159
|
end
|
162
160
|
|
163
161
|
def control
|
@@ -262,8 +260,8 @@ module Split
|
|
262
260
|
exp_config = redis.hgetall(experiment_config_key)
|
263
261
|
|
264
262
|
options = {
|
265
|
-
resettable: exp_config[
|
266
|
-
algorithm: exp_config[
|
263
|
+
resettable: exp_config["resettable"],
|
264
|
+
algorithm: exp_config["algorithm"],
|
267
265
|
alternatives: load_alternatives_from_redis,
|
268
266
|
goals: Split::GoalsCollection.new(@name).load_from_redis,
|
269
267
|
metadata: load_metadata_from_redis
|
@@ -328,11 +326,11 @@ module Split
|
|
328
326
|
winning_counts.each do |alternative, wins|
|
329
327
|
alternative_probabilities[alternative] = wins / number_of_simulations.to_f
|
330
328
|
end
|
331
|
-
|
329
|
+
alternative_probabilities
|
332
330
|
end
|
333
331
|
|
334
332
|
def count_simulated_wins(winning_alternatives)
|
335
|
-
|
333
|
+
# initialize a hash to keep track of winning alternative in simulations
|
336
334
|
winning_counts = {}
|
337
335
|
alternatives.each do |alternative|
|
338
336
|
winning_counts[alternative] = 0
|
@@ -341,7 +339,7 @@ module Split
|
|
341
339
|
winning_alternatives.each do |alternative|
|
342
340
|
winning_counts[alternative] += 1
|
343
341
|
end
|
344
|
-
|
342
|
+
winning_counts
|
345
343
|
end
|
346
344
|
|
347
345
|
def find_simulated_winner(simulated_cr_hash)
|
@@ -353,7 +351,7 @@ module Split
|
|
353
351
|
end
|
354
352
|
end
|
355
353
|
winner = winning_pair[0]
|
356
|
-
|
354
|
+
winner
|
357
355
|
end
|
358
356
|
|
359
357
|
def calc_simulated_conversion_rates(beta_params)
|
@@ -363,11 +361,11 @@ module Split
|
|
363
361
|
beta_params.each do |alternative, params|
|
364
362
|
alpha = params[0]
|
365
363
|
beta = params[1]
|
366
|
-
simulated_conversion_rate =
|
364
|
+
simulated_conversion_rate = Split::Algorithms.beta_distribution_rng(alpha, beta)
|
367
365
|
simulated_cr_hash[alternative] = simulated_conversion_rate
|
368
366
|
end
|
369
367
|
|
370
|
-
|
368
|
+
simulated_cr_hash
|
371
369
|
end
|
372
370
|
|
373
371
|
def calc_beta_params(goal = nil)
|
@@ -381,7 +379,7 @@ module Split
|
|
381
379
|
|
382
380
|
beta_params[alternative] = params
|
383
381
|
end
|
384
|
-
|
382
|
+
beta_params
|
385
383
|
end
|
386
384
|
|
387
385
|
def calc_time=(time)
|
@@ -394,11 +392,11 @@ module Split
|
|
394
392
|
|
395
393
|
def jstring(goal = nil)
|
396
394
|
js_id = if goal.nil?
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
js_id.gsub(
|
395
|
+
name
|
396
|
+
else
|
397
|
+
name + "-" + goal
|
398
|
+
end
|
399
|
+
js_id.gsub("/", "--")
|
402
400
|
end
|
403
401
|
|
404
402
|
def cohorting_disabled?
|
@@ -410,95 +408,93 @@ module Split
|
|
410
408
|
|
411
409
|
def disable_cohorting
|
412
410
|
@cohorting_disabled = true
|
413
|
-
redis.hset(experiment_config_key, :cohorting, true)
|
411
|
+
redis.hset(experiment_config_key, :cohorting, true.to_s)
|
414
412
|
end
|
415
413
|
|
416
414
|
def enable_cohorting
|
417
415
|
@cohorting_disabled = false
|
418
|
-
redis.hset(experiment_config_key, :cohorting, false)
|
416
|
+
redis.hset(experiment_config_key, :cohorting, false.to_s)
|
419
417
|
end
|
420
418
|
|
421
419
|
protected
|
420
|
+
def experiment_config_key
|
421
|
+
"experiment_configurations/#{@name}"
|
422
|
+
end
|
422
423
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
def load_metadata_from_configuration
|
428
|
-
Split.configuration.experiment_for(@name)[:metadata]
|
429
|
-
end
|
424
|
+
def load_metadata_from_configuration
|
425
|
+
Split.configuration.experiment_for(@name)[:metadata]
|
426
|
+
end
|
430
427
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
428
|
+
def load_metadata_from_redis
|
429
|
+
meta = redis.get(metadata_key)
|
430
|
+
JSON.parse(meta) unless meta.nil?
|
431
|
+
end
|
435
432
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
433
|
+
def load_alternatives_from_configuration
|
434
|
+
alts = Split.configuration.experiment_for(@name)[:alternatives]
|
435
|
+
raise ArgumentError, "Experiment configuration is missing :alternatives array" unless alts
|
436
|
+
if alts.is_a?(Hash)
|
437
|
+
alts.keys
|
438
|
+
else
|
439
|
+
alts.flatten
|
440
|
+
end
|
443
441
|
end
|
444
|
-
end
|
445
442
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
443
|
+
def load_alternatives_from_redis
|
444
|
+
alternatives = redis.lrange(@name, 0, -1)
|
445
|
+
alternatives.map do |alt|
|
446
|
+
alt = begin
|
447
|
+
JSON.parse(alt)
|
448
|
+
rescue
|
449
|
+
alt
|
450
|
+
end
|
451
|
+
Split::Alternative.new(alt, @name)
|
452
|
+
end
|
455
453
|
end
|
456
|
-
end
|
457
454
|
|
458
455
|
private
|
456
|
+
def redis
|
457
|
+
Split.redis
|
458
|
+
end
|
459
459
|
|
460
|
-
|
461
|
-
|
462
|
-
|
460
|
+
def redis_interface
|
461
|
+
RedisInterface.new
|
462
|
+
end
|
463
463
|
|
464
|
-
|
465
|
-
|
466
|
-
|
464
|
+
def persist_experiment_configuration
|
465
|
+
redis_interface.add_to_set(:experiments, name)
|
466
|
+
redis_interface.persist_list(name, @alternatives.map { |alt| { alt.name => alt.weight }.to_json })
|
467
|
+
goals_collection.save
|
467
468
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
469
|
+
if @metadata
|
470
|
+
redis.set(metadata_key, @metadata.to_json)
|
471
|
+
else
|
472
|
+
delete_metadata
|
473
|
+
end
|
474
|
+
end
|
472
475
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
+
def remove_experiment_configuration
|
477
|
+
@alternatives.each(&:delete)
|
478
|
+
goals_collection.delete
|
476
479
|
delete_metadata
|
480
|
+
redis.del(@name)
|
477
481
|
end
|
478
|
-
end
|
479
482
|
|
480
|
-
|
481
|
-
|
482
|
-
goals_collection.delete
|
483
|
-
delete_metadata
|
484
|
-
redis.del(@name)
|
485
|
-
end
|
483
|
+
def experiment_configuration_has_changed?
|
484
|
+
existing_experiment = Experiment.find(@name)
|
486
485
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
existing_experiment.goals != @goals ||
|
492
|
-
existing_experiment.metadata != @metadata
|
493
|
-
end
|
486
|
+
existing_experiment.alternatives.map(&:to_s) != @alternatives.map(&:to_s) ||
|
487
|
+
existing_experiment.goals != @goals ||
|
488
|
+
existing_experiment.metadata != @metadata
|
489
|
+
end
|
494
490
|
|
495
|
-
|
496
|
-
|
497
|
-
|
491
|
+
def goals_collection
|
492
|
+
Split::GoalsCollection.new(@name, @goals)
|
493
|
+
end
|
498
494
|
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
495
|
+
def remove_experiment_cohorting
|
496
|
+
@cohorting_disabled = false
|
497
|
+
redis.hdel(experiment_config_key, :cohorting)
|
498
|
+
end
|
503
499
|
end
|
504
500
|
end
|