Allow admin to declare whether to include the badge description in message (#20684)

* Allow admin to declare whether to include the badge description in badge achievement message

* Update spec/services/badges/award_spec.rb

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Add proper acceptance to tests

* Tweak tests

* Switch to kwargs

* Clean up test acceptance

* Change interface

* Update spec/requests/admin/badge_achievements_spec.rb

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update app/services/badges/award.rb

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Adjust interface

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Ben Halpern 2024-02-28 11:02:58 -05:00 committed by GitHub
parent 585bea212d
commit 78f9bec3e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 108 additions and 28 deletions

View file

@ -32,9 +32,14 @@ module Admin
end
usernames = permitted_params[:usernames].downcase.split(/\s*,\s*/)
include_default_description = permitted_params[:include_default_description] == "1"
message = permitted_params[:message_markdown].presence ||
I18n.t("admin.badge_achievements_controller.congrats", community: ::Settings::Community.community_name)
BadgeAchievements::BadgeAwardWorker.perform_async(usernames, permitted_params[:badge], message)
BadgeAchievements::BadgeAwardWorker
.perform_async(usernames,
permitted_params[:badge],
message,
include_default_description)
flash[:success] = I18n.t("admin.badge_achievements_controller.rewarded")
redirect_to admin_badge_achievements_path
@ -46,7 +51,7 @@ module Admin
private
def permitted_params
params.permit(:usernames, :badge, :message_markdown)
params.permit(:usernames, :badge, :message_markdown, :include_default_description)
end
end
end

View file

@ -1,6 +1,6 @@
module Badges
class Award
def self.call(user_relation, slug, message_markdown)
def self.call(user_relation, slug, message_markdown, include_default_description: true)
return unless (badge_id = Badge.id_for_slug(slug))
user_relation.find_each do |user|
@ -9,6 +9,7 @@ module Badges
achievement = user.badge_achievements.create(
badge_id: badge_id,
rewarding_context_message_markdown: message_markdown,
include_default_description: include_default_description,
)
user.touch if achievement.persisted?
end

View file

@ -6,6 +6,7 @@ module Badges
return unless (badge_id = Badge.id_for_slug(BADGE_SLUG))
Article.joins(:user)
.published
.where("articles.published_at > ?", 1.week.ago)
.where("articles.published_at < ?", 1.hour.ago)
.where("articles.score >= ?", 0)

View file

@ -27,6 +27,7 @@ module Notifications
attr_reader :badge_achievement
def json_data
description = badge_achievement.include_default_description ? badge_achievement.badge.description : nil
{
user: user_data(badge_achievement.user),
badge_achievement: {
@ -34,7 +35,7 @@ module Notifications
rewarding_context_message: badge_achievement.rewarding_context_message,
badge: {
title: badge_achievement.badge.title,
description: badge_achievement.badge.description,
description: description,
badge_image_url: badge_achievement.badge.badge_image_url,
credits_awarded: badge_achievement.badge.credits_awarded
}

View file

@ -19,10 +19,17 @@
<div class="crayons-field">
<%= f.label :message_markdown, "Override Default Message", class: "crayons-field__label" %>
<p class="crayons-field__description">
Supports Markdown
Supports Markdown. This overrides the "message" sent in notifications and emails, it does not override the badge description.
</p>
<%= f.text_area :message_markdown, size: "40x3", class: "crayons-textfield" %>
</div>
<div class="crayons-field crayons-field--checkbox pb-2 pl-1">
<%= f.check_box :include_default_description, checked: true %>
<%= f.label :include_default_description, "Include Default Description", class: "crayons-field__label pt-2" %>
</div>
<p class="crayons-field__description">
If checked, the default badge <em>description</em> will be included above the message. If unchecked, only the message will be included.
</p>
<div>
<%= f.submit "Award Badges", class: "c-btn c-btn--primary" %>
</div>

View file

@ -5,9 +5,12 @@
<p style="text-align:center;padding:20px 0px">
<img src="<%= @badge.badge_image_url %>" style="height:200px" alt="<%= @badge.title %> badge" loading="lazy" />
</p>
<p>
<%= @badge.description %>
</p>
<% if @badge_achievement.include_default_description %>
<p>
<%= @badge.description %>
</p>
<% end %>
<% if @badge_achievement.rewarding_context_message.present? %>
<p>
@ -21,7 +24,7 @@
<p>
<a
href="<%= user_url(@user) %>"
style="font-weight:bold;color:white;background:#4e57ef;padding: 5px 10px;border-radius:3px;text-decoration:none">
style="font-weight:bold;color:white;background:#4e57ef;padding: 12px 15px;border-radius:6px;text-decoration:none">
Check out your profile
</a>
</p>

View file

@ -1,6 +1,6 @@
Congratulations, <%= @user.name %>! You got the <%= @badge.title %> badge! Be sure to check out your profile.
<%= @badge.description %>
<%= @badge.description if @badge_achievement.include_default_description %>
<%= strip_tags @badge_achievement.rewarding_context_message %>

View file

@ -4,7 +4,9 @@
<h3 class="fw-normal fs-l s:fs-xl m:fs-2xl">
<%= t("views.notifications.badge.heading_html", badge: tag.strong(sanitize(json_data["badge_achievement"]["badge"]["title"]), class: "fw-bold")) %>
</h3>
<p class="fs-l"><%= json_data["badge_achievement"]["badge"]["description"] %></p>
<% if json_data["badge_achievement"]["badge"]["description"] %>
<p class="fs-l"><%= json_data["badge_achievement"]["badge"]["description"] %></p>
<% end %>
</header>
<div class="crayons-card crayons-card--secondary p-4 w-100 grid gap-2">

View file

@ -4,7 +4,7 @@ module BadgeAchievements
sidekiq_options queue: :high_priority, retry: 10
def perform(usernames, badge_slug, message)
def perform(usernames, badge_slug, message, include_default_description = true) # rubocop:disable Style/OptionalBooleanParameter
if (award_class = "Badges::#{badge_slug.classify}".safe_constantize)
award_class.call
else
@ -12,6 +12,7 @@ module BadgeAchievements
User.where(username: usernames),
badge_slug,
message,
include_default_description: include_default_description,
)
end
end

View file

@ -0,0 +1,5 @@
class AddIncludeBadgeDescriptionInEmail < ActiveRecord::Migration[7.0]
def change
add_column :badge_achievements, :include_default_description, :boolean, default: true, null: false
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_02_12_142953) do
ActiveRecord::Schema[7.0].define(version: 2024_02_27_181740) do
# These are extensions that must be enabled in order to support this database
enable_extension "citext"
enable_extension "ltree"
@ -200,6 +200,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_02_12_142953) do
create_table "badge_achievements", force: :cascade do |t|
t.bigint "badge_id", null: false
t.datetime "created_at", precision: nil, null: false
t.boolean "include_default_description", default: true, null: false
t.bigint "rewarder_id"
t.text "rewarding_context_message"
t.text "rewarding_context_message_markdown"

