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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +97 -15
  3. data/app/controllers/active_analytics/application_controller.rb +29 -6
  4. data/app/controllers/active_analytics/assets_controller.rb +36 -0
  5. data/app/controllers/active_analytics/browsers_controller.rb +27 -0
  6. data/app/controllers/active_analytics/pages_controller.rb +9 -10
  7. data/app/controllers/active_analytics/referrers_controller.rb +9 -5
  8. data/app/controllers/active_analytics/sites_controller.rb +5 -4
  9. data/app/helpers/active_analytics/browsers_helper.rb +12 -0
  10. data/app/lib/active_analytics/histogram.rb +47 -0
  11. data/app/models/active_analytics/browsers_per_day.rb +40 -0
  12. data/app/models/active_analytics/views_per_day.rb +16 -53
  13. data/app/views/active_analytics/assets/_charts.css +249 -0
  14. data/app/{assets/stylesheets/active_analytics/style.css → views/active_analytics/assets/_style.css} +36 -26
  15. data/app/views/active_analytics/assets/application.css.erb +2 -0
  16. data/app/{assets/javascripts/active_analytics → views/active_analytics/assets}/application.js +1 -3
  17. data/app/{assets/stylesheets/active_analytics → views/active_analytics/assets}/ariato.css +237 -234
  18. data/app/views/active_analytics/assets/browsers/arc.svg +1 -0
  19. data/app/views/active_analytics/assets/browsers/brave.svg +1 -0
  20. data/app/views/active_analytics/assets/browsers/chrome.svg +1 -0
  21. data/app/views/active_analytics/assets/browsers/default.svg +8 -0
  22. data/app/views/active_analytics/assets/browsers/firefox.svg +1 -0
  23. data/app/views/active_analytics/assets/browsers/internet_explorer.svg +1 -0
  24. data/app/views/active_analytics/assets/browsers/microsoft_edge.svg +1 -0
  25. data/app/views/active_analytics/assets/browsers/opera.svg +1 -0
  26. data/app/views/active_analytics/assets/browsers/safari.svg +1 -0
  27. data/app/views/active_analytics/assets/browsers/samsung_internet.svg +1 -0
  28. data/app/views/active_analytics/assets/browsers/vivaldi.svg +1 -0
  29. data/app/views/active_analytics/assets/browsers/yandex.svg +1 -0
  30. data/app/views/active_analytics/browsers/_table.html.erb +17 -0
  31. data/app/views/active_analytics/browsers/_version_table.html.erb +16 -0
  32. data/app/views/active_analytics/browsers/index.html.erb +9 -0
  33. data/app/views/active_analytics/browsers/show.html.erb +17 -0
  34. data/app/views/active_analytics/pages/_table.html.erb +6 -17
  35. data/app/views/active_analytics/pages/index.html.erb +2 -2
  36. data/app/views/active_analytics/pages/show.html.erb +7 -4
  37. data/app/views/active_analytics/referrers/_table.html.erb +9 -2
  38. data/app/views/active_analytics/referrers/index.html.erb +3 -3
  39. data/app/views/active_analytics/referrers/show.html.erb +6 -3
  40. data/app/views/active_analytics/sites/_histogram.html.erb +11 -4
  41. data/app/views/active_analytics/sites/_histogram_header.html.erb +10 -0
  42. data/app/views/active_analytics/sites/show.html.erb +9 -4
  43. data/app/views/layouts/active_analytics/_footer.html.erb +7 -7
  44. data/app/views/layouts/active_analytics/_header.html.erb +1 -3
  45. data/app/views/layouts/active_analytics/application.html.erb +5 -3
  46. data/config/routes.rb +7 -1
  47. data/db/migrate/20210303094108_create_active_analytics_views_per_days.rb +2 -3
  48. data/db/migrate/20240823150626_create_active_analytics_browsers_per_days.rb +13 -0
  49. data/lib/active_analytics/engine.rb +0 -4
  50. data/lib/active_analytics/version.rb +1 -1
  51. data/lib/active_analytics.rb +77 -3
  52. metadata +49 -12
  53. data/app/assets/stylesheets/active_analytics/application.css +0 -15
  54. data/app/assets/stylesheets/active_analytics/charts.css +0 -296
  55. /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: 8be54d2704a6f0c7141fd6c0c4d276992d13f8cd1126868a5e71d038ba4333e5
