FEATURE: add penalty options for take action (#10926)
* FEATURE: add penalty options for take action Add the ability to silence or suspend users from the "take action" button when moderators are flagging posts. This allows for a more streamlined active moderation workflow, when moderating against a topic directly.
This commit is contained in:
parent
e062b94e7f
commit
d68ad82a9e
|
@ -7,6 +7,9 @@ import ActionSummary from "discourse/models/action-summary";
|
|||
import { MAX_MESSAGE_LENGTH } from "discourse/models/post-action-type";
|
||||
import optionalService from "discourse/lib/optional-service";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import I18n from "I18n";
|
||||
import User from "discourse/models/user";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
adminTools: optionalService(),
|
||||
|
@ -17,6 +20,58 @@ export default Controller.extend(ModalFunctionality, {
|
|||
isWarning: false,
|
||||
topicActionByName: null,
|
||||
spammerDetails: null,
|
||||
flagActions: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.flagActions = {
|
||||
icon: "gavel",
|
||||
label: I18n.t("flagging.take_action"),
|
||||
actions: [
|
||||
{
|
||||
id: "agree_and_keep",
|
||||
icon: "thumbs-up",
|
||||
label: I18n.t("flagging.take_action_options.default.title"),
|
||||
description: I18n.t("flagging.take_action_options.default.details"),
|
||||
},
|
||||
{
|
||||
id: "agree_and_suspend",
|
||||
icon: "ban",
|
||||
label: I18n.t("flagging.take_action_options.suspend.title"),
|
||||
description: I18n.t("flagging.take_action_options.suspend.details"),
|
||||
client_action: "suspend",
|
||||
},
|
||||
{
|
||||
id: "agree_and_silence",
|
||||
icon: "microphone-slash",
|
||||
label: I18n.t("flagging.take_action_options.silence.title"),
|
||||
description: I18n.t("flagging.take_action_options.silence.details"),
|
||||
client_action: "silence",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
clientSuspend(performAction) {
|
||||
this._penalize("showSuspendModal", performAction);
|
||||
},
|
||||
|
||||
clientSilence(performAction) {
|
||||
this._penalize("showSilenceModal", performAction);
|
||||
},
|
||||
|
||||
async _penalize(adminToolMethod, performAction) {
|
||||
if (this.adminTools) {
|
||||
let createdBy = await User.findByUsername(this.model.username);
|
||||
let postId = this.model.id;
|
||||
let postEdit = this.model.cooked;
|
||||
return this.adminTools[adminToolMethod](createdBy, {
|
||||
postId,
|
||||
postEdit,
|
||||
before: performAction,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
|
@ -24,9 +79,8 @@ export default Controller.extend(ModalFunctionality, {
|
|||
spammerDetails: null,
|
||||
});
|
||||
|
||||
let adminTools = this.adminTools;
|
||||
if (adminTools) {
|
||||
adminTools.checkSpammer(this.get("model.user_id")).then((result) => {
|
||||
if (this.adminTools) {
|
||||
this.adminTools.checkSpammer(this.get("model.user_id")).then((result) => {
|
||||
this.set("spammerDetails", result);
|
||||
});
|
||||
}
|
||||
|
@ -133,9 +187,28 @@ export default Controller.extend(ModalFunctionality, {
|
|||
}
|
||||
},
|
||||
|
||||
takeAction() {
|
||||
this.send("createFlag", { takeAction: true });
|
||||
takeAction(action) {
|
||||
let performAction = (o = {}) => {
|
||||
o.takeAction = true;
|
||||
this.send("createFlag", o);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
if (action.client_action) {
|
||||
let actionMethod = this[`client${action.client_action.classify()}`];
|
||||
if (actionMethod) {
|
||||
return actionMethod.call(this, () =>
|
||||
performAction({ skipClose: true })
|
||||
);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`No handler for ${action.client_action} found`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.set("model.hidden", true);
|
||||
return performAction();
|
||||
}
|
||||
},
|
||||
|
||||
createFlag(opts) {
|
||||
|
@ -171,7 +244,9 @@ export default Controller.extend(ModalFunctionality, {
|
|||
postAction
|
||||
.act(this.model, params)
|
||||
.then(() => {
|
||||
if (!opts.skipClose) {
|
||||
this.send("closeModal");
|
||||
}
|
||||
if (params.message) {
|
||||
this.set("message", "");
|
||||
}
|
||||
|
|
|
@ -35,13 +35,10 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if canTakeAction}}
|
||||
{{d-button
|
||||
class="btn-danger"
|
||||
action=(action "takeAction")
|
||||
disabled=submitDisabled
|
||||
title="flagging.take_action_tooltip"
|
||||
icon="gavel"
|
||||
label="flagging.take_action"
|
||||
{{reviewable-bundled-action
|
||||
bundle=flagActions
|
||||
performAction=(action "takeAction")
|
||||
reviewableUpdating=submitDisabled
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
import { test } from "qunit";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
import userFixtures from "discourse/tests/fixtures/user-fixtures";
|
||||
|
||||
acceptance("flagging", {
|
||||
loggedIn: true,
|
||||
afterEach() {
|
||||
sandbox.restore();
|
||||
},
|
||||
pretend(pretenderServer, helper) {
|
||||
const userResponse = Object.assign({}, userFixtures["/u/charlie.json"]);
|
||||
pretenderServer.get("/u/uwe_keim.json", () => {
|
||||
return helper.response(userResponse);
|
||||
});
|
||||
pretenderServer.get("/admin/users/255.json", () => {
|
||||
return helper.response({
|
||||
id: 255,
|
||||
automatic: false,
|
||||
name: "admin",
|
||||
username: "admin",
|
||||
user_count: 0,
|
||||
alias_level: 99,
|
||||
visible: true,
|
||||
automatic_membership_email_domains: "",
|
||||
primary_group: false,
|
||||
title: null,
|
||||
grant_trust_level: null,
|
||||
has_messages: false,
|
||||
flair_url: null,
|
||||
flair_bg_color: null,
|
||||
flair_color: null,
|
||||
bio_raw: null,
|
||||
bio_cooked: null,
|
||||
public_admission: false,
|
||||
allow_membership_requests: true,
|
||||
membership_request_template: "Please add me",
|
||||
full_name: null,
|
||||
});
|
||||
});
|
||||
pretenderServer.get("/admin/users/5.json", () => {
|
||||
return helper.response({
|
||||
id: 5,
|
||||
automatic: false,
|
||||
name: "user",
|
||||
username: "user",
|
||||
user_count: 0,
|
||||
alias_level: 99,
|
||||
visible: true,
|
||||
automatic_membership_email_domains: "",
|
||||
primary_group: false,
|
||||
title: null,
|
||||
grant_trust_level: null,
|
||||
has_messages: false,
|
||||
flair_url: null,
|
||||
flair_bg_color: null,
|
||||
flair_color: null,
|
||||
bio_raw: null,
|
||||
bio_cooked: null,
|
||||
public_admission: false,
|
||||
allow_membership_requests: true,
|
||||
membership_request_template: "Please add me",
|
||||
full_name: null,
|
||||
});
|
||||
});
|
||||
pretenderServer.put("admin/users/5/silence", () => {
|
||||
return helper.response({
|
||||
silenced: true,
|
||||
});
|
||||
});
|
||||
pretenderServer.post("post_actions", () => {
|
||||
return helper.response({
|
||||
response: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
async function openFlagModal() {
|
||||
if (exists(".topic-post:first-child button.show-more-actions")) {
|
||||
await click(".topic-post:first-child button.show-more-actions");
|
||||
}
|
||||
|
||||
await click(".topic-post:first-child button.create-flag");
|
||||
}
|
||||
|
||||
test("Flag modal opening", async (assert) => {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await openFlagModal();
|
||||
assert.ok(exists(".flag-modal-body"), "it shows the flag modal");
|
||||
});
|
||||
|
||||
test("Flag take action dropdown exists", async (assert) => {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await openFlagModal();
|
||||
await click("#radio_inappropriate");
|
||||
await selectKit(".reviewable-action-dropdown").expand();
|
||||
assert.ok(
|
||||
exists("[data-value='agree_and_silence']"),
|
||||
"it shows the silence action option"
|
||||
);
|
||||
await click("[data-value='agree_and_silence']");
|
||||
assert.ok(exists(".silence-user-modal"), "it shows the silence modal");
|
||||
});
|
||||
|
||||
test("Can silence from take action", async (assert) => {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await openFlagModal();
|
||||
await click("#radio_inappropriate");
|
||||
await selectKit(".reviewable-action-dropdown").expand();
|
||||
await click("[data-value='agree_and_silence']");
|
||||
|
||||
const silenceUntilCombobox = selectKit(".silence-until .combobox");
|
||||
await silenceUntilCombobox.expand();
|
||||
await silenceUntilCombobox.selectRowByValue("tomorrow");
|
||||
await fillIn(".silence-reason", "for breaking the rules");
|
||||
await click(".perform-silence");
|
||||
assert.equal(find(".bootbox.modal:visible").length, 0);
|
||||
});
|
||||
|
||||
test("Gets dismissable warning from canceling incomplete silence from take action", async (assert) => {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await openFlagModal();
|
||||
await click("#radio_inappropriate");
|
||||
await selectKit(".reviewable-action-dropdown").expand();
|
||||
await click("[data-value='agree_and_silence']");
|
||||
|
||||
const silenceUntilCombobox = selectKit(".silence-until .combobox");
|
||||
await silenceUntilCombobox.expand();
|
||||
await silenceUntilCombobox.selectRowByValue("tomorrow");
|
||||
await fillIn(".silence-reason", "for breaking the rules");
|
||||
await click(".d-modal-cancel");
|
||||
assert.equal(find(".bootbox.modal:visible").length, 1);
|
||||
|
||||
await click(".modal-footer .btn-default");
|
||||
assert.equal(find(".bootbox.modal:visible").length, 0);
|
||||
assert.ok(exists(".silence-user-modal"), "it shows the silence modal");
|
||||
|
||||
await click(".d-modal-cancel");
|
||||
assert.equal(find(".bootbox.modal:visible").length, 1);
|
||||
|
||||
await click(".modal-footer .btn-primary");
|
||||
assert.equal(find(".bootbox.modal:visible").length, 0);
|
||||
});
|
|
@ -490,6 +490,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.flag-modal .modal-inner-container .select-kit.reviewable-action-dropdown {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
table.reviewable-scores {
|
||||
width: 100%;
|
||||
|
|
|
@ -2997,7 +2997,17 @@ en:
|
|||
flagging:
|
||||
title: "Thanks for helping to keep our community civil!"
|
||||
action: "Flag Post"
|
||||
take_action: "Take Action"
|
||||
take_action: "Take Action..."
|
||||
take_action_options:
|
||||
default:
|
||||
title: "Take Action"
|
||||
details: "Reach the flag threshold immediately, rather than waiting for more community flags"
|
||||
suspend:
|
||||
title: "Suspend User"
|
||||
details: "Reach the flag threshold, and suspend the user"
|
||||
silence:
|
||||
title: "Silence User"
|
||||
details: "Reach the flag threshold, and silence the user"
|
||||
notify_action: "Message"
|
||||
official_warning: "Official Warning"
|
||||
delete_spammer: "Delete Spammer"
|
||||
|
|
Loading…
Reference in New Issue