View file

@ -46,7 +46,7 @@ RSpec.describe "/admin/content_manager/badge_achievements" do
message_markdown: "you got a badge nice one"
}
expect(BadgeAchievements::BadgeAwardWorker).to have_received(:perform_async).with(
usernames_array, badge.slug, "you got a badge nice one"
usernames_array, badge.slug, "you got a badge nice one", false
)
expect(request.flash[:success]).to include("Badges are being rewarded. The task will finish shortly.")
end
@ -60,7 +60,10 @@ RSpec.describe "/admin/content_manager/badge_achievements" do
message_markdown: "Hinder me? Thou fool. No living man may hinder me!"
}
expect(BadgeAchievements::BadgeAwardWorker).to have_received(:perform_async).with(
usernames_array, badge.slug, "Hinder me? Thou fool. No living man may hinder me!"
usernames_array,
badge.slug,
"Hinder me? Thou fool. No living man may hinder me!",
false,
)
expect(request.flash[:success]).to include("Badges are being rewarded. The task will finish shortly.")
end
@ -72,9 +75,43 @@ RSpec.describe "/admin/content_manager/badge_achievements" do
usernames: usernames_string,
message_markdown: ""
}
expect(BadgeAchievements::BadgeAwardWorker).to have_received(:perform_async).with(usernames_array,
badge.slug,
expected_message)
expect(BadgeAchievements::BadgeAwardWorker)
.to have_received(:perform_async).with(usernames_array,
badge.slug,
expected_message,
false)
expect(request.flash[:success]).to include("Badges are being rewarded. The task will finish shortly.")
end
it "includes default description if passed as true" do
allow(BadgeAchievements::BadgeAwardWorker).to receive(:perform_async)
post admin_badge_achievements_award_badges_path, params: {
badge: badge.slug,
usernames: usernames_string,
message_markdown: "",
include_default_description: "1"
}
expect(BadgeAchievements::BadgeAwardWorker)
.to have_received(:perform_async).with(usernames_array,
badge.slug,
expected_message,
true)
expect(request.flash[:success]).to include("Badges are being rewarded. The task will finish shortly.")
end
it "does not include default description if passed as false" do
allow(BadgeAchievements::BadgeAwardWorker).to receive(:perform_async)
post admin_badge_achievements_award_badges_path, params: {
badge: badge.slug,
usernames: usernames_string,
message_markdown: "",
include_default_description: "0"
}
expect(BadgeAchievements::BadgeAwardWorker)
.to have_received(:perform_async).with(usernames_array,
badge.slug,
expected_message,
false)
expect(request.flash[:success]).to include("Badges are being rewarded. The task will finish shortly.")
end
@ -83,8 +120,11 @@ RSpec.describe "/admin/content_manager/badge_achievements" do
usernames: usernames_string,
message_markdown: ""
}
expect(BadgeAchievements::BadgeAwardWorker).not_to have_received(:perform_async).with(usernames_array,
badge.slug, "")
expect(BadgeAchievements::BadgeAwardWorker)
.not_to have_received(:perform_async).with(usernames_array,
badge.slug,
"",
false)
end
it "does not award a badge if the username provided is not lowercase" do
@ -93,8 +133,11 @@ RSpec.describe "/admin/content_manager/badge_achievements" do
usernames: user.username.upcase,
message_markdown: ""
}
expect(BadgeAchievements::BadgeAwardWorker).not_to have_received(:perform_async).with(user.username.upcase,
badge.slug, "")
expect(BadgeAchievements::BadgeAwardWorker)
.not_to have_received(:perform_async).with(user.username.upcase,
badge.slug,
"",
false)
end
end

