ab_panel 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eddcde97ae111db6ebc623a046cb420165781b04
4
- data.tar.gz: 2eef6a7d7788394cc81c4d508cca4ce598c2aaf0
3
+ metadata.gz: 17de85b0bf79398d4c004cb2a7de53b13347843e
4
+ data.tar.gz: ec7bbc8ade945df459db9f83b754c2cefab9f9c9
5
5
  SHA512:
6
- metadata.gz: 364bb674be3048779273917e7a35fa51927356bf67e9187a323c296b30a0d5d61101d1fae1c8eac20dc3f28a4a8c51e6f7e8d9cee199feca714cb5771851704c
7
- data.tar.gz: 1c0dca5e98a83de0785a7654452ce68881eb6a471530d3758aaad0eb2f85761dfecb2d6b5c3d1626877380d70de84ca3f0ccf03444b18315e86c6735edbbbb69
6
+ metadata.gz: 556d32d1a6b2965db653703aea0a2e02914fc526f80e3a392ceb3bef66374912961b6ab82f7275903e4272fe2aab774d38e123c4b5dbfdc68370fdd69961f474
7
+ data.tar.gz: 9abeeb2287f2ec6626068d4ec89dc185d21cc8bf1d83c66ef29d7ec067439836a3b3c2e6226928fe8746a44369b348a9418cc9d9f9d43cc5b24f9910a5ce3c0a
data/README.md CHANGED
@@ -18,6 +18,35 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install ab_panel
20
20
 
21
+ ## Upgrading from 0.2.0 to 0.3.0
22
+
23
+ In this new version we've added weights to different conditions/scenarios. This
24
+ is so that you can rollout certain features slowly. We've also removed the
25
+ original (control scenario) that is added standard.
26
+
27
+ The only thing you need to do to upgrade is update the ``ab_panel.yml``.
28
+
29
+ Old:
30
+
31
+ ```yaml
32
+
33
+ foo:
34
+ - bar1
35
+ - bar2
36
+
37
+ ```
38
+
39
+ New (if you want to keep original or need original):
40
+
41
+ ```yaml
42
+
43
+ foo:
44
+ bar1: 2
45
+ bar2: 2
46
+ original: 2
47
+
48
+ ```
49
+
21
50
  ## Usage
22
51
 
23
52
  Create a config file with one or more experiments and conditions.
@@ -26,13 +55,14 @@ In `config/ab_panel.yml`
26
55
 
27
56
  ```yaml
28
57
  my_experiment:
29
- - condition_b
30
- - condition_c
58
+ original: 1
59
+ condition_b: 1
60
+ condition_c: 1
31
61
  ```
32
62
 
33
63
  Note that this will create 3 conditions:
34
64
 
35
- 1. Original condition (control condition)
65
+ 1. Original condition
36
66
  2. Condition B
37
67
  3. Condition C
38
68
 
data/ab_panel.gemspec CHANGED
@@ -6,8 +6,8 @@ require 'ab_panel/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "ab_panel"
8
8
  spec.version = AbPanel::VERSION
9
- spec.authors = ["Wouter de Vos", "Mark Mulder"]
10
- spec.email = ["wouter@springest.com", "markmulder@gmail.com"]
9
+ spec.authors = ["Wouter de Vos", "Mark Mulder", "Peter de Ruijter"]
10
+ spec.email = ["wouter@springest.com", "markmulder@gmail.com", "hello@thisiswho.im"]
11
11
  spec.description = %q{Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.}
12
12
  spec.summary = %q{Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.}
13
13
  spec.homepage = "https://github.com/Springest/ab_panel"
data/lib/ab_panel.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'set'
2
+ require_relative './array'
3
+
2
4
  Dir[File.expand_path(File.join(
3
5
  File.dirname(__FILE__),'ab_panel','**','*.rb'))]
4
6
  .each {|f| require f}
@@ -37,6 +39,10 @@ module AbPanel
37
39
  config.scenarios experiment
38
40
  end
39
41
 
42
+ def weights(experiment)
43
+ config.weights experiment
44
+ end
45
+
40
46
  def properties
41
47
  @env[:properties]
42
48
  end
