New badge, Rookie of the Month, for two new high quality users.

This commit is contained in:
Robin Ward 2017-03-30 15:19:51 -04:00
parent f42b6054e6
commit 893e93dfbe
6 changed files with 152 additions and 1 deletions

View File

@ -1,5 +1,4 @@
module Jobs
class CalculateAvgTime < Jobs::Scheduled
every 1.day

View File

@ -0,0 +1,63 @@
require 'badge_granter'
module Jobs
class GrantRookieBadges < Jobs::Scheduled
every 1.month
MAX_AWARDED = 2
def execute(args)
badge = Badge.find(Badge::RookieOfTheMonth)
scores.each do |user_id, score|
# Don't bother awarding to users who haven't received any likes
if score > 0.0
BadgeGranter.grant(badge, User.find(user_id))
end
end
end
def scores
scores = {}
# Find recent accounts and come up with a score based on how many likes they
# received, based on how much they posted and how old the accounts of the people
# who voted on them are.
sql = <<~SQL
SELECT u.id,
SUM(CASE
WHEN pa.id IS NOT NULL THEN
CASE
WHEN liked_by.created_at > (CURRENT_TIMESTAMP - '1 week'::INTERVAL) THEN 0.1
WHEN liked_by.created_at > (CURRENT_TIMESTAMP - '1 month'::INTERVAL) THEN 0.5
ELSE 1.0
END
ELSE 0
END) / COUNT(DISTINCT p.id) AS score
FROM users AS u
INNER JOIN user_stats AS us ON u.id = us.user_id
LEFT OUTER JOIN posts AS p ON p.user_id = u.id
LEFT OUTER JOIN post_actions AS pa ON
pa.post_id = p.id AND pa.post_action_type_id = :like
LEFT OUTER JOIN users AS liked_by ON liked_by.id = pa.user_id
WHERE u.active AND
u.id > 0 AND
NOT(u.admin) AND
NOT(u.moderator) AND
u.created_at >= CURRENT_TIMESTAMP - '1 month'::INTERVAL
GROUP BY u.id
HAVING COUNT(p.id) > 0
ORDER BY score DESC
LIMIT :max_awarded
SQL
User.exec_sql(sql, {
like: PostActionType.types[:like],
max_awarded: MAX_AWARDED
}).each do |row|
scores[row['id'].to_i] = row['score'].to_f
end
scores
end
end
end

View File

@ -56,6 +56,8 @@ class Badge < ActiveRecord::Base
GivesBack = 32
Empathetic = 39
RookieOfTheMonth = 44
# other consts
AutobiographerMinBioLength = 10

View File

@ -3290,6 +3290,11 @@ en:
description: Replied to a Post via email
long_description: |
This badge is granted the first time you reply to a post via email :e-mail:.
rookie_of_the_month:
name: "Rookie of the Month"
description: Contributed a lot of value in their first month
long_description: |
This badge is granted to two high quality users who joined in the last month. The quality is determined by how many of their posts were liked and by whom.
admin_login:
success: "Email Sent"

View File

@ -401,6 +401,20 @@ Badge.seed do |b|
b.system = true
end
Badge.seed do |b|
b.id = Badge::RookieOfTheMonth
b.name = "Rookie of the Month"
b.badge_type_id = BadgeType::Bronze
b.multiple_grant = false
b.target_posts = false
b.show_posts = false
b.query = nil
b.badge_grouping_id = BadgeGrouping::GettingStarted
b.default_badge_grouping_id = BadgeGrouping::GettingStarted
b.trigger = Badge::Trigger::None
b.system = true
end
Badge.where("NOT system AND id < 100").each do |badge|
new_id = [Badge.maximum(:id) + 1, 100].max
old_id = badge.id

View File

@ -0,0 +1,68 @@
require 'rails_helper'
require_dependency 'jobs/scheduled/grant_rookie_badges'
describe Jobs::GrantRookieBadges do
let(:granter) { described_class.new }
it "runs correctly" do
user = Fabricate(:user, created_at: 1.week.ago)
p = Fabricate(:post, user: user)
old_user = Fabricate(:user, created_at: 6.months.ago)
PostAction.act(old_user, p, PostActionType.types[:like])
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::RookieOfTheMonth)
expect(badge).to be_present
end
describe '.scores' do
it "doesn't award it to accounts over a month old" do
user = Fabricate(:user, created_at: 2.months.ago)
Fabricate(:post, user: user)
expect(granter.scores.keys).not_to include(user.id)
end
it "returns active accounts created in the last month" do
user = Fabricate(:user, created_at: 1.week.ago)
Fabricate(:post, user: user)
expect(granter.scores.keys).to include(user.id)
end
it "likes from older accounts are scored higher" do
user = Fabricate(:user, created_at: 1.week.ago)
p = Fabricate(:post, user: user)
new_user = Fabricate(:user, created_at: 2.days.ago)
med_user = Fabricate(:user, created_at: 3.weeks.ago)
old_user = Fabricate(:user, created_at: 6.months.ago)
PostAction.act(new_user, p, PostActionType.types[:like])
PostAction.act(med_user, p, PostActionType.types[:like])
PostAction.act(old_user, p, PostActionType.types[:like])
expect(granter.scores[user.id]).to eq(1.6)
# It goes down the more they post
Fabricate(:post, user: user)
expect(granter.scores[user.id]).to eq(0.8)
end
it "is limited to two accounts" do
u1 = Fabricate(:user, created_at: 1.week.ago)
u2 = Fabricate(:user, created_at: 2.weeks.ago)
u3 = Fabricate(:user, created_at: 3.weeks.ago)
Fabricate(:post, user: u1)
Fabricate(:post, user: u2)
Fabricate(:post, user: u3)
expect(granter.scores.keys.size).to eq(2)
end
end
end