FEATURE: Webhooks and Event for user being granted a badge

FEATURE: Webhooks and Event for user being granted a badge

Adding a webhook for badge revocation is left for future work as it’s relatively rare.

diff --git a/app/models/web_hook_event_type.rb b/app/models/web_hook_event_type.rb
index d3822d1..952c1c2 100644
--- a/app/models/web_hook_event_type.rb
+++ b/app/models/web_hook_event_type.rb
@@ -13,6 +13,7 @@ class WebHookEventType < ActiveRecord::Base
   NOTIFICATION = 10
   SOLVED = 11
   ASSIGN = 12
+  USER_BADGE = 13
 
   has_and_belongs_to_many :web_hooks
 
diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb
index 2249b33..c6dfd82 100644
--- a/app/services/badge_granter.rb
+++ b/app/services/badge_granter.rb
@@ -410,7 +410,7 @@ class BadgeGranter
   end
 
   def self.send_notification(user_id, username, locale, badge)
-    I18n.with_locale(notification_locale(locale)) do
+    notification = I18n.with_locale(notification_locale(locale)) do
       Notification.create!(
         user_id: user_id,
         notification_type: Notification.types[:granted_badge],
@@ -423,6 +423,10 @@ class BadgeGranter
         }.to_json
       )
     end
+
+    DiscourseEvent.trigger(:user_badge_granted, badge, user_id)
+
+    notification
   end
 
 end
diff --git a/config/initializers/012-web_hook_events.rb b/config/initializers/012-web_hook_events.rb
index 6ce6501..727119a 100644
--- a/config/initializers/012-web_hook_events.rb
+++ b/config/initializers/012-web_hook_events.rb
@@ -74,6 +74,16 @@ end
   end
 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
+end
+
 DiscourseEvent.on(:reviewable_created) do |reviewable|
   WebHook.enqueue_object_hooks(:reviewable, reviewable, :reviewable_created, reviewable.serializer)
 end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 9345ba5..f395e62 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -3562,6 +3562,9 @@ en:
         notification_event:
           name: "Notification Event"
           details: "When a user receives a notification in their feed."
+        user_badge_event:
+          name: "Badge Grant Event"
+          details: "When a user receives a badge."
         delivery_status:
           title: "Delivery Status"
           inactive: "Inactive"
diff --git a/db/fixtures/007_web_hook_event_types.rb b/db/fixtures/007_web_hook_event_types.rb
index 5cbcd3e..8bf8e93 100644
--- a/db/fixtures/007_web_hook_event_types.rb
+++ b/db/fixtures/007_web_hook_event_types.rb
@@ -59,3 +59,8 @@ WebHookEventType.seed do |b|
   b.id = WebHookEventType::ASSIGN
   b.name = "assign"
 end
+
+WebHookEventType.seed do |b|
+  b.id = WebHookEventType::USER_BADGE
+  b.name = "user_badge"
+end
diff --git a/spec/fabricators/web_hook_fabricator.rb b/spec/fabricators/web_hook_fabricator.rb
index 6d03c7b..4647251 100644
--- a/spec/fabricators/web_hook_fabricator.rb
+++ b/spec/fabricators/web_hook_fabricator.rb
@@ -102,3 +102,11 @@ Fabricator(:notification_web_hook, from: :web_hook) do
     web_hook.web_hook_event_types = [transients[:notification_hook]]
   end
 end
+
+Fabricator(:user_badge_web_hook, from: :web_hook) do
+  transient user_badge_hook: WebHookEventType.find_by(name: 'user_badge')
+
+  after_build do |web_hook, transients|
+    web_hook.web_hook_event_types = [transients[:user_badge_hook]]
+  end
+end
diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb
index 47b4c20..586458a 100644
--- a/spec/models/web_hook_spec.rb
+++ b/spec/models/web_hook_spec.rb
@@ -486,5 +486,30 @@ describe WebHook do
       payload = JSON.parse(job_args["payload"])
       expect(payload["id"]).to eq(reviewable.id)
     end
+
+    it 'should enqueue the right hooks for badge grants' do
+      Fabricate(:user_badge_web_hook)
+      badge = Fabricate(:badge)
+      badge.multiple_grant = true
+      badge.show_posts = true
+      badge.save
+
+      now = Time.now
+      freeze_time now
+
+      BadgeGranter.grant(badge, user, granted_by: admin, post_id: post.id)
+
+      job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
+      expect(job_args["event_name"]).to eq("user_badge_granted")
+      payload = JSON.parse(job_args["payload"])
+      expect(payload["badge_id"]).to eq(badge.id)
+      expect(payload["user_id"]).to eq(user.id)
+      expect(payload["granted_by_id"]).to eq(admin.id)
+      # be_within required because rounding occurs
+      expect(Time.zone.parse(payload["granted_at"]).to_f).to be_within(0.001).of(now.to_f)
+      expect(payload["post_id"]).to eq(post.id)
+
+      # Future work: revoke badge hook
+    end
   end
 end

GitHub sha: 138d4aeb

This commit appears in #9392 which was approved by eviltrout. It was merged by riking.