sir_tracks_alot 0.4.0 → 0.6.2

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 (38) hide show
  1. data/Gemfile +2 -1
  2. data/Gemfile.lock +54 -0
  3. data/README.rdoc +29 -1
  4. data/VERSION +1 -1
  5. data/benchmarks/activity_benchmark.rb +22 -23
  6. data/benchmarks/benchmark_helper.rb +30 -0
  7. data/benchmarks/count_benchmark.rb +33 -0
  8. data/lib/sir_tracks_alot.rb +16 -14
  9. data/lib/sir_tracks_alot/activity.rb +7 -6
  10. data/lib/sir_tracks_alot/clock.rb +4 -0
  11. data/lib/sir_tracks_alot/count.rb +87 -39
  12. data/lib/sir_tracks_alot/event_helper.rb +1 -1
  13. data/lib/sir_tracks_alot/filter_helper.rb +28 -18
  14. data/lib/sir_tracks_alot/persistable.rb +4 -0
  15. data/lib/sir_tracks_alot/queue/report_cache.rb +2 -0
  16. data/lib/sir_tracks_alot/queue/report_config.rb +3 -0
  17. data/lib/sir_tracks_alot/queue/report_queue.rb +8 -10
  18. data/lib/sir_tracks_alot/reports/actor_activity_report.rb +3 -3
  19. data/lib/sir_tracks_alot/reports/basic_report.rb +1 -1
  20. data/lib/sir_tracks_alot/reports/filter_report.rb +5 -11
  21. data/lib/sir_tracks_alot/reports/report.rb +16 -2
  22. data/lib/sir_tracks_alot/reports/simple_report.rb +43 -0
  23. data/lib/sir_tracks_alot/reports/target_report.rb +2 -7
  24. data/lib/sir_tracks_alot/summary.rb +9 -0
  25. data/sir_tracks_alot.gemspec +140 -0
  26. data/spec/activity_spec.rb +18 -59
  27. data/spec/count_spec.rb +56 -25
  28. data/spec/queue/report_queue_spec.rb +8 -8
  29. data/spec/redis_spec_helper.rb +8 -0
  30. data/spec/reports/actor_report_spec.rb +16 -16
  31. data/spec/reports/filter_report_spec.rb +1 -1
  32. data/spec/reports/report_spec.rb +15 -0
  33. data/spec/reports/root_stem_report_spec.rb +1 -1
  34. data/spec/reports/simple_report_spec.rb +63 -0
  35. data/spec/reports/target_report_spec.rb +9 -4
  36. data/spec/sir_tracks_alot_spec.rb +4 -0
  37. data/spec/spec_helper.rb +1 -9
  38. metadata +38 -10
@@ -15,7 +15,7 @@ describe SirTracksAlot::Activity do
15
15
  it "should store valid activities" do
16
16
  @activity.should be_valid
17
17
  end
18
-
18
+
19
19
  it "should not store invalid activities" do
20
20
  activity = SirTracksAlot::Activity.create({})
21
21
  activity.should_not be_valid
@@ -46,10 +46,10 @@ describe SirTracksAlot::Activity do
46
46
  SirTracksAlot::Activity.all.size.should == 8
47
47
  end
48
48
 
49
- it "should purge counted" do
49
+ it "should purge counted, not touching first 500 (by default)" do
50
50
  SirTracksAlot::Activity.all.each{|a| a.counted!}
51
- SirTracksAlot::Activity.purge!
52
- SirTracksAlot::Activity.all.size.should == 0
51
+ SirTracksAlot::Activity.purge!()
52
+ SirTracksAlot::Activity.all.size.should == 8
53
53
  end
54
54
 
55
55
  it "should purge counted by range" do
@@ -68,15 +68,15 @@ describe SirTracksAlot::Activity do
68
68
  it "should find activities matching attribute regex" do
69
69
  SirTracksAlot::Activity.filter(:owner => 'owner', :target => /categories/).size.should == 1
70
70
  end
71
-
71
+
72
72
  it "should find activities matching attribute string" do
73
73
  SirTracksAlot::Activity.filter(:owner => 'owner', :category => 'category').size.should == 1
74
74
  end
