split-counters 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 +7 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/Rakefile +7 -0
- data/Readme.mdown +97 -0
- data/lib/split/counters/version.rb +5 -0
- data/lib/split/counters.rb +69 -0
- data/lib/split/countersdashboard/helpers.rb +11 -0
- data/lib/split/countersdashboard/public/countersdashboard.js +9 -0
- data/lib/split/countersdashboard/public/reset.css +48 -0
- data/lib/split/countersdashboard/public/style.css +316 -0
- data/lib/split/countersdashboard/views/_counter.erb +43 -0
- data/lib/split/countersdashboard/views/index.erb +9 -0
- data/lib/split/countersdashboard/views/layout.erb +28 -0
- data/lib/split/countersdashboard.rb +43 -0
- data/screenshots/screenshot1.png +0 -0
- data/spec/counters_spec.rb +64 -0
- data/spec/spec_helper.rb +21 -0
- data/split-counters.gemspec +24 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f3f6a298b34fbb6e817f9f200bcd4789a180a1d2
|
4
|
+
data.tar.gz: 4dead8b7639298a0795c6f3863065c205abe7876
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5e43a0776886cc1b5ef7a1e9182becd3a5dc4999eacc8a7b02ec957e3a58629961cec0657b7b71caedfe7393718f63078d286b59f9249844ce4d4e9f7c598188
|
7
|
+
data.tar.gz: 69f8f55e94dfc7d4ac8c6d0cf030cf33638c92de65d0f1b95d31bcab9555417c4ea08dad8a691547fce644d813b552100f31860f579266ddc2d9e162771675bf
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Bernard Kroes
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/Readme.mdown
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Split::Counters
|
2
|
+
|
3
|
+
An extension to [Split](http://github.com/andrew/split) to add counters per experiment and alternative.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
The split gem and its dependencies.
|
8
|
+
|
9
|
+
## Setup
|
10
|
+
|
11
|
+
If you are using bundler add split to your Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'split-counters', :require => 'split/counters'
|
15
|
+
```
|
16
|
+
|
17
|
+
and if you want to use the separate 'counters' dashboard:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'split-counters', :require => ['split/counters', 'split/countersdashboard']
|
21
|
+
```
|
22
|
+
|
23
|
+
Then run:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
bundle install
|
27
|
+
```
|
28
|
+
|
29
|
+
Otherwise install the gem:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
gem install split-counters
|
33
|
+
```
|
34
|
+
|
35
|
+
and require it in your project:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'split/counters'
|
39
|
+
```
|
40
|
+
|
41
|
+
## Usage
|
42
|
+
|
43
|
+
A simple example in a Rails app could be:
|
44
|
+
```ruby
|
45
|
+
def index
|
46
|
+
the_ab_test = ab_test("exp", "alt1", "alt2")
|
47
|
+
|
48
|
+
ab_counter_inc("IndexViews", "exp", the_ab_test)
|
49
|
+
ab_counter_inc("PageViews", "exp", ab_test("exp", "alt1", "alt2"))
|
50
|
+
end
|
51
|
+
|
52
|
+
def other
|
53
|
+
the_ab_test = ab_test("exp", "alt1", "alt2")
|
54
|
+
|
55
|
+
ab_counter_inc("OtherViews", "exp", the_ab_test)
|
56
|
+
ab_counter_inc("PageViews", "exp", ab_test("exp", "alt1", "alt2"))
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
## Counter Dashboard
|
61
|
+
|
62
|
+

|
63
|
+
|
64
|
+
To enable the 'counters' dashboard (and the Split dashboard), add this to your routes:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class Whitelist
|
68
|
+
def matches?(request)
|
69
|
+
(request.remote_ip == '127.0.0.1') || (Rails.env == 'development')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
mount Split::Dashboard, :at => 'split', :constraints => Whitelist.new
|
73
|
+
mount Split::Countersdashboard, :at => 'splitcounters', :constraints => Whitelist.new
|
74
|
+
```
|
75
|
+
|
76
|
+
This will make the counters dashboard available at /splitcounters (in development mode or from 127.0.0.1).
|
77
|
+
|
78
|
+
## Development
|
79
|
+
|
80
|
+
Source hosted at [GitHub](http://github.com/bernardkroes/split-counters).
|
81
|
+
Report Issues/Feature requests on [GitHub Issues](http://github.com/bernardkroes/split-counters/issues).
|
82
|
+
|
83
|
+
Tests can be run with `rake spec`
|
84
|
+
|
85
|
+
### Note on Patches/Pull Requests
|
86
|
+
|
87
|
+
* Fork the project.
|
88
|
+
* Make your feature addition or bug fix.
|
89
|
+
* Add tests for it. This is important so I don't break it in a
|
90
|
+
future version unintentionally.
|
91
|
+
* Commit, do not mess with rakefile, version, or history.
|
92
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
93
|
+
* Send me a pull request. Bonus points for topic branches.
|
94
|
+
|
95
|
+
## Copyright
|
96
|
+
|
97
|
+
Copyright (c) 2014 Bernard Kroes. See LICENSE for details.
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "split/helper"
|
2
|
+
|
3
|
+
module Split
|
4
|
+
module CounterHelper
|
5
|
+
def ab_counter_inc(counter_name, experiment, alternative)
|
6
|
+
begin
|
7
|
+
Split::Counters.inc(counter_name, experiment, alternative)
|
8
|
+
rescue => e
|
9
|
+
raise(e) unless Split.configuration.db_failover
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Counters
|
15
|
+
def self.hash_name_for_name(in_name)
|
16
|
+
"co:#{in_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.keyname_for_experiment_and_alternative(experiment, alternative)
|
20
|
+
[experiment.gsub(":", ""), alternative.gsub(":", "")].join(':')
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.inc(name, experiment, alternative)
|
24
|
+
Split.redis.hincrby(Split::Counters.hash_name_for_name(name), Split::Counters.keyname_for_experiment_and_alternative(experiment, alternative), 1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.current_value(name, experiment, alternative)
|
28
|
+
Split.redis.hget(Split::Counters.hash_name_for_name(name), Split::Counters.keyname_for_experiment_and_alternative(experiment, alternative))
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.exists?(name)
|
32
|
+
Split.redis.exists(Split::Counters.hash_name_for_name(name))
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.delete(name)
|
36
|
+
Split.redis.del(Split::Counters.hash_name_for_name(name))
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.reset(name, experiment, alternative)
|
40
|
+
Split.redis.hdel(Split::Counters.hash_name_for_name(name), Split::Counters.keyname_for_experiment_and_alternative(experiment, alternative))
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.all_values_hash(name)
|
44
|
+
return_hash = {}
|
45
|
+
result_hash = Split.redis.hgetall(self.hash_name_for_name(name)) # {"exp1:alt1"=>"1", "exp1:alt2"=>"2", "exp2:alt1"=>"1", "exp2:alt2"=>"2"}
|
46
|
+
result_hash.each do |key, value|
|
47
|
+
experiment, alternative = key.split(":")
|
48
|
+
return_hash[experiment] ||= Hash.new
|
49
|
+
return_hash[experiment].merge!({ alternative => value.to_i })
|
50
|
+
end
|
51
|
+
return_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.all_counter_names
|
55
|
+
Split.redis.keys("co:*").collect { |k| k.gsub(/^.*:/,"") }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module Split::Helper
|
61
|
+
include Split::CounterHelper
|
62
|
+
end
|
63
|
+
|
64
|
+
if defined?(Rails)
|
65
|
+
class ActionController::Base
|
66
|
+
ActionController::Base.send :include, Split::CounterHelper
|
67
|
+
ActionController::Base.helper Split::CounterHelper
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
function confirmResetCounter() {
|
2
|
+
var agree = confirm("This will reset the value for this counter, experiment and alternative?");
|
3
|
+
return agree ? true : false;
|
4
|
+
}
|
5
|
+
|
6
|
+
function confirmDeleteCounter() {
|
7
|
+
var agree = confirm("Are you sure you want to delete this counter and all its data?");
|
8
|
+
return agree ? true : false;
|
9
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
html, body, div, span, applet, object, iframe,
|
2
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
3
|
+
a, abbr, acronym, address, big, cite, code,
|
4
|
+
del, dfn, em, font, img, ins, kbd, q, s, samp,
|
5
|
+
small, strike, strong, sub, sup, tt, var,
|
6
|
+
dl, dt, dd, ul, li,
|
7
|
+
form, label, legend,
|
8
|
+
table, caption, tbody, tfoot, thead, tr, th, td {
|
9
|
+
margin: 0;
|
10
|
+
padding: 0;
|
11
|
+
border: 0;
|
12
|
+
outline: 0;
|
13
|
+
font-weight: inherit;
|
14
|
+
font-style: normal;
|
15
|
+
font-size: 100%;
|
16
|
+
font-family: inherit;
|
17
|
+
}
|
18
|
+
|
19
|
+
:focus {
|
20
|
+
outline: 0;
|
21
|
+
}
|
22
|
+
|
23
|
+
body {
|
24
|
+
line-height: 1;
|
25
|
+
}
|
26
|
+
|
27
|
+
ul {
|
28
|
+
list-style: none;
|
29
|
+
}
|
30
|
+
|
31
|
+
table {
|
32
|
+
border-collapse: collapse;
|
33
|
+
border-spacing: 0;
|
34
|
+
}
|
35
|
+
|
36
|
+
caption, th, td {
|
37
|
+
text-align: left;
|
38
|
+
font-weight: normal;
|
39
|
+
}
|
40
|
+
|
41
|
+
blockquote:before, blockquote:after,
|
42
|
+
q:before, q:after {
|
43
|
+
content: "";
|
44
|
+
}
|
45
|
+
|
46
|
+
blockquote, q {
|
47
|
+
quotes: "" "";
|
48
|
+
}
|
@@ -0,0 +1,316 @@
|
|
1
|
+
html {
|
2
|
+
background: #efefef;
|
3
|
+
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
4
|
+
font-size: 13px;
|
5
|
+
}
|
6
|
+
|
7
|
+
body {
|
8
|
+
padding: 0 10px;
|
9
|
+
margin: 10px auto 0;
|
10
|
+
max-width:800px;
|
11
|
+
}
|
12
|
+
|
13
|
+
.header {
|
14
|
+
background: #ededed;
|
15
|
+
background: -webkit-gradient(linear, left top, left bottom,
|
16
|
+
color-stop(0%,#576a76),
|
17
|
+
color-stop(100%,#4d5256));
|
18
|
+
background: -moz-linear-gradient(top, #576076 0%, #414e58 100%);
|
19
|
+
background: -webkit-linear-gradient(top, #576a76 0%, #414e58 100%);
|
20
|
+
background: -o-linear-gradient(top, #576a76 0%, #414e58 100%);
|
21
|
+
background: -ms-linear-gradient(top, #576a76 0%, #414e58 100%);
|
22
|
+
background: linear-gradient(top, #576a76 0%, #414e58 100%);
|
23
|
+
border-bottom: 1px solid #fff;
|
24
|
+
-moz-border-radius-topleft: 5px;
|
25
|
+
-webkit-border-top-left-radius: 5px;
|
26
|
+
border-top-left-radius: 5px;
|
27
|
+
-moz-border-radius-topright: 5px;
|
28
|
+
-webkit-border-top-right-radius:5px;
|
29
|
+
border-top-right-radius: 5px;
|
30
|
+
|
31
|
+
overflow:hidden;
|
32
|
+
padding: 10px 5%;
|
33
|
+
text-shadow:0 1px 0 #000;
|
34
|
+
}
|
35
|
+
|
36
|
+
.header h1 {
|
37
|
+
color: #eee;
|
38
|
+
float:left;
|
39
|
+
font-size:1.2em;
|
40
|
+
font-weight:normal;
|
41
|
+
margin:2px 30px 0 0;
|
42
|
+
}
|
43
|
+
|
44
|
+
.header ul li {
|
45
|
+
display: inline;
|
46
|
+
}
|
47
|
+
|
48
|
+
.header ul li a {
|
49
|
+
color: #eee;
|
50
|
+
text-decoration: none;
|
51
|
+
margin-right: 10px;
|
52
|
+
display: inline-block;
|
53
|
+
padding: 4px 8px;
|
54
|
+
-moz-border-radius: 10px;
|
55
|
+
-webkit-border-radius:10px;
|
56
|
+
border-radius: 10px;
|
57
|
+
|
58
|
+
}
|
59
|
+
|
60
|
+
.header ul li a:hover {
|
61
|
+
background: rgba(255,255,255,0.1);
|
62
|
+
}
|
63
|
+
|
64
|
+
.header ul li a:active {
|
65
|
+
-moz-box-shadow: inset 0 1px 0 rgba(0,0,0,0.2);
|
66
|
+
-webkit-box-shadow:inset 0 1px 0 rgba(0,0,0,0.2);
|
67
|
+
box-shadow: inset 0 1px 0 rgba(0,0,0,0.2);
|
68
|
+
}
|
69
|
+
|
70
|
+
.header ul li.current a {
|
71
|
+
background: rgba(255,255,255,0.1);
|
72
|
+
-moz-box-shadow: inset 0 1px 0 rgba(0,0,0,0.2);
|
73
|
+
-webkit-box-shadow:inset 0 1px 0 rgba(0,0,0,0.2);
|
74
|
+
box-shadow: inset 0 1px 0 rgba(0,0,0,0.2);
|
75
|
+
color: #fff;
|
76
|
+
}
|
77
|
+
|
78
|
+
.header p.environment {
|
79
|
+
clear: both;
|
80
|
+
padding: 10px 0 0 0;
|
81
|
+
color: #BBB;
|
82
|
+
font-style: italic;
|
83
|
+
float: right;
|
84
|
+
}
|
85
|
+
|
86
|
+
#main {
|
87
|
+
padding: 10px 5%;
|
88
|
+
background: #f9f9f9;
|
89
|
+
border:1px solid #ccc;
|
90
|
+
border-top:none;
|
91
|
+
-moz-box-shadow: 0 3px 10px rgba(0,0,0,0.2);
|
92
|
+
-webkit-box-shadow:0 3px 10px rgba(0,0,0,0.2);
|
93
|
+
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
|
94
|
+
overflow: hidden;
|
95
|
+
}
|
96
|
+
|
97
|
+
#main .logo {
|
98
|
+
float: right;
|
99
|
+
margin: 10px;
|
100
|
+
}
|
101
|
+
|
102
|
+
#main span.hl {
|
103
|
+
background: #efefef;
|
104
|
+
padding: 2px;
|
105
|
+
}
|
106
|
+
|
107
|
+
#main h1 {
|
108
|
+
margin: 10px 0;
|
109
|
+
font-size: 190%;
|
110
|
+
font-weight: bold;
|
111
|
+
color: #0080FF;
|
112
|
+
}
|
113
|
+
|
114
|
+
#main table {
|
115
|
+
width: 100%;
|
116
|
+
margin:0 0 10px;
|
117
|
+
}
|
118
|
+
|
119
|
+
#main table tr td, #main table tr th {
|
120
|
+
border-bottom: 1px solid #ccc;
|
121
|
+
padding: 6px;
|
122
|
+
}
|
123
|
+
|
124
|
+
#main table tr th {
|
125
|
+
background: #efefef;
|
126
|
+
color: #888;
|
127
|
+
font-size: 80%;
|
128
|
+
text-transform:uppercase;
|
129
|
+
}
|
130
|
+
|
131
|
+
#main table tr td.no-data {
|
132
|
+
text-align: center;
|
133
|
+
padding: 40px 0;
|
134
|
+
color: #999;
|
135
|
+
font-style: italic;
|
136
|
+
font-size: 130%;
|
137
|
+
}
|
138
|
+
|
139
|
+
#main a {
|
140
|
+
color: #111;
|
141
|
+
}
|
142
|
+
|
143
|
+
#main p {
|
144
|
+
margin: 5px 0;
|
145
|
+
}
|
146
|
+
|
147
|
+
#main p.intro {
|
148
|
+
margin-bottom: 15px;
|
149
|
+
font-size: 85%;
|
150
|
+
color: #999;
|
151
|
+
margin-top: 0;
|
152
|
+
line-height: 1.3;
|
153
|
+
}
|
154
|
+
|
155
|
+
#main h1.wi {
|
156
|
+
margin-bottom: 5px;
|
157
|
+
}
|
158
|
+
|
159
|
+
#main p.sub {
|
160
|
+
font-size: 95%;
|
161
|
+
color: #999;
|
162
|
+
}
|
163
|
+
|
164
|
+
.experiment {
|
165
|
+
background:#fff;
|
166
|
+
border: 1px solid #eee;
|
167
|
+
border-bottom:none;
|
168
|
+
margin:30px 0;
|
169
|
+
}
|
170
|
+
|
171
|
+
.experiment_with_goal {
|
172
|
+
margin: -32px 0 30px 0;
|
173
|
+
}
|
174
|
+
|
175
|
+
.experiment .experiment-header {
|
176
|
+
background: #f4f4f4;
|
177
|
+
background: -webkit-gradient(linear, left top, left bottom,
|
178
|
+
color-stop(0%,#f4f4f4),
|
179
|
+
color-stop(100%,#e0e0e0));
|
180
|
+
background: -moz-linear-gradient (top, #f4f4f4 0%, #e0e0e0 100%);
|
181
|
+
background: -webkit-linear-gradient(top, #f4f4f4 0%, #e0e0e0 100%);
|
182
|
+
background: -o-linear-gradient (top, #f4f4f4 0%, #e0e0e0 100%);
|
183
|
+
background: -ms-linear-gradient (top, #f4f4f4 0%, #e0e0e0 100%);
|
184
|
+
background: linear-gradient (top, #f4f4f4 0%, #e0e0e0 100%);
|
185
|
+
border-top:1px solid #fff;
|
186
|
+
overflow:hidden;
|
187
|
+
padding:0 10px;
|
188
|
+
}
|
189
|
+
|
190
|
+
.experiment h2 {
|
191
|
+
color:#888;
|
192
|
+
margin: 12px 0 12px 0;
|
193
|
+
font-size: 1em;
|
194
|
+
font-weight:bold;
|
195
|
+
float:left;
|
196
|
+
text-shadow:0 1px 0 rgba(255,255,255,0.8);
|
197
|
+
}
|
198
|
+
|
199
|
+
.experiment h2 .goal {
|
200
|
+
font-style: italic;
|
201
|
+
}
|
202
|
+
|
203
|
+
.experiment h2 .version {
|
204
|
+
font-style:italic;
|
205
|
+
font-size:0.8em;
|
206
|
+
color:#bbb;
|
207
|
+
font-weight:normal;
|
208
|
+
}
|
209
|
+
|
210
|
+
.experiment table em{
|
211
|
+
font-style:italic;
|
212
|
+
font-size:0.9em;
|
213
|
+
color:#bbb;
|
214
|
+
}
|
215
|
+
|
216
|
+
.experiment table .totals td {
|
217
|
+
background: #eee;
|
218
|
+
font-weight: bold;
|
219
|
+
}
|
220
|
+
|
221
|
+
#footer {
|
222
|
+
padding: 10px 5%;
|
223
|
+
color: #999;
|
224
|
+
font-size: 85%;
|
225
|
+
line-height: 1.5;
|
226
|
+
padding-top: 10px;
|
227
|
+
}
|
228
|
+
|
229
|
+
#footer p a {
|
230
|
+
color: #999;
|
231
|
+
}
|
232
|
+
|
233
|
+
.inline-controls {
|
234
|
+
float:right;
|
235
|
+
}
|
236
|
+
|
237
|
+
.inline-controls small {
|
238
|
+
color: #888;
|
239
|
+
font-size: 11px;
|
240
|
+
}
|
241
|
+
|
242
|
+
.inline-controls form {
|
243
|
+
display: inline-block;
|
244
|
+
font-size: 10px;
|
245
|
+
line-height: 38px;
|
246
|
+
}
|
247
|
+
|
248
|
+
.inline-controls input {
|
249
|
+
margin-left: 10px;
|
250
|
+
}
|
251
|
+
|
252
|
+
.worse, .better {
|
253
|
+
color: #773F3F;
|
254
|
+
font-size: 10px;
|
255
|
+
font-weight:bold;
|
256
|
+
}
|
257
|
+
|
258
|
+
.better {
|
259
|
+
color: #408C48;
|
260
|
+
}
|
261
|
+
|
262
|
+
a.button, button, input[type="submit"] {
|
263
|
+
padding: 4px 10px;
|
264
|
+
overflow: hidden;
|
265
|
+
background: #d8dae0;
|
266
|
+
-moz-box-shadow: 0 1px 0 rgba(0,0,0,0.5);
|
267
|
+
-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.5);
|
268
|
+
box-shadow: 0 1px 0 rgba(0,0,0,0.5);
|
269
|
+
border:none;
|
270
|
+
-moz-border-radius: 30px;
|
271
|
+
-webkit-border-radius:30px;
|
272
|
+
border-radius: 30px;
|
273
|
+
color:#2e3035;
|
274
|
+
cursor: pointer;
|
275
|
+
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
276
|
+
text-decoration: none;
|
277
|
+
text-shadow:0 1px 0 rgba(255,255,255,0.8);
|
278
|
+
-moz-user-select: none;
|
279
|
+
-webkit-user-select:none;
|
280
|
+
user-select: none;
|
281
|
+
white-space: nowrap;
|
282
|
+
}
|
283
|
+
a.button:hover, button:hover, input[type="submit"]:hover,
|
284
|
+
a.button:focus, button:focus, input[type="submit"]:focus{
|
285
|
+
background:#bbbfc7;
|
286
|
+
}
|
287
|
+
a.button:active, button:active, input[type="submit"]:active{
|
288
|
+
-moz-box-shadow: inset 0 0 4px #484d57;
|
289
|
+
-webkit-box-shadow:inset 0 0 4px #484d57;
|
290
|
+
box-shadow: inset 0 0 4px #484d57;
|
291
|
+
position:relative;
|
292
|
+
top:1px;
|
293
|
+
}
|
294
|
+
|
295
|
+
a.button.red, button.red, input[type="submit"].red,
|
296
|
+
a.button.green, button.green, input[type="submit"].green {
|
297
|
+
color:#fff;
|
298
|
+
text-shadow:0 1px 0 rgba(0,0,0,0.4);
|
299
|
+
}
|
300
|
+
|
301
|
+
a.button.red, button.red, input[type="submit"].red {
|
302
|
+
background:#a56d6d;
|
303
|
+
}
|
304
|
+
a.button.red:hover, button.red:hover, input[type="submit"].red:hover,
|
305
|
+
a.button.red:focus, button.red:focus, input[type="submit"].red:focus {
|
306
|
+
background:#895C5C;
|
307
|
+
}
|
308
|
+
a.button.green, button.green, input[type="submit"].green {
|
309
|
+
background:#8daa92;
|
310
|
+
}
|
311
|
+
a.button.green:hover, button.green:hover, input[type="submit"].green:hover,
|
312
|
+
a.button.green:focus, button.green:focus, input[type="submit"].green:focus {
|
313
|
+
background:#768E7A;
|
314
|
+
}
|
315
|
+
|
316
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<div class="experiment">
|
2
|
+
<div class="experiment-header">
|
3
|
+
<h2>
|
4
|
+
Counter: <%= counter_name %>
|
5
|
+
</h2>
|
6
|
+
<div class='inline-controls'>
|
7
|
+
<form action="<%= url "/counter/#{counter_name}" %>" method='post' onclick="return confirmDeleteCounter()">
|
8
|
+
<input type="hidden" name="_method" value="delete"/>
|
9
|
+
<input type="submit" value="Delete" class="red">
|
10
|
+
</form>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<table>
|
14
|
+
<tr>
|
15
|
+
<th>Experiment Name</th>
|
16
|
+
<th>Alternative</th>
|
17
|
+
<th>Counter</th>
|
18
|
+
<th>Reset</th>
|
19
|
+
</tr>
|
20
|
+
<% Split::Counters.all_values_hash(counter_name).each do |experiment, alternative_hash| %>
|
21
|
+
<% total_value = 0 %>
|
22
|
+
<% alternative_hash.each do |alternative, value| %>
|
23
|
+
<tr>
|
24
|
+
<td><%= experiment %></td>
|
25
|
+
<td><%= alternative %></td>
|
26
|
+
<td><%= value %></td>
|
27
|
+
<td>
|
28
|
+
<form action="<%= url "/counter/reset/#{counter_name}/#{experiment}/#{alternative}" %>" method='post' onclick="return confirmResetCounter()">
|
29
|
+
<input type="submit" value="Reset Data">
|
30
|
+
</form>
|
31
|
+
</td>
|
32
|
+
<tr>
|
33
|
+
<% total_value += value %>
|
34
|
+
<% end %>
|
35
|
+
<tr class="totals">
|
36
|
+
<td>Totals</td>
|
37
|
+
<td></td>
|
38
|
+
<td><%= total_value %></td>
|
39
|
+
<td></td>
|
40
|
+
<tr>
|
41
|
+
<% end %>
|
42
|
+
</table>
|
43
|
+
</div>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% if @counter_names.any? %>
|
2
|
+
<p class="intro">The list below contains all the registered counters.</p>
|
3
|
+
<% @counter_names.each do |counter_name| %>
|
4
|
+
<%= erb :_counter, :locals => {:counter_name => counter_name} %>
|
5
|
+
<% end %>
|
6
|
+
<% else %>
|
7
|
+
<p class="intro">No counters have been registered.</p>
|
8
|
+
<% end %>
|
9
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
|
5
|
+
<link href="<%= url 'reset.css' %>" media="screen" rel="stylesheet" type="text/css">
|
6
|
+
<link href="<%= url 'style.css' %>" media="screen" rel="stylesheet" type="text/css">
|
7
|
+
<script type="text/javascript" src='<%= url 'countersdashboard.js' %>'></script>
|
8
|
+
<title>Split Counters</title>
|
9
|
+
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<div class="header">
|
13
|
+
<h1>Split Counters Dashboard</h1>
|
14
|
+
<p class="environment"><%= @current_env %></p>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div id="main">
|
18
|
+
<%= yield %>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div id="footer">
|
22
|
+
<p>
|
23
|
+
<a href="http://github.com/bernardkroes/split-counters">Split Counters</a>.
|
24
|
+
Powered by <a href="http://github.com/andrew/split">Split</a> v<%=Split::VERSION %>
|
25
|
+
</p>
|
26
|
+
</div>
|
27
|
+
</body>
|
28
|
+
</html>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'split'
|
3
|
+
require 'bigdecimal'
|
4
|
+
require 'split/countersdashboard/helpers'
|
5
|
+
|
6
|
+
module Split
|
7
|
+
class Countersdashboard < Sinatra::Base
|
8
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
9
|
+
|
10
|
+
set :views, "#{dir}/countersdashboard/views"
|
11
|
+
set :public_folder, "#{dir}/countersdashboard/public"
|
12
|
+
set :static, true
|
13
|
+
set :method_override, true
|
14
|
+
|
15
|
+
helpers Split::CountersdashboardHelpers
|
16
|
+
|
17
|
+
get '/' do
|
18
|
+
@counter_names = Split::Counters.all_counter_names
|
19
|
+
# Display Rails Environment mode (or Rack version if not using Rails)
|
20
|
+
if Object.const_defined?('Rails')
|
21
|
+
@current_env = Rails.env.titlecase
|
22
|
+
else
|
23
|
+
@current_env = "Rack: #{Rack.version}"
|
24
|
+
end
|
25
|
+
erb :index
|
26
|
+
end
|
27
|
+
|
28
|
+
delete '/counter/:counter' do
|
29
|
+
if Split::Counters.exists?(params[:counter])
|
30
|
+
Split::Counters.delete(params[:counter])
|
31
|
+
end
|
32
|
+
redirect url('/')
|
33
|
+
end
|
34
|
+
|
35
|
+
post '/counter/reset/:counter/:experiment/:alternative' do
|
36
|
+
if Split::Counters.exists?(params[:counter])
|
37
|
+
Split::Counters.reset(params[:counter], params[:experiment], params[:alternative])
|
38
|
+
end
|
39
|
+
redirect url('/')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
Binary file
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Split::Counters do
|
4
|
+
include Split::Helper
|
5
|
+
|
6
|
+
describe 'basic use' do
|
7
|
+
before(:each) do
|
8
|
+
Split::Counters.delete('co')
|
9
|
+
Split::Counters.delete('co2')
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:each) do
|
13
|
+
Split::Counters.delete('co')
|
14
|
+
Split::Counters.delete('co2')
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create a counter upon using it, if it does not exist" do
|
18
|
+
Split::Counters.inc('co', 'exp1', 'alt1')
|
19
|
+
Split::Counters.current_value('co', 'exp1', 'alt1').should eq("1")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should create a counter directly upon using it, if it does not exist" do
|
23
|
+
Split::Counters.delete('co')
|
24
|
+
Split::Counters.inc('co','exp1', 'alt1')
|
25
|
+
Split::Counters.current_value('co', 'exp1', 'alt1').should eq("1")
|
26
|
+
Split::Counters.exists?('co').should eq(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be possible to delete a counter" do
|
30
|
+
Split::Counters.inc('co','exp1', 'alt1')
|
31
|
+
Split::Counters.exists?('co').should eq(true)
|
32
|
+
Split::Counters.delete('co')
|
33
|
+
Split::Counters.exists?('co').should eq(false)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be possible to reset a counter" do
|
37
|
+
Split::Counters.delete('co')
|
38
|
+
Split::Counters.inc('co','exp1', 'alt1')
|
39
|
+
Split::Counters.current_value('co', 'exp1', 'alt1').should eq("1")
|
40
|
+
Split::Counters.reset('co', 'exp1', 'alt1')
|
41
|
+
Split::Counters.inc('co','exp1', 'alt1')
|
42
|
+
Split::Counters.current_value('co', 'exp1', 'alt1').should eq("1")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be possible to get all experiments and hashs counter values" do
|
46
|
+
Split::Counters.delete('co')
|
47
|
+
Split::Counters.inc('co', 'exp1', 'alt1')
|
48
|
+
Split::Counters.inc('co', 'exp1', 'alt2')
|
49
|
+
Split::Counters.inc('co', 'exp1', 'alt2')
|
50
|
+
Split::Counters.inc('co', 'exp2', 'alt1')
|
51
|
+
# {"exp1"=>{"alt1"=>1, "alt2"=>2}, "exp2"=>{"alt1"=>1}}
|
52
|
+
Split::Counters.all_values_hash('co').length.should eq(2)
|
53
|
+
Split::Counters.all_values_hash('co')['exp1'].length.should eq(2)
|
54
|
+
Split::Counters.all_values_hash('co')['exp2'].length.should eq(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should be possible to get a list of all counters" do
|
58
|
+
Split::Counters.delete('co')
|
59
|
+
Split::Counters.inc('co', 'exp1', 'alt1')
|
60
|
+
Split::Counters.inc('co2','exp2', 'alt1')
|
61
|
+
Split::Counters.all_counter_names.length.should eq(2)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'split'
|
4
|
+
require 'split/helper'
|
5
|
+
require 'split/counters'
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
def session
|
9
|
+
@session ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def params
|
13
|
+
@params ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def request(ua = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27')
|
17
|
+
r = OpenStruct.new
|
18
|
+
r.user_agent = ua
|
19
|
+
r.ip = '192.168.1.1'
|
20
|
+
@request ||= r
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "split/counters/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Bernard Kroes"]
|
7
|
+
gem.email = ["bernardkroes@gmail.com"]
|
8
|
+
gem.summary = %q{Split extension for counting things per experiment and alternative}
|
9
|
+
gem.description = %q{This gem adds counters to Split}
|
10
|
+
gem.homepage = "https://github.com/bernardkroes/split-counters"
|
11
|
+
gem.license = 'MIT'
|
12
|
+
|
13
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
gem.name = "split-counters"
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
gem.version = Split::Counters::VERSION
|
19
|
+
|
20
|
+
gem.add_dependency(%q<split>, [">= 0.7.0"])
|
21
|
+
|
22
|
+
# Development Dependencies
|
23
|
+
gem.add_development_dependency(%q<rspec>, ["~> 2.14"])
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: split-counters
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bernard Kroes
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: split
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.7.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.7.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.14'
|
41
|
+
description: This gem adds counters to Split
|
42
|
+
email:
|
43
|
+
- bernardkroes@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- Gemfile
|
49
|
+
- LICENSE
|
50
|
+
- Rakefile
|
51
|
+
- Readme.mdown
|
52
|
+
- lib/split/counters.rb
|
53
|
+
- lib/split/counters/version.rb
|
54
|
+
- lib/split/countersdashboard.rb
|
55
|
+
- lib/split/countersdashboard/helpers.rb
|
56
|
+
- lib/split/countersdashboard/public/countersdashboard.js
|
57
|
+
- lib/split/countersdashboard/public/reset.css
|
58
|
+
- lib/split/countersdashboard/public/style.css
|
59
|
+
- lib/split/countersdashboard/views/_counter.erb
|
60
|
+
- lib/split/countersdashboard/views/index.erb
|
61
|
+
- lib/split/countersdashboard/views/layout.erb
|
62
|
+
- screenshots/screenshot1.png
|
63
|
+
- spec/counters_spec.rb
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
- split-counters.gemspec
|
66
|
+
homepage: https://github.com/bernardkroes/split-counters
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.0.6
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: Split extension for counting things per experiment and alternative
|
90
|
+
test_files:
|
91
|
+
- spec/counters_spec.rb
|
92
|
+
- spec/spec_helper.rb
|