sir_tracks_alot 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. data/Gemfile +12 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +17 -0
  4. data/Rakefile +19 -0
  5. data/VERSION +1 -0
  6. data/benchmarks/activity_benchmark.rb +35 -0
  7. data/benchmarks/report_benchmark.rb +21 -0
  8. data/lib/sir_tracks_alot/activity.rb +139 -0
  9. data/lib/sir_tracks_alot/clock.rb +7 -0
  10. data/lib/sir_tracks_alot/persistable.rb +17 -0
  11. data/lib/sir_tracks_alot/queue/queue_helper.rb +24 -0
  12. data/lib/sir_tracks_alot/queue/report_cache.rb +13 -0
  13. data/lib/sir_tracks_alot/queue/report_config.rb +30 -0
  14. data/lib/sir_tracks_alot/queue/report_queue.rb +59 -0
  15. data/lib/sir_tracks_alot/reports/activity_report.rb +37 -0
  16. data/lib/sir_tracks_alot/reports/actor_activity_report.rb +47 -0
  17. data/lib/sir_tracks_alot/reports/actor_report.rb +54 -0
  18. data/lib/sir_tracks_alot/reports/basic_report.rb +30 -0
  19. data/lib/sir_tracks_alot/reports/filter_report.rb +55 -0
  20. data/lib/sir_tracks_alot/reports/report.rb +35 -0
  21. data/lib/sir_tracks_alot/reports/root_stem_report.rb +44 -0
  22. data/lib/sir_tracks_alot/reports/target_report.rb +54 -0
  23. data/lib/sir_tracks_alot/reports/trackable_report.rb +13 -0
  24. data/lib/sir_tracks_alot.rb +129 -0
  25. data/spec/activity_spec.rb +174 -0
  26. data/spec/queue/report_config_spec.rb +21 -0
  27. data/spec/queue/report_queue_spec.rb +71 -0
  28. data/spec/reports/activity_report_spec.rb +30 -0
  29. data/spec/reports/actor_activity_report_spec.rb +21 -0
  30. data/spec/reports/actor_report_spec.rb +39 -0
  31. data/spec/reports/basic_report_spec.rb +58 -0
  32. data/spec/reports/filter_report_spec.rb +46 -0
  33. data/spec/reports/root_stem_report_spec.rb +50 -0
  34. data/spec/reports/shared_report_specs.rb +9 -0
  35. data/spec/reports/target_report_spec.rb +62 -0
  36. data/spec/sir_tracks_alot_spec.rb +101 -0
  37. data/spec/spec.opts +7 -0
  38. data/spec/spec_helper.rb +66 -0
  39. data/test/helper.rb +10 -0
  40. data/test/test_sir_tracks_alot.rb +7 -0
  41. data/views/reports/group.html.erb +27 -0
  42. data/views/reports/table.html.erb +24 -0
  43. metadata +234 -0
