blackbeard 0.0.4.0 → 0.0.5.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +44 -16
  3. data/TODO.md +3 -1
  4. data/dashboard/routes/cohorts.rb +25 -0
  5. data/dashboard/routes/features.rb +7 -4
  6. data/dashboard/routes/groups.rb +6 -3
  7. data/dashboard/routes/metrics.rb +25 -5
  8. data/dashboard/routes/tests.rb +6 -3
  9. data/dashboard/views/cohorts/index.erb +22 -0
  10. data/dashboard/views/cohorts/show.erb +41 -0
  11. data/dashboard/views/groups/show.erb +2 -2
  12. data/dashboard/views/layout.erb +1 -0
  13. data/dashboard/views/metrics/show.erb +37 -10
  14. data/dashboard/views/shared/_charts.erb +20 -0
  15. data/lib/blackbeard/chart.rb +65 -0
  16. data/lib/blackbeard/chartable.rb +47 -0
  17. data/lib/blackbeard/cohort.rb +48 -0
  18. data/lib/blackbeard/cohort_data.rb +72 -0
  19. data/lib/blackbeard/cohort_metric.rb +47 -0
  20. data/lib/blackbeard/context.rb +11 -2
  21. data/lib/blackbeard/dashboard.rb +2 -3
  22. data/lib/blackbeard/dashboard_helpers.rb +0 -22
  23. data/lib/blackbeard/errors.rb +2 -0
  24. data/lib/blackbeard/group.rb +3 -0
  25. data/lib/blackbeard/group_metric.rb +39 -0
  26. data/lib/blackbeard/metric.rb +30 -14
  27. data/lib/blackbeard/metric_data/base.rb +18 -35
  28. data/lib/blackbeard/metric_data/total.rb +2 -1
  29. data/lib/blackbeard/metric_data/uid_generator.rb +38 -0
  30. data/lib/blackbeard/metric_data/unique.rb +1 -1
  31. data/lib/blackbeard/metric_date.rb +10 -0
  32. data/lib/blackbeard/metric_hour.rb +8 -0
  33. data/lib/blackbeard/pirate.rb +18 -0
  34. data/lib/blackbeard/redis_store.rb +10 -1
  35. data/lib/blackbeard/storable.rb +1 -0
  36. data/lib/blackbeard/version.rb +1 -1
  37. data/spec/chart_spec.rb +38 -0
  38. data/spec/chartable_spec.rb +56 -0
  39. data/spec/cohort_data_spec.rb +142 -0
  40. data/spec/cohort_metric_spec.rb +26 -0
  41. data/spec/cohort_spec.rb +31 -0
  42. data/spec/context_spec.rb +9 -1
  43. data/spec/dashboard/cohorts_spec.rb +43 -0
  44. data/spec/dashboard/groups_spec.rb +0 -7
  45. data/spec/dashboard/metrics_spec.rb +35 -0
  46. data/spec/group_metric_spec.rb +26 -0
  47. data/spec/metric_data/base_spec.rb +0 -16
  48. data/spec/metric_data/uid_generator_spec.rb +40 -0
  49. data/spec/metric_spec.rb +23 -12
  50. data/spec/pirate_spec.rb +22 -1
  51. data/spec/redis_store_spec.rb +8 -2
  52. data/spec/storable_spec.rb +3 -0
  53. metadata +29 -3
  54. data/dashboard/views/metrics/_metric_data.erb +0 -59
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Blackbeard
4
+ describe CohortMetric do
5
+ let(:metric) { Metric.create(:total, "one-total") }
6
+ let(:cohort) { Cohort.create(:example) }
7
+ let(:cohort_metric){ CohortMetric.new( cohort, metric) }
8
+ let(:metric_data) { cohort_metric.metric_data }
9
+
10
+ describe "add" do
11
+ let(:context) { double(:unique_identifier => 'uid', :controller => double, :user => double) }
12
+
13
+ it "should increment metric data" do
14
+ cohort.should_receive(:hour_id_for_participant).with('uid').and_return("2014010101")
15
+ metric_data.should_receive(:add_at).with("2014010101","uid",1)
16
+ cohort_metric.add(context, 1)
17
+ end
18
+
19
+ it "should not increment non participants" do
20
+ cohort.should_receive(:hour_id_for_participant).with('uid').and_return(nil)
21
+ metric_data.should_not_receive(:add_at).with("2014010101","uid",1)
22
+ cohort_metric.add(context, 1)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Blackbeard
4
+ describe Cohort do
5
+ let(:uid){ "an id" }
6
+ let(:hour){ Time.new(2014,3,5,1) }
7
+ let(:cohort) { Cohort.new(:happy) }
8
+ let(:cohort_data) { CohortData.new(cohort) }
9
+
10
+ describe "add" do
11
+ let(:context){ double :unique_identifier => uid }
12
+ before :each do
13
+ cohort.stub(:data).and_return(cohort_data)
14
+ end
15
+
16
+ it "should save the cohort if its new" do
17
+ cohort.should_receive(:save)
18
+ cohort.add(context)
19
+ end
20
+ it "should call add_with_force if forced used" do
21
+ cohort_data.should_receive(:add_with_force).with(uid, hour).and_return(true)
22
+ cohort.add(context, hour, true).should be_true
23
+ end
24
+ it "should call add_without_force if not forced used" do
25
+ cohort_data.should_receive(:add_without_force).with(uid, hour).and_return(true)
26
+ cohort.add(context, hour).should be_true
27
+ end
28
+ end
29
+
30
+ end
31
+ end
data/spec/context_spec.rb CHANGED
@@ -9,6 +9,7 @@ module Blackbeard
9
9
  let(:total_metric) { Metric.create(:total, :things) }
10
10
  let(:unique_metric) { Metric.create(:unique, :things) }
11
11
  let(:test) { Test.create(:example_test) }
12
+ let(:cohort) { Cohort.create(:joined) }
12
13
 
13
14
  describe "#add_total" do
14
15
  it "should call add on the total metric" do
@@ -26,6 +27,14 @@ module Blackbeard
26
27
  end
27
28
  end
28
29
 
30
+ describe "#add_to_cohort" do
31
+ it "should call add on the cohort" do
32
+ pirate.should_receive(:cohort).with("joined"){ cohort }
33
+ cohort.should_receive(:add).with(context, nil, false)
34
+ context.add_to_cohort(:joined)
35
+ end
36
+ end
37
+
29
38
  describe "#ab_test" do
30
39
  before :each do
31
40
  pirate.should_receive(:test).with(test.id).and_return(test)
@@ -94,7 +103,6 @@ module Blackbeard
94
103
  inactive_feature.should_receive(:active_for?).with(context).and_return(false)
95
104
  context.feature_active?(:inactive_feature).should be_false
96
105
  end
97
-
98
106
  end
99
107
 
100
108
  end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + './../spec_helper')
2
+
3
+ require 'rack/test'
4
+ require 'blackbeard/dashboard'
5
+
6
+ module Blackbeard
7
+ describe Dashboard do
8
+ include Rack::Test::Methods
9
+
10
+ let(:app) { Dashboard }
11
+
12
+ describe "get /cohorts" do
13
+ it "should list all the groups" do
14
+ Cohort.create("jostling")
15
+ get "/cohorts"
16
+
17
+ last_response.should be_ok
18
+ last_response.body.should include('cohorts')
19
+ end
20
+ end
21
+
22
+ describe "get /cohorts/:id" do
23
+ it "should show a metric" do
24
+ cohort = Cohort.create("jostling")
25
+ get "/cohorts/#{cohort.id}"
26
+
27
+ last_response.should be_ok
28
+ last_response.body.should include("jostling")
29
+ end
30
+ end
31
+
32
+ describe "post /cohorts/:id" do
33
+ it "should update the cohort" do
34
+ cohort = Cohort.create("jostling")
35
+ post "/cohorts/#{cohort.id}", :name => 'hello'
36
+
37
+ last_response.should be_ok
38
+ Cohort.find("jostling").name.should == 'hello'
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -9,13 +9,6 @@ module Blackbeard
9
9
 
10
10
  let(:app) { Dashboard }
11
11
 
12
- describe "get /" do
13
- it "should redirect" do
14
- get "/"
15
- last_response.should be_ok
16
- end
17
- end
18
-
19
12
  describe "get /groups" do
20
13
  it "should list all the groups" do
21
14
  Group.create("jostling")
@@ -27,6 +27,27 @@ module Blackbeard
27
27
  last_response.should be_ok
28
28
  last_response.body.should include("jostling")
29
29
  end
30
+
31
+ it "should show a metric with a group" do
32
+ metric = Metric.create("total", "jostling")
33
+ group = Group.create(:example)
34
+ metric.add_group(group)
35
+ get "/metrics/#{metric.type}/#{metric.type_id}", :group_id => group.id
36
+
37
+ last_response.should be_ok
38
+ last_response.body.should include("jostling")
39
+ end
40
+
41
+ it "should show a metric with a cohort" do
42
+ metric = Metric.create("total", "jostling")
43
+ cohort = Cohort.create(:example)
44
+ metric.add_cohort(cohort)
45
+ get "/metrics/#{metric.type}/#{metric.type_id}", :cohort_id => cohort.id
46
+
47
+ last_response.should be_ok
48
+ last_response.body.should include("jostling")
49
+ end
50
+
30
51
  end
31
52
 
32
53
  describe "post /metrics/:type/:type_id" do
@@ -53,5 +74,19 @@ module Blackbeard
53
74
  end
54
75
 
55
76
 
77
+ describe "post /metrics/:type/:type_id/cohorts" do
78
+ it "should add a cohort to the metric" do
79
+ metric = Metric.create("total", "jostling")
80
+ cohort = Cohort.create("admin")
81
+ post "/metrics/#{metric.type}/#{metric.type_id}/cohorts", :cohort_id => cohort.id
82
+
83
+ last_response.should be_redirect
84
+ follow_redirect!
85
+ last_request.url.should == "http://example.org/metrics/#{metric.type}/#{metric.type_id}?cohort_id=#{cohort.id}"
86
+ metric.has_cohort?(cohort).should be_true
87
+ end
88
+ end
89
+
90
+
56
91
  end
57
92
  end
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Blackbeard
4
+ describe GroupMetric do
5
+ let(:metric) { Metric.create(:total, "one-total") }
6
+ let(:group) { Group.create(:example) }
7
+ let(:metric_data) { group_metric.metric_data }
8
+ # let(:group_metric_data) { metric.metric_data(group) }
9
+ let(:group_metric){ GroupMetric.new( group, metric) }
10
+ describe "add" do
11
+ let(:context) { double(:unique_identifier => 'uid', :controller => double, :user => double) }
12
+
13
+ it "should increment metric data" do
14
+ group.stub(:segment_for).and_return("segment")
15
+ metric_data.should_receive(:add).with("uid",1, "segment")
16
+ group_metric.add(context, 1)
17
+ end
18
+
19
+ it "should not increment nil segments" do
20
+ group.stub(:segment_for).and_return(nil)
21
+ metric_data.should_not_receive(:add).with("uid",1, "segment")
22
+ group_metric.add(context, 1)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -27,22 +27,6 @@ module Blackbeard
27
27
  end
28
28
  end
29
29
 
30
- describe "recent_hours" do
31
- let(:start_at) { Time.new(2014,1,1,12,0,0) }
32
-
33
- it "should return results for recent hours" do
34
- metric_data.recent_hours(3, start_at).should have(3).metric_hours
35
- end
36
- end
37
-
38
- describe "recent_days" do
39
- let(:start_on) { Date.new(2014,1,3) }
40
-
41
- it "should return results for recent days" do
42
- metric_data.recent_days(3, start_on).should have(3).metric_days
43
- end
44
- end
45
-
46
30
  describe "hour_keys_for_day" do
