user_groups: Send a message on changing user-groups subscribers.

After this commit a notification message is sent to users if they are
added to user_groups by someone else or they are removed from user_groups
by someone else.

Fixes #23642.
This commit is contained in:
Ujjawal Modi
2023-01-29 18:32:07 +05:30
committed by Tim Abbott
parent e163e3ced5
commit d0dbdfa52d
3 changed files with 131 additions and 9 deletions

View File

@@ -126,7 +126,7 @@ def add_emoji_to_message() -> Dict[str, object]:
# The message ID here is hardcoded based on the corresponding value
# for the example message IDs we use in zulip.yaml.
message_id = 46
message_id = 47
emoji_name = "octopus"
emoji_code = "1f419"
reaction_type = "unicode_emoji"

View File

@@ -7,6 +7,8 @@ from django.utils.timezone import now as timezone_now
from zerver.actions.realm_settings import do_set_realm_property
from zerver.actions.user_groups import check_add_user_group, promote_new_full_members
from zerver.actions.users import do_deactivate_user
from zerver.lib.mention import silent_mention_syntax_for_user
from zerver.lib.streams import ensure_stream
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import most_recent_usermessage
@@ -361,14 +363,27 @@ class UserGroupAPITestCase(UserGroupTestCase):
self.assertEqual(UserGroupMembership.objects.filter(user_group=user_group).count(), 1)
othello = self.example_user("othello")
add = [othello.id]
params = {"add": orjson.dumps(add).decode()}
# A bot
webhook_bot = self.example_user("webhook_bot")
# A deactivated user
iago = self.example_user("iago")
do_deactivate_user(iago, acting_user=None)
params = {"add": orjson.dumps([othello.id]).decode()}
initial_last_message = self.get_last_message()
result = self.client_post(f"/json/user_groups/{user_group.id}/members", info=params)
self.assert_json_success(result)
self.assertEqual(UserGroupMembership.objects.filter(user_group=user_group).count(), 2)
members = get_direct_memberships_of_users(user_group, [hamlet, othello])
self.assert_length(members, 2)
# A notification message is sent for adding to user group.
self.assertNotEqual(self.get_last_message(), initial_last_message)
expected_notification = (
f"{silent_mention_syntax_for_user(hamlet)} added you to the group @_*support*."
)
self.assertEqual(self.get_last_message().content, expected_notification)
# Test adding a member already there.
result = self.client_post(f"/json/user_groups/{user_group.id}/members", info=params)
self.assert_json_error(result, f"User {othello.id} is already a member of this group")
@@ -376,6 +391,24 @@ class UserGroupAPITestCase(UserGroupTestCase):
members = get_direct_memberships_of_users(user_group, [hamlet, othello])
self.assert_length(members, 2)
# Test user adding itself,bot and deactivated user to user group.
desdemona = self.example_user("desdemona")
self.login_user(desdemona)
params = {"add": orjson.dumps([desdemona.id, iago.id, webhook_bot.id]).decode()}
initial_last_message = self.get_last_message()
result = self.client_post(f"/json/user_groups/{user_group.id}/members", info=params)
self.assert_json_success(result)
self.assertEqual(UserGroupMembership.objects.filter(user_group=user_group).count(), 5)
members = get_direct_memberships_of_users(
user_group, [hamlet, othello, desdemona, iago, webhook_bot]
)
self.assert_length(members, 5)
# No notification message is sent for adding to user group.
self.assertEqual(self.get_last_message(), initial_last_message)
aaron = self.example_user("aaron")
# For normal testing we again log in with hamlet
@@ -383,20 +416,44 @@ class UserGroupAPITestCase(UserGroupTestCase):
self.login_user(hamlet)
# Test remove members
params = {"delete": orjson.dumps([othello.id]).decode()}
initial_last_message = self.get_last_message()
result = self.client_post(f"/json/user_groups/{user_group.id}/members", info=params)
self.assert_json_success(result)
self.assertEqual(UserGroupMembership.objects.filter(user_group=user_group).count(), 1)
members = get_direct_memberships_of_users(user_group, [hamlet, othello, aaron])
self.assert_length(members, 1)
self.assertEqual(UserGroupMembership.objects.filter(user_group=user_group).count(), 4)
members = get_direct_memberships_of_users(
user_group, [hamlet, othello, aaron, desdemona, webhook_bot, iago]
)
self.assert_length(members, 4)
# A notification message is sent for removing from user group.
self.assertNotEqual(self.get_last_message(), initial_last_message)
expected_notification = (
f"{silent_mention_syntax_for_user(hamlet)} removed you from the group @_*support*."
)
self.assertEqual(self.get_last_message().content, expected_notification)
# Test remove a member that's already removed
params = {"delete": orjson.dumps([othello.id]).decode()}
result = self.client_post(f"/json/user_groups/{user_group.id}/members", info=params)
self.assert_json_error(result, f"There is no member '{othello.id}' in this user group")
# Test user remove itself,bot and deactivated user from user group.
desdemona = self.example_user("desdemona")
self.login_user(desdemona)
params = {"delete": orjson.dumps([desdemona.id, iago.id, webhook_bot.id]).decode()}
initial_last_message = self.get_last_message()
result = self.client_post(f"/json/user_groups/{user_group.id}/members", info=params)
self.assert_json_success(result)
self.assertEqual(UserGroupMembership.objects.filter(user_group=user_group).count(), 1)
members = get_direct_memberships_of_users(user_group, [hamlet, othello, aaron])
members = get_direct_memberships_of_users(user_group, [hamlet, othello, desdemona])
self.assert_length(members, 1)
# No notification message is sent for removing from user group.
self.assertEqual(self.get_last_message(), initial_last_message)
# Test when nothing is provided
result = self.client_post(f"/json/user_groups/{user_group.id}/members", info={})
msg = 'Nothing to do. Specify at least one of "add" or "delete".'

View File

@@ -1,8 +1,11 @@
from typing import Optional, Sequence
from typing import List, Optional, Sequence
from django.conf import settings
from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _
from django.utils.translation import override as override_language
from zerver.actions.message_send import do_send_messages, internal_prep_private_message
from zerver.actions.user_groups import (
add_subgroups_to_user_group,
bulk_add_members_to_user_group,
@@ -15,6 +18,7 @@ from zerver.actions.user_groups import (
)
from zerver.decorator import require_member_or_admin, require_user_group_edit_permission
from zerver.lib.exceptions import JsonableError
from zerver.lib.mention import MentionBackend, silent_mention_syntax_for_user
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success
from zerver.lib.user_groups import (
@@ -29,7 +33,7 @@ from zerver.lib.user_groups import (
)
from zerver.lib.users import access_user_by_id, user_ids_to_users
from zerver.lib.validator import check_bool, check_int, check_list
from zerver.models import UserProfile
from zerver.models import UserGroup, UserProfile, get_system_bot
from zerver.views.streams import compose_views
@@ -115,6 +119,55 @@ def update_user_group_backend(
return json_success(request, data)
def notify_for_user_group_subscription_changes(
acting_user: UserProfile,
recipient_users: List[UserProfile],
user_group: UserGroup,
*,
send_subscription_message: bool = False,
send_unsubscription_message: bool = False,
) -> None:
realm = acting_user.realm
mention_backend = MentionBackend(realm.id)
notifications = []
notification_bot = get_system_bot(settings.NOTIFICATION_BOT, realm.id)
for recipient_user in recipient_users:
if recipient_user.id == acting_user.id:
# Don't send notification message if you subscribed/unsubscribed yourself.
continue
if recipient_user.is_bot:
# Don't send notification message to bots.
continue
if not recipient_user.is_active:
# Don't send notification message to deactivated users.
continue
with override_language(recipient_user.default_language):
if send_subscription_message:
message = _("{user_full_name} added you to the group {group_name}.").format(
user_full_name=silent_mention_syntax_for_user(acting_user),
group_name=f"@_*{user_group.name}*",
)
if send_unsubscription_message:
message = _("{user_full_name} removed you from the group {group_name}.").format(
user_full_name=silent_mention_syntax_for_user(acting_user),
group_name=f"@_*{user_group.name}*",
)
notifications.append(
internal_prep_private_message(
sender=notification_bot,
recipient_user=recipient_user,
content=message,
mention_backend=mention_backend,
)
)
if len(notifications) > 0:
do_send_messages(notifications)
def add_members_to_group_backend(
request: HttpRequest, user_profile: UserProfile, user_group_id: int, members: Sequence[int]
) -> HttpResponse:
@@ -135,6 +188,12 @@ def add_members_to_group_backend(
member_user_ids = [member_user.id for member_user in member_users]
bulk_add_members_to_user_group(user_group, member_user_ids, acting_user=user_profile)
notify_for_user_group_subscription_changes(
acting_user=user_profile,
recipient_users=member_users,
user_group=user_group,
send_subscription_message=True,
)
return json_success(request)
@@ -153,6 +212,12 @@ def remove_members_from_group_backend(
user_profile_ids = [user.id for user in user_profiles]
remove_members_from_user_group(user_group, user_profile_ids, acting_user=user_profile)
notify_for_user_group_subscription_changes(
acting_user=user_profile,
recipient_users=user_profiles,
user_group=user_group,
send_unsubscription_message=True,
)
return json_success(request)