Feature/Fix: Flagged posts user notifications (#8041)

Feature/Fix: Flagged posts user notifications (#8041)

  • FIX: User should get notified when a post is deleted

  • FEATURE: Notify posters when restoring flagged posts

  • Fix typo

Co-Authored-By: Régis Hanol regis@hanol.fr

  • Improve tests
diff --git a/app/models/reviewable_flagged_post.rb b/app/models/reviewable_flagged_post.rb
index 009c2a7..e523ded 100644
--- a/app/models/reviewable_flagged_post.rb
+++ b/app/models/reviewable_flagged_post.rb
@@ -198,6 +198,7 @@ class ReviewableFlaggedPost < Reviewable
 
     # Undo hide/silence if applicable
     if post&.hidden?
+      notify_poster(performed_by)
       post.unhide!
       UserSilencer.unsilence(post.user) if UserSilencer.was_silenced_for?(post)
     end
@@ -210,30 +211,26 @@ class ReviewableFlaggedPost < Reviewable
 
   def perform_delete_and_ignore(performed_by, args)
     result = perform_ignore(performed_by, args)
-    PostDestroyer.new(performed_by, post).destroy
+    destroyer(performed_by, post).destroy
     result
   end
 
   def perform_delete_and_ignore_replies(performed_by, args)
     result = perform_ignore(performed_by, args)
-
-    reply_ids = post.reply_ids(Guardian.new(performed_by), only_replies_to_single_post: false)
-    replies = Post.where(id: reply_ids.map { |r| r[:id] })
-    PostDestroyer.new(performed_by, post).destroy
-    replies.each { |reply| PostDestroyer.new(performed_by, reply).destroy }
+    PostDestroyer.delete_with_replies(performed_by, post, self)
 
     result
   end
 
   def perform_delete_and_agree(performed_by, args)
     result = agree(performed_by, args)
-    PostDestroyer.new(performed_by, post).destroy
+    destroyer(performed_by, post).destroy
     result
   end
 
   def perform_delete_and_agree_replies(performed_by, args)
     result = agree(performed_by, args)
-    PostDestroyer.delete_with_replies(performed_by, post)
+    PostDestroyer.delete_with_replies(performed_by, post, self)
     result
   end
 
@@ -283,6 +280,25 @@ protected
     end
   end
 
+private
+
+  def destroyer(performed_by, post)
+    PostDestroyer.new(performed_by, post, reviewable: self)
+  end
+
+  def notify_poster(performed_by)
+    return unless performed_by.human? && performed_by.staff?
+
+    Jobs.enqueue(
+      :send_system_message,
+      user_id: post.user_id,
+      message_type: :flags_disagreed,
+      message_options: {
+        flagged_post_raw_content: post.raw,
+        url: post.url
+      }
+    )
+  end
 end
 
 # == Schema Information
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index caf705c..e1d5a55 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -2555,7 +2555,23 @@ en:
         The community flagged this post and now it is hidden. **Because this post has been hidden more than once, your post will now remain hidden until it is handled by a staff member.**
 
         For additional guidance, please refer to our [community guidelines](%{base_url}/guidelines).
+    
+    flags_disagreed:
+      title: "Flagged post restored by staff"
+      subject_template: "Flagged post restored by staff"
+      text_body_template: |
+        Hello,
+
+        This is an automated message from %{site_name} to let you know that [your post](%{base_url}%{url}) was restored.
+
+        This post was flagged by the community and a staff member opted to restore it.
 
+        [details="Click to expand restored post"]
+        `‍`` markdown
+        %{flagged_post_raw_content}
+        `‍``
+        [/details]
+  
     flags_agreed_and_post_deleted:
       title: "Flagged post removed by staff"
       subject_template: "Flagged post removed by staff"
diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb
index 228e5ad..4851752 100644
--- a/lib/post_destroyer.rb
+++ b/lib/post_destroyer.rb
@@ -39,10 +39,10 @@ class PostDestroyer
     end
   end
 
-  def self.delete_with_replies(performed_by, post)
+  def self.delete_with_replies(performed_by, post, reviewable = nil)
     reply_ids = post.reply_ids(Guardian.new(performed_by), only_replies_to_single_post: false)
     replies = Post.where(id: reply_ids.map { |r| r[:id] })
-    PostDestroyer.new(performed_by, post).destroy
+    PostDestroyer.new(performed_by, post, reviewable: reviewable).destroy
     replies.each { |reply| PostDestroyer.new(performed_by, reply).destroy }
   end
 
@@ -156,12 +156,10 @@ class PostDestroyer
       TopicUser.update_post_action_cache(post_id: @post.id)
 
       DB.after_commit do
-        if reviewable = @post.reviewable_flag
-          if @opts[:defer_flags]
-            ignore(reviewable)
-          else
-            agree(reviewable)
-          end
+        if @opts[:reviewable]
+          notify_deletion(@opts[:reviewable])
+        elsif reviewable = @post.reviewable_flag
+          @opts[:defer_flags] ? ignore(reviewable) : agree(reviewable)
         end
       end
     end
@@ -263,23 +261,7 @@ class PostDestroyer
   end
 
   def agree(reviewable)
-    if @user.human? && @user.staff? && rs = reviewable.reviewable_scores.order('created_at DESC').first
-      Jobs.enqueue(
-        :send_system_message,
-        user_id: @post.user_id,
-        message_type: :flags_agreed_and_post_deleted,
-        message_options: {
-          flagged_post_raw_content: @post.raw,
-          url: @post.url,
-          flag_reason: I18n.t(
-            "flag_reasons.#{PostActionType.types[rs.reviewable_score_type]}",
-            locale: SiteSetting.default_locale,
-            base_path: Discourse.base_path
-          )
-        }
-      )
-    end
-
+    notify_deletion(reviewable)
     result = reviewable.perform(@user, :agree_and_keep, post_was_deleted: true)
     reviewable.transition_to(result.transition_to, @user)
   end
@@ -289,6 +271,26 @@ class PostDestroyer
     reviewable.transition_to(:ignored, @user)
   end
 
+  def notify_deletion(reviewable)
+    allowed_user = @user.human? && @user.staff?
+    return unless allowed_user && rs = reviewable.reviewable_scores.order('created_at DESC').first
+
+    Jobs.enqueue(
+      :send_system_message,
+      user_id: @post.user_id,
+      message_type: :flags_agreed_and_post_deleted,
+      message_options: {
+        flagged_post_raw_content: @post.raw,
+        url: @post.url,
+        flag_reason: I18n.t(
+          "flag_reasons.#{PostActionType.types[rs.reviewable_score_type]}",
+          locale: SiteSetting.default_locale,
+          base_path: Discourse.base_path
+        )
+      }
+    )
+  end
+
   def trash_user_actions
     UserAction.where(target_post_id: @post.id).each do |ua|
       row = {
diff --git a/spec/models/reviewable_flagged_post_spec.rb b/spec/models/reviewable_flagged_post_spec.rb
index 7df51b1..d060ac3 100644
--- a/spec/models/reviewable_flagged_post_spec.rb
+++ b/spec/models/reviewable_flagged_post_spec.rb
@@ -273,4 +273,35 @@ RSpec.describe ReviewableFlaggedPost, type: :model do
 
   end
 
+  describe "#perform_delete_and_agree" do
+    it "notifies the user about the flagged post deletion" do
+      reviewable = Fabricate(:reviewable_flagged_post)
+      reviewable.add_score(
+        moderator, PostActionType.types[:spam],
+        created_at: reviewable.created_at
+      )
+
+      reviewable.perform(moderator, :delete_and_agree)
+
+      assert_pm_creation_enqueued(reviewable.post.user_id, "flags_agreed_and_post_deleted")
+    end
+  end
+
+  describe "#perform_disagree" do
+    it "notifies the user about the flagged post being restored" do
+      reviewable = Fabricate(:reviewable_flagged_post)
+      reviewable.post.update(hidden: true, hidden_at: Time.zone.now, hidden_reason_id: PostActionType.types[:spam])
+
+      reviewable.perform(moderator, :disagree)
+
+      assert_pm_creation_enqueued(reviewable.post.user_id, "flags_disagreed")
+    end
+  end
+
+  def assert_pm_creation_enqueued(user_id, pm_type)
+    expect(Jobs::SendSystemMessage.jobs.length).to eq(1)
+      job = Jobs::SendSystemMessage.jobs[0]
+      expect(job["args"][0]["user_id"]).to eq(user_id)
+      expect(job["args"][0]["message_type"]).to eq(pm_type)
+  end
 end

GitHub sha: e515324a

1 Like