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.
- data/Gemfile +12 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/benchmarks/activity_benchmark.rb +35 -0
- data/benchmarks/report_benchmark.rb +21 -0
- data/lib/sir_tracks_alot/activity.rb +139 -0
- data/lib/sir_tracks_alot/clock.rb +7 -0
- data/lib/sir_tracks_alot/persistable.rb +17 -0
- data/lib/sir_tracks_alot/queue/queue_helper.rb +24 -0
- data/lib/sir_tracks_alot/queue/report_cache.rb +13 -0
- data/lib/sir_tracks_alot/queue/report_config.rb +30 -0
- data/lib/sir_tracks_alot/queue/report_queue.rb +59 -0
- data/lib/sir_tracks_alot/reports/activity_report.rb +37 -0
- data/lib/sir_tracks_alot/reports/actor_activity_report.rb +47 -0
- data/lib/sir_tracks_alot/reports/actor_report.rb +54 -0
- data/lib/sir_tracks_alot/reports/basic_report.rb +30 -0
- data/lib/sir_tracks_alot/reports/filter_report.rb +55 -0
- data/lib/sir_tracks_alot/reports/report.rb +35 -0
- data/lib/sir_tracks_alot/reports/root_stem_report.rb +44 -0
- data/lib/sir_tracks_alot/reports/target_report.rb +54 -0
- data/lib/sir_tracks_alot/reports/trackable_report.rb +13 -0
- data/lib/sir_tracks_alot.rb +129 -0
- data/spec/activity_spec.rb +174 -0
- data/spec/queue/report_config_spec.rb +21 -0
- data/spec/queue/report_queue_spec.rb +71 -0
- data/spec/reports/activity_report_spec.rb +30 -0
- data/spec/reports/actor_activity_report_spec.rb +21 -0
- data/spec/reports/actor_report_spec.rb +39 -0
- data/spec/reports/basic_report_spec.rb +58 -0
- data/spec/reports/filter_report_spec.rb +46 -0
- data/spec/reports/root_stem_report_spec.rb +50 -0
- data/spec/reports/shared_report_specs.rb +9 -0
- data/spec/reports/target_report_spec.rb +62 -0
- data/spec/sir_tracks_alot_spec.rb +101 -0
- data/spec/spec.opts +7 -0
- data/spec/spec_helper.rb +66 -0
- data/test/helper.rb +10 -0
- data/test/test_sir_tracks_alot.rb +7 -0
- data/views/reports/group.html.erb +27 -0
- data/views/reports/table.html.erb +24 -0
- 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
|