redis_analytics 0.1.0 → 0.6.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 (61) hide show
  1. data/CHANGELOG.md +12 -0
  2. data/CONTRIBUTING.md +33 -0
  3. data/Guardfile +10 -0
  4. data/README.md +105 -23
  5. data/Rakefile +3 -11
  6. data/TODO.md +12 -0
  7. data/bin/redis_analytics_dashboard +1 -1
  8. data/config.ru +13 -0
  9. data/lib/redis_analytics.rb +5 -1
  10. data/lib/redis_analytics/analytics.rb +17 -203
  11. data/lib/redis_analytics/api.rb +60 -0
  12. data/lib/redis_analytics/configuration.rb +47 -16
  13. data/lib/redis_analytics/dashboard.rb +28 -89
  14. data/lib/redis_analytics/dashboard/public/{favicon.ico → img/favicon.ico} +0 -0
  15. data/lib/redis_analytics/dashboard/public/javascripts/{bootstrap.min.js → vendor/bootstrap/bootstrap.min.js} +0 -0
  16. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-1.9.1.min.js → vendor/jquery-1.9.1.min.js} +0 -0
  17. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-jvectormap-1.2.2.min.js → vendor/jquery-jvectormap-1.2.2.min.js} +0 -0
  18. data/lib/redis_analytics/dashboard/public/javascripts/{jquery-jvectormap-world-mill-en.js → vendor/jquery-jvectormap-world-mill-en.js} +0 -0
  19. data/lib/redis_analytics/dashboard/public/javascripts/{morris.min.js → vendor/morris.min.js} +0 -0
  20. data/lib/redis_analytics/dashboard/public/javascripts/{raphael-min.js → vendor/raphael-min.js} +0 -0
  21. data/lib/redis_analytics/dashboard/views/activity.erb +7 -0
  22. data/lib/redis_analytics/dashboard/views/dialogs/unique_visits.erb +41 -0
  23. data/lib/redis_analytics/dashboard/views/dialogs/visits.erb +40 -0
  24. data/lib/redis_analytics/dashboard/views/footer.erb +1 -3
  25. data/lib/redis_analytics/dashboard/views/header.erb +17 -42
  26. data/lib/redis_analytics/dashboard/views/layout.erb +5 -22
  27. data/lib/redis_analytics/dashboard/views/visits.erb +38 -143
  28. data/lib/redis_analytics/dashboard/views/visits_js.erb +110 -247
  29. data/lib/redis_analytics/dashboard/views/widgets/bounce_rate.erb +3 -0
  30. data/lib/redis_analytics/dashboard/views/widgets/browsers_donut.erb +8 -0
  31. data/lib/redis_analytics/dashboard/views/widgets/first_visits.erb +3 -0
  32. data/lib/redis_analytics/dashboard/views/widgets/page_depth.erb +3 -0
  33. data/lib/redis_analytics/dashboard/views/widgets/referers_donut.erb +8 -0
  34. data/lib/redis_analytics/dashboard/views/widgets/total_page_views.erb +3 -0
  35. data/lib/redis_analytics/dashboard/views/widgets/total_visits.erb +3 -0
  36. data/lib/redis_analytics/dashboard/views/widgets/unique_visits_line.erb +26 -0
  37. data/lib/redis_analytics/dashboard/views/widgets/visit_duration.erb +3 -0
  38. data/lib/redis_analytics/dashboard/views/widgets/visit_spark.erb +23 -0
  39. data/lib/redis_analytics/dashboard/views/widgets/visitor_recency_slices.erb +39 -0
  40. data/lib/redis_analytics/dashboard/views/widgets/visits_area.erb +30 -0
  41. data/lib/redis_analytics/dashboard/views/widgets/visits_donut.erb +8 -0
  42. data/lib/redis_analytics/dashboard/views/widgets/world_map.erb +50 -0
  43. data/lib/redis_analytics/filter.rb +33 -0
  44. data/lib/redis_analytics/helpers.rb +36 -30
  45. data/lib/redis_analytics/metrics.rb +96 -0
  46. data/lib/redis_analytics/time_ext.rb +72 -2
  47. data/lib/redis_analytics/tracker.rb +13 -5
  48. data/lib/redis_analytics/version.rb +1 -1
  49. data/lib/redis_analytics/visit.rb +122 -0
  50. data/redis_analytics.gemspec +19 -14
  51. data/spec/lib/redis_analytics/analytics_spec.rb +59 -0
  52. data/spec/lib/redis_analytics/configuration_spec.rb +158 -0
  53. data/spec/lib/redis_analytics/dashboard_spec.rb +32 -0
  54. data/spec/lib/redis_analytics/filter_spec.rb +34 -0
  55. data/spec/lib/redis_analytics/tracker_spec.rb +20 -0
  56. data/spec/spec_helper.rb +13 -6
  57. data/spec/support/fakeredis.rb +1 -0
  58. data/wsd.png +0 -0
  59. metadata +268 -126
  60. data/lib/redis_analytics/config.ru +0 -10
  61. data/spec/redis_analytics_spec.rb +0 -57
