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
@@ -0,0 +1,58 @@
1
+ require 'blackbeard/metric_data/base'
2
+
3
+ module Blackbeard
4
+ module MetricData
5
+ class Unique < Base
6
+
7
+ DEFAULT_SEGMENT = 'uniques'
8
+ def add(uid, amount = nil, segment = DEFAULT_SEGMENT)
9
+ add_at(tz.now, uid, amount, segment)
10
+ end
11
+
12
+ def add_at(time, uid, amount = nil, segment = DEFAULT_SEGMENT)
13
+ key = key_for_hour(time)
14
+ segment_key = segment_key(key, segment)
15
+
16
+ db.set_add_member(hours_set_key, key)
17
+ db.set_add_member(key, segment_key)
18
+ db.set_add_member(segment_key, uid)
19
+ #TODO: if not today, blow away rollup keys
20
+ end
21
+
22
+ def result_for_hour(time)
23
+ key = key_for_hour(time)
24
+ segment_keys = db.set_members(key)
25
+ result = {}
26
+ segment_keys.each do |segment_key|
27
+ segment = segment_key.split(/::/).last
28
+ result[segment] = db.set_count(segment_key)
29
+ end
30
+ result
31
+ end
32
+
33
+ def segment_key(key, segment)
34
+ "#{key}::#{segment}"
35
+ end
36
+
37
+ private
38
+
39
+ def merge_results(keys)
40
+ segments = {}
41
+ keys.each do |key|
42
+ segment_keys = db.set_members(key)
43
+ segment_keys.each do |segment_key|
44
+ segment = segment_key.split(/::/).last
45
+ segments[segment] ||= []
46
+ segments[segment].push(segment_key)
47
+ end
48
+ end
49
+ merged_results = {}
50
+ segments.each do |segment, segment_keys|
51
+ merged_results[segment] = db.set_union_count(segment_keys)
52
+ end
53
+ merged_results
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,11 @@
1
+ module Blackbeard
2
+ class MetricDate
3
+ attr_reader :date, :result
4
+
5
+ def initialize(date, result)
6
+ @date = date
7
+ @result = result
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Blackbeard
2
+ class MetricHour
3
+ attr_reader :hour, :result
4
+
5
+ def initialize(time, result)
6
+ @hour = round_to_beginning_of_hour(time)
7
+ @result = result
8
+ end
9
+
10
+ private
11
+
12
+ def round_to_beginning_of_hour(t)
13
+ t - ((t.min * 60) + t.sec)
14
+ end
15
+
16
+ end
17
+ end
@@ -1,47 +1,58 @@
1
1
  require "blackbeard/context"
2
2
  require "blackbeard/metric"
3
- require "blackbeard/metric/unique"
4
- require "blackbeard/metric/total"
5
- require "blackbeard/feature"
3
+ require "blackbeard/metric_data/unique"
4
+ require "blackbeard/metric_data/total"
5
+ require "blackbeard/test"
6
6
  require "blackbeard/errors"
7
+ require "blackbeard/group"
7
8
 
8
9
  module Blackbeard
9
10
  class Pirate
10
11
  def initialize
11
- @total_metrics = {}
12
- @unique_metrics = {}
13
- @features = {}
12
+ @metrics = {}
13
+ @tests = {}
14
14
  end
15
15
 
16
- def total_metric(name)
17
- @total_metrics[name] ||= Metric::Total.new(name)
16
+ def metric(type, type_id)
17
+ @metrics["#{type}::#{type_id}"] ||= Metric.new(type, type_id)
18
18
  end
19
19
 
20
- def unique_metric(name)
21
- @unique_metrics[name] ||= Metric::Unique.new(name)
20
+ def test(id)
21
+ @tests[id] ||= Test.new(id)
22
22
  end
23
23
 
24
- def feature(name)
25
- @features[name] ||= Feature.new(name)
24
+ def context(*args)
25
+ Context.new(self, *args)
26
26
  end
27
27
 
28
- def context(options = {})
29
- Context.new(self, options)
28
+ def set_context(*args)
29
+ @set_context = context(*args)
30
30
  end
31
31
 
32
- def set_context(options = {})
33
- @set_context = Context.new(self, options)
32
+ def clear_context
33
+ @set_context = nil
34
34
  end
35
35
 
36
- def add_unique(name)
37
- raise MissingContextError unless @set_context
38
- @set_context.add_unique(name)
36
+ def add_unique(id)
37
+ return self unless @set_context
38
+ @set_context.add_unique(id)
39
39
  end
40
40
 
41
- def add_total(name, amount)
42
- raise MissingContextError unless @set_context
43
- @set_context.add_total(name, amount)
41
+ def add_total(id, amount)
42
+ return self unless @set_context
43
+ @set_context.add_total(id, amount)
44
44
  end
45
45
 
46
+ def ab_test(id, options)
47
+ return self unless @set_context
48
+ @set_context.ab_test(id, options)
49
+ end
50
+
51
+ def active?(id)
52
+ return self unless @set_context
53
+ @set_context.active?(id)
54
+ end
55
+
56
+
46
57
  end
47
58
  end
@@ -14,10 +14,20 @@ module Blackbeard
14
14
  redis.keys
15
15
  end
16
16
 
17
+
18
+ # Hash commands
17
19
  def hash_key_set_if_not_exists(hash_key, field, value)
18
20
  redis.hsetnx(hash_key, field, value)
19
21
  end
20
22
 
23
+ def hash_set(hash_key, field, value)
24
+ redis.hset(hash_key, field, value)
25
+ end
26
+
27
+ def hash_multi_set(hash_key, hash)
28
+ redis.mapped_hmset(hash_key, hash) unless hash.empty?
29
+ end
30
+
21
31
  def hash_length(hash_key)
22
32
  redis.hlen(hash_key)
23
33
  end
@@ -26,24 +36,50 @@ module Blackbeard
26
36
  redis.hkeys(hash_key)
27
37
  end
28
38
 
39
+ def hash_get(hash_key, field)
40
+ redis.hget(hash_key, field)
41
+ end
42
+
43
+ def hash_get_all(hash_key)
44
+ redis.hgetall(hash_key)
45
+ end
46
+
47
+ def hash_increment_by_float(hash_key, field, float)
48
+ redis.hincrbyfloat(hash_key, field, float)
49
+ end
50
+
51
+ # Set commands
29
52
  def set_members(set_key)
30
53
  redis.smembers(set_key)
31
54
  end
32
55
 
56
+ def set_remove_member(set_key, member)
57
+ redis.srem(set_key, member)
58
+ end
59
+
33
60
  def set_add_member(set_key, member)
34
61
  redis.sadd(set_key, member)
35
62
  end
36
63
 
64
+ def set_add_members(set_key, *members)
65
+ redis.sadd(set_key, members.flatten)
66
+ end
67
+
37
68
  def set_count(set_key)
38
69
  redis.scard(set_key)
39
70
  end
40
71
 
72
+ def set_union_count(*keys)
73
+ redis.sunionstore('set_union_count', keys.flatten)
74
+ end
75
+
76
+ # String commands
41
77
  def del(*keys)
42
78
  redis.del(*keys)
43
79
  end
44
80
 
45
- def increment_by_float(key, increment)
46
- redis.incrbyfloat(key, increment)
81
+ def increment_by_float(key, float)
82
+ redis.incrbyfloat(key, float)
47
83
  end
48
84
 
49
85
  def increment(key)
@@ -54,5 +90,13 @@ module Blackbeard
54
90
  redis.get(key)
55
91
  end
56
92
 
93
+ def multi_get(*keys)
94
+ redis.mget(*keys.flatten)
95
+ end
96
+
97
+ def set(key, value)
98
+ redis.set(key, value)
99
+ end
100
+
57
101
  end
58
102
  end
@@ -0,0 +1,13 @@
1
+ module Blackbeard
2
+ class SelectedVariation
3
+ def initialize(test, variation)
4
+ @test = test
5
+ @variation = variation
6
+ end
7
+
8
+ def ==(s)
9
+ @test.add_variation(s)
10
+ @variation == s.to_s
11
+ end
12
+ end
13
+ end
@@ -1,19 +1,32 @@
1
+ require 'blackbeard/configuration_methods'
2
+ require 'blackbeard/storable_attributes'
3
+ require 'blackbeard/storable_has_many'
4
+ require 'blackbeard/storable_has_set'
5
+
1
6
  module Blackbeard
2
7
  class Storable
3
- attr_reader :name
8
+ include ConfigurationMethods
9
+ include StorableHasMany
10
+ include StorableHasSet
11
+ include StorableAttributes
4
12
 
5
- def initialize(name)
6
- @name = name.to_s.downcase
7
- db.hash_key_set_if_not_exists(master_key, key, tz.now.to_date)
8
- end
13
+ class << self
14
+ def set_master_key(master_key)
15
+ @master_key = master_key.to_s
16
+ end
9
17
 
10
- def master_key
11
- raise "define master key in the class that inherits from storable"
18
+ def master_key
19
+ return @master_key if defined? @master_key
20
+ return self.superclass.master_key if self.superclass.respond_to?(:master_key)
21
+ raise StorableMasterKeyUndefined, "define master key in the class that inherits from storable"
22
+ end
12
23
  end
13
24
 
14
- def self.new_from_key(key)
15
- name = key.split('::').last
16
- self.new(name)
25
+ attr_reader :id
26
+
27
+ def initialize(id)
28
+ @id = id.to_s.downcase
29
+ db.hash_key_set_if_not_exists(master_key, key, tz.now.to_date)
17
30
  end
18
31
 
19
32
  def self.all_keys
@@ -24,34 +37,33 @@ module Blackbeard
24
37
  db.hash_length(master_key)
25
38
  end
26
39
 
27
- def self.all
28
- all_keys.map{ |key| new_from_key(key) }
40
+ def self.new_from_keys(*keys)
41
+ keys.flatten.map{ |key| new_from_key(key) }
29
42
  end
30
43
 
31
- protected
32
-
33
- def key
34
- "#{master_key}::#{ name }"
44
+ def self.new_from_key(key)
45
+ if key =~ /^#{master_key}::(.+)$/
46
+ new($1)
47
+ else
48
+ nil
49
+ end
35
50
  end
36
51
 
37
- def master_key
38
- self.class.master_key
52
+ def self.all
53
+ all_keys.map{ |key| new_from_key(key) }
39
54
  end
40
55
 
41
- def db
42
- self.class.db
56
+ def ==(o)
57
+ o.class == self.class && o.id == self.id
43
58
  end
44
59
 
45
- def self.db
46
- Blackbeard.db
60
+ def key
61
+ "#{master_key}::#{ id }"
47
62
  end
48
63
 
49
- def tz
50
- self.class.tz
64
+ def master_key
65
+ self.class.master_key
51
66
  end
52
67
 
53
- def self.tz
54
- Blackbeard.tz
55
- end
56
68
  end
57
69
  end
