mention: Mentions via subgroup should be highlighted.

This commit does not add highlighting to any pre-existing group mentions
for which a user was part of the mentioned group via a subgroup. This
only fixes it for mentions moving forward.
Fixes https://chat.zulip.org/#narrow/channel/9-issues/topic/group.20mention.20not.20highlighted/near/2008541
This commit is contained in:
Shubham Padia
2024-12-20 08:05:01 +00:00
committed by Tim Abbott
parent 3b3ab05f67
commit 0b4fe15c8a
8 changed files with 102 additions and 19 deletions

View File

@@ -58,7 +58,7 @@ export type MarkdownHelpers = {
// user groups // user groups
get_user_group_from_name: (name: string) => {id: number; name: string} | undefined; get_user_group_from_name: (name: string) => {id: number; name: string} | undefined;
is_member_of_user_group: (user_id: number, user_group_id: number) => boolean; is_member_of_user_group: (user_group_id: number, user_id: number) => boolean;
// stream hashes // stream hashes
get_stream_by_name: (stream_name: string) => {stream_id: number; name: string} | undefined; get_stream_by_name: (stream_name: string) => {stream_id: number; name: string} | undefined;
@@ -318,7 +318,7 @@ function parse_with_options(
display_text = "@" + group.name; display_text = "@" + group.name;
classes = "user-group-mention"; classes = "user-group-mention";
if ( if (
helper_config.is_member_of_user_group(helper_config.my_user_id(), group.id) helper_config.is_member_of_user_group(group.id, helper_config.my_user_id())
) { ) {
// Mentioned the current user's group. // Mentioned the current user's group.
mentioned_group = true; mentioned_group = true;

View File

@@ -72,7 +72,7 @@ export const get_helpers = (): MarkdownHelpers => ({
// user groups // user groups
get_user_group_from_name: (name) => user_group(user_groups.get_user_group_from_name(name)), get_user_group_from_name: (name) => user_group(user_groups.get_user_group_from_name(name)),
is_member_of_user_group: user_groups.is_direct_member_of, is_member_of_user_group: user_groups.is_user_in_group,
// stream hashes // stream hashes
get_stream_by_name: (name) => stream(stream_data.get_sub(name)), get_stream_by_name: (name) => stream(stream_data.get_sub(name)),

View File

@@ -205,7 +205,7 @@ export const update_elements = ($content: JQuery): void => {
const my_user_id = people.my_current_user_id(); const my_user_id = people.my_current_user_id();
// Mark user group you're a member of. // Mark user group you're a member of.
if (user_groups.is_direct_member_of(my_user_id, user_group_id)) { if (user_groups.is_user_in_group(user_group_id, my_user_id)) {
$(this).addClass("user-mention-me"); $(this).addClass("user-mention-me");
} }

View File

@@ -55,7 +55,7 @@ function get_user_group_from_name(name) {
return user_group_map.get(name); return user_group_map.get(name);
} }
function is_member_of_user_group(user_id, user_group_id) { function is_member_of_user_group(user_group_id, user_id) {
assert.equal(user_group_id, staff_group.id); assert.equal(user_group_id, staff_group.id);
assert.equal(user_id, my_id); assert.equal(user_id, my_id);
return true; return true;

View File

@@ -76,8 +76,14 @@ const group_other = {
id: 2, id: 2,
members: [cordelia.user_id], members: [cordelia.user_id],
}; };
const group_me_via_subgroup = {
name: "I am part of this group via a subgroup",
id: 3,
members: [],
direct_subgroup_ids: [group_me.id],
};
user_groups.initialize({ user_groups.initialize({
realm_user_groups: [group_me, group_other], realm_user_groups: [group_me, group_other, group_me_via_subgroup],
}); });
const stream = { const stream = {
@@ -363,6 +369,33 @@ run_test("user-group-mention", () => {
assert.equal($group_other.text(), `@${group_other.name}`); assert.equal($group_other.text(), `@${group_other.name}`);
}); });
run_test("user-group-mention", () => {
// Setup
const $content = get_content_element();
const $group_me_via_subgroup = $.create("user-group-mention(me_via_subgroup)");
$group_me_via_subgroup.set_find_results(".highlight", false);
$group_me_via_subgroup.attr("data-user-group-id", group_me_via_subgroup.id);
const $group_other = $.create("user-group-mention(other)");
$group_other.set_find_results(".highlight", false);
$group_other.attr("data-user-group-id", group_other.id);
$content.set_find_results(
".user-group-mention",
$array([$group_me_via_subgroup, $group_other]),
);
// Initial asserts
assert.ok(!$group_me_via_subgroup.hasClass("user-mention-me"));
assert.equal($group_me_via_subgroup.text(), "never-been-set");
assert.equal($group_other.text(), "never-been-set");
rm.update_elements($content);
// Final asserts
assert.ok($group_me_via_subgroup.hasClass("user-mention-me"));
assert.equal($group_me_via_subgroup.text(), `@${group_me_via_subgroup.name}`);
assert.equal($group_other.text(), `@${group_other.name}`);
});
run_test("user-group-mention (error)", () => { run_test("user-group-mention (error)", () => {
const $content = get_content_element(); const $content = get_content_element();
const $group = $.create("user-group-mention(bogus)"); const $group = $.create("user-group-mention(bogus)");

View File

@@ -4,8 +4,9 @@ from dataclasses import dataclass
from re import Match from re import Match
from django.conf import settings from django.conf import settings
from django.db.models import Prefetch, Q from django.db.models import Q
from zerver.lib.user_groups import get_recursive_group_members
from zerver.lib.users import get_inaccessible_user_ids from zerver.lib.users import get_inaccessible_user_ids
from zerver.models import NamedUserGroup, UserProfile from zerver.models import NamedUserGroup, UserProfile
from zerver.models.streams import get_linkable_streams from zerver.models.streams import get_linkable_streams
@@ -269,21 +270,13 @@ class MentionData:
self.user_group_members: dict[int, list[int]] = {} self.user_group_members: dict[int, list[int]] = {}
user_group_names = possible_user_group_mentions(content) user_group_names = possible_user_group_mentions(content)
if user_group_names: if user_group_names:
# We are not directly doing 'prefetch_related("direct_members")'
# because then we would have to filter out deactivated users
# using 'group.direct_members.filter(is_active=True)', and that
# would take away the advantage of using 'prefetch_related'
# because of not using group.direct_members.all().
for group in NamedUserGroup.objects.filter( for group in NamedUserGroup.objects.filter(
realm_id=realm_id, name__in=user_group_names, is_system_group=False realm_id=realm_id, name__in=user_group_names, is_system_group=False
).prefetch_related(
Prefetch(
"direct_members",
queryset=UserProfile.objects.filter(realm_id=realm_id, is_active=True),
)
): ):
self.user_group_name_info[group.name.lower()] = group self.user_group_name_info[group.name.lower()] = group
self.user_group_members[group.id] = [m.id for m in group.direct_members.all()] self.user_group_members[group.id] = [
m.id for m in get_recursive_group_members(group)
]
def get_user_by_name(self, name: str) -> FullNameInfo | None: def get_user_by_name(self, name: str) -> FullNameInfo | None:
# warning: get_user_by_name is not dependable if two # warning: get_user_by_name is not dependable if two

View File

@@ -17,11 +17,12 @@ from zerver.actions.user_topics import do_set_user_topic_visibility_policy
from zerver.lib.message import messages_for_ids from zerver.lib.message import messages_for_ids
from zerver.lib.message_cache import MessageDict from zerver.lib.message_cache import MessageDict
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import queries_captured from zerver.lib.test_helpers import most_recent_message, queries_captured
from zerver.lib.topic import TOPIC_NAME from zerver.lib.topic import TOPIC_NAME
from zerver.lib.utils import assert_is_not_none from zerver.lib.utils import assert_is_not_none
from zerver.models import Attachment, Message, NamedUserGroup, Realm, UserProfile, UserTopic from zerver.models import Attachment, Message, NamedUserGroup, Realm, UserProfile, UserTopic
from zerver.models.groups import SystemGroups from zerver.models.groups import SystemGroups
from zerver.models.messages import UserMessage
from zerver.models.realms import WildcardMentionPolicyEnum, get_realm from zerver.models.realms import WildcardMentionPolicyEnum, get_realm
from zerver.models.streams import get_stream from zerver.models.streams import get_stream
@@ -1557,6 +1558,42 @@ class EditMessageTest(ZulipTestCase):
) )
self.assert_json_success(result) self.assert_json_success(result)
def test_user_group_mentions_via_subgroup_when_editing(self) -> None:
user_profile = self.example_user("iago")
self.login("hamlet")
self.subscribe(user_profile, "Denmark")
my_group = check_add_user_group(
user_profile.realm, "my_group", [user_profile], acting_user=user_profile
)
my_group_via_subgroup = check_add_user_group(
user_profile.realm, "my_group_via_subgroup", [], acting_user=user_profile
)
add_subgroups_to_user_group(my_group_via_subgroup, [my_group], acting_user=None)
self.send_stream_message(
self.example_user("hamlet"), "Denmark", content="there is no mention"
)
message = most_recent_message(user_profile)
assert (
UserMessage.objects.get(
user_profile=user_profile, message=message
).flags.mentioned.is_set
is False
)
result = self.client_patch(
"/json/messages/" + str(message.id),
{
"content": "test @*my_group_via_subgroup* mention",
},
)
self.assert_json_success(result)
assert UserMessage.objects.get(
user_profile=user_profile, message=message
).flags.mentioned.is_set
def test_user_group_mention_restrictions_while_editing(self) -> None: def test_user_group_mention_restrictions_while_editing(self) -> None:
iago = self.example_user("iago") iago = self.example_user("iago")
shiva = self.example_user("shiva") shiva = self.example_user("shiva")

View File

@@ -2153,6 +2153,26 @@ class StreamMessagesTest(ZulipTestCase):
): ):
self.send_stream_message(cordelia, "test_stream", content) self.send_stream_message(cordelia, "test_stream", content)
def test_user_group_mentions_via_subgroup(self) -> None:
user_profile = self.example_user("iago")
self.subscribe(user_profile, "Denmark")
my_group = check_add_user_group(
user_profile.realm, "my_group", [user_profile], acting_user=user_profile
)
my_group_via_subgroup = check_add_user_group(
user_profile.realm, "my_group_via_subgroup", [], acting_user=user_profile
)
add_subgroups_to_user_group(my_group_via_subgroup, [my_group], acting_user=None)
self.send_stream_message(
self.example_user("hamlet"), "Denmark", content="test @*my_group_via_subgroup* mention"
)
message = most_recent_message(user_profile)
assert UserMessage.objects.get(
user_profile=user_profile, message=message
).flags.mentioned.is_set
def test_user_group_mention_restrictions(self) -> None: def test_user_group_mention_restrictions(self) -> None:
iago = self.example_user("iago") iago = self.example_user("iago")
shiva = self.example_user("shiva") shiva = self.example_user("shiva")