@@ -83,7 +89,7 @@ module AbPanel
83
89
  selected = begin
84
90
  already_assigned.send(experiment).condition
85
91
  rescue
86
- scenarios(experiment)[rand(scenarios(experiment).size)]
92
+ scenarios(experiment).weighted_sample(weights(experiment))
87
93
  end
88
94
 
89
95
  cs[experiment]["#{selected}?"] = true
@@ -12,9 +12,13 @@ module AbPanel
12
12
 
13
13
  def scenarios(experiment)
14
14
  raise ArgumentError.new( "Fatal: Experiment config not found for #{experiment}" ) unless experiments.include? experiment.to_sym
15
- ( settings[experiment.to_sym].map(&:to_sym) + [:original] ).uniq
15
+ ( settings[experiment.to_sym].keys.map(&:to_sym)).uniq
16
16
  end
17
17
 
18
+ def weights(experiment)
19
+ raise ArgumentError.new( "Fatal: Experiment config not found for #{experiment}" ) unless experiments.include? experiment.to_sym
20
+ settings[experiment.to_sym].map { |s| s[1] }
21
+ end
18
22
 
19
23
  def settings
20
24
  @settings ||= YAML.load(
@@ -1,3 +1,3 @@
1
1
  module AbPanel
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/array.rb ADDED
@@ -0,0 +1,25 @@
1
+ class Array
2
+ def weighted_sample(weights=nil)
3
+ weights = Array.new(length, 1.0) if weights.nil? || weights.sum == 0
4
+ total = weights.sum
5
+
6
+ # The total sum of weights is multiplied by a random number
7
+ trigger = Kernel::rand * total
8
+
9
+ subtotal = 0
10
+ result = nil
11
+
12
+ # The subtotal is checked agains the trigger. The higher the sum, the higher
13
+ # the probability of triggering a result.
14
+ weights.each_with_index do |weight, index|
15
+ subtotal += weight
16
+
17
+ if subtotal > trigger
18
+ result = self[index]
19
+ break
20
+ end
21
+ end
22
+ # Returns self.last from current array if result is nil
23
+ result || last
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe AbPanel::Config do
4
+ let(:config) { AbPanel::Config.new }
5
+ before do
6
+ AbPanel::Config.any_instance.stub(:settings) { { exp1: { scenario1: 25, scenario2: 75 } } }
7
+ end
8
+
9
+ describe '.experiments' do
10
+ subject { config.experiments }
11
+ it { should =~ [:exp1] }
12
+ end
13
+
14
+ describe '.weights' do
15
+ subject { config.weights('exp1') }
16
+
17
+ it { should =~ [75.0, 25.0] }
18
+ end
19
+ end
@@ -7,6 +7,21 @@ describe AbPanel do
7
7
  it { should =~ %w(experiment1 experiment2).map(&:to_sym) }
8
8
  end
9
9
 
10
+ describe ".weights" do
11
+ let(:experiment) { AbPanel.experiments.first }
12
+ subject { AbPanel.weights(experiment) }
13
+
14
+ it { should == [25, 25, 25, 25] }
15
+
16
+ describe "With a nonexistent experiment" do
17
+ let(:experiment) { :does_not_exist }
18
+
19
+ it 'should throw an ArgumentError' do
20
+ expect { subject }.to raise_exception ArgumentError
21
+ end
22
+ end
23
+ end
24
+
10
25
  describe ".scenarios" do
11
26
  subject { AbPanel.scenarios(experiment) }
12
27
 
@@ -14,7 +29,7 @@ describe AbPanel do
14
29
 
15
30
  it { should =~ %w( scenario1 scenario2 scenario3 original ).map(&:to_sym) }
16
31
 
17
- describe "With an unexisting experiment" do
32
+ describe "With an nonexistent experiment" do
18
33
  let(:experiment) { :does_not_exist }
19
34
 
20
35
  it 'should throw an ArgumentError' do
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Array do
4
+ describe '.weighted_sample' do
5
+ before do
6
+ Kernel.stub(:rand) { 0.5 }
7
+ end
8
+
9
+ context "Stub test" do
10
+ subject { Kernel.rand }
11
+ it { should eq 0.5 }
12
+ end
13
+
14
+ let(:array) { [1, 2, 3, 4] }
15
+ subject { array.weighted_sample }
16
+
17
+ it { should eq 3 }
18
+
19
+ context "different random" do
20
+ before do
21
+ Kernel.stub(:rand) { 0 }
22
+ end
23
+
24
+ it { should eq 1 }
25
+ end
26
+
27
+ context "different random" do
28
+ before do
29
+ Kernel.stub(:rand) { 1 }
30
+ end
31
+
32
+ it { should eq 4 }
33
+ end
34
+
35
+ context "with weights" do
36
+ subject { array.weighted_sample([1, 0, 0, 0]) }
37
+ it { should eq 1 }
38
+ end
39
+
40
+ context "all the same weights" do
41
+ before { Kernel.stub(:rand) { 1 } }
42
+ subject { array.weighted_sample([0, 0, 0, 0]) }
43
+ it { should eq 4 }
44
+ context "random 0" do
45
+ before { Kernel.stub(:rand) { 0 } }
46
+ it { should eq 1 }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,9 +1,11 @@
1
1
  experiment1:
2
- - scenario1
3
- - scenario2
4
- - scenario3
2
+ scenario1: 25
3
+ scenario2: 25
4
+ scenario3: 25
5
+ original: 25
5
6
 
6
7
  experiment2:
7
- - scenario4
8
- - scenario5
8
+ scenario4: 33.4
9
+ scenario5: 33.3
10
+ original: 33.3
9
11
 
metadata CHANGED
@@ -1,124 +1,126 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ab_panel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter de Vos
8
8
  - Mark Mulder
9
+ - Peter de Ruijter
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2013-11-18 00:00:00.000000000 Z
13
+ date: 2014-03-18 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: bundler
16
17
  requirement: !ruby/object:Gem::Requirement
17
18
  requirements:
18
- - - ~>
19
+ - - "~>"
19
20
  - !ruby/object:Gem::Version
20
21
  version: '1.3'
21
22
  type: :development
22
23
  prerelease: false
23
24
  version_requirements: !ruby/object:Gem::Requirement
24
25
  requirements:
25
- - - ~>
26
+ - - "~>"
26
27
  - !ruby/object:Gem::Version
27
28
  version: '1.3'
28
29
  - !ruby/object:Gem::Dependency
29
30
  name: rails
30
31
  requirement: !ruby/object:Gem::Requirement
31
32
  requirements:
32
- - - ~>
33
+ - - "~>"
33
34
  - !ruby/object:Gem::Version
34
35
  version: '3.2'
35
36
  type: :development
36
37
  prerelease: false
37
38
  version_requirements: !ruby/object:Gem::Requirement
38
39
  requirements:
39
- - - ~>
40
+ - - "~>"
40
41
  - !ruby/object:Gem::Version
41
42
  version: '3.2'
42
43
  - !ruby/object:Gem::Dependency
43
44
  name: rake
44
45
  requirement: !ruby/object:Gem::Requirement
45
46
  requirements:
46
- - - '>='
47
+ - - ">="
47
48
  - !ruby/object:Gem::Version
48
49
  version: '0'
49
50
  type: :development
50
51
  prerelease: false
51
52
  version_requirements: !ruby/object:Gem::Requirement
52
53
  requirements:
53
- - - '>='
54
+ - - ">="
54
55
  - !ruby/object:Gem::Version
55
56
  version: '0'
56
57
  - !ruby/object:Gem::Dependency
57
58
  name: fakeweb
58
59
  requirement: !ruby/object:Gem::Requirement
59
60
  requirements:
60
- - - '>='
61
+ - - ">="
61
62
  - !ruby/object:Gem::Version
62
63
  version: '0'
63
64
  type: :development
64
65
  prerelease: false
65
66
  version_requirements: !ruby/object:Gem::Requirement
66
67
  requirements:
67
- - - '>='
68
+ - - ">="
68
69
  - !ruby/object:Gem::Version
69
70
  version: '0'
70
71
  - !ruby/object:Gem::Dependency
71
72
  name: rspec
72
73
  requirement: !ruby/object:Gem::Requirement
73
74
  requirements:
74
- - - '>='
75
+ - - ">="
75
76
  - !ruby/object:Gem::Version
76
77
  version: '0'
77
78
  type: :development
78
79
  prerelease: false
79
80
  version_requirements: !ruby/object:Gem::Requirement
80
81
  requirements:
81
- - - '>='
82
+ - - ">="
82
83
  - !ruby/object:Gem::Version
83
84
  version: '0'
84
85
  - !ruby/object:Gem::Dependency
85
86
  name: debugger
86
87
  requirement: !ruby/object:Gem::Requirement
87
88
  requirements:
88
- - - '>='
89
+ - - ">="
89
90
  - !ruby/object:Gem::Version
90
91
  version: '0'
91
92
  type: :development
92
93
  prerelease: false
93
94
  version_requirements: !ruby/object:Gem::Requirement
94
95
  requirements:
95
- - - '>='
96
+ - - ">="
96
97
  - !ruby/object:Gem::Version
97
98
  version: '0'
98
99
  - !ruby/object:Gem::Dependency
99
100
  name: mixpanel
100
101
  requirement: !ruby/object:Gem::Requirement
101
102
  requirements:
102
- - - '>='
103
+ - - ">="
103
104
  - !ruby/object:Gem::Version
104
105
  version: '0'
105
106
  type: :runtime
106
107
  prerelease: false
107
108
  version_requirements: !ruby/object:Gem::Requirement
108
109
  requirements:
109
- - - '>='
110
+ - - ">="
110
111
  - !ruby/object:Gem::Version
111
112
  version: '0'
112
113
  description: Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.
113
114
  email:
114
115
  - wouter@springest.com
115
116
  - markmulder@gmail.com
117
+ - hello@thisiswho.im
116
118
  executables: []
117
119
  extensions: []
118
120
  extra_rdoc_files: []
119
121
  files:
120
- - .gitignore
121
- - .travis.yml
122
+ - ".gitignore"
123
+ - ".travis.yml"
122
124
  - Gemfile
123
125
  - LICENSE.txt
124
126
  - README.md
@@ -196,9 +198,12 @@ files:
196
198
  - lib/ab_panel/javascript.rb
197
199
  - lib/ab_panel/mixpanel.rb
198
200
  - lib/ab_panel/version.rb
201
+ - lib/array.rb
202
+ - spec/ab_panel/config_spec.rb
199
203
  - spec/ab_panel/controller_additions_spec.rb
200
204
  - spec/ab_panel/javascript_spec.rb
201
205
  - spec/ab_panel_spec.rb
206
+ - spec/array_spec.rb
202
207
  - spec/spec_helper.rb
203
208
  - spec/support/fakeweb.rb
204
209
  - spec/support/files/config/ab_panel.yml
@@ -213,17 +218,17 @@ require_paths:
213
218
  - lib
214
219
  required_ruby_version: !ruby/object:Gem::Requirement
215
220
  requirements:
216
- - - '>='
221
+ - - ">="
217
222
  - !ruby/object:Gem::Version
218
223
  version: '0'
219
224
  required_rubygems_version: !ruby/object:Gem::Requirement
220
225
  requirements:
221
- - - '>='
226
+ - - ">="
222
227
  - !ruby/object:Gem::Version
223
228
  version: '0'
224
229
  requirements: []
225
230
  rubyforge_project:
226
- rubygems_version: 2.0.3
231
+ rubygems_version: 2.2.0
227
232
  signing_key:
228
233
  specification_version: 4
229
234
  summary: Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.
@@ -294,9 +299,11 @@ test_files:
294
299
  - example/vendor/assets/javascripts/.gitkeep
295
300
  - example/vendor/assets/stylesheets/.gitkeep
296
301
  - example/vendor/plugins/.gitkeep
302
+ - spec/ab_panel/config_spec.rb
297
303
  - spec/ab_panel/controller_additions_spec.rb
298
304
  - spec/ab_panel/javascript_spec.rb
299
305
  - spec/ab_panel_spec.rb
306
+ - spec/array_spec.rb
300
307
  - spec/spec_helper.rb
301
308
  - spec/support/fakeweb.rb
302
309
  - spec/support/files/config/ab_panel.yml