[Git][noosfero/noosfero][master] 4 commits: scheduler: defer off-request work
Weblate
gitlab at gitlab.com
Wed Jul 22 09:59:47 BRT 2015
Weblate pushed to branch master at Noosfero / noosfero
Commits:
cd9c0b07 by Braulio Bhavamitra at 2015-07-21T18:54:38Z
scheduler: defer off-request work
- - - - -
4aee1457 by Braulio Bhavamitra at 2015-07-21T18:54:59Z
Add analytics plugin
- - - - -
71f53f04 by Bráulio Bhavamitra at 2015-07-21T21:57:34Z
Merge branch 'analytics-plugin' into 'master'
Analytics plugin
See merge request !632
- - - - -
6b50d1d9 by Weblate at 2015-07-22T14:59:00Z
Merge remote-tracking branch 'origin/master'
- - - - -
15 changed files:
- + config/initializers/unicorn.rb
- + lib/noosfero/scheduler/defer.rb
- + plugins/analytics/controllers/profile/analytics_plugin/time_on_page_controller.rb
- + plugins/analytics/db/migrate/20150715001149_init_analytics_plugin.rb
- + plugins/analytics/lib/analytics_plugin.rb
- + plugins/analytics/lib/analytics_plugin/base.rb
- + plugins/analytics/lib/ext/profile.rb
- + plugins/analytics/locales/en.yml
- + plugins/analytics/locales/pt.yml
- + plugins/analytics/models/analytics_plugin/page_view.rb
- + plugins/analytics/models/analytics_plugin/visit.rb
- + plugins/analytics/po/pt/analytics.po
- + plugins/analytics/public/javascripts/analytics.js
- + plugins/analytics/test/functional/content_viewer_controller_test.rb
- + plugins/analytics/views/analytics_plugin/_body_ending.html.slim
Changes:
=====================================
config/initializers/unicorn.rb
=====================================
--- /dev/null
+++ b/config/initializers/unicorn.rb
@@ -0,0 +1,8 @@
+require_dependency 'scheduler/defer'
+
+if defined? Unicorn
+ ObjectSpace.each_object Unicorn::HttpServer do |s|
+ s.extend Scheduler::Defer::Unicorn
+ end
+end
+
=====================================
lib/noosfero/scheduler/defer.rb
=====================================
--- /dev/null
+++ b/lib/noosfero/scheduler/defer.rb
@@ -0,0 +1,95 @@
+# based on https://github.com/discourse/discourse/blob/master/lib/scheduler/defer.rb
+
+module Scheduler
+ module Deferrable
+ def initialize
+ # FIXME: do some other way when not using Unicorn
+ @async = (not Rails.env.test?) and defined? Unicorn
+ @queue = Queue.new
+ @mutex = Mutex.new
+ @paused = false
+ @thread = nil
+ end
+
+ def pause
+ stop!
+ @paused = true
+ end
+
+ def resume
+ @paused = false
+ end
+
+ # for test
+ def async= val
+ @async = val
+ end
+
+ def later desc = nil, &blk
+ if @async
+ start_thread unless (@thread && @thread.alive?) || @paused
+ @queue << [blk, desc]
+ else
+ blk.call
+ end
+ end
+
+ def stop!
+ @thread.kill if @thread and @thread.alive?
+ @thread = nil
+ end
+
+ # test only
+ def stopped?
+ !(@thread and @thread.alive?)
+ end
+
+ def do_all_work
+ while !@queue.empty?
+ do_work _non_block=true
+ end
+ end
+
+ private
+
+ def start_thread
+ @mutex.synchronize do
+ return if @thread && @thread.alive?
+ @thread = Thread.new do
+ while true
+ do_work
+ end
+ end
+ @thread.priority = -2
+ end
+ end
+
+ # using non_block to match Ruby #deq
+ def do_work non_block=false
+ job, desc = @queue.deq non_block
+ begin
+ job.call
+ rescue => ex
+ ExceptionNotifier.notify_exception ex, message: "Running deferred code '#{desc}'"
+ end
+ rescue => ex
+ ExceptionNotifier.notify_exception ex, message: "Processing deferred code queue"
+ end
+ end
+
+ class Defer
+
+ module Unicorn
+ def process_client client
+ ::Scheduler::Defer.pause
+ super client
+ ::Scheduler::Defer.do_all_work
+ ::Scheduler::Defer.resume
+ end
+ end
+
+ extend Deferrable
+ initialize
+ end
+
+end
=====================================
plugins/analytics/controllers/profile/analytics_plugin/time_on_page_controller.rb
=====================================
--- /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
=====================================
plugins/analytics/db/migrate/20150715001149_init_analytics_plugin.rb
=====================================
--- /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
=====================================
plugins/analytics/lib/analytics_plugin.rb
=====================================
--- /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
=====================================
plugins/analytics/lib/analytics_plugin/base.rb
=====================================
--- /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
=====================================
plugins/analytics/lib/ext/profile.rb
=====================================
--- /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
=====================================
plugins/analytics/locales/en.yml
=====================================
--- /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
+
=====================================
plugins/analytics/locales/pt.yml
=====================================
--- /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
=====================================
plugins/analytics/models/analytics_plugin/page_view.rb
=====================================
--- /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
+
=====================================
plugins/analytics/models/analytics_plugin/visit.rb
=====================================
--- /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
=====================================
plugins/analytics/po/pt/analytics.po
=====================================
--- /dev/null
+++ b/plugins/analytics/po/pt/analytics.po
@@ -0,0 +1,29 @@
+# translation of analytic.po to portuguese
+# Krishnamurti Lelis Lima Vieira Nunes <krishna at colivre.coop.br>, 2007.
+# noosfero - Brazilian Portuguese translation
+# Copyright (C) 2007,
+# Forum Brasileiro de Economia Solidaria <http://www.fbes.org.br/>
+# Copyright (C) 2007,
+# Ynternet.org Foundation <http://www.ynternet.org/>
+# This file is distributed under the same license as noosfero itself.
+# Joenio Costa <joenio at colivre.coop.br>, 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ř <michal at cihar.com>\n"
+"Language-Team: Portuguese <https://hosted.weblate.org/projects/noosfero"
+"/plugin-solr/pt/>\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"
+
=====================================
plugins/analytics/public/javascripts/analytics.js
=====================================
--- /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)
+
=====================================
plugins/analytics/test/functional/content_viewer_controller_test.rb
=====================================
--- /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
=====================================
plugins/analytics/views/analytics_plugin/_body_ending.html.slim
=====================================
--- /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()
+
View it on GitLab: https://gitlab.com/noosfero/noosfero/compare/0c90a215e85ebd3c37749589100c69fbade23c46...6b50d1d94c792d2bdbf588cd34da9b8afb787b22
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listas.softwarelivre.org/pipermail/noosfero-dev/attachments/20150722/8becf5e5/attachment-0001.html>
More information about the Noosfero-dev
mailing list