75
-
75
+
76
76
  it "should find activities matching attribute string and attribute regex" do
77
77
  SirTracksAlot::Activity.filter(:owner => 'owner', :category => 'category', :target => /categories/).size.should == 1
78
78
  end
79
-
79
+
80
80
  it "should not find activities matching attribute string but not attribute regex" do
81
81
  SirTracksAlot::Activity.filter(:owner => 'owner', :category => 'category', :target => /not_there/).size.should == 0
82
82
  end
@@ -91,8 +91,12 @@ describe SirTracksAlot::Activity do
91
91
  @activities.each{|a| SirTracksAlot.record(a)}
92
92
  end
93
93
 
94
+ it "should filter by members which are regex's" do
95
+ # SirTracksAlot::Activity.filter(:owner => 'owner', :target => [/categories/, /\/categories\//]).size.should == 3
96
+ end
97
+
94
98
  it "should filter by members of one array" do
95
- SirTracksAlot::Activity.filter(:owner => 'owner', :target => ['/categories/item1', '/categories/item2']).size.should == 3
99
+ SirTracksAlot::Activity.filter(:owner => 'owner', :target => ['/categories/item1', '/categories/item2']).size.should == 3
96
100
  end
97
101
 
98
102
  it "should filter by combinations of arrays" do
@@ -115,53 +119,8 @@ describe SirTracksAlot::Activity do
115
119
 
116
120
  it "should purge counted by range, sorting to most recent by last event" do
117
121
  SirTracksAlot::Activity.recent({:owner => 'owner'}, :start => 1).first.last_event.should == '1279709941'
118
- end
119
-
120
- end
121
-
122
- context 'when counting' do
123
- before do
124
- @mock_activity = mock(SirTracksAlot::Activity, :visits => 1, :views => 2)
125
- SirTracksAlot::Activity.stub!(:find => [@mock_activity])
126
- end
127
-
128
- # it "should look for activities using find options" do
129
- # SirTracksAlot::Activity.should_receive(:find).with(:owner => 'owner').and_return([@mock_activity])
130
- # SirTracksAlot::Activity.count(:views, :owner => 'owner')
131
- # end
132
- #
133
- # it "should look for views when counting views" do
134
- # @mock_activity.should_receive(:views).once.and_return(1)
135
- # SirTracksAlot::Activity.count(:views, :owner => 'owner')
136
- # end
137
- #
138
- # it "should not look for views when counting visits" do
139
- # @mock_activity.should_receive(:views).never
140
- # SirTracksAlot::Activity.count(:visits, :owner => 'owner')
141
- # end
142
- #
143
- # it "should return views and visits" do
144
- # SirTracksAlot::Activity.count([:visits, :views], :owner => 'owner').should == [1,2]
145
- # end
146
- #
147
- # it "should return visits" do
148
- # SirTracksAlot::Activity.count([:visits], :owner => 'owner').should == 1
149
- # end
150
- #
151
- # it "should ignore empty only" do
152
- # SirTracksAlot::Activity.count([:visits], :owner => 'owner', :only => {}).should == 1
153
- # end
154
- #
155
- # it "should filter by category" do
156
- # @mock_activity.should_receive(:category).once.and_return('category')
157
- # SirTracksAlot::Activity.count([:visits], :owner => 'owner', :category => /category/)
158
- # end
159
- #
160
- # it "should filter by category and match returning correct count" do
161
- # @mock_activity.stub!(:category => 'category')
162
- # SirTracksAlot::Activity.count([:visits], :owner => 'owner', :category => /category/).should == 1
163
- # end
164
- end
122
+ end
123
+ end
165
124
 
166
125
  context 'when counting views' do
167
126
  before do
@@ -177,11 +136,11 @@ describe SirTracksAlot::Activity do
177
136
  it "should return grouped counts" do
178
137
  @activity.views(:daily).should be_kind_of(Hash)
179
138
  end
180
-
139
+
181
140
  it "should return count as hash value" do
182
141
  @activity.views(:daily).values.should == [1]
183
142
  end
184
-
143
+
185
144
  it "should return count key'd by date" do
186
145
  key = Time.at(@now).utc.strftime(SirTracksAlot::Activity::DATE_FORMATS[:daily])
187
146
  @activity.views(:daily).keys.should == [key]
@@ -203,13 +162,13 @@ describe SirTracksAlot::Activity do
203
162
  200.times{@activity.events << SirTracksAlot::Clock.now}
204
163
  @activity.visits.should == 1
205
164
  end
206
-
165
+
207
166
  it "should count 2 visits separated by greater than default session duration as 2 visits" do
208
167
  @activity.events << SirTracksAlot::Clock.now
209
168
  @activity.events << SirTracksAlot::Clock.now + 2000
210
169
  @activity.visits.should == 2
211
170
  end
212
-
171
+
213
172
  it "should count 2 visits separated by greater than default session duration as 2, and a 3rd" do
214
173
  5.times{@activity.events << SirTracksAlot::Clock.now}
215
174
  @activity.events << SirTracksAlot::Clock.now + 2000
@@ -4,14 +4,13 @@ describe SirTracksAlot::Count do
4
4
  include DataHelper
5
5
 
6
6
  before do
7
- RedisSpecHelper.reset
8
-
7
+ RedisSpecHelper.reset
9
8
  @set_activities.each{|a| SirTracksAlot.record(a)}
10
9
  end
11
10
 
12
11
  context 'when creating' do
13
12
  before do
14
- @count_attributes = [{:owner => 'owner', :actor => '/users/user1', :target => '/categories/item1', :views => 1, :visits => 2, :date => '07/01/2010', :hour => 5}]
13
+ @count_attributes = [{:owner => 'owner', :actor => '/users/user1', :target => '/categories/item1'}]
15
14
  end
16
15
 
17
16
  it 'should create valid count' do
@@ -22,6 +21,10 @@ describe SirTracksAlot::Count do
22
21
  SirTracksAlot::Count.create(@count_attributes[0])
23
22
  SirTracksAlot::Count.create(@count_attributes[0]).should_not be_valid
24
23
  end
24
+
25
+ it "should have a to_hash with attributes" do
26
+ SirTracksAlot::Count.create(@count_attributes[0]).to_hash.should == {:owner=>"owner", :target=>"/categories/item1", :id=>"1", :category=>nil, :actor=>"/users/user1"}
27
+ end
25
28
  end
26
29
 
27
30
  context 'when filtering' do
@@ -29,8 +32,16 @@ describe SirTracksAlot::Count do
29
32
  SirTracksAlot::Count.count(:owner => 'owner')
30
33
  end
31
34
 
35
+ it "should build rows" do
36
+ SirTracksAlot::Count.rows(:owner => 'owner').should == [["/categories", 1, 1], ["/categories/item1", 4, 4], ["/categories/item2", 1, 1], ["/other_categories", 2, 2], ["/other_categories/item", 2, 2]]
37
+ end
38
+
39
+ it "should build rows with custom title" do
40
+ SirTracksAlot::Count.rows({:owner => 'owner'}, 'title').should == [["title", 10, 10]]
41
+ end
42
+
32
43
  it 'should filter by string attribute' do
33
- SirTracksAlot::Count.filter(:owner => 'owner').size.should == 13
44
+ SirTracksAlot::Count.filter(:owner => 'owner').size.should == 5
34
45
  end
35
46
 
36
47
  it 'should filter by array of strings' do
@@ -39,7 +50,7 @@ describe SirTracksAlot::Count do
39
50
 
40
51
  it 'should include all targets when filtering just by ower' do
41
52
  SirTracksAlot::Count.filter(:owner => 'owner').collect{|c| c.target}.compact.sort.should ==
42
- ["/categories", "/categories/item1", "/categories/item1", "/categories/item1", "/categories/item2", "/other_categories", "/other_categories/item"].sort
53
+ ["/categories", "/categories/item1", "/categories/item2", "/other_categories", "/other_categories/item"].sort
43
54
  end
44
55
 
45
56
  it 'should include only filtered targets' do
@@ -48,15 +59,39 @@ describe SirTracksAlot::Count do
48
59
  end
49
60
  end
50
61
 
51
- context 'when counting' do
62
+ context 'when summarizing' do
52
63
  before do
