Commit 492c3d63597701e331a22aad279e182c569f03a4

Authored by Braulio Bhavamitra
1 parent 15eb68fe

analytics: measure time on page

plugins/analytics/controllers/myprofile/analytics_plugin/stats_controller.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +class AnalyticsPlugin::StatsController < MyProfileController
  2 +
  3 + no_design_blocks
  4 +
  5 + before_filter :skip_page_view
  6 +
  7 + def index
  8 + end
  9 +
  10 + protected
  11 +
  12 + def default_url_options
  13 + # avoid rails' use_relative_controller!
  14 + {use_route: '/'}
  15 + end
  16 +
  17 + def skip_page_view
  18 + @analytics_skip_page_view = true
  19 + end
  20 +
  21 +end
... ...
plugins/analytics/lib/analytics_plugin.rb
1 1 module AnalyticsPlugin
2 2  
3   - TimeOnPageUpdateInterval = 2.minutes * 1000
  3 + TimeOnPageUpdateInterval = 2.minutes
  4 + TimeOnPageUpdateIntervalMs = TimeOnPageUpdateInterval * 1000
4 5  
5 6 extend Noosfero::Plugin::ParentMethods
6 7  
... ...
plugins/analytics/lib/analytics_plugin/base.rb
... ... @@ -28,8 +28,7 @@ class AnalyticsPlugin::Base &lt; Noosfero::Plugin
28 28  
29 29 unless profile.analytics_anonymous?
30 30 # FIXME: use session.id in Rails 4
31   - session_id = Marshal.load(Base64.decode64 request['_session_id'])['session_id'] rescue nil
32   - #session_id = request.session_options[:id]
  31 + session_id = request.session_options[:id]
33 32 page_view.user = user
34 33 page_view.session_id = session_id
35 34 end
... ... @@ -40,4 +39,12 @@ class AnalyticsPlugin::Base &lt; Noosfero::Plugin
40 39 }]
41 40 end
42 41  
  42 + def control_panel_buttons
  43 + {
  44 + title: I18n.t('analytics_plugin.lib.plugin.panel_button'),
  45 + icon: 'analytics-access',
  46 + url: {controller: 'analytics_plugin/stats', action: :index}
  47 + }
  48 + end
  49 +
43 50 end
... ...
plugins/analytics/lib/ext/profile.rb
... ... @@ -13,7 +13,7 @@ end
13 13 class Profile
14 14  
15 15 def analytics_settings attrs = {}
16   - @analytics_settings ||= Noosfero::Plugin::Settings.new self, AnalyticsPlugin, attrs
  16 + @analytics_settings ||= Noosfero::Plugin::Settings.new self, ::AnalyticsPlugin, attrs
17 17 attrs.each{ |a, v| @analytics_settings.send "#{a}=", v }
18 18 @analytics_settings
19 19 end
... ...
plugins/analytics/locales/en.yml
... ... @@ -5,6 +5,13 @@ en: &amp;en
5 5 plugin:
6 6 name: 'Access tracking'
7 7 description: 'Register the access of selected profiles'
  8 + panel_button: 'Access tracking'
  9 +
  10 + views:
  11 + stats:
  12 + user: 'User'
  13 + initial_time: 'Time'
  14 + pages: 'Pages'
8 15  
9 16 en-US:
10 17 <<: *en
... ...
plugins/analytics/locales/pt.yml
... ... @@ -5,6 +5,13 @@ pt: &amp;pt
5 5 plugin:
6 6 name: 'Rastreio de accesso'
7 7 description: 'Registra o acesso de perfis selecionados'
  8 + panel_button: 'Rastreio de accesso'
  9 +
  10 + views:
  11 + stats:
  12 + user: 'Usuário'
  13 + initial_time: 'Horário'
  14 + pages: 'Páginas'
8 15  
9 16 pt-BR:
10 17 <<: *pt
... ...
plugins/analytics/models/analytics_plugin/page_view.rb
... ... @@ -25,10 +25,24 @@ class AnalyticsPlugin::PageView &lt; ActiveRecord::Base
25 25 before_validation :fill_referer_page_view, on: :create
26 26 before_validation :fill_visit, on: :create
27 27  
  28 + scope :latest, -> { order 'request_started_at DESC' }
  29 +
28 30 def request_duration
29 31 self.request_finished_at - self.request_started_at
30 32 end
31 33  
  34 + def initial_time
  35 + self.page_loaded_at || self.request_finished_at
  36 + end
  37 +
  38 + def user_last_time_seen
  39 + self.initial_time + self.time_on_page
  40 + end
  41 +
  42 + def user_on_page?
  43 + Time.now < self.user_last_time_seen + AnalyticsPlugin::TimeOnPageUpdateInterval
  44 + end
  45 +
32 46 def page_load!
33 47 self.page_loaded_at = Time.now
34 48 self.update_column :page_loaded_at, self.page_loaded_at
... ... @@ -36,10 +50,9 @@ class AnalyticsPlugin::PageView &lt; ActiveRecord::Base
36 50  
37 51 def increase_time_on_page!
38 52 now = Time.now
39   - initial_time = self.page_loaded_at || self.request_finished_at
40   - return unless now > initial_time
  53 + return unless now > self.initial_time
