sir_tracks_alot 0.4.0 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
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