mirror of
https://github.com/wekan/wekan.git
synced 2026-11-05 01:20:07 -05:00
Honor muted boards for every notification source
Apply the board watch level at the final recipient boundary so direct assignments, mentions, card or list watchers, and broad-event notifications cannot send mail or profile notifications to muted users. Keep Watching and Tracking active members eligible, and cover missing/default-muted, inactive and non-member recipients. Thanks to Nissulya and xet7 ! Fixes #6658,
This commit is contained in:
1 parent
d63a942851
commit
4be77e7189
3 files changed
+86
-1
No files matched your search
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const NOTIFYING_LEVELS = new Set(['watching', 'tracking']);
|
||||
|
||||
/**
|
||||
* Keep notification candidates who are active board members and whose board
|
||||
* watch level permits notifications. A missing watcher entry has the board's
|
||||
* default `muted` meaning and must not be bypassed by assignment, mentions,
|
||||
* card/list watching or BIGEVENTS_PATTERN.
|
||||
*/
|
||||
function boardNotificationRecipients(candidates, members, boardWatchers) {
|
||||
const activeMemberIds = new Set(
|
||||
(members || []).filter(member => member.isActive === true).map(member => member.userId),
|
||||
);
|
||||
const notifyingUserIds = new Set(
|
||||
(boardWatchers || [])
|
||||
.filter(watcher => NOTIFYING_LEVELS.has(watcher.level))
|
||||
.map(watcher => watcher.userId),
|
||||
);
|
||||
|
||||
return [...new Set(candidates || [])].filter(
|
||||
userId => activeMemberIds.has(userId) && notifyingUserIds.has(userId),
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { boardNotificationRecipients };
|
||||
@@ -12,6 +12,9 @@ import { safeDeliver } from '/server/lib/webhookGuard';
|
||||
import { labelDisplayName } from '/models/lib/labelDisplayName';
|
||||
import { getFeatureFlags } from '/models/lib/featureFlags';
|
||||
import { ACTIVITY_NOTIFICATION_TITLE } from '/server/lib/activityNotificationTitle';
|
||||
const {
|
||||
boardNotificationRecipients,
|
||||
} = require('/models/lib/boardNotificationRecipients');
|
||||
|
||||
function normalizeActivityText(value, fallback = '') {
|
||||
return typeof value === 'string' ? value : fallback;
|
||||
@@ -393,7 +396,15 @@ Activities.after.insert(async (userId, doc) => {
|
||||
}
|
||||
}
|
||||
|
||||
watchers = watchers.filter(x => activeMemberIds.includes(x));
|
||||
// #6658: Muted is the board's final notification boundary. Direct
|
||||
// assignment (#5833), mentions, list/card watchers and BIGEVENTS_PATTERN
|
||||
// may nominate a recipient, but none may override that recipient's board
|
||||
// watch level. No watcher entry also means the default `muted` level.
|
||||
watchers = boardNotificationRecipients(
|
||||
watchers,
|
||||
board.members,
|
||||
board.watchers,
|
||||
);
|
||||
}
|
||||
|
||||
(await Notifications.getUsers(watchers)).forEach((user) => {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const {
|
||||
boardNotificationRecipients,
|
||||
} = require('../models/lib/boardNotificationRecipients');
|
||||
|
||||
const members = [
|
||||
{ userId: 'watching', isActive: true },
|
||||
{ userId: 'tracking', isActive: true },
|
||||
{ userId: 'muted', isActive: true },
|
||||
{ userId: 'default-muted', isActive: true },
|
||||
{ userId: 'inactive', isActive: false },
|
||||
];
|
||||
const boardWatchers = [
|
||||
{ userId: 'watching', level: 'watching' },
|
||||
{ userId: 'tracking', level: 'tracking' },
|
||||
{ userId: 'muted', level: 'muted' },
|
||||
{ userId: 'inactive', level: 'watching' },
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
boardNotificationRecipients(
|
||||
['watching', 'tracking', 'watching'],
|
||||
members,
|
||||
boardWatchers,
|
||||
),
|
||||
['watching', 'tracking'],
|
||||
'Watching and Tracking active members are notified once',
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
boardNotificationRecipients(
|
||||
['muted', 'default-muted', 'inactive', 'not-a-member'],
|
||||
members,
|
||||
boardWatchers,
|
||||
),
|
||||
[],
|
||||
'Muted/default-muted, inactive and non-member candidates stay silent',
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
boardNotificationRecipients(undefined, undefined, undefined),
|
||||
[],
|
||||
'missing board data cannot accidentally notify anyone',
|
||||
);
|
||||
|
||||
console.log('boardMutedNotifications6658: muted board boundary passed');
|
||||
Reference in new issue
Block a user