Commit 492c3d63597701e331a22aad279e182c569f03a4
1 parent
15eb68fe
Exists in
master
and in
29 other branches
analytics: measure time on page
Showing
12 changed files
with
127 additions
and
14 deletions
Show diff stats
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
plugins/analytics/lib/analytics_plugin/base.rb
| ... | ... | @@ -28,8 +28,7 @@ class AnalyticsPlugin::Base < 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 < 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: &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: &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 < 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 < 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 < 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 < 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 < 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 < 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 | + |  | |
| 17 | + = _'ago' | |
| 18 | + td | |
| 19 | + - visit.page_views.each do |page_view| | |
| 20 | + = link_to page_view.url, page_view.url | |
| 21 | + | | |
| 22 | + = "(#{distance_of_time_in_words page_view.time_on_page})" | |
| 23 | + | -> | |
| 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' | ... | ... |