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.
- data/CHANGELOG.md +12 -0
- data/CONTRIBUTING.md +33 -0
- data/Guardfile +10 -0
- data/README.md +105 -23
- data/Rakefile +3 -11
- data/TODO.md +12 -0
- data/bin/redis_analytics_dashboard +1 -1
- data/config.ru +13 -0
- data/lib/redis_analytics.rb +5 -1
- data/lib/redis_analytics/analytics.rb +17 -203
- data/lib/redis_analytics/api.rb +60 -0
- data/lib/redis_analytics/configuration.rb +47 -16
- data/lib/redis_analytics/dashboard.rb +28 -89
- data/lib/redis_analytics/dashboard/public/{favicon.ico → img/favicon.ico} +0 -0
- data/lib/redis_analytics/dashboard/public/javascripts/{bootstrap.min.js → vendor/bootstrap/bootstrap.min.js} +0 -0
- data/lib/redis_analytics/dashboard/public/javascripts/{jquery-1.9.1.min.js → vendor/jquery-1.9.1.min.js} +0 -0
- data/lib/redis_analytics/dashboard/public/javascripts/{jquery-jvectormap-1.2.2.min.js → vendor/jquery-jvectormap-1.2.2.min.js} +0 -0
- data/lib/redis_analytics/dashboard/public/javascripts/{jquery-jvectormap-world-mill-en.js → vendor/jquery-jvectormap-world-mill-en.js} +0 -0
- data/lib/redis_analytics/dashboard/public/javascripts/{morris.min.js → vendor/morris.min.js} +0 -0
- data/lib/redis_analytics/dashboard/public/javascripts/{raphael-min.js → vendor/raphael-min.js} +0 -0
- data/lib/redis_analytics/dashboard/views/activity.erb +7 -0
- data/lib/redis_analytics/dashboard/views/dialogs/unique_visits.erb +41 -0
- data/lib/redis_analytics/dashboard/views/dialogs/visits.erb +40 -0
- data/lib/redis_analytics/dashboard/views/footer.erb +1 -3
- data/lib/redis_analytics/dashboard/views/header.erb +17 -42
- data/lib/redis_analytics/dashboard/views/layout.erb +5 -22
- data/lib/redis_analytics/dashboard/views/visits.erb +38 -143
- data/lib/redis_analytics/dashboard/views/visits_js.erb +110 -247
- data/lib/redis_analytics/dashboard/views/widgets/bounce_rate.erb +3 -0
- data/lib/redis_analytics/dashboard/views/widgets/browsers_donut.erb +8 -0
- data/lib/redis_analytics/dashboard/views/widgets/first_visits.erb +3 -0
- data/lib/redis_analytics/dashboard/views/widgets/page_depth.erb +3 -0
- data/lib/redis_analytics/dashboard/views/widgets/referers_donut.erb +8 -0
- data/lib/redis_analytics/dashboard/views/widgets/total_page_views.erb +3 -0
- data/lib/redis_analytics/dashboard/views/widgets/total_visits.erb +3 -0
- data/lib/redis_analytics/dashboard/views/widgets/unique_visits_line.erb +26 -0
- data/lib/redis_analytics/dashboard/views/widgets/visit_duration.erb +3 -0
- data/lib/redis_analytics/dashboard/views/widgets/visit_spark.erb +23 -0
- data/lib/redis_analytics/dashboard/views/widgets/visitor_recency_slices.erb +39 -0
- data/lib/redis_analytics/dashboard/views/widgets/visits_area.erb +30 -0
- data/lib/redis_analytics/dashboard/views/widgets/visits_donut.erb +8 -0
- data/lib/redis_analytics/dashboard/views/widgets/world_map.erb +50 -0
- data/lib/redis_analytics/filter.rb +33 -0
- data/lib/redis_analytics/helpers.rb +36 -30
- data/lib/redis_analytics/metrics.rb +96 -0
- data/lib/redis_analytics/time_ext.rb +72 -2
- data/lib/redis_analytics/tracker.rb +13 -5
- data/lib/redis_analytics/version.rb +1 -1
- data/lib/redis_analytics/visit.rb +122 -0
- data/redis_analytics.gemspec +19 -14
- data/spec/lib/redis_analytics/analytics_spec.rb +59 -0
- data/spec/lib/redis_analytics/configuration_spec.rb +158 -0
- data/spec/lib/redis_analytics/dashboard_spec.rb +32 -0
- data/spec/lib/redis_analytics/filter_spec.rb +34 -0
- data/spec/lib/redis_analytics/tracker_spec.rb +20 -0
- data/spec/spec_helper.rb +13 -6
- data/spec/support/fakeredis.rb +1 -0
- data/wsd.png +0 -0
- metadata +268 -126
- data/lib/redis_analytics/config.ru +0 -10
- data/spec/redis_analytics_spec.rb +0 -57
data/CHANGELOG.md
ADDED
@@ -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
|
data/CONTRIBUTING.md
ADDED
@@ -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
|
data/Guardfile
ADDED
@@ -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
|
-
|
1
|
+
## redis_analytics [](https://travis-ci.org/saturnine/redis_analytics) [](https://coveralls.io/r/saturnine/redis_analytics) [](http://badge.fury.io/rb/redis_analytics)
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
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
|
13
|
+
Yes, It uses the excellent [Morris.js](http://www.oesmith.co.uk/morris.js/) for graphs/charts
|
14
14
|
|
15
15
|

|
16
16
|
|
17
|
-
|
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
|
-
|
30
|
+
### How do I enable tracking in my rack-compliant app?
|
31
31
|
|
32
|
-
|
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
|
-
|
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
|
-
|
53
|
+
### Where can I see the dashboard?
|
54
54
|
|
55
|
-
|
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
|
-
|
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
|
-
|
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
|
-
##
|
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
|
+

|
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
|
-
#
|
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
|
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|
|
data/config.ru
ADDED
@@ -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
|
data/lib/redis_analytics.rb
CHANGED
@@ -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
|
20
|
+
record if should_record?
|
37
21
|
@response.finish
|
38
22
|
end
|
39
23
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|