blackbeard 0.0.4.0 → 0.0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +44 -16
- data/TODO.md +3 -1
- data/dashboard/routes/cohorts.rb +25 -0
- data/dashboard/routes/features.rb +7 -4
- data/dashboard/routes/groups.rb +6 -3
- data/dashboard/routes/metrics.rb +25 -5
- data/dashboard/routes/tests.rb +6 -3
- data/dashboard/views/cohorts/index.erb +22 -0
- data/dashboard/views/cohorts/show.erb +41 -0
- data/dashboard/views/groups/show.erb +2 -2
- data/dashboard/views/layout.erb +1 -0
- data/dashboard/views/metrics/show.erb +37 -10
- data/dashboard/views/shared/_charts.erb +20 -0
- data/lib/blackbeard/chart.rb +65 -0
- data/lib/blackbeard/chartable.rb +47 -0
- data/lib/blackbeard/cohort.rb +48 -0
- data/lib/blackbeard/cohort_data.rb +72 -0
- data/lib/blackbeard/cohort_metric.rb +47 -0
- data/lib/blackbeard/context.rb +11 -2
- data/lib/blackbeard/dashboard.rb +2 -3
- data/lib/blackbeard/dashboard_helpers.rb +0 -22
- data/lib/blackbeard/errors.rb +2 -0
- data/lib/blackbeard/group.rb +3 -0
- data/lib/blackbeard/group_metric.rb +39 -0
- data/lib/blackbeard/metric.rb +30 -14
- data/lib/blackbeard/metric_data/base.rb +18 -35
- data/lib/blackbeard/metric_data/total.rb +2 -1
- data/lib/blackbeard/metric_data/uid_generator.rb +38 -0
- data/lib/blackbeard/metric_data/unique.rb +1 -1
- data/lib/blackbeard/metric_date.rb +10 -0
- data/lib/blackbeard/metric_hour.rb +8 -0
- data/lib/blackbeard/pirate.rb +18 -0
- data/lib/blackbeard/redis_store.rb +10 -1
- data/lib/blackbeard/storable.rb +1 -0
- data/lib/blackbeard/version.rb +1 -1
- data/spec/chart_spec.rb +38 -0
- data/spec/chartable_spec.rb +56 -0
- data/spec/cohort_data_spec.rb +142 -0
- data/spec/cohort_metric_spec.rb +26 -0
- data/spec/cohort_spec.rb +31 -0
- data/spec/context_spec.rb +9 -1
- data/spec/dashboard/cohorts_spec.rb +43 -0
- data/spec/dashboard/groups_spec.rb +0 -7
- data/spec/dashboard/metrics_spec.rb +35 -0
- data/spec/group_metric_spec.rb +26 -0
- data/spec/metric_data/base_spec.rb +0 -16
- data/spec/metric_data/uid_generator_spec.rb +40 -0
- data/spec/metric_spec.rb +23 -12
- data/spec/pirate_spec.rb +22 -1
- data/spec/redis_store_spec.rb +8 -2
- data/spec/storable_spec.rb +3 -0
- metadata +29 -3
- data/dashboard/views/metrics/_metric_data.erb +0 -59
@@ -1,32 +1,22 @@
|
|
1
1
|
require 'blackbeard/metric_hour'
|
2
2
|
require 'blackbeard/metric_date'
|
3
3
|
require 'date'
|
4
|
+
require 'blackbeard/chart'
|
5
|
+
require 'blackbeard/metric_data/uid_generator'
|
6
|
+
require 'blackbeard/chartable'
|
4
7
|
|
5
8
|
module Blackbeard
|
6
9
|
module MetricData
|
7
10
|
class Base
|
8
11
|
include ConfigurationMethods
|
9
|
-
attr_reader :metric, :group
|
10
12
|
|
11
|
-
|
13
|
+
attr_reader :metric, :group, :cohort
|
14
|
+
|
15
|
+
# TODO: refactor so you pass group and cohort in as options
|
16
|
+
def initialize(metric, group = nil, cohort = nil)
|
12
17
|
@metric = metric
|
13
18
|
@group = group
|
14
|
-
|
15
|
-
|
16
|
-
def recent_days(count=28, starting_on = tz.now.to_date)
|
17
|
-
Array(0..count-1).map do |offset|
|
18
|
-
date = starting_on - offset
|
19
|
-
result = result_for_day(date)
|
20
|
-
Blackbeard::MetricDate.new(date, result)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def recent_hours(count = 24, starting_at = tz.now)
|
25
|
-
Array(0..count-1).map do |offset|
|
26
|
-
hour = starting_at - (offset * 3600)
|
27
|
-
result = result_for_hour(hour)
|
28
|
-
Blackbeard::MetricHour.new(hour, result)
|
29
|
-
end
|
19
|
+
@cohort = cohort
|
30
20
|
end
|
31
21
|
|
32
22
|
def hour_keys_for_day(date)
|
@@ -44,27 +34,18 @@ module Blackbeard
|
|
44
34
|
|
45
35
|
def key
|
46
36
|
@key ||= begin
|
47
|
-
lookup_hash = "metric_data_keys"
|
48
|
-
lookup_field = "metric-#{metric.id}"
|
49
|
-
lookup_field += "::group-#{group.id}" if group
|
50
|
-
uid = db.hash_get(lookup_hash, lookup_field)
|
51
|
-
if uid.nil?
|
52
|
-
uid = db.increment("metric_data_next_uid")
|
53
|
-
# write and read to avoid race conditional writes
|
54
|
-
db.hash_key_set_if_not_exists(lookup_hash, lookup_field, uid)
|
55
|
-
uid = db.hash_get(lookup_hash, lookup_field)
|
56
|
-
end
|
57
37
|
"data::#{uid}"
|
58
38
|
end
|
59
39
|
end
|
60
40
|
|
41
|
+
def uid
|
42
|
+
uid = UidGenerator.new(self).uid
|
43
|
+
end
|
44
|
+
|
61
45
|
def segments
|
62
|
-
|
63
|
-
group.segments
|
64
|
-
else
|
65
|
-
[self.class::DEFAULT_SEGMENT]
|
66
|
-
end
|
46
|
+
[self.class::DEFAULT_SEGMENT]
|
67
47
|
end
|
48
|
+
|
68
49
|
private
|
69
50
|
|
70
51
|
def generate_result_for_day(date)
|
@@ -75,7 +56,6 @@ module Blackbeard
|
|
75
56
|
result
|
76
57
|
end
|
77
58
|
|
78
|
-
|
79
59
|
def hour_keys
|
80
60
|
db.set_members(hours_set_key)
|
81
61
|
end
|
@@ -93,7 +73,10 @@ module Blackbeard
|
|
93
73
|
end
|
94
74
|
|
95
75
|
def key_for_hour(time)
|
96
|
-
|
76
|
+
if time.kind_of?(Time)
|
77
|
+
time = time.strftime("%Y%m%d%H")
|
78
|
+
end
|
79
|
+
"#{key}::#{ time }"
|
97
80
|
end
|
98
81
|
|
99
82
|
end
|
@@ -11,10 +11,11 @@ module Blackbeard
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def add_at(time, uid, amount = 1, segment = DEFAULT_SEGMENT)
|
14
|
+
# TODO: ensure time is in correct timezone
|
14
15
|
key = key_for_hour(time)
|
15
16
|
db.set_add_member(hours_set_key, key)
|
16
17
|
db.hash_increment_by_float(key, segment, amount.to_f)
|
17
|
-
#TODO: if not today, blow away rollup keys
|
18
|
+
# TODO: if not today, blow away rollup keys
|
18
19
|
end
|
19
20
|
|
20
21
|
def result_for_hour(time)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Blackbeard
|
2
|
+
module MetricData
|
3
|
+
class UidGenerator
|
4
|
+
include ConfigurationMethods
|
5
|
+
|
6
|
+
def initialize(metric_data)
|
7
|
+
@metric = metric_data.metric
|
8
|
+
@group = metric_data.group
|
9
|
+
@cohort = metric_data.cohort
|
10
|
+
end
|
11
|
+
|
12
|
+
def uid
|
13
|
+
db.hash_get(lookup_hash, lookup_field) || generate_uid
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def lookup_hash
|
19
|
+
"metric_data_keys"
|
20
|
+
end
|
21
|
+
|
22
|
+
def lookup_field
|
23
|
+
lookup_field = "metric-#{@metric.id}"
|
24
|
+
lookup_field += "::group-#{@group.id}" if @group
|
25
|
+
lookup_field += "::group-#{@cohort.id}" if @cohort
|
26
|
+
lookup_field
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate_uid
|
30
|
+
uid = db.increment("metric_data_next_uid")
|
31
|
+
# write and read to avoid race conditional writes
|
32
|
+
db.hash_key_set_if_not_exists(lookup_hash, lookup_field, uid)
|
33
|
+
db.hash_get(lookup_hash, lookup_field)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -10,6 +10,7 @@ module Blackbeard
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def add_at(time, uid, amount = nil, segment = DEFAULT_SEGMENT)
|
13
|
+
#TODO: unsure time is in proper timezone
|
13
14
|
key = key_for_hour(time)
|
14
15
|
segment_key = segment_key(key, segment)
|
15
16
|
|
@@ -55,4 +56,3 @@ module Blackbeard
|
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|
58
|
-
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Blackbeard
|
2
2
|
class MetricDate
|
3
|
+
#TODO refactor with MetricHour to be compaosed
|
3
4
|
attr_reader :date, :result
|
4
5
|
|
5
6
|
def initialize(date, result)
|
@@ -7,5 +8,14 @@ module Blackbeard
|
|
7
8
|
@result = result
|
8
9
|
end
|
9
10
|
|
11
|
+
def results_for(segments)
|
12
|
+
segments.map{|s| result[s].to_f }
|
13
|
+
end
|
14
|
+
|
15
|
+
def result_rows(segments)
|
16
|
+
[@date] + results_for(segments)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
10
20
|
end
|
11
21
|
end
|
data/lib/blackbeard/pirate.rb
CHANGED
@@ -6,6 +6,7 @@ require "blackbeard/test"
|
|
6
6
|
require "blackbeard/errors"
|
7
7
|
require "blackbeard/group"
|
8
8
|
require "blackbeard/feature"
|
9
|
+
require "blackbeard/cohort"
|
9
10
|
|
10
11
|
module Blackbeard
|
11
12
|
class Pirate
|
@@ -13,16 +14,22 @@ module Blackbeard
|
|
13
14
|
@metrics = {}
|
14
15
|
@tests = {}
|
15
16
|
@features = {}
|
17
|
+
@cohorts = {}
|
16
18
|
end
|
17
19
|
|
18
20
|
def metric(type, type_id)
|
19
21
|
@metrics["#{type}::#{type_id}"] ||= Metric.find_or_create(type, type_id)
|
20
22
|
end
|
21
23
|
|
24
|
+
# TODO: abstract out memoization to a cache class
|
22
25
|
def test(id)
|
23
26
|
@tests[id] ||= Test.find_or_create(id)
|
24
27
|
end
|
25
28
|
|
29
|
+
def cohort(id)
|
30
|
+
@cohorts[id] ||= Cohort.find_or_create(id)
|
31
|
+
end
|
32
|
+
|
26
33
|
def feature(id)
|
27
34
|
@features[id] ||= Feature.find_or_create(id)
|
28
35
|
end
|
@@ -39,6 +46,7 @@ module Blackbeard
|
|
39
46
|
@set_context = nil
|
40
47
|
end
|
41
48
|
|
49
|
+
# TODO: metaprogram all the context delegators
|
42
50
|
def add_unique(id)
|
43
51
|
return self unless @set_context
|
44
52
|
@set_context.add_unique(id)
|
@@ -49,6 +57,16 @@ module Blackbeard
|
|
49
57
|
@set_context.add_total(id, amount)
|
50
58
|
end
|
51
59
|
|
60
|
+
def add_to_cohort(id, timestamp = nil)
|
61
|
+
return self unless @set_context
|
62
|
+
@set_context.add_to_cohort(id, timestamp)
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_to_cohort!(id, timestamp = nil)
|
66
|
+
return self unless @set_context
|
67
|
+
@set_context.add_to_cohort!(id, timestamp)
|
68
|
+
end
|
69
|
+
|
52
70
|
def ab_test(id, options)
|
53
71
|
return self unless @set_context
|
54
72
|
@set_context.ab_test(id, options)
|
@@ -16,6 +16,7 @@ module Blackbeard
|
|
16
16
|
|
17
17
|
|
18
18
|
# Hash commands
|
19
|
+
# TODO: rename to hash_set_if_not_exisits
|
19
20
|
def hash_key_set_if_not_exists(hash_key, field, value)
|
20
21
|
redis.hsetnx(hash_key, field, value)
|
21
22
|
end
|
@@ -28,6 +29,10 @@ module Blackbeard
|
|
28
29
|
redis.mapped_hmset(hash_key, hash) unless hash.empty?
|
29
30
|
end
|
30
31
|
|
32
|
+
def hash_multi_get(hash_key, *fields)
|
33
|
+
redis.hmget(hash_key, *fields) unless fields.empty?
|
34
|
+
end
|
35
|
+
|
31
36
|
def hash_length(hash_key)
|
32
37
|
redis.hlen(hash_key)
|
33
38
|
end
|
@@ -44,8 +49,12 @@ module Blackbeard
|
|
44
49
|
redis.hgetall(hash_key)
|
45
50
|
end
|
46
51
|
|
52
|
+
def hash_increment_by(hash_key, field, int)
|
53
|
+
redis.hincrby(hash_key, field, int.to_i)
|
54
|
+
end
|
55
|
+
|
47
56
|
def hash_increment_by_float(hash_key, field, float)
|
48
|
-
redis.hincrbyfloat(hash_key, field, float)
|
57
|
+
redis.hincrbyfloat(hash_key, field, float.to_f)
|
49
58
|
end
|
50
59
|
|
51
60
|
def hash_field_exists(hash_key, field)
|
data/lib/blackbeard/storable.rb
CHANGED
data/lib/blackbeard/version.rb
CHANGED
data/spec/chart_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
module Blackbeard
|
4
|
+
describe Chart do
|
5
|
+
let(:title) { 'This is a title' }
|
6
|
+
let(:chart){
|
7
|
+
Chart.new(
|
8
|
+
:title => title,
|
9
|
+
:columns=>["Date","Total","Uniques"],
|
10
|
+
:rows=>[
|
11
|
+
[Date.today, 10.5, 12],
|
12
|
+
[Date.today+1, 11.5, 13],
|
13
|
+
[Date.today+2, 0, 1],
|
14
|
+
])
|
15
|
+
}
|
16
|
+
it "should put title and height in options" do
|
17
|
+
chart.options.should == {:title => title, :height => 300}
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "data" do
|
21
|
+
it "should have cols" do
|
22
|
+
chart.data[:cols].should == [
|
23
|
+
{:label=>"Date", :type=>"string"},
|
24
|
+
{:label=>"Total", :type=>"number"},
|
25
|
+
{:label=>"Uniques", :type=>"number"}
|
26
|
+
]
|
27
|
+
end
|
28
|
+
it "should have rows" do
|
29
|
+
chart.data[:rows].should == [
|
30
|
+
{:c=>[{:v=>Date.today}, {:v=>10.5}, {:v=>12}]},
|
31
|
+
{:c=>[{:v=>Date.today+1}, {:v=>11.5}, {:v=>13}]},
|
32
|
+
{:c=>[{:v=>Date.today+2}, {:v=>0}, {:v=>1}]}
|
33
|
+
]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
module Blackbeard
|
4
|
+
module MetricData
|
5
|
+
describe Chartable do
|
6
|
+
|
7
|
+
class ExampleChartable
|
8
|
+
include Chartable
|
9
|
+
|
10
|
+
def chartable_result_for_day(day)
|
11
|
+
{'segment1' => 0}
|
12
|
+
end
|
13
|
+
|
14
|
+
def chartable_result_for_hour(hour)
|
15
|
+
{'segment1' => 0}
|
16
|
+
end
|
17
|
+
|
18
|
+
def chartable_segments
|
19
|
+
['segment1']
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:example){ ExampleChartable.new }
|
25
|
+
|
26
|
+
describe "recent_hours" do
|
27
|
+
let(:start_at) { Time.new(2014,1,1,12,0,0) }
|
28
|
+
|
29
|
+
it "should return results for recent hours" do
|
30
|
+
example.recent_hours(3, start_at).should have(3).metric_hours
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "recent_days" do
|
35
|
+
let(:start_on) { Date.new(2014,1,3) }
|
36
|
+
|
37
|
+
it "should return results for recent days" do
|
38
|
+
example.recent_days(3, start_on).should have(3).metric_days
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "recent_hours_chart" do
|
43
|
+
it "should return a chart obj" do
|
44
|
+
example.recent_hours_chart.should be_a(Chart)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "recent_days_chart" do
|
49
|
+
it "should return a chart obj" do
|
50
|
+
example.recent_days_chart.should be_a(Chart)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,142 @@
|
|
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(:hour_id){ data.send(:hour_id, hour)}
|
8
|
+
let(:different_hour){ Time.new(2014,3,5,2) }
|
9
|
+
let(:different_hour_id){ data.send(:hour_id, different_hour)}
|
10
|
+
let(:cohort) { Cohort.new(:happy) }
|
11
|
+
let(:data) { cohort.data }
|
12
|
+
|
13
|
+
describe "#add with force" do
|
14
|
+
context "no pre-existing cohort" do
|
15
|
+
it "should call add without force" do
|
16
|
+
data.should_receive(:add_without_force).with(uid, hour)
|
17
|
+
data.add_with_force(uid, hour)
|
18
|
+
end
|
19
|
+
it "should return true" do
|
20
|
+
data.add_with_force(uid, hour).should be_true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "already in same cohort" do
|
25
|
+
before :each do
|
26
|
+
data.add_without_force(uid, hour)
|
27
|
+
end
|
28
|
+
it "should not increment the hours" do
|
29
|
+
expect{
|
30
|
+
data.add_with_force(uid, hour)
|
31
|
+
}.to_not change{ data.participants_for_hour(hour) }
|
32
|
+
end
|
33
|
+
it "should not update the participants" do
|
34
|
+
expect{
|
35
|
+
data.add_with_force(uid, hour)
|
36
|
+
}.to_not change{ data.hour_id_for_participant(uid) }
|
37
|
+
end
|
38
|
+
it "should return true" do
|
39
|
+
data.add_with_force(uid, hour).should be_true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "already in different cohort" do
|
44
|
+
before :each do
|
45
|
+
data.add_without_force(uid, different_hour)
|
46
|
+
end
|
47
|
+
it "should de-increment the existing hour field" do
|
48
|
+
expect{
|
49
|
+
data.add_with_force(uid, hour)
|
50
|
+
}.to change{ data.participants_for_hour(different_hour) }.by(-1)
|
51
|
+
end
|
52
|
+
it "should increment the new hour field" do
|
53
|
+
expect{
|
54
|
+
data.add_with_force(uid, hour)
|
55
|
+
}.to change{ data.participants_for_hour(hour) }.by(1)
|
56
|
+
end
|
57
|
+
it "should update the participant to the current hour" do
|
58
|
+
expect{
|
59
|
+
data.add_with_force(uid, hour)
|
60
|
+
}.to change{ data.hour_id_for_participant(uid) }.from(different_hour_id).to(hour_id)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#add without force" do
|
66
|
+
context "already in cohort" do
|
67
|
+
before :each do
|
68
|
+
data.add_without_force(uid, different_hour)
|
69
|
+
end
|
70
|
+
it "should not update the paricipant to the current hour" do
|
71
|
+
expect{
|
72
|
+
data.add_without_force(uid, hour)
|
73
|
+
}.to_not change{ data.hour_id_for_participant(uid) }
|
74
|
+
end
|
75
|
+
it "should not increment the new hour field" do
|
76
|
+
expect{
|
77
|
+
data.add_without_force(uid, hour)
|
78
|
+
}.to_not change{ data.participants_for_hour(hour) }
|
79
|
+
end
|
80
|
+
it "should return false" do
|
81
|
+
data.add_without_force(uid, hour).should be_false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "not in cohort" do
|
86
|
+
it "should update the participant to the current hour" do
|
87
|
+
expect{
|
88
|
+
data.add_without_force(uid, hour)
|
89
|
+
}.to change{ data.hour_id_for_participant(uid) }.from(nil).to(hour_id)
|
90
|
+
end
|
91
|
+
it "should increment the new hour field" do
|
92
|
+
expect{
|
93
|
+
data.add_without_force(uid, hour)
|
94
|
+
}.to change{ data.participants_for_hour(hour) }.by(1)
|
95
|
+
end
|
96
|
+
it "should return true" do
|
97
|
+
data.add_without_force(uid, hour).should be_true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "countint participants" do
|
103
|
+
let(:aug22) { Date.new(2003,8,22) }
|
104
|
+
let(:aug22_1pm) { Time.new(2003,8,22,13) }
|
105
|
+
let(:aug22_11am) { Time.new(2003,8,22,11) }
|
106
|
+
let(:aug22_9am) { Time.new(2003,8,22,9) }
|
107
|
+
let(:aug23_3pm) { Time.new(2003,8,23,15) }
|
108
|
+
let(:context1) { double :unique_identifier => '1' }
|
109
|
+
let(:context2) { double :unique_identifier => '2' }
|
110
|
+
let(:context3) { double :unique_identifier => '3' }
|
111
|
+
let(:context4) { double :unique_identifier => '4' }
|
112
|
+
|
113
|
+
before :each do
|
114
|
+
cohort.add(context1, aug22_1pm)
|
115
|
+
cohort.add(context2, aug22_1pm)
|
116
|
+
cohort.add(context3, aug22_11am)
|
117
|
+
cohort.add(context4, aug23_3pm)
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "participants for hour" do
|
121
|
+
it "should return the count for each hour" do
|
122
|
+
data.participants_for_hour(aug22_1pm).should == 2
|
123
|
+
data.participants_for_hour(aug22_11am).should == 1
|
124
|
+
data.participants_for_hour(aug22_9am).should == 0
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "participants for hours" do
|
129
|
+
it "should return the count for each hour" do
|
130
|
+
data.participants_for_hours([aug22_9am, aug22_11am, aug22_1pm]).should == [0,1,2]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "participants for day" do
|
135
|
+
it "should sum the day" do
|
136
|
+
data.participants_for_day(aug22).should == 3
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|