Add new cases for reaction weights to lead to more effective rankings (#16138)

* Base author reaction functionality

* Get logic more in place

* Finalize tests

* Add admin clause for new user points

* Add proper registered_at for seeded user

* Fix regsitered_at in e2e

* Add registered_at across the board in e2e tests

* Update comments

* Update spec/models/reaction_spec.rb

Co-authored-by: Jamie Gaskins <jamie@forem.com>

* Update spec/models/reaction_spec.rb

Co-authored-by: Jamie Gaskins <jamie@forem.com>

* Update spec/models/reaction_spec.rb

Co-authored-by: Jamie Gaskins <jamie@forem.com>

* Put points in constant and refactor points resave logic

Co-authored-by: Jamie Gaskins <jamie@forem.com>
This commit is contained in:
Ben Halpern 2022-01-28 12:28:18 -05:00 committed by GitHub
parent 6a69d9bc61
commit 96a935ffec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 2 deletions

View file

@ -16,6 +16,9 @@ class Reaction < ApplicationRecord
REACTABLE_TYPES = %w[Comment Article User].freeze
STATUSES = %w[valid invalid confirmed archived].freeze
# Days to ramp up new user points weight
NEW_USER_RAMPUP_DAYS_COUNT = 10
belongs_to :reactable, polymorphic: true
belongs_to :user
@ -158,9 +161,19 @@ class Reaction < ApplicationRecord
def assign_points
base_points = BASE_POINTS.fetch(category, 1.0)
# Ajust for certain states
base_points = 0 if status == "invalid"
base_points /= 2 if reactable_type == "User"
base_points *= 2 if status == "confirmed"
unless persisted? # Actions we only want to apply upon initial creation
# Author's comment reaction counts for more weight on to their own posts. (5.0 vs 1.0)
base_points *= 5 if positive_reaction_to_comment_on_own_article?
# New users will have their reaction weight gradually ramp by 0.1 from 0 to 1.0.
base_points *= new_user_adjusted_points if new_untrusted_user # New users get minimal reaction weight
end
self.points = user ? (base_points * user.reputation_modifier) : -5
end
@ -179,4 +192,18 @@ class Reaction < ApplicationRecord
def notify_slack_channel_about_vomit_reaction
Slack::Messengers::ReactionVomit.call(reaction: self)
end
def positive_reaction_to_comment_on_own_article?
BASE_POINTS.fetch(category, 1.0).positive? &&
reactable_type == "Comment" &&
reactable&.commentable&.user_id == user_id
end
def new_user_adjusted_points
((Time.current - user.registered_at).seconds.in_days / NEW_USER_RAMPUP_DAYS_COUNT)
end
def new_untrusted_user
user.registered_at > NEW_USER_RAMPUP_DAYS_COUNT.days.ago && !user.trusted? && !user.any_admin?
end
end

View file

@ -1,9 +1,9 @@
require "rails_helper"
RSpec.describe Reaction, type: :model do
let(:user) { create(:user) }
let(:user) { create(:user, registered_at: 20.days.ago) }
let(:article) { create(:article, user: user) }
let(:reaction) { build(:reaction, reactable: article) }
let(:reaction) { build(:reaction, reactable: article, user: user) }
describe "builtin validations" do
subject { build(:reaction, reactable: article, user: user) }
@ -74,12 +74,54 @@ RSpec.describe Reaction, type: :model do
expect(reaction.points).to eq(0)
end
it "assigns extra 5 points if reaction is to comment on author's post" do
comment = create(:comment, commentable: article)
comment_reaction = create(:reaction, reactable: comment, user: user)
expect(comment_reaction.points).to eq(5.0)
end
it "does not extra 5 points if reaction is to comment on author's post" do
second_user = create(:user)
second_article = create(:article, user: second_user)
comment = create(:comment, commentable: second_article)
comment_reaction = create(:reaction, reactable: comment, user: user)
expect(comment_reaction.points).to eq(1)
end
it "assigns the correct points if reaction is confirmed" do
reaction_points = reaction.points
reaction.update(status: "confirmed")
expect(reaction.points).to eq(reaction_points * 2)
end
it "assigns fractional points to new users on create" do
newish_user = create(:user, registered_at: 3.days.ago)
reaction = create(:reaction, reactable: article, user: newish_user)
expect(reaction.points).to be_within(0.1).of(0.3)
end
it "Does not assign new fractional logic on re-save" do
reaction.save
original_points = reaction.points
reaction.user.update_column(:registered_at, 7.days.ago)
reaction.save
expect(reaction.points).to eq(original_points)
end
it "assigns full points to new users who is also trusted" do
newish_user = create(:user, registered_at: 3.days.ago)
newish_user.add_role(:trusted)
create(:reaction, reactable: article, user: newish_user)
expect(reaction.points).to be_within(0.1).of(1.0)
end
it "assigns full points to new users who is admin" do
newish_user = create(:user, registered_at: 3.days.ago)
newish_user.add_role(:admin)
create(:reaction, reactable: article, user: newish_user)
expect(reaction.points).to be_within(0.1).of(1.0)
end
context "when user is trusted" do
before { reaction.user.add_role(:trusted) }

View file

@ -32,6 +32,7 @@ seeder.create_if_doesnt_exist(User, "email", "admin@forem.local") do
username: "Admin_McAdmin",
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
confirmed_at: Time.current,
registered_at: Time.current,
password: "password",
password_confirmation: "password",
saw_onboarding: true,
@ -76,6 +77,7 @@ seeder.create_if_doesnt_exist(User, "email", "trusted-user-1@forem.local") do
username: "trusted_user_1",
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
confirmed_at: Time.current,
registered_at: Time.current,
password: "password",
password_confirmation: "password",
saw_onboarding: true,
@ -120,6 +122,7 @@ seeder.create_if_doesnt_exist(User, "email", "change-password-user@forem.com") d
username: "changepassworduser",
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
confirmed_at: Time.current,
registered_at: Time.current,
password: "password",
password_confirmation: "password",
saw_onboarding: true,
@ -146,6 +149,7 @@ seeder.create_if_doesnt_exist(User, "email", "article-editor-v1-user@forem.local
username: "article_editor_v1_user",
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
confirmed_at: Time.current,
registered_at: Time.current,
password: "password",
password_confirmation: "password",
saw_onboarding: true,
@ -173,6 +177,7 @@ seeder.create_if_doesnt_exist(User, "email", "article-editor-v2-user@forem.local
username: "article_editor_v2_user",
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
confirmed_at: Time.current,
registered_at: Time.current,
password: "password",
password_confirmation: "password",
saw_onboarding: true,
@ -199,6 +204,7 @@ seeder.create_if_doesnt_exist(User, "email", "notifications-user@forem.local") d
username: "notifications_user",
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
confirmed_at: Time.current,
registered_at: Time.current,
password: "password",
password_confirmation: "password",
saw_onboarding: true,
@ -271,6 +277,7 @@ seeder.create_if_doesnt_exist(User, "email", "liquid-tags-user@forem.local") do
username: "liquid_tags_user",
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
confirmed_at: Time.current,
registered_at: Time.current,
password: "password",
password_confirmation: "password",
saw_onboarding: true,
@ -431,6 +438,7 @@ seeder.create_if_doesnt_exist(User, "email", "series-user@forem.local") do
username: "series_user",
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
confirmed_at: Time.current,
registered_at: Time.current,
password: "password",
password_confirmation: "password",
saw_onboarding: true,