47
31
  it "should return 1 key for every hour from morning to night" do
48
32
  keys_for_day = metric_data.hour_keys_for_day(Date.new(2014,1,1))
@@ -0,0 +1,40 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ module Blackbeard
4
+ module MetricData
5
+
6
+ describe UidGenerator do
7
+ let(:metric) { Blackbeard::Metric.new(:total, "one-total") }
8
+ let(:metric2) { Blackbeard::Metric.new(:total, "two-total") }
9
+ let(:metric_data) { metric.metric_data }
10
+ let(:metric_data2) { metric2.metric_data }
11
+
12
+ context "already existing uid" do
13
+ it "should return the existing uid" do
14
+ UidGenerator.new(metric_data).uid.should == UidGenerator.new(metric_data).uid
15
+ end
16
+ end
17
+ context "new metric_data" do
18
+ it "should increment to the next uid" do
19
+ uid = UidGenerator.new(metric_data).uid
20
+ UidGenerator.new(metric_data2).uid.to_i.should == uid.to_i + 1
21
+ end
22
+ end
23
+
24
+ describe "lookup field" do
25
+ context "with a cohort" do
26
+ it "should return the field unique to cohort" do
27
+ metric.save
28
+ cohort = Cohort.create(:example_cohort)
29
+ cohort_metric = CohortMetric.new(cohort, metric)
30
+ metric.add_cohort(cohort)
31
+ gen1 = UidGenerator.new(cohort_metric.metric_data)
32
+ gen2 = UidGenerator.new(metric.metric_data)
33
+ gen1.send(:lookup_field).should_not == gen2.send(:lookup_field)
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
data/spec/metric_spec.rb CHANGED
@@ -3,9 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
  module Blackbeard
4
4
  describe Metric do
5
5
  let(:metric) { Metric.create(:total, "one-total") }
6
- let(:group) { Group.create(:example) }
7
6
  let(:metric_data) { metric.metric_data }
8
- let(:group_metric_data) { metric.metric_data(group) }
9
7
 
10
8
  describe "self.all" do
11
9
  before :each do
@@ -20,31 +18,31 @@ module Blackbeard
20
18
 
21
19
  describe "add" do
22
20
  let(:context) { double(:unique_identifier => 'uid', :controller => double, :user => double) }
23
- before :each do
24
- metric.stub(:groups).and_return([group])
25
- end
26
21
 
27
22
  it "should increment metric data" do
28
23
  metric_data.should_receive(:add).with("uid",1)
29
24
  metric.add(context, 1)
30
25
  end
31
26
 
32
- it "should increment metric data for each group" do
33
- group.stub(:segment_for).and_return("segment")
34
- group_metric_data.should_receive(:add).with("uid", 1, "segment" )
27
+ it "should call add on all group metrics" do
28
+ group_metric = double
29
+ metric.should_receive(:group_metrics).and_return([group_metric])
30
+ group_metric.should_receive(:add).with(context, 1)
35
31
  metric.add(context, 1)
36
32
  end
37
33
 
38
- it "should not increment nil segments" do
39
- group.stub(:segment_for).and_return(nil)
40
- group_metric_data.should_not_receive(:add)
34
+ it "should call add on all cohort metrics" do
35
+ cohort_metric = double
36
+ metric.should_receive(:cohort_metrics).and_return([cohort_metric])
37
+ cohort_metric.should_receive(:add).with(context, 1)
41
38
  metric.add(context, 1)
42
39
  end
40
+
43
41
  end
44
42
 
45
43
  describe "addable_groups" do
44
+ let!(:group) { Group.create(:example) }
46
45
  it "should include the groups not added" do
47
- group # to initialize it
48
46
  metric.addable_groups.map{|g| g.id }.should include(group.id)
49
47
  end
50
48
 
@@ -54,5 +52,18 @@ module Blackbeard
54
52
  end
55
53
  end
56
54
 
