blackbeard 0.0.2.0 → 0.0.3.1
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 +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
|
+
[](https://codeclimate.com/repos/5300f44be30ba0790d01b5a7/feed) [](https://codeclimate.com/repos/5300f44be30ba0790d01b5a7/feed) [](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