split 2.2.0 → 3.0.0
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/.travis.yml +1 -2
- data/Appraisals +1 -5
- data/CHANGELOG.md +18 -0
- data/README.md +9 -3
- data/gemfiles/5.0.gemfile +1 -1
- data/lib/split.rb +20 -19
- data/lib/split/algorithms/block_randomization.rb +22 -0
- data/lib/split/alternative.rb +29 -7
- data/lib/split/dashboard/helpers.rb +5 -1
- data/lib/split/dashboard/views/_experiment.erb +31 -1
- data/lib/split/encapsulated_helper.rb +2 -0
- data/lib/split/helper.rb +20 -0
- data/lib/split/persistence.rb +5 -3
- data/lib/split/persistence/dual_adapter.rb +3 -0
- data/lib/split/user.rb +2 -0
- data/lib/split/version.rb +2 -2
- data/lib/split/zscore.rb +1 -1
- data/spec/algorithms/block_randomization_spec.rb +32 -0
- data/spec/alternative_spec.rb +31 -0
- data/spec/dashboard_helpers_spec.rb +14 -0
- data/spec/experiment_spec.rb +0 -2
- metadata +5 -5
- data/gemfiles/4.1.gemfile +0 -9
- data/lib/split/algorithms.rb +0 -4
- data/lib/split/extensions.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0a3a695f8950186c2af7c9fd33b4201f785656a
|
4
|
+
data.tar.gz: f24adb5a893e0cac0cbf29c06ec86d9ae692c1a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41138ef7911a9843cbc3fa81f78b28184087dacfff4d291d820ae32faf343fce0a9552f4d0123d647751ceadf8c1f242d44bd7e476e50a69165a49b13e422e27
|
7
|
+
data.tar.gz: c8cfabeb28e143872e7c81f5d3e4dd28f2cb9c8d274f21a31481eaa52725eed06876ef6959ad44aacb84c47f064c84953d2edffd4767951774011037aff28e96
|
data/.travis.yml
CHANGED
data/Appraisals
CHANGED
@@ -1,12 +1,8 @@
|
|
1
|
-
appraise "4.1" do
|
2
|
-
gem "rails", "~> 4.1"
|
3
|
-
end
|
4
|
-
|
5
1
|
appraise "4.2" do
|
6
2
|
gem "rails", "~> 4.2"
|
7
3
|
end
|
8
4
|
|
9
5
|
appraise "5.0" do
|
10
6
|
gem "rails", "~> 5.0"
|
11
|
-
gem "sinatra",
|
7
|
+
gem "sinatra", git: "https://github.com/sinatra/sinatra"
|
12
8
|
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
|
+
## 3.0.0 (March 30th, 2017)
|
2
|
+
|
3
|
+
Features:
|
4
|
+
|
5
|
+
- added block randomization algorithm and specs (@hulleywood, #475)
|
6
|
+
- Add ab_record_extra_info to allow record extra info to alternative and display on dashboard. (@tranngocsam, #460)
|
7
|
+
|
8
|
+
Bugfixes:
|
9
|
+
|
10
|
+
- Avoid crashing on Ruby 2.4 for numeric strings (@flori, #470)
|
11
|
+
- Fix issue where redis isn't required (@tomciopp , #466)
|
12
|
+
|
13
|
+
Misc:
|
14
|
+
|
15
|
+
- Avoid variable_size_secure_compare private method (@eliotsykes, #465)
|
16
|
+
|
1
17
|
## 2.2.0 (November 11th, 2016)
|
2
18
|
|
19
|
+
**Backwards incompatible!** Redis keys are renamed. Please make sure all running tests are completed before you upgrade, as they will reset.
|
20
|
+
|
3
21
|
Features:
|
4
22
|
|
5
23
|
- Remove dependency on Redis::Namespace (@bschaeffer, #425)
|
data/README.md
CHANGED
@@ -439,9 +439,9 @@ You may want to password protect that page, you can do so with `Rack::Auth::Basi
|
|
439
439
|
Split::Dashboard.use Rack::Auth::Basic do |username, password|
|
440
440
|
# Protect against timing attacks:
|
441
441
|
# - Use & (do not use &&) so that it doesn't short circuit.
|
442
|
-
# - Use
|
443
|
-
ActiveSupport::SecurityUtils.
|
444
|
-
ActiveSupport::SecurityUtils.
|
442
|
+
# - Use digests to stop length information leaking
|
443
|
+
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(username), ::Digest::SHA256.hexdigest(ENV["SPLIT_USERNAME"])) &
|
444
|
+
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest(ENV["SPLIT_PASSWORD"]))
|
445
445
|
end
|
446
446
|
|
447
447
|
# Apps without activesupport
|
@@ -793,6 +793,12 @@ It is possible to specify static weights to favor certain alternatives.
|
|
793
793
|
This algorithm will automatically weight the alternatives based on their relative performance,
|
794
794
|
choosing the better-performing ones more often as trials are completed.
|
795
795
|
|
796
|
+
`Split::Algorithms::BlockRandomization` is an algorithm that ensures equal
|
797
|
+
participation across all alternatives. This algorithm will choose the alternative
|
798
|
+
with the fewest participants. In the event of multiple minimum participant alternatives
|
799
|
+
(i.e. starting a new "Block") the algorithm will choose a random alternative from
|
800
|
+
those minimum participant alternatives.
|
801
|
+
|
796
802
|
Users may also write their own algorithms. The default algorithm may be specified globally in the configuration file, or on a per experiment basis using the experiments hash of the configuration file.
|
797
803
|
|
798
804
|
To change the algorithm globally for all experiments, use the following in your initializer:
|
data/gemfiles/5.0.gemfile
CHANGED
data/lib/split.rb
CHANGED
@@ -1,24 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
alternative
|
4
|
-
configuration
|
5
|
-
exceptions
|
6
|
-
experiment
|
7
|
-
experiment_catalog
|
8
|
-
extensions
|
9
|
-
goals_collection
|
10
|
-
helper
|
11
|
-
metric
|
12
|
-
persistence
|
13
|
-
encapsulated_helper
|
14
|
-
redis_interface
|
15
|
-
trial
|
16
|
-
user
|
17
|
-
version
|
18
|
-
zscore].each do |f|
|
19
|
-
require "split/#{f}"
|
20
|
-
end
|
2
|
+
require 'redis'
|
21
3
|
|
4
|
+
require 'split/algorithms/block_randomization'
|
5
|
+
require 'split/algorithms/weighted_sample'
|
6
|
+
require 'split/algorithms/whiplash'
|
7
|
+
require 'split/alternative'
|
8
|
+
require 'split/configuration'
|
9
|
+
require 'split/encapsulated_helper'
|
10
|
+
require 'split/exceptions'
|
11
|
+
require 'split/experiment'
|
12
|
+
require 'split/experiment_catalog'
|
13
|
+
require 'split/extensions/string'
|
14
|
+
require 'split/goals_collection'
|
15
|
+
require 'split/helper'
|
16
|
+
require 'split/metric'
|
17
|
+
require 'split/persistence'
|
18
|
+
require 'split/redis_interface'
|
19
|
+
require 'split/trial'
|
20
|
+
require 'split/user'
|
21
|
+
require 'split/version'
|
22
|
+
require 'split/zscore'
|
22
23
|
require 'split/engine' if defined?(Rails)
|
23
24
|
|
24
25
|
module Split
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Selects alternative with minimum count of participants
|
2
|
+
# If all counts are even (i.e. all are minimum), samples from all possible alternatives
|
3
|
+
|
4
|
+
module Split
|
5
|
+
module Algorithms
|
6
|
+
module BlockRandomization
|
7
|
+
class << self
|
8
|
+
def choose_alternative(experiment)
|
9
|
+
minimum_participant_alternatives(experiment.alternatives).sample
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def minimum_participant_alternatives(alternatives)
|
15
|
+
alternatives_by_count = alternatives.group_by(&:participant_count)
|
16
|
+
min_group = alternatives_by_count.min_by { |k, v| k }
|
17
|
+
min_group.last
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/split/alternative.rb
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'split/zscore'
|
3
|
-
|
4
|
-
# TODO - take out require and implement using file paths?
|
5
|
-
|
6
2
|
module Split
|
7
3
|
class Alternative
|
8
4
|
attr_accessor :name
|
9
5
|
attr_accessor :experiment_name
|
10
6
|
attr_accessor :weight
|
11
|
-
|
12
|
-
include Zscore
|
7
|
+
attr_accessor :recorded_info
|
13
8
|
|
14
9
|
def initialize(name, experiment_name)
|
15
10
|
@experiment_name = experiment_name
|
@@ -127,10 +122,37 @@ module Split
|
|
127
122
|
z_score = Split::Zscore.calculate(p_a, n_a, p_c, n_c)
|
128
123
|
end
|
129
124
|
|
125
|
+
def extra_info
|
126
|
+
data = Split.redis.hget(key, 'recorded_info')
|
127
|
+
if data && data.length > 1
|
128
|
+
begin
|
129
|
+
JSON.parse(data)
|
130
|
+
rescue
|
131
|
+
{}
|
132
|
+
end
|
133
|
+
else
|
134
|
+
{}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def record_extra_info(k, value = 1)
|
139
|
+
@recorded_info = self.extra_info || {}
|
140
|
+
|
141
|
+
if value.kind_of?(Numeric)
|
142
|
+
@recorded_info[k] ||= 0
|
143
|
+
@recorded_info[k] += value
|
144
|
+
else
|
145
|
+
@recorded_info[k] = value
|
146
|
+
end
|
147
|
+
|
148
|
+
Split.redis.hset key, 'recorded_info', (@recorded_info || {}).to_json
|
149
|
+
end
|
150
|
+
|
130
151
|
def save
|
131
152
|
Split.redis.hsetnx key, 'participant_count', 0
|
132
153
|
Split.redis.hsetnx key, 'completed_count', 0
|
133
154
|
Split.redis.hsetnx key, 'p_winner', p_winner
|
155
|
+
Split.redis.hsetnx key, 'recorded_info', (@recorded_info || {}).to_json
|
134
156
|
end
|
135
157
|
|
136
158
|
def validate!
|
@@ -140,7 +162,7 @@ module Split
|
|
140
162
|
end
|
141
163
|
|
142
164
|
def reset
|
143
|
-
Split.redis.hmset key, 'participant_count', 0, 'completed_count', 0
|
165
|
+
Split.redis.hmset key, 'participant_count', 0, 'completed_count', 0, 'recorded_info', nil
|
144
166
|
unless goals.empty?
|
145
167
|
goals.each do |g|
|
146
168
|
field = "completed_count:#{g}"
|
@@ -18,7 +18,11 @@ module Split
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def round(number, precision = 2)
|
21
|
-
|
21
|
+
begin
|
22
|
+
BigDecimal.new(number.to_s)
|
23
|
+
rescue ArgumentError
|
24
|
+
BigDecimal.new(0)
|
25
|
+
end.round(precision).to_f
|
22
26
|
end
|
23
27
|
|
24
28
|
def confidence_level(z_score)
|
@@ -5,6 +5,25 @@
|
|
5
5
|
<% end %>
|
6
6
|
|
7
7
|
<% experiment.calc_winning_alternatives %>
|
8
|
+
<%
|
9
|
+
extra_columns = []
|
10
|
+
experiment.alternatives.each do |alternative|
|
11
|
+
extra_info = alternative.extra_info || {}
|
12
|
+
extra_columns += extra_info.keys
|
13
|
+
end
|
14
|
+
|
15
|
+
extra_columns.uniq!
|
16
|
+
summary_texts = {}
|
17
|
+
extra_columns.each do |column|
|
18
|
+
extra_infos = experiment.alternatives.map(&:extra_info).select{|extra_info| extra_info && extra_info[column] }
|
19
|
+
if extra_infos[0][column].kind_of?(Numeric)
|
20
|
+
summary_texts[column] = extra_infos.inject(0){|sum, extra_info| sum += extra_info[column]}
|
21
|
+
else
|
22
|
+
summary_texts[column] = "N/A"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
%>
|
26
|
+
|
8
27
|
|
9
28
|
<div class="<%= experiment_class %>" data-name="<%= experiment.name %>" data-complete="<%= experiment.has_winner? %>">
|
10
29
|
<div class="experiment-header">
|
@@ -32,6 +51,9 @@
|
|
32
51
|
<th>Non-finished</th>
|
33
52
|
<th>Completed</th>
|
34
53
|
<th>Conversion Rate</th>
|
54
|
+
<% extra_columns.each do |column| %>
|
55
|
+
<th><%= column %></th>
|
56
|
+
<% end %>
|
35
57
|
<th>
|
36
58
|
<form>
|
37
59
|
<select id="dropdown-<%=experiment.jstring(goal)%>" name="dropdown-<%=experiment.jstring(goal)%>">
|
@@ -82,6 +104,9 @@
|
|
82
104
|
});
|
83
105
|
});
|
84
106
|
</script>
|
107
|
+
<% extra_columns.each do |column| %>
|
108
|
+
<td><%= alternative.extra_info && alternative.extra_info[column] %></td>
|
109
|
+
<% end %>
|
85
110
|
<td>
|
86
111
|
<div class="box-<%=experiment.jstring(goal)%> confidence-<%=experiment.jstring(goal)%>">
|
87
112
|
<span title='z-score: <%= round(alternative.z_score(goal), 3) %>'><%= confidence_level(alternative.z_score(goal)) %></span>
|
@@ -90,7 +115,7 @@
|
|
90
115
|
<div class="box-<%=experiment.jstring(goal)%> probability-<%=experiment.jstring(goal)%>">
|
91
116
|
<span title="p_winner: <%= round(alternative.p_winner(goal), 3) %>"><%= number_to_percentage(round(alternative.p_winner(goal), 3)) %>%</span>
|
92
117
|
</div>
|
93
|
-
</td>
|
118
|
+
</td>
|
94
119
|
<td>
|
95
120
|
<% if experiment.has_winner? %>
|
96
121
|
<% if experiment.winner.name == alternative.name %>
|
@@ -118,6 +143,11 @@
|
|
118
143
|
<td><%= total_unfinished %></td>
|
119
144
|
<td><%= total_completed %></td>
|
120
145
|
<td>N/A</td>
|
146
|
+
<% extra_columns.each do |column| %>
|
147
|
+
<td>
|
148
|
+
<%= summary_texts[column] %>
|
149
|
+
</td>
|
150
|
+
<% end %>
|
121
151
|
<td>N/A</td>
|
122
152
|
<td>N/A</td>
|
123
153
|
</tr>
|
data/lib/split/helper.rb
CHANGED
@@ -76,6 +76,26 @@ module Split
|
|
76
76
|
Split.configuration.db_failover_on_db_error.call(e)
|
77
77
|
end
|
78
78
|
|
79
|
+
def ab_record_extra_info(metric_descriptor, key, value = 1)
|
80
|
+
return if exclude_visitor? || Split.configuration.disabled?
|
81
|
+
metric_descriptor, goals = normalize_metric(metric_descriptor)
|
82
|
+
experiments = Metric.possible_experiments(metric_descriptor)
|
83
|
+
|
84
|
+
if experiments.any?
|
85
|
+
experiments.each do |experiment|
|
86
|
+
alternative_name = ab_user[experiment.key]
|
87
|
+
|
88
|
+
if alternative_name
|
89
|
+
alternative = experiment.alternatives.find{|alt| alt.name == alternative_name}
|
90
|
+
alternative.record_extra_info(key, value) if alternative
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
rescue => e
|
95
|
+
raise unless Split.configuration.db_failover
|
96
|
+
Split.configuration.db_failover_on_db_error.call(e)
|
97
|
+
end
|
98
|
+
|
79
99
|
def override_present?(experiment_name)
|
80
100
|
override_alternative(experiment_name)
|
81
101
|
end
|
data/lib/split/persistence.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
%w[session_adapter cookie_adapter redis_adapter dual_adapter].each do |f|
|
3
|
-
require "split/persistence/#{f}"
|
4
|
-
end
|
5
2
|
|
6
3
|
module Split
|
7
4
|
module Persistence
|
5
|
+
require 'split/persistence/cookie_adapter'
|
6
|
+
require 'split/persistence/dual_adapter'
|
7
|
+
require 'split/persistence/redis_adapter'
|
8
|
+
require 'split/persistence/session_adapter'
|
9
|
+
|
8
10
|
ADAPTERS = {
|
9
11
|
:cookie => Split::Persistence::CookieAdapter,
|
10
12
|
:session => Split::Persistence::SessionAdapter
|
data/lib/split/user.rb
CHANGED
data/lib/split/version.rb
CHANGED
data/lib/split/zscore.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Split::Algorithms::BlockRandomization do
|
4
|
+
|
5
|
+
let(:experiment) { Split::Experiment.new 'experiment' }
|
6
|
+
let(:alternative_A) { Split::Alternative.new 'A', 'experiment' }
|
7
|
+
let(:alternative_B) { Split::Alternative.new 'B', 'experiment' }
|
8
|
+
let(:alternative_C) { Split::Alternative.new 'C', 'experiment' }
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
allow(experiment).to receive(:alternatives) { [alternative_A, alternative_B, alternative_C] }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return an alternative" do
|
15
|
+
expect(Split::Algorithms::BlockRandomization.choose_alternative(experiment).class).to eq(Split::Alternative)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should always return the minimum participation option" do
|
19
|
+
allow(alternative_A).to receive(:participant_count) { 1 }
|
20
|
+
allow(alternative_B).to receive(:participant_count) { 1 }
|
21
|
+
allow(alternative_C).to receive(:participant_count) { 0 }
|
22
|
+
expect(Split::Algorithms::BlockRandomization.choose_alternative(experiment)).to eq(alternative_C)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return one of the minimum participation options when multiple" do
|
26
|
+
allow(alternative_A).to receive(:participant_count) { 0 }
|
27
|
+
allow(alternative_B).to receive(:participant_count) { 0 }
|
28
|
+
allow(alternative_C).to receive(:participant_count) { 0 }
|
29
|
+
alternative = Split::Algorithms::BlockRandomization.choose_alternative(experiment)
|
30
|
+
expect([alternative_A, alternative_B, alternative_C].include?(alternative)).to be(true)
|
31
|
+
end
|
32
|
+
end
|
data/spec/alternative_spec.rb
CHANGED
@@ -274,4 +274,35 @@ describe Split::Alternative do
|
|
274
274
|
expect(control.z_score(goal2)).to eq('N/A')
|
275
275
|
end
|
276
276
|
end
|
277
|
+
|
278
|
+
describe "extra_info" do
|
279
|
+
it "reads saved value of recorded_info in redis" do
|
280
|
+
saved_recorded_info = {"key_1" => 1, "key_2" => "2"}
|
281
|
+
Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", 'recorded_info', saved_recorded_info.to_json
|
282
|
+
extra_info = alternative.extra_info
|
283
|
+
|
284
|
+
expect(extra_info).to eql(saved_recorded_info)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
describe "record_extra_info" do
|
289
|
+
it "saves key" do
|
290
|
+
alternative.record_extra_info("signup", 1)
|
291
|
+
expect(alternative.extra_info["signup"]).to eql(1)
|
292
|
+
end
|
293
|
+
|
294
|
+
it "adds value to saved key's value second argument is number" do
|
295
|
+
alternative.record_extra_info("signup", 1)
|
296
|
+
alternative.record_extra_info("signup", 2)
|
297
|
+
expect(alternative.extra_info["signup"]).to eql(3)
|
298
|
+
end
|
299
|
+
|
300
|
+
it "sets saved's key value to the second argument if it's a string" do
|
301
|
+
alternative.record_extra_info("signup", "Value 1")
|
302
|
+
expect(alternative.extra_info["signup"]).to eql("Value 1")
|
303
|
+
|
304
|
+
alternative.record_extra_info("signup", "Value 2")
|
305
|
+
expect(alternative.extra_info["signup"]).to eql("Value 2")
|
306
|
+
end
|
307
|
+
end
|
277
308
|
end
|
@@ -24,5 +24,19 @@ describe Split::DashboardHelpers do
|
|
24
24
|
expect(confidence_level(2.58)).to eq('99% confidence')
|
25
25
|
expect(confidence_level(3.00)).to eq('99% confidence')
|
26
26
|
end
|
27
|
+
|
28
|
+
describe '#round' do
|
29
|
+
it 'can round number strings' do
|
30
|
+
expect(round('3.1415')).to eq BigDecimal.new('3.14')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'can round number strings for precsion' do
|
34
|
+
expect(round('3.1415', 1)).to eq BigDecimal.new('3.1')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can handle invalid number strings' do
|
38
|
+
expect(round('N/A')).to be_zero
|
39
|
+
end
|
40
|
+
end
|
27
41
|
end
|
28
42
|
end
|
data/spec/experiment_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: split
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Nesbitt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -172,11 +172,10 @@ files:
|
|
172
172
|
- LICENSE
|
173
173
|
- README.md
|
174
174
|
- Rakefile
|
175
|
-
- gemfiles/4.1.gemfile
|
176
175
|
- gemfiles/4.2.gemfile
|
177
176
|
- gemfiles/5.0.gemfile
|
178
177
|
- lib/split.rb
|
179
|
-
- lib/split/algorithms.rb
|
178
|
+
- lib/split/algorithms/block_randomization.rb
|
180
179
|
- lib/split/algorithms/weighted_sample.rb
|
181
180
|
- lib/split/algorithms/whiplash.rb
|
182
181
|
- lib/split/alternative.rb
|
@@ -198,7 +197,6 @@ files:
|
|
198
197
|
- lib/split/exceptions.rb
|
199
198
|
- lib/split/experiment.rb
|
200
199
|
- lib/split/experiment_catalog.rb
|
201
|
-
- lib/split/extensions.rb
|
202
200
|
- lib/split/extensions/string.rb
|
203
201
|
- lib/split/goals_collection.rb
|
204
202
|
- lib/split/helper.rb
|
@@ -213,6 +211,7 @@ files:
|
|
213
211
|
- lib/split/user.rb
|
214
212
|
- lib/split/version.rb
|
215
213
|
- lib/split/zscore.rb
|
214
|
+
- spec/algorithms/block_randomization_spec.rb
|
216
215
|
- spec/algorithms/weighted_sample_spec.rb
|
217
216
|
- spec/algorithms/whiplash_spec.rb
|
218
217
|
- spec/alternative_spec.rb
|
@@ -262,6 +261,7 @@ signing_key:
|
|
262
261
|
specification_version: 4
|
263
262
|
summary: Rack based split testing framework
|
264
263
|
test_files:
|
264
|
+
- spec/algorithms/block_randomization_spec.rb
|
265
265
|
- spec/algorithms/weighted_sample_spec.rb
|
266
266
|
- spec/algorithms/whiplash_spec.rb
|
267
267
|
- spec/alternative_spec.rb
|
data/gemfiles/4.1.gemfile
DELETED
data/lib/split/algorithms.rb
DELETED
data/lib/split/extensions.rb
DELETED