FEATURE: Add `IgnoredUsersSummary` daily job (#7144)

approved

#1

FEATURE: Add IgnoredUsersSummary daily job (#7144)

  • FEATURE: Add IgnoredUsersSummary daily job

Why?

This is part of the Ability to ignore a user feature.

We want to:

  1. Send an automatic group PM that goes out to moderators
  2. When {x} users have Ignored the same user, threshold defined by a site setting, default of 5
  3. Only send this message every X days which is defined by another site setting
diff --git a/app/jobs/scheduled/ignored_users_summary.rb b/app/jobs/scheduled/ignored_users_summary.rb
new file mode 100644
index 0000000..1371091
--- /dev/null
+++ b/app/jobs/scheduled/ignored_users_summary.rb
@@ -0,0 +1,42 @@
+module Jobs
+  class IgnoredUsersSummary < Jobs::Scheduled
+    every 1.day
+
+    def execute(args)
+      return unless SiteSetting.ignore_user_enabled
+
+      params = {
+        threshold: SiteSetting.ignored_users_count_message_threshold,
+        gap_days: SiteSetting.ignored_users_message_gap_days,
+        coalesced_gap_days: SiteSetting.ignored_users_message_gap_days + 1,
+      }
+      user_ids = DB.query_single(<<~SQL, params)
+        SELECT ignored_user_id
+        FROM ignored_users
+        WHERE COALESCE(summarized_at, CURRENT_TIMESTAMP + ':coalesced_gap_days DAYS'::INTERVAL) - ':gap_days DAYS'::INTERVAL > CURRENT_TIMESTAMP
+        GROUP BY ignored_user_id
+        HAVING COUNT(ignored_user_id) >= :threshold
+      SQL
+
+      User.where(id: user_ids).find_each { |user| notify_user(user) }
+    end
+
+    private
+
+    def notify_user(user)
+      params = SystemMessage.new(user).defaults.merge(ignores_threshold: SiteSetting.ignored_users_count_message_threshold)
+      title = I18n.t("system_messages.ignored_users_summary.subject_template")
+      raw = I18n.t("system_messages.ignored_users_summary.text_body_template", params)
+
+      PostCreator.create(
+        Discourse.system_user,
+        target_group_names: Group[:moderators].name,
+        archetype: Archetype.private_message,
+        subtype: TopicSubtype.system_message,
+        title: title,
+        raw: raw,
+        skip_validations: true)
+      IgnoredUser.where(ignored_user_id: user.id).update_all(summarized_at: Time.zone.now)
+    end
+  end
+end
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 224d94f..b43594a 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1823,6 +1823,10 @@ en:
 
     ignore_user_enabled: "[Beta] Allow ignoring users."
 
+    ignored_users_count_message_threshold: "Notify moderators if a particular user is ignored by this many other users."
+
+    ignored_users_message_gap_days: "How long wait before notifying moderators again about a user who has been ignored by many others"
+
     user_website_domains_whitelist: "User website will be verified against these domains. Pipe-delimited list."
 
     allow_profile_backgrounds: "Allow users to upload profile backgrounds."
@@ -2829,6 +2833,18 @@ en:
       %{raw}
       `‍``
 
+    ignored_users_summary:
+      title: "Ignored User passed threshold"
+      subject_template: "A user is being ignored by many other users"
+      text_body_template: |
+        Hello,
+
+        This is an automated message from %{site_name} to inform you about a potentially problematic user who has been ignored by %{ignores_threshold} users. It would be smart to review their forum activity.
+
+        Please check the [user's profile](%{base_url}/u/%{username}/summary).
+
+        For additional guidance, please refer to our [community guidelines](%{base_url}/guidelines).
+
     too_many_spam_flags:
       title: "Too Many Spam Flags"
       subject_template: "New account on hold"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 123da3e..a2c4f31 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -532,6 +532,14 @@ users:
   ignore_user_enabled:
     default: false
     client: true
+  ignored_users_count_message_threshold:
+    default: 5
+    client: true
+    min: 1
+  ignored_users_message_gap_days:
+    default: 365
+    client: true
+    min: 1
 
 groups:
   enable_group_directory:
diff --git a/db/migrate/20190314144755_add_summarized_at_column_to_ignored_users_table.rb b/db/migrate/20190314144755_add_summarized_at_column_to_ignored_users_table.rb
new file mode 100644
index 0000000..6acb32c
--- /dev/null
+++ b/db/migrate/20190314144755_add_summarized_at_column_to_ignored_users_table.rb
@@ -0,0 +1,5 @@
+class AddSummarizedAtColumnToIgnoredUsersTable < ActiveRecord::Migration[5.2]
+  def change
+    add_column :ignored_users, :summarized_at, :datetime
+  end
+end
diff --git a/spec/fabricators/post_custom_field_fabricator.rb b/spec/fabricators/post_custom_field_fabricator.rb
new file mode 100644
index 0000000..1c764dc
--- /dev/null
+++ b/spec/fabricators/post_custom_field_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:post_custom_field) do
+  post
+  name { sequence(:key) { |i| "key#{i}" } }
+  value "test value"
+end
diff --git a/spec/jobs/ignored_users_summary_spec.rb b/spec/jobs/ignored_users_summary_spec.rb
new file mode 100644
index 0000000..683e10c
--- /dev/null
+++ b/spec/jobs/ignored_users_summary_spec.rb
@@ -0,0 +1,77 @@
+require 'rails_helper'
+
+require_dependency 'jobs/scheduled/ignored_users_summary'
+
+describe Jobs::IgnoredUsersSummary do
+  before do
+    SiteSetting.ignore_user_enabled = true
+    SiteSetting.ignored_users_count_message_threshold = 1
+    SiteSetting.ignored_users_message_gap_days = 365
+  end
+
+  subject { Jobs::IgnoredUsersSummary.new.execute({}) }
+
+  context "with no ignored users" do
+    it "does nothing" do
+      subject
+      expect { subject }.to_not change { Post.count }
+    end
+  end
+
+  context "when some ignored users exist" do
+    let(:tarek) { Fabricate(:user, username: "tarek") }
+    let(:matt) { Fabricate(:user, username: "matt") }
+    let(:john) { Fabricate(:user, username: "john") }
+
+    before do
+      Fabricate(:ignored_user, user: tarek, ignored_user: matt)
+      Fabricate(:ignored_user, user_id: tarek.id, ignored_user_id: john.id)
+    end
+
+    context "when no system message exists for the ignored users" do
+      context "when threshold is not hit" do
+        before do
+          SiteSetting.ignored_users_count_message_threshold = 5
+        end
+
+        it "does nothing" do
+          subject
+          expect { subject }.to_not change { Post.count }
+        end
+      end
+
+      context "when threshold is hit" do
+        it "creates a system message" do
+          subject
+          posts = Post.joins(:topic).where(topics: {
+            archetype: Archetype.private_message,
+            subtype: TopicSubtype.system_message
+          })
+          expect(posts.count).to eq(2)
+          expect(posts[0].raw).to include(matt.username)
+          expect(posts[1].raw).to include(john.username)
+        end
+      end
+    end
+
+    context "when a system message already exists for the ignored users" do
+      context "when threshold is not hit" do
+        before do
+          SiteSetting.ignored_users_count_message_threshold = 5
+        end
+
+        it "does nothing" do
+          subject
+          expect { subject }.to_not change { Post.count }
+        end
+      end
+
+      context "when threshold is hit" do
+        it "does nothing" do
+          subject
+          expect { subject }.to_not change { Post.count }
+        end
+      end
+    end
+  end
+end

GitHub sha: bd6d31c9


Approved #2