4
- data.tar.gz: 4a6eb25b26894054f9a1b669a198a6f97d2e13bdf892d8b09b5f8da5251036db
3
+ metadata.gz: a008072c9c416bc367e4952deb03036e422368fc98a1da10e0e6921a59f45a09
4
+ data.tar.gz: a7a1999f79b95ba4007280e36be378dd2c6e00885f0254db8b9a3dfbc2e67dbd
5
5
  SHA512:
6
- metadata.gz: efae89347949dc906204ee4188b278a4482e46a2575a83868c4ffc73915869c46f88517fc235c4976ebec9f7474bc26b6823066156aaa2ccffa9ffaeb1128164
7
- data.tar.gz: 4b94f1808a3ce5da5e8050fda39270fb7f65990aecb708f0dc6e54174139fa5d9a4f4c87431917be607fed4ad3580710af84b8449f290bbebb78308086ed6f25
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
- Your controllers have to call `ActiveAnalytics.record_request(request)` to record page views. The Rails way to achieve is to use a `before_action` :
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
- before_action :record_page_view
62
+ after_action :record_page_view
40
63
 
41
64
  def record_page_view
42
- # Add a condition to record only your canonical domain
43
- # and use a gem such as crawler_detect to skip bots.
44
- ActiveAnalytics.record_request(request)
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 `skip_before_action :record_page_view` in the relevant controller.
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
- Finally, just add the route to ActiveAnalytics dashboard at the desired endpoint:
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
- mount ActiveAnalytics::Engine, at: "analytics" # http://localhost:3000/analytics
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. So you have to monkey patch `ActiveAnalytics::ApplicationController` in order to inject your own mechanism. Create a file in `config/initializers/active_analytics.rb` to add a before action :
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
- # config/initializers/active_analytics.rb
63
- require_dependency "active_analytics/application_controller"
135
+ # lib/patches/active_analytics.rb
64
136
 
65
- module ActiveAnalytics
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
- If you have Devise, you can check the permission directly from routes.rb :
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 < ActionController::Base
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
- if Date.parse(params[:from]) > Date.parse(params[:to])
8
- redirect_to(params.to_unsafe_hash.merge(from: 7.days.ago.to_date, to: Date.today))
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(params[:from], params[:to])
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
- scope = ViewsPerDay.where(site: params[:site]).between_dates(params[:from], params[:to])
11
- @histogram = ViewsPerDay::Histogram.new(scope.order_by_date.group_by_date, params[:from], params[:to])
12
- @pages = scope.top(100).group_by_page
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
- dates_scopes = ViewsPerDay.between_dates(params[:from], params[:to])
17
- page_scope = dates_scopes.where(site: params[:site], page: page_from_params)
18
- @histogram = ViewsPerDay::Histogram.new(page_scope.order_by_date.group_by_date, params[:from], params[:to])
19
- @referrers = page_scope.top.group_by_referrer_site
20
-
21
- @next_pages = dates_scopes.where(referrer_host: params[:site], referrer_path: page_from_params).top(100).group_by_page
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
- scope = ViewsPerDay.where(site: params[:site]).between_dates(params[:from], params[:to])
9
- @referrers = scope.top(100).group_by_referrer_site
10
- @histogram = ViewsPerDay::Histogram.new(scope.order_by_date.group_by_date, params[:from], params[:to])
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
- scope = ViewsPerDay.where(site: params[:site], referrer_host: params[:referrer]).between_dates(params[:from], params[:to])
15
- @histogram = ViewsPerDay::Histogram.new(scope.order_by_date.group_by_date, params[:from], params[:to])
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
- scope = current_views_per_days
14
- @histogram = ViewsPerDay::Histogram.new(scope.order_by_date.group_by_date, params[:from], params[:to])
15
- @referrers = scope.top.group_by_referrer_site
16
- @pages = scope.top.group_by_page
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).pluck("date, SUM(total)").map do |row|
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
- ViewsPerDay::Histogram.new(self)
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
- find_or_create_by!(params) if where(params).update_all("total = total + 1") == 0
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