typing_indicator: Add a 'stream_id' parameter to 'POST /typing'.

This commit adds a 'stream_id' parameter to the 'POST /typing'
endpoint.

Now, 'to' is used only for "direct" type. In the case of
"stream" type, 'stream_id' and 'topic' are used.
This commit is contained in:
Prakhar Pratyush 2023-10-12 12:43:34 +05:30 committed by Tim Abbott
parent 78683c1b9c
commit c520a96354
6 changed files with 76 additions and 47 deletions

View File

@ -29,10 +29,11 @@ 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 * [`POST /typing`](/api/set-typing-status): Stopped using the `to` parameter
being composed is `"stream"`, changed the `to` parameter to accept the for the `"stream"` type. Previously, in the case of the `"stream"` type, it
ID of the stream in which the message is being typed. Previously, it accepted a single-element list containing the ID of the stream. Added an
accepted a single-element list containing the ID of the stream. optional parameter, `stream_id`. Now, `to` is used only for `"direct"` type.
In the case of `"stream"` type, `stream_id` and `topic` are used.
* Note that stream typing notifications were not enabled in any Zulip client * Note that stream typing notifications were not enabled in any Zulip client
prior to feature level 215. prior to feature level 215.

View File

@ -47,7 +47,7 @@ function send_direct_message_typing_notification(user_ids_array, operation) {
function send_stream_typing_notification(stream_id, topic, operation) { function send_stream_typing_notification(stream_id, topic, operation) {
const data = { const data = {
type: "stream", type: "stream",
to: JSON.stringify(stream_id), stream_id: JSON.stringify(stream_id),
topic, topic,
op: operation, op: operation,
}; };

View File

@ -1351,7 +1351,7 @@ def set_typing_status(client: Client) -> None:
request = { request = {
"type": "stream", "type": "stream",
"op": "start", "op": "start",
"to": stream_id, "stream_id": 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, "stream_id": stream_id,
"topic": topic, "topic": topic,
} }
result = client.set_typing_status(request) result = client.set_typing_status(request)

View File

@ -16689,17 +16689,14 @@ paths:
- name: to - name: to
in: query in: query
description: | description: |
For `"direct"` type it is the user IDs of the recipients of the message User IDs of the recipients of the message being typed. Required for the
being typed. Send a JSON-encoded list of user IDs. (Use a list even if `"direct"` type. Ignored in the case of `"stream"` type. Send a JSON-encoded
there is only one recipient.) list of user IDs. (Use a list even if there is only one recipient.)
For `"stream"` type it is the ID of the stream in which the message is **Changes**: In Zulip 8.0 (feature level 215), stopped using this parameter
being typed. for the `"stream"` type. Previously, in the case of the `"stream"` type, it
accepted a single-element list containing the ID of the stream. A new parameter,
**Changes**: In Zulip 8.0 (feature level 215), for the `"stream"` `type`, `stream_id`, is now used for this.
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 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
@ -16711,14 +16708,22 @@ paths:
content: content:
application/json: application/json:
schema: schema:
oneOf: type: array
- type: integer items:
- type: array type: integer
items: minLength: 1
type: integer
minLength: 1
example: [9, 10] example: [9, 10]
required: true - name: stream_id
in: query
description: |
ID of the stream in which the message is being typed. Required for the `"stream"`
type. Ignored in the case of `"direct"` type.
**Changes**: New in Zulip 8.0 (feature level 215). Previously, a single-element
list containing the ID of the stream was passed in `to` parameter.
schema:
type: integer
example: 7
- name: topic - name: topic
in: query in: query
description: | description: |

View File

@ -56,17 +56,7 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
""" """
sender = self.example_user("hamlet") sender = self.example_user("hamlet")
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": "2"}) result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": "2"})
self.assert_json_error(result, "Invalid data type for recipients") self.assert_json_error(result, "to is not a list")
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: def test_invalid_user_id_for_direct_messages(self) -> None:
""" """
@ -75,7 +65,7 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
sender = self.example_user("hamlet") sender = self.example_user("hamlet")
invalid_user_ids = orjson.dumps([2, "a", 4]).decode() invalid_user_ids = orjson.dumps([2, "a", 4]).decode()
result = self.api_post(sender, "/api/v1/typing", {"op": "start", "to": invalid_user_ids}) 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") self.assert_json_error(result, "to[1] is not an integer")
def test_empty_to_array_direct_messages(self) -> None: def test_empty_to_array_direct_messages(self) -> None:
""" """
@ -102,6 +92,32 @@ 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")
class TypingValidateStreamIdTopicArgumentsTest(ZulipTestCase):
def test_missing_stream_id(self) -> None:
"""
Sending stream typing notifications without 'stream_id' fails.
"""
sender = self.example_user("hamlet")
result = self.api_post(
sender,
"/api/v1/typing",
{"type": "stream", "op": "start", "topic": "test"},
)
self.assert_json_error(result, "Missing stream_id")
def test_invalid_stream_id(self) -> None:
"""
Sending stream typing notifications without 'stream_id' as an integer fails.
"""
sender = self.example_user("hamlet")
result = self.api_post(
sender,
"/api/v1/typing",
{"type": "stream", "op": "start", "stream_id": "invalid", "topic": "test"},
)
self.assert_json_error(result, 'Argument "stream_id" is not valid JSON.')
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 +125,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": str(stream_id)}, {"type": "stream", "op": "start", "stream_id": str(stream_id)},
) )
self.assert_json_error(result, "Missing topic") self.assert_json_error(result, "Missing topic")
@ -124,7 +140,7 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
{ {
"type": "stream", "type": "stream",
"op": "start", "op": "start",
"to": str(stream_id), "stream_id": str(stream_id),
"topic": topic, "topic": topic,
}, },
) )
@ -364,7 +380,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
params = dict( params = dict(
type="stream", type="stream",
op="start", op="start",
to=str(stream_id), stream_id=str(stream_id),
topic=topic, topic=topic,
) )
@ -398,7 +414,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
params = dict( params = dict(
type="stream", type="stream",
op="stop", op="stop",
to=str(stream_id), stream_id=str(stream_id),
topic=topic, topic=topic,
) )
@ -467,7 +483,7 @@ class TestSendTypingNotificationsSettings(ZulipTestCase):
params = dict( params = dict(
type="stream", type="stream",
op="start", op="start",
to=str(stream_id), stream_id=str(stream_id),
topic=topic, topic=topic,
) )

