active_analytics 0.2.1 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +97 -15
- data/app/controllers/active_analytics/application_controller.rb +29 -6
- data/app/controllers/active_analytics/assets_controller.rb +36 -0
- data/app/controllers/active_analytics/browsers_controller.rb +27 -0
- data/app/controllers/active_analytics/pages_controller.rb +9 -10
- data/app/controllers/active_analytics/referrers_controller.rb +9 -5
- data/app/controllers/active_analytics/sites_controller.rb +5 -4
- data/app/helpers/active_analytics/browsers_helper.rb +12 -0
- data/app/lib/active_analytics/histogram.rb +47 -0
- data/app/models/active_analytics/browsers_per_day.rb +40 -0
- data/app/models/active_analytics/views_per_day.rb +16 -53
- data/app/views/active_analytics/assets/_charts.css +249 -0
- data/app/{assets/stylesheets/active_analytics/style.css → views/active_analytics/assets/_style.css} +36 -26
- data/app/views/active_analytics/assets/application.css.erb +2 -0
- data/app/{assets/javascripts/active_analytics → views/active_analytics/assets}/application.js +1 -3
- data/app/{assets/stylesheets/active_analytics → views/active_analytics/assets}/ariato.css +237 -234
- data/app/views/active_analytics/assets/browsers/arc.svg +1 -0
- data/app/views/active_analytics/assets/browsers/brave.svg +1 -0
- data/app/views/active_analytics/assets/browsers/chrome.svg +1 -0
- data/app/views/active_analytics/assets/browsers/default.svg +8 -0
- data/app/views/active_analytics/assets/browsers/firefox.svg +1 -0
- data/app/views/active_analytics/assets/browsers/internet_explorer.svg +1 -0
- data/app/views/active_analytics/assets/browsers/microsoft_edge.svg +1 -0
- data/app/views/active_analytics/assets/browsers/opera.svg +1 -0
- data/app/views/active_analytics/assets/browsers/safari.svg +1 -0
- data/app/views/active_analytics/assets/browsers/samsung_internet.svg +1 -0
- data/app/views/active_analytics/assets/browsers/vivaldi.svg +1 -0
- data/app/views/active_analytics/assets/browsers/yandex.svg +1 -0
- data/app/views/active_analytics/browsers/_table.html.erb +17 -0
- data/app/views/active_analytics/browsers/_version_table.html.erb +16 -0
- data/app/views/active_analytics/browsers/index.html.erb +9 -0
- data/app/views/active_analytics/browsers/show.html.erb +17 -0
- data/app/views/active_analytics/pages/_table.html.erb +6 -17
- data/app/views/active_analytics/pages/index.html.erb +2 -2
- data/app/views/active_analytics/pages/show.html.erb +7 -4
- data/app/views/active_analytics/referrers/_table.html.erb +9 -2
- data/app/views/active_analytics/referrers/index.html.erb +3 -3
- data/app/views/active_analytics/referrers/show.html.erb +6 -3
- data/app/views/active_analytics/sites/_histogram.html.erb +11 -4
- data/app/views/active_analytics/sites/_histogram_header.html.erb +10 -0
- data/app/views/active_analytics/sites/show.html.erb +9 -4
- data/app/views/layouts/active_analytics/_footer.html.erb +7 -7
- data/app/views/layouts/active_analytics/_header.html.erb +1 -3
- data/app/views/layouts/active_analytics/application.html.erb +5 -3
- data/config/routes.rb +7 -1
- data/db/migrate/20210303094108_create_active_analytics_views_per_days.rb +2 -3
- data/db/migrate/20240823150626_create_active_analytics_browsers_per_days.rb +13 -0
- data/lib/active_analytics/engine.rb +0 -4
- data/lib/active_analytics/version.rb +1 -1
- data/lib/active_analytics.rb +77 -3
- metadata +49 -12
- data/app/assets/stylesheets/active_analytics/application.css +0 -15
- data/app/assets/stylesheets/active_analytics/charts.css +0 -296
- /data/app/{assets/javascripts/active_analytics → views/active_analytics/assets}/ariato.js +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a008072c9c416bc367e4952deb03036e422368fc98a1da10e0e6921a59f45a09
|
4
|
+
data.tar.gz: a7a1999f79b95ba4007280e36be378dd2c6e00885f0254db8b9a3dfbc2e67dbd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ad8d068d845fbe22c00a3cafbbcc2cd4459934e4b2ba7790c394cb1c69e4858c1f58eb25ef76b5e0b77e3c71597ce1d1ac6f2caa8173a62b83f8b51f644226b
|
7
|
+
data.tar.gz: 256cc8d412ca06ea8cb71f522d4935fa212076e17a240deb03e075aa2ec8da37ea6d602dc6bd536d5a6f6b029e9c249757efdb185ac5394fb16d0f4b61ba1e7f
|
data/README.md
CHANGED
@@ -32,38 +32,109 @@ rails active_analytics:install:migrations
|
|
32
32
|
rails db:migrate
|
33
33
|
```
|
34
34
|
|
35
|
-
|
35
|
+
Add the route to ActiveAnalytics dashboard at the desired endpoint:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# config/routes.rb
|
39
|
+
mount ActiveAnalytics::Engine, at: "analytics" # http://localhost:3000/analytics
|
40
|
+
```
|
41
|
+
By default ActiveAnalytics will extend `ActionController::Base`, but you can specify a custom base controller for the ActiveAnalytics dashboard:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# config/initializers/active_analytics.rb
|
45
|
+
Rails.application.configure do
|
46
|
+
ActiveAnalytics.base_controller_class = "AdminController"
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
|
51
|
+
The next step is to collect trafic and there is 2 options.
|
52
|
+
|
53
|
+
### Record requests synchronously
|
54
|
+
|
55
|
+
This is the easiest way to start with.
|
56
|
+
However it's less performant since it triggers a write into your database for each request.
|
57
|
+
Your controllers have to call `ActiveAnalytics.record_request(request)` to record page views.
|
58
|
+
The Rails way to achieve is to use `after_action` :
|
36
59
|
|
37
60
|
```ruby
|
38
61
|
class ApplicationController < ActionController::Base
|
39
|
-
|
62
|
+
after_action :record_page_view
|
40
63
|
|
41
64
|
def record_page_view
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
65
|
+
# This is a basic example, you might need to customize some conditions.
|
66
|
+
# For most sites, it makes no sense to record anything other than HTML.
|
67
|
+
if response.content_type && response.content_type.start_with?("text/html")
|
68
|
+
# Add a condition to record only your canonical domain
|
69
|
+
# and use a gem such as crawler_detect to skip bots.
|
70
|
+
ActiveAnalytics.record_request(request)
|
71
|
+
end
|
45
72
|
end
|
46
73
|
end
|
47
74
|
```
|
48
75
|
|
49
|
-
In case you don't want to record all page views, because each application has sensitive URLs such as password reset and so on, simply define a `
|
76
|
+
In case you don't want to record all page views, because each application has sensitive URLs such as password reset and so on, simply define a `skip_after_action :record_page_view` in the relevant controller.
|
77
|
+
|
78
|
+
### Queue requests asynchronously
|
50
79
|
|
51
|
-
|
80
|
+
It requires more work and it's relevant if your application handle a large trafic.
|
81
|
+
The idea is to queue data into Redis because it does not require the database writing to the disk on each request.
|
82
|
+
First you have to set the Redis URL or connection.
|
52
83
|
|
53
84
|
```ruby
|
54
|
-
|
85
|
+
# File lib/patches/active_analytics.rb or config/initializers/active_analytics.rb
|
86
|
+
|
87
|
+
ActiveAnalytics.redis_url = "redis://user:password@host/1" # Default ENV["ACTIVE_ANALYTICS_REDIS_URL"] || ENV["REDIS_URL"] || "redis://localhost"
|
88
|
+
|
89
|
+
# If you use special connection options you have to instantiate it yourself
|
90
|
+
ActiveAnalytics.redis = Redis.new(
|
91
|
+
url: ENV["REDIS_URL"],
|
92
|
+
reconnect_attempts: 10,
|
93
|
+
ssl_params: {verify_mode: OpenSSL::SSL::VERIFY_NONE}
|
94
|
+
)
|
95
|
+
```
|
96
|
+
|
97
|
+
Then your controllers have to call `ActiveAnalytics.queue_request(request)` to queue page views.
|
98
|
+
The Rails way to achieve is to use `after_action` :
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
class ApplicationController < ActionController::Base
|
102
|
+
after_action :queue_page_view
|
103
|
+
|
104
|
+
def queue_page_view
|
105
|
+
# This is a basic example, you might need to customize some conditions.
|
106
|
+
# For most sites, it makes no sense to record anything other than HTML.
|
107
|
+
if response.content_type && response.content_type.start_with?("text/html")
|
108
|
+
# Add a condition to record only your canonical domain
|
109
|
+
# and use a gem such as crawler_detect to skip bots.
|
110
|
+
ActiveAnalytics.queue_request(request)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
55
114
|
```
|
56
115
|
|
116
|
+
Queued data need to be saved into the database in order to be viewable in the ActiveAnalytics dashboard.
|
117
|
+
For that, call `ActiveAnalytics.flush_queue` from a cron task or a background job.
|
118
|
+
|
119
|
+
It's up to you if you want to flush the queue every hour or every 10 minutes.
|
120
|
+
I advise to execute the last flush of the day at 23:59.
|
121
|
+
It prevents from shifting the trafic to the next day.
|
122
|
+
In that case only the last minute will be shifted to the next day, even if the flush ends after midnight.
|
123
|
+
This small imperfection allows a simpler implementation for now.
|
124
|
+
Keep it simple !
|
125
|
+
|
126
|
+
|
57
127
|
## Authentication and permissions
|
58
128
|
|
59
|
-
ActiveAnalytics cannot guess how you handle user authentication, because it is different for all Rails applications.
|
129
|
+
ActiveAnalytics cannot guess how you handle user authentication, because it is different for all Rails applications.
|
130
|
+
So you have to monkey patch `ActiveAnalytics::ApplicationController` in order to inject your own mechanism.
|
131
|
+
The patch can be saved wherever you want.
|
132
|
+
For example, I like to have all the patches in one place, so I put them in `lib/patches`.
|
60
133
|
|
61
134
|
```ruby
|
62
|
-
#
|
63
|
-
require_dependency "active_analytics/application_controller"
|
135
|
+
# lib/patches/active_analytics.rb
|
64
136
|
|
65
|
-
|
66
|
-
class ApplicationController
|
137
|
+
ActiveAnalytics::ApplicationController.class_eval do
|
67
138
|
before_action :require_admin
|
68
139
|
|
69
140
|
def require_admin
|
@@ -74,7 +145,18 @@ module ActiveAnalytics
|
|
74
145
|
end
|
75
146
|
```
|
76
147
|
|
77
|
-
|
148
|
+
Then you have to require the monkey patch.
|
149
|
+
Because it's loaded via require, it won't be reloaded in development.
|
150
|
+
Since you are not supposed to change this file often, it should not be an issue.
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
# config/application.rb
|
154
|
+
config.after_initialize do
|
155
|
+
require "patches/active_analytics"
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
If you use Devise, you can check the permission directly from routes.rb :
|
78
160
|
|
79
161
|
```ruby
|
80
162
|
# config/routes.rb
|
@@ -88,4 +170,4 @@ The gem is available as open-source under the terms of the [MIT License](https:/
|
|
88
170
|
|
89
171
|
Made by [Base Secrète](https://basesecrete.com).
|
90
172
|
|
91
|
-
Rails developer? Check out [RoRvsWild](https://rorvswild.com), our Ruby on Rails application monitoring tool.
|
173
|
+
Rails developer? Check out [RoRvsWild](https://rorvswild.com), our Ruby on Rails application monitoring tool.
|
@@ -1,18 +1,41 @@
|
|
1
1
|
module ActiveAnalytics
|
2
|
-
class ApplicationController <
|
2
|
+
class ApplicationController < ActiveAnalytics.base_controller_class.constantize
|
3
|
+
layout "active_analytics/application"
|
4
|
+
|
5
|
+
helper PagesHelper
|
6
|
+
helper SitesHelper
|
7
|
+
helper BrowsersHelper
|
3
8
|
|
4
9
|
private
|
5
10
|
|
11
|
+
def from_date
|
12
|
+
@from_date ||= Date.parse(params[:from])
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_date
|
16
|
+
@to_date ||= Date.parse(params[:to])
|
17
|
+
end
|
18
|
+
|
6
19
|
def require_date_range
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
rescue TypeError, ArgumentError # Raise by Date.parse when invalid format
|
20
|
+
redirect_to(params.to_unsafe_hash.merge(from: to_date, to: from_date)) if from_date > to_date
|
21
|
+
rescue TypeError, ArgumentError # Raised by Date.parse when invalid format
|
11
22
|
redirect_to(params.to_unsafe_hash.merge(from: 7.days.ago.to_date, to: Date.today))
|
12
23
|
end
|
13
24
|
|
14
25
|
def current_views_per_days
|
15
|
-
ViewsPerDay.where(site: params[:site]).between_dates(
|
26
|
+
ViewsPerDay.where(site: params[:site]).between_dates(from_date, to_date)
|
27
|
+
end
|
28
|
+
|
29
|
+
def previous_views_per_days
|
30
|
+
ViewsPerDay.where(site: params[:site]).between_dates(previous_from_date, previous_to_date)
|
31
|
+
end
|
32
|
+
|
33
|
+
def previous_from_date
|
34
|
+
from_date - (to_date - from_date)
|
35
|
+
end
|
36
|
+
|
37
|
+
def previous_to_date
|
38
|
+
to_date - (to_date - from_date)
|
16
39
|
end
|
17
40
|
end
|
18
41
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "active_analytics/application_controller"
|
4
|
+
|
5
|
+
module ActiveAnalytics
|
6
|
+
class AssetsController < ApplicationController
|
7
|
+
protect_from_forgery except: :show
|
8
|
+
|
9
|
+
@@root = ActiveAnalytics::Engine.root.join("app/views", controller_path + "/").to_s
|
10
|
+
|
11
|
+
def self.endpoints
|
12
|
+
return @endpoints if @endpoints
|
13
|
+
paths = Dir["#{@@root}**/*"].keep_if { |path| File.file?(path) }
|
14
|
+
files = paths.map { |path| path.to_s.delete_prefix(@@root).delete_suffix(".erb") }
|
15
|
+
@endpoints = files.delete_if { |str| str.start_with?("_") }
|
16
|
+
end
|
17
|
+
|
18
|
+
def show
|
19
|
+
if self.class.endpoints.include?(params[:file])
|
20
|
+
expires_in(1.day, public: true)
|
21
|
+
render_asset(params[:file])
|
22
|
+
else
|
23
|
+
raise ActionController::RoutingError.new("Not found #{params[:file]}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def render_asset(path)
|
30
|
+
ext = File.extname(params[:file])
|
31
|
+
path_without_ext = path.delete_suffix(ext)
|
32
|
+
mime_type = Mime::Type.lookup_by_extension(ext.delete_prefix("."))
|
33
|
+
render("#{controller_path}/#{path_without_ext}", mime_type: mime_type)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_dependency "active_analytics/application_controller"
|
2
|
+
|
3
|
+
module ActiveAnalytics
|
4
|
+
class BrowsersController < ApplicationController
|
5
|
+
def index
|
6
|
+
@histogram = Histogram.new(current_browsers_per_days.order_by_date.group_by_date, from_date, to_date)
|
7
|
+
@previous_histogram = Histogram.new(previous_browsers_per_days.order_by_date.group_by_date, previous_from_date, previous_to_date)
|
8
|
+
@browsers = current_browsers_per_days.group_by_name.top(100)
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
@histogram = Histogram.new(current_browsers_per_days.order_by_date.group_by_date, from_date, to_date)
|
13
|
+
@previous_histogram = Histogram.new(previous_browsers_per_days.order_by_date.group_by_date, previous_from_date, previous_to_date)
|
14
|
+
@browsers = current_browsers_per_days.group_by_version.top(100)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def current_browsers_per_days
|
20
|
+
BrowsersPerDay.filter_by(params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def previous_browsers_per_days
|
24
|
+
BrowsersPerDay.filter_by(params.merge(from: previous_from_date, to: previous_to_date))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -7,19 +7,18 @@ module ActiveAnalytics
|
|
7
7
|
before_action :require_date_range
|
8
8
|
|
9
9
|
def index
|
10
|
-
|
11
|
-
@
|
12
|
-
@pages =
|
10
|
+
@histogram = Histogram.new(current_views_per_days.order_by_date.group_by_date, from_date, to_date)
|
11
|
+
@previous_histogram = Histogram.new(previous_views_per_days.order_by_date.group_by_date, previous_from_date, previous_to_date)
|
12
|
+
@pages = current_views_per_days.top(100).group_by_page
|
13
13
|
end
|
14
14
|
|
15
15
|
def show
|
16
|
-
|
17
|
-
|
18
|
-
@histogram =
|
19
|
-
@
|
20
|
-
|
21
|
-
@
|
22
|
-
@previous_pages = dates_scopes.where(site: params[:site], page: page_from_params).where.not(referrer_path: nil).top(100).group_by_referrer_page
|
16
|
+
page_scope = current_views_per_days.where(page: page_from_params)
|
17
|
+
previous_page_scope = previous_views_per_days.where(page: page_from_params)
|
18
|
+
@histogram = Histogram.new(page_scope.order_by_date.group_by_date, from_date, to_date)
|
19
|
+
@previous_histogram = Histogram.new(previous_page_scope.order_by_date.group_by_date, previous_from_date, previous_to_date)
|
20
|
+
@next_pages = current_views_per_days.where(referrer_host: params[:site], referrer_path: page_from_params).top(100).group_by_page
|
21
|
+
@previous_pages = page_scope.top(100).group_by_referrer_page
|
23
22
|
end
|
24
23
|
end
|
25
24
|
end
|
@@ -5,14 +5,18 @@ module ActiveAnalytics
|
|
5
5
|
before_action :require_date_range
|
6
6
|
|
7
7
|
def index
|
8
|
-
|
9
|
-
@
|
10
|
-
@
|
8
|
+
@referrers = current_views_per_days.top(100).group_by_referrer_site
|
9
|
+
@histogram = Histogram.new(current_views_per_days.order_by_date.group_by_date, from_date, to_date)
|
10
|
+
@previous_histogram = Histogram.new(previous_views_per_days.order_by_date.group_by_date, previous_from_date, previous_to_date)
|
11
11
|
end
|
12
12
|
|
13
13
|
def show
|
14
|
-
|
15
|
-
|
14
|
+
referrer_host, referrer_path = params[:referrer].split("/", 2)
|
15
|
+
scope = current_views_per_days.where(referrer_host: referrer_host)
|
16
|
+
scope = scope.where(referrer_path: "/" + referrer_path) if referrer_path.present?
|
17
|
+
previous_scope = previous_views_per_days.where(referrer_host: params[:referrer])
|
18
|
+
@histogram = Histogram.new(scope.order_by_date.group_by_date, from_date, to_date)
|
19
|
+
@previous_histogram = Histogram.new(previous_scope.order_by_date.group_by_date, previous_from_date, previous_to_date)
|
16
20
|
@previous_pages = scope.top(100).group_by_referrer_page
|
17
21
|
@next_pages = scope.top(100).group_by_page
|
18
22
|
end
|
@@ -10,10 +10,11 @@ module ActiveAnalytics
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def show
|
13
|
-
|
14
|
-
@
|
15
|
-
@referrers =
|
16
|
-
@pages =
|
13
|
+
@histogram = Histogram.new(current_views_per_days.order_by_date.group_by_date, from_date, to_date)
|
14
|
+
@previous_histogram = Histogram.new(previous_views_per_days.order_by_date.group_by_date, previous_from_date, previous_to_date)
|
15
|
+
@referrers = current_views_per_days.top.group_by_referrer_site
|
16
|
+
@pages = current_views_per_days.top.group_by_page
|
17
|
+
@browsers = BrowsersPerDay.filter_by(params).group_by_name.top
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActiveAnalytics
|
2
|
+
module BrowsersHelper
|
3
|
+
def browser_icon(browser_name)
|
4
|
+
path = "browsers/#{browser_name.to_s.parameterize(separator: "_")}.svg"
|
5
|
+
if AssetsController.endpoints.include?(path)
|
6
|
+
image_tag(asset_url(path, host: request.host), alt: browser_name, class: "referer-favicon", width: 16, height: 16)
|
7
|
+
else
|
8
|
+
image_tag(asset_url("browsers/default.svg", host: request.host), alt: browser_name, class: "referer-favicon", width: 16, height: 16)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ActiveAnalytics
|
2
|
+
class Histogram
|
3
|
+
attr_reader :bars, :from_date, :to_date
|
4
|
+
|
5
|
+
def initialize(scope, from_date, to_date)
|
6
|
+
@scope = scope
|
7
|
+
@from_date, @to_date = from_date, to_date
|
8
|
+
@bars = scope.map { |record| Bar.new(record.date, record.total, self) }
|
9
|
+
fill_missing_days(@bars, @from_date, @to_date)
|
10
|
+
end
|
11
|
+
|
12
|
+
def fill_missing_days(bars, from, to)
|
13
|
+
i = 0
|
14
|
+
while (day = from + i) <= to
|
15
|
+
if !@bars[i] || @bars[i].label != day
|
16
|
+
@bars.insert(i, Bar.new(day, 0, self))
|
17
|
+
end
|
18
|
+
i += 1
|
19
|
+
end
|
20
|
+
@bars
|
21
|
+
end
|
22
|
+
|
23
|
+
def max_value
|
24
|
+
@max_total ||= bars.map(&:value).max
|
25
|
+
end
|
26
|
+
|
27
|
+
def total
|
28
|
+
@bars.reduce(0) { |sum, bar| sum += bar.value }
|
29
|
+
end
|
30
|
+
|
31
|
+
class Bar
|
32
|
+
attr_reader :label, :value, :histogram
|
33
|
+
|
34
|
+
def initialize(label, value, histogram)
|
35
|
+
@label, @value, @histogram = label, value, histogram
|
36
|
+
end
|
37
|
+
|
38
|
+
def height
|
39
|
+
if histogram.max_value > 0
|
40
|
+
(value.to_f / histogram.max_value).round(2)
|
41
|
+
else
|
42
|
+
0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveAnalytics
|
2
|
+
class BrowsersPerDay < ApplicationRecord
|
3
|
+
# TODO: Deduplicate
|
4
|
+
scope :top, -> (n = 10) { order_by_totals.limit(n) }
|
5
|
+
scope :order_by_date, -> { order(:date) }
|
6
|
+
scope :order_by_totals, -> { order(Arel.sql("SUM(total) DESC")) }
|
7
|
+
scope :between_dates, -> (from, to) { where(date: from..to) }
|
8
|
+
|
9
|
+
def self.append(params)
|
10
|
+
total = params.delete(:total) || 1
|
11
|
+
params[:site] = params[:site].downcase if params[:site]
|
12
|
+
where(params).first.try(:increment!, :total, total) || create!(params.merge(total: total))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.group_by_name
|
16
|
+
group(:name).select("name, sum(total) AS total")
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.group_by_version
|
20
|
+
group(:name, :version).select("version, sum(total) AS total")
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.group_by_date
|
24
|
+
group(:date).select("date, sum(total) as total")
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.filter_by(params)
|
28
|
+
scope = all
|
29
|
+
scope = scope.between_dates(params[:from], params[:to]) if params[:from].present? && params[:to].present?
|
30
|
+
scope = scope.where(site: params[:site]) if params[:site].present?
|
31
|
+
scope = scope.where(name: params[:id]) if params[:id].present?
|
32
|
+
scope = scope.where(version: params[:version]) if params[:version].present?
|
33
|
+
scope
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_param
|
37
|
+
name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -26,53 +26,6 @@ module ActiveAnalytics
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
class Day
|
30
|
-
attr_reader :day, :total
|
31
|
-
def initialize(day, total)
|
32
|
-
@day, @total = day, total
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
class Histogram
|
37
|
-
attr_reader :bars, :from_date, :to_date
|
38
|
-
|
39
|
-
def initialize(scope, from_date, to_date)
|
40
|
-
@bars = scope.map { |day| Bar.new(day.day, day.total, self) }
|
41
|
-
fill_missing_days(@bars, Date.parse(from_date.to_s), Date.parse(to_date.to_s))
|
42
|
-
end
|
43
|
-
|
44
|
-
def fill_missing_days(bars, from, to)
|
45
|
-
i = 0
|
46
|
-
while (day = from + i) <= to
|
47
|
-
if !@bars[i] || @bars[i].label != day
|
48
|
-
@bars.insert(i, Bar.new(day, 0, self))
|
49
|
-
end
|
50
|
-
i += 1
|
51
|
-
end
|
52
|
-
@bars
|
53
|
-
end
|
54
|
-
|
55
|
-
def max_value
|
56
|
-
@max_total ||= bars.map(&:value).max
|
57
|
-
end
|
58
|
-
|
59
|
-
def total
|
60
|
-
@bars.reduce(0) { |sum, bar| sum += bar.value }
|
61
|
-
end
|
62
|
-
|
63
|
-
class Bar
|
64
|
-
attr_reader :label, :value, :histogram
|
65
|
-
|
66
|
-
def initialize(label, value, histogram)
|
67
|
-
@label, @value, @histogram = label, value, histogram
|
68
|
-
end
|
69
|
-
|
70
|
-
def height
|
71
|
-
(value.to_f / histogram.max_value).round(2)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
29
|
def self.group_by_site
|
77
30
|
group(:site).pluck("site, SUM(total)").map do |row|
|
78
31
|
Site.new(row[0], row[1])
|
@@ -98,22 +51,32 @@ module ActiveAnalytics
|
|
98
51
|
end
|
99
52
|
|
100
53
|
def self.group_by_date
|
101
|
-
group(:date).
|
102
|
-
Day.new(row[0], row[1])
|
103
|
-
end
|
54
|
+
group(:date).select("date, sum(total) AS total")
|
104
55
|
end
|
105
56
|
|
106
57
|
def self.to_histogram
|
107
|
-
|
58
|
+
Histogram.new(self)
|
108
59
|
end
|
109
60
|
|
110
61
|
def self.append(params)
|
62
|
+
total = params.delete(:total) || 1
|
111
63
|
params[:site] = params[:site].downcase if params[:site]
|
112
|
-
params[:page] = params[:page].downcase if params[:page]
|
113
64
|
params[:referrer_path] = nil if params[:referrer_path].blank?
|
114
65
|
params[:referrer_path] = params[:referrer_path].downcase if params[:referrer_path]
|
115
66
|
params[:referrer_host] = params[:referrer_host].downcase if params[:referrer_host]
|
116
|
-
|
67
|
+
where(params).first.try(:increment!, :total, total) || create!(params.merge(total: total))
|
68
|
+
end
|
69
|
+
|
70
|
+
SLASH = "/"
|
71
|
+
|
72
|
+
def self.split_referrer(referrer)
|
73
|
+
return [nil, nil] if referrer.blank?
|
74
|
+
if (uri = URI(referrer)).host.present?
|
75
|
+
[uri.host, uri.path.presence]
|
76
|
+
else
|
77
|
+
strings = referrer.split(SLASH, 2)
|
78
|
+
[strings[0], strings[1] ? SLASH + strings[1] : nil]
|
79
|
+
end
|
117
80
|
end
|
118
81
|
end
|
119
82
|
end
|