mirror of https://github.com/zulip/zulip.git
typing_indicator: Update the 'to' parameter to accept stream ID.
When the `type` of the message being composed is "stream", this commit updates the `to` parameter to accept the ID of the stream in which the message is being typed. Earlier, it accepted a single-element list containing the ID of the stream. Sending the element instead of a list containing the single element makes more sense.
This commit is contained in:
parent
8b12cc606a
commit
e23f3ac89b
|
@ -29,6 +29,14 @@ format used by the Zulip server that they are interacting with.
|
||||||
* [`POST /typing`](/api/set-typing-status): Stopped supporting `private`
|
* [`POST /typing`](/api/set-typing-status): Stopped supporting `private`
|
||||||
as a valid value for the `type` parameter.
|
as a valid value for the `type` parameter.
|
||||||
|
|
||||||
|
* [`POST /typing`](/api/set-typing-status): When the `type` of the message
|
||||||
|
being composed is `"stream"`, changed the `to` parameter to accept the
|
||||||
|
ID of the stream in which the message is being typed. Previously, it
|
||||||
|
accepted a single-element list containing the ID of the stream.
|
||||||
|
|
||||||
|
* Note that stream typing notifications were not enabled in any Zulip client
|
||||||
|
prior to feature level 215.
|
||||||
|
|
||||||
**Feature level 214**
|
**Feature level 214**
|
||||||
|
|
||||||
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
|
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
|
||||||
|
|
|
@ -1351,7 +1351,7 @@ def set_typing_status(client: Client) -> None:
|
||||||
request = {
|
request = {
|
||||||
"type": "stream",
|
"type": "stream",
|
||||||
"op": "start",
|
"op": "start",
|
||||||
"to": [stream_id],
|
"to": stream_id,
|
||||||
"topic": topic,
|
"topic": topic,
|
||||||
}
|
}
|
||||||
result = client.set_typing_status(request)
|
result = client.set_typing_status(request)
|
||||||
|
@ -1369,7 +1369,7 @@ def set_typing_status(client: Client) -> None:
|
||||||
request = {
|
request = {
|
||||||
"type": "stream",
|
"type": "stream",
|
||||||
"op": "stop",
|
"op": "stop",
|
||||||
"to": [stream_id],
|
"to": stream_id,
|
||||||
"topic": topic,
|
"topic": topic,
|
||||||
}
|
}
|
||||||
result = client.set_typing_status(request)
|
result = client.set_typing_status(request)
|
||||||
|
|
|
@ -16630,7 +16630,11 @@ paths:
|
||||||
See the subsystems documentation on [typing indicators][typing-protocol-docs]
|
See the subsystems documentation on [typing indicators][typing-protocol-docs]
|
||||||
for additional design details on Zulip's typing notifications protocol.
|
for additional design details on Zulip's typing notifications protocol.
|
||||||
|
|
||||||
**Changes**: Support for displaying stream typing notifications was new
|
**Changes**: Clients shouldn't care about the APIs prior to Zulip 8.0 (feature level 215)
|
||||||
|
for stream typing notifications, as no client actually implemented
|
||||||
|
the previous API for those.
|
||||||
|
|
||||||
|
Support for displaying stream typing notifications was new
|
||||||
in Zulip 4.0 (feature level 58). Clients should indicate they support
|
in Zulip 4.0 (feature level 58). Clients should indicate they support
|
||||||
processing stream typing notifications via the `stream_typing_notifications`
|
processing stream typing notifications via the `stream_typing_notifications`
|
||||||
value in the `client_capabilities` parameter of the
|
value in the `client_capabilities` parameter of the
|
||||||
|
@ -16689,10 +16693,15 @@ paths:
|
||||||
being typed. Send a JSON-encoded list of user IDs. (Use a list even if
|
being typed. Send a JSON-encoded list of user IDs. (Use a list even if
|
||||||
there is only one recipient.)
|
there is only one recipient.)
|
||||||
|
|
||||||
For `"stream"` type it is a single element list containing ID of stream in
|
For `"stream"` type it is the ID of the stream in which the message is
|
||||||
which the message is being typed.
|
being typed.
|
||||||
|
|
||||||
**Changes**: Support for typing notifications for stream messages
|
**Changes**: In Zulip 8.0 (feature level 215), for the `"stream"` `type`,
|
||||||
|
changed this parameter to accept the ID of the stream in which the message
|
||||||
|
is being typed. Previously, it accepted a single-element list containing
|
||||||
|
the ID of the stream.
|
||||||
|
|
||||||
|
Support for typing notifications for stream messages
|
||||||
is new in Zulip 4.0 (feature level 58). Previously, typing
|
is new in Zulip 4.0 (feature level 58). Previously, typing
|
||||||
notifications were only for direct messages.
|
notifications were only for direct messages.
|
||||||
|
|
||||||
|
@ -16702,9 +16711,12 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: array
|
oneOf:
|
||||||
items:
|
- type: integer
|
||||||
type: integer
|
- type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minLength: 1
|
||||||
example: [9, 10]
|
example: [9, 10]
|
||||||
required: true
|
required: true
|
||||||
- name: topic
|
- name: topic
|
||||||
|
|
|
@ -50,6 +50,33 @@ class TypingMessagetypeTest(ZulipTestCase):
|
||||||
|
|
||||||
|
|
||||||
class TypingValidateToArgumentsTest(ZulipTestCase):
|
class TypingValidateToArgumentsTest(ZulipTestCase):
|
||||||
|
def test_invalid_to_for_direct_messages(self) -> None:
|
||||||
|
"""
|
||||||
|
Sending dms typing notifications without 'to' as a list fails.
|
||||||
|
"""
|
||||||
|
sender = self.example_user("hamlet")
|
||||||
|
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": "2"})
|
||||||
|
self.assert_json_error(result, "Invalid data type for recipients")
|
||||||
|
|
||||||
|
def test_invalid_to_for_stream_messages(self) -> None:
|
||||||
|
"""
|
||||||
|
Sending stream typing notifications without 'to' as an integer fails.
|
||||||
|
"""
|
||||||
|
sender = self.example_user("hamlet")
|
||||||
|
result = self.api_post(
|
||||||
|
sender, "/api/v1/typing", {"type": "stream", "op": "start", "to": "invalid"}
|
||||||
|
)
|
||||||
|
self.assert_json_error(result, "Invalid data type for stream ID")
|
||||||
|
|
||||||
|
def test_invalid_user_id_for_direct_messages(self) -> None:
|
||||||
|
"""
|
||||||
|
Sending dms typing notifications with invalid user_id fails.
|
||||||
|
"""
|
||||||
|
sender = self.example_user("hamlet")
|
||||||
|
invalid_user_ids = orjson.dumps([2, "a", 4]).decode()
|
||||||
|
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": invalid_user_ids})
|
||||||
|
self.assert_json_error(result, "Recipient list may only contain user IDs")
|
||||||
|
|
||||||
def test_empty_to_array_direct_messages(self) -> None:
|
def test_empty_to_array_direct_messages(self) -> None:
|
||||||
"""
|
"""
|
||||||
Sending dms typing notification without recipient fails
|
Sending dms typing notification without recipient fails
|
||||||
|
@ -58,16 +85,6 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
|
||||||
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": "[]"})
|
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": "[]"})
|
||||||
self.assert_json_error(result, "Empty 'to' list")
|
self.assert_json_error(result, "Empty 'to' list")
|
||||||
|
|
||||||
def test_empty_to_array_stream(self) -> None:
|
|
||||||
"""
|
|
||||||
Sending stream typing notification without recipient fails
|
|
||||||
"""
|
|
||||||
sender = self.example_user("hamlet")
|
|
||||||
result = self.api_post(
|
|
||||||
sender, "/api/v1/typing", {"type": "stream", "op": "start", "to": "[]"}
|
|
||||||
)
|
|
||||||
self.assert_json_error(result, "Empty 'to' list")
|
|
||||||
|
|
||||||
def test_missing_recipient(self) -> None:
|
def test_missing_recipient(self) -> None:
|
||||||
"""
|
"""
|
||||||
Sending typing notification without recipient fails
|
Sending typing notification without recipient fails
|
||||||
|
@ -76,15 +93,6 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
|
||||||
result = self.api_post(sender, "/api/v1/typing", {"op": "start"})
|
result = self.api_post(sender, "/api/v1/typing", {"op": "start"})
|
||||||
self.assert_json_error(result, "Missing 'to' argument")
|
self.assert_json_error(result, "Missing 'to' argument")
|
||||||
|
|
||||||
def test_argument_to_is_not_valid_json(self) -> None:
|
|
||||||
"""
|
|
||||||
Sending typing notification to invalid recipient fails
|
|
||||||
"""
|
|
||||||
sender = self.example_user("hamlet")
|
|
||||||
invalid = "bad email"
|
|
||||||
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": invalid})
|
|
||||||
self.assert_json_error(result, 'Argument "to" is not valid JSON.')
|
|
||||||
|
|
||||||
def test_bogus_user_id(self) -> None:
|
def test_bogus_user_id(self) -> None:
|
||||||
"""
|
"""
|
||||||
Sending typing notification to invalid recipient fails
|
Sending typing notification to invalid recipient fails
|
||||||
|
@ -94,14 +102,6 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
|
||||||
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": invalid})
|
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": invalid})
|
||||||
self.assert_json_error(result, "Invalid user ID 9999999")
|
self.assert_json_error(result, "Invalid user ID 9999999")
|
||||||
|
|
||||||
def test_send_multiple_stream_ids(self) -> None:
|
|
||||||
sender = self.example_user("hamlet")
|
|
||||||
|
|
||||||
result = self.api_post(
|
|
||||||
sender, "/api/v1/typing", {"type": "stream", "op": "stop", "to": "[1, 2, 3]"}
|
|
||||||
)
|
|
||||||
self.assert_json_error(result, "Cannot send to multiple streams")
|
|
||||||
|
|
||||||
def test_includes_stream_id_but_not_topic(self) -> None:
|
def test_includes_stream_id_but_not_topic(self) -> None:
|
||||||
sender = self.example_user("hamlet")
|
sender = self.example_user("hamlet")
|
||||||
stream_id = self.get_stream_id("general")
|
stream_id = self.get_stream_id("general")
|
||||||
|
@ -109,7 +109,7 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
|
||||||
result = self.api_post(
|
result = self.api_post(
|
||||||
sender,
|
sender,
|
||||||
"/api/v1/typing",
|
"/api/v1/typing",
|
||||||
{"type": "stream", "op": "start", "to": orjson.dumps([stream_id]).decode()},
|
{"type": "stream", "op": "start", "to": str(stream_id)},
|
||||||
)
|
)
|
||||||
self.assert_json_error(result, "Missing topic")
|
self.assert_json_error(result, "Missing topic")
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
|
||||||
{
|
{
|
||||||
"type": "stream",
|
"type": "stream",
|
||||||
"op": "start",
|
"op": "start",
|
||||||
"to": orjson.dumps([stream_id]).decode(),
|
"to": str(stream_id),
|
||||||
"topic": topic,
|
"topic": topic,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -364,7 +364,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
|
||||||
params = dict(
|
params = dict(
|
||||||
type="stream",
|
type="stream",
|
||||||
op="start",
|
op="start",
|
||||||
to=orjson.dumps([stream_id]).decode(),
|
to=str(stream_id),
|
||||||
topic=topic,
|
topic=topic,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -398,7 +398,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
|
||||||
params = dict(
|
params = dict(
|
||||||
type="stream",
|
type="stream",
|
||||||
op="stop",
|
op="stop",
|
||||||
to=orjson.dumps([stream_id]).decode(),
|
to=str(stream_id),
|
||||||
topic=topic,
|
topic=topic,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -467,7 +467,7 @@ class TestSendTypingNotificationsSettings(ZulipTestCase):
|
||||||
params = dict(
|
params = dict(
|
||||||
type="stream",
|
type="stream",
|
||||||
op="start",
|
op="start",
|
||||||
to=orjson.dumps([stream_id]).decode(),
|
to=str(stream_id),
|
||||||
topic=topic,
|
topic=topic,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from zerver.actions.typing import check_send_typing_notification, do_send_stream_typing_notification
|
from zerver.actions.typing import check_send_typing_notification, do_send_stream_typing_notification
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError
|
||||||
|
from zerver.lib.recipient_parsing import extract_direct_message_recipient_ids, extract_stream_id
|
||||||
from zerver.lib.request import REQ, has_request_variables
|
from zerver.lib.request import REQ, has_request_variables
|
||||||
from zerver.lib.response import json_success
|
from zerver.lib.response import json_success
|
||||||
from zerver.lib.streams import access_stream_by_id, access_stream_for_send_message
|
from zerver.lib.streams import access_stream_by_id, access_stream_for_send_message
|
||||||
from zerver.lib.validator import check_int, check_list, check_string_in
|
from zerver.lib.validator import check_string_in
|
||||||
from zerver.models import UserProfile
|
from zerver.models import UserProfile
|
||||||
|
|
||||||
VALID_OPERATOR_TYPES = ["start", "stop"]
|
VALID_OPERATOR_TYPES = ["start", "stop"]
|
||||||
|
@ -23,36 +24,32 @@ def send_notification_backend(
|
||||||
"type", str_validator=check_string_in(VALID_RECIPIENT_TYPES), default="direct"
|
"type", str_validator=check_string_in(VALID_RECIPIENT_TYPES), default="direct"
|
||||||
),
|
),
|
||||||
operator: str = REQ("op", str_validator=check_string_in(VALID_OPERATOR_TYPES)),
|
operator: str = REQ("op", str_validator=check_string_in(VALID_OPERATOR_TYPES)),
|
||||||
notification_to: List[int] = REQ("to", json_validator=check_list(check_int)),
|
notification_to: str = REQ("to"),
|
||||||
topic: Optional[str] = REQ("topic", default=None),
|
topic: Optional[str] = REQ("topic", default=None),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
to_length = len(notification_to)
|
|
||||||
|
|
||||||
if to_length == 0:
|
|
||||||
raise JsonableError(_("Empty 'to' list"))
|
|
||||||
|
|
||||||
recipient_type_name = req_type
|
recipient_type_name = req_type
|
||||||
if recipient_type_name == "stream":
|
if recipient_type_name == "stream":
|
||||||
if to_length > 1:
|
stream_id = extract_stream_id(notification_to)
|
||||||
raise JsonableError(_("Cannot send to multiple streams"))
|
|
||||||
|
|
||||||
if topic is None:
|
if topic is None:
|
||||||
raise JsonableError(_("Missing topic"))
|
raise JsonableError(_("Missing topic"))
|
||||||
|
|
||||||
if not user_profile.send_stream_typing_notifications:
|
if not user_profile.send_stream_typing_notifications:
|
||||||
raise JsonableError(_("User has disabled typing notifications for stream messages"))
|
raise JsonableError(_("User has disabled typing notifications for stream messages"))
|
||||||
|
|
||||||
stream_id = notification_to[0]
|
|
||||||
# Verify that the user has access to the stream and has
|
# Verify that the user has access to the stream and has
|
||||||
# permission to send messages to it.
|
# permission to send messages to it.
|
||||||
stream = access_stream_by_id(user_profile, stream_id)[0]
|
stream = access_stream_by_id(user_profile, stream_id)[0]
|
||||||
access_stream_for_send_message(user_profile, stream, forwarder_user_profile=None)
|
access_stream_for_send_message(user_profile, stream, forwarder_user_profile=None)
|
||||||
do_send_stream_typing_notification(user_profile, operator, stream, topic)
|
do_send_stream_typing_notification(user_profile, operator, stream, topic)
|
||||||
else:
|
else:
|
||||||
|
user_ids = extract_direct_message_recipient_ids(notification_to)
|
||||||
|
to_length = len(user_ids)
|
||||||
|
if to_length == 0:
|
||||||
|
raise JsonableError(_("Empty 'to' list"))
|
||||||
|
|
||||||
if not user_profile.send_private_typing_notifications:
|
if not user_profile.send_private_typing_notifications:
|
||||||
raise JsonableError(_("User has disabled typing notifications for direct messages"))
|
raise JsonableError(_("User has disabled typing notifications for direct messages"))
|
||||||
|
|
||||||
user_ids = notification_to
|
|
||||||
check_send_typing_notification(user_profile, user_ids, operator)
|
check_send_typing_notification(user_profile, user_ids, operator)
|
||||||
|
|
||||||
return json_success(request)
|
return json_success(request)
|
||||||
|
|
Loading…
Reference in New Issue