settings: Add automatically follow and unmute topics policy settings.

This commit adds two user settings, named
* `automatically_follow_topics_policy`
* `automatically_unmute_topics_in_muted_streams_policy`

The settings control the user's preference on which topics they
will automatically 'follow' or 'unmute in muted streams'.

The policies offer four options:
1. Topics I participate in
2. Topics I send a message to
3. Topics I start
4. Never (default)

There is no support for configuring the settings through the UI yet.
This commit is contained in:
Prakhar Pratyush 2023-06-17 21:07:04 +05:30 committed by Tim Abbott
parent c349d1137c
commit 58568a60d6
20 changed files with 1761 additions and 18 deletions

View File

@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 8.0
**Feature level 214**
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
[`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings):
Added two new user settings, `automatically_follow_topics_policy` and
`automatically_unmute_topics_in_muted_streams_policy`. The settings control the
user's preference on which topics the user will automatically 'follow' and
'unmute in muted streams' respectively.
**Feature level 213**
* [`POST /register`](/api/register-queue): Fixed incorrect handling of

View File

@ -230,6 +230,9 @@ python_rules = RuleList(
"good_lines": ["topic_name"],
"bad_lines": ['subject="foo"', " MAX_SUBJECT_LEN"],
"exclude": FILES_WITH_LEGACY_SUBJECT,
"exclude_line": {
("zerver/lib/message.py", "message__subject__iexact=message.topic_name(),"),
},
"include_only": {
"zerver/data_import/",
"zerver/lib/",

View File

@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# Changes should be accompanied by documentation explaining what the
# new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 213
API_FEATURE_LEVEL = 214
# Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump

View File

@ -30,6 +30,7 @@ from django.utils.translation import override as override_language
from django_stubs_ext import ValuesQuerySet
from zerver.actions.uploads import do_claim_attachments
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
from zerver.lib.addressee import Addressee
from zerver.lib.alert_words import get_alert_word_automaton
from zerver.lib.cache import cache_with_key, user_profile_delivery_email_cache_key
@ -50,7 +51,9 @@ from zerver.lib.message import (
check_user_group_mention_allowed,
normalize_body,
render_markdown,
set_visibility_policy_possible,
truncate_topic,
visibility_policy_for_send_message,
wildcard_mention_allowed,
)
from zerver.lib.muted_users import get_muting_users
@ -180,6 +183,7 @@ class RecipientInfoResult:
service_bot_tuples: List[Tuple[int, int]]
all_bot_user_ids: Set[int]
topic_participant_user_ids: Set[int]
sender_muted_stream: Optional[bool]
class ActiveUserDict(TypedDict):
@ -212,6 +216,7 @@ def get_recipient_info(
stream_wildcard_mention_in_followed_topic_user_ids: Set[int] = set()
muted_sender_user_ids: Set[int] = get_muting_users(sender_id)
topic_participant_user_ids: Set[int] = set()
sender_muted_stream: Optional[bool] = None
if recipient.type == Recipient.PERSONAL:
# The sender and recipient may be the same id, so
@ -275,7 +280,14 @@ def get_recipient_info(
.order_by("user_profile_id")
)
message_to_user_ids = [row["user_profile_id"] for row in subscription_rows]
message_to_user_ids = list()
for row in subscription_rows:
message_to_user_ids.append(row["user_profile_id"])
# We store the 'sender_muted_stream' information here to avoid db query at
# a later stage when we perform automatically unmute topic in muted stream operation.
if row["user_profile_id"] == sender_id:
sender_muted_stream = row["is_muted"]
user_id_to_visibility_policy = stream_topic.user_id_to_visibility_policy_dict()
def notification_recipients(setting: str) -> Set[int]:
@ -466,6 +478,7 @@ def get_recipient_info(
service_bot_tuples=service_bot_tuples,
all_bot_user_ids=all_bot_user_ids,
topic_participant_user_ids=topic_participant_user_ids,
sender_muted_stream=sender_muted_stream,
)
@ -642,6 +655,7 @@ def build_message_send_dict(
message_send_dict = SendMessageRequest(
stream=stream,
sender_muted_stream=info.sender_muted_stream,
local_id=local_id,
sender_queue_id=sender_queue_id,
realm=realm,
@ -896,6 +910,8 @@ def do_send_messages(
# This next loop is responsible for notifying other parts of the
# Zulip system about the messages we just committed to the database:
# * Sender automatically follows or unmutes the topic depending on 'automatically_follow_topics_policy'
# and 'automatically_unmute_topics_in_muted_streams_policy' user settings.
# * Notifying clients via send_event
# * Triggering outgoing webhooks via the service event queue.
# * Updating the `first_message_id` field for streams without any message history.
@ -910,6 +926,39 @@ def do_send_messages(
# assert needed because stubs for django are missing
assert send_request.stream is not None
realm_id = send_request.stream.realm_id
sender = send_request.message.sender
# Determine and set the visibility_policy depending on 'automatically_follow_topics_policy'
# and 'automatically_unmute_topics_in_muted_streams_policy'.
if set_visibility_policy_possible(sender, send_request.message) and not (
sender.automatically_follow_topics_policy
== sender.automatically_unmute_topics_in_muted_streams_policy
== UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER
):
try:
user_topic = UserTopic.objects.get(
user_profile=sender,
stream_id=send_request.stream.id,
topic_name__iexact=send_request.message.topic_name(),
)
visibility_policy = user_topic.visibility_policy
except UserTopic.DoesNotExist:
visibility_policy = UserTopic.VisibilityPolicy.INHERIT
new_visibility_policy = visibility_policy_for_send_message(
sender,
send_request.message,
send_request.stream,
send_request.sender_muted_stream,
visibility_policy,
)
if new_visibility_policy:
do_set_user_topic_visibility_policy(
user_profile=sender,
stream=send_request.stream,
topic=send_request.message.topic_name(),
visibility_policy=new_visibility_policy,
)
# Deliver events to the real-time push system, as well as
# enqueuing any additional processing triggered by the message.

View File

@ -1,10 +1,18 @@
from typing import Any, Dict, Optional
from zerver.actions.create_user import create_historical_user_messages
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
from zerver.lib.emoji import check_emoji_request, get_emoji_data
from zerver.lib.exceptions import ReactionExistsError
from zerver.lib.message import access_message, update_to_dict_cache
from zerver.lib.message import (
access_message,
set_visibility_policy_possible,
should_change_visibility_policy,
update_to_dict_cache,
visibility_policy_for_participation,
)
from zerver.lib.stream_subscription import subscriber_ids_with_stream_history_access
from zerver.lib.streams import access_stream_by_id
from zerver.models import Message, Reaction, Recipient, Stream, UserMessage, UserProfile
from zerver.tornado.django_api import send_event_on_commit
@ -82,6 +90,32 @@ def do_add_reaction(
reaction.save()
# Determine and set the visibility_policy depending on 'automatically_follow_topics_policy'
# and 'automatically_unmute_topics_in_muted_streams_policy'.
if set_visibility_policy_possible(
user_profile, message
) and UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION in [
user_profile.automatically_follow_topics_policy,
user_profile.automatically_unmute_topics_in_muted_streams_policy,
]:
stream_id = message.recipient.type_id
(stream, sub) = access_stream_by_id(user_profile, stream_id)
assert stream is not None
if sub:
new_visibility_policy = visibility_policy_for_participation(user_profile, sub.is_muted)
if new_visibility_policy and should_change_visibility_policy(
new_visibility_policy,
user_profile,
stream_id,
topic_name=message.topic_name(),
):
do_set_user_topic_visibility_policy(
user_profile=user_profile,
stream=stream,
topic=message.topic_name(),
visibility_policy=new_visibility_policy,
)
notify_reaction_update(user_profile, message, reaction, "add")

View File

@ -1,7 +1,14 @@
from django.utils.translation import gettext as _
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
from zerver.lib.exceptions import JsonableError
from zerver.models import Realm, SubMessage, UserMessage
from zerver.lib.message import (
set_visibility_policy_possible,
should_change_visibility_policy,
visibility_policy_for_participation,
)
from zerver.lib.streams import access_stream_by_id
from zerver.models import Realm, SubMessage, UserMessage, UserProfile
from zerver.tornado.django_api import send_event_on_commit
@ -48,6 +55,33 @@ def do_add_submessage(
)
submessage.save()
# Determine and set the visibility_policy depending on 'automatically_follow_topics_policy'
# and 'automatically_unmute_topics_policy'.
sender = submessage.sender
if set_visibility_policy_possible(
sender, submessage.message
) and UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION in [
sender.automatically_follow_topics_policy,
sender.automatically_unmute_topics_in_muted_streams_policy,
]:
stream_id = submessage.message.recipient.type_id
(stream, sub) = access_stream_by_id(sender, stream_id)
assert stream is not None
if sub:
new_visibility_policy = visibility_policy_for_participation(sender, sub.is_muted)
if new_visibility_policy and should_change_visibility_policy(
new_visibility_policy,
sender,
stream_id,
topic_name=submessage.message.topic_name(),
):
do_set_user_topic_visibility_policy(
user_profile=sender,
stream=stream,
topic=submessage.message.topic_name(),
visibility_policy=new_visibility_policy,
)
event = dict(
type="submessage",
msg_type=msg_type,

View File

@ -20,7 +20,7 @@ import ahocorasick
import orjson
from django.conf import settings
from django.db import connection
from django.db.models import Max, Sum
from django.db.models import Max, QuerySet, Sum
from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _
from django_stubs_ext import ValuesQuerySet
@ -47,9 +47,15 @@ from zerver.lib.stream_subscription import (
get_subscribed_stream_recipient_ids_for_user,
num_subscribers_for_stream_id,
)
from zerver.lib.streams import get_web_public_streams_queryset
from zerver.lib.streams import can_access_stream_history, get_web_public_streams_queryset
from zerver.lib.timestamp import datetime_to_timestamp
from zerver.lib.topic import DB_TOPIC_NAME, MESSAGE__TOPIC, TOPIC_LINKS, TOPIC_NAME
from zerver.lib.topic import (
DB_TOPIC_NAME,
MESSAGE__TOPIC,
TOPIC_LINKS,
TOPIC_NAME,
messages_for_topic,
)
from zerver.lib.types import DisplayRecipientT, EditHistoryEvent, UserDisplayRecipient
from zerver.lib.url_preview.types import UrlEmbedData
from zerver.lib.user_groups import is_user_in_group
@ -147,6 +153,7 @@ class SendMessageRequest:
message: Message
rendering_result: MessageRenderingResult
stream: Optional[Stream]
sender_muted_stream: Optional[bool]
local_id: Optional[str]
sender_queue_id: Optional[str]
realm: Realm
@ -1713,3 +1720,180 @@ def update_to_dict_cache(
cache_set_many(items_for_remote_cache)
return message_ids
def visibility_policy_for_participation(
sender: UserProfile,
is_stream_muted: Optional[bool],
) -> Optional[int]:
"""
This function determines the visibility policy to set when a user
participates in a topic, depending on the 'automatically_follow_topics_policy'
and 'automatically_unmute_topics_in_muted_streams_policy' settings.
"""
if (
sender.automatically_follow_topics_policy
== UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION
):
return UserTopic.VisibilityPolicy.FOLLOWED
if (
is_stream_muted
and sender.automatically_unmute_topics_in_muted_streams_policy
== UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION
):
return UserTopic.VisibilityPolicy.UNMUTED
return None
def visibility_policy_for_send(
sender: UserProfile,
is_stream_muted: Optional[bool],
) -> Optional[int]:
if (
sender.automatically_follow_topics_policy
== UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND
):
return UserTopic.VisibilityPolicy.FOLLOWED
if (
is_stream_muted
and sender.automatically_unmute_topics_in_muted_streams_policy
== UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND
):
return UserTopic.VisibilityPolicy.UNMUTED
return None
def visibility_policy_for_send_message(
sender: UserProfile,
message: Message,
stream: Stream,
is_stream_muted: Optional[bool],
current_visibility_policy: int,
) -> Optional[int]:
"""
This function determines the visibility policy to set when a message
is sent to a topic, depending on the 'automatically_follow_topics_policy'
and 'automatically_unmute_topics_in_muted_streams_policy' settings.
It returns None when the policies can't make it more visible than the
current visibility policy.
"""
# We prioritize 'FOLLOW' over 'UNMUTE' in muted streams.
# We need to carefully handle the following two cases:
#
# 1. When an action qualifies for multiple values. Example:
# - starting a topic is INITIATION, PARTICIPATION as well as SEND
# - sending a non-first message is PARTICIPATION as well as SEND
# action | 'automatically_follow_topics_policy' | 'automatically_unmute_topics_in_muted_streams_policy' | visibility_policy
# start | ON_PARTICIPATION / ON_SEND | ON_INITIATION | FOLLOWED
# send | ON_SEND / ON_PARTICIPATION | ON_PARTICIPATION / ON_SEND | FOLLOWED
#
# 2. When both the policies have the same values.
# action | 'automatically_follow_topics_policy' | 'automatically_unmute_topics_in_muted_streams_policy' | visibility_policy
# start | ON_INITIATION | ON_INITIATION | FOLLOWED
# partc | ON_PARTICIPATION | ON_PARTICIPATION | FOLLOWED
# send | ON_SEND | ON_SEND | FOLLOWED
visibility_policy = None
if current_visibility_policy == UserTopic.VisibilityPolicy.FOLLOWED:
return visibility_policy
visibility_policy_participation = visibility_policy_for_participation(sender, is_stream_muted)
visibility_policy_send = visibility_policy_for_send(sender, is_stream_muted)
if UserTopic.VisibilityPolicy.FOLLOWED in (
visibility_policy_participation,
visibility_policy_send,
):
return UserTopic.VisibilityPolicy.FOLLOWED
if UserTopic.VisibilityPolicy.UNMUTED in (
visibility_policy_participation,
visibility_policy_send,
):
visibility_policy = UserTopic.VisibilityPolicy.UNMUTED
# If a topic has a visibility policy set, it can't be the case
# of initiation. We return early, thus saving a DB query.
if current_visibility_policy != UserTopic.VisibilityPolicy.INHERIT:
if visibility_policy and current_visibility_policy == visibility_policy:
return None
return visibility_policy
# Now we need to check if the user initiated the topic.
old_accessible_messages_in_topic: Union[QuerySet[Message], QuerySet[UserMessage]]
if can_access_stream_history(sender, stream):
old_accessible_messages_in_topic = messages_for_topic(
realm_id=sender.realm_id,
stream_recipient_id=message.recipient_id,
topic_name=message.topic_name(),
).exclude(id=message.id)
else:
# We use the user's own message access to avoid leaking information in
# private streams with protected history.
old_accessible_messages_in_topic = UserMessage.objects.filter(
user_profile=sender,
message__recipient_id=message.recipient_id,
message__subject__iexact=message.topic_name(),
).exclude(message_id=message.id)
if (
sender.automatically_follow_topics_policy
== UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION
and not old_accessible_messages_in_topic.exists()
):
return UserTopic.VisibilityPolicy.FOLLOWED
if (
is_stream_muted
and sender.automatically_unmute_topics_in_muted_streams_policy
== UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION
and not old_accessible_messages_in_topic.exists()
):
visibility_policy = UserTopic.VisibilityPolicy.UNMUTED
return visibility_policy
def should_change_visibility_policy(
new_visibility_policy: int,
sender: UserProfile,
stream_id: int,
topic_name: str,
) -> bool:
try:
user_topic = UserTopic.objects.get(
user_profile=sender, stream_id=stream_id, topic_name__iexact=topic_name
)
except UserTopic.DoesNotExist:
return True
current_visibility_policy = user_topic.visibility_policy
if new_visibility_policy == current_visibility_policy:
return False
# The intent of these "automatically follow or unmute" policies is that they
# can only increase the user's visibility policy for the topic. If a topic is
# already FOLLOWED, we don't change the state to UNMUTED due to these policies.
if current_visibility_policy == UserTopic.VisibilityPolicy.FOLLOWED:
return False
return True
def set_visibility_policy_possible(user_profile: UserProfile, message: Message) -> bool:
"""If the user can set a visibility policy."""
if not message.is_stream_message():
return False
if user_profile.is_bot:
return False
if user_profile.realm != message.get_realm():
return False
return True

View File

@ -0,0 +1,32 @@
# Generated by Django 4.2.5 on 2023-09-19 10:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0475_realm_jitsi_server_url"),
]
operations = [
migrations.AddField(
model_name="realmuserdefault",
name="automatically_follow_topics_policy",
field=models.PositiveSmallIntegerField(default=3),
),
migrations.AddField(
model_name="realmuserdefault",
name="automatically_unmute_topics_in_muted_streams_policy",
field=models.PositiveSmallIntegerField(default=3),
),
migrations.AddField(
model_name="userprofile",
name="automatically_follow_topics_policy",
field=models.PositiveSmallIntegerField(default=3),
),
migrations.AddField(
model_name="userprofile",
name="automatically_unmute_topics_in_muted_streams_policy",
field=models.PositiveSmallIntegerField(default=3),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 4.2.5 on 2023-10-02 05:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0476_realmuserdefault_automatically_follow_topics_policy_and_more"),
]
operations = [
migrations.AlterField(
model_name="realmuserdefault",
name="automatically_follow_topics_policy",
field=models.PositiveSmallIntegerField(default=4),
),
migrations.AlterField(
model_name="realmuserdefault",
name="automatically_unmute_topics_in_muted_streams_policy",
field=models.PositiveSmallIntegerField(default=4),
),
migrations.AlterField(
model_name="userprofile",
name="automatically_follow_topics_policy",
field=models.PositiveSmallIntegerField(default=4),
),
migrations.AlterField(
model_name="userprofile",
name="automatically_unmute_topics_in_muted_streams_policy",
field=models.PositiveSmallIntegerField(default=4),
),
]

View File

@ -1642,6 +1642,30 @@ class UserBaseSettings(models.Model):
default=REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_AUTOMATIC
)
# The following two settings control which topics to automatically
# 'follow' or 'unmute in a muted stream', respectively.
# Follow or unmute a topic automatically on:
# - PARTICIPATION: Send a message, React to a message, Participate in a poll or Edit a TO-DO list.
# - SEND: Send a message.
# - INITIATION: Send the first message in the topic.
# - NEVER: Never automatically follow or unmute a topic.
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION = 1
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND = 2
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION = 3
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER = 4
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES = [
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND,
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
]
automatically_follow_topics_policy = models.PositiveSmallIntegerField(
default=AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER
)
automatically_unmute_topics_in_muted_streams_policy = models.PositiveSmallIntegerField(
default=AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER
)
# Whether or not the user wants to sync their drafts.
enable_drafts_synchronization = models.BooleanField(default=True)
@ -1737,6 +1761,8 @@ class UserBaseSettings(models.Model):
enable_followed_topic_push_notifications=bool,
enable_followed_topic_audible_notifications=bool,
enable_followed_topic_wildcard_mentions_notify=bool,
automatically_follow_topics_policy=int,
automatically_unmute_topics_in_muted_streams_policy=int,
)
notification_setting_types = {

View File

@ -10206,6 +10206,44 @@ paths:
- 2
- 3
example: 1
- name: automatically_follow_topics_policy
in: query
description: |
Which [topics to follow automatically](/help/mute-a-topic).
- 1 - Topics the user participates in
- 2 - Topics the user sends a message to
- 3 - Topics the user starts
- 4 - Never
**Changes**: New in Zulip 8.0 (feature level 214).
schema:
type: integer
enum:
- 1
- 2
- 3
- 4
example: 1
- name: automatically_unmute_topics_in_muted_streams_policy
in: query
description: |
Which [topics to unmute automatically in muted streams](/help/mute-a-topic).
- 1 - Topics the user participates in
- 2 - Topics the user sends a message to
- 3 - Topics the user starts
- 4 - Never
**Changes**: New in Zulip 8.0 (feature level 214).
schema:
type: integer
enum:
- 1
- 2
- 3
- 4
example: 1
- name: presence_enabled
in: query
description: |
@ -12422,6 +12460,28 @@ paths:
**Changes**: New in Zulip 7.0 (feature level 168), replacing the
previous `realm_name_in_notifications` boolean;
`true` corresponded to `Always`, and `false` to `Never`.
automatically_follow_topics_policy:
type: integer
description: |
Which [topics to follow automatically](/help/mute-a-topic).
- 1 - Topics the user participates in
- 2 - Topics the user sends a message to
- 3 - Topics the user starts
- 4 - Never
**Changes**: New in Zulip 8.0 (feature level 214).
automatically_unmute_topics_in_muted_streams_policy:
type: integer
description: |
Which [topics to unmute automatically in muted streams](/help/mute-a-topic).
- 1 - Topics the user participates in
- 2 - Topics the user sends a message to
- 3 - Topics the user starts
- 4 - Never
**Changes**: New in Zulip 8.0 (feature level 214).
presence_enabled:
type: boolean
description: |
@ -14561,6 +14621,28 @@ paths:
**Changes**: New in Zulip 7.0 (feature level 168), replacing the
previous `realm_name_in_notifications` boolean;
`true` corresponded to `Always`, and `false` to `Never`.
automatically_follow_topics_policy:
type: integer
description: |
Which [topics to follow automatically](/help/mute-a-topic).
- 1 - Topics the user participates in
- 2 - Topics the user sends a message to
- 3 - Topics the user starts
- 4 - Never
**Changes**: New in Zulip 8.0 (feature level 214).
automatically_unmute_topics_in_muted_streams_policy:
type: integer
description: |
Which [topics to unmute automatically in muted streams](/help/mute-a-topic).
- 1 - Topics the user participates in
- 2 - Topics the user sends a message to
- 3 - Topics the user starts
- 4 - Never
**Changes**: New in Zulip 8.0 (feature level 214).
presence_enabled:
type: boolean
description: |
@ -15808,6 +15890,44 @@ paths:
- 2
- 3
example: 1
- name: automatically_follow_topics_policy
in: query
description: |
Which [topics to follow automatically](/help/mute-a-topic).
- 1 - Topics the user participates in
- 2 - Topics the user sends a message to
- 3 - Topics the user starts
- 4 - Never
**Changes**: New in Zulip 8.0 (feature level 214).
schema:
type: integer
enum:
- 1
- 2
- 3
- 4
example: 1
- name: automatically_unmute_topics_in_muted_streams_policy
in: query
description: |
Which [topics to unmute automatically in muted streams](/help/mute-a-topic).
- 1 - Topics the user participates in
- 2 - Topics the user sends a message to
- 3 - Topics the user starts
- 4 - Never
**Changes**: New in Zulip 8.0 (feature level 214).
schema:
type: integer
enum:
- 1
- 2
- 3
- 4
example: 1
- name: presence_enabled
in: query
description: |

View File

@ -535,11 +535,195 @@ class NormalActionsTest(BaseAction):
)
def test_stream_send_message_events(self) -> None:
user_profile = self.example_user("hamlet")
events = self.verify_action(
lambda: self.send_stream_message(user_profile, "Verona", "hello"),
client_gravatar=False,
hamlet = self.example_user("hamlet")
for stream_name in ["Verona", "Denmark", "core team"]:
stream = get_stream(stream_name, hamlet.realm)
sub = get_subscription(stream.name, hamlet)
do_change_subscription_property(hamlet, sub, stream, "is_muted", True, acting_user=None)
def verify_events_generated_and_reset_visibility_policy(
events: List[Dict[str, Any]], stream_name: str, topic: str
) -> None:
# event-type: muted_topics
check_muted_topics("events[0]", events[0])
# event-type: user_topic
check_user_topic("events[1]", events[1])
if events[2]["type"] == "message":
check_message("events[2]", events[2])
else:
# event-type: reaction
check_reaction_add("events[2]", events[2])
# Reset visibility policy
do_set_user_topic_visibility_policy(
hamlet,
get_stream(stream_name, hamlet.realm),
topic,
visibility_policy=UserTopic.VisibilityPolicy.INHERIT,
)
# Events generated during send message action depends on the 'automatically_follow_topics_policy'
# and 'automatically_unmute_topics_in_muted_streams_policy' settings. Here we test all the
# possible combinations.
# action: participation
# 'automatically_follow_topics_policy' | 'automatically_unmute_topics_in_muted_streams_policy' | visibility_policy
# ON_PARTICIPATION | ON_INITIATION | FOLLOWED
# ON_PARTICIPATION | ON_PARTICIPATION | FOLLOWED
# ON_PARTICIPATION | ON_SEND | FOLLOWED
# ON_PARTICIPATION | NEVER | FOLLOWED
message_id = self.send_stream_message(hamlet, "Verona", "hello", "topic")
message = Message.objects.get(id=message_id)
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
for setting_value in UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES:
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_unmute_topics_in_muted_streams_policy",
setting_value=setting_value,
acting_user=None,
)
# Three events are generated:
# 2 for following the topic and 1 for adding reaction.
events = self.verify_action(
lambda: do_add_reaction(hamlet, message, "tada", "1f389", "unicode_emoji"),
client_gravatar=False,
num_events=3,
)
verify_events_generated_and_reset_visibility_policy(events, "Verona", "topic")
do_remove_reaction(hamlet, message, "1f389", "unicode_emoji")
# action: send
# 'automatically_follow_topics_policy' | 'automatically_unmute_topics_in_muted_streams_policy' | visibility_policy
# ON_SEND | ON_INITIATION | FOLLOWED
# ON_SEND | ON_PARTICIPATION | FOLLOWED
# ON_SEND | ON_SEND | FOLLOWED
# ON_SEND | NEVER | FOLLOWED
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND,
acting_user=None,
)
for setting_value in UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES:
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_unmute_topics_in_muted_streams_policy",
setting_value=setting_value,
acting_user=None,
)
# Three events are generated:
# 2 for following the topic and 1 for the message sent.
events = self.verify_action(
lambda: self.send_stream_message(hamlet, "Verona", "hello", "topic"),
client_gravatar=False,
num_events=3,
)
verify_events_generated_and_reset_visibility_policy(events, "Verona", "topic")
# action: initiation
# 'automatically_follow_topics_policy' | 'automatically_unmute_topics_in_muted_streams_policy' | visibility_policy
# ON_INITIATION | ON_INITIATION | FOLLOWED
# ON_INITIATION | ON_PARTICIPATION | FOLLOWED
# ON_INITIATION | ON_SEND | FOLLOWED
# ON_INITIATION | NEVER | FOLLOWED
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
for index, setting_value in enumerate(
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES
):
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_unmute_topics_in_muted_streams_policy",
setting_value=setting_value,
acting_user=None,
)
# Three events are generated:
# 2 for following the topic and 1 for the message sent.
send_message = lambda index=index: self.send_stream_message(
hamlet, "Denmark", "hello", f"new topic {index}"
)
events = self.verify_action(
send_message,
client_gravatar=False,
num_events=3,
)
verify_events_generated_and_reset_visibility_policy(
events, "Denmark", f"new topic {index}"
)
# 'automatically_follow_topics_policy' | 'automatically_unmute_topics_in_muted_streams_policy' | visibility_policy
# NEVER | ON_INITIATION | UNMUTED
# NEVER | ON_PARTICIPATION | UNMUTED
# NEVER | ON_SEND | UNMUTED
# NEVER | NEVER | NA
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
for setting_value in [
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND,
]:
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_unmute_topics_in_muted_streams_policy",
setting_value=setting_value,
acting_user=None,
)
# Three events are generated:
# 2 for unmuting the topic and 1 for the message sent.
events = self.verify_action(
lambda: self.send_stream_message(hamlet, "core team", "hello", "topic"),
client_gravatar=False,
num_events=3,
)
verify_events_generated_and_reset_visibility_policy(events, "core team", "topic")
# If current_visibility_policy is already set to the value the policies would set.
do_set_user_topic_visibility_policy(
hamlet,
get_stream("core team", hamlet.realm),
"new Topic",
visibility_policy=UserTopic.VisibilityPolicy.UNMUTED,
)
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_unmute_topics_in_muted_streams_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# 1 event for the message sent
events = self.verify_action(
lambda: self.send_stream_message(hamlet, "core team", "hello", "new Topic"),
client_gravatar=False,
num_events=1,
)
do_change_user_setting(
user_profile=hamlet,
setting_name="automatically_unmute_topics_in_muted_streams_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
# Only one message event is generated
events = self.verify_action(
lambda: self.send_stream_message(hamlet, "core team", "hello"),
client_gravatar=True,
)
# event-type: message
check_message("events[0]", events[0])
assert isinstance(events[0]["message"]["avatar_url"], str)
@ -551,7 +735,7 @@ class NormalActionsTest(BaseAction):
)
events = self.verify_action(
lambda: self.send_stream_message(user_profile, "Verona", "hello"),
lambda: self.send_stream_message(hamlet, "core team", "hello"),
client_gravatar=True,
)
check_message("events[0]", events[0])
@ -560,13 +744,9 @@ class NormalActionsTest(BaseAction):
# Here we add coverage for the case where 'apply_unread_message_event'
# should be called and unread messages in unmuted or followed topic in
# muted stream is treated as unmuted stream message, thus added to 'unmuted_stream_msgs'.
stream = get_stream("Verona", user_profile.realm)
sub = get_subscription(stream.name, user_profile)
do_change_subscription_property(
user_profile, sub, stream, "is_muted", True, acting_user=None
)
stream = get_stream("Verona", hamlet.realm)
do_set_user_topic_visibility_policy(
user_profile,
hamlet,
stream,
"test",
visibility_policy=UserTopic.VisibilityPolicy.UNMUTED,
@ -2063,6 +2243,8 @@ class NormalActionsTest(BaseAction):
"desktop_icon_count_display",
"presence_enabled",
"realm_name_in_email_notifications_policy",
"automatically_follow_topics_policy",
"automatically_unmute_topics_in_muted_streams_policy",
]:
# These settings are tested in their own tests.
continue
@ -2201,6 +2383,38 @@ class NormalActionsTest(BaseAction):
check_user_settings_update("events[0]", events[0])
check_update_global_notifications("events[1]", events[1], 2)
def test_change_automatically_follow_topics_policy(self) -> None:
notification_setting = "automatically_follow_topics_policy"
for setting_value in UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES:
events = self.verify_action(
partial(
do_change_user_setting,
self.user_profile,
notification_setting,
setting_value,
acting_user=self.user_profile,
),
num_events=1,
)
check_user_settings_update("events[0]", events[0])
def test_change_automatically_unmute_topics_in_muted_streams_policy(self) -> None:
notification_setting = "automatically_unmute_topics_in_muted_streams_policy"
for setting_value in UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES:
events = self.verify_action(
partial(
do_change_user_setting,
self.user_profile,
notification_setting,
setting_value,
acting_user=self.user_profile,
),
num_events=1,
)
check_user_settings_update("events[0]", events[0])
def test_realm_update_org_type(self) -> None:
realm = self.user_profile.realm
@ -3129,6 +3343,8 @@ class RealmPropertyActionTest(BaseAction):
email_notifications_batching_period_seconds=[120, 300],
email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_TYPES,
realm_name_in_email_notifications_policy=UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES,
automatically_follow_topics_policy=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES,
automatically_unmute_topics_in_muted_streams_policy=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES,
)
vals = test_values.get(name)

View File

@ -1454,12 +1454,24 @@ class StreamMessagesTest(ZulipTestCase):
topic_name = "foo"
content = "whatever"
# Note: We don't need to assert the db query count for each possible
# combination of 'automatically_follow_topics_policy' and 'automatically_unmute_topics_in_muted_streams_policy',
# as the query count depends only on the actions, i.e., 'ON_INITIATION',
# 'ON_PARTICIPATION', and 'NEVER', and is independent of the final visibility_policy set.
# Asserting query count using one of the above-mentioned settings fulfils our purpose.
# To get accurate count of the queries, we should make sure that
# caches don't come into play. If we count queries while caches are
# filled, we will get a lower count. Caches are not supposed to be
# persistent, so our test can also fail if cache is invalidated
# during the course of the unit test.
flush_per_request_caches()
do_change_user_setting(
user_profile=sender,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
with self.assert_database_query_count(13):
check_send_stream_message(
sender=sender,
@ -1469,6 +1481,57 @@ class StreamMessagesTest(ZulipTestCase):
body=content,
)
do_change_user_setting(
user_profile=sender,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
# There will be an increase in the query count of 5 while sending
# the first message to a topic.
# 5 queries: 1 to check if it is the first message in the topic +
# 1 to check if the topic is already followed + 3 to follow the topic.
flush_per_request_caches()
with self.assert_database_query_count(18):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic="new topic",
body=content,
)
do_change_user_setting(
user_profile=sender,
setting_name="automatically_follow_topics_policy",
setting_value=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
self.send_stream_message(self.example_user("iago"), stream_name, "Hello", "topic 2")
# There will be an increase in the query count of 4 while sending
# a message to a topic with visibility policy other than FOLLOWED.
# 1 to check if the topic is already followed + 3 queries to follow the topic.
flush_per_request_caches()
with self.assert_database_query_count(17):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic="topic 2",
body=content,
)
# If the topic is already FOLLOWED, there will be an increase in the query
# count of 1 to check if the topic is already followed.
flush_per_request_caches()
with self.assert_database_query_count(14):
check_send_stream_message(
sender=sender,
client=sending_client,
stream_name=stream_name,
topic="topic 2",
body=content,
)
def test_stream_message_dict(self) -> None:
user_profile = self.example_user("iago")
self.subscribe(user_profile, "Denmark")

View File

@ -1345,6 +1345,8 @@ class RealmAPITest(ZulipTestCase):
email_notifications_batching_period_seconds=[120, 300],
email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_TYPES,
realm_name_in_email_notifications_policy=UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES,
automatically_follow_topics_policy=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES,
automatically_unmute_topics_in_muted_streams_policy=UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES,
)
vals = test_values.get(name)

View File

@ -362,6 +362,8 @@ class ChangeSettingsTest(ZulipTestCase):
desktop_icon_count_display=2,
email_address_visibility=3,
realm_name_in_email_notifications_policy=2,
automatically_follow_topics_policy=1,
automatically_unmute_topics_in_muted_streams_policy=1,
)
self.login("hamlet")

View File

@ -1,12 +1,16 @@
from datetime import datetime, timezone
from typing import Any, Dict, List
import orjson
import time_machine
from django.utils.timezone import now as timezone_now
from zerver.actions.reactions import check_add_reaction
from zerver.actions.user_settings import do_change_user_setting
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
from zerver.lib.stream_topic import StreamTopicTarget
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import get_subscription
from zerver.lib.user_topics import get_topic_mutes, topic_has_visibility_policy
from zerver.models import UserProfile, UserTopic, get_stream
@ -638,3 +642,895 @@ class UnmutedTopicsTests(ZulipTestCase):
result = self.api_post(user, url, data)
self.assert_json_error(result, "Invalid stream ID")
class AutomaticallyFollowTopicsTests(ZulipTestCase):
def test_automatically_follow_topic_on_initiation(self) -> None:
hamlet = self.example_user("hamlet")
cordelia = self.example_user("cordelia")
iago = self.example_user("iago")
stream = get_stream("Verona", hamlet.realm)
topic_name = "teST topic"
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, set())
# For hamlet & cordelia,
# 'automatically_follow_topics_policy' set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION'.
for user in [hamlet, cordelia]:
do_change_user_setting(
user,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
# Hamlet starts a topic. DO automatically follow the topic.
self.send_stream_message(hamlet, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {hamlet.id})
# Cordelia sends a message to the topic which hamlet started. DON'T automatically follow the topic.
self.send_stream_message(cordelia, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {hamlet.id})
# Iago has 'automatically_follow_topics_policy' set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER'.
# DON'T automatically follow the topic, even if he starts the topic.
do_change_user_setting(
iago,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
self.send_stream_message(iago, stream_name=stream.name, topic_name="New Topic")
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name="New Topic",
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, set())
# When a user sends the first message to a topic with protected history,
# the user starts that topic from their perspective. So, the user
# should follow the topic if 'automatically_follow_topics_policy' is set
# to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION', even if the message
# is not the first message in the topic.
private_stream = self.make_stream(stream_name="private stream", invite_only=True)
self.subscribe(iago, private_stream.name)
self.send_stream_message(iago, private_stream.name)
# Hamlet should automatically follow the topic, even if it already has messages.
self.subscribe(hamlet, private_stream.name)
self.send_stream_message(hamlet, private_stream.name)
stream_topic_target = StreamTopicTarget(
stream_id=private_stream.id,
topic_name="test",
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {hamlet.id})
def test_automatically_follow_topic_on_send(self) -> None:
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", hamlet.realm)
topic_name = "teST topic"
self.send_stream_message(aaron, stream.name, "hello", topic_name)
# For hamlet, 'automatically_follow_topics_policy' set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND'.
do_change_user_setting(
hamlet,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND,
acting_user=None,
)
# For aaron, 'automatically_follow_topics_policy' NOT set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND'.
do_change_user_setting(
aaron,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, set())
# Hamlet sends a message. DO automatically follow the topic.
# Aaron sends a message. DON'T automatically follow the topic.
self.send_stream_message(hamlet, stream_name=stream.name, topic_name=topic_name)
self.send_stream_message(aaron, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {hamlet.id})
def test_automatically_follow_topic_on_participation_send_message(self) -> None:
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", hamlet.realm)
topic_name = "teST topic"
# For hamlet, 'automatically_follow_topics_policy' set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
hamlet,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# For aaron, 'automatically_follow_topics_policy' NOT set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
aaron,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, set())
# Hamlet sends a message. DO automatically follow the topic.
# Aaron sends a message. DON'T automatically follow the topic.
self.send_stream_message(hamlet, stream_name=stream.name, topic_name=topic_name)
self.send_stream_message(aaron, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {hamlet.id})
def test_automatically_follow_topic_on_participation_add_reaction(self) -> None:
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", aaron.realm)
topic_name = "teST topic"
# For cordelia, 'automatically_follow_topics_policy' set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
cordelia,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# For aaron, 'automatically_follow_topics_policy' NOT set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
aaron,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, set())
message_id = self.send_stream_message(
hamlet, stream_name=stream.name, topic_name=topic_name
)
# Cordelia reacts to a message. DO automatically follow the topic.
# Aaron reacts to a message. DON'T automatically follow the topic.
check_add_reaction(
user_profile=cordelia,
message_id=message_id,
emoji_name="smile",
emoji_code=None,
reaction_type=None,
)
check_add_reaction(
user_profile=aaron,
message_id=message_id,
emoji_name="smile",
emoji_code=None,
reaction_type=None,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {cordelia.id})
# We don't decrease visibility policy
sub = get_subscription(stream.name, cordelia)
sub.is_muted = True
sub.save()
do_change_user_setting(
cordelia,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
do_change_user_setting(
cordelia,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
check_add_reaction(
user_profile=cordelia,
message_id=message_id,
emoji_name="plus",
emoji_code=None,
reaction_type=None,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {cordelia.id})
# increase visibility policy
do_set_user_topic_visibility_policy(
cordelia,
stream,
topic_name,
visibility_policy=UserTopic.VisibilityPolicy.MUTED,
)
check_add_reaction(
user_profile=cordelia,
message_id=message_id,
emoji_name="heart",
emoji_code=None,
reaction_type=None,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {cordelia.id})
# Add test coverage for 'should_change_visibility_policy' when
# new_visibility_policy == current_visibility_policy
check_add_reaction(
user_profile=cordelia,
message_id=message_id,
emoji_name="tada",
emoji_code=None,
reaction_type=None,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {cordelia.id})
def test_automatically_follow_topic_on_participation_participate_in_poll(self) -> None:
iago = self.example_user("iago")
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", aaron.realm)
topic_name = "teST topic"
# For iago, 'automatically_follow_topics_policy' set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
iago,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# For aaron, 'automatically_follow_topics_policy' NOT set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
aaron,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, set())
# Hamlet creates a poll.
payload = dict(
type="stream",
to=orjson.dumps(stream.name).decode(),
topic=topic_name,
content="/poll Preference?\n\nyes\nno",
)
result = self.api_post(hamlet, "/api/v1/messages", payload)
self.assert_json_success(result)
# Iago participates in the poll. DO automatically follow the topic.
# Aaron participates in the poll. DON'T automatically follow the topic.
message = self.get_last_message()
def participate_in_poll(user: UserProfile, data: Dict[str, object]) -> None:
content = orjson.dumps(data).decode()
payload = dict(
message_id=message.id,
msg_type="widget",
content=content,
)
result = self.api_post(user, "/api/v1/submessage", payload)
self.assert_json_success(result)
participate_in_poll(iago, dict(type="vote", key="1,1", vote=1))
participate_in_poll(aaron, dict(type="new_option", idx=7, option="maybe"))
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {iago.id})
def test_automatically_follow_topic_on_participation_edit_todo_list(self) -> None:
othello = self.example_user("othello")
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", aaron.realm)
topic_name = "teST topic"
# For othello, 'automatically_follow_topics_policy' set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
othello,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# For aaron, 'automatically_follow_topics_policy' NOT set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
aaron,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, set())
# Hamlet creates a todo list.
payload = dict(
type="stream",
to=orjson.dumps(stream.name).decode(),
topic=topic_name,
content="/todo",
)
result = self.api_post(hamlet, "/api/v1/messages", payload)
self.assert_json_success(result)
# Othello edits the todo list. DO automatically follow the topic.
# Aaron edits the todo list. DON'T automatically follow the topic.
message = self.get_last_message()
def edit_todo_list(user: UserProfile, data: Dict[str, object]) -> None:
content = orjson.dumps(data).decode()
payload = dict(
message_id=message.id,
msg_type="widget",
content=content,
)
result = self.api_post(user, "/api/v1/submessage", payload)
self.assert_json_success(result)
edit_todo_list(othello, dict(type="new_task", key=7, task="eat", desc="", completed=False))
edit_todo_list(aaron, dict(type="strike", key="5,9"))
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.FOLLOWED
)
self.assertEqual(user_ids, {othello.id})
class AutomaticallyUnmuteTopicsTests(ZulipTestCase):
def test_automatically_unmute_topic_on_initiation(self) -> None:
hamlet = self.example_user("hamlet")
cordelia = self.example_user("cordelia")
iago = self.example_user("iago")
stream = get_stream("Verona", hamlet.realm)
topic_name = "teST topic"
for user in [hamlet, cordelia, iago]:
sub = get_subscription(stream.name, user)
sub.is_muted = True
sub.save()
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# For hamlet & cordelia, 'automatically_unmute_topics_in_muted_streams_policy'
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION'.
for user in [hamlet, cordelia]:
do_change_user_setting(
user,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
# Hamlet starts a topic. DO automatically unmute the topic.
self.send_stream_message(hamlet, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {hamlet.id})
# Cordelia sends a message to the topic which hamlet started. DON'T automatically unmute the topic.
self.send_stream_message(cordelia, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {hamlet.id})
# Iago has 'automatically_unmute_topics_in_muted_streams_policy' set to
# 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER'.
# DON'T automatically unmute the topic, even if he starts the topic.
do_change_user_setting(
iago,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
self.send_stream_message(iago, stream_name=stream.name, topic_name="New Topic")
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name="New Topic",
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# When a user sends the first message to a topic with protected history,
# the user starts that topic from their perspective. So, the user
# should unmute the topic if 'automatically_unmute_topics_in_muted_streams_policy'
# is set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION', even if
# the message is not the first message in the topic.
private_stream = self.make_stream(stream_name="private stream", invite_only=True)
self.subscribe(iago, private_stream.name)
self.send_stream_message(iago, private_stream.name)
# Hamlet should automatically unmute the topic, even if it already has messages.
self.subscribe(hamlet, private_stream.name)
sub = get_subscription(private_stream.name, hamlet)
sub.is_muted = True
sub.save()
self.send_stream_message(hamlet, private_stream.name)
stream_topic_target = StreamTopicTarget(
stream_id=private_stream.id,
topic_name="test",
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {hamlet.id})
def test_automatically_unmute_topic_on_send(self) -> None:
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", hamlet.realm)
topic_name = "teST topic"
self.send_stream_message(aaron, stream.name, "hello", topic_name)
for user in [hamlet, aaron]:
sub = get_subscription(stream.name, user)
sub.is_muted = True
sub.save()
# For hamlet, 'automatically_unmute_topics_in_muted_streams_policy'
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND'.
do_change_user_setting(
hamlet,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND,
acting_user=None,
)
# For aaron, 'automatically_unmute_topics_in_muted_streams_policy' NOT
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND'.
do_change_user_setting(
aaron,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# Hamlet sends a message. DO automatically unmute the topic.
# Aaron sends a message. DON'T automatically unmute the topic.
self.send_stream_message(hamlet, stream_name=stream.name, topic_name=topic_name)
self.send_stream_message(aaron, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {hamlet.id})
def test_automatically_unmute_topic_on_participation_send_message(self) -> None:
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", hamlet.realm)
topic_name = "teST topic"
for user in [hamlet, aaron]:
sub = get_subscription(stream.name, user)
sub.is_muted = True
sub.save()
# For hamlet, 'automatically_unmute_topics_in_muted_streams_policy'
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
hamlet,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# For aaron, 'automatically_unmute_topics_in_muted_streams_policy' NOT
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
aaron,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# Hamlet sends a message. DO automatically unmute the topic.
# Aaron sends a message. DON'T automatically unmute the topic.
self.send_stream_message(hamlet, stream_name=stream.name, topic_name=topic_name)
self.send_stream_message(aaron, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {hamlet.id})
def test_automatically_unmute_topic_on_participation_add_reaction(self) -> None:
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", aaron.realm)
topic_name = "teST topic"
for user in [cordelia, hamlet, aaron]:
sub = get_subscription(stream.name, user)
sub.is_muted = True
sub.save()
# For cordelia, 'automatically_unmute_topics_in_muted_streams_policy'
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
cordelia,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# For aaron, 'automatically_unmute_topics_in_muted_streams_policy' NOT
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
aaron,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
message_id = self.send_stream_message(
hamlet, stream_name=stream.name, topic_name=topic_name
)
# Cordelia reacts to a message. DO automatically unmute the topic.
# Aaron reacts to a message. DON'T automatically unmute the topic.
check_add_reaction(
user_profile=cordelia,
message_id=message_id,
emoji_name="smile",
emoji_code=None,
reaction_type=None,
)
check_add_reaction(
user_profile=aaron,
message_id=message_id,
emoji_name="smile",
emoji_code=None,
reaction_type=None,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {cordelia.id})
def test_automatically_unmute_topic_on_participation_participate_in_poll(self) -> None:
iago = self.example_user("iago")
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", aaron.realm)
topic_name = "teST topic"
for user in [iago, hamlet, aaron]:
sub = get_subscription(stream.name, user)
sub.is_muted = True
sub.save()
# For iago, 'automatically_unmute_topics_in_muted_streams_policy'
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
iago,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# For aaron, 'automatically_unmute_topics_in_muted_streams_policy' NOT
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
aaron,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# Hamlet creates a poll.
payload = dict(
type="stream",
to=orjson.dumps(stream.name).decode(),
topic=topic_name,
content="/poll Preference?\n\nyes\nno",
)
result = self.api_post(hamlet, "/api/v1/messages", payload)
self.assert_json_success(result)
# Iago participates in the poll. DO automatically unmute the topic.
# Aaron participates in the poll. DON'T automatically unmute the topic.
message = self.get_last_message()
def participate_in_poll(user: UserProfile, data: Dict[str, object]) -> None:
content = orjson.dumps(data).decode()
payload = dict(
message_id=message.id,
msg_type="widget",
content=content,
)
result = self.api_post(user, "/api/v1/submessage", payload)
self.assert_json_success(result)
participate_in_poll(iago, dict(type="vote", key="1,1", vote=1))
participate_in_poll(aaron, dict(type="new_option", idx=7, option="maybe"))
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {iago.id})
def test_automatically_unmute_topic_on_participation_edit_todo_list(self) -> None:
othello = self.example_user("othello")
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
stream = get_stream("Verona", aaron.realm)
topic_name = "teST topic"
for user in [othello, hamlet, aaron]:
sub = get_subscription(stream.name, user)
sub.is_muted = True
sub.save()
# For othello, 'automatically_unmute_topics_in_muted_streams_policy'
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
othello,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
# For aaron, 'automatically_unmute_topics_in_muted_streams_policy' NOT
# set to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION'.
do_change_user_setting(
aaron,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# Hamlet creates a todo list.
payload = dict(
type="stream",
to=orjson.dumps(stream.name).decode(),
topic=topic_name,
content="/todo",
)
result = self.api_post(hamlet, "/api/v1/messages", payload)
self.assert_json_success(result)
# Othello edits the todo list. DO automatically unmute the topic.
# Aaron edits the todo list. DON'T automatically unmute the topic.
message = self.get_last_message()
def edit_todo_list(user: UserProfile, data: Dict[str, object]) -> None:
content = orjson.dumps(data).decode()
payload = dict(
message_id=message.id,
msg_type="widget",
content=content,
)
result = self.api_post(user, "/api/v1/submessage", payload)
self.assert_json_success(result)
edit_todo_list(othello, dict(type="new_task", key=7, task="eat", desc="", completed=False))
edit_todo_list(aaron, dict(type="strike", key="5,9"))
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {othello.id})
def test_only_automatically_increase_visibility_policy(self) -> None:
aaron = self.example_user("aaron")
hamlet = self.example_user("hamlet")
stream = get_stream("Verona", aaron.realm)
topic_name = "teST topic"
for user in [hamlet, aaron]:
sub = get_subscription(stream.name, user)
sub.is_muted = True
sub.save()
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# If a topic is already FOLLOWED, we don't change the state to UNMUTED as the
# intent of these "automatically follow or unmute" policies is that they can only
# increase the user's visibility policy for the topic.
do_set_user_topic_visibility_policy(
aaron,
stream,
topic_name,
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
)
do_change_user_setting(
aaron,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
self.send_stream_message(aaron, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# increase visibility from MUTED to UNMUTED
topic_name = "new Topic"
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
do_set_user_topic_visibility_policy(
hamlet,
stream,
topic_name,
visibility_policy=UserTopic.VisibilityPolicy.MUTED,
)
do_change_user_setting(
hamlet,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
self.send_stream_message(hamlet, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, {hamlet.id})
def test_automatically_unmute_policy_unmuted_stream(self) -> None:
aaron = self.example_user("aaron")
cordelia = self.example_user("cordelia")
stream = get_stream("Verona", aaron.realm)
topic_name = "teST topic"
stream_topic_target = StreamTopicTarget(
stream_id=stream.id,
topic_name=topic_name,
)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
# The 'automatically_unmute_topics_in_muted_streams_policy' setting has
# NO effect in unmuted streams.
do_change_user_setting(
aaron,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_INITIATION,
acting_user=None,
)
self.send_stream_message(aaron, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())
do_set_user_topic_visibility_policy(
cordelia,
stream,
topic_name,
visibility_policy=UserTopic.VisibilityPolicy.MUTED,
)
do_change_user_setting(
cordelia,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_PARTICIPATION,
acting_user=None,
)
self.send_stream_message(cordelia, stream_name=stream.name, topic_name=topic_name)
user_ids = stream_topic_target.user_ids_with_visibility_policy(
UserTopic.VisibilityPolicy.UNMUTED
)
self.assertEqual(user_ids, set())

View File

@ -1910,6 +1910,7 @@ class RecipientInfoTest(ZulipTestCase):
service_bot_tuples=[],
all_bot_user_ids=set(),
topic_participant_user_ids=set(),
sender_muted_stream=False,
)
self.assertEqual(info, expected_info)

View File

@ -556,6 +556,14 @@ def update_realm_user_settings_defaults(
json_validator=check_int_in(UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES),
default=None,
),
automatically_follow_topics_policy: Optional[int] = REQ(
json_validator=check_int_in(UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES),
default=None,
),
automatically_unmute_topics_in_muted_streams_policy: Optional[int] = REQ(
json_validator=check_int_in(UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES),
default=None,
),
presence_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
enter_sends: Optional[bool] = REQ(json_validator=check_bool, default=None),
enable_drafts_synchronization: Optional[bool] = REQ(json_validator=check_bool, default=None),

View File

@ -258,6 +258,14 @@ def json_change_settings(
json_validator=check_int_in(UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES),
default=None,
),
automatically_follow_topics_policy: Optional[int] = REQ(
json_validator=check_int_in(UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES),
default=None,
),
automatically_unmute_topics_in_muted_streams_policy: Optional[int] = REQ(
json_validator=check_int_in(UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES),
default=None,
),
presence_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
enter_sends: Optional[bool] = REQ(json_validator=check_bool, default=None),
send_private_typing_notifications: Optional[bool] = REQ(

View File

@ -30,6 +30,7 @@ from zerver.actions.realm_linkifiers import do_add_linkifier
from zerver.actions.scheduled_messages import check_schedule_message
from zerver.actions.streams import bulk_add_subscriptions
from zerver.actions.user_groups import create_user_group_in_database
from zerver.actions.user_settings import do_change_user_setting
from zerver.actions.users import do_change_user_role
from zerver.lib.bulk_create import bulk_create_streams
from zerver.lib.generate_test_data import create_test_data, generate_topics
@ -825,6 +826,29 @@ class Command(BaseCommand):
UserProfile.objects.filter(is_bot=False, realm=zulip_realm)
)
# As we plan to change the default values for 'automatically_follow_topics_policy' and
# 'automatically_unmute_topics_in_muted_streams_policy' in the future, it will lead to
# skewing a lot of our tests, which now need to take into account extra events and database queries.
#
# We explicitly set the values for both settings to 'AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER'
# to make the tests independent of the default values.
#
# We have separate tests to verify events generated, database query counts,
# and other important details related to the above-mentioned settings.
for user in user_profiles:
do_change_user_setting(
user,
"automatically_follow_topics_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
do_change_user_setting(
user,
"automatically_unmute_topics_in_muted_streams_policy",
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
acting_user=None,
)
# Create a test realm emoji.
IMAGE_FILE_PATH = static_path("images/test-images/checkbox.png")
with open(IMAGE_FILE_PATH, "rb") as fp: