mypy: Add EmojiInfo type.

We now serialize still_url as None for non-animated emojis,
instead of omitting the field. The webapp does proper checks
for falsiness here.  The mobile app does not yet use the field
(to my knowledge).

We bump the API version here. More discussion here:

https://chat.zulip.org/#narrow/stream/378-api-design/topic/still_url/near/1302573
This commit is contained in:
Steve Howell 2021-12-29 15:16:15 +00:00 committed by Tim Abbott
parent a6201b430f
commit c04a8097f3
7 changed files with 34 additions and 15 deletions

View File

@ -84,6 +84,7 @@ exports.test_realm_emojis = {
author_id: 222, author_id: 222,
deactivated: false, deactivated: false,
source_url: "/some/path/to/emoji", source_url: "/some/path/to/emoji",
still_url: null,
}, },
}; };

View File

@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 5.0 ## Changes in Zulip 5.0
**Feature level 113**
* `GET /realm/emoji`, `POST /realm/emoji/{emoji_name}`, [`GET
/events`](/api/get-events), [`POST /register`](/api/register-queue):
The `still_url` field for custom emoji objects is now always
present, with a value of null for non-animated emoji. Previously, it
only was present for animated emoji.
**Feature level 112** **Feature level 112**
* [`GET /events`](/api/get-events): Updated `update_message` event type * [`GET /events`](/api/get-events): Updated `update_message` event type

View File

@ -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 = 112 API_FEATURE_LEVEL = 113
# 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

@ -712,9 +712,7 @@ realm_emoji_type = DictType(
("source_url", str), ("source_url", str),
("deactivated", bool), ("deactivated", bool),
("author_id", int), ("author_id", int),
], ("still_url", OptionalType(str)),
optional_keys=[
("still_url", str),
], ],
) )

View File

@ -1503,7 +1503,7 @@ class Emoji(markdown.inlinepatterns.Pattern):
orig_syntax = match.group("syntax") orig_syntax = match.group("syntax")
name = orig_syntax[1:-1] name = orig_syntax[1:-1]
active_realm_emoji: Dict[str, Dict[str, str]] = {} active_realm_emoji: Dict[str, EmojiInfo] = {}
db_data = self.md.zulip_db_data db_data = self.md.zulip_db_data
if db_data is not None: if db_data is not None:
active_realm_emoji = db_data["active_realm_emoji"] active_realm_emoji = db_data["active_realm_emoji"]

View File

