mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 11:35:55 +01:00
feat: introduce ACP defined option to rescind notif or do nothing on flag resolve/reject
/cc #10867
This commit is contained in:
@@ -94,6 +94,8 @@
|
|||||||
"min:rep:signature": 0,
|
"min:rep:signature": 0,
|
||||||
"flags:limitPerTarget": 0,
|
"flags:limitPerTarget": 0,
|
||||||
"flags:autoFlagOnDownvoteThreshold": 0,
|
"flags:autoFlagOnDownvoteThreshold": 0,
|
||||||
|
"flags:actionOnResolve": "rescind",
|
||||||
|
"flags:actionOnReject": "rescind",
|
||||||
"notificationType_upvote": "notification",
|
"notificationType_upvote": "notification",
|
||||||
"notificationType_new-topic": "notification",
|
"notificationType_new-topic": "notification",
|
||||||
"notificationType_new-reply": "notification",
|
"notificationType_new-reply": "notification",
|
||||||
|
|||||||
@@ -23,5 +23,9 @@
|
|||||||
"flags.limit-per-target-placeholder": "Default: 0",
|
"flags.limit-per-target-placeholder": "Default: 0",
|
||||||
"flags.limit-per-target-help": "When a post or user is flagged multiple times, each additional flag is considered a "report" and added to the original flag. Set this option to a number other than zero to limit the number of reports an item can receive.",
|
"flags.limit-per-target-help": "When a post or user is flagged multiple times, each additional flag is considered a "report" and added to the original flag. Set this option to a number other than zero to limit the number of reports an item can receive.",
|
||||||
"flags.auto-flag-on-downvote-threshold": "Number of downvotes to auto flag posts (Set to 0 to disable, default: 0)",
|
"flags.auto-flag-on-downvote-threshold": "Number of downvotes to auto flag posts (Set to 0 to disable, default: 0)",
|
||||||
"flags.auto-resolve-on-ban": "Automatically resolve all of a user's tickets when they are banned"
|
"flags.auto-resolve-on-ban": "Automatically resolve all of a user's tickets when they are banned",
|
||||||
|
"flags.action-on-resolve": "Do the following when a flag is resolved",
|
||||||
|
"flags.action-on-reject": "Do the following when a flag is rejected",
|
||||||
|
"flags.action.nothing": "Do nothing",
|
||||||
|
"flags.action.rescind": "Rescind the notification send to moderators/administrators"
|
||||||
}
|
}
|
||||||
@@ -679,7 +679,10 @@ Flags.update = async function (flagId, uid, changeset) {
|
|||||||
} else {
|
} else {
|
||||||
tasks.push(db.sortedSetAdd(`flags:byState:${changeset[prop]}`, now, flagId));
|
tasks.push(db.sortedSetAdd(`flags:byState:${changeset[prop]}`, now, flagId));
|
||||||
tasks.push(db.sortedSetRemove(`flags:byState:${current[prop]}`, flagId));
|
tasks.push(db.sortedSetRemove(`flags:byState:${current[prop]}`, flagId));
|
||||||
if (changeset[prop] === 'resolved' || changeset[prop] === 'rejected') {
|
if (changeset[prop] === 'resolved' && meta.config['flags:actionOnResolve'] === 'rescind') {
|
||||||
|
tasks.push(notifications.rescind(`flag:${current.type}:${current.targetId}`));
|
||||||
|
}
|
||||||
|
if (changeset[prop] === 'rejected' && meta.config['flags:actionOnReject'] === 'rescind') {
|
||||||
tasks.push(notifications.rescind(`flag:${current.type}:${current.targetId}`));
|
tasks.push(notifications.rescind(`flag:${current.type}:${current.targetId}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,26 @@
|
|||||||
<label for="flags:autoFlagOnDownvoteThreshold">[[admin/settings/reputation:flags.auto-flag-on-downvote-threshold]]</label>
|
<label for="flags:autoFlagOnDownvoteThreshold">[[admin/settings/reputation:flags.auto-flag-on-downvote-threshold]]</label>
|
||||||
<input type="text" class="form-control" placeholder="0" data-field="flags:autoFlagOnDownvoteThreshold" id="flags:autoFlagOnDownvoteThreshold">
|
<input type="text" class="form-control" placeholder="0" data-field="flags:autoFlagOnDownvoteThreshold" id="flags:autoFlagOnDownvoteThreshold">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="flags:actionOnResolve">[[admin/settings/reputation:flags.action-on-resolve]]</label>
|
||||||
|
<select class="form-control" data-field="flags:actionOnResolve" name="flags:actionOnResolve" id="flags:actionOnResolve">
|
||||||
|
<option value="">[[admin/settings/reputation:flags.action.nothing]]</option>
|
||||||
|
<option value="rescind">[[admin/settings/reputation:flags.action.rescind]]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="flags:actionOnReject">[[admin/settings/reputation:flags.action-on-reject]]</label>
|
||||||
|
<select class="form-control" data-field="flags:actionOnReject" name="flags:actionOnReject" id="flags:actionOnReject">
|
||||||
|
<option value="">[[admin/settings/reputation:flags.action.nothing]]</option>
|
||||||
|
<option value="rescind">[[admin/settings/reputation:flags.action.rescind]]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||||
<input type="checkbox" class="mdl-switch__input" data-field="flags:autoResolveOnBan">
|
<input type="checkbox" class="mdl-switch__input" data-field="flags:autoResolveOnBan">
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const Groups = require('../src/groups');
|
|||||||
const Meta = require('../src/meta');
|
const Meta = require('../src/meta');
|
||||||
const Privileges = require('../src/privileges');
|
const Privileges = require('../src/privileges');
|
||||||
const utils = require('../src/utils');
|
const utils = require('../src/utils');
|
||||||
|
const api = require('../src/api');
|
||||||
|
|
||||||
describe('Flags', () => {
|
describe('Flags', () => {
|
||||||
let uid1;
|
let uid1;
|
||||||
@@ -511,26 +512,75 @@ describe('Flags', () => {
|
|||||||
assert.strictEqual('wip', state);
|
assert.strictEqual('wip', state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rescind notification if flag is resolved', async () => {
|
describe('resolve/reject', () => {
|
||||||
const flagsAPI = require('../src/api/flags');
|
let result;
|
||||||
const result = await Topics.post({
|
let flagObj;
|
||||||
cid: category.cid,
|
beforeEach(async () => {
|
||||||
uid: uid3,
|
result = await Topics.post({
|
||||||
title: 'Topic to flag',
|
cid: category.cid,
|
||||||
content: 'This is flaggable content',
|
uid: uid3,
|
||||||
});
|
title: 'Topic to flag',
|
||||||
const flagObj = await flagsAPI.create({ uid: uid1 }, { type: 'post', id: result.postData.pid, reason: 'spam' });
|
content: 'This is flaggable content',
|
||||||
await sleep(2000);
|
});
|
||||||
|
flagObj = await api.flags.create({ uid: uid1 }, { type: 'post', id: result.postData.pid, reason: 'spam' });
|
||||||
let userNotifs = await User.notifications.getAll(adminUid);
|
await sleep(2000);
|
||||||
assert(userNotifs.includes(`flag:post:${result.postData.pid}`));
|
|
||||||
|
|
||||||
await Flags.update(flagObj.flagId, adminUid, {
|
|
||||||
state: 'resolved',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
userNotifs = await User.notifications.getAll(adminUid);
|
it('should rescind notification if flag is resolved', async () => {
|
||||||
assert(!userNotifs.includes(`flag:post:${result.postData.pid}`));
|
let userNotifs = await User.notifications.getAll(adminUid);
|
||||||
|
assert(userNotifs.includes(`flag:post:${result.postData.pid}`));
|
||||||
|
|
||||||
|
await Flags.update(flagObj.flagId, adminUid, {
|
||||||
|
state: 'resolved',
|
||||||
|
});
|
||||||
|
|
||||||
|
userNotifs = await User.notifications.getAll(adminUid);
|
||||||
|
assert(!userNotifs.includes(`flag:post:${result.postData.pid}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rescind notification if flag is rejected', async () => {
|
||||||
|
let userNotifs = await User.notifications.getAll(adminUid);
|
||||||
|
assert(userNotifs.includes(`flag:post:${result.postData.pid}`));
|
||||||
|
|
||||||
|
await Flags.update(flagObj.flagId, adminUid, {
|
||||||
|
state: 'rejected',
|
||||||
|
});
|
||||||
|
|
||||||
|
userNotifs = await User.notifications.getAll(adminUid);
|
||||||
|
assert(!userNotifs.includes(`flag:post:${result.postData.pid}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if flag is resolved but ACP action is not "rescind"', async () => {
|
||||||
|
Meta.config['flags:actionOnResolve'] = '';
|
||||||
|
|
||||||
|
let userNotifs = await User.notifications.getAll(adminUid);
|
||||||
|
assert(userNotifs.includes(`flag:post:${result.postData.pid}`));
|
||||||
|
|
||||||
|
await Flags.update(flagObj.flagId, adminUid, {
|
||||||
|
state: 'resolved',
|
||||||
|
});
|
||||||
|
|
||||||
|
userNotifs = await User.notifications.getAll(adminUid);
|
||||||
|
assert(userNotifs.includes(`flag:post:${result.postData.pid}`));
|
||||||
|
|
||||||
|
delete Meta.config['flags:actionOnResolve'];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if flag is rejected but ACP action is not "rescind"', async () => {
|
||||||
|
Meta.config['flags:actionOnReject'] = '';
|
||||||
|
|
||||||
|
let userNotifs = await User.notifications.getAll(adminUid);
|
||||||
|
assert(userNotifs.includes(`flag:post:${result.postData.pid}`));
|
||||||
|
|
||||||
|
await Flags.update(flagObj.flagId, adminUid, {
|
||||||
|
state: 'rejected',
|
||||||
|
});
|
||||||
|
|
||||||
|
userNotifs = await User.notifications.getAll(adminUid);
|
||||||
|
assert(userNotifs.includes(`flag:post:${result.postData.pid}`));
|
||||||
|
|
||||||
|
delete Meta.config['flags:actionOnReject'];
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user