@@ -0,0 +1,12 @@
1
+ ## 0.2.0
2
+
3
+ * refactor of the dashboard sinatra app
4
+ * code revamp and cleanup
5
+ * path filters and proc filters
6
+ * JSON api for reports
7
+ * dashboard and api endpoints no longer mandatory
8
+ * various bug fixes and tests
9
+
10
+ ## 0.1.0
11
+
12
+ * first version
@@ -0,0 +1,33 @@
1
+ # Contributing to redis_analytics
2
+
3
+ ## Contributors List
4
+
5
+ * Schubert Cardozo
6
+ * Sascha Wessel
7
+
8
+ ## Adding new features
9
+
10
+ * Create a topic branch for your feature and make your changes there
11
+ * Do not fail the build or compromise on the code coverage too much (adding more test cases would be fantastic a contribution)
12
+ * Send me a pull request
13
+ * You should include tests where possible
14
+ * Make updates to the changelog
15
+
16
+ ## Bugs/Features
17
+
18
+ * The [issue tracker](https://github.com/saturnine/redis_analytics/issues) is
19
+ the preferred channel for [bug reports](#bugs), [features requests](#features)
20
+ and [submitting pull requests](#pull-requests)
21
+
22
+ * Please **do not** use the issue tracker for personal support requests (use
23
+ [Stack Overflow](http://stackoverflow.com/questions/tagged/redis_analytics)).
24
+
25
+ * Please **do not** derail or troll issues.
26
+
27
+ * Keep the discussion on topic and respect the opinions of others.
28
+
29
+ # Translations
30
+
31
+ * You can help us translate the project. Simply drop me a message here on Github
32
+
33
+ # Thank you for your contribution
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
7
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
8
+ watch('spec/spec_helper.rb') { "spec" }
9
+ end
10
+
data/README.md CHANGED
@@ -1,20 +1,20 @@
1
- # redis_analytics [![Build Status](https://travis-ci.org/saturnine/redis_analytics.png?branch=master)](https://travis-ci.org/saturnine/redis_analytics)
1
+ ## redis_analytics [![Build Status](https://travis-ci.org/saturnine/redis_analytics.png?branch=master)](https://travis-ci.org/saturnine/redis_analytics) [![Coverage Status](https://coveralls.io/repos/saturnine/redis_analytics/badge.png?branch=master)](https://coveralls.io/r/saturnine/redis_analytics) [![Gem Version](https://badge.fury.io/rb/redis_analytics.png)](http://badge.fury.io/rb/redis_analytics)
2
2
 
3
- ## What is redis_analytics?
3
+ ### What is redis_analytics?
4
4
 
5
5
  A gem that provides a Redis based web analytics solution for your rack-compliant apps
6
6
 
7
- ## Why should I use it?
7
+ ### Why should I use it?
8
8
 
9
9
  It gives you detailed analytics about visitors, unique visitors, browsers, OS, visitor recency, traffic sources and more
10
10
 
11
- ## Does it have a cool dashboard?
11
+ ### Does it have a cool dashboard?
12
12
 
13
- Yes, It uses the excellent [Morris.js](http://www.oesmith.co.uk/morris.js/) for the main dashboard and [Highcharts](http://www.highcharts.com) for drawing the various detailed graphs
13
+ Yes, It uses the excellent [Morris.js](http://www.oesmith.co.uk/morris.js/) for graphs/charts
14
14
 
15
15
  ![Screenshot](https://github.com/saturnine/redis_analytics/raw/master/screenshot.png)
16
16
 
17
- ## OK, so how do I install it?
17
+ ### OK, so how do I install it?
18
18
 
19
19
  `gem install redis_analytics`
20
20
 
@@ -27,9 +27,9 @@ gem 'redis_analytics'
27
27
  Make sure your redis server is running! Redis configuration is outside the scope of this README, but
28
28
  check out the [Redis documentation](http://redis.io/documentation).
29
29
 
30
- ## How do I enable tracking in my rack-compliant app?
30
+ ### How do I enable tracking in my rack-compliant app?
31
31
 
32
- ### Step 1: Load the redis_analytics library and configure it
32
+ #### Step 1: Load the redis_analytics library and configure it
33
33
 
34
34
  ```ruby
35
35
  # this is not required unless you use :require => false in your Gemfile
@@ -41,7 +41,7 @@ Rack::RedisAnalytics.configure do |configuration|
41
41
  configuration.redis_namespace = 'ra'
42
42
  end
43
43
  ```
44
- ### Step 2: Use the Tracker rack middleware (NOT REQUIRED FOR RAILS)
44
+ #### Step 2: Use the Tracker rack middleware (NOT REQUIRED FOR RAILS)
45
45
 
46
46
  ```ruby
47
47
  # in Sinatra you would do...
@@ -50,9 +50,17 @@ use Rack::RedisAnalytics::Tracker
50
50
 
51
51
  For rails the middleware is added automatically, so you do not need to add it manually using `config.middleware.use`
52
52
 
53
- ## Where do I view the dashboard?
53
+ ### Where can I see the dashboard?
54
54
 
55
- ### Option 1: Set a dashboard endpoint in your configuration
55
+ Simply navigate to `/redis_analytics` in your rack app
56
+
57
+ You can also run the standalone dashboard sinatra app like:
58
+
59
+ `redis_analytics_dashboard --redis-host 127.0.0.1 --redis-port 6379 --redis-namespace ra`
60
+
61
+ ### Where do I change the dashboard URL endpoint?
62
+
63
+ Set a dashboard endpoint in your configuration:
56
64
 
57
65
  ```ruby
58
66
  Rack::RedisAnalytics.configure do |configuration|
@@ -60,15 +68,7 @@ Rack::RedisAnalytics.configure do |configuration|
60
68
  end
61
69
  ```
62
70
 
63
- and navigate to [http://localhost:3000/dashboard](http://localhost:3000/dashboard) assuming your rack-compliant app is hosted at [http://localhost:3000](http://localhost:3000)
64
-
65
- ### Option 2: Simply run the bundled Sinatra application binary
66
-
67
- `redis_analytics_dashboard --redis-host 127.0.0.1 --redis-port 6379 --redis-namespace ra`
68
-
69
- and navigate to [http://localhost:4567](http://localhost:4567)
70
-
71
- ## What if I have multiple rails apps that I want to track as one?
71
+ ### What if I have multiple rails apps that I want to track as one?
72
72
 
73
73
  In the configuration, keep the value of redis_namespace the same across all your rails apps
74
74
 
@@ -79,7 +79,7 @@ Rack::RedisAnalytics.configure do |configuration|
79
79
  end
80
80
  ```
81
81
 
82
- ## Why is the Geolocation tracking giving me wrong results?
82
+ ### Why is the Geolocation tracking giving me wrong results?
83
83
 
84
84
  IP based Geolocation works using [MaxMind's](http://www.maxmind.com) GeoLite database. The free version is not as accurate as their commercial version.
85
85
  Also it is recommended to regularly get an updated binary of 'GeoLite Country' database from [here](http://dev.maxmind.com/geoip/geolite) and extract the GeoIP.dat file into a local directory.
@@ -93,6 +93,88 @@ Rack::RedisAnalytics.configure do |configuration|
93
93
  end
94
94
  ```
95
95
 
96
- ## Copyright
96
+ ## Customizing & Extending
97
+
98
+ ### Tracking custom metrics
99
+
100
+ You can define and track your own metrics by defining an instance method inside the `Metrics` module
101
+
102
+ All you need to do, is make sure the method name conforms to the following format:
103
+
104
+ `[abc]_[x]_per_[y]`
105
+
106
+ where
107
+
108
+ * `abc` is a metric name
109
+ * `x` can be any one of `ratio` or `count` and defines how the metric is stored (`zset` or `string` respectively)
110
+ * `y` can be any one of `hit` or `visit` and defines how will be tracked (once per hit or once per visit)
111
+
112
+ The return value of the method should be `Fixnum` for `count` and `String` for `ratio`
113
+
114
+ If the return value is an `error` or `nil` the metric won't be tracked
115
+
116
+ You can access the `Rack::Request` object via `@rack_request` and the `Rack::Response` object via `@rack_response` in your method
117
+
118
+ You are free to define other methods that do not have the above format in the `Metrics` module as helper methods
119
+
120
+ ```ruby
121
+ module Rack::RedisAnalytics::Metrics
122
+
123
+ # whenever a product is sold, i want to track it per product_id
124
+ def product_sales_ratio_per_hit
125
+ if @request.path == '/product/sale'
126
+ return @request.params['product_id']
127
+ end
128
+ end
129
+
130
+ # whenever a product is viewed by a user, i want to track it per product & user
131
+ def user_product_views_ratio_per_hit
132
+ if @request.path == '/product/info'
133
+ return "#{@request.params['product_id']}_#{@request.params['user_id']}"
134
+ end
135
+ end
136
+
137
+ # how many times did a visitor reach the payment step
138
+ def payment_step_count_per_hit
139
+ return 1 if @request.path == '/payment'
140
+ end
141
+
142
+ end
143
+ ```
144
+
145
+ ### Using filters
146
+
147
+ ```ruby
148
+ Rack::RedisAnalytics.configure do |configuration|
149
+
150
+ # simple string path filter
151
+ configuration.add_path_filter('/robots.txt')
152
+
153
+ # regexp path filter
154
+ configuration.add_path_filter(/^\/favicon.ico$/)
155
+
156
+ # generic filters
157
+ configuration.add_filter do |request, response|
158
+ request.params['layout'] == 'print'
159
+ end
160
+
161
+ # generic filters
162
+ configuration.add_filter do |request, response|
163
+ request.ip =~ /^172.16/ or request.ip =~ /^192.168/
164
+ end
165
+
166
+ end
167
+ ```
168
+
169
+ ## How does it work?
170
+
171
+ ![Screenshot](https://github.com/saturnine/redis_analytics/raw/master/wsd.png)
172
+
173
+ ## License
174
+
175
+ Since redis_analytics is licensed under MIT, you can use redis_analytics, provided you leave the attribution as is, in code as well as on the dashboard pages
176
+
177
+ Copyright (c) 2012-2013 Schubert Cardozo. See [LICENSE!](LICENSE) for further details.
178
+
179
+
97
180
 
98
- Copyright (c) 2012-2013 Schubert Cardozo. See LICENSE for further details.
data/Rakefile CHANGED
@@ -1,19 +1,11 @@
1
- # require 'bundler/gem_tasks'
1
+ # encoding: UTF-8
2
+ require 'bundler/gem_tasks'
2
3
  require 'rake'
3
4
  require 'rspec/core'
4
5
  require 'rspec/core/rake_task'
5
6
 
6
- # encoding: UTF-8
7
- require 'rubygems'
8
- begin
9
- require 'bundler/setup'
10
- rescue LoadError
11
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
12
- end
13
-
14
7
  RSpec::Core::RakeTask.new(:spec) do |spec|
15
8
  spec.pattern = 'spec/**/*_spec.rb'
16
- spec.rspec_opts = ['--backtrace']
17
9
  end
18
10
 
19
- task :default => :spec
11
+ task :default => [:spec, :build]
data/TODO.md ADDED
@@ -0,0 +1,12 @@
1
+ ## TODO
2
+
3
+ * cleanup and refactor analytics.rb
4
+ * cleanup and refactor dashboard.rb
5
+ * dashboard to have custom date selectors
6
+ * need to get more metrics tracked, like landing/exit pages ratio, ratio of page URL's, ratio of http-responses
7
+ * improve 'traffic sources' metric to show - organic vs search-engine vs referral vs campaign
8
+ * js/jquery to refresh dashboard numbers per second (real time mode)
9
+ * advanced filtering options
10
+ * tracker beacon to track from external apps
11
+ * transactions and audits?
12
+ * move to bootstrap 3?
@@ -3,7 +3,7 @@
3
3
  require 'rubygems'
4
4
  path = File.expand_path '../../lib', __FILE__
5
5
  $:.unshift(path) if File.directory?(path) && !$:.include?(path)
6
- require 'redis_analytics/dashboard'
6
+ require 'redis_analytics'
7
7
  args = Hash[*ARGV] rescue {}
8
8
  if args.key?('--redis-host') and args.key?('--redis-port')
9
9
  Rack::RedisAnalytics.configure do |c|
@@ -0,0 +1,13 @@
1
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
2
+
3
+ require 'redis_analytics'
4
+ Rack::RedisAnalytics.configure do |c|
5
+ c.redis_connection = Redis.new
6
+ c.add_path_filter(/^\/favicon.ico$/)
7
+ end
8
+
9
+ app = Rack::Builder.app do
10
+ use Rack::RedisAnalytics::Tracker
11
+ run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, "You have been tracked!"] }
12
+ end
13
+ run app
@@ -2,14 +2,18 @@ require 'rack'
2
2
  require 'redis'
3
3
  require 'browser'
4
4
  require 'sinatra'
5
- require 'geoip'
5
+ require 'geoip'
6
6
 
7
7
  require 'redis_analytics'
8
8
  require 'redis_analytics/version'
9
+ require 'redis_analytics/filter'
9
10
  require 'redis_analytics/configuration'
10
11
  require 'redis_analytics/analytics'
12
+ require 'redis_analytics/metrics'
13
+ require 'redis_analytics/visit'
11
14
  require 'redis_analytics/time_ext'
12
15
  require 'redis_analytics/helpers'
16
+ require 'redis_analytics/api'
13
17
  require 'redis_analytics/dashboard'
14
18
 
15
19
  require 'redis_analytics/tracker'
@@ -2,230 +2,44 @@
2
2
  require 'digest/md5'
3
3
  module Rack
4
4
  module RedisAnalytics
5
-
6
5
  class Analytics
7
-
8
- PAGEVIEWS = [['/health', 'o', 'd', 't'], ['/track', 'o', 'd', 't']]
9
-
10
- TRANSACTIONS = {
11
- "flights_search" => ["/flights/search", :o, :d],
12
- "flights_results" => ["/flights/results", :o, :d],
13
- "flights_book_step_1" => ["/flights/itinerary/:itinerary_id/info"],
14
- "flights_book_step_2" => ["/flights/itinerary/:itinerary_id/traveller"],
15
- "flights_book_step_3" => ["/flights/itinerary/:itinerary_id/pay"],
16
- "flights_book_done" => ["/flights/itinerary/:itinerary_id/confirmation"],
17
- "products" => ["/products/:id/review", :user_id]
18
- }
19
-
20
- REFERRERS = ['google', 'bing', 'yahoo', 'cleartrip', 'github']
21
6
 
22
7
  def initialize(app)
23
8
  @app = app
24
- @redis_key_prefix = "#{RedisAnalytics.redis_namespace}:"
25
9
  end
26
-
27
- # def call(env)
28
- # dup.call!(env)
29
- # end
30
10
 
31
11
  def call(env)
12
+ dup.call!(env)
13
+ end
14
+
15
+ def call!(env)
32
16
  @env = env
33
17
  @request = Request.new(env)
34
18
  status, headers, body = @app.call(env)
35
19
  @response = Rack::Response.new(body, status, headers)
36
- record if @response.ok? and @response.content_type =~ /^text\/html/
20
+ record if should_record?
37
21
  @response.finish
38
22
  end
39
23
 
40
- def parse_path(path)
41
- keys = []
42
- spc = %w{. + ( )}
43
- pattern =
44
- path.to_str.gsub(/((:\w+)|[\*#{spc.join}])/) do |match|
45
- case match
46
- # when "*"
47
- # keys << 'splat'
48
- # "(.*?)"
49
- when *spc
50
- Regexp.escape(match)
51
- else
52
- keys << $2[1..-1]
53
- "([^/?&#]+)"
54
- end
24
+ def should_record?
25
+ return false unless @response.ok?
26
+ return false unless @response.content_type =~ /^text\/html/
27
+ RedisAnalytics.path_filters.each do |filter|
28
+ return false if filter.matches?(@request.path)
55
29
  end
56
- [/^#{pattern}$/, keys]
57
- end
58
-
59
- def for_each_time_range(t)
60
- RedisAnalytics.redis_key_timestamps.map{|x, y| [t.strftime(x), y]}.each do |ts, expire|
61
- yield(ts, expire) # returns an array of the redis methods to call
62
- # r.each do |method, args|
63
- # RedisAnalytics.redis_connection.send(method, *args)
64
- # RedisAnalytics.redis_connection.expire(args[1]) if expire # assuming args[1] is always the key that is being operated on.. will this always work?
65
- # end
30
+ RedisAnalytics.filters.each do |filter|
31
+ return false if filter.matches?(@request, @response)
66
32
  end
33
+ return true
67
34
  end
68
35
 
69
36
  def record
70
- t = Time.now
71
-
72
- # record pageviews
73
- path = @request.path
74
- params = @request.params
75
- if i = PAGEVIEWS.index{|x| x[0] == path}
76
- page = PAGEVIEWS[i]
77
- params.select{|x, y| page[1..-1].include?(x)}.each do |k, v|
78
- for_each_time_range(t) do |ts, expire|
79
- h = Digest::MD5.hexdigest(v)
80
- RedisAnalytics.redis_connection.hset("#{@redis_key_prefix}page", h, v)
81
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}page", expire) if expire
82
-
83
- RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}page_#{i}_#{page.index(k)}_#{h}:#{ts}")
84
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}page_#{i}_#{page.index(k)}_#{h}:#{ts}", expire) if expire
85
- end
86
- end
87
- end
88
-
89
- returning_visitor = @request.cookies[RedisAnalytics.returning_user_cookie_name]
90
- recent_visitor = @request.cookies[RedisAnalytics.visit_cookie_name]
91
-
92
- vcn_seq, rucn_seq = nil
93
- visit_start_time =visit_end_time = first_visit_time = t.to_i
94
-
95
- if not returning_visitor and not recent_visitor
96
- rucn_seq = RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}unique_visits")
97
- vcn_seq = RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}visits")
98
- visit(t, :rucn_seq => rucn_seq)
99
- new_visit(t)
100
- visit_time(t, t.to_i)
101
- page_view(t)
102
- elsif returning_visitor and not recent_visitor
103
- vcn_seq = RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}visits")
104
- rucn_seq, first_visit_time, last_visit_time = returning_visitor.split('.')
105
- visit(t, :last_visit_time => last_visit_time.to_i, :rucn_seq => rucn_seq)
106
- page_view(t)
107
- elsif returning_visitor and recent_visitor
108
- rucn_seq, vcn_seq, visit_start_time, visit_end_time = recent_visitor.split('.')
109
- rucn_seq, first_visit_time = returning_visitor.split('.')
110
- visit_time(t, visit_end_time.to_i)
111
- page_view(t, visit_start_time.to_i == visit_end_time.to_i)
112
- elsif not returning_visitor and recent_visitor
113
- rucn_seq, vcn_seq, visit_start_time, visit_end_time = recent_visitor.split('.')
114
- visit_time(t, visit_end_time.to_i)
115
- page_view(t, visit_start_time.to_i == visit_end_time.to_i)
116
- end
117
-
118
- # create the recent visit cookie
119
- @response.set_cookie(RedisAnalytics.visit_cookie_name, {:value => "#{rucn_seq}.#{vcn_seq}.#{visit_start_time}.#{t.to_i}", :expires => t + (RedisAnalytics.visit_timeout.to_i * 60 )})
120
-
121
- # create the permanent cookie (2 years)
122
- @response.set_cookie(RedisAnalytics.returning_user_cookie_name, {:value => "#{rucn_seq}.#{first_visit_time}.#{t.to_i}", :expires => t + (60 * 60 * 24 * 5)}) # 5 hours temp
123
-
124
- puts "TIME = [#{t}]"
125
- puts "VISIT = #{vcn_seq}"
126
- puts "UNIQUE VISIT = #{rucn_seq}"
127
- end
128
-
129
- def new_visit(t)
130
- for_each_time_range(t) do |ts, expire|
131
- RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}new_visits:#{ts}")
132
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}new_visits:#{ts}",expire) if expire
133
- end
37
+ v = Visit.new(@request, @response)
38
+ @response = v.record
39
+ @response.set_cookie(RedisAnalytics.current_visit_cookie_name, v.updated_current_visit_info)
40
+ @response.set_cookie(RedisAnalytics.first_visit_cookie_name, v.updated_first_visit_info)
134
41
  end
135
42
 
136
- def visit(t, options = {})
137
- last_visit_time = options[:last_visit_time] || nil
138
- rucn_seq = options[:rucn_seq] || nil
139
- geo_country_code = nil
140
- referrer = nil
141
- ua = nil
142
-
143
- # Geo IP Country code fetch
144
- if defined?(GeoIP)
145
- begin
146
- g = GeoIP.new(RedisAnalytics.geo_ip_data_path)
147
- geo_country_code = g.country(@request.ip).to_hash[:country_code2]
148
- puts "Tracking IP #{@request.ip} using #{g.inspect} => GOT #{geo_country_code}"
149
- rescue Exception => e
150
- puts "Warning: Unable to fetch country info #{e}"
151
- end
152
- end
153
-
154
- # Referrer regex decode
155
- if @request.referrer
156
- REFERRERS.each do |referrer|
157
- # this will track x.google.mysite.com as google so its buggy, fix the regex
158
- if m = @request.referrer.match(/^(https?:\/\/)?([a-zA-Z0-9\.\-]+\.)?(#{referrer})\.([a-zA-Z\.]+)(:[0-9]+)?(\/.*)?$/)
159
- "REFERRER => #{m.to_a[3]}"
160
- referrer = m.to_a[3]
161
- else
162
- referrer = 'other'
163
- end
164
- end
165
- else
166
- referrer = 'organic'
167
- end
168
-
169
- # User agent
170
- ua = Browser.new(:ua => @request.user_agent, :accept_language => 'en-us')
171
-
172
- for_each_time_range(t) do |ts, expire|
173
-
174
- # increment the total visits
175
- RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}visits:#{ts}")
176
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}visits:#{ts}", expire) if expire
177
-
178
- if rucn_seq
179
- unique = RedisAnalytics.redis_connection.sadd("#{@redis_key_prefix}unique_visits:#{ts}", rucn_seq)
180
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}unique_visits:#{ts}", expire) if expire
181
-
182
- # geo ip tracking
183
- if geo_country_code and geo_country_code =~ /^[A-Z]{2}$/
184
- RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_country:#{ts}", 1, geo_country_code)
185
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_country:#{ts}", expire) if expire
186
- end
187
-
188
- # referrer tracking
189
- RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_referrers:#{ts}", 1, referrer)
190
- end
191
-
192
- # tracking for visitor recency
193
- if last_visit_time
194
- days_since_last_visit = ((t.to_i - last_visit_time)/(24*3600)).round
195
- RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_recency:#{ts}", 1, days_since_last_visit)
196
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_recency:#{ts}", expire) if expire
197
- end
198
-
199
- RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_browsers:#{ts}", 1, ua.name)
200
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_browsers:#{ts}", expire) if expire
201
-
202
- RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_platforms:#{ts}", 1, ua.platform.to_s)
203
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_platforms:#{ts}", expire) if expire
204
-
205
- RedisAnalytics.redis_connection.zincrby("#{@redis_key_prefix}ratio_devices:#{ts}", 1, ua.mobile? ? 'M' : 'D')
206
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}ratio_devices:#{ts}", expire) if expire
207
- end
208
- end
209
-
210
- def visit_time(t, visit_end_time = nil)
211
- for_each_time_range(t) do |ts, expire|
212
- RedisAnalytics.redis_connection.incrby("#{@redis_key_prefix}visit_time:#{ts}", t.to_i - visit_end_time)
213
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}visit_time:#{ts}", expire) if expire
214
- end
215
- end
216
-
217
- # 2nd pageview in a visit
218
- def page_view(t, second_page_view = false)
219
- for_each_time_range(t) do |ts, expire|
220
- RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}page_views:#{ts}")
221
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}page_views:#{ts}", expire) if expire
222
-
223
- RedisAnalytics.redis_connection.incr("#{@redis_key_prefix}second_page_views:#{ts}") if second_page_view
224
- RedisAnalytics.redis_connection.expire("#{@redis_key_prefix}second_page_views:#{ts}", expire) if second_page_view and expire
225
- end
226
- end
227
-
228
-
229
43
  end
230
44
  end
231
45
  end