FEATURE: Add new don't feed the trolls feature (#21001)
Responding to negative behaviour tends to solicit more of the same. Common wisdom states: "don't feed the trolls". This change codifies that advice by introducing a new nudge when hitting the reply button on a flagged post. It will be shown if either the current user, or two other users (configurable via a site setting) have flagged the post.
This commit is contained in:
parent
351e3ccd98
commit
e002a24eca
|
@ -550,10 +550,14 @@ class Post < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_flagged?
|
def is_flagged?
|
||||||
|
flags.count != 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def flags
|
||||||
post_actions.where(
|
post_actions.where(
|
||||||
post_action_type_id: PostActionType.flag_types_without_custom.values,
|
post_action_type_id: PostActionType.flag_types_without_custom.values,
|
||||||
deleted_at: nil,
|
deleted_at: nil,
|
||||||
).count != 0
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reviewable_flag
|
def reviewable_flag
|
||||||
|
|
|
@ -555,6 +555,8 @@ en:
|
||||||
|
|
||||||
get_a_room: You’ve replied to @%{reply_username} %{count} times, did you know you could send them a personal message instead?
|
get_a_room: You’ve replied to @%{reply_username} %{count} times, did you know you could send them a personal message instead?
|
||||||
|
|
||||||
|
dont_feed_the_trolls: This post has already been flagged for moderator attention. Are you sure you wish to reply to it? Replies to negative content tend to encourage more negative behavior.
|
||||||
|
|
||||||
too_many_replies: |
|
too_many_replies: |
|
||||||
### You have reached the reply limit for this topic
|
### You have reached the reply limit for this topic
|
||||||
|
|
||||||
|
@ -2179,6 +2181,8 @@ en:
|
||||||
|
|
||||||
get_a_room_threshold: "Number of posts a user has to make to the same person in the same topic before being warned."
|
get_a_room_threshold: "Number of posts a user has to make to the same person in the same topic before being warned."
|
||||||
|
|
||||||
|
dont_feed_the_trolls_threshold: "Number of flags from other users before being warned."
|
||||||
|
|
||||||
enable_mobile_theme: "Mobile devices use a mobile-friendly theme, with the ability to switch to the full site. Disable this if you want to use a custom stylesheet that is fully responsive."
|
enable_mobile_theme: "Mobile devices use a mobile-friendly theme, with the ability to switch to the full site. Disable this if you want to use a custom stylesheet that is fully responsive."
|
||||||
|
|
||||||
dominating_topic_minimum_percent: "What percentage of posts a user has to make in a topic before being reminded about overly dominating a topic."
|
dominating_topic_minimum_percent: "What percentage of posts a user has to make in a topic before being reminded about overly dominating a topic."
|
||||||
|
|
|
@ -2390,6 +2390,7 @@ uncategorized:
|
||||||
educate_until_posts: 2
|
educate_until_posts: 2
|
||||||
sequential_replies_threshold: 2
|
sequential_replies_threshold: 2
|
||||||
get_a_room_threshold: 3
|
get_a_room_threshold: 3
|
||||||
|
dont_feed_the_trolls_threshold: 2
|
||||||
dominating_topic_minimum_percent: 40
|
dominating_topic_minimum_percent: 40
|
||||||
disable_avatar_education_message: false
|
disable_avatar_education_message: false
|
||||||
pm_warn_user_last_seen_months_ago: 24
|
pm_warn_user_last_seen_months_ago: 24
|
||||||
|
|
|
@ -226,6 +226,33 @@ class ComposerMessagesFinder
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_dont_feed_the_trolls
|
||||||
|
return if !replying?
|
||||||
|
|
||||||
|
post =
|
||||||
|
if @details[:post_id]
|
||||||
|
Post.find_by(id: @details[:post_id])
|
||||||
|
else
|
||||||
|
@topic.first_post
|
||||||
|
end
|
||||||
|
|
||||||
|
return if post.blank?
|
||||||
|
|
||||||
|
flags = post.flags.group(:user_id).count
|
||||||
|
flagged_by_replier = flags[@user.id].to_i > 0
|
||||||
|
flagged_by_others = flags.values.sum >= SiteSetting.dont_feed_the_trolls_threshold
|
||||||
|
|
||||||
|
return if !flagged_by_replier && !flagged_by_others
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "dont_feed_the_trolls",
|
||||||
|
templateName: "education",
|
||||||
|
wait_for_typing: false,
|
||||||
|
extraClass: "urgent",
|
||||||
|
body: PrettyText.cook(I18n.t("education.dont_feed_the_trolls")),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def check_reviving_old_topic
|
def check_reviving_old_topic
|
||||||
return unless replying?
|
return unless replying?
|
||||||
if @topic.nil? || SiteSetting.warn_reviving_old_topic_age < 1 || @topic.last_posted_at.nil? ||
|
if @topic.nil? || SiteSetting.warn_reviving_old_topic_age < 1 || @topic.last_posted_at.nil? ||
|
||||||
|
|
|
@ -328,6 +328,75 @@ RSpec.describe ComposerMessagesFinder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#dont_feed_the_trolls" do
|
||||||
|
fab!(:user) { Fabricate(:user) }
|
||||||
|
fab!(:author) { Fabricate(:user) }
|
||||||
|
fab!(:other_user) { Fabricate(:user) }
|
||||||
|
fab!(:third_user) { Fabricate(:user) }
|
||||||
|
fab!(:topic) { Fabricate(:topic, user: author) }
|
||||||
|
fab!(:original_post) { Fabricate(:post, topic: topic, user: author) }
|
||||||
|
fab!(:unflagged_post) { Fabricate(:post, topic: topic, user: author) }
|
||||||
|
fab!(:self_flagged_post) { Fabricate(:post, topic: topic, user: author) }
|
||||||
|
fab!(:under_flagged_post) { Fabricate(:post, topic: topic, user: author) }
|
||||||
|
fab!(:over_flagged_post) { Fabricate(:post, topic: topic, user: author) }
|
||||||
|
|
||||||
|
before { SiteSetting.dont_feed_the_trolls_threshold = 2 }
|
||||||
|
|
||||||
|
it "does not show a message for unflagged posts" do
|
||||||
|
finder =
|
||||||
|
ComposerMessagesFinder.new(
|
||||||
|
user,
|
||||||
|
composer_action: "reply",
|
||||||
|
topic_id: topic.id,
|
||||||
|
post_id: unflagged_post.id,
|
||||||
|
)
|
||||||
|
expect(finder.check_dont_feed_the_trolls).to be_blank
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows a message when the replier has already flagged the post" do
|
||||||
|
Fabricate(:flag, post: self_flagged_post, user: user)
|
||||||
|
finder =
|
||||||
|
ComposerMessagesFinder.new(
|
||||||
|
user,
|
||||||
|
composer_action: "reply",
|
||||||
|
topic_id: topic.id,
|
||||||
|
post_id: self_flagged_post.id,
|
||||||
|
)
|
||||||
|
expect(finder.check_dont_feed_the_trolls).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows a message when replying to flagged topic (first post)" do
|
||||||
|
Fabricate(:flag, post: original_post, user: user)
|
||||||
|
finder = ComposerMessagesFinder.new(user, composer_action: "reply", topic_id: topic.id)
|
||||||
|
expect(finder.check_dont_feed_the_trolls).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not show a message when not enough others have flagged the post" do
|
||||||
|
Fabricate(:flag, post: under_flagged_post, user: other_user)
|
||||||
|
finder =
|
||||||
|
ComposerMessagesFinder.new(
|
||||||
|
user,
|
||||||
|
composer_action: "reply",
|
||||||
|
topic_id: topic.id,
|
||||||
|
post_id: under_flagged_post.id,
|
||||||
|
)
|
||||||
|
expect(finder.check_dont_feed_the_trolls).to be_blank
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows a message when enough others have already flagged the post" do
|
||||||
|
Fabricate(:flag, post: over_flagged_post, user: other_user)
|
||||||
|
Fabricate(:flag, post: over_flagged_post, user: third_user)
|
||||||
|
finder =
|
||||||
|
ComposerMessagesFinder.new(
|
||||||
|
user,
|
||||||
|
composer_action: "reply",
|
||||||
|
topic_id: topic.id,
|
||||||
|
post_id: over_flagged_post.id,
|
||||||
|
)
|
||||||
|
expect(finder.check_dont_feed_the_trolls).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe ".check_get_a_room" do
|
describe ".check_get_a_room" do
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user) }
|
||||||
fab!(:other_user) { Fabricate(:user) }
|
fab!(:other_user) { Fabricate(:user) }
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe "Composer don't feed the trolls popup", type: :system, js: true do
|
||||||
|
fab!(:user) { Fabricate(:user) }
|
||||||
|
fab!(:troll) { Fabricate(:user) }
|
||||||
|
fab!(:topic) { Fabricate(:topic, user: user) }
|
||||||
|
fab!(:post) { Fabricate(:post, user: user, topic: topic) }
|
||||||
|
fab!(:reply) { Fabricate(:post, user: troll, topic: topic) }
|
||||||
|
fab!(:flag) { Fabricate(:flag, post: reply, user: user) }
|
||||||
|
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||||
|
|
||||||
|
before { sign_in user }
|
||||||
|
|
||||||
|
it "shows a popup when about to reply to a troll" do
|
||||||
|
SiteSetting.educate_until_posts = 0
|
||||||
|
|
||||||
|
topic_page.visit_topic(topic)
|
||||||
|
topic_page.click_post_action_button(reply, :reply)
|
||||||
|
|
||||||
|
expect(topic_page).to have_composer_popup_content(I18n.t("education.dont_feed_the_trolls"))
|
||||||
|
end
|
||||||
|
end
|
|
@ -43,6 +43,10 @@ module PageObjects
|
||||||
composer_input.value == content
|
composer_input.value == content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_popup_content?(content)
|
||||||
|
composer_popup.has_content?(content)
|
||||||
|
end
|
||||||
|
|
||||||
def select_action(action)
|
def select_action(action)
|
||||||
find(action(action)).click
|
find(action(action)).click
|
||||||
self
|
self
|
||||||
|
@ -83,6 +87,10 @@ module PageObjects
|
||||||
def composer_input
|
def composer_input
|
||||||
find("#{COMPOSER_ID} .d-editor .d-editor-input")
|
find("#{COMPOSER_ID} .d-editor .d-editor-input")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def composer_popup
|
||||||
|
find("#{COMPOSER_ID} .composer-popup")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -71,6 +71,8 @@ module PageObjects
|
||||||
case button
|
case button
|
||||||
when :bookmark
|
when :bookmark
|
||||||
post_by_number(post).find(".bookmark.with-reminder").click
|
post_by_number(post).find(".bookmark.with-reminder").click
|
||||||
|
when :reply
|
||||||
|
post_by_number(post).find(".post-controls .reply").click
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -111,6 +113,10 @@ module PageObjects
|
||||||
@composer_component.has_content?(content)
|
@composer_component.has_content?(content)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_composer_popup_content?(content)
|
||||||
|
@composer_component.has_popup_content?(content)
|
||||||
|
end
|
||||||
|
|
||||||
def send_reply
|
def send_reply
|
||||||
find("#reply-control .save-or-cancel .create").click
|
find("#reply-control .save-or-cancel .create").click
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue