message_list: Hide group DMs where everyone is muted.

This commit hides group DMs where all recipients are muted from
different views.

Fixes: zulip#34886.

Co-authored-by: Aman Agrawal <amanagr@zulip.com>
This commit is contained in:
Pratik Chanda
2025-07-08 21:46:34 +05:30
committed by Tim Abbott
parent 6b6a02f932
commit 81ca9e1b36
4 changed files with 36 additions and 33 deletions

View File

@@ -1073,8 +1073,12 @@ export class Filter {
return this.has_operator("search"); return this.has_operator("search");
} }
is_non_group_direct_message(): boolean { is_search_for_specific_group_or_user(): boolean {
return this.has_operator("dm") && this.operands("dm")[0]!.split(",").length === 1; return (
this.has_operator("dm") ||
this.has_operator("dm-including") ||
this.has_operator("sender")
);
} }
contains_no_partial_conversations(): boolean { contains_no_partial_conversations(): boolean {

View File

@@ -237,8 +237,10 @@ export class MessageListData {
} }
messages_filtered_for_user_mutes(messages: Message[]): Message[] { messages_filtered_for_user_mutes(messages: Message[]): Message[] {
if (this.filter.is_non_group_direct_message()) { // Don't exclude messages sent by muted users if we're
// We are in a 1:1 direct message narrow, so do not do any filtering. // searching for a specific group or user, since the user
// presumably wants to see those messages.
if (this.filter.is_search_for_specific_group_or_user()) {
return [...messages]; return [...messages];
} }
@@ -247,16 +249,9 @@ export class MessageListData {
return true; return true;
} }
const recipients = util.extract_pm_recipients(message.to_user_ids); const recipients = util.extract_pm_recipients(message.to_user_ids);
if (recipients.length > 1) { const recipient_ids = recipients.map((recipient) => Number.parseInt(recipient, 10));
// Direct message group message const unmuted_recipient_ids = muted_users.filter_muted_user_ids(recipient_ids);
return true; return unmuted_recipient_ids.length > 0;
}
const recipient_id = Number.parseInt(util.the(recipients), 10);
return (
!muted_users.is_user_muted(recipient_id) &&
!muted_users.is_user_muted(message.sender_id)
);
}); });
} }

View File

@@ -403,7 +403,7 @@ test("basics", () => {
terms = [{operator: "dm", operand: "joe@example.com"}]; terms = [{operator: "dm", operand: "joe@example.com"}];
filter = new Filter(terms); filter = new Filter(terms);
assert.ok(filter.is_non_group_direct_message()); assert.ok(filter.is_search_for_specific_group_or_user());
assert.ok(filter.contains_only_private_messages()); assert.ok(filter.contains_only_private_messages());
assert.ok(filter.can_mark_messages_read()); assert.ok(filter.can_mark_messages_read());
assert.ok(filter.contains_no_partial_conversations()); assert.ok(filter.contains_no_partial_conversations());
@@ -421,7 +421,7 @@ test("basics", () => {
{operator: "near", operand: "17"}, {operator: "near", operand: "17"},
]; ];
filter = new Filter(terms); filter = new Filter(terms);
assert.ok(filter.is_non_group_direct_message()); assert.ok(filter.is_search_for_specific_group_or_user());
assert.ok(filter.contains_only_private_messages()); assert.ok(filter.contains_only_private_messages());
assert.ok(!filter.can_mark_messages_read()); assert.ok(!filter.can_mark_messages_read());
assert.ok(filter.contains_no_partial_conversations()); assert.ok(filter.contains_no_partial_conversations());
@@ -436,7 +436,7 @@ test("basics", () => {
terms = [{operator: "dm", operand: "joe@example.com,jack@example.com"}]; terms = [{operator: "dm", operand: "joe@example.com,jack@example.com"}];
filter = new Filter(terms); filter = new Filter(terms);
assert.ok(!filter.is_non_group_direct_message()); assert.ok(filter.is_search_for_specific_group_or_user());
assert.ok(filter.contains_only_private_messages()); assert.ok(filter.contains_only_private_messages());
assert.ok(filter.can_mark_messages_read()); assert.ok(filter.can_mark_messages_read());
assert.ok(filter.contains_no_partial_conversations()); assert.ok(filter.contains_no_partial_conversations());
@@ -474,7 +474,7 @@ test("basics", () => {
terms = [{operator: "dm-including", operand: "joe@example.com"}]; terms = [{operator: "dm-including", operand: "joe@example.com"}];
filter = new Filter(terms); filter = new Filter(terms);
assert.ok(!filter.is_non_group_direct_message()); assert.ok(filter.is_search_for_specific_group_or_user());
assert.ok(filter.contains_only_private_messages()); assert.ok(filter.contains_only_private_messages());
assert.ok(!filter.has_operator("search")); assert.ok(!filter.has_operator("search"));
assert.ok(!filter.can_mark_messages_read()); assert.ok(!filter.can_mark_messages_read());

View File

@@ -140,19 +140,21 @@ run_test("muting", () => {
// mentions override muting // mentions override muting
{id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true}, {id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true},
// 10 = muted user, 9 = non-muted user, 11 = you // 10,12 = muted users, 9 = non-muted user, 11 = you
// muted to group direct message // muted to group direct message
{id: 4, type: "private", to_user_ids: "9,10,11", sender_id: 10}, {id: 4, type: "private", to_user_ids: "9,10", sender_id: 10},
// non-muted to group direct message // non-muted to group direct message
{id: 5, type: "private", to_user_ids: "9,10,11", sender_id: 9}, {id: 5, type: "private", to_user_ids: "9,10", sender_id: 9},
// muted to 1:1 direct message // muted to 1:1 direct message
{id: 6, type: "private", to_user_ids: "11", sender_id: 10}, {id: 6, type: "private", to_user_ids: "10", sender_id: 10},
// non-muted to 1:1 direct message // non-muted to 1:1 direct message
{id: 7, type: "private", to_user_ids: "11", sender_id: 9}, {id: 7, type: "private", to_user_ids: "9", sender_id: 9},
// 1:1 direct message to muted // 1:1 direct message to muted
{id: 8, type: "private", to_user_ids: "10", sender_id: 11}, {id: 8, type: "private", to_user_ids: "10", sender_id: 11},
// 1:1 direct message to non-muted // 1:1 direct message to non-muted
{id: 9, type: "private", to_user_ids: "9", sender_id: 11}, {id: 9, type: "private", to_user_ids: "9", sender_id: 11},
// group direct message with everyone muted
{id: 10, type: "private", to_user_ids: "10,12", sender_id: 10},
]; ];
user_topics.update_user_topics( user_topics.update_user_topics(
@@ -162,6 +164,7 @@ run_test("muting", () => {
user_topics.all_visibility_policies.MUTED, user_topics.all_visibility_policies.MUTED,
); );
muted_users.add_muted_user(10); muted_users.add_muted_user(10);
muted_users.add_muted_user(12);
// `messages_filtered_for_topic_mutes` should skip filtering // `messages_filtered_for_topic_mutes` should skip filtering
// messages if `excludes_muted_topics` is false. // messages if `excludes_muted_topics` is false.
@@ -180,12 +183,13 @@ run_test("muting", () => {
{id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true}, // mentions override muting {id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true}, // mentions override muting
// `messages_filtered_for_topic_mutes` does not affect direct messages // `messages_filtered_for_topic_mutes` does not affect direct messages
{id: 4, type: "private", to_user_ids: "9,10,11", sender_id: 10}, {id: 4, type: "private", to_user_ids: "9,10", sender_id: 10},
{id: 5, type: "private", to_user_ids: "9,10,11", sender_id: 9}, {id: 5, type: "private", to_user_ids: "9,10", sender_id: 9},
{id: 6, type: "private", to_user_ids: "11", sender_id: 10}, {id: 6, type: "private", to_user_ids: "10", sender_id: 10},
{id: 7, type: "private", to_user_ids: "11", sender_id: 9}, {id: 7, type: "private", to_user_ids: "9", sender_id: 9},
{id: 8, type: "private", to_user_ids: "10", sender_id: 11}, {id: 8, type: "private", to_user_ids: "10", sender_id: 11},
{id: 9, type: "private", to_user_ids: "9", sender_id: 11}, {id: 9, type: "private", to_user_ids: "9", sender_id: 11},
{id: 10, type: "private", to_user_ids: "10,12", sender_id: 10},
]); ]);
const res_user = mld.messages_filtered_for_user_mutes(msgs); const res_user = mld.messages_filtered_for_user_mutes(msgs);
@@ -195,11 +199,11 @@ run_test("muting", () => {
{id: 2, type: "stream", stream_id: 1, topic: "whatever"}, {id: 2, type: "stream", stream_id: 1, topic: "whatever"},
{id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true}, {id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true},
// muted to group direct message // muted to group direct message
{id: 4, type: "private", to_user_ids: "9,10,11", sender_id: 10}, {id: 4, type: "private", to_user_ids: "9,10", sender_id: 10},
// non-muted to group direct message // non-muted to group direct message
{id: 5, type: "private", to_user_ids: "9,10,11", sender_id: 9}, {id: 5, type: "private", to_user_ids: "9,10", sender_id: 9},
// non-muted to 1:1 direct message // non-muted to 1:1 direct message
{id: 7, type: "private", to_user_ids: "11", sender_id: 9}, {id: 7, type: "private", to_user_ids: "9", sender_id: 9},
// 1:1 direct message to non-muted // 1:1 direct message to non-muted
{id: 9, type: "private", to_user_ids: "9", sender_id: 11}, {id: 9, type: "private", to_user_ids: "9", sender_id: 11},
]); ]);
@@ -210,9 +214,9 @@ run_test("muting", () => {
assert.deepEqual(filtered_messages, [ assert.deepEqual(filtered_messages, [
{id: 2, type: "stream", stream_id: 1, topic: "whatever"}, {id: 2, type: "stream", stream_id: 1, topic: "whatever"},
{id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true}, {id: 3, type: "stream", stream_id: 1, topic: "muted", mentioned: true},
{id: 4, type: "private", to_user_ids: "9,10,11", sender_id: 10}, {id: 4, type: "private", to_user_ids: "9,10", sender_id: 10},
{id: 5, type: "private", to_user_ids: "9,10,11", sender_id: 9}, {id: 5, type: "private", to_user_ids: "9,10", sender_id: 9},
{id: 7, type: "private", to_user_ids: "11", sender_id: 9}, {id: 7, type: "private", to_user_ids: "9", sender_id: 9},
{id: 9, type: "private", to_user_ids: "9", sender_id: 11}, {id: 9, type: "private", to_user_ids: "9", sender_id: 11},
]); ]);