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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1c97063d4d1ccf4c2cd5dfd2e83b98b3f55a934b9dfa9b5862ede3ee5b8c58c
|
4
|
+
data.tar.gz: 28e30003a6d2059baf91482f5ac9a97b0b7d2e37c61a425710cbf1adf21a4a83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 988f115f0f96870188221552cca45f6a7207f548500dbf2a5446abaa47ac7365571644a5a231443ca7616182b68c3139f8d62fdcf18d1593fca8898092a332e2
|
7
|
+
data.tar.gz: b6a668159d8b6fe9529bae5bc650f120b9de1bdf593bc89d0d949a2e07e8cfda7c96b4c12e5e4615696cf31b845215f95ce60b4abcb9ff3d7ef056669a4ae51d
|
data/.github/workflows/ci.yml
CHANGED
@@ -34,8 +34,8 @@ jobs:
|
|
34
34
|
- gemfile: 7.0.gemfile
|
35
35
|
ruby: '3.0'
|
36
36
|
|
37
|
-
|
38
|
-
|
37
|
+
- gemfile: 7.0.gemfile
|
38
|
+
ruby: '3.1'
|
39
39
|
|
40
40
|
|
41
41
|
runs-on: ubuntu-latest
|
@@ -51,7 +51,7 @@ jobs:
|
|
51
51
|
--health-retries 5
|
52
52
|
|
53
53
|
steps:
|
54
|
-
- uses: actions/checkout@
|
54
|
+
- uses: actions/checkout@v3
|
55
55
|
|
56
56
|
- uses: ruby/setup-ruby@v1
|
57
57
|
with:
|
@@ -69,3 +69,6 @@ jobs:
|
|
69
69
|
run: bundle exec rspec
|
70
70
|
env:
|
71
71
|
REDIS_URL: redis:6379
|
72
|
+
|
73
|
+
- name: Rubocop
|
74
|
+
run: bundle exec rubocop
|
data/.rubocop.yml
CHANGED
@@ -1,12 +1,9 @@
|
|
1
|
-
inherit_from: .rubocop_todo.yml
|
2
|
-
|
3
1
|
AllCops:
|
4
2
|
TargetRubyVersion: 2.5
|
5
3
|
DisabledByDefault: true
|
4
|
+
SuggestExtensions: false
|
6
5
|
Exclude:
|
7
|
-
- 'Appraisals'
|
8
6
|
- 'gemfiles/**/*'
|
9
|
-
- 'spec/**/*.rb'
|
10
7
|
|
11
8
|
Style/AndOr:
|
12
9
|
Enabled: true
|
@@ -114,7 +111,7 @@ Layout/SpaceInsideParens:
|
|
114
111
|
Enabled: true
|
115
112
|
|
116
113
|
Style/StringLiterals:
|
117
|
-
Enabled:
|
114
|
+
Enabled: true
|
118
115
|
EnforcedStyle: double_quotes
|
119
116
|
|
120
117
|
Layout/IndentationStyle:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# 4.0.2 (December 2nd, 2022)
|
2
|
+
|
3
|
+
Bugfixes:
|
4
|
+
- Stop crashing on non-hash json (@knarewski, #697)
|
5
|
+
- Handle when Rails is partially loaded as a Gem (@TSMMark, #687)
|
6
|
+
|
7
|
+
Features:
|
8
|
+
- Add support for redis-client, which does not automatically cast types to strings (@knarewski, #696)
|
9
|
+
- Add ability to initialize experiments (@robin-phung, #673)
|
10
|
+
|
11
|
+
Misc:
|
12
|
+
- Fix default branch name and gem metadata indentation (@ursm, #693)
|
13
|
+
- Update actions/checkout to v3 (@andrehjr, #683)
|
14
|
+
- Enforce double quotes (@andrehjr, #682)
|
15
|
+
- Fix Rubocop Style/* Offenses (@andrehjr, #681)
|
16
|
+
- Enable rubocop on Github Actions (@andrehjr, #680)
|
17
|
+
- Fix all Layout issues on the project (@andrehjr, #679)
|
18
|
+
- Fix Style/HashSyntax offenses (@andrehjr, #678)
|
19
|
+
- Remove usage of deprecated implicit block expectation from specs (@andrehjr, #677)
|
20
|
+
- Remove appraisals configuration (@andrehjr, #676)
|
21
|
+
- Add Ruby 3.1 (@andrehjr, #675)
|
22
|
+
- Encapsulate Split::Algorithms at our own module to avoid explicit calling rubystats everywhere (@andrehjr, #674)
|
23
|
+
|
1
24
|
## 4.0.1 (December 30th, 2021)
|
2
25
|
|
3
26
|
Bugfixes:
|
data/CONTRIBUTING.md
CHANGED
@@ -25,7 +25,7 @@ Want to contribute to Split? That's great! Here are a couple of guidelines that
|
|
25
25
|
|
26
26
|
## Setup instructions
|
27
27
|
|
28
|
-
You can find in-depth instructions to install in our [README](https://github.com/splitrb/split/blob/
|
28
|
+
You can find in-depth instructions to install in our [README](https://github.com/splitrb/split/blob/main/README.md).
|
29
29
|
|
30
30
|
*Note*: Split requires Ruby 1.9.2 or higher.
|
31
31
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -19,11 +19,13 @@ Split is designed to be hacker friendly, allowing for maximum customisation and
|
|
19
19
|
|
20
20
|
### Requirements
|
21
21
|
|
22
|
-
Split
|
22
|
+
Split v4.0+ is currently tested with Ruby >= 2.5 and Rails >= 5.2.
|
23
|
+
|
24
|
+
If your project requires compatibility with Ruby 2.4.x or older Rails versions. You can try v3.0 or v0.8.0(for Ruby 1.9.3)
|
23
25
|
|
24
26
|
Split uses Redis as a datastore.
|
25
27
|
|
26
|
-
Split only supports Redis
|
28
|
+
Split only supports Redis 4.0 or greater.
|
27
29
|
|
28
30
|
If you're on OS X, Homebrew is the simplest way to install Redis:
|
29
31
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require 'appraisal'
|
4
|
+
require "bundler/gem_tasks"
|
5
|
+
require "rspec/core/rake_task"
|
7
6
|
|
8
|
-
RSpec::Core::RakeTask.new(
|
7
|
+
RSpec::Core::RakeTask.new("spec")
|
9
8
|
|
10
|
-
task :
|
9
|
+
task default: :spec
|
data/gemfiles/5.2.gemfile
CHANGED
data/gemfiles/6.0.gemfile
CHANGED
data/gemfiles/6.1.gemfile
CHANGED
data/gemfiles/7.0.gemfile
CHANGED
@@ -12,12 +12,11 @@ module Split
|
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
15
|
+
def minimum_participant_alternatives(alternatives)
|
16
|
+
alternatives_by_count = alternatives.group_by(&:participant_count)
|
17
|
+
min_group = alternatives_by_count.min_by { |k, v| k }
|
18
|
+
min_group.last
|
19
|
+
end
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
# A multi-armed bandit implementation inspired by
|
4
4
|
# @aaronsw and victorykit/whiplash
|
5
|
-
require 'rubystats'
|
6
5
|
|
7
6
|
module Split
|
8
7
|
module Algorithms
|
@@ -13,26 +12,25 @@ module Split
|
|
13
12
|
end
|
14
13
|
|
15
14
|
private
|
15
|
+
def arm_guess(participants, completions)
|
16
|
+
a = [participants, 0].max
|
17
|
+
b = [participants-completions, 0].max
|
18
|
+
Split::Algorithms.beta_distribution_rng(a + fairness_constant, b + fairness_constant)
|
19
|
+
end
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
alternatives.each do |alternative|
|
26
|
-
guesses[alternative.name] = arm_guess(alternative.participant_count, alternative.all_completed_count)
|
21
|
+
def best_guess(alternatives)
|
22
|
+
guesses = {}
|
23
|
+
alternatives.each do |alternative|
|
24
|
+
guesses[alternative.name] = arm_guess(alternative.participant_count, alternative.all_completed_count)
|
25
|
+
end
|
26
|
+
gmax = guesses.values.max
|
27
|
+
best = guesses.keys.select { |name| guesses[name] == gmax }
|
28
|
+
best.sample
|
27
29
|
end
|
28
|
-
gmax = guesses.values.max
|
29
|
-
best = guesses.keys.select { |name| guesses[name] == gmax }
|
30
|
-
best.sample
|
31
|
-
end
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
def fairness_constant
|
32
|
+
7
|
33
|
+
end
|
36
34
|
end
|
37
35
|
end
|
38
36
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "matrix"
|
5
|
+
rescue LoadError => error
|
6
|
+
if error.message.match?(/matrix/)
|
7
|
+
$stderr.puts "You don't have matrix installed in your application. Please add it to your Gemfile and run bundle install"
|
8
|
+
raise
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require "rubystats"
|
13
|
+
|
14
|
+
module Split
|
15
|
+
module Algorithms
|
16
|
+
class << self
|
17
|
+
def beta_distribution_rng(a, b)
|
18
|
+
Rubystats::BetaDistribution.new(a, b).rng
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/split/alternative.rb
CHANGED
@@ -38,11 +38,11 @@ module Split
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def participant_count
|
41
|
-
Split.redis.hget(key,
|
41
|
+
Split.redis.hget(key, "participant_count").to_i
|
42
42
|
end
|
43
43
|
|
44
44
|
def participant_count=(count)
|
45
|
-
Split.redis.hset(key,
|
45
|
+
Split.redis.hset(key, "participant_count", count.to_i)
|
46
46
|
end
|
47
47
|
|
48
48
|
def completed_count(goal = nil)
|
@@ -67,13 +67,13 @@ module Split
|
|
67
67
|
def set_field(goal)
|
68
68
|
field = "completed_count"
|
69
69
|
field += ":" + goal unless goal.nil?
|
70
|
-
|
70
|
+
field
|
71
71
|
end
|
72
72
|
|
73
73
|
def set_prob_field(goal)
|
74
74
|
field = "p_winner"
|
75
75
|
field += ":" + goal unless goal.nil?
|
76
|
-
|
76
|
+
field
|
77
77
|
end
|
78
78
|
|
79
79
|
def set_completed_count(count, goal = nil)
|
@@ -82,7 +82,7 @@ module Split
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def increment_participation
|
85
|
-
Split.redis.hincrby key,
|
85
|
+
Split.redis.hincrby key, "participant_count", 1
|
86
86
|
end
|
87
87
|
|
88
88
|
def increment_completion(goal = nil)
|
@@ -112,7 +112,7 @@ module Split
|
|
112
112
|
control = experiment.control
|
113
113
|
alternative = self
|
114
114
|
|
115
|
-
return
|
115
|
+
return "N/A" if control.name == alternative.name
|
116
116
|
|
117
117
|
p_a = alternative.conversion_rate(goal)
|
118
118
|
p_c = control.conversion_rate(goal)
|
@@ -121,13 +121,13 @@ module Split
|
|
121
121
|
n_c = control.participant_count
|
122
122
|
|
123
123
|
# can't calculate zscore for P(x) > 1
|
124
|
-
return
|
124
|
+
return "N/A" if p_a > 1 || p_c > 1
|
125
125
|
|
126
126
|
Split::Zscore.calculate(p_a, n_a, p_c, n_c)
|
127
127
|
end
|
128
128
|
|
129
129
|
def extra_info
|
130
|
-
data = Split.redis.hget(key,
|
130
|
+
data = Split.redis.hget(key, "recorded_info")
|
131
131
|
if data && data.length > 1
|
132
132
|
begin
|
133
133
|
JSON.parse(data)
|
@@ -149,24 +149,24 @@ module Split
|
|
149
149
|
@recorded_info[k] = value
|
150
150
|
end
|
151
151
|
|
152
|
-
Split.redis.hset key,
|
152
|
+
Split.redis.hset key, "recorded_info", (@recorded_info || {}).to_json
|
153
153
|
end
|
154
154
|
|
155
155
|
def save
|
156
|
-
Split.redis.hsetnx key,
|
157
|
-
Split.redis.hsetnx key,
|
158
|
-
Split.redis.hsetnx key,
|
159
|
-
Split.redis.hsetnx key,
|
156
|
+
Split.redis.hsetnx key, "participant_count", 0
|
157
|
+
Split.redis.hsetnx key, "completed_count", 0
|
158
|
+
Split.redis.hsetnx key, "p_winner", p_winner
|
159
|
+
Split.redis.hsetnx key, "recorded_info", (@recorded_info || {}).to_json
|
160
160
|
end
|
161
161
|
|
162
162
|
def validate!
|
163
163
|
unless String === @name || hash_with_correct_values?(@name)
|
164
|
-
raise ArgumentError,
|
164
|
+
raise ArgumentError, "Alternative must be a string"
|
165
165
|
end
|
166
166
|
end
|
167
167
|
|
168
168
|
def reset
|
169
|
-
Split.redis.hmset key,
|
169
|
+
Split.redis.hmset key, "participant_count", 0, "completed_count", 0, "recorded_info", ""
|
170
170
|
unless goals.empty?
|
171
171
|
goals.each do |g|
|
172
172
|
field = "completed_count:#{g}"
|
@@ -180,13 +180,12 @@ module Split
|
|
180
180
|
end
|
181
181
|
|
182
182
|
private
|
183
|
+
def hash_with_correct_values?(name)
|
184
|
+
Hash === name && String === name.keys.first && Float(name.values.first) rescue false
|
185
|
+
end
|
183
186
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
def key
|
189
|
-
"#{experiment_name}:#{name}"
|
190
|
-
end
|
187
|
+
def key
|
188
|
+
"#{experiment_name}:#{name}"
|
189
|
+
end
|
191
190
|
end
|
192
191
|
end
|
data/lib/split/cache.rb
CHANGED
@@ -29,10 +29,10 @@ module Split
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def find_combined_experiment(metric_descriptor)
|
32
|
-
raise(Split::InvalidExperimentsFormatError,
|
33
|
-
raise(Split::InvalidExperimentsFormatError,
|
34
|
-
raise(Split::InvalidExperimentsFormatError,
|
35
|
-
Split
|
32
|
+
raise(Split::InvalidExperimentsFormatError, "Invalid descriptor class (String or Symbol required)") unless metric_descriptor.class == String || metric_descriptor.class == Symbol
|
33
|
+
raise(Split::InvalidExperimentsFormatError, "Enable configuration") unless Split.configuration.enabled
|
34
|
+
raise(Split::InvalidExperimentsFormatError, "Enable `allow_multiple_experiments`") unless Split.configuration.allow_multiple_experiments
|
35
|
+
Split.configuration.experiments[metric_descriptor.to_sym]
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
data/lib/split/configuration.rb
CHANGED
@@ -39,83 +39,83 @@ module Split
|
|
39
39
|
def bots
|
40
40
|
@bots ||= {
|
41
41
|
# Indexers
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
42
|
+
"AdsBot-Google" => "Google Adwords",
|
43
|
+
"Baidu" => "Chinese search engine",
|
44
|
+
"Baiduspider" => "Chinese search engine",
|
45
|
+
"bingbot" => "Microsoft bing bot",
|
46
|
+
"Butterfly" => "Topsy Labs",
|
47
|
+
"Gigabot" => "Gigabot spider",
|
48
|
+
"Googlebot" => "Google spider",
|
49
|
+
"MJ12bot" => "Majestic-12 spider",
|
50
|
+
"msnbot" => "Microsoft bot",
|
51
|
+
"rogerbot" => "SeoMoz spider",
|
52
|
+
"PaperLiBot" => "PaperLi is another content curation service",
|
53
|
+
"Slurp" => "Yahoo spider",
|
54
|
+
"Sogou" => "Chinese search engine",
|
55
|
+
"spider" => "generic web spider",
|
56
|
+
"UnwindFetchor" => "Gnip crawler",
|
57
|
+
"WordPress" => "WordPress spider",
|
58
|
+
"YandexAccessibilityBot" => "Yandex accessibility spider",
|
59
|
+
"YandexBot" => "Yandex spider",
|
60
|
+
"YandexMobileBot" => "Yandex mobile spider",
|
61
|
+
"ZIBB" => "ZIBB spider",
|
62
62
|
|
63
63
|
# HTTP libraries
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
64
|
+
"Apache-HttpClient" => "Java http library",
|
65
|
+
"AppEngine-Google" => "Google App Engine",
|
66
|
+
"curl" => "curl unix CLI http client",
|
67
|
+
"ColdFusion" => "ColdFusion http library",
|
68
|
+
"EventMachine HttpClient" => "Ruby http library",
|
69
|
+
"Go http package" => "Go http library",
|
70
|
+
"Go-http-client" => "Go http library",
|
71
|
+
"Java" => "Generic Java http library",
|
72
|
+
"libwww-perl" => "Perl client-server library loved by script kids",
|
73
|
+
"lwp-trivial" => "Another Perl library loved by script kids",
|
74
|
+
"Python-urllib" => "Python http library",
|
75
|
+
"PycURL" => "Python http library",
|
76
|
+
"Test Certificate Info" => "C http library?",
|
77
|
+
"Typhoeus" => "Ruby http library",
|
78
|
+
"Wget" => "wget unix CLI http client",
|
79
79
|
|
80
80
|
# URL expanders / previewers
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
81
|
+
"awe.sm" => "Awe.sm URL expander",
|
82
|
+
"bitlybot" => "bit.ly bot",
|
83
|
+
"bot@linkfluence.net" => "Linkfluence bot",
|
84
|
+
"facebookexternalhit" => "facebook bot",
|
85
|
+
"Facebot" => "Facebook crawler",
|
86
|
+
"Feedfetcher-Google" => "Google Feedfetcher",
|
87
|
+
"https://developers.google.com/+/web/snippet" => "Google+ Snippet Fetcher",
|
88
|
+
"LinkedInBot" => "LinkedIn bot",
|
89
|
+
"LongURL" => "URL expander service",
|
90
|
+
"NING" => "NING - Yet Another Twitter Swarmer",
|
91
|
+
"Pinterestbot" => "Pinterest Bot",
|
92
|
+
"redditbot" => "Reddit Bot",
|
93
|
+
"ShortLinkTranslate" => "Link shortener",
|
94
|
+
"Slackbot" => "Slackbot link expander",
|
95
|
+
"TweetmemeBot" => "TweetMeMe Crawler",
|
96
|
+
"Twitterbot" => "Twitter URL expander",
|
97
|
+
"UnwindFetch" => "Gnip URL expander",
|
98
|
+
"vkShare" => "VKontake Sharer",
|
99
99
|
|
100
100
|
# Uptime monitoring
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
101
|
+
"check_http" => "Nagios monitor",
|
102
|
+
"GoogleStackdriverMonitoring" => "Google Cloud monitor",
|
103
|
+
"NewRelicPinger" => "NewRelic monitor",
|
104
|
+
"Panopta" => "Monitoring service",
|
105
|
+
"Pingdom" => "Pingdom monitoring",
|
106
|
+
"SiteUptime" => "Site monitoring services",
|
107
|
+
"UptimeRobot" => "Monitoring service",
|
108
108
|
|
109
109
|
# ???
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
110
|
+
"DigitalPersona Fingerprint Software" => "HP Fingerprint scanner",
|
111
|
+
"ShowyouBot" => "Showyou iOS app spider",
|
112
|
+
"ZyBorg" => "Zyborg? Hmmm....",
|
113
|
+
"ELB-HealthChecker" => "ELB Health Check"
|
114
114
|
}
|
115
115
|
end
|
116
116
|
|
117
|
-
def experiments=
|
118
|
-
raise InvalidExperimentsFormatError.new(
|
117
|
+
def experiments=(experiments)
|
118
|
+
raise InvalidExperimentsFormatError.new("Experiments must be a Hash") unless experiments.respond_to?(:keys)
|
119
119
|
@experiments = experiments
|
120
120
|
end
|
121
121
|
|
@@ -157,8 +157,8 @@ module Split
|
|
157
157
|
|
158
158
|
@experiments.each do |experiment_name, settings|
|
159
159
|
alternatives = if (alts = value_for(settings, :alternatives))
|
160
|
-
|
161
|
-
|
160
|
+
normalize_alternatives(alts)
|
161
|
+
end
|
162
162
|
|
163
163
|
experiment_data = {
|
164
164
|
alternatives: alternatives,
|
@@ -213,14 +213,14 @@ module Split
|
|
213
213
|
|
214
214
|
def initialize
|
215
215
|
@ignore_ip_addresses = []
|
216
|
-
@ignore_filter = proc{ |request| is_robot? || is_ignored_ip_address? }
|
216
|
+
@ignore_filter = proc { |request| is_robot? || is_ignored_ip_address? }
|
217
217
|
@db_failover = false
|
218
|
-
@db_failover_on_db_error = proc{|error|} # e.g. use Rails logger here
|
219
|
-
@on_experiment_reset = proc{|experiment|}
|
220
|
-
@on_experiment_delete = proc{|experiment|}
|
221
|
-
@on_before_experiment_reset = proc{|experiment|}
|
222
|
-
@on_before_experiment_delete = proc{|experiment|}
|
223
|
-
@on_experiment_winner_choose = proc{|experiment|}
|
218
|
+
@db_failover_on_db_error = proc { |error| } # e.g. use Rails logger here
|
219
|
+
@on_experiment_reset = proc { |experiment| }
|
220
|
+
@on_experiment_delete = proc { |experiment| }
|
221
|
+
@on_before_experiment_reset = proc { |experiment| }
|
222
|
+
@on_before_experiment_delete = proc { |experiment| }
|
223
|
+
@on_experiment_winner_choose = proc { |experiment| }
|
224
224
|
@db_failover_allow_parameter_override = false
|
225
225
|
@allow_multiple_experiments = false
|
226
226
|
@enabled = true
|
@@ -232,20 +232,19 @@ module Split
|
|
232
232
|
@include_rails_helper = true
|
233
233
|
@beta_probability_simulations = 10000
|
234
234
|
@winning_alternative_recalculation_interval = 60 * 60 * 24 # 1 day
|
235
|
-
@redis = ENV.fetch(ENV.fetch(
|
235
|
+
@redis = ENV.fetch(ENV.fetch("REDIS_PROVIDER", "REDIS_URL"), "redis://localhost:6379")
|
236
236
|
@dashboard_pagination_default_per_page = 10
|
237
237
|
end
|
238
238
|
|
239
239
|
private
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
240
|
+
def value_for(hash, key)
|
241
|
+
if hash.kind_of?(Hash)
|
242
|
+
hash.has_key?(key.to_s) ? hash[key.to_s] : hash[key.to_sym]
|
243
|
+
end
|
244
244
|
end
|
245
|
-
end
|
246
245
|
|
247
|
-
|
248
|
-
|
249
|
-
|
246
|
+
def escaped_bots
|
247
|
+
bots.map { |key, _| Regexp.escape(key) }
|
248
|
+
end
|
250
249
|
end
|
251
250
|
end
|