Add feed event for extended page view (#20198)

This commit is contained in:
Ben Halpern 2023-10-02 10:28:31 +07:00 committed by GitHub
parent b67d5c4358
commit 8268a59ae5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 12 deletions

View file

@ -12,7 +12,8 @@ class FeedEvent < ApplicationRecord
impression: 0,
click: 1,
reaction: 2,
comment: 3
comment: 3,
extended_pageview: 4
}
CONTEXT_TYPE_HOME = "home".freeze
@ -25,8 +26,8 @@ class FeedEvent < ApplicationRecord
].freeze
DEFAULT_TIMEBOX = 5.minutes.freeze
REACTION_SCORE_MULTIPLIER = 5
COMMENT_SCORE_MULTIPLIER = 10
REACTION_SCORE_MULTIPLIER = 6
COMMENT_SCORE_MULTIPLIER = 12
validates :article_position, numericality: { only_integer: true, greater_than: 0 }
validates :context_type, inclusion: { in: VALID_CONTEXT_TYPES }, presence: true
@ -35,7 +36,7 @@ class FeedEvent < ApplicationRecord
validates :user_id, numericality: { only_integer: true }, allow_nil: true
def self.record_journey_for(user, article:, category:)
return unless %i[reaction comment].include?(category)
return unless %i[reaction comment extended_pageview].include?(category)
last_click = where(user: user, category: :click).last
return unless last_click&.article_id == article.id
@ -67,19 +68,22 @@ class FeedEvent < ApplicationRecord
clicks = FeedEvent.where(article_id: article_id, category: "click")
reactions = FeedEvent.where(article_id: article_id, category: "reaction")
comments = FeedEvent.where(article_id: article_id, category: "comment")
pageviews = FeedEvent.where(article_id: article_id, category: "extended_pageview")
# Count the distinct users for impressions and each event type
distinct_impressions_users = impressions.distinct.pluck(:user_id)
distinct_clicks_users = clicks.distinct.pluck(:user_id)
distinct_reactions_users = reactions.distinct.pluck(:user_id)
distinct_comments_users = comments.distinct.pluck(:user_id)
distinct_pageviews_users = pageviews.distinct.pluck(:user_id)
# Calculate score based on distinct users
reactions_score = distinct_reactions_users.size * REACTION_SCORE_MULTIPLIER
clicks_score = distinct_clicks_users.size # 1x multiplier for clicks
comments_score = distinct_comments_users.size * COMMENT_SCORE_MULTIPLIER
pageviews_score = distinct_pageviews_users.size # 1x multiplier for extended pageviews
score = (clicks_score + reactions_score + comments_score).to_f / distinct_impressions_users.size
score = (clicks_score + pageviews_score + reactions_score + comments_score).to_f / distinct_impressions_users.size
# Update the article counters
Article.where(id: article_id).update_all(

View file

@ -4,6 +4,7 @@ module Articles
# @see Articles::UpdatePageViewsWorker for the sibling that's responsible for recording page
# views.
module PageViewUpdater
EXTENDED_PAGEVIEW_NUMBER = 60
# @param article_id [Integer]
# @param user_id [Integer]
#
@ -22,10 +23,13 @@ module Articles
page_view = PageView.order(created_at: :desc)
.find_or_create_by(article_id: article_id, user_id: user_id)
return true if page_view.new_record?
page_view.update_column(:time_tracked_in_seconds, page_view.time_tracked_in_seconds + 15)
new_time_mark = page_view.time_tracked_in_seconds + 15
page_view.update_column(:time_tracked_in_seconds, new_time_mark)
if new_time_mark == EXTENDED_PAGEVIEW_NUMBER
FeedEvent.record_journey_for(page_view.user, article: page_view.article, category: :extended_pageview)
end
true
end

View file

@ -3,6 +3,7 @@ require "rails_helper"
RSpec.describe FeedEvent do
let(:reaction_multiplier) { FeedEvent::REACTION_SCORE_MULTIPLIER }
let(:comment_multiplier) { FeedEvent::COMMENT_SCORE_MULTIPLIER }
let(:valid_categories) { %w[impression click reaction comment extended_pageview] }
describe "validations" do
it { is_expected.to belong_to(:article).optional }
@ -10,7 +11,7 @@ RSpec.describe FeedEvent do
it { is_expected.to belong_to(:user).optional }
it { is_expected.to validate_numericality_of(:user_id).only_integer.allow_nil }
it { is_expected.to define_enum_for(:category).with_values(%i[impression click reaction comment]) }
it { is_expected.to define_enum_for(:category).with_values(valid_categories) }
it { is_expected.to validate_numericality_of(:article_position).is_greater_than(0).only_integer }
it { is_expected.to validate_inclusion_of(:context_type).in_array(%w[home search tag]) }
end
@ -113,7 +114,7 @@ RSpec.describe FeedEvent do
article.reload
expect(article.feed_success_score).to eq(5.0) # One reaction by one distinct user
expect(article.feed_success_score).to eq(reaction_multiplier) # One reaction by one distinct user
expect(article.feed_impressions_count).to eq(1) # One impression
expect(article.feed_clicks_count).to eq(0) # Zero clicks
end
@ -123,13 +124,14 @@ RSpec.describe FeedEvent do
create_list(:feed_event, 4, category: "click", article: article, user: user1)
create_list(:feed_event, 3, category: "reaction", article: article, user: user2)
create_list(:feed_event, 2, category: "comment", article: article, user: user2)
create_list(:feed_event, 3, category: "extended_pageview", article: article, user: user2)
# Trigger the after_save
create(:feed_event, category: "impression", article: article, user: user2)
article.reload
expect(article.feed_success_score).to eq((1 + reaction_multiplier + comment_multiplier) / 2.0) # Calculated score
expect(article.feed_success_score).to eq((1 + 1 + reaction_multiplier + comment_multiplier) / 2.0)
expect(article.feed_clicks_count).to eq(4)
expect(article.feed_impressions_count).to eq(3)
end
@ -149,6 +151,7 @@ RSpec.describe FeedEvent do
create(:feed_event, category: "click", article: article1, user: user1)
create(:feed_event, category: "reaction", article: article1, user: user1)
create(:feed_event, category: "comment", article: article1, user: user2)
create(:feed_event, category: "extended_pageview", article: article1, user: user2)
# Create some feed events for article2
create(:feed_event, category: "impression", article: article2, user: user2)
@ -160,11 +163,11 @@ RSpec.describe FeedEvent do
article1.reload
article2.reload
expect(article1.feed_success_score).to eq((1 + reaction_multiplier + comment_multiplier) / 2.0) # Calculated score
expect(article1.feed_success_score).to eq((1 + 1 + reaction_multiplier + comment_multiplier) / 2.0)
expect(article1.feed_impressions_count).to eq(3)
expect(article1.feed_clicks_count).to eq(1)
expect(article2.feed_success_score).to eq((1 + 5) / 1.0) # Calculated score
expect(article2.feed_success_score).to eq((1 + reaction_multiplier) / 1.0) # Calculated score
expect(article2.feed_impressions_count).to eq(1)
expect(article2.feed_clicks_count).to eq(1)
end

View file

@ -29,5 +29,34 @@ RSpec.describe Articles::PageViewUpdater do
expect { method_call }.not_to change(PageView, :count)
end
end
context "when time count equals EXTENDED_PAGEVIEW_NUMBER" do
let(:article) { create(:article, user: create(:user)) }
before do
create(:feed_event, user: user, article: article, category: :click)
end
it "sends a feed event journey when it receives a page view length of 60" do
4.times do
described_class.call(article_id: article.id, user_id: user.id)
end
expect(FeedEvent.last.category).to eq("extended_pageview")
end
it "does not send feed event journey when it receives a page view length of less than 60" do
2.times do
described_class.call(article_id: article.id, user_id: user.id)
end
expect(FeedEvent.all.size).to be 1
end
it "only sends one event when it passes through the 60 range" do
8.times do
described_class.call(article_id: article.id, user_id: user.id)
end
expect(FeedEvent.all.size).to be 2
end
end
end
end