@@ -0,0 +1,54 @@
1
+ module Blackbeard
2
+ module StorableAttributes
3
+ def self.included(base)
4
+ base.send :include, InstanceMethods
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def storable_attributes=(x)
10
+ @storable_attributes = x
11
+ end
12
+
13
+ def storable_attributes
14
+ return @storable_attributes if defined? @storable_attributes
15
+ return self.superclass.storable_attributes if self.superclass.respond_to?(:storable_attributes)
16
+ []
17
+ end
18
+
19
+ def string_attributes(*attributes)
20
+ self.storable_attributes += attributes
21
+ attributes.each do |method_name|
22
+ method_name = method_name.to_sym
23
+ send :define_method, method_name do
24
+ storable_attributes_hash[method_name.to_s]
25
+ end
26
+ send :define_method, "#{method_name}=".to_sym do |value|
27
+ db.hash_set(attributes_hash_key, method_name, value)
28
+ storable_attributes_hash[method_name.to_s] = value
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ module InstanceMethods
35
+ def update_attributes(tainted_params)
36
+ attributes = self.class.storable_attributes
37
+ safe_attributes = tainted_params.keys.select{ |k| attributes.include?(k.to_sym) }
38
+ safe_attributes.each do |attribute|
39
+ self.send("#{attribute}=".to_sym, tainted_params[attribute])
40
+ end
41
+ end
42
+
43
+ def storable_attributes_hash
44
+ @storable_attributes ||= db.hash_get_all(attributes_hash_key)
45
+ end
46
+
47
+ def attributes_hash_key
48
+ "#{key}::attributes"
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,60 @@
1
+ module Blackbeard
2
+ module StorableHasMany
3
+ def self.included(base)
4
+ base.send :include, InstanceMethods
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def has_many(options = {})
14
+ options.each_pair do |plural, klass|
15
+ _has_many(plural, klass)
16
+ end
17
+ end
18
+
19
+ def _has_many(plural, klass)
20
+ plural = plural.to_s.downcase
21
+ singular = klass.name.split('::').last.downcase
22
+
23
+ methods = <<-END_OF_RUBY
24
+
25
+ def has_#{singular}?(o)
26
+ #{singular}_ids.include?(o.id)
27
+ end
28
+
29
+ def add_#{singular}(o)
30
+ db.set_add_member(#{plural}_set_key, o.key) unless has_#{singular}?(o)
31
+ \@#{plural} = nil
32
+ end
33
+
34
+ def remove_#{singular}(o)
35
+ db.set_remove_member(#{plural}_set_key, o.key)
36
+ \@#{plural} = nil
37
+ end
38
+
39
+ def #{plural}
40
+ \@#{plural} ||= #{klass.name}.new_from_keys(#{singular}_keys)
41
+ end
42
+
43
+ def #{singular}_ids
44
+ #{plural}.map{ |g| g.id }
45
+ end
46
+
47
+ def #{singular}_keys
48
+ db.set_members(#{plural}_set_key)
49
+ end
50
+
51
+ def #{plural}_set_key
52
+ key+"::#{plural}"
53
+ end
54
+ END_OF_RUBY
55
+
56
+ class_eval(methods)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ module Blackbeard
2
+ module StorableHasSet
3
+ def self.included(base)
4
+ base.send :include, InstanceMethods
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def has_set(options = {})
14
+ options.each_pair do |plural, singular|
15
+ _has_set(plural, singular)
16
+ end
17
+ end
18
+
19
+ def _has_set(plural, singular)
20
+ plural = plural.to_s.downcase
21
+ singular = singular.to_s.downcase
22
+
23
+ methods = <<-END_OF_RUBY
24
+
25
+ def has_#{singular}?(o)
26
+ #{plural}.include?(o)
27
+ end
28
+
29
+ def add_#{singular}(o)
30
+ db.set_add_member(#{plural}_key, o) unless has_#{singular}?(o)
31
+ \@#{plural} = nil
32
+ end
33
+
34
+ def add_#{plural}(*args)
35
+ args.flatten.reject!{|o| has_#{singular}?(o) }
36
+ db.set_add_members(#{plural}_key, args) unless args.empty?
37
+ \@#{plural} = nil
38
+ end
39
+
40
+ def remove_#{singular}(o)
41
+ db.set_remove_member(#{plural}_key, o)
42
+ \@#{plural} = nil
43
+ end
44
+
45
+ def #{plural}
46
+ \@#{plural} ||= db.set_members(#{plural}_key)
47
+ end
48
+
49
+ def #{plural}_key
50
+ key+"::#{plural}"
51
+ end
52
+
53
+ END_OF_RUBY
54
+
55
+ class_eval(methods)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,21 @@
1
+ require 'blackbeard/storable'
2
+
3
+ module Blackbeard
4
+ class Test < Storable
5
+ set_master_key :tests
6
+ string_attributes :name, :description
7
+ has_set :variations => :variation
8
+
9
+ def select_variation
10
+ # add :off unless :off, :control, :default, or :inactive
11
+ :off
12
+ end
13
+
14
+ def name
15
+ storable_attributes_hash['name'] || id
16
+ end
17
+
18
+ private
19
+
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Blackbeard
2
- VERSION = "0.0.2.0"
2
+ VERSION = "0.0.3.1"
3
3
  end
data/lib/blackbeard.rb CHANGED
@@ -5,14 +5,6 @@ module Blackbeard
5
5
  extend self
6
6
  attr_accessor :config
7
7
 
8
- def tz
9
- config.tz
10
- end
11
-
12
- def db
13
- config.db
14
- end
15
-
16
8
  def self.pirate
17
9
  @config ||= Configuration.new
18
10
  yield(config)
@@ -0,0 +1,15 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Blackbeard
4
+ describe Configuration do
5
+ let(:config) { Configuration.new }
6
+ describe "define_group" do
7
+ it "should store the block in group_definiations" do
8
+ config.define_group(:hello) do |user,controller|
9
+ "world"
10
+ end
11
+ config.group_definitions[:hello].call.should == 'world'
12
+ end
13
+ end
14
+ end
15
+ end