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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +9 -0
  3. data/Guardfile +8 -0
  4. data/README.md +162 -20
  5. data/Rakefile +6 -0
  6. data/TODO.md +13 -34
  7. data/blackbeard.gemspec +5 -1
  8. data/console.rb +3 -0
  9. data/dashboard/public/bootstrap3-editable/css/bootstrap-editable.css +663 -0
  10. data/dashboard/public/bootstrap3-editable/img/clear.png +0 -0
  11. data/dashboard/public/bootstrap3-editable/img/loading.gif +0 -0
  12. data/dashboard/public/bootstrap3-editable/js/bootstrap-editable.min.js +7 -0
  13. data/dashboard/public/fonts/glyphicons-halflings-regular.eot +0 -0
  14. data/dashboard/public/fonts/glyphicons-halflings-regular.svg +229 -0
  15. data/dashboard/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  16. data/dashboard/public/fonts/glyphicons-halflings-regular.woff +0 -0
  17. data/dashboard/public/javascripts/bootstrap.min.js +7 -0
  18. data/dashboard/public/javascripts/jquery-1.10.2.min.js +6 -0
  19. data/dashboard/public/stylesheets/application.css +28 -0
  20. data/dashboard/public/stylesheets/bootstrap-theme.css +7 -0
  21. data/dashboard/public/stylesheets/bootstrap.css +7 -0
  22. data/dashboard/routes/base.rb +19 -0
  23. data/dashboard/routes/groups.rb +22 -0
  24. data/dashboard/routes/home.rb +11 -0
  25. data/dashboard/routes/metrics.rb +30 -0
  26. data/dashboard/routes/tests.rb +23 -0
  27. data/dashboard/views/groups/index.erb +22 -0
  28. data/dashboard/views/groups/show.erb +58 -0
  29. data/dashboard/views/index.erb +4 -0
  30. data/dashboard/views/layout.erb +48 -0
  31. data/dashboard/views/metrics/_metric_data.erb +59 -0
  32. data/dashboard/views/metrics/index.erb +23 -0
  33. data/dashboard/views/metrics/show.erb +73 -0
  34. data/dashboard/views/tests/index.erb +21 -0
  35. data/dashboard/views/tests/show.erb +58 -0
  36. data/lib/blackbeard/configuration.rb +8 -1
  37. data/lib/blackbeard/configuration_methods.rb +24 -0
  38. data/lib/blackbeard/context.rb +33 -21
  39. data/lib/blackbeard/dashboard.rb +17 -21
  40. data/lib/blackbeard/dashboard_helpers.rb +29 -0
  41. data/lib/blackbeard/errors.rb +2 -2
  42. data/lib/blackbeard/group.rb +35 -0
  43. data/lib/blackbeard/metric.rb +34 -32
  44. data/lib/blackbeard/metric_data/base.rb +101 -0
  45. data/lib/blackbeard/metric_data/total.rb +39 -0
  46. data/lib/blackbeard/metric_data/unique.rb +58 -0
  47. data/lib/blackbeard/metric_date.rb +11 -0
  48. data/lib/blackbeard/metric_hour.rb +17 -0
  49. data/lib/blackbeard/pirate.rb +33 -22
  50. data/lib/blackbeard/redis_store.rb +46 -2
  51. data/lib/blackbeard/selected_variation.rb +13 -0
  52. data/lib/blackbeard/storable.rb +39 -27
  53. data/lib/blackbeard/storable_attributes.rb +54 -0
  54. data/lib/blackbeard/storable_has_many.rb +60 -0
  55. data/lib/blackbeard/storable_has_set.rb +59 -0
  56. data/lib/blackbeard/test.rb +21 -0
  57. data/lib/blackbeard/version.rb +1 -1
  58. data/lib/blackbeard.rb +0 -8
  59. data/spec/configuration_spec.rb +15 -0
  60. data/spec/context_spec.rb +94 -19
  61. data/spec/dashboard/groups_spec.rb +50 -0
  62. data/spec/dashboard/home_spec.rb +20 -0
  63. data/spec/dashboard/metrics_spec.rb +57 -0
  64. data/spec/dashboard/tests_spec.rb +43 -0
  65. data/spec/group_spec.rb +36 -0
  66. data/spec/metric_data/base_spec.rb +57 -0
  67. data/spec/metric_data/total_spec.rb +116 -0
  68. data/spec/metric_data/unique_spec.rb +91 -0
  69. data/spec/metric_spec.rb +52 -44
  70. data/spec/pirate_spec.rb +32 -15
  71. data/spec/redis_store_spec.rb +121 -0
  72. data/spec/spec_helper.rb +13 -1
  73. data/spec/storable_attributes_spec.rb +47 -0
  74. data/spec/storable_has_many_spec.rb +49 -0
  75. data/spec/storable_has_set_spec.rb +39 -0
  76. data/spec/storable_spec.rb +33 -0
  77. data/spec/test_spec.rb +25 -0
  78. metadata +133 -17
  79. data/lib/blackbeard/dashboard/helpers.rb +0 -8
  80. data/lib/blackbeard/dashboard/views/layout.erb +0 -15
  81. data/lib/blackbeard/dashboard/views/metrics/index.erb +0 -10
  82. data/lib/blackbeard/dashboard/views/metrics/show.erb +0 -16
  83. data/lib/blackbeard/feature.rb +0 -13
  84. data/lib/blackbeard/metric/total.rb +0 -17
  85. data/lib/blackbeard/metric/unique.rb +0 -18
  86. data/spec/dashboard_spec.rb +0 -38
  87. data/spec/total_metric_spec.rb +0 -65
  88. data/spec/unique_metric_spec.rb +0 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 613ebf477638192eb2ab160882e277bb3b6d0f1a
4
- data.tar.gz: 55fea491870b73d48e81ff393a7ae2ba22f337d1
3
+ metadata.gz: be704d408f74d3fed4eceb91f873c087967ed947
4
+ data.tar.gz: 33e1a39fe2efeea751f99b9e769e00c72b19e99e
5
5
  SHA512:
6
- metadata.gz: 05a94bcd373c3c302a64179489a6b1efc3bd58980684f7afcd8200a690663fb9e7daed6ab01c9d5d4f3fae946d11e03326298d938ac94e24057a6da8e08d38dc
7
- data.tar.gz: 49f3612b4a6c53f0a5c8b6910ee585e17473e30071ec778318aa6a4715ea3a756ef025f8fb091f4a522a490cae6b87f0686828f72f7e1788f0202e7f79e30b7d
6
+ metadata.gz: 33d585c054f11b46334cfdbd98907d0b659decd5b4df3d06172b6c44c365ad97500461be00f7d10324ca9499f7620786271a5b22745fca72f39fb6758dbd55f5
7
+ data.tar.gz: d932724e9091f21dd1c16557cd821ac9a3c7b380391b362d0582a1cf80166381fae3cb07816dbe15f5e2fb5cff8169ede3087ae5b8d0ce9d8568dd5a56ea0271
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ services:
6
+ - redis-server
7
+ addons:
8
+ code_climate:
9
+ repo_token: c4f9d40da5073ae61d9bea245b5602eb70566eab3e47248b68dcccc25cb79b56
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
- In an initializer create your global $pirate and pass in your configuration.
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
- config.redis = Redis.new # required. Will automatically be namespaced.
29
- config.namespace = "Blackbeard" # optional
30
- config.timezone = "America/Los_Angeles" # optional
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.context(:user_id => user_id, :cookies => cookies).add_unique(:logged_in_user)
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.context(...).add_total(:like, +1) # increment a like
60
- $pirate.context(...).add_total(:like, -1) # de-increment a like
61
- $pirate.context(...).add_total(:revenue, +119.95) # can also accept floats
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
- ### Setting Context
136
+ ### Chaining Metrics
65
137
 
66
- Most of Blackbeard's calls are done via a context.
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.context(:user_id => current_user.id, :bot => false, :cookies => app_cookie_jar)
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
- To establish identity, a context must have a user_id or a cookie_jar where blackbeard will cookie a visitor with it's own uid. As cookie_jar is optional, you can collect metrics outside a web request. You can also increment metrics for users other than the one currently logged in. For example, if user A refers visitor B and vistor B joins, then you can increment User A's referrals.
172
+ The best things to test are the biggest things that don't fit into a hash of options:
73
173
 
74
- It gets pretty tedious for a web app to constantly set the context. To make that more paletable you can use a before filter in Rails (or your framework's equivalent) and the $pirate.set_context method.
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
- before_action do |controller|
78
- $pirate.set_context(
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 `set_context` you can now make calls directly on $pirate and they will be delegate to that context.
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.add_total(:like, +1)
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
@@ -1 +1,7 @@
1
+ #!/usr/bin/env rake
1
2
  require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec')
6
+
7
+ task :default => :spec
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.variation(:link, :control) do
63
- something.blue
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
- You can run them in crons or asynchronously.
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.6"
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
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+ require 'blackbeard'
3
+