53
- @counts = SirTracksAlot::Count.count(:owner => 'owner', :category => ['categories'], :action => ['view'])
64
+ SirTracksAlot::Count.count(:owner => 'owner')
54
65
  end
55
66
 
56
- it "should count all activities for owner" do
67
+ it "should summarize daily counts" do
68
+ SirTracksAlot::Count.summarize.should == {"2010/07/21"=>[2, 2], "2010/07/22"=>[8, 8]}
69
+ end
70
+
71
+ it "should summarize daily counts with find options" do
72
+ SirTracksAlot::Count.summarize(:daily, :target => /.+/).should == {"2010/07/21"=>[2, 2], "2010/07/22"=>[8, 8]}
73
+ end
74
+
75
+ it "should summarize hourly counts" do
76
+ SirTracksAlot::Count.summarize(:hourly).should == {"2010/07/21 17:00 UTC"=>[1, 1], "2010/07/21 20:00 UTC"=>[1, 1], "2010/07/22 01:00 UTC"=>[8, 8]}
77
+ end
78
+
79
+ end
80
+
81
+ context 'when totalling' do
82
+ before do
57
83
  SirTracksAlot::Count.count(:owner => 'owner')
58
- SirTracksAlot::Count.find(:owner => 'owner').size.should == 13 # one for each unique target, time combo
59
84
  end
85
+
86
+ it "should total views and visits" do
87
+ SirTracksAlot::Count.total([:views, :visits], :owner => 'owner').should == [10,10]
88
+ end
89
+ end
90
+
91
+ context 'when counting hourly' do
92
+ before do
93
+ @counts = SirTracksAlot::Count.count(:owner => 'owner')
94
+ end
60
95
 
61
96
  it "should set to counted all activities counted" do
62
97
  SirTracksAlot::Count.count(:owner => 'owner')
@@ -65,24 +100,20 @@ describe SirTracksAlot::Count do
65
100
 
66
101
  it "should only count activities once" do
67
102
  SirTracksAlot::Count.count(:owner => 'owner', :category => ['categories'], :action => ['view']).should be_empty
103
+ end
104
+
105
+ it "should include summaries" do
106
+ @counts[0].summaries.size.should == 3
107
+ end
108
+ end
109
+
110
+ context 'when counting daily' do
111
+ before do
112
+ @counts = SirTracksAlot::Count.count({:owner => 'owner'}, {}, :daily)
68
113
  end
69
114
 
70
- context 'views' do
71
- it "should include counts by hour" do
72
- @counts[0].hour.should == '18'
73
- end
74
-
75
- it "should not include counts not requested" do
76
- @counts.collect{|c| c.target}.should_not include("/other_categories")
77
- end
115
+ it "should count all activities for owner" do
116
+ SirTracksAlot::Count.find(:owner => 'owner').size.should == 5 # one for each unique target
78
117
  end
79
-
80
- context 'visits' do
81
- it 'should include counts by hour' do
82
- # @counts[1].should == {"/categories"=>{"2010/07/21 18"=>1},
83
- # "/categories/item1"=>{"2010/07/21 18"=>2, "2010/07/21 10"=>1, "2010/07/21 13"=>1},
84
- # "/categories/item2"=>{"2010/07/21 18"=>1}}
85
- end
86
- end
87
118
  end
88
119
  end
@@ -16,17 +16,17 @@ describe SirTracksAlot::Queue::ReportQueue do
16
16
  before do
17
17
  @config = mock(SirTracksAlot::Queue::ReportConfig, :options => {}, :options= => true, :id => 1)
18
18
  @activities.each{|a| SirTracksAlot.record(a)}
19
- SirTracksAlot::Queue::ReportQueue.push('owner', :actor_report, {})
19
+ SirTracksAlot::Queue::ReportQueue.push('owner', :actor_report, 'report', {})
20
20
  end
21
21
 
22
22
  it "should create a new queue" do
23
23
  SirTracksAlot::Queue::ReportQueue.should_receive(:find_or_create).with(:name => SirTracksAlot::Queue::ReportQueue::QUEUE_NAME).and_return(@queue)
