ablab 0.2.0 → 0.2.1
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/app/assets/javascripts/ablab/tracker.js +1 -1
- data/app/assets/stylesheets/ablab/ablab.css +20 -1
- data/app/controllers/ablab/base_controller.rb +6 -4
- data/app/helpers/ablab/ablab_helper.rb +4 -3
- data/app/views/ablab/base/dashboard.html.erb +28 -9
- data/lib/ablab.rb +8 -3
- data/lib/ablab/store/memory.rb +12 -8
- data/lib/ablab/store/redis.rb +10 -8
- data/lib/ablab/version.rb +1 -1
- data/spec/ablab/store/store_examples.rb +8 -0
- data/spec/ablab_spec.rb +28 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd4478bb1810dd0d5afddf97eb299cf2ebaf343b
|
4
|
+
data.tar.gz: 0290705fe61858b5b355fee0ade8d9bb3e44a5fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6ec28c30693a9030cd5d995d2d73242be556d16b0ac8167458d8bcec811c76a309250c0e610a02158654b04f9cb3d565dca8dea141ccca4b65658a9e53c380a
|
7
|
+
data.tar.gz: 1a4b4e194003966c232068aa3bc1f4e0bde85751746aee96c76ddf60b6ad1a828f63074185e57c5157f0a099589541bb172e4f00ec8cf23cc7b917ce68e4b5b4
|
@@ -3,10 +3,14 @@
|
|
3
3
|
They will automatically be included in application.css.
|
4
4
|
*/
|
5
5
|
|
6
|
-
|
6
|
+
html {
|
7
7
|
box-sizing: border-box;
|
8
8
|
}
|
9
9
|
|
10
|
+
*, *:before, *:after {
|
11
|
+
box-sizing: inherit;
|
12
|
+
}
|
13
|
+
|
10
14
|
body {
|
11
15
|
font-family: sans-serif;
|
12
16
|
background: #f5f5f5;
|
@@ -91,9 +95,24 @@ table th {
|
|
91
95
|
border: none;
|
92
96
|
border-bottom: 2px solid #e96e25;
|
93
97
|
border-left: 1px dotted #e96e25;
|
98
|
+
cursor: help;
|
94
99
|
}
|
95
100
|
|
96
101
|
table {
|
97
102
|
border: 1px solid #ffa26b;
|
98
103
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
99
104
|
}
|
105
|
+
|
106
|
+
th i {
|
107
|
+
display: none;
|
108
|
+
position: absolute;
|
109
|
+
font-size: 12px;
|
110
|
+
font-weight: normal;
|
111
|
+
background: #ffa26b;
|
112
|
+
padding: 0.5em 0.809em;
|
113
|
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
114
|
+
}
|
115
|
+
|
116
|
+
th:hover i {
|
117
|
+
display: block;
|
118
|
+
}
|
@@ -8,10 +8,12 @@ module Ablab
|
|
8
8
|
|
9
9
|
def track
|
10
10
|
exp = experiment(params[:experiment].to_sym)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
Thread.new do
|
12
|
+
if params[:event] == 'success'
|
13
|
+
exp.track_success!
|
14
|
+
else
|
15
|
+
exp.track_view!
|
16
|
+
end
|
15
17
|
end
|
16
18
|
respond_to do |format|
|
17
19
|
format.js
|
@@ -15,8 +15,9 @@ module Ablab
|
|
15
15
|
'%.3f' % decimal
|
16
16
|
end
|
17
17
|
|
18
|
-
def significant?(
|
19
|
-
|
18
|
+
def significant?(results)
|
19
|
+
return false if results[:sessions] < 30 || results[:conversions] < 5
|
20
|
+
results[:z_score] && results[:z_score] >= 1.65
|
20
21
|
end
|
21
22
|
|
22
23
|
def confidence(z_score, na = 'n/a')
|
@@ -40,7 +41,7 @@ module Ablab
|
|
40
41
|
-1
|
41
42
|
end
|
42
43
|
end
|
43
|
-
significant?(winner_results
|
44
|
+
significant?(winner_results) && winner_name == group_name
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
@@ -11,15 +11,34 @@
|
|
11
11
|
<% end %>
|
12
12
|
<table>
|
13
13
|
<tr>
|
14
|
-
<th>Group
|
15
|
-
|
16
|
-
|
17
|
-
<th>
|
18
|
-
|
19
|
-
|
20
|
-
<th>
|
21
|
-
|
22
|
-
|
14
|
+
<th>Group
|
15
|
+
<i>The group, defining a variant of the experiment</i>
|
16
|
+
</th>
|
17
|
+
<th>Description
|
18
|
+
<i>The description of the variant of the experiment corresponding to a group</i>
|
19
|
+
</th>
|
20
|
+
<th>Views
|
21
|
+
<i>How many times the experiment was viewed in a group</i>
|
22
|
+
</th>
|
23
|
+
<th>Sessions
|
24
|
+
<i>How many unique visitors in a group viewed the experiment</i>
|
25
|
+
</th>
|
26
|
+
<th>Successes
|
27
|
+
<i>How many times the goal was reached for a group</i>
|
28
|
+
</th>
|
29
|
+
<th>Conversions
|
30
|
+
<i>How many unique visitors in a group reached the goal</i>
|
31
|
+
</th>
|
32
|
+
<th>Conversion rate
|
33
|
+
<i>Ratio of convertions over sessions</i>
|
34
|
+
</th>
|
35
|
+
<th>Z-score
|
36
|
+
<i>Z-score for the confidence testing</i>
|
37
|
+
</th>
|
38
|
+
<th>Confidence
|
39
|
+
<i>Probability that this group is having a real effect on the
|
40
|
+
conversion rate, compared to the control group</i>
|
41
|
+
</th>
|
23
42
|
</tr>
|
24
43
|
<% experiment.results.each do |group_name, results| %>
|
25
44
|
<tr class="<%= 'winner' if winner?(experiment, group_name) %> <%= 'control' if results[:control] %>">
|
data/lib/ablab.rb
CHANGED
@@ -65,6 +65,11 @@ module Ablab
|
|
65
65
|
@goal
|
66
66
|
end
|
67
67
|
|
68
|
+
def percentage_of_visitors(percentage = nil)
|
69
|
+
@percentage_of_visitors = percentage if percentage
|
70
|
+
@percentage_of_visitors || 100
|
71
|
+
end
|
72
|
+
|
68
73
|
def group(name, options = {})
|
69
74
|
group = Group.new(name, options[:description])
|
70
75
|
@groups << group
|
@@ -85,7 +90,6 @@ module Ablab
|
|
85
90
|
|
86
91
|
def initialize(experiment, session_id)
|
87
92
|
@experiment, @session_id = experiment, session_id
|
88
|
-
@experiment = experiment
|
89
93
|
end
|
90
94
|
|
91
95
|
def in_group?(name)
|
@@ -102,8 +106,9 @@ module Ablab
|
|
102
106
|
|
103
107
|
def group
|
104
108
|
return @group unless @group.nil?
|
105
|
-
|
106
|
-
|
109
|
+
size = 1000.0 * (experiment.percentage_of_visitors) / 100.0
|
110
|
+
idx = (draw * experiment.groups.size / size).floor
|
111
|
+
@group = experiment.groups[idx].try(:name)
|
107
112
|
end
|
108
113
|
|
109
114
|
def draw
|
data/lib/ablab/store/memory.rb
CHANGED
@@ -5,27 +5,25 @@ module Ablab
|
|
5
5
|
class Memory
|
6
6
|
def initialize
|
7
7
|
@views = Hash.new do |hash, key|
|
8
|
-
hash[key] = Hash.new { |
|
8
|
+
hash[key] = Hash.new { |h, k| h[k] = 0 }
|
9
9
|
end
|
10
10
|
@sessions = Hash.new do |hash, key|
|
11
|
-
hash[key] = Hash.new { |
|
11
|
+
hash[key] = Hash.new { |h, k| h[k] = Set.new }
|
12
12
|
end
|
13
13
|
@successes = Hash.new do |hash, key|
|
14
|
-
hash[key] = Hash.new { |
|
14
|
+
hash[key] = Hash.new { |h, k| h[k] = 0 }
|
15
15
|
end
|
16
16
|
@conversions = Hash.new do |hash, key|
|
17
|
-
hash[key] = Hash.new { |
|
17
|
+
hash[key] = Hash.new { |h, k| h[k] = Set.new }
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def track_view!(experiment, bucket, session_id)
|
22
|
-
|
23
|
-
@sessions[experiment][bucket].add(session_id)
|
22
|
+
track(experiment, bucket, session_id, @views, @sessions)
|
24
23
|
end
|
25
24
|
|
26
25
|
def track_success!(experiment, bucket, session_id)
|
27
|
-
|
28
|
-
@conversions[experiment][bucket].add(session_id)
|
26
|
+
track(experiment, bucket, session_id, @successes, @conversions)
|
29
27
|
end
|
30
28
|
|
31
29
|
def views(experiment, bucket)
|
@@ -52,6 +50,12 @@ module Ablab
|
|
52
50
|
conversions: conversions(experiment, bucket)
|
53
51
|
}
|
54
52
|
end
|
53
|
+
|
54
|
+
private def track(experiment, bucket, session_id, counter, set)
|
55
|
+
return false if bucket.nil?
|
56
|
+
counter[experiment][bucket] += 1
|
57
|
+
set[experiment][bucket].add(session_id)
|
58
|
+
end
|
55
59
|
end
|
56
60
|
end
|
57
61
|
end
|
data/lib/ablab/store/redis.rb
CHANGED
@@ -11,17 +11,11 @@ module Ablab
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def track_view!(experiment, bucket, session_id)
|
14
|
-
|
15
|
-
redis.incr(key(:views, experiment, bucket))
|
16
|
-
redis.pfadd(key(:sessions, experiment, bucket), session_id)
|
17
|
-
end
|
14
|
+
track(experiment, bucket, session_id, :views, :sessions)
|
18
15
|
end
|
19
16
|
|
20
17
|
def track_success!(experiment, bucket, session_id)
|
21
|
-
|
22
|
-
redis.incr(key(:successes, experiment, bucket))
|
23
|
-
redis.pfadd(key(:conversions, experiment, bucket), session_id)
|
24
|
-
end
|
18
|
+
track(experiment, bucket, session_id, :successes, :conversions)
|
25
19
|
end
|
26
20
|
|
27
21
|
def views(experiment, bucket)
|
@@ -59,6 +53,14 @@ module Ablab
|
|
59
53
|
private def key(type, experiment, bucket)
|
60
54
|
"#{@key_prefix}:#{type}:#{experiment}:#{bucket}"
|
61
55
|
end
|
56
|
+
|
57
|
+
private def track(experiment, bucket, session_id, counter, set)
|
58
|
+
return false if bucket.nil?
|
59
|
+
redis.pipelined do
|
60
|
+
redis.incr(key(counter, experiment, bucket))
|
61
|
+
redis.pfadd(key(set, experiment, bucket), session_id)
|
62
|
+
end
|
63
|
+
end
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
data/lib/ablab/version.rb
CHANGED
@@ -2,6 +2,10 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.shared_examples 'store' do
|
4
4
|
describe '#track_view!' do
|
5
|
+
it 'returns false if group is nil' do
|
6
|
+
expect(store.track_view!(:foo, nil, 'abc')).to be(false)
|
7
|
+
end
|
8
|
+
|
5
9
|
it 'tracks views' do
|
6
10
|
5.times { store.track_view!(:foo, :a, 'abc') }
|
7
11
|
7.times { store.track_view!(:foo, :b, 'abc') }
|
@@ -23,6 +27,10 @@ RSpec.shared_examples 'store' do
|
|
23
27
|
end
|
24
28
|
|
25
29
|
describe '#track_success!' do
|
30
|
+
it 'returns false if group is nil' do
|
31
|
+
expect(store.track_success!(:foo, nil, 'abc')).to be(false)
|
32
|
+
end
|
33
|
+
|
26
34
|
it 'tracks successes' do
|
27
35
|
5.times { store.track_success!(:foo, :a, 'abc') }
|
28
36
|
7.times { store.track_success!(:foo, :b, 'abc') }
|
data/spec/ablab_spec.rb
CHANGED
@@ -74,13 +74,24 @@ describe Ablab do
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
describe '#
|
77
|
+
describe '#goal' do
|
78
78
|
it 'sets the experiment goal' do
|
79
79
|
experiment.goal 'foo bar'
|
80
80
|
expect(experiment.goal).to eq('foo bar')
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
+
describe '#percentage_of_visitors' do
|
85
|
+
it 'returns 100 if never set' do
|
86
|
+
expect(experiment.percentage_of_visitors).to eq(100)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'sets the percentage of visitors included in the experiment' do
|
90
|
+
experiment.percentage_of_visitors 25
|
91
|
+
expect(experiment.percentage_of_visitors).to eq(25)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
84
95
|
describe '#group' do
|
85
96
|
it 'creates a group' do
|
86
97
|
experiment.group :a, description: 'foo bar baz'
|
@@ -156,5 +167,21 @@ describe Ablab do
|
|
156
167
|
run2 = Ablab::Run.new(experiment, 'foobar')
|
157
168
|
expect(run1.group).to eq(run2.group)
|
158
169
|
end
|
170
|
+
|
171
|
+
it 'selects only the given percentage of users' do
|
172
|
+
experiment.percentage_of_visitors 30
|
173
|
+
run = Ablab::Run.new(experiment, 0)
|
174
|
+
allow(run).to receive(:draw).and_return 0
|
175
|
+
expect(run).to be_in_group(:control)
|
176
|
+
run = Ablab::Run.new(experiment, 0)
|
177
|
+
allow(run).to receive(:draw).and_return 100
|
178
|
+
expect(run).to be_in_group(:a)
|
179
|
+
run = Ablab::Run.new(experiment, 0)
|
180
|
+
allow(run).to receive(:draw).and_return 200
|
181
|
+
expect(run).to be_in_group(:b)
|
182
|
+
run = Ablab::Run.new(experiment, 0)
|
183
|
+
allow(run).to receive(:draw).and_return 300
|
184
|
+
expect(run.group).to be_nil
|
185
|
+
end
|
159
186
|
end
|
160
187
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ablab
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Ongaro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -115,7 +115,7 @@ files:
|
|
115
115
|
- spec/ablab/store/store_examples.rb
|
116
116
|
- spec/ablab_spec.rb
|
117
117
|
- spec/spec_helper.rb
|
118
|
-
homepage:
|
118
|
+
homepage: https://github.com/lucaong/ablab
|
119
119
|
licenses:
|
120
120
|
- MIT
|
121
121
|
metadata: {}
|
@@ -135,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
135
|
version: '0'
|
136
136
|
requirements: []
|
137
137
|
rubyforge_project:
|
138
|
-
rubygems_version: 2.
|
138
|
+
rubygems_version: 2.2.2
|
139
139
|
signing_key:
|
140
140
|
specification_version: 4
|
141
141
|
summary: Ablab - A/B testing on Rails
|