split 4.0.1 → 4.0.3
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/.github/workflows/ci.yml +8 -3
- data/.rubocop.yml +2 -5
- data/CHANGELOG.md +38 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +1 -1
- data/README.md +11 -3
- 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 +1 -3
- data/lib/split/algorithms/block_randomization.rb +5 -6
- data/lib/split/algorithms/whiplash.rb +16 -18
- data/lib/split/algorithms.rb +14 -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/_experiment.erb +2 -1
- 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 +93 -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 +20 -20
- 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 +3 -4
- data/lib/split/persistence/session_adapter.rb +0 -2
- data/lib/split/persistence.rb +4 -4
- data/lib/split/redis_interface.rb +7 -1
- 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 +79 -35
- data/spec/encapsulated_helper_spec.rb +12 -14
- data/spec/experiment_catalog_spec.rb +14 -13
- data/spec/experiment_spec.rb +132 -123
- data/spec/goals_collection_spec.rb +17 -15
- data/spec/helper_spec.rb +415 -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 +28 -29
- data/spec/persistence/session_adapter_spec.rb +2 -3
- data/spec/persistence_spec.rb +1 -2
- data/spec/redis_interface_spec.rb +26 -14
- data/spec/spec_helper.rb +16 -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 +21 -20
- metadata +25 -14
- data/.rubocop_todo.yml +0 -226
- data/Appraisals +0 -19
- data/gemfiles/5.0.gemfile +0 -9
- data/gemfiles/5.1.gemfile +0 -9
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
|
@@ -272,7 +270,16 @@ module Split
|
|
272
270
|
set_alternatives_and_options(options)
|
273
271
|
end
|
274
272
|
|
273
|
+
def can_calculate_winning_alternatives?
|
274
|
+
self.alternatives.all? do |alternative|
|
275
|
+
alternative.participant_count >= 0 &&
|
276
|
+
(alternative.participant_count >= alternative.completed_count)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
275
280
|
def calc_winning_alternatives
|
281
|
+
return unless can_calculate_winning_alternatives?
|
282
|
+
|
276
283
|
# Cache the winning alternatives so we recalculate them once per the specified interval.
|
277
284
|
intervals_since_epoch =
|
278
285
|
Time.now.utc.to_i / Split.configuration.winning_alternative_recalculation_interval
|
@@ -328,11 +335,11 @@ module Split
|
|
328
335
|
winning_counts.each do |alternative, wins|
|
329
336
|
alternative_probabilities[alternative] = wins / number_of_simulations.to_f
|
330
337
|
end
|
331
|
-
|
338
|
+
alternative_probabilities
|
332
339
|
end
|
333
340
|
|
334
341
|
def count_simulated_wins(winning_alternatives)
|
335
|
-
|
342
|
+
# initialize a hash to keep track of winning alternative in simulations
|
336
343
|
winning_counts = {}
|
337
344
|
alternatives.each do |alternative|
|
338
345
|
winning_counts[alternative] = 0
|
@@ -341,7 +348,7 @@ module Split
|
|
341
348
|
winning_alternatives.each do |alternative|
|
342
349
|
winning_counts[alternative] += 1
|
343
350
|
end
|
344
|
-
|
351
|
+
winning_counts
|
345
352
|
end
|
346
353
|
|
347
354
|
def find_simulated_winner(simulated_cr_hash)
|
@@ -353,7 +360,7 @@ module Split
|
|
353
360
|
end
|
354
361
|
end
|
355
362
|
winner = winning_pair[0]
|
356
|
-
|
363
|
+
winner
|
357
364
|
end
|
358
365
|
|
359
366
|
def calc_simulated_conversion_rates(beta_params)
|
@@ -363,11 +370,11 @@ module Split
|
|
363
370
|
beta_params.each do |alternative, params|
|
364
371
|
alpha = params[0]
|
365
372
|
beta = params[1]
|
366
|
-
simulated_conversion_rate =
|
373
|
+
simulated_conversion_rate = Split::Algorithms.beta_distribution_rng(alpha, beta)
|
367
374
|
simulated_cr_hash[alternative] = simulated_conversion_rate
|
368
375
|
end
|
369
376
|
|
370
|
-
|
377
|
+
simulated_cr_hash
|
371
378
|
end
|
372
379
|
|
373
380
|
def calc_beta_params(goal = nil)
|
@@ -381,7 +388,7 @@ module Split
|
|
381
388
|
|
382
389
|
beta_params[alternative] = params
|
383
390
|
end
|
384
|
-
|
391
|
+
beta_params
|
385
392
|
end
|
386
393
|
|
387
394
|
def calc_time=(time)
|
@@ -394,11 +401,11 @@ module Split
|
|
394
401
|
|
395
402
|
def jstring(goal = nil)
|
396
403
|
js_id = if goal.nil?
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
js_id.gsub(
|
404
|
+
name
|
405
|
+
else
|
406
|
+
name + "-" + goal
|
407
|
+
end
|
408
|
+
js_id.gsub("/", "--")
|
402
409
|
end
|
403
410
|
|
404
411
|
def cohorting_disabled?
|
@@ -410,95 +417,93 @@ module Split
|
|
410
417
|
|
411
418
|
def disable_cohorting
|
412
419
|
@cohorting_disabled = true
|
413
|
-
redis.hset(experiment_config_key, :cohorting, true)
|
420
|
+
redis.hset(experiment_config_key, :cohorting, true.to_s)
|
414
421
|
end
|
415
422
|
|
416
423
|
def enable_cohorting
|
417
424
|
@cohorting_disabled = false
|
418
|
-
redis.hset(experiment_config_key, :cohorting, false)
|
425
|
+
redis.hset(experiment_config_key, :cohorting, false.to_s)
|
419
426
|
end
|
420
427
|
|
421
428
|
protected
|
429
|
+
def experiment_config_key
|
430
|
+
"experiment_configurations/#{@name}"
|
431
|
+
end
|
422
432
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
def load_metadata_from_configuration
|
428
|
-
Split.configuration.experiment_for(@name)[:metadata]
|
429
|
-
end
|
433
|
+
def load_metadata_from_configuration
|
434
|
+
Split.configuration.experiment_for(@name)[:metadata]
|
435
|
+
end
|
430
436
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
437
|
+
def load_metadata_from_redis
|
438
|
+
meta = redis.get(metadata_key)
|
439
|
+
JSON.parse(meta) unless meta.nil?
|
440
|
+
end
|
435
441
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
442
|
+
def load_alternatives_from_configuration
|
443
|
+
alts = Split.configuration.experiment_for(@name)[:alternatives]
|
444
|
+
raise ArgumentError, "Experiment configuration is missing :alternatives array" unless alts
|
445
|
+
if alts.is_a?(Hash)
|
446
|
+
alts.keys
|
447
|
+
else
|
448
|
+
alts.flatten
|
449
|
+
end
|
443
450
|
end
|
444
|
-
end
|
445
451
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
452
|
+
def load_alternatives_from_redis
|
453
|
+
alternatives = redis.lrange(@name, 0, -1)
|
454
|
+
alternatives.map do |alt|
|
455
|
+
alt = begin
|
456
|
+
JSON.parse(alt)
|
457
|
+
rescue
|
458
|
+
alt
|
459
|
+
end
|
460
|
+
Split::Alternative.new(alt, @name)
|
461
|
+
end
|
455
462
|
end
|
456
|
-
end
|
457
463
|
|
458
464
|
private
|
465
|
+
def redis
|
466
|
+
Split.redis
|
467
|
+
end
|
459
468
|
|
460
|
-
|
461
|
-
|
462
|
-
|
469
|
+
def redis_interface
|
470
|
+
RedisInterface.new
|
471
|
+
end
|
463
472
|
|
464
|
-
|
465
|
-
|
466
|
-
|
473
|
+
def persist_experiment_configuration
|
474
|
+
redis_interface.add_to_set(:experiments, name)
|
475
|
+
redis_interface.persist_list(name, @alternatives.map { |alt| { alt.name => alt.weight }.to_json })
|
476
|
+
goals_collection.save
|
467
477
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
478
|
+
if @metadata
|
479
|
+
redis.set(metadata_key, @metadata.to_json)
|
480
|
+
else
|
481
|
+
delete_metadata
|
482
|
+
end
|
483
|
+
end
|
472
484
|
|
473
|
-
|
474
|
-
|
475
|
-
|
485
|
+
def remove_experiment_configuration
|
486
|
+
@alternatives.each(&:delete)
|
487
|
+
goals_collection.delete
|
476
488
|
delete_metadata
|
489
|
+
redis.del(@name)
|
477
490
|
end
|
478
|
-
end
|
479
491
|
|
480
|
-
|
481
|
-
|
482
|
-
goals_collection.delete
|
483
|
-
delete_metadata
|
484
|
-
redis.del(@name)
|
485
|
-
end
|
486
|
-
|
487
|
-
def experiment_configuration_has_changed?
|
488
|
-
existing_experiment = Experiment.find(@name)
|
492
|
+
def experiment_configuration_has_changed?
|
493
|
+
existing_experiment = Experiment.find(@name)
|
489
494
|
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
495
|
+
existing_experiment.alternatives.map(&:to_s) != @alternatives.map(&:to_s) ||
|
496
|
+
existing_experiment.goals != @goals ||
|
497
|
+
existing_experiment.metadata != @metadata
|
498
|
+
end
|
494
499
|
|
495
|
-
|
496
|
-
|
497
|
-
|
500
|
+
def goals_collection
|
501
|
+
Split::GoalsCollection.new(@name, @goals)
|
502
|
+
end
|
498
503
|
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
504
|
+
def remove_experiment_cohorting
|
505
|
+
@cohorting_disabled = false
|
506
|
+
redis.hdel(experiment_config_key, :cohorting)
|
507
|
+
end
|
503
508
|
end
|
504
509
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Split
|
3
4
|
class ExperimentCatalog
|
4
5
|
# Return all experiments
|
5
6
|
def self.all
|
6
7
|
# Call compact to prevent nil experiments from being returned -- seems to happen during gem upgrades
|
7
|
-
Split.redis.smembers(:experiments).map {|e| find(e)}.compact
|
8
|
+
Split.redis.smembers(:experiments).map { |e| find(e) }.compact
|
8
9
|
end
|
9
10
|
|
10
11
|
# Return experiments without a winner (considered "active") first
|
11
12
|
def self.all_active_first
|
12
|
-
all.partition{|e| not e.winner}.map{|es| es.sort_by(&:name)}.flatten
|
13
|
+
all.partition { |e| not e.winner }.map { |es| es.sort_by(&:name) }.flatten
|
13
14
|
end
|
14
15
|
|
15
16
|
def self.find(name)
|
@@ -19,14 +20,14 @@ module Split
|
|
19
20
|
def self.find_or_initialize(metric_descriptor, control = nil, *alternatives)
|
20
21
|
# Check if array is passed to ab_test
|
21
22
|
# e.g. ab_test('name', ['Alt 1', 'Alt 2', 'Alt 3'])
|
22
|
-
if control.is_a?
|
23
|
+
if control.is_a?(Array) && alternatives.length.zero?
|
23
24
|
control, alternatives = control.first, control[1..-1]
|
24
25
|
end
|
25
26
|
|
26
27
|
experiment_name_with_version, goals = normalize_experiment(metric_descriptor)
|
27
|
-
experiment_name = experiment_name_with_version.to_s.split(
|
28
|
+
experiment_name = experiment_name_with_version.to_s.split(":")[0]
|
28
29
|
Split::Experiment.new(experiment_name,
|
29
|
-
:
|
30
|
+
alternatives: [control].compact + alternatives, goals: goals)
|
30
31
|
end
|
31
32
|
|
32
33
|
def self.find_or_create(metric_descriptor, control = nil, *alternatives)
|
@@ -4,7 +4,7 @@ class String
|
|
4
4
|
# Constatntize is often provided by ActiveSupport, but ActiveSupport is not a dependency of Split.
|
5
5
|
unless method_defined?(:constantize)
|
6
6
|
def constantize
|
7
|
-
names = self.split(
|
7
|
+
names = self.split("::")
|
8
8
|
names.shift if names.empty? || names.first.empty?
|
9
9
|
|
10
10
|
constant = Object
|
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|
module Split
|
4
4
|
class GoalsCollection
|
5
|
-
|
6
|
-
def initialize(experiment_name, goals=nil)
|
5
|
+
def initialize(experiment_name, goals = nil)
|
7
6
|
@experiment_name = experiment_name
|
8
7
|
@goals = goals
|
9
8
|
end
|
@@ -15,10 +14,10 @@ module Split
|
|
15
14
|
def load_from_configuration
|
16
15
|
goals = Split.configuration.experiment_for(@experiment_name)[:goals]
|
17
16
|
|
18
|
-
if goals
|
19
|
-
goals = []
|
20
|
-
else
|
17
|
+
if goals
|
21
18
|
goals.flatten
|
19
|
+
else
|
20
|
+
[]
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
@@ -29,7 +28,7 @@ module Split
|
|
29
28
|
|
30
29
|
def validate!
|
31
30
|
unless @goals.nil? || @goals.kind_of?(Array)
|
32
|
-
raise ArgumentError,
|
31
|
+
raise ArgumentError, "Goals must be an array"
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
@@ -38,9 +37,8 @@ module Split
|
|
38
37
|
end
|
39
38
|
|
40
39
|
private
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
40
|
+
def goals_key
|
41
|
+
"#{@experiment_name}:goals"
|
42
|
+
end
|
45
43
|
end
|
46
44
|
end
|
data/lib/split/helper.rb
CHANGED
@@ -12,9 +12,9 @@ module Split
|
|
12
12
|
alternative = if Split.configuration.enabled && !exclude_visitor?
|
13
13
|
experiment.save
|
14
14
|
raise(Split::InvalidExperimentsFormatError) unless (Split.configuration.experiments || {}).fetch(experiment.name.to_sym, {})[:combined_experiments].nil?
|
15
|
-
trial = Trial.new(:
|
16
|
-
:
|
17
|
-
:
|
15
|
+
trial = Trial.new(user: ab_user, experiment: experiment,
|
16
|
+
override: override_alternative(experiment.name), exclude: exclude_visitor?,
|
17
|
+
disabled: split_generically_disabled?)
|
18
18
|
alt = trial.choose!(self)
|
19
19
|
alt ? alt.name : nil
|
20
20
|
else
|
@@ -44,21 +44,21 @@ module Split
|
|
44
44
|
ab_user.delete(experiment.key)
|
45
45
|
end
|
46
46
|
|
47
|
-
def finish_experiment(experiment, options = {:
|
47
|
+
def finish_experiment(experiment, options = { reset: true })
|
48
48
|
return false if active_experiments[experiment.name].nil?
|
49
49
|
return true if experiment.has_winner?
|
50
50
|
should_reset = experiment.resettable? && options[:reset]
|
51
51
|
if ab_user[experiment.finished_key] && !should_reset
|
52
|
-
|
52
|
+
true
|
53
53
|
else
|
54
54
|
alternative_name = ab_user[experiment.key]
|
55
55
|
trial = Trial.new(
|
56
|
-
:
|
57
|
-
:
|
58
|
-
:
|
59
|
-
:
|
60
|
-
)
|
61
|
-
|
56
|
+
user: ab_user,
|
57
|
+
experiment: experiment,
|
58
|
+
alternative: alternative_name,
|
59
|
+
goals: options[:goals],
|
60
|
+
)
|
61
|
+
|
62
62
|
trial.complete!(self)
|
63
63
|
|
64
64
|
if should_reset
|
@@ -69,7 +69,7 @@ module Split
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
def ab_finished(metric_descriptor, options = {:
|
72
|
+
def ab_finished(metric_descriptor, options = { reset: true })
|
73
73
|
return if exclude_visitor? || Split.configuration.disabled?
|
74
74
|
metric_descriptor, goals = normalize_metric(metric_descriptor)
|
75
75
|
experiments = Metric.possible_experiments(metric_descriptor)
|
@@ -77,7 +77,7 @@ module Split
|
|
77
77
|
if experiments.any?
|
78
78
|
experiments.each do |experiment|
|
79
79
|
next if override_present?(experiment.key)
|
80
|
-
finish_experiment(experiment, options.merge(:
|
80
|
+
finish_experiment(experiment, options.merge(goals: goals))
|
81
81
|
end
|
82
82
|
end
|
83
83
|
rescue => e
|
@@ -86,7 +86,7 @@ module Split
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def ab_record_extra_info(metric_descriptor, key, value = 1)
|
89
|
-
return if exclude_visitor? || Split.configuration.disabled?
|
89
|
+
return if exclude_visitor? || Split.configuration.disabled? || value.nil?
|
90
90
|
metric_descriptor, _ = normalize_metric(metric_descriptor)
|
91
91
|
experiments = Metric.possible_experiments(metric_descriptor)
|
92
92
|
|
@@ -95,7 +95,7 @@ module Split
|
|
95
95
|
alternative_name = ab_user[experiment.key]
|
96
96
|
|
97
97
|
if alternative_name
|
98
|
-
alternative = experiment.alternatives.find{|alt| alt.name == alternative_name}
|
98
|
+
alternative = experiment.alternatives.find { |alt| alt.name == alternative_name }
|
99
99
|
alternative.record_extra_info(key, value) if alternative
|
100
100
|
end
|
101
101
|
end
|
@@ -105,7 +105,7 @@ module Split
|
|
105
105
|
Split.configuration.db_failover_on_db_error.call(e)
|
106
106
|
end
|
107
107
|
|
108
|
-
def ab_active_experiments
|
108
|
+
def ab_active_experiments
|
109
109
|
ab_user.active_experiments
|
110
110
|
rescue => e
|
111
111
|
raise unless Split.configuration.db_failover
|
@@ -127,14 +127,14 @@ module Split
|
|
127
127
|
def override_alternative_by_cookies(experiment_name)
|
128
128
|
return unless defined?(request)
|
129
129
|
|
130
|
-
if request.cookies && request.cookies.key?(
|
131
|
-
experiments = JSON.parse(request.cookies[
|
130
|
+
if request.cookies && request.cookies.key?("split_override")
|
131
|
+
experiments = JSON.parse(request.cookies["split_override"]) rescue {}
|
132
132
|
experiments[experiment_name]
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
136
136
|
def split_generically_disabled?
|
137
|
-
defined?(params) && params[
|
137
|
+
defined?(params) && params["SPLIT_DISABLE"]
|
138
138
|
end
|
139
139
|
|
140
140
|
def ab_user
|
@@ -150,7 +150,7 @@ module Split
|
|
150
150
|
end
|
151
151
|
|
152
152
|
def is_preview?
|
153
|
-
defined?(request) && defined?(request.headers) && request.headers[
|
153
|
+
defined?(request) && defined?(request.headers) && request.headers["x-purpose"] == "preview"
|
154
154
|
end
|
155
155
|
|
156
156
|
def is_ignored_ip_address?
|
data/lib/split/metric.rb
CHANGED
@@ -16,13 +16,13 @@ module Split
|
|
16
16
|
def self.load_from_redis(name)
|
17
17
|
metric = Split.redis.hget(:metrics, name)
|
18
18
|
if metric
|
19
|
-
experiment_names = metric.split(
|
19
|
+
experiment_names = metric.split(",")
|
20
20
|
|
21
21
|
experiments = experiment_names.collect do |experiment_name|
|
22
22
|
Split::ExperimentCatalog.find(experiment_name)
|
23
23
|
end
|
24
24
|
|
25
|
-
Split::Metric.new(:
|
25
|
+
Split::Metric.new(name: name, experiments: experiments)
|
26
26
|
else
|
27
27
|
nil
|
28
28
|
end
|
@@ -31,7 +31,7 @@ module Split
|
|
31
31
|
def self.load_from_configuration(name)
|
32
32
|
metrics = Split.configuration.metrics
|
33
33
|
if metrics && metrics[name]
|
34
|
-
Split::Metric.new(:
|
34
|
+
Split::Metric.new(experiments: metrics[name], name: name)
|
35
35
|
else
|
36
36
|
nil
|
37
37
|
end
|
@@ -77,7 +77,7 @@ module Split
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def save
|
80
|
-
Split.redis.hset(:metrics, name, experiments.map(&:name).join(
|
80
|
+
Split.redis.hset(:metrics, name, experiments.map(&:name).join(","))
|
81
81
|
end
|
82
82
|
|
83
83
|
def complete!
|
@@ -97,6 +97,5 @@ module Split
|
|
97
97
|
return metric_name, goals
|
98
98
|
end
|
99
99
|
private_class_method :normalize_metric
|
100
|
-
|
101
100
|
end
|
102
101
|
end
|