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.
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
data/spec/metric_spec.rb CHANGED
@@ -1,50 +1,58 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- describe Blackbeard::Metric do
4
-
5
- describe "hour_keys" do
6
- before :each do
7
- @total_metric = Blackbeard::Metric::Total.new("one-total")
8
- end
9
-
10
- it "should return an empty array if no metrics" do
11
- @total_metric.send(:hour_keys).should == []
12
- end
13
-
14
- it "should return an array for each hour" do
15
- @total_metric.add('user1', 1)
16
- key = @total_metric.send(:key_for_hour, Blackbeard.tz.now)
17
- @total_metric.send(:hour_keys).should == [key]
3
+ module Blackbeard
4
+ describe Metric do
5
+ let(:metric) { Metric.new(:total, "one-total") }
6
+ let(:group) { Group.new(:example) }
7
+ let(:metric_data) { metric.metric_data }
8
+ let(:group_metric_data) { metric.metric_data(group) }
9
+
10
+ describe "self.all" do
11
+ before :each do
12
+ Metric.new(:total, "one-total")
13
+ Metric.new(:total, "two-total")
14
+ Metric.new(:unique, "one-unique")
15
+ end
16
+ it "should return a Metric Object for each Metric created" do
17
+ Metric.all.should have(3).metrics
18
+ end
19
+ end
20
+
21
+ describe "add" do
22
+ let(:context) { double(:unique_identifier => 'uid', :controller => double, :user => double) }
23
+ before :each do
24
+ metric.stub(:groups).and_return([group])
25
+ end
26
+
27
+ it "should increment metric data" do
28
+ metric_data.should_receive(:add).with("uid",1)
29
+ metric.add(context, 1)
30
+ end
31
+
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" )
35
+ metric.add(context, 1)
36
+ end
37
+
38
+ it "should not increment nil segments" do
39
+ group.stub(:segment_for).and_return(nil)
40
+ group_metric_data.should_not_receive(:add)
41
+ metric.add(context, 1)
42
+ end
43
+ end
44
+
45
+ describe "addable_groups" do
46
+ it "should include the groups not added" do
47
+ group # to initialize it
48
+ metric.addable_groups.map{|g| g.id }.should include(group.id)
49
+ end
50
+
51
+ it "should not include the group added" do
52
+ metric.add_group(group)
53
+ metric.addable_groups.map{|g| g.id }.should_not include(group.id)
54
+ end
18
55
  end
19
56
 
20
57
  end
21
-
22
- describe "hours" do
23
- before :each do
24
- @total_metric = Blackbeard::Metric::Total.new("one-total")
25
- end
26
-
27
- it "should return an array of hashes" do
28
- @total_metric.add('user1', 1)
29
- @total_metric.hours.should be_an(Array)
30
- @total_metric.hours.first.should be_a(Hash)
31
- end
32
- end
33
-
34
- describe "self.all" do
35
- before :each do
36
- Blackbeard::Metric::Total.new("one-total")
37
- Blackbeard::Metric::Total.new("two-total")
38
- Blackbeard::Metric::Unique.new("one-unique")
39
- end
40
- it "should return a Metric Object for each Metric created" do
41
- Blackbeard::Metric.all.should have(3).metrics
42
- end
43
-
44
- it "should instantiate each metric with the correct class" do
45
- Blackbeard::Metric.all.select{|m| m.name == "two-total"}.should have(1).metric
46
- Blackbeard::Metric.all.select{|m| m.name == "two-total"}.first.should be_a(Blackbeard::Metric::Total)
47
- end
48
- end
49
-
50
58
  end
data/spec/pirate_spec.rb CHANGED
@@ -6,20 +6,16 @@ describe Blackbeard::Pirate do
6
6
  describe "memoization" do
7
7
  let(:name){ "bond" }
8
8
 
9
- it "should get features once" do
10
- Blackbeard::Feature.should_receive(:new).with(name).once.and_return(true)
11
- 4.times{ pirate.feature(name) }
9
+ it "should get metrics once" do
10
+ Blackbeard::Metric.should_receive(:new).with(:total, name).once.and_return(double)
11
+ 4.times{ pirate.metric(:total, name) }
12
12
  end
13
13
 
14
- it "should get unique metrics once" do
15
- Blackbeard::Metric::Unique.should_receive(:new).with(name).once.and_return(true)
16
- 4.times{ pirate.unique_metric(name) }
14
+ it "should get test once" do
15
+ Blackbeard::Test.should_receive(:new).with(name).once.and_return(double)
16
+ 4.times{ pirate.test(name) }
17
17
  end