View file

@ -8,21 +8,31 @@ RSpec.describe Badges::Award, type: :service do
it "awards badges" do
expect do
described_class.call(User.all, "one-year-club", "Congrats on a badge!")
described_class.call(User.all, "one-year-club", "Congrats on a badge!", include_default_description: true)
end.to change(BadgeAchievement, :count).by(2)
end
it "creates correct badge achievements" do
described_class.call(User.all, "one-year-club", "Congrats on a badge!")
described_class.call(User.all, "one-year-club", "Congrats on a badge!", include_default_description: true)
expect(user.badge_achievements.pluck(:badge_id)).to eq([badge.id])
expect(user2.badge_achievements.pluck(:rewarding_context_message_markdown))
.to eq(["Congrats on a badge!"])
end
it "creates correct badge achievements without default description" do
described_class.call(User.all, "one-year-club", "Congrats on a badge!", include_default_description: false)
expect(user.badge_achievements.pluck(:include_default_description)).to eq([false])
end
it "creates correct badge achievements with default description" do
described_class.call(User.all, "one-year-club", "Congrats on a badge!", include_default_description: true)
expect(user.badge_achievements.pluck(:include_default_description)).to eq([true])
end
it "doesn't award badges to spam accounts" do
spammer = create(:user, username: "spam_account")
described_class.call(User.all, "one-year-club", "Congrats on a badge!")
described_class.call(User.all, "one-year-club", "Congrats on a badge!", include_default_description: true)
expect(user.badge_achievements.any?).to be true
expect(spammer.badge_achievements.any?).to be false
end

View file

@ -15,16 +15,16 @@ RSpec.describe BadgeAchievements::BadgeAwardWorker, type: :worker do
it "sends badge email" do
relation = User.none
allow(User).to receive(:where).with(username: ["jess"]).and_return(relation)
worker.perform(["jess"], "test", "yo")
worker.perform(["jess"], "test", "yo", false)
expect(Badges::Award).to have_received(:call).with(relation, "test", "yo")
expect(Badges::Award).to have_received(:call).with(relation, "test", "yo", include_default_description: false)
end
end
context "with predefined badge_slug" do
it "sends badge email" do
allow(Badges::AwardYearlyClub).to receive(:call)
worker.perform([], "award_yearly_club", "")
worker.perform([], "award_yearly_club", "", true)
expect(Badges::AwardYearlyClub).to have_received(:call)
expect(Badges::Award).not_to have_received(:call)