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.
- 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
|