mirror of https://github.com/zulip/zulip.git
api: Add "users/<int:user_id>/status" endpoint.
The documentation Creates a shared UserStatus schema that's used for the return value of this new endpoint and for the existing user_status objects returned by the register queue endpoint. Co-authored-by: Suyash Vardhan Mathur <suyash.mathur@research.iiit.ac.in> Fixes #19079.
This commit is contained in:
parent
d6672c57ff
commit
62dfd93a83
|
@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 9.0
|
||||
|
||||
**Feature level 262**:
|
||||
|
||||
* [`GET /users/{user_id}/status`](/api/get-user-status): Added a new
|
||||
endpoint to fetch an individual user's currently set
|
||||
[status](/help/status-and-availability).
|
||||
|
||||
**Feature level 261**
|
||||
|
||||
* [`POST /invites`](/api/send-invites),
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
* [Deactivate own user](/api/deactivate-own-user)
|
||||
* [Set "typing" status](/api/set-typing-status)
|
||||
* [Get user presence](/api/get-user-presence)
|
||||
* [Get a user's status](/api/get-user-status)
|
||||
* [Get presence of all users](/api/get-presence)
|
||||
* [Get attachments](/api/get-attachments)
|
||||
* [Delete an attachment](/api/remove-attachment)
|
||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
|||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
API_FEATURE_LEVEL = 261
|
||||
API_FEATURE_LEVEL = 262
|
||||
|
||||
# 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
|
||||
|
|
|
@ -113,3 +113,22 @@ def update_user_status(
|
|||
user_profile_id=user_profile_id,
|
||||
defaults=defaults,
|
||||
)
|
||||
|
||||
|
||||
def get_user_status(user_profile: UserProfile) -> UserInfoDict:
|
||||
status_set_by_user = (
|
||||
UserStatus.objects.filter(user_profile=user_profile)
|
||||
.values(
|
||||
"user_profile_id",
|
||||
"user_profile__presence_enabled",
|
||||
"status_text",
|
||||
"emoji_name",
|
||||
"emoji_code",
|
||||
"reaction_type",
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not status_set_by_user:
|
||||
return {}
|
||||
return format_user_status(status_set_by_user)
|
||||
|
|
|
@ -195,6 +195,20 @@ def get_user_presence(client: Client) -> None:
|
|||
validate_against_openapi_schema(result, "/users/{user_id_or_email}/presence", "get", "200")
|
||||
|
||||
|
||||
@openapi_test_function("/users/{user_id}/status:get")
|
||||
def get_user_status(client: Client) -> None:
|
||||
# {code_example|start}
|
||||
# Get the status currently set by a user.
|
||||
user_id = 11
|
||||
result = client.call_endpoint(
|
||||
url=f"/users/{user_id}/status",
|
||||
method="GET",
|
||||
)
|
||||
# {code_example|end}
|
||||
|
||||
validate_against_openapi_schema(result, "/users/{user_id}/status", "get", "200")
|
||||
|
||||
|
||||
@openapi_test_function("/users/me/presence:post")
|
||||
def update_presence(client: Client) -> None:
|
||||
request = {
|
||||
|
@ -1701,6 +1715,7 @@ def test_users(client: Client, owner_client: Client) -> None:
|
|||
reactivate_user(client)
|
||||
update_user(client)
|
||||
update_status(client)
|
||||
get_user_status(client)
|
||||
get_user_by_email(client)
|
||||
get_subscription_status(client)
|
||||
get_profile(client)
|
||||
|
|
|
@ -8416,6 +8416,68 @@ paths:
|
|||
"200":
|
||||
$ref: "#/components/responses/SimpleSuccess"
|
||||
|
||||
/users/{user_id}/status:
|
||||
get:
|
||||
operationId: get-user-status
|
||||
summary: Get a user's status
|
||||
tags: ["users"]
|
||||
description: |
|
||||
Get the [status](/help/status-and-availability) currently set by a
|
||||
user in the organization.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 262). Previously,
|
||||
user statuses could only be fetched via the [`POST
|
||||
/register`](/api/register-queue) endpoint.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/UserId"
|
||||
responses:
|
||||
"200":
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/JsonSuccessBase"
|
||||
- additionalProperties: false
|
||||
properties:
|
||||
result: {}
|
||||
msg: {}
|
||||
ignored_parameters_unsupported: {}
|
||||
status:
|
||||
allOf:
|
||||
- description: |
|
||||
The status set by the user. Note that, if the user doesn't have a status
|
||||
currently set, then the returned dictionary will be empty as none of the
|
||||
keys listed below will be present.
|
||||
- $ref: "#/components/schemas/UserStatus"
|
||||
|
||||
example:
|
||||
{
|
||||
"result": "success",
|
||||
"msg": "",
|
||||
"status":
|
||||
{
|
||||
"status_text": "on vacation",
|
||||
"emoji_name": "car",
|
||||
"emoji_code": "1f697",
|
||||
"reaction_type": "unicode_emoji",
|
||||
},
|
||||
}
|
||||
"400":
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/CodedError"
|
||||
- example:
|
||||
{
|
||||
"result": "error",
|
||||
"msg": "No such user",
|
||||
"code": "BAD_REQUEST",
|
||||
}
|
||||
description: |
|
||||
An example JSON error response when the user does not exist:
|
||||
/users/{user_id_or_email}/presence:
|
||||
get:
|
||||
operationId: get-user-presence
|
||||
|
@ -13652,63 +13714,11 @@ paths:
|
|||
**Changes**: The emoji parameters are new in Zulip 5.0 (feature level 86).
|
||||
Previously, Zulip did not support emoji associated with statuses.
|
||||
additionalProperties:
|
||||
description: |
|
||||
`{user_id}`: Object containing the status details of a user
|
||||
with the key of the object being the ID of the user.
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
away:
|
||||
type: boolean
|
||||
deprecated: true
|
||||
description: |
|
||||
If present, the user has marked themself "away".
|
||||
|
||||
**Changes**: Deprecated in Zulip 6.0 (feature level 148);
|
||||
starting with that feature level, `away` is a legacy way to
|
||||
access the user's `presence_enabled` setting, with
|
||||
`away = !presence_enabled`. To be removed in a future release.
|
||||
status_text:
|
||||
type: string
|
||||
description: |
|
||||
If present, the text content of the user's status message.
|
||||
emoji_name:
|
||||
type: string
|
||||
description: |
|
||||
If present, the name for the emoji to associate with the user's status.
|
||||
|
||||
**Changes**: New in Zulip 5.0 (feature level 86).
|
||||
emoji_code:
|
||||
type: string
|
||||
description: |
|
||||
If present, a unique identifier, defining the specific emoji codepoint
|
||||
requested, within the namespace of the `reaction_type`.
|
||||
|
||||
**Changes**: New in Zulip 5.0 (feature level 86).
|
||||
reaction_type:
|
||||
type: string
|
||||
enum:
|
||||
- unicode_emoji
|
||||
- realm_emoji
|
||||
- zulip_extra_emoji
|
||||
description: |
|
||||
If present, a string indicating the type of emoji. Each emoji
|
||||
`reaction_type` has an independent namespace for values of `emoji_code`.
|
||||
|
||||
Must be one of the following values:
|
||||
|
||||
- `unicode_emoji` : In this namespace, `emoji_code` will be a
|
||||
dash-separated hex encoding of the sequence of Unicode codepoints
|
||||
that define this emoji in the Unicode specification.
|
||||
|
||||
- `realm_emoji` : In this namespace, `emoji_code` will be the ID of
|
||||
the uploaded [custom emoji](/help/custom-emoji).
|
||||
|
||||
- `zulip_extra_emoji` : These are special emoji included with Zulip.
|
||||
In this namespace, `emoji_code` will be the name of the emoji (e.g.
|
||||
"zulip").
|
||||
|
||||
**Changes**: New in Zulip 5.0 (feature level 86).
|
||||
allOf:
|
||||
- description: |
|
||||
`{user_id}`: Object containing the status details of a user
|
||||
with the key of the object being the ID of the user.
|
||||
- $ref: "#/components/schemas/UserStatus"
|
||||
user_settings:
|
||||
type: object
|
||||
description: |
|
||||
|
@ -21100,6 +21110,61 @@ components:
|
|||
**Changes**: Starting with Zulip 7.0 (feature level 178), always
|
||||
`false` when present as the server no longer stores which client
|
||||
submitted presence data.
|
||||
UserStatus:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
away:
|
||||
type: boolean
|
||||
deprecated: true
|
||||
description: |
|
||||
If present, the user has marked themself "away".
|
||||
|
||||
**Changes**: Deprecated in Zulip 6.0 (feature level 148);
|
||||
starting with that feature level, `away` is a legacy way to
|
||||
access the user's `presence_enabled` setting, with
|
||||
`away = !presence_enabled`. To be removed in a future release.
|
||||
status_text:
|
||||
type: string
|
||||
description: |
|
||||
If present, the text content of the user's status message.
|
||||
emoji_name:
|
||||
type: string
|
||||
description: |
|
||||
If present, the name for the emoji to associate with the user's status.
|
||||
|
||||
**Changes**: New in Zulip 5.0 (feature level 86).
|
||||
emoji_code:
|
||||
type: string
|
||||
description: |
|
||||
If present, a unique identifier, defining the specific emoji codepoint
|
||||
requested, within the namespace of the `reaction_type`.
|
||||
|
||||
**Changes**: New in Zulip 5.0 (feature level 86).
|
||||
reaction_type:
|
||||
type: string
|
||||
enum:
|
||||
- unicode_emoji
|
||||
- realm_emoji
|
||||
- zulip_extra_emoji
|
||||
description: |
|
||||
If present, a string indicating the type of emoji. Each emoji
|
||||
`reaction_type` has an independent namespace for values of `emoji_code`.
|
||||
|
||||
Must be one of the following values:
|
||||
|
||||
- `unicode_emoji` : In this namespace, `emoji_code` will be a
|
||||
dash-separated hex encoding of the sequence of Unicode codepoints
|
||||
that define this emoji in the Unicode specification.
|
||||
|
||||
- `realm_emoji` : In this namespace, `emoji_code` will be the ID of
|
||||
the uploaded [custom emoji](/help/custom-emoji).
|
||||
|
||||
- `zulip_extra_emoji` : These are special emoji included with Zulip.
|
||||
In this namespace, `emoji_code` will be the name of the emoji (e.g.
|
||||
"zulip").
|
||||
|
||||
**Changes**: New in Zulip 5.0 (feature level 86).
|
||||
Draft:
|
||||
type: object
|
||||
description: |
|
||||
|
|
|
@ -3,7 +3,12 @@ from typing import Any, Dict, Optional
|
|||
import orjson
|
||||
|
||||
from zerver.lib.test_classes import ZulipTestCase
|
||||
from zerver.lib.user_status import UserInfoDict, get_all_users_status_dict, update_user_status
|
||||
from zerver.lib.user_status import (
|
||||
UserInfoDict,
|
||||
get_all_users_status_dict,
|
||||
get_user_status,
|
||||
update_user_status,
|
||||
)
|
||||
from zerver.models import UserProfile, UserStatus
|
||||
from zerver.models.clients import get_client
|
||||
|
||||
|
@ -65,6 +70,17 @@ class UserStatusTest(ZulipTestCase):
|
|||
),
|
||||
)
|
||||
|
||||
fetched_status = get_user_status(hamlet)
|
||||
self.assertEqual(
|
||||
fetched_status,
|
||||
dict(
|
||||
status_text="out to lunch",
|
||||
emoji_name="car",
|
||||
emoji_code="1f697",
|
||||
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||
),
|
||||
)
|
||||
|
||||
rec_count = UserStatus.objects.filter(user_profile_id=hamlet.id).count()
|
||||
self.assertEqual(rec_count, 1)
|
||||
|
||||
|
@ -88,6 +104,17 @@ class UserStatusTest(ZulipTestCase):
|
|||
),
|
||||
)
|
||||
|
||||
fetched_status = get_user_status(hamlet)
|
||||
self.assertEqual(
|
||||
fetched_status,
|
||||
dict(
|
||||
status_text="out to lunch",
|
||||
emoji_name="car",
|
||||
emoji_code="1f697",
|
||||
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||
),
|
||||
)
|
||||
|
||||
# Clear the status_text and emoji_info now.
|
||||
update_user_status(
|
||||
user_profile_id=hamlet.id,
|
||||
|
@ -103,6 +130,12 @@ class UserStatusTest(ZulipTestCase):
|
|||
{},
|
||||
)
|
||||
|
||||
fetched_status = get_user_status(hamlet)
|
||||
self.assertEqual(
|
||||
fetched_status,
|
||||
{},
|
||||
)
|
||||
|
||||
# Set Hamlet to in a meeting.
|
||||
update_user_status(
|
||||
user_profile_id=hamlet.id,
|
||||
|
@ -118,6 +151,12 @@ class UserStatusTest(ZulipTestCase):
|
|||
dict(status_text="in a meeting"),
|
||||
)
|
||||
|
||||
fetched_status = get_user_status(hamlet)
|
||||
self.assertEqual(
|
||||
fetched_status,
|
||||
dict(status_text="in a meeting"),
|
||||
)
|
||||
|
||||
# Test user status for inaccessible users.
|
||||
self.set_up_db_for_testing_user_access()
|
||||
cordelia = self.example_user("cordelia")
|
||||
|
@ -204,6 +243,13 @@ class UserStatusTest(ZulipTestCase):
|
|||
dict(away=True, status_text="on vacation"),
|
||||
)
|
||||
|
||||
result = self.client_get(f"/json/users/{hamlet.id}/status")
|
||||
result_dict = self.assert_json_success(result)
|
||||
self.assertEqual(
|
||||
result_dict["status"],
|
||||
dict(away=True, status_text="on vacation"),
|
||||
)
|
||||
|
||||
# Setting away is a deprecated way of accessing a user's presence_enabled
|
||||
# setting. Can be removed when clients migrate "away" (also referred to as
|
||||
# "unavailable") feature to directly use the presence_enabled setting.
|
||||
|
@ -234,6 +280,19 @@ class UserStatusTest(ZulipTestCase):
|
|||
),
|
||||
)
|
||||
|
||||
result = self.client_get(f"/json/users/{hamlet.id}/status")
|
||||
result_dict = self.assert_json_success(result)
|
||||
self.assertEqual(
|
||||
result_dict["status"],
|
||||
dict(
|
||||
away=True,
|
||||
status_text="on vacation",
|
||||
emoji_name="car",
|
||||
emoji_code="1f697",
|
||||
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||
),
|
||||
)
|
||||
|
||||
# Server should remove emoji_code and reaction_type if emoji_name is empty.
|
||||
self.update_status_and_assert_event(
|
||||
payload=dict(
|
||||
|
@ -252,6 +311,13 @@ class UserStatusTest(ZulipTestCase):
|
|||
dict(away=True, status_text="on vacation"),
|
||||
)
|
||||
|
||||
result = self.client_get(f"/json/users/{hamlet.id}/status")
|
||||
result_dict = self.assert_json_success(result)
|
||||
self.assertEqual(
|
||||
result_dict["status"],
|
||||
dict(away=True, status_text="on vacation"),
|
||||
)
|
||||
|
||||
# Now revoke "away" status.
|
||||
self.update_status_and_assert_event(
|
||||
payload=dict(away=orjson.dumps(False).decode()),
|
||||
|
@ -280,6 +346,13 @@ class UserStatusTest(ZulipTestCase):
|
|||
dict(status_text="in office"),
|
||||
)
|
||||
|
||||
result = self.client_get(f"/json/users/{hamlet.id}/status")
|
||||
result_dict = self.assert_json_success(result)
|
||||
self.assertEqual(
|
||||
result_dict["status"],
|
||||
dict(status_text="in office"),
|
||||
)
|
||||
|
||||
# And finally clear your info.
|
||||
self.update_status_and_assert_event(
|
||||
payload=dict(status_text=""),
|
||||
|
@ -290,6 +363,13 @@ class UserStatusTest(ZulipTestCase):
|
|||
{},
|
||||
)
|
||||
|
||||
result = self.client_get(f"/json/users/{hamlet.id}/status")
|
||||
result_dict = self.assert_json_success(result)
|
||||
self.assertEqual(
|
||||
result_dict["status"],
|
||||
{},
|
||||
)
|
||||
|
||||
# Turn on "away" status again.
|
||||
self.update_status_and_assert_event(
|
||||
payload=dict(away=orjson.dumps(True).decode()),
|
||||
|
@ -312,3 +392,23 @@ class UserStatusTest(ZulipTestCase):
|
|||
user_status_info(hamlet),
|
||||
dict(status_text="at the beach", away=True),
|
||||
)
|
||||
|
||||
result = self.client_get(f"/json/users/{hamlet.id}/status")
|
||||
result_dict = self.assert_json_success(result)
|
||||
self.assertEqual(
|
||||
result_dict["status"],
|
||||
dict(status_text="at the beach", away=True),
|
||||
)
|
||||
|
||||
# Invalid user ID should fail
|
||||
result = self.client_get("/json/users/12345/status")
|
||||
self.assert_json_error(result, "No such user")
|
||||
|
||||
# Test status if the status has not been set
|
||||
iago = self.example_user("iago")
|
||||
result = self.client_get(f"/json/users/{iago.id}/status")
|
||||
result_dict = self.assert_json_success(result)
|
||||
self.assertEqual(
|
||||
result_dict["status"],
|
||||
{},
|
||||
)
|
||||
|
|
|
@ -18,7 +18,8 @@ from zerver.lib.request import RequestNotes
|
|||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import datetime_to_timestamp
|
||||
from zerver.lib.typed_endpoint import ApiParamConfig, typed_endpoint
|
||||
from zerver.lib.users import check_can_access_user
|
||||
from zerver.lib.user_status import get_user_status
|
||||
from zerver.lib.users import access_user_by_id, check_can_access_user
|
||||
from zerver.models import UserActivity, UserPresence, UserProfile, UserStatus
|
||||
from zerver.models.users import get_active_user, get_active_user_profile_by_id_in_realm
|
||||
|
||||
|
@ -66,6 +67,13 @@ def get_presence_backend(
|
|||
return json_success(request, data=result)
|
||||
|
||||
|
||||
def get_status_backend(
|
||||
request: HttpRequest, user_profile: UserProfile, user_id: int
|
||||
) -> HttpResponse:
|
||||
target_user = access_user_by_id(user_profile, user_id, for_admin=False)
|
||||
return json_success(request, data={"status": get_user_status(target_user)})
|
||||
|
||||
|
||||
@human_users_only
|
||||
@typed_endpoint
|
||||
def update_user_status_backend(
|
||||
|
|
|
@ -89,6 +89,7 @@ from zerver.views.muted_users import mute_user, unmute_user
|
|||
from zerver.views.onboarding_steps import mark_onboarding_step_as_read
|
||||
from zerver.views.presence import (
|
||||
get_presence_backend,
|
||||
get_status_backend,
|
||||
get_statuses_for_realm,
|
||||
update_active_status_backend,
|
||||
update_user_status_backend,
|
||||
|
@ -396,6 +397,7 @@ v1_api_and_json_patterns = [
|
|||
rest_path("users/<user_id_or_email>/presence", GET=get_presence_backend),
|
||||
rest_path("realm/presence", GET=get_statuses_for_realm),
|
||||
rest_path("users/me/status", POST=update_user_status_backend),
|
||||
rest_path("users/<int:user_id>/status", GET=get_status_backend),
|
||||
# user_groups -> zerver.views.user_groups
|
||||
rest_path("user_groups", GET=get_user_group),
|
||||
rest_path("user_groups/create", POST=add_user_group),
|
||||
|
|
Loading…
Reference in New Issue