diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index fa4c28e33f..6bffb6a552 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -10,6 +10,11 @@ below features are supported. ## Changes in Zulip 4.0 +**Feature level 43** + +* [`GET /users/{user_id_or_email}/presence`]: Added support for + passing the `user_id` to identify the target user. + **Feature level 42** * `PATCH /settings/display`: Added a new `default_view` setting allowing diff --git a/templates/zerver/api/get-user-presence.md b/templates/zerver/api/get-user-presence.md index 581a1e0e57..83ab5a5005 100644 --- a/templates/zerver/api/get-user-presence.md +++ b/templates/zerver/api/get-user-presence.md @@ -1,32 +1,32 @@ # Get user presence -{generate_api_description(/users/{email}/presence:get)} +{generate_api_description(/users/{user_id_or_email}/presence:get)} ## Usage examples {start_tabs} {tab|python} -{generate_code_example(python)|/users/{email}/presence:get|example} +{generate_code_example(python)|/users/{user_id_or_email}/presence:get|example} {tab|curl} -{generate_code_example(curl)|/users/{email}/presence:get|example} +{generate_code_example(curl)|/users/{user_id_or_email}/presence:get|example} {end_tabs} ## Parameters -{generate_api_arguments_table|zulip.yaml|/users/{email}/presence:get} +{generate_api_arguments_table|zulip.yaml|/users/{user_id_or_email}/presence:get} ## Response #### Return values -{generate_return_values_table|zulip.yaml|/users/{email}/presence:get} +{generate_return_values_table|zulip.yaml|/users/{user_id_or_email}/presence:get} #### Example response A typical successful JSON response may look like: -{generate_code_example|/users/{email}/presence:get|fixture(200)} +{generate_code_example|/users/{user_id_or_email}/presence:get|fixture(200)} diff --git a/version.py b/version.py index b04a7e4c68..3240c85025 100644 --- a/version.py +++ b/version.py @@ -30,7 +30,7 @@ DESKTOP_WARNING_VERSION = "5.2.0" # # Changes should be accompanied by documentation explaining what the # new level means in templates/zerver/api/changelog.md. -API_FEATURE_LEVEL = 42 +API_FEATURE_LEVEL = 43 # 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 diff --git a/zerver/openapi/curl_param_value_generators.py b/zerver/openapi/curl_param_value_generators.py index 71fb467e63..27d449377d 100644 --- a/zerver/openapi/curl_param_value_generators.py +++ b/zerver/openapi/curl_param_value_generators.py @@ -219,7 +219,7 @@ def delete_event_queue() -> Dict[str, object]: } -@openapi_param_value_generator(["/users/{email}/presence:get"]) +@openapi_param_value_generator(["/users/{user_id_or_email}/presence:get"]) def get_user_presence() -> Dict[str, object]: iago = helpers.example_user("iago") client = Client.objects.create(name="curl-test-client-3") diff --git a/zerver/openapi/python_examples.py b/zerver/openapi/python_examples.py index 4e097140e4..bc96499527 100644 --- a/zerver/openapi/python_examples.py +++ b/zerver/openapi/python_examples.py @@ -140,7 +140,7 @@ def test_authorization_errors_fatal(client: Client, nonadmin_client: Client) -> validate_against_openapi_schema(result, "/users/me/subscriptions", "post", "400_1") -@openapi_test_function("/users/{email}/presence:get") +@openapi_test_function("/users/{user_id_or_email}/presence:get") def get_user_presence(client: Client) -> None: # {code_example|start} @@ -148,7 +148,7 @@ def get_user_presence(client: Client) -> None: result = client.get_user_presence("iago@zulip.com") # {code_example|end} - validate_against_openapi_schema(result, "/users/{email}/presence", "get", "200") + validate_against_openapi_schema(result, "/users/{user_id_or_email}/presence", "get", "200") @openapi_test_function("/users/me/presence:post") diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 09fc9094d4..9bb5875afd 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -4260,7 +4260,7 @@ paths: - $ref: "#/components/schemas/JsonSuccess" - example: {"msg": "", "result": "success"} - /users/{email}/presence: + /users/{user_id_or_email}/presence: get: operationId: get_user_presence tags: ["users"] @@ -4273,16 +4273,19 @@ paths: presence endpoint, which returns data for all active users in the organization, instead. - `GET {{ api_url }}/v1/users/{email}/presence` + `GET {{ api_url }}/v1/users/{user_id_or_email}/presence` See [Zulip's developer documentation](https://zulip.readthedocs.io/en/latest/subsystems/presence.html) for details on the data model for presence in Zulip. parameters: - - name: email + - name: user_id_or_email in: path description: | - The email address of the user whose presence you want to fetch. + The user_id or Zulip display email address of the user whose presence you want to fetch. + + **Changes**: New in Zulip 4.0 (feature level 43). Previous versions only supported + identifying the user by Zulip display email. schema: type: string example: iago@zulip.com diff --git a/zerver/tests/test_openapi.py b/zerver/tests/test_openapi.py index ecc8e73862..2ca4748506 100644 --- a/zerver/tests/test_openapi.py +++ b/zerver/tests/test_openapi.py @@ -1063,7 +1063,10 @@ class OpenAPIRegexTest(ZulipTestCase): find_openapi_endpoint("/users/23/subscriptions/21") == "/users/{user_id}/subscriptions/{stream_id}" ) - assert find_openapi_endpoint("/users/iago@zulip.com/presence") == "/users/{email}/presence" + assert ( + find_openapi_endpoint("/users/iago@zulip.com/presence") + == "/users/{user_id_or_email}/presence" + ) assert find_openapi_endpoint("/users/iago@zulip.com") == "/users/{email}" assert find_openapi_endpoint("/messages/23") == "/messages/{message_id}" assert find_openapi_endpoint("/realm/emoji/realm_emoji_1") == "/realm/emoji/{emoji_name}" diff --git a/zerver/tests/test_presence.py b/zerver/tests/test_presence.py index 4fc03da00b..466264d40b 100644 --- a/zerver/tests/test_presence.py +++ b/zerver/tests/test_presence.py @@ -464,10 +464,17 @@ class SingleUserPresenceTests(ZulipTestCase): result = self.client_get("/json/users/cordelia@zulip.com/presence") self.assert_json_error(result, "No presence data for cordelia@zulip.com") + cordelia = self.example_user("cordelia") + result = self.client_get(f"/json/users/{cordelia.id}/presence") + self.assert_json_error(result, f"No presence data for {cordelia.id}") + do_deactivate_user(self.example_user("cordelia")) result = self.client_get("/json/users/cordelia@zulip.com/presence") self.assert_json_error(result, "No such user") + result = self.client_get(f"/json/users/{cordelia.id}/presence") + self.assert_json_error(result, "No such user") + result = self.client_get("/json/users/default-bot@zulip.com/presence") self.assert_json_error(result, "Presence is not supported for bot users.") @@ -476,6 +483,10 @@ class SingleUserPresenceTests(ZulipTestCase): result = self.client_get("/json/users/othello@zulip.com/presence", subdomain="zephyr") self.assert_json_error(result, "No such user") + othello = self.example_user("othello") + result = self.client_get(f"/json/users/{othello.id}/presence", subdomain="zephyr") + self.assert_json_error(result, "No such user") + # Then, we check everything works self.login("hamlet") result = self.client_get("/json/users/othello@zulip.com/presence") @@ -485,6 +496,13 @@ class SingleUserPresenceTests(ZulipTestCase): ) self.assertEqual(set(result_dict["presence"]["website"].keys()), {"status", "timestamp"}) + result = self.client_get(f"/json/users/{othello.id}/presence") + result_dict = result.json() + self.assertEqual( + set(result_dict["presence"].keys()), {"ZulipAndroid", "website", "aggregated"} + ) + self.assertEqual(set(result_dict["presence"]["website"].keys()), {"status", "timestamp"}) + def test_ping_only(self) -> None: self.login("othello") diff --git a/zerver/views/presence.py b/zerver/views/presence.py index 23840dcfe0..bd373cb2a8 100644 --- a/zerver/views/presence.py +++ b/zerver/views/presence.py @@ -13,25 +13,40 @@ from zerver.lib.request import REQ, JsonableError, has_request_variables from zerver.lib.response import json_error, json_success from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.validator import check_bool, check_capped_string -from zerver.models import UserActivity, UserPresence, UserProfile, get_active_user +from zerver.models import ( + UserActivity, + UserPresence, + UserProfile, + get_active_user, + get_active_user_profile_by_id_in_realm, +) def get_presence_backend( - request: HttpRequest, user_profile: UserProfile, email: str + request: HttpRequest, user_profile: UserProfile, user_id_or_email: str ) -> HttpResponse: # This isn't used by the webapp; it's available for API use by # bots and other clients. We may want to add slim_presence # support for it (or just migrate its API wholesale) later. + try: - target = get_active_user(email, user_profile.realm) + try: + user_id = int(user_id_or_email) + target = get_active_user_profile_by_id_in_realm(user_id, user_profile.realm) + except ValueError: + email = user_id_or_email + target = get_active_user(email, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_("No such user")) + if target.is_bot: return json_error(_("Presence is not supported for bot users.")) presence_dict = get_presence_for_user(target.id) if len(presence_dict) == 0: - return json_error(_("No presence data for {email}").format(email=email)) + return json_error( + _("No presence data for {user_id_or_email}").format(user_id_or_email=user_id_or_email) + ) # For initial version, we just include the status and timestamp keys result = dict(presence=presence_dict[target.email]) diff --git a/zproject/urls.py b/zproject/urls.py index a586f6b4c8..64ac4b7bc5 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -371,7 +371,7 @@ v1_api_and_json_patterns = [ # It's important that this sit after users/me/presence so that # Django's URL resolution order doesn't break the # /users/me/presence endpoint. - rest_path("users//presence", GET=get_presence_backend), + rest_path("users//presence", GET=get_presence_backend), rest_path("realm/presence", GET=get_statuses_for_realm), rest_path("users/me/status", POST=update_user_status_backend), # user_groups -> zerver.views.user_groups