split 3.3.2 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,11 +3,11 @@ rvm:
3
3
  - 1.9.3
4
4
  - 2.0
5
5
  - 2.1.10
6
- - 2.2.0
7
6
  - 2.2.2
8
- - 2.4.5
9
- - 2.5.3
10
- - 2.6.0
7
+ - 2.3.8
8
+ - 2.4.9
9
+ - 2.5.7
10
+ - 2.6.5
11
11
 
12
12
  gemfile:
13
13
  - gemfiles/4.2.gemfile
@@ -16,7 +16,6 @@ gemfile:
16
16
  - gemfiles/5.2.gemfile
17
17
  - gemfiles/6.0.gemfile
18
18
 
19
-
20
19
  matrix:
21
20
  exclude:
22
21
  - rvm: 1.9.3
@@ -43,20 +42,15 @@ matrix:
43
42
  gemfile: gemfiles/5.2.gemfile
44
43
  - rvm: 2.1.10
45
44
  gemfile: gemfiles/6.0.gemfile
46
- - rvm: 2.2.0
47
- gemfile: gemfiles/5.0.gemfile
48
- - rvm: 2.2.0
49
- gemfile: gemfiles/5.1.gemfile
50
- - rvm: 2.2.0
51
- gemfile: gemfiles/5.2.gemfile
52
- - rvm: 2.2.0
53
- gemfile: gemfiles/6.0.gemfile
54
45
  - rvm: 2.2.2
55
46
  gemfile: gemfiles/6.0.gemfile
56
- - rvm: 2.4.5
47
+ - rvm: 2.3.8
48
+ gemfile: gemfiles/6.0.gemfile
49
+ - rvm: 2.4.9
57
50
  gemfile: gemfiles/6.0.gemfile
58
51
 
59
52
  before_install:
53
+ - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
60
54
  - gem install bundler --version=1.17.3
61
55
 
62
56
  script:
data/Appraisals CHANGED
@@ -15,5 +15,5 @@ appraise "5.2" do
15
15
  end
16
16
 
17
17
  appraise "6.0" do
18
- gem 'rails', '~> 6.0.0.beta3'
18
+ gem 'rails', '~> 6.0'
19
19
  end
