mirror of https://github.com/zulip/zulip.git
send_message: Add an optional parameter in the success response.
Add an optional `automatic_new_visibility_policy` enum field in the success response to indicate the new visibility policy value due to the `automatically_follow_topics_policy` and `automatically_unmute_topics_in_muted_streams_policy` user settings during the send message action. Only present if there is a change in the visibility policy.
This commit is contained in:
parent
0c649ff2aa
commit
17a0304309
|
@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 8.0
|
## Changes in Zulip 8.0
|
||||||
|
|
||||||
|
**Feature level 218**
|
||||||
|
|
||||||
|
* [`POST /messages`](/api/send-message): Added an optional
|
||||||
|
`automatic_new_visibility_policy` enum field in the success response
|
||||||
|
to indicate the new visibility policy value due to the [visibility policy settings](/help/mute-a-topic)
|
||||||
|
during the send message action.
|
||||||
|
|
||||||
**Feature level 217**
|
**Feature level 217**
|
||||||
|
|
||||||
* [`POST /mobile_push/test_notification`](/api/test-notify): Added new endpoint
|
* [`POST /mobile_push/test_notification`](/api/test-notify): Added new endpoint
|
||||||
|
|
|
@ -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 = 217
|
API_FEATURE_LEVEL = 218
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -196,6 +196,12 @@ class ActiveUserDict(TypedDict):
|
||||||
bot_type: Optional[int]
|
bot_type: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SentMessageResult:
|
||||||
|
message_id: int
|
||||||
|
automatic_new_visibility_policy: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
def get_recipient_info(
|
def get_recipient_info(
|
||||||
*,
|
*,
|
||||||
realm_id: int,
|
realm_id: int,
|
||||||
|
@ -843,7 +849,7 @@ def do_send_messages(
|
||||||
email_gateway: bool = False,
|
email_gateway: bool = False,
|
||||||
scheduled_message_to_self: bool = False,
|
scheduled_message_to_self: bool = False,
|
||||||
mark_as_read: Sequence[int] = [],
|
mark_as_read: Sequence[int] = [],
|
||||||
) -> List[int]:
|
) -> List[SentMessageResult]:
|
||||||
"""See
|
"""See
|
||||||
https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html
|
https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html
|
||||||
for high-level documentation on this subsystem.
|
for high-level documentation on this subsystem.
|
||||||
|
@ -964,6 +970,7 @@ def do_send_messages(
|
||||||
topic=send_request.message.topic_name(),
|
topic=send_request.message.topic_name(),
|
||||||
visibility_policy=new_visibility_policy,
|
visibility_policy=new_visibility_policy,
|
||||||
)
|
)
|
||||||
|
send_request.automatic_new_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.
|
||||||
|
@ -1123,7 +1130,14 @@ def do_send_messages(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return [send_request.message.id for send_request in send_message_requests]
|
sent_message_results = [
|
||||||
|
SentMessageResult(
|
||||||
|
message_id=send_request.message.id,
|
||||||
|
automatic_new_visibility_policy=send_request.automatic_new_visibility_policy,
|
||||||
|
)
|
||||||
|
for send_request in send_message_requests
|
||||||
|
]
|
||||||
|
return sent_message_results
|
||||||
|
|
||||||
|
|
||||||
def already_sent_mirrored_message_id(message: Message) -> Optional[int]:
|
def already_sent_mirrored_message_id(message: Message) -> Optional[int]:
|
||||||
|
@ -1236,8 +1250,8 @@ def check_send_stream_message(
|
||||||
) -> int:
|
) -> int:
|
||||||
addressee = Addressee.for_stream_name(stream_name, topic)
|
addressee = Addressee.for_stream_name(stream_name, topic)
|
||||||
message = check_message(sender, client, addressee, body, realm)
|
message = check_message(sender, client, addressee, body, realm)
|
||||||
|
sent_message_result = do_send_messages([message])[0]
|
||||||
return do_send_messages([message])[0]
|
return sent_message_result.message_id
|
||||||
|
|
||||||
|
|
||||||
def check_send_stream_message_by_id(
|
def check_send_stream_message_by_id(
|
||||||
|
@ -1250,8 +1264,8 @@ def check_send_stream_message_by_id(
|
||||||
) -> int:
|
) -> int:
|
||||||
addressee = Addressee.for_stream_id(stream_id, topic)
|
addressee = Addressee.for_stream_id(stream_id, topic)
|
||||||
message = check_message(sender, client, addressee, body, realm)
|
message = check_message(sender, client, addressee, body, realm)
|
||||||
|
sent_message_result = do_send_messages([message])[0]
|
||||||
return do_send_messages([message])[0]
|
return sent_message_result.message_id
|
||||||
|
|
||||||
|
|
||||||
def check_send_private_message(
|
def check_send_private_message(
|
||||||
|
@ -1259,8 +1273,8 @@ def check_send_private_message(
|
||||||
) -> int:
|
) -> int:
|
||||||
addressee = Addressee.for_user_profile(receiving_user)
|
addressee = Addressee.for_user_profile(receiving_user)
|
||||||
message = check_message(sender, client, addressee, body)
|
message = check_message(sender, client, addressee, body)
|
||||||
|
sent_message_result = do_send_messages([message])[0]
|
||||||
return do_send_messages([message])[0]
|
return sent_message_result.message_id
|
||||||
|
|
||||||
|
|
||||||
# check_send_message:
|
# check_send_message:
|
||||||
|
@ -1281,7 +1295,7 @@ def check_send_message(
|
||||||
widget_content: Optional[str] = None,
|
widget_content: Optional[str] = None,
|
||||||
*,
|
*,
|
||||||
skip_stream_access_check: bool = False,
|
skip_stream_access_check: bool = False,
|
||||||
) -> int:
|
) -> SentMessageResult:
|
||||||
addressee = Addressee.legacy_build(sender, recipient_type_name, message_to, topic_name)
|
addressee = Addressee.legacy_build(sender, recipient_type_name, message_to, topic_name)
|
||||||
try:
|
try:
|
||||||
message = check_message(
|
message = check_message(
|
||||||
|
@ -1299,7 +1313,7 @@ def check_send_message(
|
||||||
skip_stream_access_check=skip_stream_access_check,
|
skip_stream_access_check=skip_stream_access_check,
|
||||||
)
|
)
|
||||||
except ZephyrMessageAlreadySentError as e:
|
except ZephyrMessageAlreadySentError as e:
|
||||||
return e.message_id
|
return SentMessageResult(message_id=e.message_id)
|
||||||
return do_send_messages([message])[0]
|
return do_send_messages([message])[0]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1745,8 +1759,8 @@ def internal_send_private_message(
|
||||||
)
|
)
|
||||||
if message is None:
|
if message is None:
|
||||||
return None
|
return None
|
||||||
message_ids = do_send_messages([message])
|
sent_message_result = do_send_messages([message])[0]
|
||||||
return message_ids[0]
|
return sent_message_result.message_id
|
||||||
|
|
||||||
|
|
||||||
def internal_send_stream_message(
|
def internal_send_stream_message(
|
||||||
|
@ -1769,8 +1783,9 @@ def internal_send_stream_message(
|
||||||
|
|
||||||
if message is None:
|
if message is None:
|
||||||
return None
|
return None
|
||||||
message_ids = do_send_messages([message])
|
|
||||||
return message_ids[0]
|
sent_message_result = do_send_messages([message])[0]
|
||||||
|
return sent_message_result.message_id
|
||||||
|
|
||||||
|
|
||||||
def internal_send_stream_message_by_name(
|
def internal_send_stream_message_by_name(
|
||||||
|
@ -1790,8 +1805,8 @@ def internal_send_stream_message_by_name(
|
||||||
|
|
||||||
if message is None:
|
if message is None:
|
||||||
return None
|
return None
|
||||||
message_ids = do_send_messages([message])
|
sent_message_result = do_send_messages([message])[0]
|
||||||
return message_ids[0]
|
return sent_message_result.message_id
|
||||||
|
|
||||||
|
|
||||||
def internal_send_huddle_message(
|
def internal_send_huddle_message(
|
||||||
|
@ -1806,5 +1821,5 @@ def internal_send_huddle_message(
|
||||||
)
|
)
|
||||||
if message is None:
|
if message is None:
|
||||||
return None
|
return None
|
||||||
message_ids = do_send_messages([message])
|
sent_message_result = do_send_messages([message])[0]
|
||||||
return message_ids[0]
|
return sent_message_result.message_id
|
||||||
|
|
|
@ -302,10 +302,10 @@ def send_scheduled_message(scheduled_message: ScheduledMessage) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
scheduled_message_to_self = scheduled_message.recipient == scheduled_message.sender.recipient
|
scheduled_message_to_self = scheduled_message.recipient == scheduled_message.sender.recipient
|
||||||
message_id = do_send_messages(
|
sent_message_result = do_send_messages(
|
||||||
[send_request], scheduled_message_to_self=scheduled_message_to_self
|
[send_request], scheduled_message_to_self=scheduled_message_to_self
|
||||||
)[0]
|
)[0]
|
||||||
scheduled_message.delivered_message_id = message_id
|
scheduled_message.delivered_message_id = sent_message_result.message_id
|
||||||
scheduled_message.delivered = True
|
scheduled_message.delivered = True
|
||||||
scheduled_message.save(update_fields=["delivered", "delivered_message_id"])
|
scheduled_message.save(update_fields=["delivered", "delivered_message_id"])
|
||||||
notify_remove_scheduled_message(scheduled_message.sender, scheduled_message.id)
|
notify_remove_scheduled_message(scheduled_message.sender, scheduled_message.id)
|
||||||
|
|
|
@ -208,6 +208,7 @@ class SendMessageRequest:
|
||||||
limit_unread_user_ids: Optional[Set[int]] = None
|
limit_unread_user_ids: Optional[Set[int]] = None
|
||||||
service_queue_events: Optional[Dict[str, List[Dict[str, Any]]]] = None
|
service_queue_events: Optional[Dict[str, List[Dict[str, Any]]]] = None
|
||||||
disable_external_notifications: bool = False
|
disable_external_notifications: bool = False
|
||||||
|
automatic_new_visibility_policy: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
# We won't try to fetch more unread message IDs from the database than
|
# We won't try to fetch more unread message IDs from the database than
|
||||||
|
|
|
@ -345,7 +345,9 @@ def send_initial_realm_messages(realm: Realm) -> None:
|
||||||
)
|
)
|
||||||
for message in welcome_messages
|
for message in welcome_messages
|
||||||
]
|
]
|
||||||
message_ids = do_send_messages(messages)
|
message_ids = [
|
||||||
|
sent_message_result.message_id for sent_message_result in do_send_messages(messages)
|
||||||
|
]
|
||||||
|
|
||||||
# We find the one of our just-sent messages with turtle.png in it,
|
# We find the one of our just-sent messages with turtle.png in it,
|
||||||
# and react to it. This is a bit hacky, but works and is kinda a
|
# and react to it. This is a bit hacky, but works and is kinda a
|
||||||
|
|
|
@ -1071,7 +1071,7 @@ Output:
|
||||||
recipient_list = [to_user.id]
|
recipient_list = [to_user.id]
|
||||||
(sending_client, _) = Client.objects.get_or_create(name=sending_client_name)
|
(sending_client, _) = Client.objects.get_or_create(name=sending_client_name)
|
||||||
|
|
||||||
return check_send_message(
|
sent_message_result = check_send_message(
|
||||||
from_user,
|
from_user,
|
||||||
sending_client,
|
sending_client,
|
||||||
"private",
|
"private",
|
||||||
|
@ -1079,6 +1079,7 @@ Output:
|
||||||
None,
|
None,
|
||||||
content,
|
content,
|
||||||
)
|
)
|
||||||
|
return sent_message_result.message_id
|
||||||
|
|
||||||
def send_huddle_message(
|
def send_huddle_message(
|
||||||
self,
|
self,
|
||||||
|
@ -1092,7 +1093,7 @@ Output:
|
||||||
|
|
||||||
(sending_client, _) = Client.objects.get_or_create(name=sending_client_name)
|
(sending_client, _) = Client.objects.get_or_create(name=sending_client_name)
|
||||||
|
|
||||||
return check_send_message(
|
sent_message_result = check_send_message(
|
||||||
from_user,
|
from_user,
|
||||||
sending_client,
|
sending_client,
|
||||||
"private",
|
"private",
|
||||||
|
@ -1100,6 +1101,7 @@ Output:
|
||||||
None,
|
None,
|
||||||
content,
|
content,
|
||||||
)
|
)
|
||||||
|
return sent_message_result.message_id
|
||||||
|
|
||||||
def send_stream_message(
|
def send_stream_message(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -6166,6 +6166,8 @@ paths:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "#/components/schemas/JsonSuccessBase"
|
- $ref: "#/components/schemas/JsonSuccessBase"
|
||||||
- additionalProperties: false
|
- additionalProperties: false
|
||||||
|
required:
|
||||||
|
- id
|
||||||
properties:
|
properties:
|
||||||
result: {}
|
result: {}
|
||||||
msg: {}
|
msg: {}
|
||||||
|
@ -6174,7 +6176,36 @@ paths:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
The unique ID assigned to the sent message.
|
The unique ID assigned to the sent message.
|
||||||
example: {"msg": "", "id": 42, "result": "success"}
|
automatic_new_visibility_policy:
|
||||||
|
type: integer
|
||||||
|
enum:
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
description: |
|
||||||
|
If the message's sender had configured their [visibility policy settings](/help/mute-a-topic)
|
||||||
|
to potentially automatically follow or unmute topics when sending messages,
|
||||||
|
and one of these policies did in fact change the user's visibility policy
|
||||||
|
for the topic where this message was sent, the new value for that user's
|
||||||
|
visibility policy for the recipient topic.
|
||||||
|
|
||||||
|
Only present if the sender's visibility was in fact changed.
|
||||||
|
|
||||||
|
Intended to be used by clients where it is important to explicitly display a
|
||||||
|
notice that the topic's visibility policy has changed automatically due to
|
||||||
|
the message sent. The information is also important to correctly decide whether to
|
||||||
|
display a warning or notice suggesting to unmute the topic after sending a message
|
||||||
|
to a muted stream. Such a notice might feel confusing in the event that the act of
|
||||||
|
sending the message had already resulted in the user unmuting or following the topic
|
||||||
|
in question.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 218).
|
||||||
|
example:
|
||||||
|
{
|
||||||
|
"msg": "",
|
||||||
|
"id": 42,
|
||||||
|
"automatic_new_visibility_policy": 2,
|
||||||
|
"result": "success",
|
||||||
|
}
|
||||||
"400":
|
"400":
|
||||||
description: Bad request.
|
description: Bad request.
|
||||||
content:
|
content:
|
||||||
|
|
|
@ -568,6 +568,47 @@ class MessagePOSTTest(ZulipTestCase):
|
||||||
result, "Stream '&<"'><non-existent>' does not exist"
|
result, "Stream '&<"'><non-existent>' does not exist"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_message_to_stream_with_automatically_change_visibility_policy(self) -> None:
|
||||||
|
"""
|
||||||
|
Sending a message to a stream with the automatic follow/unmute policy
|
||||||
|
enabled results in including an extra optional parameter in the response.
|
||||||
|
"""
|
||||||
|
user = self.example_user("hamlet")
|
||||||
|
do_change_user_setting(
|
||||||
|
user,
|
||||||
|
"automatically_follow_topics_policy",
|
||||||
|
UserProfile.AUTOMATICALLY_CHANGE_VISIBILITY_POLICY_ON_SEND,
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
result = self.api_post(
|
||||||
|
user,
|
||||||
|
"/api/v1/messages",
|
||||||
|
{
|
||||||
|
"type": "stream",
|
||||||
|
"to": orjson.dumps("Verona").decode(),
|
||||||
|
"content": "Test message",
|
||||||
|
"topic": "Test topic",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
content = self.assert_json_success(result)
|
||||||
|
assert "automatic_new_visibility_policy" in content
|
||||||
|
self.assertEqual(content["automatic_new_visibility_policy"], 3)
|
||||||
|
|
||||||
|
# Hamlet sends another message to the same topic. There will be no change in the visibility
|
||||||
|
# policy, so the 'automatic_new_visibility_policy' parameter should be absent in the result.
|
||||||
|
result = self.api_post(
|
||||||
|
user,
|
||||||
|
"/api/v1/messages",
|
||||||
|
{
|
||||||
|
"type": "stream",
|
||||||
|
"to": orjson.dumps("Verona").decode(),
|
||||||
|
"content": "Another Test message",
|
||||||
|
"topic": "Test topic",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
content = self.assert_json_success(result)
|
||||||
|
assert "automatic_new_visibility_policy" not in content
|
||||||
|
|
||||||
def test_personal_message(self) -> None:
|
def test_personal_message(self) -> None:
|
||||||
"""
|
"""
|
||||||
Sending a personal message to a valid username is successful.
|
Sending a personal message to a valid username is successful.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from email.headerregistry import Address
|
from email.headerregistry import Address
|
||||||
from typing import Iterable, Optional, Sequence, Union, cast
|
from typing import Dict, Iterable, Optional, Sequence, Union, cast
|
||||||
|
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
@ -221,7 +221,8 @@ def send_message_backend(
|
||||||
raise JsonableError(_("Invalid mirrored message"))
|
raise JsonableError(_("Invalid mirrored message"))
|
||||||
sender = user_profile
|
sender = user_profile
|
||||||
|
|
||||||
ret = check_send_message(
|
data: Dict[str, int] = {}
|
||||||
|
sent_message_result = check_send_message(
|
||||||
sender,
|
sender,
|
||||||
client,
|
client,
|
||||||
recipient_type_name,
|
recipient_type_name,
|
||||||
|
@ -236,7 +237,12 @@ def send_message_backend(
|
||||||
sender_queue_id=queue_id,
|
sender_queue_id=queue_id,
|
||||||
widget_content=widget_content,
|
widget_content=widget_content,
|
||||||
)
|
)
|
||||||
return json_success(request, data={"id": ret})
|
data["id"] = sent_message_result.message_id
|
||||||
|
if sent_message_result.automatic_new_visibility_policy:
|
||||||
|
data[
|
||||||
|
"automatic_new_visibility_policy"
|
||||||
|
] = sent_message_result.automatic_new_visibility_policy
|
||||||
|
return json_success(request, data=data)
|
||||||
|
|
||||||
|
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
|
|
|
@ -118,7 +118,9 @@ From image editing program:
|
||||||
for message in staged_messages
|
for message in staged_messages
|
||||||
]
|
]
|
||||||
|
|
||||||
message_ids = do_send_messages(messages)
|
message_ids = [
|
||||||
|
sent_message_result.message_id for sent_message_result in do_send_messages(messages)
|
||||||
|
]
|
||||||
|
|
||||||
preview_message = Message.objects.get(
|
preview_message = Message.objects.get(
|
||||||
id__in=message_ids, content__icontains="image previews"
|
id__in=message_ids, content__icontains="image previews"
|
||||||
|
|
Loading…
Reference in New Issue