sir_tracks_alot 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source :gemcutter
2
+ gem 'logging'
3
+ gem 'twitter'
4
+ gem 'ruport'
5
+ gem 'ohm'
6
+ gem 'redis'
7
+
8
+ group :development do
9
+ gem 'rspec'
10
+ gem 'rspec_hpricot_matchers'
11
+ gem 'hpricot'
12
+ end
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,7 @@
1
+ module SirTracksAlot
2
+ class Clock
3
+ def self.now
4
+ Time.now.utc.to_i
5
+ end
6
+ end
7
+ 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,13 @@
1
+ module SirTracksAlot
2
+ module Queue
3
+ class ReportCache < Persistable
4
+ attribute :created_at
5
+ attribute :report
6
+ attribute :owner
7
+ attribute :html
8
+
9
+ index :report
10
+ index :owner
11
+ end
12
+ end
13
+ 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