mirror of https://github.com/zulip/zulip.git
settings: Add "Automatically follow topics where I'm mentioned" setting.
Fixes: #26795
This commit is contained in:
parent
c7c0b871c5
commit
2e71ec78e3
|
@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 8.0
|
||||
|
||||
**Feature level 235**
|
||||
|
||||
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
|
||||
[`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings):
|
||||
Added a new user setting,`automatically_follow_topics_where_mentioned`
|
||||
that allows user to automatically follow topics where the user is mentioned.
|
||||
|
||||
**Feature level 234**
|
||||
|
||||
* Mobile push notifications now include a `realm_name` field.
|
||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
|||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
API_FEATURE_LEVEL = 234
|
||||
API_FEATURE_LEVEL = 235
|
||||
|
||||
# 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
|
||||
|
|
|
@ -46,6 +46,7 @@ export type RealmDefaultSettings = {
|
|||
wildcard_mentions_notify: boolean;
|
||||
automatically_follow_topics_policy: number;
|
||||
automatically_unmute_topics_in_muted_streams_policy: number;
|
||||
automatically_follow_topics_where_mentioned: boolean;
|
||||
};
|
||||
|
||||
export let realm_user_settings_defaults: RealmDefaultSettings;
|
||||
|
|
|
@ -586,6 +586,9 @@ export const notification_settings_labels = {
|
|||
automatically_unmute_topics_in_muted_streams_policy: $t({
|
||||
defaultMessage: "Automatically unmute topics in muted streams",
|
||||
}),
|
||||
automatically_follow_topics_where_mentioned: $t({
|
||||
defaultMessage: "Automatically follow topics where I'm mentioned",
|
||||
}),
|
||||
};
|
||||
|
||||
export const realm_user_settings_defaults_labels = {
|
||||
|
@ -756,6 +759,7 @@ const other_notification_settings = [
|
|||
"notification_sound",
|
||||
"automatically_follow_topics_policy",
|
||||
"automatically_unmute_topics_in_muted_streams_policy",
|
||||
"automatically_follow_topics_where_mentioned",
|
||||
];
|
||||
|
||||
export const all_notification_settings = [
|
||||
|
|
|
@ -58,6 +58,7 @@ export type UserSettings = (StreamNotificationSettings &
|
|||
send_read_receipts: boolean;
|
||||
automatically_follow_topics_policy: number;
|
||||
automatically_unmute_topics_in_muted_streams_policy: number;
|
||||
automatically_follow_topics_where_mentioned: boolean;
|
||||
timezone: string;
|
||||
};
|
||||
|
||||
|
|
|
@ -79,6 +79,12 @@
|
|||
{{> dropdown_options_widget option_values=automatically_unmute_topics_in_muted_streams_policy_values}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{> settings_checkbox
|
||||
setting_name="automatically_follow_topics_where_mentioned"
|
||||
is_checked=(lookup settings_object "automatically_follow_topics_where_mentioned")
|
||||
label=(lookup settings_label "automatically_follow_topics_where_mentioned")
|
||||
prefix=notification_settings.prefix}}
|
||||
</div>
|
||||
|
||||
<div class="desktop_notifications m-10 {{#if for_realm_settings}}settings-subsection-parent{{else}}subsection-parent{{/if}}">
|
||||
|
|
|
@ -107,6 +107,8 @@ run_test("tr_tag", ({mock_template}) => {
|
|||
automatically_follow_topics_policy: "Automatically follow topics",
|
||||
automatically_unmute_topics_in_muted_streams_policy:
|
||||
"Automatically unmute topics in muted streams",
|
||||
automatically_follow_topics_where_mentioned:
|
||||
"Automatically follow topics where I'm mentioned",
|
||||
},
|
||||
show_push_notifications_tooltip: false,
|
||||
user_role_text: "Member",
|
||||
|
|
|
@ -30,7 +30,10 @@ from django.utils.translation import override as override_language
|
|||
from django_stubs_ext import ValuesQuerySet
|
||||
|
||||
from zerver.actions.uploads import do_claim_attachments
|
||||
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
|
||||
from zerver.actions.user_topics import (
|
||||
bulk_do_set_user_topic_visibility_policy,
|
||||
do_set_user_topic_visibility_policy,
|
||||
)
|
||||
from zerver.lib.addressee import Addressee
|
||||
from zerver.lib.alert_words import get_alert_word_automaton
|
||||
from zerver.lib.cache import cache_with_key, user_profile_delivery_email_cache_key
|
||||
|
@ -988,6 +991,47 @@ def do_send_messages(
|
|||
)
|
||||
send_request.automatic_new_visibility_policy = new_visibility_policy
|
||||
|
||||
# Set the visibility_policy of the users mentioned in the message
|
||||
# to "FOLLOWED" if "automatically_follow_topics_where_mentioned" is "True".
|
||||
human_user_personal_mentions = send_request.rendering_result.mentions_user_ids & (
|
||||
send_request.active_user_ids - send_request.all_bot_user_ids
|
||||
)
|
||||
expect_follow_user_profiles: Set[UserProfile] = set()
|
||||
|
||||
if len(human_user_personal_mentions) > 0:
|
||||
expect_follow_user_profiles = set(
|
||||
UserProfile.objects.filter(
|
||||
realm_id=realm_id,
|
||||
id__in=human_user_personal_mentions,
|
||||
automatically_follow_topics_where_mentioned=True,
|
||||
)
|
||||
)
|
||||
if len(expect_follow_user_profiles) > 0:
|
||||
user_topics_query_set = UserTopic.objects.filter(
|
||||
user_profile__in=expect_follow_user_profiles,
|
||||
stream_id=send_request.stream.id,
|
||||
topic_name__iexact=send_request.message.topic_name(),
|
||||
visibility_policy__in=[
|
||||
# Explicitly muted takes precedence over this setting.
|
||||
UserTopic.VisibilityPolicy.MUTED,
|
||||
# Already followed
|
||||
UserTopic.VisibilityPolicy.FOLLOWED,
|
||||
],
|
||||
)
|
||||
skip_follow_users = {
|
||||
user_topic.user_profile for user_topic in user_topics_query_set
|
||||
}
|
||||
|
||||
to_follow_users = list(expect_follow_user_profiles - skip_follow_users)
|
||||
|
||||
if to_follow_users:
|
||||
bulk_do_set_user_topic_visibility_policy(
|
||||
user_profiles=to_follow_users,
|
||||
stream=send_request.stream,
|
||||
topic=send_request.message.topic_name(),
|
||||
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
|
||||
)
|
||||
|
||||
# Deliver events to the real-time push system, as well as
|
||||
# enqueuing any additional processing triggered by the message.
|
||||
wide_message_dict = MessageDict.wide_dict(send_request.message, realm_id)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 4.2.7 on 2023-12-10 13:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0493_rename_userhotspot_to_onboardingstep"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="realmuserdefault",
|
||||
name="automatically_follow_topics_where_mentioned",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="userprofile",
|
||||
name="automatically_follow_topics_where_mentioned",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
|
@ -1768,6 +1768,7 @@ class UserBaseSettings(models.Model):
|
|||
automatically_unmute_topics_in_muted_streams_policy = models.PositiveSmallIntegerField(
|
||||
default=AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND,
|
||||
)
|
||||
automatically_follow_topics_where_mentioned = models.BooleanField(default=True)
|
||||
|
||||
# Whether or not the user wants to sync their drafts.
|
||||
enable_drafts_synchronization = models.BooleanField(default=True)
|
||||
|
@ -1866,6 +1867,7 @@ class UserBaseSettings(models.Model):
|
|||
enable_followed_topic_wildcard_mentions_notify=bool,
|
||||
automatically_follow_topics_policy=int,
|
||||
automatically_unmute_topics_in_muted_streams_policy=int,
|
||||
automatically_follow_topics_where_mentioned=bool,
|
||||
)
|
||||
|
||||
notification_setting_types = {
|
||||
|
|
|
@ -10673,6 +10673,16 @@ paths:
|
|||
- 3
|
||||
- 4
|
||||
example: 1
|
||||
- name: automatically_follow_topics_where_mentioned
|
||||
in: query
|
||||
description: |
|
||||
Whether the server will automatically mark the user as following
|
||||
topics where the user is mentioned.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 235).
|
||||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
- name: presence_enabled
|
||||
in: query
|
||||
description: |
|
||||
|
@ -12936,6 +12946,13 @@ paths:
|
|||
- 4 - Never
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 214).
|
||||
automatically_follow_topics_where_mentioned:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether the server will automatically mark the user as following
|
||||
topics where the user is mentioned.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 235).
|
||||
presence_enabled:
|
||||
type: boolean
|
||||
description: |
|
||||
|
@ -15151,6 +15168,13 @@ paths:
|
|||
- 4 - Never
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 214).
|
||||
automatically_follow_topics_where_mentioned:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether the server will automatically mark the user as following
|
||||
topics where the user is mentioned.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 235).
|
||||
presence_enabled:
|
||||
type: boolean
|
||||
description: |
|
||||
|
@ -16492,6 +16516,16 @@ paths:
|
|||
- 3
|
||||
- 4
|
||||
example: 1
|
||||
- name: automatically_follow_topics_where_mentioned
|
||||
in: query
|
||||
description: |
|
||||
Whether the server will automatically mark the user as following
|
||||
topics where the user is mentioned.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 235).
|
||||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
- name: presence_enabled
|
||||
in: query
|
||||
description: |
|
||||
|
|
|
@ -476,6 +476,36 @@ class NormalActionsTest(BaseAction):
|
|||
partial(self.send_stream_message, self.example_user("cordelia"), "Verona", content),
|
||||
)
|
||||
|
||||
def test_automatically_follow_topic_where_mentioned(self) -> None:
|
||||
user = self.example_user("hamlet")
|
||||
|
||||
do_change_user_setting(
|
||||
user_profile=user,
|
||||
setting_name="automatically_follow_topics_where_mentioned",
|
||||
setting_value=True,
|
||||
acting_user=None,
|
||||
)
|
||||
|
||||
def get_num_events() -> int: # nocoverage
|
||||
try:
|
||||
user_topic = UserTopic.objects.get(
|
||||
user_profile=user,
|
||||
stream_id=get_stream("Verona", user.realm).id,
|
||||
topic_name__iexact="test",
|
||||
)
|
||||
if user_topic.visibility_policy != UserTopic.VisibilityPolicy.FOLLOWED:
|
||||
return 3
|
||||
except UserTopic.DoesNotExist:
|
||||
return 3
|
||||
return 1
|
||||
|
||||
for i in range(3):
|
||||
content = "mentioning... @**" + user.full_name + "** hello " + str(i)
|
||||
self.verify_action(
|
||||
partial(self.send_stream_message, self.example_user("cordelia"), "Verona", content),
|
||||
num_events=get_num_events(),
|
||||
)
|
||||
|
||||
def test_topic_wildcard_mentioned_send_message_events(self) -> None:
|
||||
for i in range(3):
|
||||
content = "mentioning... @**topic** hello " + str(i)
|
||||
|
|
|
@ -1560,6 +1560,7 @@ class StreamMessagesTest(ZulipTestCase):
|
|||
self.subscribe(user_profile, "Denmark")
|
||||
|
||||
sender = self.example_user("hamlet")
|
||||
user = self.example_user("othello")
|
||||
sending_client = make_client(name="test suite")
|
||||
stream_name = "Denmark"
|
||||
topic_name = "foo"
|
||||
|
@ -1643,6 +1644,54 @@ class StreamMessagesTest(ZulipTestCase):
|
|||
body=content,
|
||||
)
|
||||
|
||||
realm = get_realm("zulip")
|
||||
subscribers = self.users_subscribed_to_stream(stream_name, realm)
|
||||
|
||||
for user in subscribers:
|
||||
do_change_user_setting(
|
||||
user_profile=user,
|
||||
setting_name="automatically_follow_topics_where_mentioned",
|
||||
setting_value=True,
|
||||
acting_user=None,
|
||||
)
|
||||
# There will be an increase in the query count of 5 while sending
|
||||
# a message with a mention to a topic if visibility policy for the
|
||||
# mentioned user is other than FOLLOWED.
|
||||
# 1 to get the user_id of the mentioned user + 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(22):
|
||||
check_send_stream_message(
|
||||
sender=sender,
|
||||
client=sending_client,
|
||||
stream_name=stream_name,
|
||||
topic="topic 2",
|
||||
body="@**" + user.full_name + "**",
|
||||
)
|
||||
# If the topic is already FOLLOWED, there will be an increase in the query
|
||||
# count of 2.
|
||||
# 1 to get the user_id of the mentioned user + 1 to check if the topic is
|
||||
# already followed.
|
||||
flush_per_request_caches()
|
||||
with self.assert_database_query_count(19):
|
||||
check_send_stream_message(
|
||||
sender=sender,
|
||||
client=sending_client,
|
||||
stream_name=stream_name,
|
||||
topic="topic 2",
|
||||
body="@**" + user.full_name + "**",
|
||||
)
|
||||
|
||||
flush_per_request_caches()
|
||||
with self.assert_database_query_count(16):
|
||||
check_send_stream_message(
|
||||
sender=sender,
|
||||
client=sending_client,
|
||||
stream_name=stream_name,
|
||||
topic="topic 2",
|
||||
body="@**all**",
|
||||
)
|
||||
|
||||
def test_stream_message_dict(self) -> None:
|
||||
user_profile = self.example_user("iago")
|
||||
self.subscribe(user_profile, "Denmark")
|
||||
|
|
|
@ -1437,6 +1437,7 @@ class RealmAPITest(ZulipTestCase):
|
|||
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,
|
||||
automatically_follow_topics_where_mentioned=[True, False],
|
||||
)
|
||||
|
||||
vals = test_values.get(name)
|
||||
|
|
|
@ -763,6 +763,45 @@ class AutomaticallyFollowTopicsTests(ZulipTestCase):
|
|||
)
|
||||
self.assertEqual(user_ids, {hamlet.id})
|
||||
|
||||
def test_automatically_follow_topic_on_mention(self) -> None:
|
||||
hamlet = self.example_user("hamlet")
|
||||
aaron = self.example_user("aaron")
|
||||
stream = get_stream("Verona", hamlet.realm)
|
||||
topic_name = "teST topic"
|
||||
|
||||
do_change_user_setting(
|
||||
hamlet,
|
||||
"automatically_follow_topics_where_mentioned",
|
||||
True,
|
||||
acting_user=None,
|
||||
)
|
||||
|
||||
content = "silently mentioning... @_**" + hamlet.full_name + "**"
|
||||
self.send_stream_message(aaron, stream.name, content, topic_name)
|
||||
|
||||
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())
|
||||
|
||||
content = "quoting... \n```quote\n@**" + hamlet.full_name + "**\n```"
|
||||
self.send_stream_message(aaron, stream.name, content, topic_name)
|
||||
user_ids = stream_topic_target.user_ids_with_visibility_policy(
|
||||
UserTopic.VisibilityPolicy.FOLLOWED
|
||||
)
|
||||
self.assertEqual(user_ids, set())
|
||||
|
||||
content = "mentioning... @**" + hamlet.full_name + "**"
|
||||
self.send_stream_message(aaron, stream.name, content, 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")
|
||||
|
|
|
@ -571,6 +571,9 @@ def update_realm_user_settings_defaults(
|
|||
json_validator=check_int_in(UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES),
|
||||
default=None,
|
||||
),
|
||||
automatically_follow_topics_where_mentioned: 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),
|
||||
enable_drafts_synchronization: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
|
|
|
@ -300,6 +300,9 @@ def json_change_settings(
|
|||
json_validator=check_int_in(UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_CHOICES),
|
||||
default=None,
|
||||
),
|
||||
automatically_follow_topics_where_mentioned: 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),
|
||||
send_private_typing_notifications: Optional[bool] = REQ(
|
||||
|
|
|
@ -860,6 +860,9 @@ class Command(BaseCommand):
|
|||
#
|
||||
# We have separate tests to verify events generated, database query counts,
|
||||
# and other important details related to the above-mentioned settings.
|
||||
#
|
||||
# We set the value of 'automatically_follow_topics_where_mentioned' to 'False' so that it
|
||||
# does not increase the number of events and db queries while running tests.
|
||||
for user in user_profiles:
|
||||
do_change_user_setting(
|
||||
user,
|
||||
|
@ -873,6 +876,12 @@ class Command(BaseCommand):
|
|||
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_NEVER,
|
||||
acting_user=None,
|
||||
)
|
||||
do_change_user_setting(
|
||||
user,
|
||||
"automatically_follow_topics_where_mentioned",
|
||||
False,
|
||||
acting_user=None,
|
||||
)
|
||||
|
||||
# Create a test realm emoji.
|
||||
IMAGE_FILE_PATH = static_path("images/test-images/checkbox.png")
|
||||
|
|
Loading…
Reference in New Issue