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