sir_tracks_alot 0.2.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 (43) hide show
  1. data/Gemfile +12 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +17 -0
  4. data/Rakefile +19 -0
  5. data/VERSION +1 -0
  6. data/benchmarks/activity_benchmark.rb +35 -0
  7. data/benchmarks/report_benchmark.rb +21 -0
  8. data/lib/sir_tracks_alot/activity.rb +139 -0
  9. data/lib/sir_tracks_alot/clock.rb +7 -0
  10. data/lib/sir_tracks_alot/persistable.rb +17 -0
  11. data/lib/sir_tracks_alot/queue/queue_helper.rb +24 -0
  12. data/lib/sir_tracks_alot/queue/report_cache.rb +13 -0
  13. data/lib/sir_tracks_alot/queue/report_config.rb +30 -0
  14. data/lib/sir_tracks_alot/queue/report_queue.rb +59 -0
  15. data/lib/sir_tracks_alot/reports/activity_report.rb +37 -0
  16. data/lib/sir_tracks_alot/reports/actor_activity_report.rb +47 -0
  17. data/lib/sir_tracks_alot/reports/actor_report.rb +54 -0
  18. data/lib/sir_tracks_alot/reports/basic_report.rb +30 -0
  19. data/lib/sir_tracks_alot/reports/filter_report.rb +55 -0
  20. data/lib/sir_tracks_alot/reports/report.rb +35 -0
  21. data/lib/sir_tracks_alot/reports/root_stem_report.rb +44 -0
  22. data/lib/sir_tracks_alot/reports/target_report.rb +54 -0
  23. data/lib/sir_tracks_alot/reports/trackable_report.rb +13 -0
  24. data/lib/sir_tracks_alot.rb +129 -0
  25. data/spec/activity_spec.rb +174 -0
  26. data/spec/queue/report_config_spec.rb +21 -0
  27. data/spec/queue/report_queue_spec.rb +71 -0
  28. data/spec/reports/activity_report_spec.rb +30 -0
  29. data/spec/reports/actor_activity_report_spec.rb +21 -0
  30. data/spec/reports/actor_report_spec.rb +39 -0
  31. data/spec/reports/basic_report_spec.rb +58 -0
  32. data/spec/reports/filter_report_spec.rb +46 -0
  33. data/spec/reports/root_stem_report_spec.rb +50 -0
  34. data/spec/reports/shared_report_specs.rb +9 -0
  35. data/spec/reports/target_report_spec.rb +62 -0
  36. data/spec/sir_tracks_alot_spec.rb +101 -0
  37. data/spec/spec.opts +7 -0
  38. data/spec/spec_helper.rb +66 -0
  39. data/test/helper.rb +10 -0
  40. data/test/test_sir_tracks_alot.rb +7 -0
  41. data/views/reports/group.html.erb +27 -0
  42. data/views/reports/table.html.erb +24 -0
  43. metadata +234 -0
