Add page_view model (#1985)

* Add page_view model

* Uncomment DJ routes

* Add time on site max and don't update article page views every time
This commit is contained in:
Ben Halpern 2019-03-07 11:53:33 -08:00 committed by GitHub
parent a18a4523a1
commit 825aa9be91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 250 additions and 1 deletions

View file

@ -116,6 +116,34 @@ function trackCustomImpressions() {
successLinks[i].addEventListener('click', function() { trackHtmlVariantSuccess(dataBody, csrfToken) });
}
}
// page view
if (ArticleElement && tokenMeta && !isBot) {
var randomNumber = Math.floor(Math.random() * 10); // 1 in 10; Only track 1 in 10 impressions
if (!checkUserLoggedIn() && randomNumber != 1) {
return;
}
var dataBody = {
article_id: ArticleElement.dataset.articleId,
referrer: document.referrer,
user_agent: navigator.userAgent,
};
var csrfToken = tokenMeta.getAttribute('content');
trackPageView(dataBody, csrfToken);
var timeOnSiteCounter = 0;
var timeOnSiteInterval = setInterval(function(){
timeOnSiteCounter++
var ArticleElement = document.getElementById('article-body');
if (ArticleElement && checkUserLoggedIn()) {
trackFifteenSecondsOnPage(ArticleElement.dataset.articleId, csrfToken);
} else {
clearInterval(timeOnSiteInterval);
}
if ( timeOnSiteCounter > 118 ) {
clearInterval(timeOnSiteInterval);
}
}, 15000)
}
}, 1500)
}
@ -144,4 +172,28 @@ function trackHtmlVariantSuccess(dataBody, csrfToken) {
body: JSON.stringify(dataBody),
credentials: 'same-origin',
})
}
function trackPageView(dataBody, csrfToken) {
window.fetch('/page_views', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(dataBody),
credentials: 'same-origin',
})
}
function trackFifteenSecondsOnPage(articleId, csrfToken) {
window.fetch('/page_views/' + articleId, {
method: 'PATCH',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json',
},
credentials: 'same-origin',
})
}

View file

@ -0,0 +1,40 @@
class PageViewsController < ApplicationController
# No policy needed. All views are for all users
def create
if user_signed_in?
PageView.create(user_id: current_user.id,
article_id: page_view_params[:article_id],
referrer: page_view_params[:referrer],
user_agent: page_view_params[:user_agent])
else
PageView.create(counts_for_number_of_views: 10,
article_id: page_view_params[:article_id],
referrer: page_view_params[:referrer],
user_agent: page_view_params[:user_agent])
end
update_article_page_views
head :ok
end
def update
if user_signed_in?
page_view = PageView.where(article_id: params[:id], user_id: current_user.id).last
page_view.update_column(:time_tracked_in_seconds, page_view.time_tracked_in_seconds + 15)
end
head :ok
end
private
def update_article_page_views
return if Rails.env.production? && rand(5) != 1 # We don't need to update the article page views every time.
article = Article.find(page_view_params[:article_id])
new_page_views_count = article.page_views.sum(:counts_for_number_of_views)
article.update_column(:page_views_count, new_page_views_count) if new_page_views_count > article.page_views_count
end
def page_view_params
params.require(:page_view).permit(%i[article_id referrer user_agent])
end
end

View file

@ -19,6 +19,7 @@ class Article < ApplicationRecord
has_many :buffer_updates
has_many :notifications, as: :notifiable
has_many :rating_votes
has_many :page_views
validates :slug, presence: { if: :published? }, format: /\A[0-9a-z-]*\z/,
uniqueness: { scope: :user_id }

4
app/models/page_view.rb Normal file
View file

@ -0,0 +1,4 @@
class PageView < ApplicationRecord
belongs_to :user, optional: true
belongs_to :article
end

View file

@ -37,6 +37,7 @@ class User < ApplicationRecord
has_many :feedback_messages
has_many :rating_votes
has_many :html_variants, dependent: :destroy
has_many :page_views
has_many :mentor_relationships_as_mentee,
class_name: "MentorRelationship", foreign_key: "mentee_id"
has_many :mentor_relationships_as_mentor,

View file

@ -131,6 +131,7 @@ Rails.application.routes.draw do
resources :push_notification_subscriptions, only: [:create]
resources :tag_adjustments, only: [:create]
resources :rating_votes, only: [:create]
resources :page_views, only: [:create, :update]
get "/notifications/:filter" => "notifications#index"
get "/notifications/:filter/:org_id" => "notifications#index"

View file

@ -0,0 +1,15 @@
class CreatePageViews < ActiveRecord::Migration[5.1]
def change
create_table :page_views do |t|
t.bigint :user_id
t.bigint :article_id
t.integer :counts_for_number_of_views, default: 1
t.integer :time_tracked_in_seconds, default: 15
t.string :referrer
t.string :user_agent
t.timestamps
end
add_index :page_views, :user_id
add_index :page_views, :article_id
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20190227163803) do
ActiveRecord::Schema.define(version: 20190305221008) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -519,6 +519,19 @@ ActiveRecord::Schema.define(version: 20190227163803) do
t.index ["slug"], name: "index_organizations_on_slug", unique: true
end
create_table "page_views", force: :cascade do |t|
t.bigint "article_id"
t.integer "counts_for_number_of_views", default: 1
t.datetime "created_at", null: false
t.string "referrer"
t.integer "time_tracked_in_seconds", default: 15
t.datetime "updated_at", null: false
t.string "user_agent"
t.bigint "user_id"
t.index ["article_id"], name: "index_page_views_on_article_id"
t.index ["user_id"], name: "index_page_views_on_user_id"
end
create_table "podcast_episodes", id: :serial, force: :cascade do |t|
t.text "body"
t.integer "comments_count", default: 0, null: false
@ -776,6 +789,7 @@ ActiveRecord::Schema.define(version: 20190227163803) do
t.datetime "github_repos_updated_at", default: "2017-01-01 05:00:00"
t.string "github_username"
t.string "gitlab_url"
t.string "inbox_type", default: "private"
t.jsonb "language_settings", default: {}, null: false
t.datetime "last_article_at", default: "2017-01-01 05:00:00"
t.datetime "last_comment_at", default: "2017-01-01 05:00:00"

View file

@ -0,0 +1,5 @@
require "rails_helper"
RSpec.describe PageView, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,116 @@
require "rails_helper"
RSpec.describe "PageViews", type: :request do
let(:user) { create(:user, :trusted) }
let(:article) { create(:article) }
describe "POST /page_views" do
context "when user signed in" do
before do
sign_in user
end
it "creates a new page view" do
post "/page_views", params: {
page_view: {
article_id: article.id
}
}
expect(article.reload.page_views.size).to eq(1)
expect(article.reload.page_views_count).to eq(1)
expect(user.reload.page_views.size).to eq(1)
expect(PageView.last.counts_for_number_of_views).to eq(1)
end
it "sends referrer" do
post "/page_views", params: {
page_view: {
article_id: article.id,
referrer: "test"
}
}
expect(PageView.last.referrer).to eq("test")
end
it "sends user agent" do
post "/page_views", params: {
page_view: {
article_id: article.id,
user_agent: "test"
}
}
expect(PageView.last.user_agent).to eq("test")
end
end
context "when user not signed in" do
it "creates a new page view" do
post "/page_views", params: {
page_view: {
article_id: article.id
}
}
expect(article.reload.page_views.size).to eq(1)
expect(article.reload.page_views_count).to eq(10)
expect(user.reload.page_views.size).to eq(0)
expect(PageView.last.counts_for_number_of_views).to eq(10)
end
it "creates stores aggregate page views" do
post "/page_views", params: { page_view: { article_id: article.id } }
post "/page_views", params: { page_view: { article_id: article.id } }
expect(article.reload.page_views_count).to eq(20)
end
it "sends referrer" do
post "/page_views", params: {
page_view: {
article_id: article.id,
referrer: "test"
}
}
expect(PageView.last.referrer).to eq("test")
end
it "sends user agent" do
post "/page_views", params: {
page_view: {
article_id: article.id,
user_agent: "test"
}
}
expect(PageView.last.user_agent).to eq("test")
end
end
end
describe "PUT /page_views/:id" do
context "when user is signed in" do
before do
sign_in user
end
it "updates a new page view time on page by 15" do
post "/page_views", params: {
page_view: {
article_id: article.id
}
}
put "/page_views/" + article.id.to_s
expect(PageView.last.time_tracked_in_seconds).to eq(30)
end
end
context "when user is not signed in" do
it "updates a new page view time on page by 15" do
post "/page_views", params: {
page_view: {
article_id: article.id
}
}
put "/page_views/" + article.id.to_s
expect(PageView.last.time_tracked_in_seconds).to eq(15)
end
end
end
end