View File

@ -1,15 +1,14 @@
from typing import Optional from typing import List, 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_string_in from zerver.lib.validator import check_int, check_list, check_string_in
from zerver.models import UserProfile from zerver.models import UserProfile
VALID_OPERATOR_TYPES = ["start", "stop"] VALID_OPERATOR_TYPES = ["start", "stop"]
@ -24,12 +23,17 @@ 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: str = REQ("to"), notification_to: Optional[List[int]] = REQ(
"to", json_validator=check_list(check_int), default=None
),
stream_id: Optional[int] = REQ(json_validator=check_int, default=None),
topic: Optional[str] = REQ("topic", default=None), topic: Optional[str] = REQ("topic", default=None),
) -> HttpResponse: ) -> HttpResponse:
recipient_type_name = req_type recipient_type_name = req_type
if recipient_type_name == "stream": if recipient_type_name == "stream":
stream_id = extract_stream_id(notification_to) if stream_id is None:
raise JsonableError(_("Missing stream_id"))
if topic is None: if topic is None:
raise JsonableError(_("Missing topic")) raise JsonableError(_("Missing topic"))
@ -42,7 +46,10 @@ def send_notification_backend(
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) if notification_to is None:
raise JsonableError(_("Missing 'to' argument"))
user_ids = notification_to
to_length = len(user_ids) to_length = len(user_ids)
if to_length == 0: if to_length == 0:
raise JsonableError(_("Empty 'to' list")) raise JsonableError(_("Empty 'to' list"))