split-counters 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Counters Dashboard](/screenshots/screenshot1.png?raw=true "Counters Dashboard")
|
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
|