narrow: Support string and integer encoding of "id" operator.

Expands support for the message ID operand for id" operator to be either
a string or an integer. Previously, this operand was always validated as
a string.
This commit is contained in:
Lauryn Menard 2023-07-17 15:39:05 +02:00 committed by Tim Abbott
parent 211934a9d9
commit 3255281a83
6 changed files with 70 additions and 15 deletions

View File

@ -20,6 +20,16 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 8.0 ## Changes in Zulip 8.0
**Feature level 194**
* [`GET /messages`](/api/get-messages),
[`GET /messages/matches_narrow`](/api/check-messages-match-narrow),
[`POST /message/flags/narrow`](/api/update-message-flags-for-narrow),
[`POST /register`](/api/register-queue):
For [search/narrow filters](/api/construct-narrow) with the `id`
operator, added support for encoding the message ID operand as either
a string or an integer. Previously, only string encoding was supported.
**Feature level 193** **Feature level 193**
* [`POST /messages/{message_id}/reactions`](/api/add-reaction), * [`POST /messages/{message_id}/reactions`](/api/add-reaction),

View File

@ -65,12 +65,33 @@ filters did.
## Narrows that use IDs ## Narrows that use IDs
### Message IDs
The `near` and `id` operators, documented in the help center, use message The `near` and `id` operators, documented in the help center, use message
IDs for their operands. IDs for their operands.
* `near:12345`: Search messages around the message with ID `12345`. * `near:12345`: Search messages around the message with ID `12345`.
* `id:12345`: Search for only message with ID `12345`. * `id:12345`: Search for only message with ID `12345`.
The message ID operand for the `id` operator may be encoded as either a
number or a string. The message ID operand for the `near` operator must
be encoded as a string.
**Changes**: Prior to Zulip 8.0 (feature level 194), the message ID
operand for the `id` operator needed to be encoded as a string.
```json
[
{
"operator": "id",
"operand": 12345
}
]
```
### Stream and user IDs
There are a few additional narrow/search options (new in Zulip 2.1) There are a few additional narrow/search options (new in Zulip 2.1)
that use either stream IDs or user IDs that are not documented in the that use either stream IDs or user IDs that are not documented in the
help center because they are primarily useful to API clients: help center because they are primarily useful to API clients:

View File

@ -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 = 193 API_FEATURE_LEVEL = 194
# 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

View File

@ -140,13 +140,20 @@ function get_messages_success(data, opts) {
process_result(data, opts); process_result(data, opts);
} }
// This function modifies the data.narrow filters to use user IDs // This function modifies the data.narrow filters to use integer IDs
// instead of emails string if it is supported. We currently don't set // instead of strings if it is supported. We currently don't set or
// or convert the emails string to user IDs directly into the Filter code // convert user emails to user IDs directly in the Filter code
// because doing so breaks the app in various modules that expect emails string. // because doing so breaks the app in various modules that expect a
// string of user emails.
function handle_operators_supporting_id_based_api(data) { function handle_operators_supporting_id_based_api(data) {
const operators_supporting_ids = new Set(["dm", "pm-with"]); const operators_supporting_ids = new Set(["dm", "pm-with"]);
const operators_supporting_id = new Set(["sender", "group-pm-with", "stream", "dm-including"]); const operators_supporting_id = new Set([
"id",
"stream",
"sender",
"group-pm-with",
"dm-including",
]);
if (data.narrow === undefined) { if (data.narrow === undefined) {
return data; return data;
@ -159,6 +166,12 @@ function handle_operators_supporting_id_based_api(data) {
} }
if (operators_supporting_id.has(filter.operator)) { if (operators_supporting_id.has(filter.operator)) {
if (filter.operator === "id") {
// The message ID may not exist locally,
// so send the filter to the server as is.
return filter;
}
if (filter.operator === "stream") { if (filter.operator === "stream") {
const stream_id = stream_data.get_stream_id(filter.operand); const stream_id = stream_data.get_stream_id(filter.operand);
if (stream_id !== undefined) { if (stream_id !== undefined) {

View File

@ -751,11 +751,17 @@ def narrow_parameter(var_name: str, json: str) -> OptionalNarrowListT:
return dict(operator=elem[0], operand=elem[1]) return dict(operator=elem[0], operand=elem[1])
if isinstance(elem, dict): if isinstance(elem, dict):
# Make sure to sync this list to frontend also when adding a new operator. # Make sure to sync this list to frontend also when adding a new operator that
# that supports user IDs. Relevant code is located in web/src/message_fetch.js # supports integer IDs. Relevant code is located in web/src/message_fetch.js
# in handle_operators_supporting_id_based_api function where you will need to update # in handle_operators_supporting_id_based_api function where you will need to
# operators_supporting_id, or operators_supporting_ids array. # update operators_supporting_id, or operators_supporting_ids array.
operators_supporting_id = ["sender", "group-pm-with", "stream", "dm-including"] operators_supporting_id = [
"id",
"stream",
"sender",
"group-pm-with",
"dm-including",
]
operators_supporting_ids = ["pm-with", "dm"] operators_supporting_ids = ["pm-with", "dm"]
operators_non_empty_operand = {"search"} operators_non_empty_operand = {"search"}

View File

@ -417,10 +417,14 @@ class NarrowBuilderTest(ZulipTestCase):
"WHERE NOT (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s OR recipient_id IN (__[POSTCOMPILE_recipient_id_3]))", "WHERE NOT (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s OR recipient_id IN (__[POSTCOMPILE_recipient_id_3]))",
) )
def test_add_term_using_id_operator(self) -> None: def test_add_term_using_id_operator_integer(self) -> None:
term = dict(operator="id", operand=555) term = dict(operator="id", operand=555)
self._do_add_term_test(term, "WHERE id = %(param_1)s") self._do_add_term_test(term, "WHERE id = %(param_1)s")
def test_add_term_using_id_operator_string(self) -> None:
term = dict(operator="id", operand="555")
self._do_add_term_test(term, "WHERE id = %(param_1)s")
def test_add_term_using_id_operator_invalid(self) -> None: def test_add_term_using_id_operator_invalid(self) -> None:
term = dict(operator="id", operand="") term = dict(operator="id", operand="")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
@ -3412,10 +3416,11 @@ class GetOldMessagesTest(ZulipTestCase):
def test_invalid_narrow_operand_in_dict(self) -> None: def test_invalid_narrow_operand_in_dict(self) -> None:
self.login("hamlet") self.login("hamlet")
# str or int is required for "sender", "stream", "dm-including" and "group-pm-with" operators # str or int is required for "id", "sender", "stream", "dm-including" and "group-pm-with"
# operators
invalid_operands = [["1"], [2], None] invalid_operands = [["1"], [2], None]
error_msg = 'elem["operand"] is not a string or integer' error_msg = 'elem["operand"] is not a string or integer'
for operand in ["sender", "group-pm-with", "stream", "dm-including"]: for operand in ["id", "sender", "stream", "dm-including", "group-pm-with"]:
self.exercise_bad_narrow_operand_using_dict_api(operand, invalid_operands, error_msg) self.exercise_bad_narrow_operand_using_dict_api(operand, invalid_operands, error_msg)
# str or int list is required for "dm" and "pm-with" operator # str or int list is required for "dm" and "pm-with" operator
@ -3432,7 +3437,7 @@ class GetOldMessagesTest(ZulipTestCase):
# For others only str is acceptable # For others only str is acceptable
invalid_operands = [2, None, [1]] invalid_operands = [2, None, [1]]
error_msg = 'elem["operand"] is not a string' error_msg = 'elem["operand"] is not a string'
for operand in ["is", "near", "has", "id"]: for operand in ["is", "near", "has"]:
self.exercise_bad_narrow_operand_using_dict_api(operand, invalid_operands, error_msg) self.exercise_bad_narrow_operand_using_dict_api(operand, invalid_operands, error_msg)
# Disallow empty search terms # Disallow empty search terms