split 3.2.0 → 4.0.5
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 +5 -5
- data/.eslintrc +1 -1
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +63 -0
- data/.rspec +1 -0
- data/.rubocop.yml +67 -1043
- data/CHANGELOG.md +174 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +6 -1
- data/README.md +79 -33
- data/Rakefile +6 -5
- data/lib/split/algorithms/block_randomization.rb +7 -6
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +17 -18
- data/lib/split/algorithms.rb +14 -0
- data/lib/split/alternative.rb +25 -25
- data/lib/split/cache.rb +27 -0
- data/lib/split/combined_experiments_helper.rb +6 -5
- data/lib/split/configuration.rb +94 -91
- data/lib/split/dashboard/helpers.rb +9 -9
- data/lib/split/dashboard/pagination_helpers.rb +86 -0
- data/lib/split/dashboard/paginator.rb +17 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +19 -2
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard/views/_experiment.erb +2 -1
- data/lib/split/dashboard/views/index.erb +24 -5
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/dashboard.rb +47 -20
- data/lib/split/encapsulated_helper.rb +15 -8
- data/lib/split/engine.rb +7 -4
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +160 -122
- data/lib/split/experiment_catalog.rb +7 -8
- data/lib/split/extensions/string.rb +2 -1
- data/lib/split/goals_collection.rb +10 -10
- data/lib/split/helper.rb +56 -24
- data/lib/split/metric.rb +6 -6
- data/lib/split/persistence/cookie_adapter.rb +52 -15
- data/lib/split/persistence/dual_adapter.rb +53 -12
- data/lib/split/persistence/redis_adapter.rb +8 -4
- data/lib/split/persistence/session_adapter.rb +1 -2
- data/lib/split/persistence.rb +8 -6
- data/lib/split/redis_interface.rb +16 -31
- data/lib/split/trial.rb +48 -41
- data/lib/split/user.rb +30 -15
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +2 -3
- data/lib/split.rb +39 -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 +84 -0
- data/spec/combined_experiments_helper_spec.rb +18 -17
- data/spec/configuration_spec.rb +41 -45
- data/spec/dashboard/pagination_helpers_spec.rb +202 -0
- data/spec/dashboard/paginator_spec.rb +38 -0
- data/spec/dashboard_helpers_spec.rb +19 -18
- data/spec/dashboard_spec.rb +153 -48
- data/spec/encapsulated_helper_spec.rb +47 -23
- data/spec/experiment_catalog_spec.rb +14 -13
- data/spec/experiment_spec.rb +224 -111
- data/spec/goals_collection_spec.rb +18 -16
- data/spec/helper_spec.rb +539 -419
- data/spec/metric_spec.rb +14 -14
- data/spec/persistence/cookie_adapter_spec.rb +105 -27
- data/spec/persistence/dual_adapter_spec.rb +158 -66
- data/spec/persistence/redis_adapter_spec.rb +35 -27
- data/spec/persistence/session_adapter_spec.rb +2 -3
- data/spec/persistence_spec.rb +1 -2
- data/spec/redis_interface_spec.rb +25 -82
- data/spec/spec_helper.rb +38 -24
- data/spec/split_spec.rb +18 -18
- data/spec/support/cookies_mock.rb +1 -2
- data/spec/trial_spec.rb +117 -70
- data/spec/user_spec.rb +69 -27
- data/split.gemspec +26 -22
- metadata +85 -37
- data/.travis.yml +0 -41
- data/Appraisals +0 -13
- data/gemfiles/4.2.gemfile +0 -9
- data/gemfiles/5.0.gemfile +0 -10
- data/gemfiles/5.1.gemfile +0 -10
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
<form action="<%= url "/reopen?experiment=#{experiment.name}" %>" method='post' onclick="return confirmReopen()">
|
|
3
3
|
<input type="submit" value="Reopen Experiment">
|
|
4
4
|
</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 %>
|
|
5
17
|
<% end %>
|
|
18
|
+
<span class="divider">|</span>
|
|
6
19
|
<% if experiment.start_time %>
|
|
7
20
|
<form action="<%= url "/reset?experiment=#{experiment.name}" %>" method='post' onclick="return confirmReset()">
|
|
8
21
|
<input type="submit" value="Reset Data">
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
summary_texts = {}
|
|
17
17
|
extra_columns.each do |column|
|
|
18
18
|
extra_infos = experiment.alternatives.map(&:extra_info).select{|extra_info| extra_info && extra_info[column] }
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
if extra_infos.length > 0 && extra_infos.all? { |extra_info| extra_info[column].kind_of?(Numeric) }
|
|
20
21
|
summary_texts[column] = extra_infos.inject(0){|sum, extra_info| sum += extra_info[column]}
|
|
21
22
|
else
|
|
22
23
|
summary_texts[column] = "N/A"
|
|
@@ -1,12 +1,14 @@
|
|
|
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
|
-
<% @experiments.each do |experiment| %>
|
|
11
|
+
<% paginated(@experiments).each do |experiment| %>
|
|
10
12
|
<% if experiment.goals.empty? %>
|
|
11
13
|
<%= erb :_experiment, :locals => {:goal => nil, :experiment => experiment} %>
|
|
12
14
|
<% else %>
|
|
@@ -16,7 +18,24 @@
|
|
|
16
18
|
<% end %>
|
|
17
19
|
<% end %>
|
|
18
20
|
<% end %>
|
|
21
|
+
|
|
22
|
+
<div class="pagination">
|
|
23
|
+
<%= pagination(@experiments) %>
|
|
24
|
+
</div>
|
|
19
25
|
<% else %>
|
|
20
26
|
<p class="intro">No experiments have started yet, you need to define them in your code and introduce them to your users.</p>
|
|
21
27
|
<p class="intro">Check out the <a href='https://github.com/splitrb/split#readme'>Readme</a> for more help getting started.</p>
|
|
22
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,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
2
|
+
|
|
3
|
+
require "sinatra/base"
|
|
4
|
+
require "split"
|
|
5
|
+
require "bigdecimal"
|
|
6
|
+
require "split/dashboard/helpers"
|
|
7
|
+
require "split/dashboard/pagination_helpers"
|
|
6
8
|
|
|
7
9
|
module Split
|
|
8
10
|
class Dashboard < Sinatra::Base
|
|
@@ -14,56 +16,81 @@ module Split
|
|
|
14
16
|
set :method_override, true
|
|
15
17
|
|
|
16
18
|
helpers Split::DashboardHelpers
|
|
19
|
+
helpers Split::DashboardPaginationHelpers
|
|
17
20
|
|
|
18
|
-
get
|
|
21
|
+
get "/" do
|
|
19
22
|
# Display experiments without a winner at the top of the dashboard
|
|
20
23
|
@experiments = Split::ExperimentCatalog.all_active_first
|
|
24
|
+
@unintialized_experiments = Split.configuration.experiments.keys - @experiments.map(&:name)
|
|
21
25
|
|
|
22
26
|
@metrics = Split::Metric.all
|
|
23
27
|
|
|
24
28
|
# Display Rails Environment mode (or Rack version if not using Rails)
|
|
25
|
-
if Object.const_defined?(
|
|
29
|
+
if Object.const_defined?("Rails") && Rails.respond_to?(:env)
|
|
26
30
|
@current_env = Rails.env.titlecase
|
|
27
31
|
else
|
|
28
|
-
|
|
32
|
+
rack_version = Rack.respond_to?(:version) ? Rack.version : Rack.release
|
|
33
|
+
@current_env = "Rack: #{rack_version}"
|
|
29
34
|
end
|
|
30
35
|
erb :index
|
|
31
36
|
end
|
|
32
37
|
|
|
33
|
-
post
|
|
34
|
-
Split::
|
|
35
|
-
redirect url(
|
|
38
|
+
post "/initialize_experiment" do
|
|
39
|
+
Split::ExperimentCatalog.find_or_create(params[:experiment]) unless params[:experiment].nil? || params[:experiment].empty?
|
|
40
|
+
redirect url("/")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
post "/force_alternative" do
|
|
44
|
+
experiment = Split::ExperimentCatalog.find(params[:experiment])
|
|
45
|
+
alternative = Split::Alternative.new(params[:alternative], experiment.name)
|
|
46
|
+
|
|
47
|
+
cookies = JSON.parse(request.cookies["split_override"]) rescue {}
|
|
48
|
+
cookies[experiment.name] = alternative.name
|
|
49
|
+
response.set_cookie("split_override", { value: cookies.to_json, path: "/" })
|
|
50
|
+
|
|
51
|
+
redirect url("/")
|
|
36
52
|
end
|
|
37
53
|
|
|
38
|
-
post
|
|
54
|
+
post "/experiment" do
|
|
39
55
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
|
40
56
|
@alternative = Split::Alternative.new(params[:alternative], params[:experiment])
|
|
41
57
|
@experiment.winner = @alternative.name
|
|
42
|
-
redirect url(
|
|
58
|
+
redirect url("/")
|
|
43
59
|
end
|
|
44
60
|
|
|
45
|
-
post
|
|
61
|
+
post "/start" do
|
|
46
62
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
|
47
63
|
@experiment.start
|
|
48
|
-
redirect url(
|
|
64
|
+
redirect url("/")
|
|
49
65
|
end
|
|
50
66
|
|
|
51
|
-
post
|
|
67
|
+
post "/reset" do
|
|
52
68
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
|
53
69
|
@experiment.reset
|
|
54
|
-
redirect url(
|
|
70
|
+
redirect url("/")
|
|
55
71
|
end
|
|
56
72
|
|
|
57
|
-
post
|
|
73
|
+
post "/reopen" do
|
|
58
74
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
|
59
75
|
@experiment.reset_winner
|
|
60
|
-
redirect url(
|
|
76
|
+
redirect url("/")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
post "/update_cohorting" do
|
|
80
|
+
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
|
81
|
+
case params[:cohorting_action].downcase
|
|
82
|
+
when "enable"
|
|
83
|
+
@experiment.enable_cohorting
|
|
84
|
+
when "disable"
|
|
85
|
+
@experiment.disable_cohorting
|
|
86
|
+
end
|
|
87
|
+
redirect url("/")
|
|
61
88
|
end
|
|
62
89
|
|
|
63
|
-
delete
|
|
90
|
+
delete "/experiment" do
|
|
64
91
|
@experiment = Split::ExperimentCatalog.find(params[:experiment])
|
|
65
92
|
@experiment.delete
|
|
66
|
-
redirect url(
|
|
93
|
+
redirect url("/")
|
|
67
94
|
end
|
|
68
95
|
end
|
|
69
96
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "split/helper"
|
|
3
4
|
|
|
4
5
|
# Split's helper exposes all kinds of methods we don't want to
|
|
@@ -14,7 +15,6 @@ require "split/helper"
|
|
|
14
15
|
#
|
|
15
16
|
module Split
|
|
16
17
|
module EncapsulatedHelper
|
|
17
|
-
|
|
18
18
|
class ContextShim
|
|
19
19
|
include Split::Helper
|
|
20
20
|
public :ab_test, :ab_finished
|
|
@@ -23,20 +23,27 @@ module Split
|
|
|
23
23
|
@context = context
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
def params
|
|
27
|
+
request.params if request && request.respond_to?(:params)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def request
|
|
31
|
+
@context.request if @context.respond_to?(:request)
|
|
32
|
+
end
|
|
33
|
+
|
|
26
34
|
def ab_user
|
|
27
35
|
@ab_user ||= Split::User.new(@context)
|
|
28
36
|
end
|
|
29
37
|
end
|
|
30
38
|
|
|
31
|
-
def ab_test(*arguments
|
|
32
|
-
split_context_shim.ab_test(*arguments
|
|
39
|
+
def ab_test(*arguments, &block)
|
|
40
|
+
split_context_shim.ab_test(*arguments, &block)
|
|
33
41
|
end
|
|
34
42
|
|
|
35
43
|
private
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
44
|
+
# instantiate and memoize a context shim in case of multiple ab_test* calls
|
|
45
|
+
def split_context_shim
|
|
46
|
+
@split_context_shim ||= ContextShim.new(self)
|
|
47
|
+
end
|
|
41
48
|
end
|
|
42
49
|
end
|
data/lib/split/engine.rb
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Split
|
|
3
4
|
class Engine < ::Rails::Engine
|
|
4
5
|
initializer "split" do |app|
|
|
5
6
|
if Split.configuration.include_rails_helper
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
|
8
|
+
::ActionController::Base.send :include, Split::Helper
|
|
9
|
+
::ActionController::Base.helper Split::Helper
|
|
10
|
+
::ActionController::Base.send :include, Split::CombinedExperimentsHelper
|
|
11
|
+
::ActionController::Base.helper Split::CombinedExperimentsHelper
|
|
12
|
+
end
|
|
10
13
|
end
|
|
11
14
|
end
|
|
12
15
|
end
|