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
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Peter T. Brown
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= sir_tracks_alot
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Peter T. Brown. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "sir_tracks_alot"
|
9
|
+
gem.summary = %Q{A high speed general purpose tracking and reporting tool which uses Redis.}
|
10
|
+
gem.description = %Q{A high speed general purpose tracking and reporting tool which uses Redis.}
|
11
|
+
gem.email = "peter@flippyhead.com"
|
12
|
+
gem.homepage = "http://github.com/flippyhead/sir_tracks_alot"
|
13
|
+
gem.authors = ["Peter T. Brown"]
|
14
|
+
gem.add_bundler_dependencies
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ohm'
|
3
|
+
require 'lib/trackable'
|
4
|
+
|
5
|
+
Ohm.connect(:host => 'localhost.com', :timeout => 10)
|
6
|
+
|
7
|
+
@owner = '/events/md10'
|
8
|
+
|
9
|
+
activities_count = Trackable::Activity.filter(:owner => @owner).size
|
10
|
+
events_count = 0; Trackable::Activity.filter(:owner => @owner).each{|a| events_count += a.events.size}
|
11
|
+
|
12
|
+
puts "**** Running benchmarks against dataset with #{activities_count} activities and #{events_count} events:"
|
13
|
+
|
14
|
+
Benchmark.bmbm do |b|
|
15
|
+
|
16
|
+
b.report('grabbing all activities, and iterating') do
|
17
|
+
Trackable::Activity.filter(:owner => @owner).collect{|a| a.target}
|
18
|
+
end
|
19
|
+
|
20
|
+
b.report('filter only') do
|
21
|
+
Trackable::Activity.filter(:owner => @owner)
|
22
|
+
end
|
23
|
+
|
24
|
+
b.report('filter and retrieve last_event') do
|
25
|
+
Trackable::Activity.filter(:owner => @owner).map{|a| a.last_event}
|
26
|
+
end
|
27
|
+
|
28
|
+
b.report('filter and retrieve all events') do
|
29
|
+
Trackable::Activity.filter(:owner => @owner).map{|a| a.events.map{|e| e}}
|
30
|
+
end
|
31
|
+
|
32
|
+
b.report('count_by daily') do
|
33
|
+
counts = Trackable::Activity.count_by(:daily, :owner => @owner)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ohm'
|
3
|
+
require 'lib/trackable'
|
4
|
+
|
5
|
+
Ohm.connect(:host => 'localhost.com', :timeout => 10)
|
6
|
+
|
7
|
+
@owner = '/events/md10'
|
8
|
+
@categories = Trackable::Activity.filter(:owner => @owner).collect{|a| a.category}.uniq
|
9
|
+
@actions = Trackable::Activity.filter(:owner => @owner).collect{|a| a.action}.uniq
|
10
|
+
|
11
|
+
activities_count = Trackable::Activity.filter(:owner => @owner).size
|
12
|
+
events_count = 0; Trackable::Activity.filter(:owner => @owner).each{|a| events_count += a.events.size}
|
13
|
+
|
14
|
+
puts "** Running benchmarks against dataset with #{activities_count} activities, #{events_count} events, and #{@categories.size} categories:"
|
15
|
+
|
16
|
+
Benchmark.bmbm do |b|
|
17
|
+
b.report('targer report') do
|
18
|
+
report = Trackable::Reports::TargetReport.render_html(:owner => @owner, :roots => @categories, :actions => @actions)
|
19
|
+
# puts report
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
class Activity < Persistable
|
3
|
+
ACTIONS = [:create, :view, :login, :search, :update, :destroy]
|
4
|
+
DATE_FORMATS = {:hourly => '%Y/%m/%d %H', :daily => '%Y/%m/%d'}
|
5
|
+
LIMIT = 1000
|
6
|
+
|
7
|
+
attribute :created_at
|
8
|
+
attribute :last_event # Clock.now
|
9
|
+
attribute :owner # 123123
|
10
|
+
attribute :actor # /users/peter-brown
|
11
|
+
attribute :target # /discussions/23423
|
12
|
+
attribute :category # **automatically set**
|
13
|
+
attribute :action # create, view, login, etc.
|
14
|
+
attribute :user_agent # IE/Safari Windows/Mac etc.
|
15
|
+
|
16
|
+
index :last_event
|
17
|
+
index :owner
|
18
|
+
index :actor
|
19
|
+
index :target
|
20
|
+
index :category
|
21
|
+
index :action
|
22
|
+
index :user_agent
|
23
|
+
|
24
|
+
list :events # Clock.now's
|
25
|
+
|
26
|
+
# Find activities that match attributes
|
27
|
+
# Strings are passed to Ohm
|
28
|
+
# Regular Expression filters match against retrieved attribute values
|
29
|
+
#
|
30
|
+
# filter(:category => 'category', :target => /\/targets\/\d+/)
|
31
|
+
def self.filter(options_for_find, &block)
|
32
|
+
activities = []
|
33
|
+
sort = options_for_find.delete(:sort) || {}
|
34
|
+
|
35
|
+
strings = {}
|
36
|
+
matchers = {}
|
37
|
+
|
38
|
+
options_for_find.each do |key, candidate|
|
39
|
+
matchers[key] = candidate if candidate.kind_of?(Regexp)
|
40
|
+
strings[key] = candidate if candidate.kind_of?(String)
|
41
|
+
end
|
42
|
+
|
43
|
+
all = SirTracksAlot::Activity.find(strings).sort(sort)
|
44
|
+
|
45
|
+
all.each do |activity|
|
46
|
+
pass = true
|
47
|
+
|
48
|
+
matchers.each do |key, matcher|
|
49
|
+
pass = false if !matcher.match(activity.send(key))
|
50
|
+
end
|
51
|
+
|
52
|
+
next unless pass
|
53
|
+
|
54
|
+
yield activity if block_given?
|
55
|
+
|
56
|
+
activities << activity
|
57
|
+
end
|
58
|
+
|
59
|
+
activities
|
60
|
+
end
|
61
|
+
|
62
|
+
# resolution can be: hourly = %Y/%m/%d %H, daily = %Y/%m/%d or any valid strftime string
|
63
|
+
def self.count_by(resolution, options_for_find = {})
|
64
|
+
groups = {}
|
65
|
+
|
66
|
+
filter(options_for_find) do |activity|
|
67
|
+
activity.views(resolution).each do |time, count|
|
68
|
+
groups[time] ||= 0
|
69
|
+
groups[time] += count
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
groups
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.recent(options_for_find, options_for_sort = {:order => 'DESC', :limit => LIMIT})
|
77
|
+
SirTracksAlot::Activity.find(options_for_find).sort_by(:last_event, options_for_sort)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.count(what, options_for_find)
|
81
|
+
what = [what] unless what.kind_of?(Array)
|
82
|
+
views, visits = 0, 0
|
83
|
+
session_duration = options_for_find.delete(:session_duration)
|
84
|
+
resolution = options_for_find.delete(:resolution)
|
85
|
+
|
86
|
+
filter(options_for_find) do |activity|
|
87
|
+
views += activity.views(resolution) if what.include?(:views)
|
88
|
+
visits += activity.visits(session_duration) if what.include?(:visits)
|
89
|
+
end
|
90
|
+
|
91
|
+
return [views, visits] if what == [:views, :visits]
|
92
|
+
return [visits, views] if what == [:visits, :views]
|
93
|
+
return views if what == [:views]
|
94
|
+
return visits if what == [:visits]
|
95
|
+
raise ArgumentError("what must be one or more of :views, :visits")
|
96
|
+
end
|
97
|
+
|
98
|
+
def views(resolution = nil)
|
99
|
+
return events.size if resolution.nil?
|
100
|
+
|
101
|
+
groups = {}
|
102
|
+
|
103
|
+
date_format = resolution.kind_of?(String) ? resolution : DATE_FORMATS[resolution]
|
104
|
+
|
105
|
+
events.each do |event|
|
106
|
+
time = Time.at(event.to_i).utc.strftime(date_format) # lop of some detail
|
107
|
+
groups[time.to_s] ||= 0
|
108
|
+
groups[time.to_s] += 1
|
109
|
+
end
|
110
|
+
|
111
|
+
return groups
|
112
|
+
end
|
113
|
+
|
114
|
+
# Count one visit for all events every session_duration (in seconds)
|
115
|
+
# e.g. 3 visits within 15 minutes counts as one, 1 visit 2 days later counts as another
|
116
|
+
def visits(sd = 1800)
|
117
|
+
session_duration = sd || 1800
|
118
|
+
count = events.size > 0 ? 1 : 0
|
119
|
+
last_event = events.first
|
120
|
+
|
121
|
+
events.each do |event|
|
122
|
+
count += 1 if (event.to_i - last_event.to_i > session_duration)
|
123
|
+
last_event = event
|
124
|
+
end
|
125
|
+
|
126
|
+
count
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def validate
|
132
|
+
assert_present :owner
|
133
|
+
assert_present :target
|
134
|
+
assert_present :action
|
135
|
+
assert_unique([:owner, :target, :action, :category, :user_agent, :actor])
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'ohm'
|
2
|
+
|
3
|
+
module SirTracksAlot
|
4
|
+
class Persistable < Ohm::Model
|
5
|
+
attribute :created_at
|
6
|
+
|
7
|
+
def self.find_or_create(attributes)
|
8
|
+
models = self.find(attributes)
|
9
|
+
models.empty? ? self.create(attributes.merge(:created_at => Clock.now)) : models.first
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def validate
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
module Queue
|
3
|
+
class QueueHelper
|
4
|
+
def self.camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
5
|
+
if first_letter_in_uppercase
|
6
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
7
|
+
else
|
8
|
+
lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.constantize(camel_cased_word)
|
13
|
+
names = camel_cased_word.split('::')
|
14
|
+
names.shift if names.empty? || names.first.empty?
|
15
|
+
|
16
|
+
constant = Object
|
17
|
+
names.each do |name|
|
18
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
19
|
+
end
|
20
|
+
constant
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
module Queue
|
3
|
+
class ReportConfig < Persistable
|
4
|
+
SESSION_DURATION = 1800
|
5
|
+
LIMIT = 10000
|
6
|
+
|
7
|
+
attribute :created_at
|
8
|
+
attribute :report
|
9
|
+
attribute :owner
|
10
|
+
attribute :options_store
|
11
|
+
|
12
|
+
index :report
|
13
|
+
index :owner
|
14
|
+
|
15
|
+
def options
|
16
|
+
self.options = {:limit => LIMIT, :session_duration => SESSION_DURATION} if options_store.nil?
|
17
|
+
Marshal.load(options_store)
|
18
|
+
end
|
19
|
+
|
20
|
+
def options=(opts)
|
21
|
+
update(:options_store => Marshal.dump(opts))
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate
|
25
|
+
assert_present :report
|
26
|
+
assert_present :owner
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
module Queue
|
3
|
+
class ReportQueue < Persistable
|
4
|
+
QUEUE_NAME = 'trackable_queue'
|
5
|
+
|
6
|
+
attribute :created_at
|
7
|
+
attribute :name
|
8
|
+
index :name
|
9
|
+
list :queue # of ReportConfigs
|
10
|
+
|
11
|
+
def self.push(owner, report, options)
|
12
|
+
config = ReportConfig.find_or_create(:owner => owner.to_s, :report => report.to_s)
|
13
|
+
config.options = options.merge(:owner => owner.to_s) # reports require owner, should be sync'd with config owner
|
14
|
+
|
15
|
+
queue = self.find_or_create(:name => QUEUE_NAME)
|
16
|
+
queue.queue << config.id
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.pop
|
20
|
+
queue = self.find(:name => QUEUE_NAME)
|
21
|
+
|
22
|
+
if queue.nil? || queue.first.nil?
|
23
|
+
SirTracksAlot.log.info("Reports queue has not been created yet.")
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
|
27
|
+
queue = queue.first
|
28
|
+
|
29
|
+
config = ReportConfig[queue.queue.pop] # raw gets us just the id's, pop to remove the las one, it's a queue!
|
30
|
+
|
31
|
+
process(config)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.process(config)
|
35
|
+
return false if config.nil?
|
36
|
+
|
37
|
+
SirTracksAlot.log.info("building report: #{config.inspect}")
|
38
|
+
|
39
|
+
begin
|
40
|
+
report = QueueHelper.constantize(QueueHelper.camelize("SirTracksAlot::Reports::#{config.report.capitalize}"))
|
41
|
+
html = report.render_html(config.options)
|
42
|
+
|
43
|
+
cache = ReportCache.find_or_create(:owner => config.owner, :report => config.report)
|
44
|
+
cache.update(:html => html)
|
45
|
+
rescue Exception => e
|
46
|
+
SirTracksAlot.log.fatal("Error building report #{config.report} for #{config.owner}: #{e}")
|
47
|
+
end
|
48
|
+
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: ability to easily run single reports
|
56
|
+
# e = Event.find_by_domain 'community.mitcio.com'
|
57
|
+
# SirTracksAlot::Queue::ReportConfig.find(:owner => e.to_trackable_id)
|
58
|
+
# c = SirTracksAlot::Queue::ReportConfig.find(:owner => e.to_trackable_id).all[-1]
|
59
|
+
# SirTracksAlot::Queue::ReportQueue.process(c)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
module Reports
|
3
|
+
class ActivityReport < SirTracksAlotReport
|
4
|
+
COLUMN_NAMES = ['actor', 'action', 'target', 'event']
|
5
|
+
LIMIT = 10000
|
6
|
+
|
7
|
+
stage :report
|
8
|
+
required_option :owner
|
9
|
+
|
10
|
+
def setup
|
11
|
+
super
|
12
|
+
options.column_names ||= COLUMN_NAMES
|
13
|
+
|
14
|
+
activities = Activity.recent({:owner => options.owner}, :order => 'DESC', :limit => (options.limit || LIMIT))
|
15
|
+
events = activities.collect{|a| [a.actor, a.action, a.target, a.last_event]}.reject{|a| a[2].nil?}
|
16
|
+
|
17
|
+
table = Table(options.column_names, :data => events)
|
18
|
+
table.sort_rows_by!('event', :order => :descending)
|
19
|
+
|
20
|
+
self.data = table
|
21
|
+
end
|
22
|
+
|
23
|
+
module Helpers
|
24
|
+
include Report::Helpers
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
class HTML < Ruport::Formatter::HTML
|
29
|
+
renders :html, :for => ActivityReport
|
30
|
+
|
31
|
+
build :report do
|
32
|
+
output << erb((options.template || TRACKABLE_ROOT+"/views/reports/table.html.erb"), :binding => binding)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
module Reports
|
3
|
+
class ActorActivityReport < SirTracksAlotReport
|
4
|
+
COLUMN_NAMES = ['target', 'page views', 'visits']
|
5
|
+
|
6
|
+
stage :main
|
7
|
+
required_option :owner, :actor
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
counts = {}
|
12
|
+
options.column_names ||= COLUMN_NAMES
|
13
|
+
|
14
|
+
activities = Activity.find(:owner => options.owner, :actor => options.actor)
|
15
|
+
|
16
|
+
activities.each do |activity|
|
17
|
+
counts[activity.target] ||= [0,0]
|
18
|
+
counts[activity.target][0] += activity.views
|
19
|
+
counts[activity.target][1] += activity.visits(options.session_duration)
|
20
|
+
end
|
21
|
+
|
22
|
+
table = Table(options.column_names) do |t|
|
23
|
+
counts.each do |target, count|
|
24
|
+
t << [target, count[0], count[1]]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
table.sort_rows_by!('page views', :order => :descending)
|
29
|
+
|
30
|
+
self.data = table
|
31
|
+
end
|
32
|
+
|
33
|
+
module Helpers
|
34
|
+
include Report::Helpers
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class HTML < Ruport::Formatter::HTML
|
39
|
+
renders :html, :for => ActorActivityReport
|
40
|
+
|
41
|
+
build :main do
|
42
|
+
output << erb((options.template || TRACKABLE_ROOT+"/views/reports/table.html.erb"), :binding => binding)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
module Reports
|
3
|
+
class ActorReport < SirTracksAlotReport
|
4
|
+
COLUMN_NAMES = ['actor', 'page views', 'visits']
|
5
|
+
|
6
|
+
stage :actor
|
7
|
+
required_option :owner
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
counts = {}
|
12
|
+
options.column_names ||= COLUMN_NAMES
|
13
|
+
options.actions ||= []
|
14
|
+
options.roots ||= []
|
15
|
+
|
16
|
+
options.roots.each do |root|
|
17
|
+
options.actions.each do |action|
|
18
|
+
activities = Activity.find(:owner => options.owner, :action => action, :category => root)
|
19
|
+
|
20
|
+
activities.each do |activity|
|
21
|
+
counts[activity.actor] ||= [0,0]
|
22
|
+
counts[activity.actor][0] += activity.views
|
23
|
+
counts[activity.actor][1] += activity.visits(options.session_duration)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
table = Table(options.column_names) do |t|
|
29
|
+
counts.each do |target, count|
|
30
|
+
t << [target, count[0], count[1]]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
table.sort_rows_by!('page views', :order => :descending)
|
35
|
+
|
36
|
+
self.data = table
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
module Helpers
|
41
|
+
include Report::Helpers
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
class HTML < Ruport::Formatter::HTML
|
46
|
+
renders :html, :for => ActorReport
|
47
|
+
|
48
|
+
build :actor 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,30 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
module Reports
|
3
|
+
class BasicReport < Report
|
4
|
+
COLUMN_NAMES = ['group', 'title', 'count']
|
5
|
+
stage :basic
|
6
|
+
|
7
|
+
def setup
|
8
|
+
options.column_names ||= COLUMN_NAMES
|
9
|
+
|
10
|
+
table = Table(options.column_names, :data => options.data)
|
11
|
+
grouping = Grouping(table, :by => "group", :order => 'group')
|
12
|
+
grouping.each{|n, g| g.sort_rows_by!('count', :order => :descending)}
|
13
|
+
|
14
|
+
self.data = grouping
|
15
|
+
end
|
16
|
+
|
17
|
+
module Helpers
|
18
|
+
include Report::Helpers
|
19
|
+
end
|
20
|
+
|
21
|
+
class HTML < Ruport::Formatter::HTML
|
22
|
+
renders :html, :for => BasicReport
|
23
|
+
|
24
|
+
build :basic do
|
25
|
+
output << erb((options.template || TRACKABLE_ROOT+"/views/reports/group.html.erb"), :binding => binding)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module SirTracksAlot
|
2
|
+
module Reports
|
3
|
+
class FilterReport < SirTracksAlotReport
|
4
|
+
COLUMN_NAMES = ['title', 'page views', 'visits']
|
5
|
+
|
6
|
+
stage :base
|
7
|
+
required_option :owner
|
8
|
+
|
9
|
+
# Build up reports by filtering things. Filters are applied and assigned a row title.
|
10
|
+
#
|
11
|
+
# SirTracksAlot::Reports::FilterReport.render_html(
|
12
|
+
# :actions => ['view', 'search']
|
13
|
+
#
|
14
|
+
# :filters = {
|
15
|
+
# 'Title' => {:category => 'category', :target => /\/categories/}
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
# :filters => {
|
19
|
+
# :only => {Profile Pages' => {:target => /^\/user_profiles\/.+$/}}
|
20
|
+
# :except => {Profile Index' => {:target => /^\/user_profiles$/}}
|
21
|
+
# })
|
22
|
+
def setup
|
23
|
+
super
|
24
|
+
|
25
|
+
counts = []
|
26
|
+
options.filters ||= {}
|
27
|
+
options.column_names ||= COLUMN_NAMES
|
28
|
+
|
29
|
+
options.filters.each do |title, options_for_find|
|
30
|
+
views, visits = Activity.count([:views, :visits], options_for_find.merge(:owner => options.owner))
|
31
|
+
counts << [title, views, visits] if views > 0
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
table = Table(options.column_names, :data => counts)
|
36
|
+
|
37
|
+
table.sort_rows_by!('page views', :order => :descending)
|
38
|
+
|
39
|
+
self.data = table
|
40
|
+
end
|
41
|
+
|
42
|
+
module Helpers
|
43
|
+
include Report::Helpers
|
44
|
+
end
|
45
|
+
|
46
|
+
class HTML < Ruport::Formatter::HTML
|
47
|
+
renders :html, :for => FilterReport
|
48
|
+
|
49
|
+
build :base do
|
50
|
+
output << erb(options.template || TRACKABLE_ROOT+"/views/reports/table.html.erb", :binding => binding)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|