@ -37,6 +37,7 @@ from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy from django.utils.translation import gettext_lazy
from django_cte import CTEManager from django_cte import CTEManager
from typing_extensions import TypedDict
from confirmation import settings as confirmation_settings from confirmation import settings as confirmation_settings
from zerver.lib import cache from zerver.lib import cache
@ -100,6 +101,15 @@ MAX_LANGUAGE_ID_LENGTH: int = 50
STREAM_NAMES = TypeVar("STREAM_NAMES", Sequence[str], AbstractSet[str]) STREAM_NAMES = TypeVar("STREAM_NAMES", Sequence[str], AbstractSet[str])
class EmojiInfo(TypedDict):
id: str
name: str
source_url: str
deactivated: bool
author_id: Optional[int]
still_url: Optional[str]
def query_for_ids(query: QuerySet, user_ids: List[int], field: str) -> QuerySet: def query_for_ids(query: QuerySet, user_ids: List[int], field: str) -> QuerySet:
""" """
This function optimizes searches of the form This function optimizes searches of the form
@ -726,11 +736,11 @@ class Realm(models.Model):
return f"<Realm: {self.string_id} {self.id}>" return f"<Realm: {self.string_id} {self.id}>"
@cache_with_key(get_realm_emoji_cache_key, timeout=3600 * 24 * 7) @cache_with_key(get_realm_emoji_cache_key, timeout=3600 * 24 * 7)
def get_emoji(self) -> Dict[str, Dict[str, Any]]: def get_emoji(self) -> Dict[str, EmojiInfo]:
return get_realm_emoji_uncached(self) return get_realm_emoji_uncached(self)
@cache_with_key(get_active_realm_emoji_cache_key, timeout=3600 * 24 * 7) @cache_with_key(get_active_realm_emoji_cache_key, timeout=3600 * 24 * 7)
def get_active_emoji(self) -> Dict[str, Dict[str, Any]]: def get_active_emoji(self) -> Dict[str, EmojiInfo]:
return get_active_realm_emoji_uncached(self) return get_active_realm_emoji_uncached(self)
def get_admin_users_and_bots( def get_admin_users_and_bots(
@ -1058,9 +1068,7 @@ class RealmEmoji(models.Model):
] ]
def get_realm_emoji_dicts( def get_realm_emoji_dicts(realm: Realm, only_active_emojis: bool = False) -> Dict[str, EmojiInfo]:
realm: Realm, only_active_emojis: bool = False
) -> Dict[str, Dict[str, Any]]:
query = RealmEmoji.objects.filter(realm=realm).select_related("author") query = RealmEmoji.objects.filter(realm=realm).select_related("author")
if only_active_emojis: if only_active_emojis:
query = query.filter(deactivated=False) query = query.filter(deactivated=False)
@ -1073,12 +1081,13 @@ def get_realm_emoji_dicts(
author_id = realm_emoji.author_id author_id = realm_emoji.author_id
emoji_url = get_emoji_url(realm_emoji.file_name, realm_emoji.realm_id) emoji_url = get_emoji_url(realm_emoji.file_name, realm_emoji.realm_id)
emoji_dict = dict( emoji_dict: EmojiInfo = dict(
id=str(realm_emoji.id), id=str(realm_emoji.id),
name=realm_emoji.name, name=realm_emoji.name,
source_url=emoji_url, source_url=emoji_url,
deactivated=realm_emoji.deactivated, deactivated=realm_emoji.deactivated,
author_id=author_id, author_id=author_id,
still_url=None,
) )
if realm_emoji.is_animated: if realm_emoji.is_animated:
@ -1095,11 +1104,11 @@ def get_realm_emoji_dicts(
return d return d
def get_realm_emoji_uncached(realm: Realm) -> Dict[str, Dict[str, Any]]: def get_realm_emoji_uncached(realm: Realm) -> Dict[str, EmojiInfo]:
return get_realm_emoji_dicts(realm) return get_realm_emoji_dicts(realm)
def get_active_realm_emoji_uncached(realm: Realm) -> Dict[str, Dict[str, Any]]: def get_active_realm_emoji_uncached(realm: Realm) -> Dict[str, EmojiInfo]:
realm_emojis = get_realm_emoji_dicts(realm, only_active_emojis=True) realm_emojis = get_realm_emoji_dicts(realm, only_active_emojis=True)
d = {} d = {}
for emoji_id, emoji_dict in realm_emojis.items(): for emoji_id, emoji_dict in realm_emojis.items():

View File

@ -13931,8 +13931,9 @@ components:
emoji's image can be found. emoji's image can be found.
still_url: still_url:
type: string type: string
nullable: true
description: | description: |
Only present when the emoji's image is animated. Only non-null when the emoji's image is animated.
The path relative to the organization's URL where a still The path relative to the organization's URL where a still
(not animated) version of the emoji can be found. (This is (not animated) version of the emoji can be found. (This is
@ -13942,7 +13943,9 @@ components:
where continuously animating it would be a bad user experience where continuously animating it would be a bad user experience
(E.g. because it would be distracting). (E.g. because it would be distracting).
**Changes**: New in Zulip 5.0 (feature level 97). **Changes**: New in Zulip 5.0 (added as optional field in
feature level 97 and then made mandatory, but nullable, in
feature level 113).
deactivated: deactivated:
type: boolean type: boolean
description: | description: |