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 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
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec')
6
+
7
+ task :default => :spec
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,5 @@
1
+ module Split
2
+ module Counters
3
+ VERSION = "0.3.0"
4
+ end
5
+ end
@@ -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,11 @@
1
+ module Split
2
+ module CountersdashboardHelpers
3
+ def url(*path_parts)
4
+ [ path_prefix, path_parts ].join("/").squeeze('/')
5
+ end
6
+
7
+ def path_prefix
8
+ request.env['SCRIPT_NAME']
9
+ end
10
+ end
11
+ 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
@@ -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