18
18
 
19
- it "should get total metrics once" do
20
- Blackbeard::Metric::Total.should_receive(:new).with(name).once.and_return(true)
21
- 4.times{ pirate.total_metric(name) }
22
- end
23
19
 
24
20
  end
25
21
 
@@ -33,15 +29,26 @@ describe Blackbeard::Pirate do
33
29
 
34
30
  describe "set context delegations" do
35
31
  context "with no set context" do
36
- it "should raise Blackbeard::MissingContextError" do
37
- expect{ pirate.add_unique(:exmaple) }.to raise_error( Blackbeard::MissingContextError )
32
+ it "add_unique should raise Blackbeard::MissingContextError" do
33
+ expect{ pirate.add_unique(:example) }.to_not raise_error
34
+ end
35
+
36
+ it "add_total should raise Blackbeard::MissingContextError" do
37
+ expect{ pirate.add_total(:example, 1) }.to_not raise_error
38
38
  end
39
- it "should raise Blackbeard::MissingContextError" do
40
- expect{ pirate.add_total(:exmaple, 1) }.to raise_error( Blackbeard::MissingContextError )
39
+
40
+ it "ab_test should raise Blackbeard::MissingContextError" do
41
+ expect{ pirate.ab_test(:example, :on => 1, :off => 2) }.to_not raise_error
42
+ end
43
+
44
+ it "active? should raise Blackbeard::MissingContextError" do
45
+ expect{ pirate.active?(:example) }.to_not raise_error
41
46
  end
47
+
42
48
  end
43
49
  context "with context set" do
44
- let!(:set_context){ pirate.set_context(:user_id => 1) }
50
+ let(:user){ double }
51
+ let!(:set_context){ pirate.set_context(user) }
45
52
 
46
53
  it "should delegate #add_unique" do
47
54
  set_context.should_receive(:add_unique).with(:example_metric).and_return(set_context)
@@ -52,6 +59,16 @@ describe Blackbeard::Pirate do
52
59
  set_context.should_receive(:add_total).with(:example_metric, 1).and_return(set_context)
53
60
  pirate.add_total(:example_metric, 1)
54
61
  end
62
+
63
+ it "should delegate #ab_test" do
64
+ set_context.should_receive(:ab_test).with(:example_metric, :on => 1, :off => 2).and_return(set_context)
65
+ pirate.ab_test(:example_metric, :on => 1, :off => 2)
66
+ end
67
+
68
+ it "should delegate #active?" do
69
+ set_context.should_receive(:active?).with(:example_metric).and_return(false)
70
+ pirate.active?(:example_metric)
71
+ end
55
72
  end
56
73
  end
57
74
 
@@ -0,0 +1,121 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Blackbeard
4
+ describe RedisStore do
5
+
6
+ it "should keep keys" do
7
+ db.set("hello", "world")
8
+ db.keys.should == ["hello"]
9
+ end
10
+
11
+ describe "hashes" do
12
+ it "should set and get" do
13
+ db.hash_set('a_hash', 'hello', 'world')
14
+ db.hash_get('a_hash', 'hello').should == 'world'
15
+ end
16
+
17
+ it "should multi set" do
18
+ db.hash_multi_set('a_hash', {:one => 'two', :three => 'four'})
19
+ db.hash_get('a_hash','three').should == 'four'
20
+ end
21
+
22
+ it "should not raise error on multi set with empty hash" do
23
+ expect{
24
+ db.hash_multi_set('a_hash', {})
25
+ }.to_not raise_error
26
+ end
27
+
28
+ it "should set if field does not exist" do
29
+ db.hash_set('a_hash', 'hello', 'world')
30
+ db.hash_key_set_if_not_exists('a_hash', 'hello', 'bar')
31
+ db.hash_get('a_hash', 'hello').should == 'world'
32
+ db.hash_key_set_if_not_exists('a_hash', 'foo', 'bar')
33
+ db.hash_get('a_hash', 'foo').should == 'bar'
34
+ end
35
+
36
+ it "should return lenth and keys" do
37
+ db.hash_set('a_hash', 'foo', 'bar')
38
+ db.hash_set('a_hash', 'hello', 'world')
39
+ db.hash_length('a_hash').should eq(2)
40
+ db.hash_keys('a_hash').should include('foo', 'hello')
41
+ end
42
+
43
+ it "should get all" do
44
+ db.hash_set('a_hash', 'foo', 'bar')
45
+ db.hash_set('a_hash', 'hello', 'world')
46
+ db.hash_get_all('a_hash').should include('foo' => 'bar', 'hello' => 'world')
47
+ end
48
+
49
+ it "should increment by float" do
50
+ db.hash_increment_by_float('a_hash', 'field', 1)
51
+ db.hash_increment_by_float('a_hash', 'field', 2.5)
52
+ db.hash_get('a_hash', 'field').should == "3.5"
53
+ end
54
+ end
55
+
56
+ describe "sets" do
57
+ it "should add and remove" do
58
+ db.set_add_member('a_set', 'foo')
59
+ db.set_add_member('a_set', 'bar')
60
+ db.set_members('a_set').should include('foo', 'bar')
61
+ db.set_remove_member('a_set', 'bar')
62
+ db.set_members('a_set').should_not include('bar')
63
+ end
64
+
65
+ it "should return true if added" do
66
+ db.set_add_member('a_set', 'foo').should be(true)
67
+ db.set_add_member('a_set', 'foo').should be(false)
68
+ end
69
+
70
+ it "should return all the members" do
71
+ db.set_add_member('a_set', 'foo')
72
+ db.set_add_member('a_set', 'bar')
73
+ db.set_members('a_set').should include('foo', 'bar')
74
+ end
75
+
76
+ it "should set multiple members at once" do
77
+ db.set_add_members('a_set', 'foo', ['bar', 'shag'])
78
+ db.set_members('a_set').should include('foo', 'bar', 'shag')
79
+ end
80
+
81
+ it "should count the members" do
82
+ db.set_add_members('a_set', 'foo', 'bar')
83
+ db.set_count('a_set').should eq(2)
84
+ end
85
+
86
+ it "should count the union" do
87
+ db.set_add_members('a_set', 'foo', 'bar')
88
+ db.set_add_members('x_set', 'bar', 'world')
89
+ db.set_union_count('a_set','x_set').should eq(3)
90
+ end
91
+
92
+ end
93
+
94
+ describe "strings" do
95
+ it "should get and set" do
96
+ db.set('hello','world')
97
+ db.get('hello').should eq('world')
98
+ end
99
+
100
+ it "should delete" do
101
+ db.set('hello','world')
102
+ expect{
103
+ db.del('hello')
104
+ }.to change{ db.get('hello') }.from('world').to(nil)
105
+ end
106
+
107
+ it "should get many at once" do
108
+ db.set('hello','world')
109
+ db.set('foo','bar')
110
+ db.multi_get('hello','herp', 'foo').should == ['world',nil,'bar']
111
+ end
112
+
113
+ it "should increment" do
114
+ db.increment('count')
115
+ db.get('count').should eq('1')
116
+ db.increment('count')
117
+ db.get('count').should eq('2')
118
+ end
119
+ end
120
+ end
121
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,22 @@
1
1
  require 'blackbeard'
2
2
  require 'redis'
3
+ require "codeclimate-test-reporter"
4
+
5
+ CodeClimate::TestReporter.start
3
6
 
4
7
  RSpec.configure do |config|
5
8
  config.before do
6
- redis = Blackbeard.db
9
+ redis = Blackbeard.config.db
10
+ namespace = "BlackbeardTests"
7
11
  keys = redis.keys
8
12
  redis.del(keys) if keys.any?
9
13
  end
10
14
  end
