diff --git a/plugins/analytics/controllers/profile/analytics_plugin/time_on_page_controller.rb b/plugins/analytics/controllers/profile/analytics_plugin/time_on_page_controller.rb new file mode 100644 index 0000000..4aa5969 --- /dev/null +++ b/plugins/analytics/controllers/profile/analytics_plugin/time_on_page_controller.rb @@ -0,0 +1,30 @@ +class AnalyticsPlugin::TimeOnPageController < ProfileController + + before_filter :skip_page_view + + def page_load + # to avoid concurrency problems with the original deferred request, also defer this + Scheduler::Defer.later do + page_view = profile.page_views.where(request_id: params[:id]).first + page_view.request = request + page_view.page_load! + end + + render nothing: true + end + + def report + page_view = profile.page_views.where(request_id: params[:id]).first + page_view.request = request + page_view.increase_time_on_page! + + render nothing: true + end + + protected + + def skip_page_view + @analytics_skip_page_view = true + end + +end diff --git a/plugins/analytics/db/migrate/20150715001149_init_analytics_plugin.rb b/plugins/analytics/db/migrate/20150715001149_init_analytics_plugin.rb new file mode 100644 index 0000000..26be9c9 --- /dev/null +++ b/plugins/analytics/db/migrate/20150715001149_init_analytics_plugin.rb @@ -0,0 +1,47 @@ +class InitAnalyticsPlugin < ActiveRecord::Migration + + def up + create_table :analytics_plugin_visits do |t| + t.integer :profile_id + end + + create_table :analytics_plugin_page_views do |t| + t.string :type + t.integer :visit_id + t.integer :track_id + t.integer :referer_page_view_id + t.string :request_id + + t.integer :user_id + t.integer :session_id + t.integer :profile_id + + t.text :url + t.text :referer_url + + t.text :user_agent + t.string :remote_ip + + t.datetime :request_started_at + t.datetime :request_finished_at + t.datetime :page_loaded_at + t.integer :time_on_page, default: 0 + + t.text :data, default: {}.to_yaml + end + add_index :analytics_plugin_page_views, :request_id + add_index :analytics_plugin_page_views, :referer_page_view_id + + add_index :analytics_plugin_page_views, :user_id + add_index :analytics_plugin_page_views, :session_id + add_index :analytics_plugin_page_views, :profile_id + add_index :analytics_plugin_page_views, :url + add_index :analytics_plugin_page_views, [:user_id, :session_id, :profile_id, :url], name: :analytics_plugin_referer_find + end + + def down + drop_table :analytics_plugin_visits + drop_table :analytics_plugin_page_views + end + +end diff --git a/plugins/analytics/lib/analytics_plugin.rb b/plugins/analytics/lib/analytics_plugin.rb new file mode 100644 index 0000000..5f03782 --- /dev/null +++ b/plugins/analytics/lib/analytics_plugin.rb @@ -0,0 +1,15 @@ +module AnalyticsPlugin + + TimeOnPageUpdateInterval = 2.minutes * 1000 + + extend Noosfero::Plugin::ParentMethods + + def self.plugin_name + I18n.t'analytics_plugin.lib.plugin.name' + end + + def self.plugin_description + I18n.t'analytics_plugin.lib.plugin.description' + end + +end diff --git a/plugins/analytics/lib/analytics_plugin/base.rb b/plugins/analytics/lib/analytics_plugin/base.rb new file mode 100644 index 0000000..b7e2562 --- /dev/null +++ b/plugins/analytics/lib/analytics_plugin/base.rb @@ -0,0 +1,43 @@ + +class AnalyticsPlugin::Base < Noosfero::Plugin + + def body_ending + return unless profile and profile.analytics_enabled? + lambda do + render 'analytics_plugin/body_ending' + end + end + + def js_files + ['analytics'].map{ |j| "javascripts/#{j}" } + end + + def application_controller_filters + [{ + type: 'around_filter', options: {}, block: -> &block do + request_started_at = Time.now + block.call + request_finished_at = Time.now + + return if @analytics_skip_page_view + return unless profile and profile.analytics_enabled? + + Scheduler::Defer.later 'analytics: register page view' do + page_view = profile.page_views.build request: request, profile_id: profile, + request_started_at: request_started_at, request_finished_at: request_finished_at + + unless profile.analytics_anonymous? + # FIXME: use session.id in Rails 4 + session_id = Marshal.load(Base64.decode64 request['_session_id'])['session_id'] rescue nil + #session_id = request.session_options[:id] + page_view.user = user + page_view.session_id = session_id + end + + page_view.save! + end + end, + }] + end + +end diff --git a/plugins/analytics/lib/ext/profile.rb b/plugins/analytics/lib/ext/profile.rb new file mode 100644 index 0000000..d026309 --- /dev/null +++ b/plugins/analytics/lib/ext/profile.rb @@ -0,0 +1,30 @@ +require_dependency 'profile' +require_dependency 'community' + +([Profile] + Profile.descendants).each do |subclass| +subclass.class_eval do + + has_many :visits, foreign_key: :profile_id, class_name: 'AnalyticsPlugin::Visit' + has_many :page_views, foreign_key: :profile_id, class_name: 'AnalyticsPlugin::PageView' + +end +end + +class Profile + + def analytics_settings attrs = {} + @analytics_settings ||= Noosfero::Plugin::Settings.new self, AnalyticsPlugin, attrs + attrs.each{ |a, v| @analytics_settings.send "#{a}=", v } + @analytics_settings + end + alias_method :analytics_settings=, :analytics_settings + + def analytics_enabled? + self.analytics_settings.enabled + end + + def analytics_anonymous? + self.analytics_settings.anonymous + end + +end diff --git a/plugins/analytics/locales/en.yml b/plugins/analytics/locales/en.yml new file mode 100644 index 0000000..ebdecd8 --- /dev/null +++ b/plugins/analytics/locales/en.yml @@ -0,0 +1,11 @@ + +en: &en + analytics_plugin: + lib: + plugin: + name: 'Access tracking' + description: 'Register the access of selected profiles' + +en-US: + <<: *en + diff --git a/plugins/analytics/locales/pt.yml b/plugins/analytics/locales/pt.yml new file mode 100644 index 0000000..86fd817 --- /dev/null +++ b/plugins/analytics/locales/pt.yml @@ -0,0 +1,10 @@ + +pt: &pt + analytics_plugin: + lib: + plugin: + name: 'Rastreio de accesso' + description: 'Registra o acesso de perfis selecionados' + +pt-BR: + <<: *pt diff --git a/plugins/analytics/models/analytics_plugin/page_view.rb b/plugins/analytics/models/analytics_plugin/page_view.rb new file mode 100644 index 0000000..1d0968a --- /dev/null +++ b/plugins/analytics/models/analytics_plugin/page_view.rb @@ -0,0 +1,67 @@ +class AnalyticsPlugin::PageView < ActiveRecord::Base + + serialize :data + + attr_accessible *self.column_names + attr_accessible :user, :profile + + attr_accessor :request + attr_accessible :request + + acts_as_having_settings field: :options + + belongs_to :visit, class_name: 'AnalyticsPlugin::Visit' + belongs_to :referer_page_view, class_name: 'AnalyticsPlugin::PageView' + + belongs_to :user, class_name: 'Person' + belongs_to :session, primary_key: :session_id, foreign_key: :session_id, class_name: 'Session' + belongs_to :profile + + validates_presence_of :visit + validates_presence_of :request, on: :create + validates_presence_of :url + + before_validation :extract_request_data, on: :create + before_validation :fill_referer_page_view, on: :create + before_validation :fill_visit, on: :create + + def request_duration + self.request_finished_at - self.request_started_at + end + + def page_load! + self.page_loaded_at = Time.now + self.update_column :page_loaded_at, self.page_loaded_at + end + + def increase_time_on_page! + now = Time.now + initial_time = self.page_loaded_at || self.request_finished_at + return unless now > initial_time + + self.time_on_page = now - initial_time + self.update_column :time_on_page, self.time_on_page + end + + protected + + def extract_request_data + self.url = self.request.url.sub /\/+$/, '' + self.referer_url = self.request.referer + self.user_agent = self.request.headers['User-Agent'] + self.request_id = self.request.env['action_dispatch.request_id'] + self.remote_ip = self.request.remote_ip + end + + def fill_referer_page_view + self.referer_page_view = AnalyticsPlugin::PageView.order('request_started_at DESC'). + where(url: self.referer_url, session_id: self.session_id, user_id: self.user_id, profile_id: self.profile_id).first if self.referer_url.present? + end + + def fill_visit + self.visit = self.referer_page_view.visit if self.referer_page_view + self.visit ||= AnalyticsPlugin::Visit.new profile: profile + end + +end + diff --git a/plugins/analytics/models/analytics_plugin/visit.rb b/plugins/analytics/models/analytics_plugin/visit.rb new file mode 100644 index 0000000..59a12cb --- /dev/null +++ b/plugins/analytics/models/analytics_plugin/visit.rb @@ -0,0 +1,11 @@ +class AnalyticsPlugin::Visit < ActiveRecord::Base + + attr_accessible *self.column_names + attr_accessible :profile + + default_scope -> { includes :page_views } + + belongs_to :profile + has_many :page_views, class_name: 'AnalyticsPlugin::PageView', dependent: :destroy + +end diff --git a/plugins/analytics/po/pt/analytics.po b/plugins/analytics/po/pt/analytics.po new file mode 100644 index 0000000..a7cf523 --- /dev/null +++ b/plugins/analytics/po/pt/analytics.po @@ -0,0 +1,29 @@ +# translation of analytic.po to portuguese +# Krishnamurti Lelis Lima Vieira Nunes , 2007. +# noosfero - Brazilian Portuguese translation +# Copyright (C) 2007, +# Forum Brasileiro de Economia Solidaria +# Copyright (C) 2007, +# Ynternet.org Foundation +# This file is distributed under the same license as noosfero itself. +# Joenio Costa , 2008. +# +# +msgid "" +msgstr "" +"Project-Id-Version: 1.0-690-gcb6e853\n" +"POT-Creation-Date: 2015-03-05 12:10-0300\n" +"PO-Revision-Date: 2015-07-21 09:23-0300\n" +"Last-Translator: Michal Čihař \n" +"Language-Team: Portuguese \n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 2.3-dev\n" + +msgid "Select the set of communities and users to track" +msgstr "Seleciona o conjunto de comunidades e usuários para rastrear" + diff --git a/plugins/analytics/public/javascripts/analytics.js b/plugins/analytics/public/javascripts/analytics.js new file mode 100644 index 0000000..f40808b --- /dev/null +++ b/plugins/analytics/public/javascripts/analytics.js @@ -0,0 +1,39 @@ +analytics = { + requestId: '', + + timeOnPage: { + updateInterval: 0, + baseUrl: '', + + report: function() { + $.ajax(analytics.timeOnPage.baseUrl+'/report', { + type: 'POST', data: {id: analytics.requestId}, + success: function(data) { + + analytics.timeOnPage.poll() + }, + }) + }, + + poll: function() { + if (analytics.timeOnPage.updateInterval) + setTimeout(analytics.timeOnPage.report, analytics.timeOnPage.updateInterval) + }, + }, + + init: function() { + analytics.timeOnPage.poll() + }, + + pageLoad: function() { + $.ajax(analytics.timeOnPage.baseUrl+'/page_load', { + type: 'POST', data: {id: analytics.requestId}, + success: function(data) { + }, + }); + } + +}; + +$(document).ready(analytics.pageLoad) + diff --git a/plugins/analytics/test/functional/content_viewer_controller_test.rb b/plugins/analytics/test/functional/content_viewer_controller_test.rb new file mode 100644 index 0000000..a19b316 --- /dev/null +++ b/plugins/analytics/test/functional/content_viewer_controller_test.rb @@ -0,0 +1,48 @@ +require 'test_helper' +require 'content_viewer_controller' + +class ContentViewerControllerTest < ActionController::TestCase + + def setup + @controller = ContentViewerController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @environment = Environment.default + @environment.enabled_plugins += ['AnalyticsPlugin'] + @environment.save! + + @user = create_user('testinguser').person + login_as @user.identifier + + @community = build Community, identifier: 'testcomm', name: 'test' + @community.analytics_settings.enabled = true + @community.analytics_settings.anonymous = false + @community.save! + @community.add_member @user + end + + should 'register page view correctly' do + @request.env['HTTP_REFERER'] = 'http://google.com' + first_url = 'http://test.host' + get :view_page, profile: @community.identifier, page: [] + assert_equal 1, @community.page_views.count + assert_equal 1, @community.visits.count + + first_page_view = @community.page_views.order(:id).first + assert_equal @request.referer, first_page_view.referer_url + + @request.env['HTTP_REFERER'] = first_url + get :view_page, profile: @community.identifier, page: @community.articles.last.path.split('/') + assert_equal 2, @community.page_views.count + assert_equal 1, @community.visits.count + + second_page_view = @community.page_views.order(:id).last + assert_equal first_page_view, second_page_view.referer_page_view + + assert_equal @user, second_page_view.user + + assert second_page_view.request_duration > 0 and second_page_view.request_duration < 1 + end + +end diff --git a/plugins/analytics/views/analytics_plugin/_body_ending.html.slim b/plugins/analytics/views/analytics_plugin/_body_ending.html.slim new file mode 100644 index 0000000..55029e1 --- /dev/null +++ b/plugins/analytics/views/analytics_plugin/_body_ending.html.slim @@ -0,0 +1,6 @@ +javascript: + analytics.timeOnPage.baseUrl = #{url_for(controller: 'analytics_plugin/time_on_page').to_json} + analytics.timeOnPage.updateInterval = #{AnalyticsPlugin::TimeOnPageUpdateInterval.to_json} + analytics.requestId = #{request.env['action_dispatch.request_id'].to_json} + analytics.init() + -- libgit2 0.21.2