streams: Handle empty topic only streams being used for announcements.

This commit updates code to send messages to "general chat" topic
if streams used for announcements for "New user signups",
"New created streams", "Moderation requests" and "Zulip updates"
have topics policy set to allow only "general chat" messages.
This commit is contained in:
Sahil Batra
2025-07-22 22:33:41 +05:30
committed by Tim Abbott
parent 639972b753
commit eb57fe10a2
8 changed files with 159 additions and 11 deletions

View File

@@ -66,6 +66,7 @@ from zerver.models import (
)
from zerver.models.groups import SystemGroups
from zerver.models.realm_audit_logs import AuditLogEventType
from zerver.models.streams import StreamTopicsPolicyEnum
from zerver.models.users import ExternalAuthID, active_user_ids, bot_owner_user_ids, get_system_bot
from zerver.tornado.django_api import send_event_on_commit
@@ -82,6 +83,11 @@ def send_message_to_signup_notification_stream(
with override_language(realm.default_language):
topic_name = _("signups")
if (
signup_announcements_stream.topics_policy
== StreamTopicsPolicyEnum.empty_topic_only.value
):
topic_name = ""
internal_send_stream_message(sender, signup_announcements_stream, topic_name, message)

View File

@@ -9,6 +9,7 @@ from zerver.lib.message import is_1_to_1_message, truncate_content
from zerver.lib.topic_link_util import get_message_link_syntax
from zerver.models import Message, Realm, UserProfile
from zerver.models.recipients import Recipient
from zerver.models.streams import StreamTopicsPolicyEnum
from zerver.models.users import get_system_bot
# We shrink the truncate length for the reported message to ensure
@@ -104,9 +105,13 @@ def send_message_report(
)
content += reported_message_preview_block
topic_name = _("{fullname}'s moderation requests").format(fullname=reported_user.full_name)
if moderation_request_channel.topics_policy == StreamTopicsPolicyEnum.empty_topic_only.value:
topic_name = ""
internal_send_stream_message(
sender=get_system_bot(settings.NOTIFICATION_BOT, moderation_request_channel.realm.id),
stream=moderation_request_channel,
topic_name=_("{fullname}'s moderation requests").format(fullname=reported_user.full_name),
topic_name=topic_name,
content=content,
)

View File

@@ -19,6 +19,7 @@ from zerver.lib.message import SendMessageRequest, remove_single_newlines
from zerver.lib.topic import messages_for_topic
from zerver.models.realm_audit_logs import AuditLogEventType, RealmAuditLog
from zerver.models.realms import Realm
from zerver.models.streams import StreamTopicsPolicyEnum
from zerver.models.users import UserProfile, get_system_bot
@@ -483,12 +484,25 @@ def get_realms_behind_zulip_update_announcements_level(level: int) -> QuerySet[R
return realms
def get_topic_name_for_zulip_update_announcements(realm: Realm) -> str:
assert realm.zulip_update_announcements_stream is not None
with override_language(realm.default_language):
topic_name = str(realm.ZULIP_UPDATE_ANNOUNCEMENTS_TOPIC_NAME)
if (
realm.zulip_update_announcements_stream.topics_policy
== StreamTopicsPolicyEnum.empty_topic_only.value
):
topic_name = ""
return topic_name
def internal_prep_group_direct_message_for_old_realm(
realm: Realm, sender: UserProfile
) -> SendMessageRequest | None:
administrators = list(realm.get_human_admin_users())
with override_language(realm.default_language):
topic_name = str(realm.ZULIP_UPDATE_ANNOUNCEMENTS_TOPIC_NAME)
if realm.zulip_update_announcements_stream is None:
content = """
Zulip now supports [configuring]({organization_settings_url}) a channel where Zulip will
@@ -500,6 +514,7 @@ a channel within one week, your organization will not miss any update messages.
organization_settings_url="/#organization/organization-settings",
)
else:
topic_name = get_topic_name_for_zulip_update_announcements(realm)
content = """
Starting tomorrow, users in your organization will receive [updates]({zulip_update_announcements_help_url})
about new Zulip features in #**{zulip_update_announcements_stream}>{topic_name}**.
@@ -560,8 +575,8 @@ def internal_prep_zulip_update_announcements_stream_messages(
message_requests = []
stream = realm.zulip_update_announcements_stream
assert stream is not None
with override_language(realm.default_language):
topic_name = str(realm.ZULIP_UPDATE_ANNOUNCEMENTS_TOPIC_NAME)
topic_name = get_topic_name_for_zulip_update_announcements(realm)
while current_level < latest_level:
content = get_zulip_update_announcements_message_for_level(level=current_level + 1)
message_requests.append(
@@ -671,11 +686,9 @@ def send_zulip_update_announcements_to_realm(realm: Realm, skip_delay: bool) ->
return
# Send an introductory message just before the first update message.
with override_language(realm.default_language):
topic_name = str(realm.ZULIP_UPDATE_ANNOUNCEMENTS_TOPIC_NAME)
stream = realm.zulip_update_announcements_stream
assert stream.recipient_id is not None
topic_name = get_topic_name_for_zulip_update_announcements(realm)
topic_has_messages = messages_for_topic(realm.id, stream.recipient_id, topic_name).exists()
if not topic_has_messages:

View File

@@ -2,6 +2,7 @@ from django.conf import settings
from typing_extensions import Any, override
from zerver.actions.realm_settings import do_set_realm_moderation_request_channel
from zerver.actions.streams import do_set_stream_property
from zerver.lib.markdown.fenced_code import get_unused_fence
from zerver.lib.mention import silent_mention_syntax_for_user
from zerver.lib.message import truncate_content
@@ -15,6 +16,7 @@ from zerver.lib.topic_link_util import (
from zerver.models import UserProfile
from zerver.models.messages import Message
from zerver.models.recipients import get_or_create_direct_message_group
from zerver.models.streams import StreamTopicsPolicyEnum
from zerver.models.users import get_system_bot
@@ -373,3 +375,23 @@ class ReportMessageTest(ZulipTestCase):
)
self.assert_json_success(result)
def test_message_report_to_channel_with_topics_disabled(self) -> None:
notification_bot = get_system_bot(settings.NOTIFICATION_BOT, self.realm.id)
do_set_stream_property(
self.moderation_request_channel,
"topics_policy",
StreamTopicsPolicyEnum.empty_topic_only.value,
self.hamlet,
)
result = self.report_message(
self.hamlet,
self.reported_message_id,
report_type="harassment",
)
self.assert_json_success(result)
report_msg = self.get_last_message()
self.assertEqual(report_msg.sender_id, notification_bot.id)
self.assertEqual(report_msg.topic_name(), "")
self.assertIn("reported", report_msg.content)

View File

@@ -10,6 +10,7 @@ from typing_extensions import override
from corporate.lib.stripe import get_latest_seat_count
from zerver.actions.create_user import notify_new_user
from zerver.actions.streams import do_set_stream_property
from zerver.actions.user_settings import do_change_user_setting
from zerver.lib.initial_password import initial_password
from zerver.lib.test_classes import ZulipTestCase
@@ -17,7 +18,7 @@ from zerver.lib.timezone import canonicalize_timezone
from zerver.models import Message, Recipient, Stream, UserProfile
from zerver.models.realms import get_realm
from zerver.models.recipients import get_direct_message_group_user_ids
from zerver.models.streams import get_stream
from zerver.models.streams import StreamTopicsPolicyEnum, get_stream
from zerver.models.users import get_system_bot
from zerver.signals import JUST_CREATED_THRESHOLD, get_device_browser, get_device_os
@@ -286,6 +287,7 @@ class TestNotifyNewUser(ZulipTestCase):
self.assertEqual(message.recipient.type, Recipient.STREAM)
actual_stream = Stream.objects.get(id=message.recipient.type_id)
self.assertEqual(actual_stream.name, "core team")
self.assertEqual(message.topic_name(), "signups")
self.assertIn(
f"@_**Cordelia, Lear's daughter|{new_user.id}** joined this organization.",
message.content,
@@ -297,6 +299,28 @@ class TestNotifyNewUser(ZulipTestCase):
notify_new_user(new_user)
self.assertEqual(self.get_message_count(), message_count + 1)
def test_notify_realm_of_new_user_in_empty_topic_only_channel(self) -> None:
realm = get_realm("zulip")
iago = self.example_user("iago")
stream = get_stream("core team", realm)
realm.signup_announcements_stream = stream
realm.save(update_fields=["signup_announcements_stream"])
do_set_stream_property(
stream, "topics_policy", StreamTopicsPolicyEnum.empty_topic_only.value, iago
)
new_user = self.example_user("cordelia")
message_count = self.get_message_count()
notify_new_user(new_user)
self.assertEqual(self.get_message_count(), message_count + 1)
message = self.get_last_message()
self.assertEqual(message.topic_name(), "")
self.assertIn(
f"@_**Cordelia, Lear's daughter|{new_user.id}** joined this organization.",
message.content,
)
def test_notify_realm_of_new_user_in_manual_license_management(self) -> None:
realm = get_realm("zulip")
admin_user_ids = set(realm.get_human_admin_users().values_list("id", flat=True))

View File

@@ -30,6 +30,7 @@ from zerver.actions.streams import (
deactivated_streams_by_old_name,
do_change_stream_group_based_setting,
do_deactivate_stream,
do_set_stream_property,
do_unarchive_stream,
)
from zerver.actions.user_groups import bulk_add_members_to_user_groups, check_add_user_group
@@ -3431,6 +3432,7 @@ class SubscriptionAPITest(ZulipTestCase):
self.assertEqual(msg.recipient.type, Recipient.STREAM)
self.assertEqual(msg.recipient.type_id, new_stream_announcements_stream.id)
self.assertEqual(msg.sender_id, self.notification_bot(self.test_realm).id)
self.assertEqual(msg.topic_name(), "new channels")
expected_msg = f"@_**{invitee_full_name}|{invitee.id}** created a new channel #**{invite_streams[0]}**."
self.assertEqual(msg.content, expected_msg)
@@ -3444,6 +3446,41 @@ class SubscriptionAPITest(ZulipTestCase):
)
self.assertEqual(msg.content, expected_msg)
def test_sucessful_subscription_notifies_in_empty_topic_only_stream(self) -> None:
invitee = self.example_user("iago")
invitee_full_name = "Iago"
current_stream = self.get_streams(invitee)[0]
invite_streams = self.make_random_stream_names([current_stream])[:1]
new_stream_announcements_stream = get_stream(current_stream, self.test_realm)
self.test_realm.new_stream_announcements_stream_id = new_stream_announcements_stream.id
self.test_realm.save()
do_set_stream_property(
new_stream_announcements_stream,
"topics_policy",
StreamTopicsPolicyEnum.empty_topic_only.value,
invitee,
)
self.subscribe_via_post(
invitee,
invite_streams,
extra_post_data=dict(
announce="true",
principals=orjson.dumps([self.user_profile.id]).decode(),
),
)
msg = self.get_second_to_last_message()
self.assertEqual(msg.recipient.type, Recipient.STREAM)
self.assertEqual(msg.recipient.type_id, new_stream_announcements_stream.id)
self.assertEqual(msg.sender_id, self.notification_bot(self.test_realm).id)
self.assertEqual(msg.topic_name(), "")
expected_msg = f"@_**{invitee_full_name}|{invitee.id}** created a new channel #**{invite_streams[0]}**."
self.assertEqual(msg.content, expected_msg)
def test_successful_cross_realm_notification(self) -> None:
"""
Calling POST /json/users/me/subscriptions in a new realm

View File

@@ -9,7 +9,7 @@ from django.utils.timezone import now as timezone_now
from typing_extensions import override
from zerver.actions.create_realm import do_create_realm
from zerver.actions.streams import do_deactivate_stream
from zerver.actions.streams import do_deactivate_stream, do_set_stream_property
from zerver.data_import.mattermost import do_convert_data
from zerver.lib.import_realm import do_import_realm
from zerver.lib.message import remove_single_newlines
@@ -21,7 +21,7 @@ from zerver.lib.zulip_update_announcements import (
from zerver.models.messages import Message
from zerver.models.realms import get_realm
from zerver.models.recipients import Recipient, get_direct_message_group_user_ids
from zerver.models.streams import get_stream
from zerver.models.streams import StreamTopicsPolicyEnum, get_stream
from zerver.models.users import get_system_bot
@@ -129,6 +129,42 @@ class ZulipUpdateAnnouncementsTest(ZulipTestCase):
self.assertEqual(stream_messages[1].content, "Announcement message 3.")
self.assertEqual(stream_messages[2].content, "Announcement message 4.")
self.assertEqual(realm.zulip_update_announcements_level, 4)
self.assertEqual(
stream_messages[0].topic_name(), realm.ZULIP_UPDATE_ANNOUNCEMENTS_TOPIC_NAME
)
self.assertEqual(
stream_messages[1].topic_name(), realm.ZULIP_UPDATE_ANNOUNCEMENTS_TOPIC_NAME
)
self.assertEqual(
stream_messages[2].topic_name(), realm.ZULIP_UPDATE_ANNOUNCEMENTS_TOPIC_NAME
)
# Test sending updates with announcements stream allowing empty topics only.
iago = self.example_user("iago")
do_set_stream_property(
verona, "topics_policy", StreamTopicsPolicyEnum.empty_topic_only.value, iago
)
new_updates = [
ZulipUpdateAnnouncement(
level=5,
message="Announcement message 5.",
),
]
self.zulip_update_announcements.extend(new_updates)
with time_machine.travel(now + timedelta(days=15), tick=False):
send_zulip_update_announcements(skip_delay=False)
realm.refresh_from_db()
stream_messages = Message.objects.filter(
realm=realm,
sender=notification_bot,
recipient__type_id=verona.id,
date_sent__gte=now + timedelta(days=15),
).order_by("id")
self.assert_length(stream_messages, 1)
self.assertEqual(stream_messages[0].topic_name(), "")
self.assertEqual(stream_messages[0].content, "Announcement message 5.")
self.assertEqual(realm.zulip_update_announcements_level, 5)
def test_send_zulip_update_announcements_with_stream_configured(self) -> None:
with mock.patch(

View File

@@ -975,6 +975,11 @@ def send_messages_for_new_subscribers(
else:
content = _("{user_name} created a new channel {new_channels}.")
topic_name = _("new channels")
if (
new_stream_announcements_stream.topics_policy
== StreamTopicsPolicyEnum.empty_topic_only.value
):
topic_name = ""
content = content.format(
user_name=silent_mention_syntax_for_user(user_profile),