41 54  
42   - self.time_on_page = now - initial_time
  55 + self.time_on_page = now - self.initial_time
43 56 self.update_column :time_on_page, self.time_on_page
44 57 end
45 58  
... ... @@ -59,7 +72,7 @@ class AnalyticsPlugin::PageView &lt; ActiveRecord::Base
59 72 end
60 73  
61 74 def fill_visit
62   - self.visit = self.referer_page_view.visit if self.referer_page_view
  75 + self.visit = self.referer_page_view.visit if self.referer_page_view and self.referer_page_view.user_on_page?
63 76 self.visit ||= AnalyticsPlugin::Visit.new profile: profile
64 77 end
65 78  
... ...
plugins/analytics/models/analytics_plugin/visit.rb
... ... @@ -3,9 +3,17 @@ class AnalyticsPlugin::Visit &lt; ActiveRecord::Base
3 3 attr_accessible *self.column_names
4 4 attr_accessible :profile
5 5  
6   - default_scope -> { includes :page_views }
7   -
8 6 belongs_to :profile
9 7 has_many :page_views, class_name: 'AnalyticsPlugin::PageView', dependent: :destroy
10 8  
  9 + default_scope -> { joins(:page_views).includes :page_views }
  10 +
  11 + scope :latest, -> { order 'analytics_plugin_page_views.request_started_at DESC' }
  12 +
  13 + def first_page_view
  14 + self.page_views.first
  15 + end
  16 +
  17 + delegate :user, :initial_time, to: :first_page_view
  18 +
11 19 end
... ...
plugins/analytics/test/functional/content_viewer_controller_test.rb
... ... @@ -31,6 +31,8 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
31 31  
32 32 first_page_view = @community.page_views.order(:id).first
33 33 assert_equal @request.referer, first_page_view.referer_url
  34 + assert_equal @user, first_page_view.user
  35 + assert first_page_view.request_duration > 0 and first_page_view.request_duration < 1
34 36  
35 37 @request.env['HTTP_REFERER'] = first_url
36 38 get :view_page, profile: @community.identifier, page: @community.articles.last.path.split('/')
... ... @@ -40,9 +42,13 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
40 42 second_page_view = @community.page_views.order(:id).last
41 43 assert_equal first_page_view, second_page_view.referer_page_view
42 44  
43   - assert_equal @user, second_page_view.user
44   -
45   - assert second_page_view.request_duration > 0 and second_page_view.request_duration < 1
  45 + # another visit, the referer is set but should be ignored because
  46 + # the user didn't report to be on the page until now
  47 + @request.env['HTTP_REFERER'] = first_url
  48 + future = Time.now + 2*AnalyticsPlugin::TimeOnPageUpdateInterval
  49 + Time.stubs(:now).returns(future)
  50 + get :view_page, profile: @community.identifier, page: @community.articles.last.path.split('/')
  51 + assert_equal 2, @community.visits.count
46 52 end
47 53  
48 54 end
... ...
plugins/analytics/views/analytics_plugin/_body_ending.html.slim
1 1 javascript:
2 2 analytics.timeOnPage.baseUrl = #{url_for(controller: 'analytics_plugin/time_on_page').to_json}
3   - analytics.timeOnPage.updateInterval = #{AnalyticsPlugin::TimeOnPageUpdateInterval.to_json}
  3 + analytics.timeOnPage.updateInterval = #{AnalyticsPlugin::TimeOnPageUpdateIntervalMs.to_json}
4 4 analytics.requestId = #{request.env['action_dispatch.request_id'].to_json}
5 5 analytics.init()
6 6  
... ...
plugins/analytics/views/analytics_plugin/stats/_table.html.slim 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +
  2 +table#analytics-stats.table data-toggle='table' data-striped='true' data-sortable='true' data-icons-prefix='fa'
  3 + thead
  4 + - unless profile.analytics_anonymous?
  5 + th= t'analytics_plugin.views.stats.user'
  6 + th= t'analytics_plugin.views.stats.initial_time'
  7 + th= t'analytics_plugin.views.stats.pages'
  8 +
  9 + tbody
  10 + - profile.visits.each do |visit|
  11 + tr
  12 + td= link_to visit.user.name, visit.user.url
  13 + td
  14 + div data-toggle="tooltip" data-title='#{l visit.initial_time}'
  15 + = time_ago_in_words(visit.initial_time)
  16 + |&nbsp
  17 + = _'ago'
  18 + td
  19 + - visit.page_views.each do |page_view|
  20 + = link_to page_view.url, page_view.url
  21 + |&nbsp;
  22 + = "(#{distance_of_time_in_words page_view.time_on_page})"
  23 + |&nbsp;->&nbsp;
  24 +
  25 +javascript:
  26 + $('#analytics-stats').bootstrapTable({
  27 + striped: true,
  28 + columns: [
  29 + {sortable: true},
  30 + {sortable: true},
  31 + {sortable: true},
  32 + ],
  33 + })
  34 +
  35 + $(document).ready(function() {
  36 + $('[data-toggle="tooltip"]').tooltip()
  37 + })
  38 +
... ...
plugins/analytics/views/analytics_plugin/stats/index.html.slim 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +- content_for :head
  2 + = javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table-all.min.js'
  3 + = stylesheet_link_tag 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table.css'
  4 +
  5 += render 'table'
... ...