blackbeard 0.0.2.0 → 0.0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +9 -0
- data/Guardfile +8 -0
- data/README.md +162 -20
- data/Rakefile +6 -0
- data/TODO.md +13 -34
- data/blackbeard.gemspec +5 -1
- data/console.rb +3 -0
- data/dashboard/public/bootstrap3-editable/css/bootstrap-editable.css +663 -0
- data/dashboard/public/bootstrap3-editable/img/clear.png +0 -0
- data/dashboard/public/bootstrap3-editable/img/loading.gif +0 -0
- data/dashboard/public/bootstrap3-editable/js/bootstrap-editable.min.js +7 -0
- data/dashboard/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/dashboard/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/dashboard/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/dashboard/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/dashboard/public/javascripts/bootstrap.min.js +7 -0
- data/dashboard/public/javascripts/jquery-1.10.2.min.js +6 -0
- data/dashboard/public/stylesheets/application.css +28 -0
- data/dashboard/public/stylesheets/bootstrap-theme.css +7 -0
- data/dashboard/public/stylesheets/bootstrap.css +7 -0
- data/dashboard/routes/base.rb +19 -0
- data/dashboard/routes/groups.rb +22 -0
- data/dashboard/routes/home.rb +11 -0
- data/dashboard/routes/metrics.rb +30 -0
- data/dashboard/routes/tests.rb +23 -0
- data/dashboard/views/groups/index.erb +22 -0
- data/dashboard/views/groups/show.erb +58 -0
- data/dashboard/views/index.erb +4 -0
- data/dashboard/views/layout.erb +48 -0
- data/dashboard/views/metrics/_metric_data.erb +59 -0
- data/dashboard/views/metrics/index.erb +23 -0
- data/dashboard/views/metrics/show.erb +73 -0
- data/dashboard/views/tests/index.erb +21 -0
- data/dashboard/views/tests/show.erb +58 -0
- data/lib/blackbeard/configuration.rb +8 -1
- data/lib/blackbeard/configuration_methods.rb +24 -0
- data/lib/blackbeard/context.rb +33 -21
- data/lib/blackbeard/dashboard.rb +17 -21
- data/lib/blackbeard/dashboard_helpers.rb +29 -0
- data/lib/blackbeard/errors.rb +2 -2
- data/lib/blackbeard/group.rb +35 -0
- data/lib/blackbeard/metric.rb +34 -32
- data/lib/blackbeard/metric_data/base.rb +101 -0
- data/lib/blackbeard/metric_data/total.rb +39 -0
- data/lib/blackbeard/metric_data/unique.rb +58 -0
- data/lib/blackbeard/metric_date.rb +11 -0
- data/lib/blackbeard/metric_hour.rb +17 -0
- data/lib/blackbeard/pirate.rb +33 -22
- data/lib/blackbeard/redis_store.rb +46 -2
- data/lib/blackbeard/selected_variation.rb +13 -0
- data/lib/blackbeard/storable.rb +39 -27
- data/lib/blackbeard/storable_attributes.rb +54 -0
- data/lib/blackbeard/storable_has_many.rb +60 -0
- data/lib/blackbeard/storable_has_set.rb +59 -0
- data/lib/blackbeard/test.rb +21 -0
- data/lib/blackbeard/version.rb +1 -1
- data/lib/blackbeard.rb +0 -8
- data/spec/configuration_spec.rb +15 -0
- data/spec/context_spec.rb +94 -19
- data/spec/dashboard/groups_spec.rb +50 -0
- data/spec/dashboard/home_spec.rb +20 -0
- data/spec/dashboard/metrics_spec.rb +57 -0
- data/spec/dashboard/tests_spec.rb +43 -0
- data/spec/group_spec.rb +36 -0
- data/spec/metric_data/base_spec.rb +57 -0
- data/spec/metric_data/total_spec.rb +116 -0
- data/spec/metric_data/unique_spec.rb +91 -0
- data/spec/metric_spec.rb +52 -44
- data/spec/pirate_spec.rb +32 -15
- data/spec/redis_store_spec.rb +121 -0
- data/spec/spec_helper.rb +13 -1
- data/spec/storable_attributes_spec.rb +47 -0
- data/spec/storable_has_many_spec.rb +49 -0
- data/spec/storable_has_set_spec.rb +39 -0
- data/spec/storable_spec.rb +33 -0
- data/spec/test_spec.rb +25 -0
- metadata +133 -17
- data/lib/blackbeard/dashboard/helpers.rb +0 -8
- data/lib/blackbeard/dashboard/views/layout.erb +0 -15
- data/lib/blackbeard/dashboard/views/metrics/index.erb +0 -10
- data/lib/blackbeard/dashboard/views/metrics/show.erb +0 -16
- data/lib/blackbeard/feature.rb +0 -13
- data/lib/blackbeard/metric/total.rb +0 -17
- data/lib/blackbeard/metric/unique.rb +0 -18
- data/spec/dashboard_spec.rb +0 -38
- data/spec/total_metric_spec.rb +0 -65
- data/spec/unique_metric_spec.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be704d408f74d3fed4eceb91f873c087967ed947
|
4
|
+
data.tar.gz: 33e1a39fe2efeea751f99b9e769e00c72b19e99e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33d585c054f11b46334cfdbd98907d0b659decd5b4df3d06172b6c44c365ad97500461be00f7d10324ca9499f7620786271a5b22745fca72f39fb6758dbd55f5
|
7
|
+
data.tar.gz: d932724e9091f21dd1c16557cd821ac9a3c7b380391b362d0582a1cf80166381fae3cb07816dbe15f5e2fb5cff8169ede3087ae5b8d0ce9d8568dd5a56ea0271
|
data/.travis.yml
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
2
|
+
watch(%r{^spec/.+_spec\.rb$})
|
3
|
+
watch(%r{^lib/blackbeard/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
4
|
+
watch(%r{^dashboard/routes/(.+)\.rb$}) { |m| "spec/dashboard/#{m[1]}_spec.rb" }
|
5
|
+
watch(%r{^dashboard/views/(.+)/.+\.erb$}) { |m| "spec/dashboard/#{m[1]}_spec.rb" }
|
6
|
+
watch('spec/spec_helper.rb') { "spec" }
|
7
|
+
end
|
8
|
+
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Blackbeard is a Redis backed metrics collection system with a Rack dashboard.
|
4
4
|
|
5
|
+
[![Code Climate](https://codeclimate.com/repos/5300f44be30ba0790d01b5a7/badges/b912a89a38a56f61398a/gpa.png)](https://codeclimate.com/repos/5300f44be30ba0790d01b5a7/feed) [![Code Climate](https://codeclimate.com/repos/5300f44be30ba0790d01b5a7/badges/b912a89a38a56f61398a/coverage.png)](https://codeclimate.com/repos/5300f44be30ba0790d01b5a7/feed) [![Build Status](https://travis-ci.org/goldstar/blackbeard.png?branch=master)](https://travis-ci.org/goldstar/blackbeard)
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
Add this line to your application's Gemfile:
|
@@ -18,27 +20,97 @@ Or install it yourself as:
|
|
18
20
|
|
19
21
|
## Usage
|
20
22
|
|
21
|
-
|
23
|
+
All Blackbeard operations are done via the global `$pirate` which you should initialize early in your app.
|
24
|
+
|
25
|
+
### Rails
|
26
|
+
|
27
|
+
To configure Rails 3+ create an initializer in `config/initializers`
|
22
28
|
|
23
29
|
```ruby
|
24
30
|
require 'blackbeard'
|
25
31
|
require 'blackbeard/dashboard'
|
26
32
|
|
27
33
|
$pirate = Blackbeard.pirate do |config|
|
28
|
-
|
29
|
-
|
30
|
-
|
34
|
+
# see configuration options below
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
And inside your ApplicationController:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
before_filter { |c| $pirate.set_context(c.current_user, c.request) }, :unless => robot?
|
42
|
+
after_filter{ |c| $pirate.clear_context }
|
43
|
+
```
|
44
|
+
|
45
|
+
### Sinatra
|
46
|
+
|
47
|
+
To configure Sinatra
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require 'blackbeard'
|
51
|
+
require 'blackbeard/dashboard'
|
52
|
+
|
53
|
+
class MySinatraApp < Sinatra::Base
|
54
|
+
enable :sessions
|
55
|
+
|
56
|
+
$pirate = Blackbead.pirate do |config|
|
57
|
+
# see configuration options below
|
58
|
+
end
|
59
|
+
|
60
|
+
before do
|
61
|
+
$pirate.set_context(current_user, self) unless robot?
|
62
|
+
end
|
63
|
+
|
64
|
+
after do
|
65
|
+
$pirate.clear_context
|
66
|
+
end
|
67
|
+
|
68
|
+
...
|
31
69
|
end
|
32
70
|
```
|
33
71
|
|
72
|
+
### Configuration options
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
$pirate = Blackbeard.pirate do |config|
|
76
|
+
config.redis = Redis.new # required. Will automatically be namespaced.
|
77
|
+
config.namespace = "Blackbeard" # optional
|
78
|
+
config.timezone = "America/Los_Angeles" # optional
|
79
|
+
config.guest_method = :guest? # optional method to call on user objects if they are guests or visitors
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
34
83
|
Note that the configuration is shared by all pirates, so only create one pirate at a time.
|
35
84
|
|
85
|
+
### Mounting the dashboard
|
86
|
+
|
36
87
|
To get the rack dashboard on Rails, mount it in your `routes.rb`. Don't forget to protect it with some constraints.
|
37
88
|
|
38
89
|
```ruby
|
39
90
|
mount Blackbeard::Dashboard => '/blackbeard', :constraints => ConstraintClassYouCreate.new
|
40
91
|
```
|
41
92
|
|
93
|
+
### Setting Context
|
94
|
+
|
95
|
+
Most of Blackbeard's calls are done via a context.
|
96
|
+
|
97
|
+
In a web request, this is handled by a before filter:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
before_filter { |controller| $pirate.set_context(controller.current_user, controller) }, :unless => robot?
|
101
|
+
after_filter { |controller| $pirate.clear_context }
|
102
|
+
```
|
103
|
+
|
104
|
+
Outside of a web request--or if you want to reference a user other than the one in the current request (e..g referrals)--you set the context before each call to `$pirate`.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
$pirate.context(user).add_metric(:referral)
|
108
|
+
```
|
109
|
+
|
110
|
+
If a context does not exist, `$pirate` will silently ignore all actions. This is useful for dealing with bots.
|
111
|
+
|
112
|
+
If the user is unidentied set user to nil or false. If your app can return a Guest object for unidentied users, see the guest configuration setting.
|
113
|
+
|
42
114
|
### Collecting Metrics
|
43
115
|
|
44
116
|
In your app, have your pirate collect the important metrics.
|
@@ -48,7 +120,7 @@ In your app, have your pirate collect the important metrics.
|
|
48
120
|
Unique counts are for metrics wherein a user may trigger the same metric twice, but should only be counted once.
|
49
121
|
|
50
122
|
```ruby
|
51
|
-
$pirate.
|
123
|
+
$pirate.add_unique(:logged_in_user)
|
52
124
|
```
|
53
125
|
|
54
126
|
#### Counting Non-Unique Metrics
|
@@ -56,38 +128,108 @@ $pirate.context(:user_id => user_id, :cookies => cookies).add_unique(:logged_in_
|
|
56
128
|
Non-unique counts are for metrics wherein a user may trigger the same metric multiple times and the amounts are summed up.
|
57
129
|
|
58
130
|
```ruby
|
59
|
-
$pirate.
|
60
|
-
$pirate.
|
61
|
-
$pirate.
|
131
|
+
$pirate.add_total(:like, +1) # increment a like
|
132
|
+
$pirate.add_total(:like, -1) # de-increment a like
|
133
|
+
$pirate.add_total(:revenue, +119.95) # can also accept floats
|
62
134
|
```
|
63
135
|
|
64
|
-
###
|
136
|
+
### Chaining Metrics
|
65
137
|
|
66
|
-
|
138
|
+
Context methods that inrement metrics always return the context, so you can chain them together.
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
$pirate.set_context(user)
|
142
|
+
$pirate.add_total(:like, +1).add_unique(:likers)
|
143
|
+
|
144
|
+
$pirate.context(user).add_total(:like, +1).add_unique(:likers)
|
145
|
+
```
|
146
|
+
|
147
|
+
|
148
|
+
### Defining Tests (changes or features)
|
149
|
+
|
150
|
+
Features are defined in your views, controller or anywhere in your app via the global $pirate. There is no configuration necessary (but see the gotcha below).
|
151
|
+
|
152
|
+
|
153
|
+
In a view:
|
154
|
+
|
155
|
+
```erb
|
156
|
+
<%= $pirate.ab_test(:new_onboarding, :control => '/onboarding', :welcome_flow => '/welcome') %>
|
157
|
+
```
|
158
|
+
|
159
|
+
In a controller:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
@onboarding_path = $pirate.ab_test(:new_onboarding, :control => '/onboarding', :welcome_flow => '/welcome') %>
|
163
|
+
```
|
164
|
+
|
165
|
+
You can call the feature multiple times with different variations:
|
67
166
|
|
68
167
|
```ruby
|
69
|
-
$pirate.
|
168
|
+
@button_bg_color = $pirate.ab_test(:button_color, :control => "#FFF", :black => "#000")
|
169
|
+
@button_text_color = $pirate.ab_test(:button_color, :control => "#CCC", :black => "#FFF")
|
70
170
|
```
|
71
171
|
|
72
|
-
|
172
|
+
The best things to test are the biggest things that don't fit into a hash of options:
|
73
173
|
|
74
|
-
|
174
|
+
```erb
|
175
|
+
<% if $pirate.ab_test(:join_form) == :long_version do %>
|
176
|
+
<!-- extra field here -->
|
177
|
+
<% end %>
|
178
|
+
```
|
75
179
|
|
76
180
|
```ruby
|
77
|
-
|
78
|
-
|
79
|
-
:user_id => controller.current_user.id,
|
80
|
-
:bot => controller.bot?,
|
81
|
-
:cookies => controller.cookies)
|
181
|
+
if $pirate.ab_test(:join_form) == :long_version do
|
182
|
+
# validate the extra fields
|
82
183
|
end
|
83
184
|
```
|
84
185
|
|
85
|
-
If you
|
186
|
+
If you're simply rolling out a feature or want a feature flipper, you can:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
if $pirate.active?(:friend_feed){ ... } # shorthand for test(:friend_feed) == :active
|
190
|
+
```
|
191
|
+
|
192
|
+
GOTCHA #1: It's good to avoid elsif and case statements when testing for features. Blackbeard learns about the features and their variations dynamically. If you're not passing in your variations as a hash, but only using conditionals, you can ensure all your variations are available with:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
$pirate.test(:friend_feed).add_variations(:variation_one, :variation_two, ...)
|
196
|
+
```
|
197
|
+
|
198
|
+
Look at the dashboard to see which variations are registered.
|
199
|
+
|
200
|
+
Features do not turn on automatically. When you first deploy the feature will be set to `:inactive` or `:control`, `:off`, or `:default` if any of those variations are defined.
|
201
|
+
|
202
|
+
GOTCHA #2: If you do not define the :inactive, :control, :off or :default variations, the result will be nil. This is the desired behavior but it may be confusing.
|
86
203
|
|
87
204
|
```ruby
|
88
|
-
$pirate.
|
205
|
+
$pirate.ab_test(:new_onboarding, :one => 'one', :two => 'two') # is the same as the next line
|
206
|
+
$pirate.ab_test(:new_onboarding, :inactive => nil, :one => 'one', :two => 'two') # nil when feature is inactive
|
207
|
+
$pirate.ab_test(:new_onboarding, :default => 'one', :two => 'two') # => 'one' when feature is inactive
|
89
208
|
```
|
90
209
|
|
210
|
+
### Defining groups
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
$pirate.define_group(:admin) do |user, context|
|
214
|
+
user.admin? # true, false
|
215
|
+
end
|
216
|
+
|
217
|
+
$pirate.define_group(:medalist) do |user, context|
|
218
|
+
user.engagement_level # nil, :bronze, :silver, :gold
|
219
|
+
end
|
220
|
+
|
221
|
+
$pirate.define_group(:seo_traffic) do |user, context|
|
222
|
+
context.session.refer =~ /google.com/ # remember to store refer in sessions
|
223
|
+
end
|
224
|
+
|
225
|
+
$pirate.define_group(:seo_traffic) do |user, context|
|
226
|
+
context.session.refer =~ /google.com/ # remember to store refer in sessions
|
227
|
+
end
|
228
|
+
|
229
|
+
$pirate.define_group(:purchasers) do |user, context|
|
230
|
+
user.purchases.any?
|
231
|
+
end
|
232
|
+
```
|
91
233
|
|
92
234
|
## Contributing
|
93
235
|
|
data/Rakefile
CHANGED
data/TODO.md
CHANGED
@@ -16,6 +16,16 @@
|
|
16
16
|
* by defined segments (e.g. staff?)
|
17
17
|
* by user_id (e.g. rollout like)
|
18
18
|
|
19
|
+
### Documentation
|
20
|
+
|
21
|
+
### API and javascript $pirate
|
22
|
+
|
23
|
+
### Funnels - collection of metrics
|
24
|
+
|
25
|
+
hour: hash each "uid->from->to" => segment
|
26
|
+
day: hash each "segment->from->to" => count
|
27
|
+
week: hash each "segment->from->to" => count
|
28
|
+
|
19
29
|
|
20
30
|
Create and manage multiple experiments in a browser/rack dashboard
|
21
31
|
|
@@ -37,41 +47,10 @@ $pirate.experiment(
|
|
37
47
|
$pirate.funnel(:checkout, 'Confirm') # User reached step 3 of funnel (Confirm)
|
38
48
|
```
|
39
49
|
|
40
|
-
### Segments
|
41
|
-
|
42
|
-
```ruby
|
43
|
-
$pirate.segment(:premium_member) do
|
44
|
-
current_user.premium_member? # true or false
|
45
|
-
end
|
46
|
-
|
47
|
-
$pirate.segment(:organization_size) do
|
48
|
-
current_user.organization_size # '0-5', '6-14', '15+'
|
49
|
-
end
|
50
|
-
```
|
51
|
-
|
52
|
-
### Changes
|
53
|
-
|
54
|
-
```ruby
|
55
|
-
$pirate.change(:link, :control => 'blue', :red => 'red')
|
56
|
-
$pirate.change(:link,
|
57
|
-
:control => nil, # noop
|
58
|
-
:red => Proc.new("red")
|
59
|
-
)
|
60
|
-
|
61
50
|
|
62
|
-
$pirate.
|
63
|
-
|
51
|
+
$pirate.define_group(:achieve_pirate_style) do |user, context|
|
52
|
+
$pirate.metric(:pirate_style).achieved?
|
64
53
|
end
|
65
54
|
|
66
|
-
$pirate.variation(:link, :red) do
|
67
|
-
...
|
68
|
-
end
|
69
|
-
```
|
70
|
-
|
71
|
-
### Outside a Web Session
|
72
55
|
|
73
|
-
|
74
|
-
|
75
|
-
```ruby
|
76
|
-
$pirate.context().goal(...)
|
77
|
-
$pirate.context()
|
56
|
+
http://blog.sourcing.io/structuring-sinatra?utm_content=buffer1955d&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer
|
data/blackbeard.gemspec
CHANGED
@@ -20,10 +20,14 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
22
|
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
-
spec.add_development_dependency "rspec", "~> 2.
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.14.1"
|
24
24
|
spec.add_development_dependency 'rack-test', '~> 0.6'
|
25
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.2.5'
|
26
|
+
spec.add_development_dependency 'terminal-notifier-guard'
|
27
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
25
28
|
|
26
29
|
spec.add_runtime_dependency "sinatra-base", "~> 1.4"
|
30
|
+
spec.add_runtime_dependency "sinatra-partial", "~> 0.4.0"
|
27
31
|
spec.add_runtime_dependency "tzinfo", "~> 0.3"
|
28
32
|
spec.add_runtime_dependency 'redis', '~> 3.0', '>= 3.0.4'
|
29
33
|
spec.add_runtime_dependency 'redis-namespace', '~> 1.4', '>= 1.4.1'
|
data/console.rb
ADDED