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,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,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
data/spec/spec_helper.rb
ADDED
@@ -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,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>
|