24
- SirTracksAlot::Queue::ReportQueue.push('owner', 'report', {})
24
+ SirTracksAlot::Queue::ReportQueue.push('owner', 'report', 'report', {})
25
25
  end
26
26
 
27
27
  it "should create report config" do
28
- SirTracksAlot::Queue::ReportConfig.should_receive(:find_or_create).with(:owner => 'owner', :report => 'report').once.and_return(@config)
29
- SirTracksAlot::Queue::ReportQueue.push('owner', 'report', {})
28
+ SirTracksAlot::Queue::ReportConfig.should_receive(:find_or_create).with(:owner => 'owner', :report => 'report', :name => 'report').once.and_return(@config)
29
+ SirTracksAlot::Queue::ReportQueue.push('owner', 'report', 'report', {})
30
30
  end
31
31
  end
32
32
 
@@ -34,7 +34,7 @@ describe SirTracksAlot::Queue::ReportQueue do
34
34
  before do
35
35
  @cache = mock(SirTracksAlot::Queue::ReportCache, :update => true)
36
36
  @activities.each{|a| SirTracksAlot.record(a)}
37
- SirTracksAlot::Queue::ReportQueue.push('owner', :target_report, {:category => ['categories', 'other_categories']})
37
+ SirTracksAlot::Queue::ReportQueue.push('owner', :target_report, 'report', {:filters => {:category => ['/categories', '/other_categories']}})
38
38
  end
39
39
 
40
40
  it "should find existing queue" do
@@ -43,7 +43,7 @@ describe SirTracksAlot::Queue::ReportQueue do
43
43
  end
44
44
 
45
45
  it "should create report cache" do
46
- SirTracksAlot::Queue::ReportCache.should_receive(:find_or_create).with(:owner => 'owner', :report => 'target_report').and_return(@cache)
46
+ SirTracksAlot::Queue::ReportCache.should_receive(:find_or_create).with(:owner => 'owner', :report => 'target_report', :name => 'report').and_return(@cache)
47
47
  SirTracksAlot::Queue::ReportQueue.pop
48
48
  end
49
49
 
@@ -52,8 +52,8 @@ describe SirTracksAlot::Queue::ReportQueue do
52
52
  SirTracksAlot::Queue::ReportQueue.pop
53
53
  end
54
54
 
55
- it "should build report HTML" do
56
- SirTracksAlot::Queue::ReportCache.should_receive(:find_or_create).with(:owner => 'owner', :report => 'target_report').and_return(@cache)
55
+ it "should build report HTML" do
56
+ SirTracksAlot::Queue::ReportCache.should_receive(:find_or_create).with(:owner => 'owner', :report => 'target_report', :name => 'report').and_return(@cache)
57
57
  @cache.should_receive(:update) do |options|
58
58
  options[:html].should have_tag('td.target', /\/categories\/item1/)
59
59
  end
@@ -0,0 +1,8 @@
1
+ class RedisSpecHelper
2
+ TEST_OPTIONS = {:db => 13}
3
+
4
+ def self.reset
5
+ Ohm.connect(TEST_OPTIONS)
6
+ Ohm.flush
7
+ end
8
+ end
@@ -10,24 +10,24 @@ describe SirTracksAlot::Reports::ActorReport do
10
10
 
11
11
  context 'building HTML' do
12
12
  before do
13
- @counts = SirTracksAlot::Count.count(OpenStruct.new(:owner => 'owner', :roots => ['categories']))
13
+ @counts = SirTracksAlot::Count.count(:owner => 'owner')
14
14
  @html = SirTracksAlot::Reports::ActorReport.render_html(:counts => @counts)
15
15
  end
16
16
 
17
- it "include target row" do
18
- @html.should have_tag('td.actor',/\/users\/user1/)
19
- end
20
-
21
- it "include count row" do
22
- @html.should have_tag('td.count', /1/)
23
- end
24
-
25
- it "include page views" do
26
- @html.should have_tag('td.page_views', /2/)
27
- end
28
-
29
- it "should ignore other owners" do
30
- @html.should_not have_tag('td.actor', '/users/user')
31
- end
17
+ # it "include target row" do
18
+ # @html.should have_tag('td.actor',/\/users\/user1/)
19
+ # end
20
+ #
21
+ # it "include count row" do
22
+ # @html.should have_tag('td.count', /1/)
23
+ # end
24
+ #
25
+ # it "include page views" do
26
+ # @html.should have_tag('td.page_views', /2/)
27
+ # end
28
+ #
29
+ # it "should ignore other owners" do
30
+ # @html.should_not have_tag('td.actor', '/users/user')
31
+ # end
32
32
  end
