ablab 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|