diff --git a/app/models/feed_event.rb b/app/models/feed_event.rb index 8135f3ada..d02fd79e0 100644 --- a/app/models/feed_event.rb +++ b/app/models/feed_event.rb @@ -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( diff --git a/app/services/articles/page_view_updater.rb b/app/services/articles/page_view_updater.rb index f5a8d69df..1e9ea0719 100644 --- a/app/services/articles/page_view_updater.rb +++ b/app/services/articles/page_view_updater.rb @@ -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 diff --git a/spec/models/feed_event_spec.rb b/spec/models/feed_event_spec.rb index a5841fd4f..acbe0954b 100644 --- a/spec/models/feed_event_spec.rb +++ b/spec/models/feed_event_spec.rb @@ -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 diff --git a/spec/services/articles/page_view_updater_spec.rb b/spec/services/articles/page_view_updater_spec.rb index a223dabb7..19d6a276d 100644 --- a/spec/services/articles/page_view_updater_spec.rb +++ b/spec/services/articles/page_view_updater_spec.rb @@ -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