55
+ describe "addable_cohorts" do
56
+ let!(:cohort){ Cohort.create(:example)}
57
+
58
+ it "should include the groups not added" do
59
+ metric.addable_cohorts.map{|g| g.id }.should include(cohort.id)
60
+ end
61
+
62
+ it "should not include the group added" do
63
+ metric.add_cohort(cohort)
64
+ metric.addable_cohorts.map{|c| c.id }.should_not include(cohort.id)
65
+ end
66
+ end
67
+
57
68
  end
58
69
  end
data/spec/pirate_spec.rb CHANGED
@@ -21,6 +21,11 @@ describe Blackbeard::Pirate do
21
21
  4.times{ pirate.feature(name) }
22
22
  end
23
23
 
24
+ it "should get cohorts once" do
25
+ Blackbeard::Cohort.should_receive(:find_or_create).with(name).once.and_return(double)
26
+ 4.times{ pirate.cohort(name) }
27
+ end
28
+
24
29
  end
25
30
 
26
31
  describe "#context" do
@@ -45,10 +50,14 @@ describe Blackbeard::Pirate do
45
50
  expect{ pirate.ab_test(:example, :on => 1, :off => 2) }.to_not raise_error
46
51
  end
47
52
 
48
- it "active? should not raise error" do
53
+ it "feature_active? should not raise error" do
49
54
  expect{ pirate.feature_active?(:example) }.to_not raise_error
50
55
  end
51
56
 
57
+ it "add_to_cohort should not raise error" do
58
+ expect{ pirate.add_to_cohort(:example) }.to_not raise_error
59
+ end
60
+
52
61
  end
53
62
  context "with context set" do
54
63
  let(:user){ double }
@@ -73,6 +82,18 @@ describe Blackbeard::Pirate do
73
82
  set_context.should_receive(:feature_active?).with(:example_feature).and_return(false)
74
83
  pirate.feature_active?(:example_feature)
75
84
  end
85
+
86
+ it "should delegate add_to_cohort" do
87
+ timestamp = double
88
+ set_context.should_receive(:add_to_cohort).with(:example, timestamp).and_return(true)
89
+ pirate.add_to_cohort(:example, timestamp)
90
+ end
91
+
92
+ it "should delegate add_to_cohort!" do
93
+ timestamp = double
94
+ set_context.should_receive(:add_to_cohort!).with(:example, timestamp).and_return(true)
95
+ pirate.add_to_cohort!(:example, timestamp)
96
+ end
76
97
  end
77
98
  end
78
99
 
@@ -14,9 +14,9 @@ module Blackbeard
14
14
  db.hash_get('a_hash', 'hello').should == 'world'
15
15
  end
16
16
 
17
- it "should multi set" do
17
+ it "should multi set and get" do
18
18
  db.hash_multi_set('a_hash', {:one => 'two', :three => 'four'})
19
- db.hash_get('a_hash','three').should == 'four'
19
+ db.hash_multi_get('a_hash',['three','one']).should == ['four','two']
20
20
  end
21
21
 
22
22
  it "should not raise error on multi set with empty hash" do
@@ -52,6 +52,12 @@ module Blackbeard
52
52
  db.hash_get('a_hash', 'field').should == "3.5"
53
53
  end
54
54
 
55
+ it "should increment by int" do
56
+ db.hash_increment_by('a_hash', 'field', 1)
57
+ db.hash_increment_by('a_hash', 'field', 2.5)
58
+ db.hash_get('a_hash', 'field').should == "3"
59
+ end
60
+
55
61
  it "should determine if key exists" do
56
62
  expect{
57
63
  db.hash_set('a_hash', 'field', 'exists')
@@ -108,6 +108,9 @@ module Blackbeard
108
108
  new_record.save
109
109
  }.to change{ new_record.new_record }.from(true).to(false)
110
110
  end
111
+ it "should return true" do
112
+ new_record.save.should be_true
113
+ end
111
114
  end
112
115
 
113
116
  describe "master_key" do