@@ -1,3 +1,25 @@
1
+ ## 3.4.0 (November 9th, 2019)
2
+
3
+ Features:
4
+ - Improve DualAdapter (@santib, #588), adds a new configuration for the DualAdapter, making it possible to keep consistency for logged_out/logged_in users. It's a opt-in flag. No Behavior was changed on this release.
5
+ - Make dashboard pagination default "per" param configurable (@alopatin, #597)
6
+
7
+ Bugfixes:
8
+ - Fix `force_alternative` for experiments with incremented version (@giraffate, #568)
9
+ - Persist alternative weights (@giraffate, #570)
10
+ - Combined experiment performance improvements (@gnanou, #575)
11
+ - Handle correctly case when ab_finished is called before ab_test for a user (@gnanou, #577)
12
+ - When loading active_experiments, it should not look into user's 'finished' keys (@andrehjr, #582)
13
+
14
+ Misc:
15
+ - Remove `rubyforge_project` from gemspec (@giraffate, #583)
16
+ - Fix URLs to replace http with https (@giraffate , #584)
17
+ - Lazily include split helpers in ActionController::Base (@hasghari, #586)
18
+ - Fix unused variable warnings (@andrehjr, #592)
19
+ - Fix ruby warnings (@andrehjr, #593)
20
+ - Update rubocop.yml config (@andrehjr, #594)
21
+ - Add frozen_string_literal to all files that were missing it (@andrehjr, #595)
22
+
1
23
  ## 3.3.2 (April 12th, 2019)
2
24
 
3
25
  Features:
@@ -68,7 +68,7 @@ members of the project's leadership.
68
68
  ## Attribution
69
69
 
70
70
  This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
72
 
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  source "https://rubygems.org"
2
3
 
3
4
  gemspec
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # [Split](http://libraries.io/rubygems/split)
1
+ # [Split](https://libraries.io/rubygems/split)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/split.svg)](http://badge.fury.io/rb/split)
4
- [![Build Status](https://secure.travis-ci.org/splitrb/split.svg?branch=master)](http://travis-ci.org/splitrb/split)
4
+ [![Build Status](https://secure.travis-ci.org/splitrb/split.svg?branch=master)](https://travis-ci.org/splitrb/split)
5
5
  [![Code Climate](https://codeclimate.com/github/splitrb/split/badges/gpa.svg)](https://codeclimate.com/github/splitrb/split)
6
6
  [![Test Coverage](https://codeclimate.com/github/splitrb/split/badges/coverage.svg)](https://codeclimate.com/github/splitrb/split/coverage)
7
7
  [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
8
8
  [![Open Source Helpers](https://www.codetriage.com/splitrb/split/badges/users.svg)](https://www.codetriage.com/splitrb/split)
9
9
 
10
- > 📈 The Rack Based A/B testing framework http://libraries.io/rubygems/split
10
+ > 📈 The Rack Based A/B testing framework https://libraries.io/rubygems/split
11
11
 
12
12
  Split is a rack based A/B testing framework designed to work with Rails, Sinatra or any other rack based app.
13
13
 
@@ -110,9 +110,9 @@ Split has two options for you to use to determine which alternative is the best.
110
110
 
111
111
  The first option (default on the dashboard) uses a z test (n>30) for the difference between your control and alternative conversion rates to calculate statistical significance. This test will tell you whether an alternative is better or worse than your control, but it will not distinguish between which alternative is the best in an experiment with multiple alternatives. Split will only tell you if your experiment is 90%, 95%, or 99% significant, and this test only works if you have more than 30 participants and 5 conversions for each branch.
112
112
 
113
- As per this [blog post](http://www.evanmiller.org/how-not-to-run-an-ab-test.html) on the pitfalls of A/B testing, it is highly recommended that you determine your requisite sample size for each branch before running the experiment. Otherwise, you'll have an increased rate of false positives (experiments which show a significant effect where really there is none).
113
+ As per this [blog post](https://www.evanmiller.org/how-not-to-run-an-ab-test.html) on the pitfalls of A/B testing, it is highly recommended that you determine your requisite sample size for each branch before running the experiment. Otherwise, you'll have an increased rate of false positives (experiments which show a significant effect where really there is none).
114
114
 
115
- [Here](http://www.evanmiller.org/ab-testing/sample-size.html) is a sample size calculator for your convenience.
115
+ [Here](https://www.evanmiller.org/ab-testing/sample-size.html) is a sample size calculator for your convenience.
116
116
 
117
117
  The second option uses simulations from a beta distribution to determine the probability that the given alternative is the winner compared to all other alternatives. You can view these probabilities by clicking on the drop-down menu labeled "Confidence." This option should be used when the experiment has more than just 1 control and 1 alternative. It can also be used for a simple, 2-alternative A/B test.
118
118
 
@@ -360,7 +360,7 @@ end
360
360
 
361
361
  If you are running `ab_test` from a view, you must define your event
362
362
  hook callback as a
363
- [helper_method](http://apidock.com/rails/AbstractController/Helpers/ClassMethods/helper_method)
363
+ [helper_method](https://apidock.com/rails/AbstractController/Helpers/ClassMethods/helper_method)
364
364
  in the controller:
365
365
 
366
366
  ``` ruby
@@ -446,7 +446,7 @@ match "/split" => Split::Dashboard, anchor: false, via: [:get, :post, :delete],
446
446
  end
447
447
  ```
448
448
 
449
- More information on this [here](http://steve.dynedge.co.uk/2011/12/09/controlling-access-to-routes-and-rack-apps-in-rails-3-with-devise-and-warden/)
449
+ More information on this [here](https://steve.dynedge.co.uk/2011/12/09/controlling-access-to-routes-and-rack-apps-in-rails-3-with-devise-and-warden/)
450
450
 
451
451
  ### Screenshot
452
452
 
@@ -556,7 +556,7 @@ and:
556
556
  ab_finished(:my_first_experiment)
557
557
  ```
558
558
 
559
- You can also add meta data for each experiment, very useful when you need more than an alternative name to change behaviour:
559
+ You can also add meta data for each experiment, which is very useful when you need more than an alternative name to change behaviour:
560
560
 
561
561
  ```ruby
562
562
  Split.configure do |config|
@@ -601,6 +601,8 @@ or in views:
601
601
  <% end %>
602
602
  ```
603
603
 
604
+ The keys used in meta data should be Strings
605
+
604
606
  #### Metrics
605
607
 
606
608
  You might wish to track generic metrics, such as conversions, and use
@@ -824,8 +826,8 @@ end
824
826
 
825
827
  ## Extensions
826
828
 
827
- - [Split::Export](http://github.com/splitrb/split-export) - Easily export A/B test data out of Split.
828
- - [Split::Analytics](http://github.com/splitrb/split-analytics) - Push test data to Google Analytics.
829
+ - [Split::Export](https://github.com/splitrb/split-export) - Easily export A/B test data out of Split.
830
+ - [Split::Analytics](https://github.com/splitrb/split-analytics) - Push test data to Google Analytics.
829
831
  - [Split::Mongoid](https://github.com/MongoHQ/split-mongoid) - Store experiment data in mongoid (still uses redis).
830
832
  - [Split::Cacheable](https://github.com/harrystech/split_cacheable) - Automatically create cache buckets per test.
831
833
  - [Split::Counters](https://github.com/bernardkroes/split-counters) - Add counters per experiment and alternative.
@@ -837,7 +839,7 @@ Ryan bates has produced an excellent 10 minute screencast about split on the Rai
837
839
 
838
840
  ## Blogposts
839
841
 
840
- * [Recipe: A/B testing with KISSMetrics and the split gem](http://robots.thoughtbot.com/post/9595887299/recipe-a-b-testing-with-kissmetrics-and-the-split-gem)
842
+ * [Recipe: A/B testing with KISSMetrics and the split gem](https://robots.thoughtbot.com/post/9595887299/recipe-a-b-testing-with-kissmetrics-and-the-split-gem)
841
843
  * [Rails A/B testing with Split on Heroku](http://blog.nathanhumbert.com/2012/02/rails-ab-testing-with-split-on-heroku.html)
842
844
 
843
845
  ## Backers
@@ -917,9 +919,9 @@ Please do! Over 70 different people have contributed to the project, you can see
917
919
 
918
920
  ### Development
919
921
 
920
- The source code is hosted at [GitHub](http://github.com/splitrb/split).
922
+ The source code is hosted at [GitHub](https://github.com/splitrb/split).
921
923
 
922
- Report issues and feature requests on [GitHub Issues](http://github.com/splitrb/split/issues).
924
+ Report issues and feature requests on [GitHub Issues](https://github.com/splitrb/split/issues).
923
925
 
924
926
  You can find a discussion form on [Google Groups](https://groups.google.com/d/forum/split-ruby).
925
927
 
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env rake
2
+ # frozen_string_literal: true
2
3
  require 'bundler/gem_tasks'
3
4
  require 'rspec/core/rake_task'
4
5
  require 'appraisal'
@@ -4,6 +4,6 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
6
  gem "codeclimate-test-reporter"
7
- gem "rails", "~> 6.0.0.beta3"
7
+ gem "rails", "~> 6.0"
8
8
 
9
9
  gemspec path: "../"
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Selects alternative with minimum count of participants
2
3
  # If all counts are even (i.e. all are minimum), samples from all possible alternatives
3
4
 
@@ -15,7 +15,7 @@ module Split
15
15
  @name = name
16
16
  @weight = 1
17
17
  end
18
- p_winner = 0.0
18
+ @p_winner = 0.0
19
19
  end
20
20
 
21
21
  def to_s
@@ -75,7 +75,7 @@ module Split
75
75
  return field
76
76
  end
77
77
 
78
- def set_completed_count (count, goal = nil)
78
+ def set_completed_count(count, goal = nil)
79
79
  field = set_field(goal)
80
80
  Split.redis.hset(key, field, count.to_i)
81
81
  end
@@ -122,7 +122,7 @@ module Split
122
122
  # can't calculate zscore for P(x) > 1
123
123
  return 'N/A' if p_a > 1 || p_c > 1
124
124
 
125
- z_score = Split::Zscore.calculate(p_a, n_a, p_c, n_c)
125
+ Split::Zscore.calculate(p_a, n_a, p_c, n_c)
126
126
  end
127
127
 
128
128
  def extra_info
@@ -31,7 +31,7 @@ module Split
31
31
  raise(Split::InvalidExperimentsFormatError, 'Invalid descriptor class (String or Symbol required)') unless metric_descriptor.class == String || metric_descriptor.class == Symbol
32
32
  raise(Split::InvalidExperimentsFormatError, 'Enable configuration') unless Split.configuration.enabled
33
33
  raise(Split::InvalidExperimentsFormatError, 'Enable `allow_multiple_experiments`') unless Split.configuration.allow_multiple_experiments
34
- experiment = Split::configuration.experiments[metric_descriptor.to_sym]
34
+ Split::configuration.experiments[metric_descriptor.to_sym]
35
35
  end
36
36
  end
37
37
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Split
3
3
  class Configuration
4
- attr_accessor :bots
5
- attr_accessor :robot_regex
6
4
  attr_accessor :ignore_ip_addresses
7
5
  attr_accessor :ignore_filter
8
6
  attr_accessor :db_failover
@@ -27,9 +25,13 @@ module Split
27
25
  attr_accessor :beta_probability_simulations
28
26
  attr_accessor :winning_alternative_recalculation_interval
29
27
  attr_accessor :redis
28
+ attr_accessor :dashboard_pagination_default_per_page
30
29
 
31
30
  attr_reader :experiments
32
31
 
32
+ attr_writer :bots
33
+ attr_writer :robot_regex
34
+
33
35
  def bots
34
36
  @bots ||= {
35
37
  # Indexers
@@ -225,6 +227,7 @@ module Split
225
227
  @beta_probability_simulations = 10000
226
228
  @winning_alternative_recalculation_interval = 60 * 60 * 24 # 1 day
227
229
  @redis = ENV.fetch(ENV.fetch('REDIS_PROVIDER', 'REDIS_URL'), 'redis://localhost:6379')
230
+ @dashboard_pagination_default_per_page = 10
228
231
  end
229
232
 
230
233
  def redis_url=(value)
@@ -33,7 +33,10 @@ module Split
33
33
  end
34
34
 
35
35
  post '/force_alternative' do
36
- Split::User.new(self)[params[:experiment]] = params[:alternative]
36
+ experiment = Split::ExperimentCatalog.find(params[:experiment])
37
+ alternative = Split::Alternative.new(params[:alternative], experiment.name)
38
+ alternative.increment_participation
39
+ Split::User.new(self)[experiment.key] = alternative.name
37
40
  redirect url('/')
38
41
  end
39
42
 
@@ -3,10 +3,9 @@ require 'split/dashboard/paginator'
3
3
 
4
4
  module Split
5
5
  module DashboardPaginationHelpers
6
- DEFAULT_PER = 10
7
-
8
6
  def pagination_per
9
- @pagination_per ||= (params[:per] || DEFAULT_PER).to_i
7
+ default_per_page = Split.configuration.dashboard_pagination_default_per_page
8
+ @pagination_per ||= (params[:per] || default_per_page).to_i
10
9
  end
11
10
 
12
11
  def page_number
@@ -21,7 +21,7 @@
21
21
  </div>
22
22
 
23
23
  <div id="footer">
24
- <p>Powered by <a href="http://github.com/splitrb/split">Split</a> v<%=Split::VERSION %></p>
24
+ <p>Powered by <a href="https://github.com/splitrb/split">Split</a> v<%=Split::VERSION %></p>
25
25
  </div>
26
26
  </body>
27
27
  </html>
@@ -3,10 +3,12 @@ module Split
3
3
  class Engine < ::Rails::Engine
4
4
  initializer "split" do |app|
5
5
  if Split.configuration.include_rails_helper
6
- ActionController::Base.send :include, Split::Helper
7
- ActionController::Base.helper Split::Helper
8
- ActionController::Base.send :include, Split::CombinedExperimentsHelper
9
- ActionController::Base.helper Split::CombinedExperimentsHelper
6
+ ActiveSupport.on_load(:action_controller) do
7
+ include Split::Helper
8
+ helper Split::Helper
9
+ include Split::CombinedExperimentsHelper
10
+ helper Split::CombinedExperimentsHelper
11
+ end
10
12
  end
11
13
  end
12
14
  end
@@ -2,13 +2,13 @@
2
2
  module Split
3
3
  class Experiment
4
4
  attr_accessor :name
5
- attr_writer :algorithm
6
- attr_accessor :resettable
7
5
  attr_accessor :goals
8
- attr_accessor :alternatives
9
6
  attr_accessor :alternative_probabilities
10
7
  attr_accessor :metadata
11
8
 
9
+ attr_reader :alternatives
10
+ attr_reader :resettable
11
+
12
12
  DEFAULT_OPTIONS = {
13
13
  :resettable => true
14
14
  }
@@ -25,7 +25,7 @@ module Split
25
25
  alternatives: load_alternatives_from_configuration,
26
26
  goals: Split::GoalsCollection.new(@name).load_from_configuration,
27
27
  metadata: load_metadata_from_configuration,
28
- resettable: exp_config[:resettable],
28
+ resettable: exp_config.fetch(:resettable, true),
29
29
  algorithm: exp_config[:algorithm]
30
30
  }
31
31
  else
@@ -62,7 +62,7 @@ module Split
62
62
  alts = load_alternatives_from_configuration
63
63
  options[:goals] = Split::GoalsCollection.new(@name).load_from_configuration
64
64
  options[:metadata] = load_metadata_from_configuration
65
- options[:resettable] = exp_config[:resettable]
65
+ options[:resettable] = exp_config.fetch(:resettable, true)
66
66
  options[:algorithm] = exp_config[:algorithm]
67
67
  end
68
68
  end
@@ -81,12 +81,12 @@ module Split
81
81
 
82
82
  if new_record?
83
83
  start unless Split.configuration.start_manually
84
+ persist_experiment_configuration
84
85
  elsif experiment_configuration_has_changed?
85
86
  reset unless Split.configuration.reset_manually
87
+ persist_experiment_configuration
86
88
  end
87
89
 
88
- persist_experiment_configuration if new_record? || experiment_configuration_has_changed?
89
-
90
90
  redis.hset(experiment_config_key, :resettable, resettable)
91
91
  redis.hset(experiment_config_key, :algorithm, algorithm.to_s)
92
92
  self
@@ -144,11 +144,13 @@ module Split
144
144
  end
145
145
 
146
146
  def has_winner?
147
- !winner.nil?
147
+ return @has_winner if defined? @has_winner
148
+ @has_winner = !winner.nil?
148
149
  end
149
150
 
150
151
  def winner=(winner_name)
151
152
  redis.hset(:experiment_winner, name, winner_name.to_s)
153
+ @has_winner = true
152
154
  end
153
155
 
154
156
  def participant_count
@@ -161,6 +163,7 @@ module Split
161
163
 
162
164
  def reset_winner
163
165
  redis.hdel(:experiment_winner, name)
166
+ @has_winner = false
164
167
  end
165
168
 
166
169
  def start
@@ -420,14 +423,22 @@ module Split
420
423
  end
421
424
 
422
425
  def load_alternatives_from_redis
423
- case redis.type(@name)
424
- when 'set' # convert legacy sets to lists
425
- alts = redis.smembers(@name)
426
- redis.del(@name)
427
- alts.reverse.each {|a| redis.lpush(@name, a) }
428
- redis.lrange(@name, 0, -1)
429
- else
430
- redis.lrange(@name, 0, -1)
426
+ alternatives = case redis.type(@name)
427
+ when 'set' # convert legacy sets to lists
428
+ alts = redis.smembers(@name)
429
+ redis.del(@name)
430
+ alts.reverse.each {|a| redis.lpush(@name, a) }
431
+ redis.lrange(@name, 0, -1)
432
+ else
433
+ redis.lrange(@name, 0, -1)
434
+ end
435
+ alternatives.map do |alt|
436
+ alt = begin
437
+ JSON.parse(alt)
438
+ rescue
439
+ alt
440
+ end
441
+ Split::Alternative.new(alt, @name)
431
442
  end
432
443
  end
433
444
 
@@ -443,7 +454,7 @@ module Split
443
454
 
444
455
  def persist_experiment_configuration
445
456
  redis_interface.add_to_set(:experiments, name)
446
- redis_interface.persist_list(name, @alternatives.map(&:name))
457
+ redis_interface.persist_list(name, @alternatives.map{|alt| {alt.name => alt.weight}.to_json})
447
458
  goals_collection.save
448
459
  redis.set(metadata_key, @metadata.to_json) unless @metadata.nil?
449
460
  end
@@ -459,7 +470,7 @@ module Split
459
470
  existing_alternatives = load_alternatives_from_redis
460
471
  existing_goals = Split::GoalsCollection.new(@name).load_from_redis
461
472
  existing_metadata = load_metadata_from_redis
462
- existing_alternatives != @alternatives.map(&:name) ||
473
+ existing_alternatives.map(&:to_s) != @alternatives.map(&:to_s) ||
463
474
  existing_goals != @goals ||
464
475
  existing_metadata != @metadata
465
476
  end