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:
parent
6a69d9bc61
commit
96a935ffec
3 changed files with 79 additions and 2 deletions
|
|
@ -16,6 +16,9 @@ class Reaction < ApplicationRecord
|
||||||
REACTABLE_TYPES = %w[Comment Article User].freeze
|
REACTABLE_TYPES = %w[Comment Article User].freeze
|
||||||
STATUSES = %w[valid invalid confirmed archived].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 :reactable, polymorphic: true
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
|
@ -158,9 +161,19 @@ class Reaction < ApplicationRecord
|
||||||
|
|
||||||
def assign_points
|
def assign_points
|
||||||
base_points = BASE_POINTS.fetch(category, 1.0)
|
base_points = BASE_POINTS.fetch(category, 1.0)
|
||||||
|
|
||||||
|
# Ajust for certain states
|
||||||
base_points = 0 if status == "invalid"
|
base_points = 0 if status == "invalid"
|
||||||
base_points /= 2 if reactable_type == "User"
|
base_points /= 2 if reactable_type == "User"
|
||||||
base_points *= 2 if status == "confirmed"
|
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
|
self.points = user ? (base_points * user.reputation_modifier) : -5
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -179,4 +192,18 @@ class Reaction < ApplicationRecord
|
||||||
def notify_slack_channel_about_vomit_reaction
|
def notify_slack_channel_about_vomit_reaction
|
||||||
Slack::Messengers::ReactionVomit.call(reaction: self)
|
Slack::Messengers::ReactionVomit.call(reaction: self)
|
||||||
end
|
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
require "rails_helper"
|
require "rails_helper"
|
||||||
|
|
||||||
RSpec.describe Reaction, type: :model do
|
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(:article) { create(:article, user: user) }
|
||||||
let(:reaction) { build(:reaction, reactable: article) }
|
let(:reaction) { build(:reaction, reactable: article, user: user) }
|
||||||
|
|
||||||
describe "builtin validations" do
|
describe "builtin validations" do
|
||||||
subject { build(:reaction, reactable: article, user: user) }
|
subject { build(:reaction, reactable: article, user: user) }
|
||||||
|
|
@ -74,12 +74,54 @@ RSpec.describe Reaction, type: :model do
|
||||||
expect(reaction.points).to eq(0)
|
expect(reaction.points).to eq(0)
|
||||||
end
|
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
|
it "assigns the correct points if reaction is confirmed" do
|
||||||
reaction_points = reaction.points
|
reaction_points = reaction.points
|
||||||
reaction.update(status: "confirmed")
|
reaction.update(status: "confirmed")
|
||||||
expect(reaction.points).to eq(reaction_points * 2)
|
expect(reaction.points).to eq(reaction_points * 2)
|
||||||
end
|
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
|
context "when user is trusted" do
|
||||||
before { reaction.user.add_role(:trusted) }
|
before { reaction.user.add_role(:trusted) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ seeder.create_if_doesnt_exist(User, "email", "admin@forem.local") do
|
||||||
username: "Admin_McAdmin",
|
username: "Admin_McAdmin",
|
||||||
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
||||||
confirmed_at: Time.current,
|
confirmed_at: Time.current,
|
||||||
|
registered_at: Time.current,
|
||||||
password: "password",
|
password: "password",
|
||||||
password_confirmation: "password",
|
password_confirmation: "password",
|
||||||
saw_onboarding: true,
|
saw_onboarding: true,
|
||||||
|
|
@ -76,6 +77,7 @@ seeder.create_if_doesnt_exist(User, "email", "trusted-user-1@forem.local") do
|
||||||
username: "trusted_user_1",
|
username: "trusted_user_1",
|
||||||
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
||||||
confirmed_at: Time.current,
|
confirmed_at: Time.current,
|
||||||
|
registered_at: Time.current,
|
||||||
password: "password",
|
password: "password",
|
||||||
password_confirmation: "password",
|
password_confirmation: "password",
|
||||||
saw_onboarding: true,
|
saw_onboarding: true,
|
||||||
|
|
@ -120,6 +122,7 @@ seeder.create_if_doesnt_exist(User, "email", "change-password-user@forem.com") d
|
||||||
username: "changepassworduser",
|
username: "changepassworduser",
|
||||||
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
||||||
confirmed_at: Time.current,
|
confirmed_at: Time.current,
|
||||||
|
registered_at: Time.current,
|
||||||
password: "password",
|
password: "password",
|
||||||
password_confirmation: "password",
|
password_confirmation: "password",
|
||||||
saw_onboarding: true,
|
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",
|
username: "article_editor_v1_user",
|
||||||
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
||||||
confirmed_at: Time.current,
|
confirmed_at: Time.current,
|
||||||
|
registered_at: Time.current,
|
||||||
password: "password",
|
password: "password",
|
||||||
password_confirmation: "password",
|
password_confirmation: "password",
|
||||||
saw_onboarding: true,
|
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",
|
username: "article_editor_v2_user",
|
||||||
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
||||||
confirmed_at: Time.current,
|
confirmed_at: Time.current,
|
||||||
|
registered_at: Time.current,
|
||||||
password: "password",
|
password: "password",
|
||||||
password_confirmation: "password",
|
password_confirmation: "password",
|
||||||
saw_onboarding: true,
|
saw_onboarding: true,
|
||||||
|
|
@ -199,6 +204,7 @@ seeder.create_if_doesnt_exist(User, "email", "notifications-user@forem.local") d
|
||||||
username: "notifications_user",
|
username: "notifications_user",
|
||||||
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
||||||
confirmed_at: Time.current,
|
confirmed_at: Time.current,
|
||||||
|
registered_at: Time.current,
|
||||||
password: "password",
|
password: "password",
|
||||||
password_confirmation: "password",
|
password_confirmation: "password",
|
||||||
saw_onboarding: true,
|
saw_onboarding: true,
|
||||||
|
|
@ -271,6 +277,7 @@ seeder.create_if_doesnt_exist(User, "email", "liquid-tags-user@forem.local") do
|
||||||
username: "liquid_tags_user",
|
username: "liquid_tags_user",
|
||||||
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
||||||
confirmed_at: Time.current,
|
confirmed_at: Time.current,
|
||||||
|
registered_at: Time.current,
|
||||||
password: "password",
|
password: "password",
|
||||||
password_confirmation: "password",
|
password_confirmation: "password",
|
||||||
saw_onboarding: true,
|
saw_onboarding: true,
|
||||||
|
|
@ -431,6 +438,7 @@ seeder.create_if_doesnt_exist(User, "email", "series-user@forem.local") do
|
||||||
username: "series_user",
|
username: "series_user",
|
||||||
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
profile_image: File.open(Rails.root.join("app/assets/images/#{rand(1..40)}.png")),
|
||||||
confirmed_at: Time.current,
|
confirmed_at: Time.current,
|
||||||
|
registered_at: Time.current,
|
||||||
password: "password",
|
password: "password",
|
||||||
password_confirmation: "password",
|
password_confirmation: "password",
|
||||||
saw_onboarding: true,
|
saw_onboarding: true,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue