mirror of https://github.com/zulip/zulip.git
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:
parent
c349d1137c
commit
58568a60d6
|
@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 8.0
|
## 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**
|
**Feature level 213**
|
||||||
|
|
||||||
* [`POST /register`](/api/register-queue): Fixed incorrect handling of
|
* [`POST /register`](/api/register-queue): Fixed incorrect handling of
|
||||||
|
|
|
@ -230,6 +230,9 @@ python_rules = RuleList(
|
||||||
"good_lines": ["topic_name"],
|
"good_lines": ["topic_name"],
|
||||||
"bad_lines": ['subject="foo"', " MAX_SUBJECT_LEN"],
|
"bad_lines": ['subject="foo"', " MAX_SUBJECT_LEN"],
|
||||||
"exclude": FILES_WITH_LEGACY_SUBJECT,
|
"exclude": FILES_WITH_LEGACY_SUBJECT,
|
||||||
|
"exclude_line": {
|
||||||
|
("zerver/lib/message.py", "message__subject__iexact=message.topic_name(),"),
|
||||||
|
},
|
||||||
"include_only": {
|
"include_only": {
|
||||||
"zerver/data_import/",
|
"zerver/data_import/",
|
||||||
"zerver/lib/",
|
"zerver/lib/",
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# 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
|
# 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
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -30,6 +30,7 @@ from django.utils.translation import override as override_language
|
||||||
from django_stubs_ext import ValuesQuerySet
|
from django_stubs_ext import ValuesQuerySet
|
||||||
|
|
||||||
from zerver.actions.uploads import do_claim_attachments
|
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.addressee import Addressee
|
||||||
from zerver.lib.alert_words import get_alert_word_automaton
|
from zerver.lib.alert_words import get_alert_word_automaton
|
||||||
from zerver.lib.cache import cache_with_key, user_profile_delivery_email_cache_key
|
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,
|
check_user_group_mention_allowed,
|
||||||
normalize_body,
|
normalize_body,
|
||||||
render_markdown,
|
render_markdown,
|
||||||
|
set_visibility_policy_possible,
|
||||||
truncate_topic,
|
truncate_topic,
|
||||||
|
visibility_policy_for_send_message,
|
||||||
wildcard_mention_allowed,
|
wildcard_mention_allowed,
|
||||||
)
|
)
|
||||||
from zerver.lib.muted_users import get_muting_users
|
from zerver.lib.muted_users import get_muting_users
|
||||||
|
@ -180,6 +183,7 @@ class RecipientInfoResult:
|
||||||
service_bot_tuples: List[Tuple[int, int]]
|
service_bot_tuples: List[Tuple[int, int]]
|
||||||
all_bot_user_ids: Set[int]
|
all_bot_user_ids: Set[int]
|
||||||
topic_participant_user_ids: Set[int]
|
topic_participant_user_ids: Set[int]
|
||||||
|
sender_muted_stream: Optional[bool]
|
||||||
|
|
||||||
|
|
||||||
class ActiveUserDict(TypedDict):
|
class ActiveUserDict(TypedDict):
|
||||||
|
@ -212,6 +216,7 @@ def get_recipient_info(
|
||||||
stream_wildcard_mention_in_followed_topic_user_ids: Set[int] = set()
|
stream_wildcard_mention_in_followed_topic_user_ids: Set[int] = set()
|
||||||
muted_sender_user_ids: Set[int] = get_muting_users(sender_id)
|
muted_sender_user_ids: Set[int] = get_muting_users(sender_id)
|
||||||
topic_participant_user_ids: Set[int] = set()
|
topic_participant_user_ids: Set[int] = set()
|
||||||
|
sender_muted_stream: Optional[bool] = None
|
||||||
|
|
||||||
if recipient.type == Recipient.PERSONAL:
|
if recipient.type == Recipient.PERSONAL:
|
||||||
# The sender and recipient may be the same id, so
|
# The sender and recipient may be the same id, so
|
||||||
|
@ -275,7 +280,14 @@ def get_recipient_info(
|
||||||
.order_by("user_profile_id")
|
.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()
|
user_id_to_visibility_policy = stream_topic.user_id_to_visibility_policy_dict()
|
||||||
|
|
||||||
def notification_recipients(setting: str) -> Set[int]:
|
def notification_recipients(setting: str) -> Set[int]:
|
||||||
|
@ -466,6 +478,7 @@ def get_recipient_info(
|
||||||
service_bot_tuples=service_bot_tuples,
|
service_bot_tuples=service_bot_tuples,
|
||||||
all_bot_user_ids=all_bot_user_ids,
|
all_bot_user_ids=all_bot_user_ids,
|
||||||
topic_participant_user_ids=topic_participant_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(
|
message_send_dict = SendMessageRequest(
|
||||||
stream=stream,
|
stream=stream,
|
||||||
|
sender_muted_stream=info.sender_muted_stream,
|
||||||
local_id=local_id,
|
local_id=local_id,
|
||||||
sender_queue_id=sender_queue_id,
|
sender_queue_id=sender_queue_id,
|
||||||
realm=realm,
|
realm=realm,
|
||||||
|
@ -896,6 +910,8 @@ def do_send_messages(
|
||||||
|
|
||||||
# This next loop is responsible for notifying other parts of the
|
# This next loop is responsible for notifying other parts of the
|
||||||
# Zulip system about the messages we just committed to the database:
|
# 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
|
# * Notifying clients via send_event
|
||||||
# * Triggering outgoing webhooks via the service event queue.
|
# * Triggering outgoing webhooks via the service event queue.
|
||||||
# * Updating the `first_message_id` field for streams without any message history.
|
# * 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 needed because stubs for django are missing
|
||||||
assert send_request.stream is not None
|
assert send_request.stream is not None
|
||||||
realm_id = send_request.stream.realm_id
|
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
|
# Deliver events to the real-time push system, as well as
|
||||||
# enqueuing any additional processing triggered by the message.
|
# enqueuing any additional processing triggered by the message.
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from zerver.actions.create_user import create_historical_user_messages
|
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.emoji import check_emoji_request, get_emoji_data
|
||||||
from zerver.lib.exceptions import ReactionExistsError
|
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.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.models import Message, Reaction, Recipient, Stream, UserMessage, UserProfile
|
||||||
from zerver.tornado.django_api import send_event_on_commit
|
from zerver.tornado.django_api import send_event_on_commit
|
||||||
|
|
||||||
|
@ -82,6 +90,32 @@ def do_add_reaction(
|
||||||
|
|
||||||
reaction.save()
|
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")
|
notify_reaction_update(user_profile, message, reaction, "add")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
from django.utils.translation import gettext as _
|
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.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
|
from zerver.tornado.django_api import send_event_on_commit
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,6 +55,33 @@ def do_add_submessage(
|
||||||
)
|
)
|
||||||
submessage.save()
|
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(
|
event = dict(
|
||||||
type="submessage",
|
type="submessage",
|
||||||
msg_type=msg_type,
|
msg_type=msg_type,
|
||||||
|
|
|
@ -20,7 +20,7 @@ import ahocorasick
|
||||||
import orjson
|
import orjson
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
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.timezone import now as timezone_now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django_stubs_ext import ValuesQuerySet
|
from django_stubs_ext import ValuesQuerySet
|
||||||
|
@ -47,9 +47,15 @@ from zerver.lib.stream_subscription import (
|
||||||
get_subscribed_stream_recipient_ids_for_user,
|
get_subscribed_stream_recipient_ids_for_user,
|
||||||
num_subscribers_for_stream_id,
|
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.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.types import DisplayRecipientT, EditHistoryEvent, UserDisplayRecipient
|
||||||
from zerver.lib.url_preview.types import UrlEmbedData
|
from zerver.lib.url_preview.types import UrlEmbedData
|
||||||
from zerver.lib.user_groups import is_user_in_group
|
from zerver.lib.user_groups import is_user_in_group
|
||||||
|
@ -147,6 +153,7 @@ class SendMessageRequest:
|
||||||
message: Message
|
message: Message
|
||||||
rendering_result: MessageRenderingResult
|
rendering_result: MessageRenderingResult
|
||||||
stream: Optional[Stream]
|
stream: Optional[Stream]
|
||||||
|
sender_muted_stream: Optional[bool]
|
||||||
local_id: Optional[str]
|
local_id: Optional[str]
|
||||||
sender_queue_id: Optional[str]
|
sender_queue_id: Optional[str]
|
||||||
realm: Realm
|
realm: Realm
|
||||||
|
@ -1713,3 +1720,180 @@ def update_to_dict_cache(
|
||||||
|
|
||||||
cache_set_many(items_for_remote_cache)
|
cache_set_many(items_for_remote_cache)
|
||||||
return message_ids
|
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
|
||||||
|
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1642,6 +1642,30 @@ class UserBaseSettings(models.Model):
|
||||||
default=REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_AUTOMATIC
|
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.
|
# Whether or not the user wants to sync their drafts.
|
||||||
enable_drafts_synchronization = models.BooleanField(default=True)
|
enable_drafts_synchronization = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
@ -1737,6 +1761,8 @@ class UserBaseSettings(models.Model):
|
||||||
enable_followed_topic_push_notifications=bool,
|
enable_followed_topic_push_notifications=bool,
|
||||||
enable_followed_topic_audible_notifications=bool,
|
enable_followed_topic_audible_notifications=bool,
|
||||||
enable_followed_topic_wildcard_mentions_notify=bool,
|
enable_followed_topic_wildcard_mentions_notify=bool,
|
||||||
|
automatically_follow_topics_policy=int,
|
||||||
|
automatically_unmute_topics_in_muted_streams_policy=int,
|
||||||
)
|
)
|
||||||
|
|
||||||
notification_setting_types = {
|
notification_setting_types = {
|
||||||
|
|
|
@ -10206,6 +10206,44 @@ paths:
|
||||||
- 2
|
- 2
|
||||||
- 3
|
- 3
|
||||||
example: 1
|
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
|
- name: presence_enabled
|
||||||
in: query
|
in: query
|
||||||
description: |
|
description: |
|
||||||
|
@ -12422,6 +12460,28 @@ paths:
|
||||||
**Changes**: New in Zulip 7.0 (feature level 168), replacing the
|
**Changes**: New in Zulip 7.0 (feature level 168), replacing the
|
||||||
previous `realm_name_in_notifications` boolean;
|
previous `realm_name_in_notifications` boolean;
|
||||||
`true` corresponded to `Always`, and `false` to `Never`.
|
`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:
|
presence_enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
|
@ -14561,6 +14621,28 @@ paths:
|
||||||
**Changes**: New in Zulip 7.0 (feature level 168), replacing the
|
**Changes**: New in Zulip 7.0 (feature level 168), replacing the
|
||||||
previous `realm_name_in_notifications` boolean;
|
previous `realm_name_in_notifications` boolean;
|
||||||
`true` corresponded to `Always`, and `false` to `Never`.
|
`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:
|
presence_enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
|
@ -15808,6 +15890,44 @@ paths:
|
||||||
- 2
|
- 2
|
||||||
- 3
|
- 3
|
||||||
example: 1
|
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
|
- name: presence_enabled
|
||||||
in: query
|
in: query
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -535,11 +535,195 @@ class NormalActionsTest(BaseAction):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_stream_send_message_events(self) -> None:
|
def test_stream_send_message_events(self) -> None:
|
||||||
user_profile = self.example_user("hamlet")
|
hamlet = self.example_user("hamlet")
|
||||||
events = self.verify_action(
|
for stream_name in ["Verona", "Denmark", "core team"]:
|
||||||
lambda: self.send_stream_message(user_profile, "Verona", "hello"),
|
stream = get_stream(stream_name, hamlet.realm)
|
||||||
client_gravatar=False,
|
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])
|
check_message("events[0]", events[0])
|
||||||
assert isinstance(events[0]["message"]["avatar_url"], str)
|
assert isinstance(events[0]["message"]["avatar_url"], str)
|
||||||
|
|
||||||
|
@ -551,7 +735,7 @@ class NormalActionsTest(BaseAction):
|
||||||
)
|
)
|
||||||
|
|
||||||
events = self.verify_action(
|
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,
|
client_gravatar=True,
|
||||||
)
|
)
|
||||||
check_message("events[0]", events[0])
|
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'
|
# Here we add coverage for the case where 'apply_unread_message_event'
|
||||||
# should be called and unread messages in unmuted or followed topic in
|
# 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'.
|
# muted stream is treated as unmuted stream message, thus added to 'unmuted_stream_msgs'.
|
||||||
stream = get_stream("Verona", user_profile.realm)
|
stream = get_stream("Verona", hamlet.realm)
|
||||||
sub = get_subscription(stream.name, user_profile)
|
|
||||||
do_change_subscription_property(
|
|
||||||
user_profile, sub, stream, "is_muted", True, acting_user=None
|
|
||||||
)
|
|
||||||
do_set_user_topic_visibility_policy(
|
do_set_user_topic_visibility_policy(
|
||||||
user_profile,
|
hamlet,
|
||||||
stream,
|
stream,
|
||||||
"test",
|
"test",
|
||||||
visibility_policy=UserTopic.VisibilityPolicy.UNMUTED,
|
visibility_policy=UserTopic.VisibilityPolicy.UNMUTED,
|
||||||
|
@ -2063,6 +2243,8 @@ class NormalActionsTest(BaseAction):
|
||||||
"desktop_icon_count_display",
|
"desktop_icon_count_display",
|
||||||
"presence_enabled",
|
"presence_enabled",
|
||||||
"realm_name_in_email_notifications_policy",
|
"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.
|
# These settings are tested in their own tests.
|
||||||
continue
|
continue
|
||||||
|
@ -2201,6 +2383,38 @@ class NormalActionsTest(BaseAction):
|
||||||
check_user_settings_update("events[0]", events[0])
|
check_user_settings_update("events[0]", events[0])
|
||||||
check_update_global_notifications("events[1]", events[1], 2)
|
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:
|
def test_realm_update_org_type(self) -> None:
|
||||||
realm = self.user_profile.realm
|
realm = self.user_profile.realm
|
||||||
|
|
||||||
|
@ -3129,6 +3343,8 @@ class RealmPropertyActionTest(BaseAction):
|
||||||
email_notifications_batching_period_seconds=[120, 300],
|
email_notifications_batching_period_seconds=[120, 300],
|
||||||
email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_TYPES,
|
email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_TYPES,
|
||||||
realm_name_in_email_notifications_policy=UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES,
|
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)
|
vals = test_values.get(name)
|
||||||
|
|
|
@ -1454,12 +1454,24 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
topic_name = "foo"
|
topic_name = "foo"
|
||||||
content = "whatever"
|
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
|
# 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
|
# 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
|
# filled, we will get a lower count. Caches are not supposed to be
|
||||||
# persistent, so our test can also fail if cache is invalidated
|
# persistent, so our test can also fail if cache is invalidated
|
||||||
# during the course of the unit test.
|
# during the course of the unit test.
|
||||||
flush_per_request_caches()
|
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):
|
with self.assert_database_query_count(13):
|
||||||
check_send_stream_message(
|
check_send_stream_message(
|
||||||
sender=sender,
|
sender=sender,
|
||||||
|
@ -1469,6 +1481,57 @@ class StreamMessagesTest(ZulipTestCase):
|
||||||
body=content,
|
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:
|
def test_stream_message_dict(self) -> None:
|
||||||
user_profile = self.example_user("iago")
|
user_profile = self.example_user("iago")
|
||||||
self.subscribe(user_profile, "Denmark")
|
self.subscribe(user_profile, "Denmark")
|
||||||
|
|
|
@ -1345,6 +1345,8 @@ class RealmAPITest(ZulipTestCase):
|
||||||
email_notifications_batching_period_seconds=[120, 300],
|
email_notifications_batching_period_seconds=[120, 300],
|
||||||
email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_TYPES,
|
email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_TYPES,
|
||||||
realm_name_in_email_notifications_policy=UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES,
|
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)
|
vals = test_values.get(name)
|
||||||
|
|
|
@ -362,6 +362,8 @@ class ChangeSettingsTest(ZulipTestCase):
|
||||||
desktop_icon_count_display=2,
|
desktop_icon_count_display=2,
|
||||||
email_address_visibility=3,
|
email_address_visibility=3,
|
||||||
realm_name_in_email_notifications_policy=2,
|
realm_name_in_email_notifications_policy=2,
|
||||||
|
automatically_follow_topics_policy=1,
|
||||||
|
automatically_unmute_topics_in_muted_streams_policy=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.login("hamlet")
|
self.login("hamlet")
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
import orjson
|
||||||
import time_machine
|
import time_machine
|
||||||
from django.utils.timezone import now as timezone_now
|
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.actions.user_topics import do_set_user_topic_visibility_policy
|
||||||
from zerver.lib.stream_topic import StreamTopicTarget
|
from zerver.lib.stream_topic import StreamTopicTarget
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
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.lib.user_topics import get_topic_mutes, topic_has_visibility_policy
|
||||||
from zerver.models import UserProfile, UserTopic, get_stream
|
from zerver.models import UserProfile, UserTopic, get_stream
|
||||||
|
|
||||||
|
@ -638,3 +642,895 @@ class UnmutedTopicsTests(ZulipTestCase):
|
||||||
|
|
||||||
result = self.api_post(user, url, data)
|
result = self.api_post(user, url, data)
|
||||||
self.assert_json_error(result, "Invalid stream ID")
|
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())
|
||||||
|
|
|
@ -1910,6 +1910,7 @@ class RecipientInfoTest(ZulipTestCase):
|
||||||
service_bot_tuples=[],
|
service_bot_tuples=[],
|
||||||
all_bot_user_ids=set(),
|
all_bot_user_ids=set(),
|
||||||
topic_participant_user_ids=set(),
|
topic_participant_user_ids=set(),
|
||||||
|
sender_muted_stream=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(info, expected_info)
|
self.assertEqual(info, expected_info)
|
||||||
|
|
|
@ -556,6 +556,14 @@ def update_realm_user_settings_defaults(
|
||||||
json_validator=check_int_in(UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES),
|
json_validator=check_int_in(UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES),
|
||||||
default=None,
|
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),
|
presence_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
enter_sends: 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),
|
enable_drafts_synchronization: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
|
|
@ -258,6 +258,14 @@ def json_change_settings(
|
||||||
json_validator=check_int_in(UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES),
|
json_validator=check_int_in(UserProfile.REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_CHOICES),
|
||||||
default=None,
|
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),
|
presence_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
enter_sends: 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(
|
send_private_typing_notifications: Optional[bool] = REQ(
|
||||||
|
|
|
@ -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.scheduled_messages import check_schedule_message
|
||||||
from zerver.actions.streams import bulk_add_subscriptions
|
from zerver.actions.streams import bulk_add_subscriptions
|
||||||
from zerver.actions.user_groups import create_user_group_in_database
|
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.actions.users import do_change_user_role
|
||||||
from zerver.lib.bulk_create import bulk_create_streams
|
from zerver.lib.bulk_create import bulk_create_streams
|
||||||
from zerver.lib.generate_test_data import create_test_data, generate_topics
|
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)
|
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.
|
# Create a test realm emoji.
|
||||||
IMAGE_FILE_PATH = static_path("images/test-images/checkbox.png")
|
IMAGE_FILE_PATH = static_path("images/test-images/checkbox.png")
|
||||||
with open(IMAGE_FILE_PATH, "rb") as fp:
|
with open(IMAGE_FILE_PATH, "rb") as fp:
|
||||||
|
|
Loading…
Reference in New Issue