ab_panel 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +33 -3
- data/ab_panel.gemspec +2 -2
- data/lib/ab_panel.rb +7 -1
- data/lib/ab_panel/config.rb +5 -1
- data/lib/ab_panel/version.rb +1 -1
- data/lib/array.rb +25 -0
- data/spec/ab_panel/config_spec.rb +19 -0
- data/spec/ab_panel_spec.rb +16 -1
- data/spec/array_spec.rb +50 -0
- data/spec/support/files/config/ab_panel.yml +7 -5
- metadata +28 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17de85b0bf79398d4c004cb2a7de53b13347843e
|
4
|
+
data.tar.gz: ec7bbc8ade945df459db9f83b754c2cefab9f9c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
30
|
-
|
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
|
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)
|
92
|
+
scenarios(experiment).weighted_sample(weights(experiment))
|
87
93
|
end
|
88
94
|
|
89
95
|
cs[experiment]["#{selected}?"] = true
|
data/lib/ab_panel/config.rb
CHANGED
@@ -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)
|
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(
|
data/lib/ab_panel/version.rb
CHANGED
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
|
data/spec/ab_panel_spec.rb
CHANGED
@@ -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
|
32
|
+
describe "With an nonexistent experiment" do
|
18
33
|
let(:experiment) { :does_not_exist }
|
19
34
|
|
20
35
|
it 'should throw an ArgumentError' do
|
data/spec/array_spec.rb
ADDED
@@ -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
|
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.
|
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:
|
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
|
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
|