@@ -0,0 +1,58 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Reports::BasicReport do
4
+ include DataHelper
5
+
6
+ before do
7
+ @report_options = {'data' => [
8
+ ['Group 1', 'Title', 1],
9
+ ['Group 3', 'Title', 99],
10
+ ['Group 2', 'Title', 2]
11
+ ]}
12
+ RedisSpecHelper.reset
13
+ end
14
+
15
+ it "should render empty" do
16
+ SirTracksAlot::Reports::BasicReport.render_html(@report_options)
17
+ end
18
+
19
+ context 'building HTML' do
20
+ before do
21
+ @html = SirTracksAlot::Reports::BasicReport.render_html(@report_options)
22
+ end
23
+
24
+ it "include target row" do
25
+ @html.should have_tag('td.title', /Title/)
26
+ end
27
+
28
+ it "include count row" do
29
+ @html.should have_tag('td.count', /1/)
30
+ end
31
+
32
+ it "should include group title" do
33
+ @html.should have_tag('h2', /Group 1/)
34
+ end
35
+
36
+ it "should include both group titles" do
37
+ @html.should have_tag('h2', :count => 3)
38
+ end
39
+
40
+ it "should include both group targets" do
41
+ @html.should have_tag('table td.title', :count => 3)
42
+ end
43
+ end
44
+
45
+ context 'using custom handlers' do
46
+ before do
47
+ SirTracksAlot::Reports::Report::Helpers.handle('title') do |value|
48
+ "--#{value}--"
49
+ end
50
+
51
+ @html = SirTracksAlot::Reports::BasicReport.render_html(@report_options)
52
+ end
53
+
54
+ it "should include custom title values" do
55
+ @html.should have_tag('table td.title', /\-\-Title\-\-/)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,46 @@
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'}
9
+ @activities.each{|a| SirTracksAlot.record(a)}
10
+ end
11
+
12
+ it "should render empty" do
13
+ SirTracksAlot::Reports::FilterReport.render_html(@report_options)
14
+ end
15
+
16
+ context 'filtering things with only' do
17
+ before do
18
+ @report_options = {
19
+ :owner => 'owner',
20
+ :filters => {
21
+ 'index' => {:target => /^\/categories$/},
22
+ 'pages' => {:target => /\/categories\/.+/},
23
+ 'blank' => {:target => /blanks/}
24
+ }
25
+ }
26
+ @html = SirTracksAlot::Reports::FilterReport.render_html(@report_options)
27
+ end
28
+
29
+ it "should include index filter" do
30
+ @html.should have_tag('td.title', /index/)
31
+ end
32
+
33
+ it "should include individual page filter" do
34
+ @html.should have_tag('td.title', /pages/)
35
+ end
36
+
37
+ it "should include correct count row" do
38
+ @html.should have_tag('td.count', /1/)
39
+ end
40
+
41
+ it "should not include matches for blank" do
42
+ @html.should_not have_tag('td.title', /blank/)
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Reports::RootStemReport do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @activities.each{|a| SirTracksAlot.record(a)}
9
+ end
10
+
11
+ it "should render empty" do
12
+ SirTracksAlot::Reports::FilterReport.render_html(:owner => 'owner')
13
+ end
14
+
15
+ context 'building HTML with categories' do
16
+ before do
17
+ @html = SirTracksAlot::Reports::RootStemReport.render_html(:owner => 'owner', :roots => ['categories'])
18
+ end
19
+
20
+ it "include index filter" do
21
+ @html.should have_tag('td', /categories pages/)
22
+ end
23
+
24
+ it "include individual page filter" do
25
+ @html.should have_tag('td', /categories index/)
26
+ end
27
+
28
+ it "include count row" do
29
+ @html.should have_tag('td.count', /2/)
30
+ end
31
+ end
32
+
33
+ context 'building HTML without categories' do
34
+ before do
35
+ @html = SirTracksAlot::Reports::RootStemReport.render_html(:owner => 'owner', :roots => ['categories'])
36
+ end
37
+
38
+ it "include index filter" do
39
+ @html.should have_tag('td', /categories pages/)
40
+ end
41
+
42
+ it "include individual page filter" do
43
+ @html.should have_tag('td', /categories index/)
44
+ end
45
+
46
+ it "include count row" do
47
+ @html.should have_tag('td.count', /2/)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ module SharedReportSpecs
2
+ shared_examples_for "all reports" do
3
+ context 'when building table data' do
4
+ it "should use default column names" do
5
+ @html.should have_tag("tr th", /visits|views|counts/)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Reports::TargetReport 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 => [])
10
+ @mock_filter = mock('Filter', :each => [])
11
+ end
12
+
13
+ it "should raise error on missing options" do
14
+ lambda{SirTracksAlot::Reports::TargetReport.render_html({})}.should raise_error(Ruport::Controller::RequiredOptionNotSet)
15
+ end
16
+
17
+ it "should find activities when building data" do
18
+ SirTracksAlot::Activity.should_receive(:filter).with(
19
+ :action => @activities[0][:action], :owner => @activities[0][:owner], :target => nil, :category=>"categories").exactly(1).times.and_return(@mock_filter)
20
+ SirTracksAlot::Reports::TargetReport.render_html(@report_attributes)
21
+ end
22
+
23
+ context 'building HTML without filters' do
24
+ before do
25
+ SirTracksAlot.record(:owner => 'other_owner', :category => 'other_category', :target => '/other_categories/item', :actor => '/users/user', :action => 'view')
26
+ @html = SirTracksAlot::Reports::TargetReport.render_html(@report_attributes)
27
+ end
28
+
29
+ it_should_behave_like 'all reports'
30
+
31
+ it "include target row" do
32
+ @html.should have_tag('td.target', /\/categories\/item/)
33
+ end
34
+
35
+ it "include count row" do
36
+ @html.should have_tag('td.count', /1/)
37
+ end
38
+
39
+ it "should ignore other owners" do
40
+ @html.should_not have_tag('td.target', '/other_categories/item')
41
+ end
42
+ end
43
+
44
+ context 'building HTML with filters' do
45
+ before do
46
+ @html = SirTracksAlot::Reports::TargetReport.render_html(
47
+ @report_attributes.merge(
48
+ :filters => {:except => [{:target => /^\/categories$/}]}
49
+ )
50
+ )
51
+ end
52
+
53
+ it "should include non excepted target row" do
54
+ @html.should have_tag('td.target', /\/categories\/item/)
55
+ end
56
+
57
+ it "should not include excepted target row" do
58
+ @html.should_not have_tag('td.target', /^\/categories$/)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,101 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe SirTracksAlot, 'when recording' do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @record_attributes = @activities[0]
9
+ end
10
+
11
+ it "should record activity" do
12
+ activity = SirTracksAlot.record(@record_attributes)
13
+ activity.should be_valid
14
+ end
15
+
16
+ it "should raise RecordInvalidError on invalid record" do
17
+ lambda {SirTracksAlot.record({})}.should
18
+ raise_error(SirTracksAlot::RecordInvalidError,
19
+ 'Activity not valid: [[:target, :not_present], [:category, :not_present], [:action, :not_present]]')
20
+ end
21
+
22
+ it "should be created" do
23
+ SirTracksAlot.record(@record_attributes)
24
+ activities = SirTracksAlot::Activity.find(:target => @record_attributes[:target])
25
+ activities.size.should == 1
26
+ end
27
+
28
+ it "should be created once" do
29
+ SirTracksAlot.record(@record_attributes)
30
+ SirTracksAlot.record(@record_attributes)
31
+ activities = SirTracksAlot::Activity.all
32
+ activities.size.should == 1
33
+ end
34
+
35
+ it "should have correct date count" do
36
+ SirTracksAlot.record(@record_attributes)
37
+ SirTracksAlot.record(@record_attributes)
38
+ activity = SirTracksAlot::Activity.all.first
39
+ activity.events.size.should == 2
40
+ end
41
+
42
+ it "should have events" do
43
+ SirTracksAlot::Clock.stub!(:now).and_return('123')
44
+ activity = SirTracksAlot.record(@record_attributes)
45
+ activity = SirTracksAlot::Activity.all.first
46
+ activity.events.first.should == '123'
47
+ end
48
+
49
+ it "should have last_event set to most recent event" do
50
+ SirTracksAlot::Clock.stub!(:now).and_return('123')
51
+ activity = SirTracksAlot.record(@record_attributes)
52
+ activity = SirTracksAlot::Activity.all.first
53
+ activity.last_event.should == '123'
54
+ end
55
+
56
+ it "should add date for now" do
57
+ SirTracksAlot::Clock.should_receive(:now).twice # once for activity create, once for events date
58
+ SirTracksAlot.record(@record_attributes)
59
+ end
60
+
61
+ it "should add custom event if provided" do
62
+ SirTracksAlot::Clock.should_receive(:now).once # only for activity create, not for events date
63
+ SirTracksAlot.record(@record_attributes.merge(:event => Time.now.utc.to_i))
64
+ end
65
+
66
+ it "should use to_trackable_id if present" do
67
+ target_mock = mock('Target')
68
+ target_mock.should_receive(:respond_to?).with(:to_trackable_id).and_return(true)
69
+ target_mock.should_receive(:to_trackable_id).and_return('target')
70
+ SirTracksAlot.record(@record_attributes.merge(:target => target_mock))
71
+ end
72
+
73
+ it "should use extract category" do
74
+ target_mock = mock('Target')
75
+ target_mock.stub!(:respond_to?).with(:to_trackable_id).and_return(true)
76
+ target_mock.stub!(:to_trackable_id).and_return('/target')
77
+
78
+ activity = SirTracksAlot.record(@record_attributes.merge(:target => target_mock))
79
+ activity.category.should == 'target'
80
+ end
81
+
82
+ context 'filter activities' do
83
+ before do
84
+ SirTracksAlot.exclude(:user_agent => /agent2/)
85
+ @activities.each{|a| SirTracksAlot.record(a)}
86
+ end
87
+
88
+ it "should include unfiltered activities" do
89
+ SirTracksAlot::Activity.find(:user_agent => 'agent1').should_not be_empty
90
+ end
91
+
92
+ it "should not include filtered activities" do
93
+ SirTracksAlot::Activity.find(:user_agent => 'agent2').should be_empty
94
+ end
95
+
96
+ after do
97
+ SirTracksAlot.clear!
98
+ end
99
+ end
100
+ end
101
+
data/spec/spec.opts ADDED
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format
3
+ profile
4
+ --timeout
5
+ 20
6
+ --diff
7
+ --backtrace
@@ -0,0 +1,66 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ require "rubygems"
10
+ require "bundler"
11
+ Bundler.setup(:development)
12
+ Bundler.require(:development)
13
+
14
+ require 'spec/reports/shared_report_specs'
15
+ require 'sir_tracks_alot'
16
+
17
+ Spec::Runner.configure do |config|
18
+ config.include(RspecHpricotMatchers)
19
+ end
20
+
21
+ class RedisSpecHelper
22
+ TEST_OPTIONS = {:db => 15}
23
+
24
+ def self.reset
25
+ Ohm.connect(TEST_OPTIONS)
26
+ Ohm.flush
27
+ end
28
+ end
29
+
30
+ module DataHelper
31
+ def initialize(*attrs)
32
+ super
33
+
34
+ @activities = [
35
+ {:owner => 'owner', :target => '/categories/item1', :actor => '/users/user1', :action => 'view', :user_agent => 'agent1'},
36
+ {:owner => 'owner', :target => '/categories/item2', :actor => '/users/user2', :action => 'view', :user_agent => 'agent2'},
37
+ {:owner => 'owner', :target => '/categories', :actor => '/users/user2', :action => 'view', :user_agent => 'agent2'},
38
+ {:owner => 'owner', :target => '/other_categories/item', :actor => '/users/user1', :action => 'view', :user_agent => 'agent1'},
39
+ {:owner => 'owner', :target => '/other_categories/item', :actor => '/users/user2', :action => 'view', :user_agent => 'agent2'},
40
+ {:owner => 'owner', :target => '/other_categories', :actor => '/users/user1', :action => 'view', :user_agent => 'agent1'},
41
+ {:owner => 'owner', :target => '/other_categories', :actor => '/users/user2', :action => 'view', :user_agent => 'agent2'}
42
+ ]
43
+
44
+ @report_attributes = {:owner => 'owner', :roots => ['categories'], :actions => ['view']}
45
+ end
46
+ end
47
+
48
+ class SirTracksAlot::Reports::CustomTargetReport < SirTracksAlot::Reports::TargetReport
49
+ def setup
50
+ super
51
+ end
52
+
53
+ module Helpers
54
+ def render_target(t)
55
+ 'customziherecheeseburger'
56
+ end
57
+ end
58
+
59
+ class HTML < Ruport::Formatter::HTML
60
+ renders :html, :for => SirTracksAlot::Reports::CustomTargetReport
61
+
62
+ build :drill_down do
63
+ output << erb((options.template || TRACKABLE_ROOT+"/views/reports/target.html.erb"), :binding => binding)
64
+ end
65
+ end
66
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'sir_tracks_alot'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestSirTracksAlot < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ <div class="report <%=options.report_class%>">
2
+ <% data.each do |name, group| %>
3
+ <h2><%=name%></h2>
4
+
5
+ <table>
6
+ <tr>
7
+ <% group.column_names.each do |column| %>
8
+ <th class="<%=to_dom_class(column)%>"><%= column %></th>
9
+ <% end %>
10
+ </tr>
11
+
12
+ <% group.each do |row| %>
13
+ <% title = row[0]; action = row[1]; views = row[2]; visits = row[3] %>
14
+
15
+ <tr>
16
+ <% row.each_with_index do |item, index| %>
17
+ <% name = group.column_names[index] %>
18
+ <td class="<%=to_dom_class(name)%> <%=item.instance_of?(Fixnum) ? 'count' : ''%>">
19
+ <%= render_data(name, item) %>
20
+ </td>
21
+ <% end %>
22
+ </tr>
23
+ <% end %>
24
+ </table>
25
+
26
+ <% end %>
27
+ </div>
@@ -0,0 +1,24 @@
1
+ <div class="report <%=options.report_class%>">
2
+ <h2><%=options.report_title%></h2>
3
+
4
+ <table>
5
+ <tr>
6
+ <% data.column_names.each do |column| %>
7
+ <th class="<%=to_dom_class(column)%>"><%= column %></th>
8
+ <% end %>
9
+ </tr>
10
+
11
+ <% data.each do |row| %>
12
+ <% title = row[0]; action = row[1]; views = row[2]; visits = row[3] %>
13
+
14
+ <tr>
15
+ <% row.each_with_index do |item, index| %>
16
+ <% name = data.column_names[index] %>
17
+ <td class="<%=to_dom_class(name)%> <%=item.instance_of?(Fixnum) ? 'count' : ''%>">
18
+ <%= render_data(name, item) %>
19
+ </td>
20
+ <% end %>
21
+ </tr>
22
+ <% end %>
23
+ </table>
24
+ </div>