33
33
  end
@@ -7,7 +7,7 @@ describe SirTracksAlot::Reports::FilterReport do
7
7
  RedisSpecHelper.reset
8
8
  @report_options = {:owner => 'owner'}
9
9
  @activities.each{|a| SirTracksAlot.record(a)}
10
- SirTracksAlot::Count.count(OpenStruct.new(:owner => 'owner', :roots => ['categories']))
10
+ SirTracksAlot::Count.count(:owner => 'owner')
11
11
  end
12
12
 
13
13
  context 'filtering things with only' do
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Reports::Report do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @activities.each{|a| SirTracksAlot.record(a)}
9
+ SirTracksAlot::Count.count(:owner => 'owner')
10
+ end
11
+
12
+ it "should " do
13
+ SirTracksAlot::Reports::Report.build_rows([{:owner => 'owner'}]).to_a.sort.should == [["/categories", 1, 1], ["/categories/item1", 1, 1], ["/categories/item2", 1, 1], ["/other_categories", 2, 2], ["/other_categories/item", 2, 2]].sort
14
+ end
15
+ end
@@ -6,7 +6,7 @@ describe SirTracksAlot::Reports::RootStemReport do
6
6
  before do
7
7
  RedisSpecHelper.reset
8
8
  @activities.each{|a| SirTracksAlot.record(a)}
9
- SirTracksAlot::Count.count(OpenStruct.new(:owner => 'owner', :roots => ['categories']))
9
+ SirTracksAlot::Count.count(:owner => 'owner')
10
10
  end
11
11
 
12
12
  context 'building HTML with categories' do
@@ -0,0 +1,63 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe SirTracksAlot::Reports::SimpleReport do
4
+ include DataHelper
5
+
6
+ before do
7
+ RedisSpecHelper.reset
8
+ @activities.each{|a| SirTracksAlot.record(a)}
9
+ SirTracksAlot::Count.count(:owner => 'owner')
10
+ end
11
+
12
+ context 'building HTML' do
13
+ context 'using filters' do
14
+ it "should use regular expressions" do
15
+ SirTracksAlot::Reports::SimpleReport.render_html(:filters => [{:owner => 'owner', :target => /.+/}],
16
+ :report_class => 'customClass').should_not have_tag('td.target', /\/users\/user1/)
17
+ end
18
+ end
19
+
20
+ context 'for table reports' do
21
+ before do
22
+ @html = SirTracksAlot::Reports::SimpleReport.render_html(:filters => [{:owner => 'owner'}],
23
+ :report_class => 'customClass')
24
+ end
25
+
26
+ it_should_behave_like 'all reports'
27
+
28
+ it "should include target row" do
29
+ @html.should have_tag('td.target', /\/categories\/item/)
30
+ end
31
+
32
+ it "should have custom class" do
33
+ @html.should have_tag('div.customClass')
34
+ end
35
+
36
+ it "should include count row" do
37
+ @html.should have_tag('td.count', /1/)
38
+ end
39
+
40
+ it "should ignore other owners" do
41
+ @html.should_not have_tag('td.target', '/other_categories/item')
42
+ end
43
+ end
44
+
45
+ context 'for table reports' do
46
+ before do
47
+ @html = SirTracksAlot::Reports::SimpleReport.render_html(:filters => [{:owner => 'owner'}],
48
+ :group => 'target',
49
+ :column_names => ['target', 'column 1', 'column 2'],
50
+ :report_class => 'customClass')
51
+ end
52
+
53
+ it "should include target group" do
54
+ @html.should have_tag('h2', /\/categories\/item/)
55
+ end
56
+
57
+ it "should include custom column names" do
58
+ @html.should have_tag('th.column_1')
59
+ end
60
+
61
+ end
62
+ end
63
+ end