From cdf1589a85439076e143c80e28b75d06e1816e2c Mon Sep 17 00:00:00 2001 From: Selase Krakani <849886+s3lase@users.noreply.github.com> Date: Mon, 24 Apr 2023 20:36:40 +0000 Subject: [PATCH] FEATURE: Add support for user badge revocation webhook events (#21204) Currently, only user badge grants emit webhook events. This change extends the `user_badge` webhook to emit user badge revocation events. A new `user_badge_revoked` event has been introduced instead of relying on the existing `user_badge_removed` event. `user_badge_removed` emitted just the `badge_id` and `user_id` which aren't helpful for generating a meaningful webhook payload for revoked(deleted) user badges. The new event emits the user badge object. --- app/models/user_badge.rb | 3 +++ config/initializers/012-web_hook_events.rb | 18 ++++++++----- config/locales/client.en.yml | 4 +-- spec/initializers/web_hook_events_spec.rb | 31 ++++++++++++++++++++++ spec/models/user_badge_spec.rb | 16 +++++++++++ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 spec/initializers/web_hook_events_spec.rb diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb index dd919156314..cadac86ed43 100644 --- a/app/models/user_badge.rb +++ b/app/models/user_badge.rb @@ -48,7 +48,10 @@ class UserBadge < ActiveRecord::Base Badge.decrement_counter "grant_count", self.badge_id UserStat.update_distinct_badge_count self.user_id UserBadge.update_featured_ranks! self.user_id + + # TODO: Follow up with a deprecation notice for `user_badge_removed` DiscourseEvent.trigger(:user_badge_removed, self.badge_id, self.user_id) + DiscourseEvent.trigger(:user_badge_revoked, user_badge: self) end def self.ensure_consistency! diff --git a/config/initializers/012-web_hook_events.rb b/config/initializers/012-web_hook_events.rb index 520f4ecd6fc..cf769d96da3 100644 --- a/config/initializers/012-web_hook_events.rb +++ b/config/initializers/012-web_hook_events.rb @@ -55,12 +55,18 @@ end DiscourseEvent.on(event) { |tag| WebHook.enqueue_object_hooks(:tag, tag, event, TagSerializer) } end -%i[user_badge_granted].each do |event| - # user_badge_revoked - DiscourseEvent.on(event) do |badge, user_id| - ub = UserBadge.find_by(badge: badge, user_id: user_id) - WebHook.enqueue_object_hooks(:user_badge, ub, event, UserBadgeSerializer) - end +DiscourseEvent.on(:user_badge_granted) do |badge_id, user_id| + ub = UserBadge.find_by(badge_id: badge_id, user_id: user_id) + WebHook.enqueue_object_hooks(:user_badge, ub, :user_badge_granted, UserBadgeSerializer) +end + +DiscourseEvent.on(:user_badge_revoked) do |args| + WebHook.enqueue_object_hooks( + :user_badge, + args[:user_badge], + :user_badge_revoked, + UserBadgeSerializer, + ) end %i[reviewable_created reviewable_score_updated].each do |event| diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 9d86c7f762a..86427f2212b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -4867,8 +4867,8 @@ en: name: "User Promoted Event" details: "When a user is promoted from one trust level to another." user_badge_event: - name: "Badge Grant Event" - details: "When a user receives a badge." + name: "Badge Event" + details: "When a badge is granted or revoked." group_user_event: name: "Group User Event" details: "When a user is added or removed in a group." diff --git a/spec/initializers/web_hook_events_spec.rb b/spec/initializers/web_hook_events_spec.rb new file mode 100644 index 00000000000..1dc7634a377 --- /dev/null +++ b/spec/initializers/web_hook_events_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +RSpec.describe "Webhook event handlers" do + fab!(:user_badge) { Fabricate(:user_badge) } + fab!(:web_hook) { Fabricate(:user_badge_web_hook) } + fab!(:user) { Fabricate(:user) } + fab!(:badge) { Fabricate(:badge) } + fab!(:post) { Fabricate(:post) } + + describe "user_badge events" do + it "enqueues user_badge_granted webhook event" do + expect do + BadgeGranter.grant(badge, user, granted_by: Discourse.system_user, post_id: post.id) + end.to change { Jobs::EmitWebHookEvent.jobs.size }.by(1) + + job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first + expect(job_args["id"]).to eq(user.user_badges.last.id) + expect(job_args["event_name"]).to eq("user_badge_granted") + end + + it "enqueues user_badge_revoked webhook event" do + expect { BadgeGranter.revoke(user_badge) }.to change { Jobs::EmitWebHookEvent.jobs.size }.by( + 1, + ) + + job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first + expect(job_args["id"]).to eq(user_badge.id) + expect(job_args["event_name"]).to eq("user_badge_revoked") + end + end +end diff --git a/spec/models/user_badge_spec.rb b/spec/models/user_badge_spec.rb index 5d3e2e7a194..eb88b05a66d 100644 --- a/spec/models/user_badge_spec.rb +++ b/spec/models/user_badge_spec.rb @@ -31,6 +31,22 @@ RSpec.describe UserBadge do end end + describe "#destroy" do + it "triggers the 'user_badge_revoked' DiscourseEvent" do + user_badge = + UserBadge.create( + badge: badge, + user: user, + granted_at: Time.zone.now, + granted_by: Discourse.system_user, + ) + + event = DiscourseEvent.track(:user_badge_revoked) { user_badge.destroy! } + + expect(event).to be_present + end + end + describe "featured rank" do fab!(:user) { Fabricate(:user) } fab!(:user_badge_tl1) do