15
+
16
+ def tz
17
+ Blackbeard.config.tz
18
+ end
19
+
20
+ def db
21
+ Blackbeard.config.db
22
+ end
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Blackbeard::Storable do
4
+ class ExampleStorableAttrBase < Blackbeard::Storable
5
+ string_attributes :name
6
+ end
7
+
8
+ class ExampleStorableAttr < ExampleStorableAttrBase
9
+ set_master_key :example
10
+ end
11
+
12
+ describe "string_attributes" do
13
+ it "should be read and write" do
14
+ example = ExampleStorableAttr.new("id")
15
+ example.name = "Some name"
16
+ example.name.should == "Some name"
17
+ end
18
+
19
+ it "should persist" do
20
+ example = ExampleStorableAttr.new("id")
21
+ example.name = "Some name"
22
+
23
+ example_reloaded = ExampleStorableAttr.new("id")
24
+ example_reloaded.name.should == "Some name"
25
+ end
26
+ end
27
+
28
+ describe "update_attributes" do
29
+ let(:storable){ ExampleStorableAttr.new("id") }
30
+ it "should not raise raise_error with non-attributes" do
31
+ expect{
32
+ storable.update_attributes(:name => 'hello')
33
+ }.to_not raise_error
34
+ end
35
+ it "should update the attribute for known attributes" do
36
+ expect{
37
+ storable.update_attributes(:name => 'hello')
38
+ }.to change{ storable.name }.from(nil).to('hello')
39
+ end
40
+
41
+ it "should update the attribute for known attributes even when strings" do
42
+ expect{
43
+ storable.update_attributes("name" => 'hello')
44
+ }.to change{ storable.name }.from(nil).to('hello')
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Blackbeard
4
+
5
+ class Thing < Storable
6
+ set_master_key :things
7
+ end
8
+
9
+ class HasManyExample < Storable
10
+ set_master_key :examples
11
+ has_many :things => Thing
12
+ end
13
+
14
+ describe StorableHasMany do
15
+ let(:example){ HasManyExample.new(:example) }
16
+ let(:thing){ Thing.new(:foo) }
17
+
18
+ it "should add and remove things" do
19
+ example.add_thing(thing)
20
+ expect{
21
+ example.remove_thing(thing)
22
+ }.to change{ example.has_thing?(thing) }.from(true).to(false)
23
+ end
24
+
25
+ it "should has_thing?" do
26
+ example.add_thing(thing)
27
+ example.has_thing?(thing).should be_true
28
+ end
29
+
30
+ it "should list things" do
31
+ expect{
32
+ example.add_thing(thing)
33
+ }.to change{ example.things.count}.from(0).to(1)
34
+ end
35
+
36
+ it "should list thing_ids" do
37
+ example.thing_ids.should == []
38
+ example.add_thing(thing)
39
+ example.thing_ids.should == [thing.id]
40
+ end
41
+
42
+ it "should list the keys" do
43
+ example.thing_keys.should == []
44
+ example.add_thing(thing)
45
+ example.thing_keys.should == [thing.key]
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Blackbeard
4
+
5
+ class HasSetExample < Storable
6
+ set_master_key :examples
7
+ has_set :things => :thing
8
+ end
9
+
10
+ describe StorableHasMany do
11
+ let(:example){ HasSetExample.new(:example) }
12
+ let(:thing) { "foo" }
13
+
14
+ it "should add and remove things" do
15
+ example.add_thing(thing)
16
+ expect{
17
+ example.remove_thing(thing)
18
+ }.to change{ example.has_thing?(thing) }.from(true).to(false)
19
+ end
20
+
21
+ it "should add many things at once" do
22
+ expect{
23
+ example.add_things("thing1", "thing2")
24
+ }.to change{ example.things.count }.by(2)
25
+ end
26
+
27
+ it "should has_thing?" do
28
+ example.add_thing(thing)
29
+ example.has_thing?(thing).should be_true
30
+ end
31
+
32
+ it "should list things" do
33
+ expect{
34
+ example.add_thing(thing)
35
+ }.to change{ example.things.count}.from(0).to(1)
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,33 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Blackbeard::Storable do
4
+ class ExampleStorable < Blackbeard::Storable
5
+ set_master_key :example
6
+ end
7
+
8
+ class AnotherStorable < Blackbeard::Storable
9
+ set_master_key :another
10
+ end
11
+
12
+ it "should be able to instantiate" do
13
+ expect{
14
+ ExampleStorable.new(:some_id)
15
+ }.to_not raise_error
16
+ end
17
+
18
+ describe "master_key" do
19
+ it "should be by class" do
20
+ ExampleStorable.master_key.should == 'example'
21
+ AnotherStorable.master_key.should == 'another'
22
+ end
23
+ end
24
+
25
+ describe "==" do
26
+ it "should match class and id" do
27
+ (ExampleStorable.new("thing") == ExampleStorable.new("thing")).should be_true
28
+ (ExampleStorable.new("thing") == ExampleStorable.new("thing2")).should be_false
29
+ (ExampleStorable.new("thing") == AnotherStorable.new("thing")).should be_false
30
+ end
31
+ end
32
+
33
+ end
data/spec/test_spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Blackbeard::Test do
4
+ let(:test){ Blackbeard::Test.new('example') }
5
+
6
+ describe "#select_variation" do
7
+ # '*' - experimenting
8
+ # :variation
9
+
10
+ context "feature is static variation" do
11
+ it "should return the static variation"
12
+ end
13
+
14
+ context "experimenting" do
15
+ context "unique_identifier has already seen the variation" do
16
+ it "should return the same variation"
17
+ end
18
+
19
+ context "unique_identifier has not already seen the variation" do
20
+ it "should employ a strategy to select a variation"
21
+ end
22
+ end
23
+
24
+ end
25
+ end