@@ -0,0 +1,35 @@
1
+ module SirTracksAlot
2
+ module Reports
3
+ class Report < Ruport::Controller
4
+ attr_accessor :all
5
+
6
+ def setup
7
+ self.data = []
8
+ options.report_class ||= ''
9
+ options.report_title ||= 'Report'
10
+ end
11
+
12
+ module Helpers
13
+ def self.handle(name, &block)
14
+ @@handlers ||= {}
15
+ name = [name] unless name.kind_of?(Array)
16
+
17
+ name.each do |n|
18
+ @@handlers[n] = block
19
+ end
20
+ end
21
+
22
+ def to_dom_class(words)
23
+ (words||'').gsub(/[^a-z0-9]/, '_')
24
+ end
25
+
26
+ def render_data(name, value)
27
+ @@handlers ||= {}
28
+ return value if !@@handlers.include?(name)
29
+ @@handlers[name].call(value)
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ module SirTracksAlot
2
+ module Reports
3
+ class RootStemReport < FilterReport
4
+ stage :base
5
+
6
+ required_option :owner
7
+
8
+ # Build up reports by filtering things as follows:
9
+ # actions = []
10
+ # roots = {}
11
+ def setup
12
+ options.actions ||= Activity::ACTIONS
13
+ options.filters ||= {}
14
+
15
+ options.actions.each do |action|
16
+ options.roots.each do |root|
17
+ cat, target = root.kind_of?(Array) ? root : [root, root]
18
+
19
+ begin
20
+ options.filters["#{cat||target} index"] = {:category => cat, :target => /^\/#{target}$/, :action => action}
21
+ options.filters["#{cat||target} pages"] = {:category => cat, :target => /^\/#{target}\/.+$/, :action => action}
22
+ rescue RegexpError
23
+ # swallow
24
+ end
25
+ end
26
+ end
27
+
28
+ super
29
+ end
30
+
31
+ module Helpers
32
+ include Report::Helpers
33
+ end
34
+
35
+ class HTML < Ruport::Formatter::HTML
36
+ renders :html, :for => RootStemReport
37
+
38
+ build :base do
39
+ output << erb(options.template || TRACKABLE_ROOT+"/views/reports/table.html.erb", :binding => binding)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,54 @@
1
+ module SirTracksAlot
2
+ module Reports
3
+ class TargetReport < SirTracksAlotReport
4
+ COLUMN_NAMES = ['target', 'page views', 'visits']
5
+
6
+ stage :target
7
+ required_option :owner, :actions
8
+
9
+ def setup
10
+ super
11
+ counts = {}
12
+ options.filters ||= false
13
+ options.column_names ||= COLUMN_NAMES
14
+ options.actions ||= []
15
+ options.roots ||= []
16
+
17
+ options.roots.each do |root|
18
+ options.actions.each do |action|
19
+ activities = Activity.filter(:owner => options.owner, :target => options.target, :action => action, :category => root)
20
+
21
+ activities.each do |activity|
22
+ counts[activity.target] ||= [0,0]
23
+ counts[activity.target][0] += activity.views
24
+ counts[activity.target][1] += activity.visits(options.session_duration)
25
+ end
26
+ end
27
+ end
28
+
29
+ table = Table(options.column_names) do |t|
30
+ counts.each do |target, count|
31
+ t << [target, count[0], count[1]]
32
+ end
33
+ end
34
+
35
+ table.sort_rows_by!('page views', :order => :descending)
36
+
37
+ self.data = table
38
+ end
39
+
40
+ module Helpers
41
+ include Report::Helpers
42
+ end
43
+
44
+
45
+ class HTML < Ruport::Formatter::HTML
46
+ renders :html, :for => TargetReport
47
+
48
+ build :target do
49
+ output << erb((options.template || TRACKABLE_ROOT+"/views/reports/table.html.erb"), :binding => binding)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ module SirTracksAlot
2
+ module Reports
3
+ class SirTracksAlotReport < Report
4
+ attr_accessor :all
5
+
6
+ def setup
7
+ super
8
+ options.categories = options.categories #|| all.collect{|a| a.category}.uniq
9
+ options.actions = options.actions #|| all.collect{|a| a.action}.uniq
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,129 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require "rubygems"
5
+ require "bundler"
6
+ Bundler.setup
7
+ Bundler.require
8
+
9
+ module SirTracksAlot
10
+ TRACKABLE_ROOT = "#{File.dirname(__FILE__)}/.."
11
+
12
+ autoload :Persistable, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot persistable.rb])
13
+ autoload :Rollup, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot rollup.rb])
14
+ autoload :Activity, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot activity.rb])
15
+ autoload :Clock, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot clock.rb])
16
+
17
+ class SirTracksAlotError < StandardError
18
+ end
19
+
20
+ class RecordInvalidError < SirTracksAlotError
21
+ end
22
+
23
+ # Creates new activity with attribues:
24
+ # :owner => string to roll up all activities by e.g. /site/1
25
+ # :actor => string representing who done it e.g. /profiles/peter-brown
26
+ # :target => string representing where they done it e.g. /messages/some-subject
27
+ # :action => what they done e.g. view or create
28
+ # :event => Time.utc.to_i of when the action activity, defaults to now
29
+ def self.record(attributes)
30
+ # event defaults to now unless overwritten
31
+ event = (attributes.delete(:event) || Clock.now)
32
+ request = attributes.delete(:request)
33
+
34
+ # try to convert to trackable paths if possible
35
+ [:target, :actor, :owner].each {|key| attributes[key] = attributes[key].respond_to?(:to_trackable_id) ?
36
+ attributes[key].to_trackable_id :
37
+ attributes[key].to_s}
38
+
39
+ # automatically extract the root, assign to "category"
40
+ attributes[:category] = attributes[:target].split('/')[1]
41
+
42
+ # assign the user_agent from the request unless overritten
43
+ attributes[:user_agent] ||= (request.nil? ? nil : request.try(:user_agent))
44
+
45
+ return if exclude?(attributes)
46
+
47
+ # find or create
48
+ activity = Activity.find_or_create(attributes)
49
+
50
+ raise RecordInvalidError.new("Activity not valid: #{activity.errors.inspect}") unless activity.valid?
51
+
52
+ activity.update(:last_event => event )
53
+ activity.events << event
54
+ activity
55
+ end
56
+
57
+ # add a hash of attribute/filter pairs to ignore when recording activities
58
+ # e.g. {:user_agent => /google/}, {:target => '/hidden', :actor => 'spy'}
59
+ def self.exclude(row)
60
+ @@filters ||= []
61
+ @@filters << row
62
+ end
63
+
64
+ def self.exclude?(attributes)
65
+ @@filters ||= []
66
+
67
+ return false if @@filters.empty? || attributes.nil?
68
+
69
+ @@filters.each do |filter|
70
+ filter.each do |key, matcher|
71
+ matcher = /#{matcher}/ if matcher.kind_of?(String)
72
+ return true if attributes[key] =~ matcher
73
+ end
74
+ end
75
+
76
+ false
77
+ end
78
+
79
+ def self.clear!
80
+ @@filters = []
81
+ end
82
+
83
+ def log
84
+ return @log if @log
85
+
86
+ @log = Logging::Logger[self]
87
+ @log.level = :debug
88
+ @log
89
+ end
90
+
91
+ module_function :log
92
+
93
+ module Helper
94
+ def to_trackable_id
95
+ '/'+[self.class.name.underscore.pluralize, self.to_param].join('/')
96
+ end
97
+
98
+ def find_by_trackable_id(trackable_id)
99
+ parts = trackable_id.split('/')
100
+ return unless parts.size == 3
101
+ return part[1].constantize.find(parts[2])
102
+ end
103
+ end
104
+
105
+ module Queue
106
+ autoload :ReportQueue, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot queue report_queue.rb])
107
+ autoload :ReportCache, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot queue report_cache.rb])
108
+ autoload :ReportConfig, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot queue report_config.rb])
109
+ autoload :QueueHelper, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot queue queue_helper.rb])
110
+ end
111
+
112
+ module Reports
113
+ autoload :Report, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports report.rb])
114
+ autoload :BasicReport, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports basic_report.rb])
115
+ autoload :SirTracksAlotReport, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports trackable_report.rb])
116
+ autoload :TargetReport, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports target_report.rb])
117
+ autoload :ActorReport, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports actor_report.rb])
118
+ autoload :ActorActivityReport, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports actor_activity_report.rb])
119
+ autoload :ActivityReport, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports activity_report.rb])
120
+ autoload :FilterReport, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports filter_report.rb])
121
+ autoload :RootStemReport, File.join(File.dirname(__FILE__), *%w[sir_tracks_alot reports root_stem_report.rb])
122
+ end
123
+ end
124
+
125
+ # Set it all up.
126
+ if Object.const_defined?("ActiveRecord")
127
+ ActiveRecord::Base.send(:include, SirTracksAlot::Helper)
128
+ end
129
+
@@ -0,0 +1,174 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Activity do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @now = SirTracksAlot::Clock.now
9
+
10
+ @activity = SirTracksAlot::Activity.create(@activities[0])
11
+ @activity.events << @now
12
+ end
13
+
14
+ context 'when creating' do
15
+ before do
16
+ end
17
+
18
+ it "should store valid activities" do
19
+ @activity.should be_valid
20
+ end
21
+
22
+ it "should not store invalid activities" do
23
+ activity = SirTracksAlot::Activity.create({})
24
+ activity.should_not be_valid
25
+ end
26
+
27
+ it "should not validate duplicates" do
28
+ activity = SirTracksAlot::Activity.create(@activities[0]) # already created in before
29
+ activity.should_not be_valid
30
+ end
31
+ end
32
+
33
+ context 'when filtering' do
34
+ before do
35
+ @mock_activity = mock(SirTracksAlot::Activity, @activities[0])
36
+ @mock_activityz = mock('SortedActivity', :sort => [@mock_activity])
37
+ SirTracksAlot::Activity.stub!(:find => @mock_activityz)
38
+ end
39
+
40
+ it "should find activities matching attribute regex" do
41
+ SirTracksAlot::Activity.filter(:owner => 'owner', :target => /categories/).size.should == 1
42
+ end
43
+
44
+ it "should find activities matching attribute string" do
45
+ SirTracksAlot::Activity.filter(:owner => 'owner', :category => 'category').size.should == 1
46
+ end
47
+
48
+ it "should find activities matching attribute string and attribute regex" do
49
+ SirTracksAlot::Activity.filter(:owner => 'owner', :category => 'category', :target => /categories/).size.should == 1
50
+ end
51
+
52
+ it "should not find activities matching attribute string but not attribute regex" do
53
+ SirTracksAlot::Activity.filter(:owner => 'owner', :category => 'category', :target => /not_there/).size.should == 0
54
+ end
55
+
56
+ it "should not match with negative regex" do
57
+ SirTracksAlot::Activity.filter(:owner => /^(?!(.*owner.*))/).size.should == 0
58
+ end
59
+ end
60
+
61
+ context 'when getting recent' do
62
+ it "should get recent" do
63
+ SirTracksAlot::Activity.recent(:owner => 'owner').should be_instance_of(Array)
64
+ end
65
+ end
66
+
67
+ context 'when counting' do
68
+ before do
69
+ @mock_activity = mock(SirTracksAlot::Activity, :visits => 1, :views => 2)
70
+ @mock_activityz = mock('SortedActivity', :sort => [@mock_activity])
71
+ SirTracksAlot::Activity.stub!(:find => @mock_activityz)
72
+ end
73
+
74
+ it "should look for activities using find options" do
75
+ SirTracksAlot::Activity.should_receive(:find).with(:owner => 'owner').and_return(@mock_activityz)
76
+ SirTracksAlot::Activity.count(:views, :owner => 'owner')
77
+ end
78
+
79
+ it "should look for views when counting views" do
80
+ @mock_activity.should_receive(:views).once.and_return(1)
81
+ SirTracksAlot::Activity.count(:views, :owner => 'owner')
82
+ end
83
+
84
+ it "should not look for views when counting visits" do
85
+ @mock_activity.should_receive(:views).never
86
+ SirTracksAlot::Activity.count(:visits, :owner => 'owner')
87
+ end
88
+
89
+ it "should return views and visits" do
90
+ SirTracksAlot::Activity.count([:visits, :views], :owner => 'owner').should == [1,2]
91
+ end
92
+
93
+ it "should return visits" do
94
+ SirTracksAlot::Activity.count([:visits], :owner => 'owner').should == 1
95
+ end
96
+
97
+ it "should ignore empty only" do
98
+ SirTracksAlot::Activity.count([:visits], :owner => 'owner', :only => {}).should == 1
99
+ end
100
+
101
+ it "should filter by category" do
102
+ @mock_activity.should_receive(:category).once.and_return('category')
103
+ SirTracksAlot::Activity.count([:visits], :owner => 'owner', :category => /category/)
104
+ end
105
+
106
+ it "should filter by category and match returning correct count" do
107
+ @mock_activity.stub!(:category => 'category')
108
+ SirTracksAlot::Activity.count([:visits], :owner => 'owner', :category => /category/).should == 1
109
+ end
110
+ end
111
+
112
+ context 'when counting groups' do
113
+ before do
114
+ # nothing
115
+ end
116
+
117
+ it "should try to parse event time" do
118
+ time = Time.at(@now)
119
+ Time.should_receive(:at).with(@now).and_return(time)
120
+ @activity.views(:daily)
121
+ end
122
+
123
+ it "should return grouped counts" do
124
+ @activity.views(:daily).should be_kind_of(Hash)
125
+ end
126
+
127
+ it "should return count as hash value" do
128
+ @activity.views(:daily).values.should == [1]
129
+ end
130
+
131
+ it "should return count key'd by date" do
132
+ key = Time.at(@now).utc.strftime(SirTracksAlot::Activity::DATE_FORMATS[:daily])
133
+ @activity.views(:daily).keys.should == [key]
134
+ end
135
+
136
+ it "should return count multiple for single group" do
137
+ SirTracksAlot::Activity.all.first.events << @now
138
+ @activity.views(:daily).values.should == [2]
139
+ end
140
+
141
+ it "should return count multiple for single group" do
142
+ # SirTracksAlot::Activity.count_by(:daily).should == ''
143
+ end
144
+
145
+ end
146
+
147
+ context 'when calculating visits' do
148
+ it "should count 2 visits within the duration as one visit" do
149
+ 2.times{@activity.events << SirTracksAlot::Clock.now}
150
+ @activity.visits.should == 1
151
+ end
152
+
153
+ it "should count 200 visits within the duration as one visit" do
154
+ 200.times{@activity.events << SirTracksAlot::Clock.now}
155
+ @activity.visits.should == 1
156
+ end
157
+
158
+ it "should count 2 visits separated by greater than default session duration as 2 visits" do
159
+ @activity.events << SirTracksAlot::Clock.now
160
+ @activity.events << SirTracksAlot::Clock.now + 2000
161
+ @activity.visits.should == 2
162
+ end
163
+
164
+ it "should count 2 visits separated by greater than default session duration as 2 visits but not by not" do
165
+ 5.times{@activity.events << SirTracksAlot::Clock.now}
166
+ @activity.events << SirTracksAlot::Clock.now + 2000
167
+ @activity.events << SirTracksAlot::Clock.now + 4000
168
+ @activity.visits.should == 3
169
+ end
170
+
171
+ end
172
+
173
+ end
174
+
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Queue::ReportConfig do
4
+ before do
5
+ RedisSpecHelper.reset
6
+ @config = SirTracksAlot::Queue::ReportConfig.find_or_create(:owner => 'owner')
7
+ end
8
+
9
+ it "should build" do
10
+ @config.should be_instance_of(SirTracksAlot::Queue::ReportConfig)
11
+ end
12
+
13
+ it "should have default options" do
14
+ @config.options.should be_instance_of(Hash)
15
+ end
16
+
17
+ it "should store options" do
18
+ @config.options = {:this => 'that'}
19
+ @config.options.should == {:this => 'that'}
20
+ end
21
+ end
@@ -0,0 +1,71 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Queue::ReportQueue do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @queue = SirTracksAlot::Queue::ReportQueue.find_or_create(:name => 'queue')
9
+ end
10
+
11
+ it "should build" do
12
+ @queue.should be_instance_of(SirTracksAlot::Queue::ReportQueue)
13
+ end
14
+
15
+ context 'when pushing' do
16
+ before do
17
+ @config = mock(SirTracksAlot::Queue::ReportConfig, :options => {}, :options= => true, :id => 1)
18
+ @activities.each{|a| SirTracksAlot.record(a)}
19
+ SirTracksAlot::Queue::ReportQueue.push('owner', :actor_report, {})
20
+ end
21
+
22
+ it "should create a new queue" do
23
+ SirTracksAlot::Queue::ReportQueue.should_receive(:find_or_create).with(:name => SirTracksAlot::Queue::ReportQueue::QUEUE_NAME).and_return(@queue)
24
+ SirTracksAlot::Queue::ReportQueue.push('owner', 'report', {})
25
+ end
26
+
27
+ it "should create report config" do
28
+ SirTracksAlot::Queue::ReportConfig.should_receive(:find_or_create).with(:owner => 'owner', :report => 'report').once.and_return(@config)
29
+ SirTracksAlot::Queue::ReportQueue.push('owner', 'report', {})
30
+ end
31
+ end
32
+
33
+ context 'when popping' do
34
+ before do
35
+ @cache = mock(SirTracksAlot::Queue::ReportCache, :update => true)
36
+ @activities.each{|a| SirTracksAlot.record(a)}
37
+ SirTracksAlot::Queue::ReportQueue.push('owner', :actor_report, {:roots => ['categories', 'other_categories'], :actions => ['view']})
38
+ end
39
+
40
+ it "should find existing queue" do
41
+ SirTracksAlot::Queue::ReportQueue.should_receive(:find).with(:name => SirTracksAlot::Queue::ReportQueue::QUEUE_NAME).and_return(nil)
42
+ SirTracksAlot::Queue::ReportQueue.pop
43
+ end
44
+
45
+ it "should create report cache" do
46
+ SirTracksAlot::Queue::ReportCache.should_receive(:find_or_create).with(:owner => 'owner', :report => 'actor_report').and_return(@cache)
47
+ SirTracksAlot::Queue::ReportQueue.pop
48
+ end
49
+
50
+ it "should constantize report by name" do
51
+ SirTracksAlot::Queue::QueueHelper.should_receive(:constantize).with("SirTracksAlot::Reports::ActorReport").and_return(SirTracksAlot::Reports::ActorReport)
52
+ SirTracksAlot::Queue::ReportQueue.pop
53
+ end
54
+
55
+ it "should build report HTML" do
56
+ SirTracksAlot::Queue::ReportCache.should_receive(:find_or_create).with(:owner => 'owner', :report => 'actor_report').and_return(@cache)
57
+ @cache.should_receive(:update) do |options|
58
+ options[:html].should have_tag('td.actor', /\/users\/user1/)
59
+ end
60
+ SirTracksAlot::Queue::ReportQueue.pop
61
+ end
62
+
63
+ end
64
+
65
+ context 'when contantizing' do
66
+ it "should support mixed" do
67
+ # not sure why constantize has trouble with the be_instance_of matcher
68
+ SirTracksAlot::Queue::QueueHelper.constantize('SirTracksAlot::Reports::ActorReport').to_s.should == 'SirTracksAlot::Reports::ActorReport'
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Reports::ActivityReport do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @report_options = {:owner => 'owner', :actor => 'actor'}
9
+ end
10
+
11
+ it "should render empty" do
12
+ SirTracksAlot::Reports::ActivityReport.render_html(@report_options)
13
+ end
14
+
15
+ context 'building HTML' do
16
+ before do
17
+ @activities.each{|a| SirTracksAlot.record(a)}
18
+ @html = SirTracksAlot::Reports::ActivityReport.render_html(@report_attributes)
19
+ end
20
+
21
+ it "include target row" do
22
+ @html.should have_tag('td.target', /\/categories\/item/)
23
+ end
24
+
25
+ it "include count row" do
26
+ @html.should have_tag('td.event', :count => @activities.size)
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Reports::ActorActivityReport do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @activities.each{|a| SirTracksAlot.record(a)}
9
+ end
10
+
11
+ context 'building HTML' do
12
+ before do
13
+ @html = SirTracksAlot::Reports::ActorActivityReport.render_html(:owner => @activities[0][:owner], :actor => @activities[0][:actor])
14
+ end
15
+
16
+ it "should so and so" do
17
+ @html.should have_tag('td', /\/categories\/item1/)
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,39 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Reports::ActorReport do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @activities.each{|a| SirTracksAlot.record(a)}
9
+ @mock_finder = mock('Finder', :find => [], :collect => [], :each => [])
10
+ end
11
+
12
+ it "should raise error on mission options" do
13
+ lambda{SirTracksAlot::Reports::ActorReport.render_html({})}.should raise_error(Ruport::Controller::RequiredOptionNotSet)
14
+ end
15
+
16
+ it "should find activities when building data" do
17
+ SirTracksAlot::Activity.should_receive(:find).exactly(1).times.and_return(@mock_finder)
18
+ SirTracksAlot::Reports::ActorReport.render_html(@report_attributes)
19
+ end
20
+
21
+ context 'building HTML' do
22
+ before do
23
+ SirTracksAlot.record(:owner => 'other_owner', :target => '/other_categories/item', :actor => '/users/user', :action => 'view')
24
+ @html = SirTracksAlot::Reports::ActorReport.render_html(@report_attributes)
25
+ end
26
+
27
+ it "include target row" do
28
+ @html.should have_tag('td.actor',/\/users\/user1/)
29
+ end
30
+
31
+ it "include count row" do
32
+ @html.should have_tag('td.count', /1/)
33
+ end
34
+
35
+ it "should ignore other owners" do
36
+ @html.should_not have_tag('td.actor', '/users/user')
37
+ end
38
+ end
39
+ end