mirror of https://github.com/zulip/zulip.git
user_status: Add backend changes to support status emoji.
In this commit: * We update the `UserStatus` model to accept `AbstractReaction` as a base class so, we can get all the fields related to store status emoji. * We update the user status endpoint (`users/me/status`) to accept status emoji fields. * We update the user status event to add status emoji fields. Co-authored-by: Yash Rathore <33805964+YashRE42@users.noreply.github.com>
This commit is contained in:
parent
ed01ffadba
commit
9fadd43830
|
@ -11,6 +11,15 @@ below features are supported.
|
||||||
|
|
||||||
## Changes in Zulip 5.0
|
## Changes in Zulip 5.0
|
||||||
|
|
||||||
|
**Feature level 86**
|
||||||
|
|
||||||
|
* [`GET /events`](/api/get-events): Added `emoji_name`,
|
||||||
|
`emoji_code`, and `reaction_type` fields to `user_status` objects.
|
||||||
|
* [`POST /register`](/api/register-queue): Added `emoji_name`,
|
||||||
|
`emoji_code`, and `reaction_type` fields to `user_status` objects.
|
||||||
|
* `POST /users/me/status`: Added support for new `emoji_name`,
|
||||||
|
`emoji_code`, and `reaction_type` parameters.
|
||||||
|
|
||||||
**Feature level 85**
|
**Feature level 85**
|
||||||
|
|
||||||
* [`POST /register`](/api/register-queue), `PATCH /realm`: Replaced `add_emoji_by_admins_only`
|
* [`POST /register`](/api/register-queue), `PATCH /realm`: Replaced `add_emoji_by_admins_only`
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in templates/zerver/api/changelog.md, as well as
|
# new level means in templates/zerver/api/changelog.md, as well as
|
||||||
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 85
|
API_FEATURE_LEVEL = 86
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -5401,7 +5401,13 @@ def update_user_presence(
|
||||||
|
|
||||||
|
|
||||||
def do_update_user_status(
|
def do_update_user_status(
|
||||||
user_profile: UserProfile, away: Optional[bool], status_text: Optional[str], client_id: int
|
user_profile: UserProfile,
|
||||||
|
away: Optional[bool],
|
||||||
|
status_text: Optional[str],
|
||||||
|
client_id: int,
|
||||||
|
emoji_name: Optional[str],
|
||||||
|
emoji_code: Optional[str],
|
||||||
|
reaction_type: Optional[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
if away is None:
|
if away is None:
|
||||||
status = None
|
status = None
|
||||||
|
@ -5417,6 +5423,9 @@ def do_update_user_status(
|
||||||
status=status,
|
status=status,
|
||||||
status_text=status_text,
|
status_text=status_text,
|
||||||
client_id=client_id,
|
client_id=client_id,
|
||||||
|
emoji_name=emoji_name,
|
||||||
|
emoji_code=emoji_code,
|
||||||
|
reaction_type=reaction_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
event = dict(
|
event = dict(
|
||||||
|
@ -5430,6 +5439,10 @@ def do_update_user_status(
|
||||||
if status_text is not None:
|
if status_text is not None:
|
||||||
event["status_text"] = status_text
|
event["status_text"] = status_text
|
||||||
|
|
||||||
|
if emoji_name is not None:
|
||||||
|
event["emoji_name"] = emoji_name
|
||||||
|
event["emoji_code"] = emoji_code
|
||||||
|
event["reaction_type"] = reaction_type
|
||||||
send_event(realm, event, active_user_ids(realm.id))
|
send_event(realm, event, active_user_ids(realm.id))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1653,6 +1653,9 @@ user_status_event = event_dict_type(
|
||||||
# force vertical
|
# force vertical
|
||||||
("away", bool),
|
("away", bool),
|
||||||
("status_text", str),
|
("status_text", str),
|
||||||
|
("emoji_name", str),
|
||||||
|
("emoji_code", str),
|
||||||
|
("reaction_type", str),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
_check_user_status = make_checker(user_status_event)
|
_check_user_status = make_checker(user_status_event)
|
||||||
|
|
|
@ -59,6 +59,7 @@ from zerver.models import (
|
||||||
Stream,
|
Stream,
|
||||||
UserMessage,
|
UserMessage,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
|
UserStatus,
|
||||||
custom_profile_fields_for_realm,
|
custom_profile_fields_for_realm,
|
||||||
get_default_stream_groups,
|
get_default_stream_groups,
|
||||||
get_realm_domains,
|
get_realm_domains,
|
||||||
|
@ -1092,6 +1093,9 @@ def apply_event(
|
||||||
user_status = state["user_status"]
|
user_status = state["user_status"]
|
||||||
away = event.get("away")
|
away = event.get("away")
|
||||||
status_text = event.get("status_text")
|
status_text = event.get("status_text")
|
||||||
|
emoji_name = event.get("emoji_name")
|
||||||
|
emoji_code = event.get("emoji_code")
|
||||||
|
reaction_type = event.get("reaction_type")
|
||||||
|
|
||||||
if user_id_str not in user_status:
|
if user_id_str not in user_status:
|
||||||
user_status[user_id_str] = {}
|
user_status[user_id_str] = {}
|
||||||
|
@ -1108,6 +1112,24 @@ def apply_event(
|
||||||
else:
|
else:
|
||||||
user_status[user_id_str]["status_text"] = status_text
|
user_status[user_id_str]["status_text"] = status_text
|
||||||
|
|
||||||
|
if emoji_name is not None:
|
||||||
|
if emoji_name == "":
|
||||||
|
user_status[user_id_str].pop("emoji_name", None)
|
||||||
|
else:
|
||||||
|
user_status[user_id_str]["emoji_name"] = emoji_name
|
||||||
|
|
||||||
|
if emoji_code is not None:
|
||||||
|
if emoji_code == "":
|
||||||
|
user_status[user_id_str].pop("emoji_code", None)
|
||||||
|
else:
|
||||||
|
user_status[user_id_str]["emoji_code"] = emoji_code
|
||||||
|
|
||||||
|
if reaction_type is not None:
|
||||||
|
if reaction_type == UserStatus.UNICODE_EMOJI and emoji_name == "":
|
||||||
|
user_status[user_id_str].pop("reaction_type", None)
|
||||||
|
else:
|
||||||
|
user_status[user_id_str]["reaction_type"] = reaction_type
|
||||||
|
|
||||||
if not user_status[user_id_str]:
|
if not user_status[user_id_str]:
|
||||||
user_status.pop(user_id_str, None)
|
user_status.pop(user_id_str, None)
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,19 @@ def get_user_info_dict(realm_id: int) -> Dict[str, Dict[str, Any]]:
|
||||||
user_profile__is_active=True,
|
user_profile__is_active=True,
|
||||||
)
|
)
|
||||||
.exclude(
|
.exclude(
|
||||||
Q(status=UserStatus.NORMAL) & Q(status_text=""),
|
Q(status=UserStatus.NORMAL)
|
||||||
|
& Q(status_text="")
|
||||||
|
& Q(emoji_name="")
|
||||||
|
& Q(emoji_code="")
|
||||||
|
& Q(reaction_type=UserStatus.UNICODE_EMOJI),
|
||||||
)
|
)
|
||||||
.values(
|
.values(
|
||||||
"user_profile_id",
|
"user_profile_id",
|
||||||
"status",
|
"status",
|
||||||
"status_text",
|
"status_text",
|
||||||
|
"emoji_name",
|
||||||
|
"emoji_code",
|
||||||
|
"reaction_type",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,12 +34,19 @@ def get_user_info_dict(realm_id: int) -> Dict[str, Dict[str, Any]]:
|
||||||
away = row["status"] == UserStatus.AWAY
|
away = row["status"] == UserStatus.AWAY
|
||||||
status_text = row["status_text"]
|
status_text = row["status_text"]
|
||||||
user_id = row["user_profile_id"]
|
user_id = row["user_profile_id"]
|
||||||
|
emoji_name = row["emoji_name"]
|
||||||
|
emoji_code = row["emoji_code"]
|
||||||
|
reaction_type = row["reaction_type"]
|
||||||
|
|
||||||
dct = {}
|
dct = {}
|
||||||
if away:
|
if away:
|
||||||
dct["away"] = away
|
dct["away"] = away
|
||||||
if status_text:
|
if status_text:
|
||||||
dct["status_text"] = status_text
|
dct["status_text"] = status_text
|
||||||
|
if emoji_name:
|
||||||
|
dct["emoji_name"] = emoji_name
|
||||||
|
dct["emoji_code"] = emoji_code
|
||||||
|
dct["reaction_type"] = reaction_type
|
||||||
|
|
||||||
user_dict[str(user_id)] = dct
|
user_dict[str(user_id)] = dct
|
||||||
|
|
||||||
|
@ -40,7 +54,13 @@ def get_user_info_dict(realm_id: int) -> Dict[str, Dict[str, Any]]:
|
||||||
|
|
||||||
|
|
||||||
def update_user_status(
|
def update_user_status(
|
||||||
user_profile_id: int, status: Optional[int], status_text: Optional[str], client_id: int
|
user_profile_id: int,
|
||||||
|
status: Optional[int],
|
||||||
|
status_text: Optional[str],
|
||||||
|
client_id: int,
|
||||||
|
emoji_name: Optional[str],
|
||||||
|
emoji_code: Optional[str],
|
||||||
|
reaction_type: Optional[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
timestamp = timezone_now()
|
timestamp = timezone_now()
|
||||||
|
@ -56,6 +76,15 @@ def update_user_status(
|
||||||
if status_text is not None:
|
if status_text is not None:
|
||||||
defaults["status_text"] = status_text
|
defaults["status_text"] = status_text
|
||||||
|
|
||||||
|
if emoji_name is not None:
|
||||||
|
defaults["emoji_name"] = emoji_name
|
||||||
|
|
||||||
|
if emoji_code is not None:
|
||||||
|
defaults["emoji_code"] = emoji_code
|
||||||
|
|
||||||
|
if reaction_type is not None:
|
||||||
|
defaults["reaction_type"] = reaction_type
|
||||||
|
|
||||||
UserStatus.objects.update_or_create(
|
UserStatus.objects.update_or_create(
|
||||||
user_profile_id=user_profile_id,
|
user_profile_id=user_profile_id,
|
||||||
defaults=defaults,
|
defaults=defaults,
|
||||||
|
|
|
@ -1430,6 +1430,24 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
The text content of the status message.
|
The text content of the status message.
|
||||||
|
emoji_name:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
The [emoji name](/api/add-reaction#parameters) for the emoji associated with the new status.
|
||||||
|
|
||||||
|
**Changes**; New in Zulip 5.0 (feature level 86).
|
||||||
|
emoji_code:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
The [emoji code](/api/add-reaction#parameters) for the emoji associated with the new status.
|
||||||
|
|
||||||
|
**Changes**; New in Zulip 5.0 (feature level 86).
|
||||||
|
reaction_type:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
The [emoji type](/api/add-reaction#parameters) for the emoji associated with the new status.
|
||||||
|
|
||||||
|
**Changes**; New in Zulip 5.0 (feature level 86).
|
||||||
user_id:
|
user_id:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -1440,6 +1458,9 @@ paths:
|
||||||
"user_id": 10,
|
"user_id": 10,
|
||||||
"away": true,
|
"away": true,
|
||||||
"status_text": "out to lunch",
|
"status_text": "out to lunch",
|
||||||
|
"emoji_name": "car",
|
||||||
|
"emoji_code": "1f697",
|
||||||
|
"reaction_type": "unicode_emoji",
|
||||||
"id": 0,
|
"id": 0,
|
||||||
}
|
}
|
||||||
- type: object
|
- type: object
|
||||||
|
@ -8360,12 +8381,19 @@ paths:
|
||||||
error messages when a search returns limited results because
|
error messages when a search returns limited results because
|
||||||
a stop word in the query was ignored.
|
a stop word in the query was ignored.
|
||||||
user_status:
|
user_status:
|
||||||
type: object
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/EmojiBase"
|
||||||
description: |
|
description: |
|
||||||
Present if `user_status` is present in `fetch_event_types`.
|
Present if `user_status` is present in `fetch_event_types`.
|
||||||
|
|
||||||
A dictionary which contains the [status](/help/status-and-availability)
|
A dictionary which contains the [status](/help/status-and-availability)
|
||||||
of all users in the Zulip organization who have set a status.
|
of all users in the Zulip organization who have set a status.
|
||||||
|
|
||||||
|
**Changes**: The emoji parameters are new in Zulip 5.0 (feature level 86).
|
||||||
|
Previously, Zulip did not support emoji associated with statuses.
|
||||||
|
|
||||||
|
A status that does not have an emoji associated with it is encoded
|
||||||
|
with `emoji_name=""`.
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
description: |
|
description: |
|
||||||
`{user_id}`: Object containing the status details of a user
|
`{user_id}`: Object containing the status details of a user
|
||||||
|
@ -12241,7 +12269,7 @@ components:
|
||||||
reaction_type: {}
|
reaction_type: {}
|
||||||
user_id: {}
|
user_id: {}
|
||||||
user: {}
|
user: {}
|
||||||
EmojiReactionBase:
|
EmojiBase:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
emoji_code:
|
emoji_code:
|
||||||
|
@ -12267,6 +12295,10 @@ components:
|
||||||
(`emoji_code` will be its ID).
|
(`emoji_code` will be its ID).
|
||||||
* `zulip_extra_emoji`: Special emoji included with Zulip. Exists to
|
* `zulip_extra_emoji`: Special emoji included with Zulip. Exists to
|
||||||
namespace the `zulip` emoji.
|
namespace the `zulip` emoji.
|
||||||
|
EmojiReactionBase:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/EmojiBase"
|
||||||
|
- properties:
|
||||||
user_id:
|
user_id:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -12279,6 +12311,7 @@ components:
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
deprecated: true
|
deprecated: true
|
||||||
description: |
|
description: |
|
||||||
|
Whether the user is a mirror dummy.
|
||||||
Dictionary with data on the user who added the reaction, including
|
Dictionary with data on the user who added the reaction, including
|
||||||
the user ID as the `id` field. **Note**: In the [events
|
the user ID as the `id` field. **Note**: In the [events
|
||||||
API](/api/get-events), this `user` dictionary
|
API](/api/get-events), this `user` dictionary
|
||||||
|
|
|
@ -196,6 +196,7 @@ from zerver.models import (
|
||||||
UserMessage,
|
UserMessage,
|
||||||
UserPresence,
|
UserPresence,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
|
UserStatus,
|
||||||
get_client,
|
get_client,
|
||||||
get_stream,
|
get_stream,
|
||||||
get_user_by_delivery_email,
|
get_user_by_delivery_email,
|
||||||
|
@ -954,23 +955,45 @@ class NormalActionsTest(BaseAction):
|
||||||
user_profile=self.user_profile,
|
user_profile=self.user_profile,
|
||||||
away=True,
|
away=True,
|
||||||
status_text="out to lunch",
|
status_text="out to lunch",
|
||||||
|
emoji_name="car",
|
||||||
|
emoji_code="1f697",
|
||||||
|
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||||
client_id=client.id,
|
client_id=client.id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
check_user_status("events[0]", events[0], {"away", "status_text"})
|
check_user_status(
|
||||||
|
"events[0]",
|
||||||
|
events[0],
|
||||||
|
{"away", "status_text", "emoji_name", "emoji_code", "reaction_type"},
|
||||||
|
)
|
||||||
events = self.verify_action(
|
events = self.verify_action(
|
||||||
lambda: do_update_user_status(
|
lambda: do_update_user_status(
|
||||||
user_profile=self.user_profile, away=False, status_text="", client_id=client.id
|
user_profile=self.user_profile,
|
||||||
|
away=False,
|
||||||
|
status_text="",
|
||||||
|
emoji_name="",
|
||||||
|
emoji_code="",
|
||||||
|
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||||
|
client_id=client.id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
check_user_status("events[0]", events[0], {"away", "status_text"})
|
check_user_status(
|
||||||
|
"events[0]",
|
||||||
|
events[0],
|
||||||
|
{"away", "status_text", "emoji_name", "emoji_code", "reaction_type"},
|
||||||
|
)
|
||||||
|
|
||||||
events = self.verify_action(
|
events = self.verify_action(
|
||||||
lambda: do_update_user_status(
|
lambda: do_update_user_status(
|
||||||
user_profile=self.user_profile, away=True, status_text=None, client_id=client.id
|
user_profile=self.user_profile,
|
||||||
|
away=True,
|
||||||
|
status_text=None,
|
||||||
|
emoji_name=None,
|
||||||
|
emoji_code=None,
|
||||||
|
reaction_type=None,
|
||||||
|
client_id=client.id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -981,6 +1004,9 @@ class NormalActionsTest(BaseAction):
|
||||||
user_profile=self.user_profile,
|
user_profile=self.user_profile,
|
||||||
away=None,
|
away=None,
|
||||||
status_text="at the beach",
|
status_text="at the beach",
|
||||||
|
emoji_name=None,
|
||||||
|
emoji_code=None,
|
||||||
|
reaction_type=None,
|
||||||
client_id=client.id,
|
client_id=client.id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -36,6 +36,9 @@ class UserStatusTest(ZulipTestCase):
|
||||||
user_profile_id=hamlet.id,
|
user_profile_id=hamlet.id,
|
||||||
status=UserStatus.AWAY,
|
status=UserStatus.AWAY,
|
||||||
status_text=None,
|
status_text=None,
|
||||||
|
emoji_name=None,
|
||||||
|
emoji_code=None,
|
||||||
|
reaction_type=None,
|
||||||
client_id=client1.id,
|
client_id=client1.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,12 +55,21 @@ class UserStatusTest(ZulipTestCase):
|
||||||
user_profile_id=hamlet.id,
|
user_profile_id=hamlet.id,
|
||||||
status=UserStatus.AWAY,
|
status=UserStatus.AWAY,
|
||||||
status_text="out to lunch",
|
status_text="out to lunch",
|
||||||
|
emoji_name="car",
|
||||||
|
emoji_code="1f697",
|
||||||
|
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||||
client_id=client2.id,
|
client_id=client2.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
user_info(hamlet),
|
user_info(hamlet),
|
||||||
dict(away=True, status_text="out to lunch"),
|
dict(
|
||||||
|
away=True,
|
||||||
|
status_text="out to lunch",
|
||||||
|
emoji_name="car",
|
||||||
|
emoji_code="1f697",
|
||||||
|
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
away_user_ids = get_away_user_ids(realm_id=realm_id)
|
away_user_ids = get_away_user_ids(realm_id=realm_id)
|
||||||
|
@ -66,24 +78,35 @@ class UserStatusTest(ZulipTestCase):
|
||||||
rec_count = UserStatus.objects.filter(user_profile_id=hamlet.id).count()
|
rec_count = UserStatus.objects.filter(user_profile_id=hamlet.id).count()
|
||||||
self.assertEqual(rec_count, 1)
|
self.assertEqual(rec_count, 1)
|
||||||
|
|
||||||
# Setting status_text to None causes it be ignored.
|
# Setting status_text and emoji_info to None causes it be ignored.
|
||||||
update_user_status(
|
update_user_status(
|
||||||
user_profile_id=hamlet.id,
|
user_profile_id=hamlet.id,
|
||||||
status=UserStatus.NORMAL,
|
status=UserStatus.NORMAL,
|
||||||
status_text=None,
|
status_text=None,
|
||||||
|
emoji_name=None,
|
||||||
|
emoji_code=None,
|
||||||
|
reaction_type=None,
|
||||||
client_id=client2.id,
|
client_id=client2.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
user_info(hamlet),
|
user_info(hamlet),
|
||||||
dict(status_text="out to lunch"),
|
dict(
|
||||||
|
status_text="out to lunch",
|
||||||
|
emoji_name="car",
|
||||||
|
emoji_code="1f697",
|
||||||
|
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clear the status_text now.
|
# Clear the status_text and emoji_info now.
|
||||||
update_user_status(
|
update_user_status(
|
||||||
user_profile_id=hamlet.id,
|
user_profile_id=hamlet.id,
|
||||||
status=None,
|
status=None,
|
||||||
status_text="",
|
status_text="",
|
||||||
|
emoji_name="",
|
||||||
|
emoji_code="",
|
||||||
|
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||||
client_id=client2.id,
|
client_id=client2.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,18 +124,27 @@ class UserStatusTest(ZulipTestCase):
|
||||||
user_profile_id=hamlet.id,
|
user_profile_id=hamlet.id,
|
||||||
status=UserStatus.AWAY,
|
status=UserStatus.AWAY,
|
||||||
status_text=None,
|
status_text=None,
|
||||||
|
emoji_name=None,
|
||||||
|
emoji_code=None,
|
||||||
|
reaction_type=None,
|
||||||
client_id=client1.id,
|
client_id=client1.id,
|
||||||
)
|
)
|
||||||
update_user_status(
|
update_user_status(
|
||||||
user_profile_id=cordelia.id,
|
user_profile_id=cordelia.id,
|
||||||
status=UserStatus.AWAY,
|
status=UserStatus.AWAY,
|
||||||
status_text=None,
|
status_text=None,
|
||||||
|
emoji_name=None,
|
||||||
|
emoji_code=None,
|
||||||
|
reaction_type=None,
|
||||||
client_id=client2.id,
|
client_id=client2.id,
|
||||||
)
|
)
|
||||||
update_user_status(
|
update_user_status(
|
||||||
user_profile_id=king_lear.id,
|
user_profile_id=king_lear.id,
|
||||||
status=UserStatus.AWAY,
|
status=UserStatus.AWAY,
|
||||||
status_text=None,
|
status_text=None,
|
||||||
|
emoji_name=None,
|
||||||
|
emoji_code=None,
|
||||||
|
reaction_type=None,
|
||||||
client_id=client2.id,
|
client_id=client2.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -127,6 +159,9 @@ class UserStatusTest(ZulipTestCase):
|
||||||
user_profile_id=hamlet.id,
|
user_profile_id=hamlet.id,
|
||||||
status=UserStatus.NORMAL,
|
status=UserStatus.NORMAL,
|
||||||
status_text="in a meeting",
|
status_text="in a meeting",
|
||||||
|
emoji_name=None,
|
||||||
|
emoji_code=None,
|
||||||
|
reaction_type=None,
|
||||||
client_id=client2.id,
|
client_id=client2.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -158,6 +193,31 @@ class UserStatusTest(ZulipTestCase):
|
||||||
result = self.client_post("/json/users/me/status", payload)
|
result = self.client_post("/json/users/me/status", payload)
|
||||||
self.assert_json_error(result, "Client did not pass any new values.")
|
self.assert_json_error(result, "Client did not pass any new values.")
|
||||||
|
|
||||||
|
# Try to omit emoji_name parameter but passing emoji_code --this should be an error.
|
||||||
|
payload = {"status_text": "In a meeting", "emoji_code": "1f4bb"}
|
||||||
|
result = self.client_post("/json/users/me/status", payload)
|
||||||
|
self.assert_json_error(
|
||||||
|
result, "Client must pass emoji_name if they pass either emoji_code or reaction_type."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Invalid emoji requests fail
|
||||||
|
payload = {"status_text": "In a meeting", "emoji_code": "1f4bb", "emoji_name": "invalid"}
|
||||||
|
result = self.client_post("/json/users/me/status", payload)
|
||||||
|
self.assert_json_error(result, "Emoji 'invalid' does not exist")
|
||||||
|
|
||||||
|
payload = {"status_text": "In a meeting", "emoji_code": "1f4bb", "emoji_name": "car"}
|
||||||
|
result = self.client_post("/json/users/me/status", payload)
|
||||||
|
self.assert_json_error(result, "Invalid emoji name.")
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"status_text": "In a meeting",
|
||||||
|
"emoji_code": "1f4bb",
|
||||||
|
"emoji_name": "car",
|
||||||
|
"reaction_type": "realm_emoji",
|
||||||
|
}
|
||||||
|
result = self.client_post("/json/users/me/status", payload)
|
||||||
|
self.assert_json_error(result, "Invalid custom emoji.")
|
||||||
|
|
||||||
# Try a long message.
|
# Try a long message.
|
||||||
long_text = "x" * 61
|
long_text = "x" * 61
|
||||||
payload = dict(status_text=long_text)
|
payload = dict(status_text=long_text)
|
||||||
|
@ -178,6 +238,52 @@ class UserStatusTest(ZulipTestCase):
|
||||||
dict(away=True, status_text="on vacation"),
|
dict(away=True, status_text="on vacation"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Server should fill emoji_code and reaction_type by emoji_name.
|
||||||
|
self.update_status_and_assert_event(
|
||||||
|
payload=dict(
|
||||||
|
away=orjson.dumps(True).decode(),
|
||||||
|
emoji_name="car",
|
||||||
|
),
|
||||||
|
expected_event=dict(
|
||||||
|
type="user_status",
|
||||||
|
user_id=hamlet.id,
|
||||||
|
away=True,
|
||||||
|
emoji_name="car",
|
||||||
|
emoji_code="1f697",
|
||||||
|
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
user_info(hamlet),
|
||||||
|
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(
|
||||||
|
away=orjson.dumps(True).decode(),
|
||||||
|
emoji_name="",
|
||||||
|
),
|
||||||
|
expected_event=dict(
|
||||||
|
type="user_status",
|
||||||
|
user_id=hamlet.id,
|
||||||
|
away=True,
|
||||||
|
emoji_name="",
|
||||||
|
emoji_code="",
|
||||||
|
reaction_type=UserStatus.UNICODE_EMOJI,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
user_info(hamlet),
|
||||||
|
dict(away=True, status_text="on vacation"),
|
||||||
|
)
|
||||||
|
|
||||||
# Now revoke "away" status.
|
# Now revoke "away" status.
|
||||||
self.update_status_and_assert_event(
|
self.update_status_and_assert_event(
|
||||||
payload=dict(away=orjson.dumps(False).decode()),
|
payload=dict(away=orjson.dumps(False).decode()),
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from zerver.decorator import human_users_only
|
from zerver.decorator import human_users_only
|
||||||
from zerver.lib.actions import do_update_user_status, update_user_presence
|
from zerver.lib.actions import do_update_user_status, update_user_presence
|
||||||
|
from zerver.lib.emoji import check_emoji_request, emoji_name_to_emoji_code
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError
|
||||||
from zerver.lib.presence import get_presence_for_user, get_presence_response
|
from zerver.lib.presence import get_presence_for_user, get_presence_response
|
||||||
from zerver.lib.request import REQ, get_request_notes, has_request_variables
|
from zerver.lib.request import REQ, get_request_notes, has_request_variables
|
||||||
|
@ -18,6 +19,7 @@ from zerver.models import (
|
||||||
UserActivity,
|
UserActivity,
|
||||||
UserPresence,
|
UserPresence,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
|
UserStatus,
|
||||||
get_active_user,
|
get_active_user,
|
||||||
get_active_user_profile_by_id_in_realm,
|
get_active_user_profile_by_id_in_realm,
|
||||||
)
|
)
|
||||||
|
@ -68,14 +70,50 @@ def update_user_status_backend(
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
away: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
away: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
status_text: Optional[str] = REQ(str_validator=check_capped_string(60), default=None),
|
status_text: Optional[str] = REQ(str_validator=check_capped_string(60), default=None),
|
||||||
|
emoji_name: Optional[str] = REQ(default=None),
|
||||||
|
emoji_code: Optional[str] = REQ(default=None),
|
||||||
|
# TODO: emoji_type is the more appropriate name for this parameter, but changing
|
||||||
|
# that requires nontrivial work on the API documentation, since it's not clear
|
||||||
|
# that the reactions endpoint would prefer such a change.
|
||||||
|
emoji_type: Optional[str] = REQ("reaction_type", default=None),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
|
||||||
if status_text is not None:
|
if status_text is not None:
|
||||||
status_text = status_text.strip()
|
status_text = status_text.strip()
|
||||||
|
|
||||||
if (away is None) and (status_text is None):
|
if (away is None) and (status_text is None) and (emoji_name is None):
|
||||||
raise JsonableError(_("Client did not pass any new values."))
|
raise JsonableError(_("Client did not pass any new values."))
|
||||||
|
|
||||||
|
if emoji_name == "":
|
||||||
|
# Reset the emoji_code and reaction_type if emoji_name is empty.
|
||||||
|
# This should clear the user's configured emoji.
|
||||||
|
emoji_code = ""
|
||||||
|
emoji_type = UserStatus.UNICODE_EMOJI
|
||||||
|
|
||||||
|
elif emoji_name is not None:
|
||||||
|
if emoji_code is None:
|
||||||
|
# The emoji_code argument is only required for rare corner
|
||||||
|
# cases discussed in the long block comment below. For simple
|
||||||
|
# API clients, we allow specifying just the name, and just
|
||||||
|
# look up the code using the current name->code mapping.
|
||||||
|
emoji_code = emoji_name_to_emoji_code(user_profile.realm, emoji_name)[0]
|
||||||
|
|
||||||
|
if emoji_type is None:
|
||||||
|
emoji_type = emoji_name_to_emoji_code(user_profile.realm, emoji_name)[1]
|
||||||
|
|
||||||
|
elif emoji_type or emoji_code:
|
||||||
|
raise JsonableError(
|
||||||
|
_("Client must pass emoji_name if they pass either emoji_code or reaction_type.")
|
||||||
|
)
|
||||||
|
|
||||||
|
# If we're asking to set an emoji (not clear it ("") or not adjust
|
||||||
|
# it (None)), we need to verify the emoji is valid.
|
||||||
|
if emoji_name not in ["", None]:
|
||||||
|
assert emoji_name is not None
|
||||||
|
assert emoji_code is not None
|
||||||
|
assert emoji_type is not None
|
||||||
|
check_emoji_request(user_profile.realm, emoji_name, emoji_code, emoji_type)
|
||||||
|
|
||||||
client = get_request_notes(request).client
|
client = get_request_notes(request).client
|
||||||
assert client is not None
|
assert client is not None
|
||||||
do_update_user_status(
|
do_update_user_status(
|
||||||
|
@ -83,6 +121,9 @@ def update_user_status_backend(
|
||||||
away=away,
|
away=away,
|
||||||
status_text=status_text,
|
status_text=status_text,
|
||||||
client_id=client.id,
|
client_id=client.id,
|
||||||
|
emoji_name=emoji_name,
|
||||||
|
emoji_code=emoji_code,
|
||||||
|
reaction_type=emoji_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
return json_success()
|
return json_success()
|
||||||
|
|
Loading…
Reference in New Issue