emojis: Use get_emoji_data.

The previous function was poorly named, asked for a
Realm object when realm_id sufficed, and returned a
tuple of strings that had different semantics.

I also avoid calling it duplicate times in a couple
places, although it was probably rarely the case that
both invocations actually happened if upstream
validations were working.

Note that there is a TypedDict called EmojiInfo, so I
chose EmojiData here.  Perhaps a better name would be
TinyEmojiData or something.

I also simplify the reaction tests with a verify
helper.
This commit is contained in:
Steve Howell
2023-07-14 12:25:57 +00:00
committed by Tim Abbott
parent b742f1241f
commit 67cdf1a7b4
9 changed files with 77 additions and 67 deletions

View File

@@ -3,7 +3,7 @@ from typing import Any, Dict, Optional
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from zerver.actions.create_user import create_historical_user_messages from zerver.actions.create_user import create_historical_user_messages
from zerver.lib.emoji import check_emoji_request, emoji_name_to_emoji_code from zerver.lib.emoji import check_emoji_request, get_emoji_data
from zerver.lib.exceptions import JsonableError from zerver.lib.exceptions import JsonableError
from zerver.lib.message import access_message, update_to_dict_cache from zerver.lib.message import access_message, update_to_dict_cache
from zerver.lib.stream_subscription import subscriber_ids_with_stream_history_access from zerver.lib.stream_subscription import subscriber_ids_with_stream_history_access
@@ -96,15 +96,18 @@ def check_add_reaction(
) -> None: ) -> None:
message, user_message = access_message(user_profile, message_id, lock_message=True) message, user_message = access_message(user_profile, message_id, lock_message=True)
if emoji_code is None or reaction_type is None:
emoji_data = get_emoji_data(message.sender.realm_id, emoji_name)
if emoji_code is None: if emoji_code is None:
# The emoji_code argument is only required for rare corner # The emoji_code argument is only required for rare corner
# cases discussed in the long block comment below. For simple # cases discussed in the long block comment below. For simple
# API clients, we allow specifying just the name, and just # API clients, we allow specifying just the name, and just
# look up the code using the current name->code mapping. # look up the code using the current name->code mapping.
emoji_code = emoji_name_to_emoji_code(message.sender.realm, emoji_name)[0] emoji_code = emoji_data.emoji_code
if reaction_type is None: if reaction_type is None:
reaction_type = emoji_name_to_emoji_code(message.sender.realm, emoji_name)[1] reaction_type = emoji_data.reaction_type
if Reaction.objects.filter( if Reaction.objects.filter(
user_profile=user_profile, user_profile=user_profile,

View File

@@ -268,7 +268,7 @@ def build_reactions(
realmemoji[realm_emoji["name"]] = realm_emoji["id"] realmemoji[realm_emoji["name"]] = realm_emoji["id"]
# For the Unicode emoji codes, we use equivalent of # For the Unicode emoji codes, we use equivalent of
# function 'emoji_name_to_emoji_code' in 'zerver/lib/emoji' here # function 'get_emoji_data' in 'zerver/lib/emoji' here
for mattermost_reaction in reactions: for mattermost_reaction in reactions:
emoji_name = mattermost_reaction["emoji_name"] emoji_name = mattermost_reaction["emoji_name"]
username = mattermost_reaction["user"] username = mattermost_reaction["user"]

View File

@@ -1163,7 +1163,7 @@ def build_reactions(
reactions = [{"name": k, "users": v, "count": len(v)} for k, v in merged_reactions.items()] reactions = [{"name": k, "users": v, "count": len(v)} for k, v in merged_reactions.items()]
# For the Unicode emoji codes, we use equivalent of # For the Unicode emoji codes, we use equivalent of
# function 'emoji_name_to_emoji_code' in 'zerver/lib/emoji' here # function 'get_emoji_data' in 'zerver/lib/emoji' here
for slack_reaction in reactions: for slack_reaction in reactions:
emoji_name = slack_reaction["name"] emoji_name = slack_reaction["name"]
if emoji_name in slack_emoji_name_to_codepoint: if emoji_name in slack_emoji_name_to_codepoint:

View File

@@ -1,6 +1,6 @@
import os import os
import re import re
from typing import Tuple from dataclasses import dataclass
import orjson import orjson
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
@@ -62,17 +62,29 @@ def translate_emoticons(text: str) -> str:
return translated return translated
def emoji_name_to_emoji_code(realm: Realm, emoji_name: str) -> Tuple[str, str]: @dataclass
# TODO: Just ask callers for realm_id. Also consider returning a class EmojiData:
# tiny dataclass instead of a tuple. emoji_code: str
realm_emoji_dict = get_name_keyed_dict_for_active_realm_emoji(realm.id) reaction_type: str
def get_emoji_data(realm_id: int, emoji_name: str) -> EmojiData:
# Even if emoji_name is either in name_to_codepoint or named "zulip",
# we still need to call get_realm_active_emoji.
realm_emoji_dict = get_name_keyed_dict_for_active_realm_emoji(realm_id)
realm_emoji = realm_emoji_dict.get(emoji_name) realm_emoji = realm_emoji_dict.get(emoji_name)
if realm_emoji is not None: if realm_emoji is not None:
return str(realm_emoji["id"]), Reaction.REALM_EMOJI emoji_code = str(realm_emoji["id"])
return EmojiData(emoji_code=emoji_code, reaction_type=Reaction.REALM_EMOJI)
if emoji_name == "zulip": if emoji_name == "zulip":
return emoji_name, Reaction.ZULIP_EXTRA_EMOJI return EmojiData(emoji_code=emoji_name, reaction_type=Reaction.ZULIP_EXTRA_EMOJI)
if emoji_name in name_to_codepoint: if emoji_name in name_to_codepoint:
return name_to_codepoint[emoji_name], Reaction.UNICODE_EMOJI emoji_code = name_to_codepoint[emoji_name]
return EmojiData(emoji_code=emoji_code, reaction_type=Reaction.UNICODE_EMOJI)
raise JsonableError(_("Emoji '{}' does not exist").format(emoji_name)) raise JsonableError(_("Emoji '{}' does not exist").format(emoji_name))

View File

@@ -13,7 +13,7 @@ from zerver.actions.message_send import (
internal_send_private_message, internal_send_private_message,
) )
from zerver.actions.reactions import do_add_reaction from zerver.actions.reactions import do_add_reaction
from zerver.lib.emoji import emoji_name_to_emoji_code from zerver.lib.emoji import get_emoji_data
from zerver.lib.message import SendMessageRequest from zerver.lib.message import SendMessageRequest
from zerver.models import Message, Realm, UserProfile, get_system_bot from zerver.models import Message, Realm, UserProfile, get_system_bot
@@ -355,5 +355,7 @@ def send_initial_realm_messages(realm: Realm) -> None:
turtle_message = Message.objects.select_for_update().get( turtle_message = Message.objects.select_for_update().get(
id__in=message_ids, content__icontains="cute/turtle.png" id__in=message_ids, content__icontains="cute/turtle.png"
) )
(emoji_code, reaction_type) = emoji_name_to_emoji_code(realm, "turtle") emoji_data = get_emoji_data(realm.id, "turtle")
do_add_reaction(welcome_bot, turtle_message, "turtle", emoji_code, reaction_type) do_add_reaction(
welcome_bot, turtle_message, "turtle", emoji_data.emoji_code, emoji_data.reaction_type
)

View File

@@ -6,7 +6,7 @@ import orjson
from zerver.actions.reactions import notify_reaction_update from zerver.actions.reactions import notify_reaction_update
from zerver.actions.streams import do_change_stream_permission from zerver.actions.streams import do_change_stream_permission
from zerver.lib.cache import cache_get, to_dict_cache_key_id from zerver.lib.cache import cache_get, to_dict_cache_key_id
from zerver.lib.emoji import emoji_name_to_emoji_code from zerver.lib.emoji import get_emoji_data
from zerver.lib.exceptions import JsonableError from zerver.lib.exceptions import JsonableError
from zerver.lib.message import extract_message_dict from zerver.lib.message import extract_message_dict
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
@@ -189,59 +189,49 @@ class ReactionEmojiTest(ZulipTestCase):
result = self.api_post(sender, "/api/v1/messages/1/reactions", reaction_info) result = self.api_post(sender, "/api/v1/messages/1/reactions", reaction_info)
self.assert_json_success(result) self.assert_json_success(result)
def test_emoji_name_to_emoji_code(self) -> None: def test_get_emoji_data(self) -> None:
"""
An emoji name is mapped canonically to emoji code.
"""
realm = get_realm("zulip") realm = get_realm("zulip")
realm_emoji = RealmEmoji.objects.get(name="green_tick") realm_emoji = RealmEmoji.objects.get(name="green_tick")
def verify(emoji_name: str, emoji_code: str, reaction_type: str) -> None:
emoji_data = get_emoji_data(realm.id, emoji_name)
self.assertEqual(emoji_data.emoji_code, emoji_code)
self.assertEqual(emoji_data.reaction_type, reaction_type)
# Test active realm emoji. # Test active realm emoji.
emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "green_tick") verify("green_tick", str(realm_emoji.id), "realm_emoji")
self.assertEqual(emoji_code, str(realm_emoji.id))
self.assertEqual(reaction_type, "realm_emoji")
# Test deactivated realm emoji. # Test deactivated realm emoji.
realm_emoji.deactivated = True realm_emoji.deactivated = True
realm_emoji.save(update_fields=["deactivated"]) realm_emoji.save(update_fields=["deactivated"])
with self.assertRaises(JsonableError) as exc: with self.assertRaises(JsonableError) as exc:
emoji_name_to_emoji_code(realm, "green_tick") get_emoji_data(realm.id, "green_tick")
self.assertEqual(str(exc.exception), "Emoji 'green_tick' does not exist") self.assertEqual(str(exc.exception), "Emoji 'green_tick' does not exist")
# Test ':zulip:' emoji. # Test ':zulip:' emoji.
emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "zulip") verify("zulip", "zulip", "zulip_extra_emoji")
self.assertEqual(emoji_code, "zulip")
self.assertEqual(reaction_type, "zulip_extra_emoji")
# Test Unicode emoji. # Test Unicode emoji.
emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "astonished") verify("astonished", "1f632", "unicode_emoji")
self.assertEqual(emoji_code, "1f632")
self.assertEqual(reaction_type, "unicode_emoji")
# Test override Unicode emoji. # Test override Unicode emoji.
overriding_emoji = RealmEmoji.objects.create( overriding_emoji = RealmEmoji.objects.create(
name="astonished", realm=realm, file_name="astonished" name="astonished", realm=realm, file_name="astonished"
) )
emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "astonished") verify("astonished", str(overriding_emoji.id), "realm_emoji")
self.assertEqual(emoji_code, str(overriding_emoji.id))
self.assertEqual(reaction_type, "realm_emoji")
# Test deactivate over-ridding realm emoji. # Test deactivate over-ridding realm emoji.
overriding_emoji.deactivated = True overriding_emoji.deactivated = True
overriding_emoji.save(update_fields=["deactivated"]) overriding_emoji.save(update_fields=["deactivated"])
emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "astonished") verify("astonished", "1f632", "unicode_emoji")
self.assertEqual(emoji_code, "1f632")
self.assertEqual(reaction_type, "unicode_emoji")
# Test override `:zulip:` emoji. # Test override `:zulip:` emoji.
overriding_emoji = RealmEmoji.objects.create(name="zulip", realm=realm, file_name="zulip") overriding_emoji = RealmEmoji.objects.create(name="zulip", realm=realm, file_name="zulip")
emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "zulip") verify("zulip", str(overriding_emoji.id), "realm_emoji")
self.assertEqual(emoji_code, str(overriding_emoji.id))
self.assertEqual(reaction_type, "realm_emoji")
# Test non-existent emoji. # Test non-existent emoji.
with self.assertRaises(JsonableError) as exc: with self.assertRaises(JsonableError) as exc:
emoji_name_to_emoji_code(realm, "invalid_emoji") get_emoji_data(realm.id, "invalid_emoji")
self.assertEqual(str(exc.exception), "Emoji 'invalid_emoji' does not exist") self.assertEqual(str(exc.exception), "Emoji 'invalid_emoji' does not exist")
@@ -376,13 +366,12 @@ class ReactionTest(ZulipTestCase):
Removes an old existing reaction but the name of emoji got changed during Removes an old existing reaction but the name of emoji got changed during
various emoji infra changes. various emoji infra changes.
""" """
realm = get_realm("zulip")
sender = self.example_user("hamlet") sender = self.example_user("hamlet")
emoji_code, reaction_type = emoji_name_to_emoji_code(realm, "smile") emoji_data = get_emoji_data(sender.realm_id, "smile")
reaction_info = { reaction_info = {
"emoji_name": "smile", "emoji_name": "smile",
"emoji_code": emoji_code, "emoji_code": emoji_data.emoji_code,
"reaction_type": reaction_type, "reaction_type": emoji_data.reaction_type,
} }
result = self.api_post(sender, "/api/v1/messages/1/reactions", reaction_info) result = self.api_post(sender, "/api/v1/messages/1/reactions", reaction_info)

View File

@@ -9,7 +9,7 @@ from django.utils.translation import gettext as _
from zerver.actions.presence import update_user_presence from zerver.actions.presence import update_user_presence
from zerver.actions.user_status import do_update_user_status from zerver.actions.user_status import do_update_user_status
from zerver.decorator import human_users_only from zerver.decorator import human_users_only
from zerver.lib.emoji import check_emoji_request, emoji_name_to_emoji_code from zerver.lib.emoji import check_emoji_request, get_emoji_data
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, RequestNotes, has_request_variables from zerver.lib.request import REQ, RequestNotes, has_request_variables
@@ -91,15 +91,17 @@ def update_user_status_backend(
emoji_type = UserStatus.UNICODE_EMOJI emoji_type = UserStatus.UNICODE_EMOJI
elif emoji_name is not None: elif emoji_name is not None:
if emoji_code is None or emoji_type is None:
emoji_data = get_emoji_data(user_profile.realm_id, emoji_name)
if emoji_code is None: if emoji_code is None:
# The emoji_code argument is only required for rare corner # The emoji_code argument is only required for rare corner
# cases discussed in the long block comment below. For simple # cases discussed in the long block comment below. For simple
# API clients, we allow specifying just the name, and just # API clients, we allow specifying just the name, and just
# look up the code using the current name->code mapping. # look up the code using the current name->code mapping.
emoji_code = emoji_name_to_emoji_code(user_profile.realm, emoji_name)[0] emoji_code = emoji_data.emoji_code
if emoji_type is None: if emoji_type is None:
emoji_type = emoji_name_to_emoji_code(user_profile.realm, emoji_name)[1] emoji_type = emoji_data.reaction_type
elif emoji_type or emoji_code: elif emoji_type or emoji_code:
raise JsonableError( raise JsonableError(

View File

@@ -5,7 +5,7 @@ from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from zerver.actions.reactions import check_add_reaction, do_remove_reaction from zerver.actions.reactions import check_add_reaction, do_remove_reaction
from zerver.lib.emoji import emoji_name_to_emoji_code from zerver.lib.emoji import get_emoji_data
from zerver.lib.exceptions import JsonableError from zerver.lib.exceptions import JsonableError
from zerver.lib.message import access_message from zerver.lib.message import access_message
from zerver.lib.request import REQ, has_request_variables from zerver.lib.request import REQ, has_request_variables
@@ -57,7 +57,7 @@ def remove_reaction(
# without needing the mapping between emoji names and codes, # without needing the mapping between emoji names and codes,
# we allow instead passing the emoji_name and looking up the # we allow instead passing the emoji_name and looking up the
# corresponding code using the current data. # corresponding code using the current data.
emoji_code = emoji_name_to_emoji_code(message.sender.realm, emoji_name)[0] emoji_code = get_emoji_data(message.sender.realm_id, emoji_name).emoji_code
if not Reaction.objects.filter( if not Reaction.objects.filter(
user_profile=user_profile, user_profile=user_profile,

View File

@@ -7,7 +7,7 @@ from zerver.actions.message_send import do_send_messages, internal_prep_stream_m
from zerver.actions.reactions import do_add_reaction from zerver.actions.reactions import do_add_reaction
from zerver.actions.streams import bulk_add_subscriptions from zerver.actions.streams import bulk_add_subscriptions
from zerver.actions.user_settings import do_change_avatar_fields from zerver.actions.user_settings import do_change_avatar_fields
from zerver.lib.emoji import emoji_name_to_emoji_code from zerver.lib.emoji import get_emoji_data
from zerver.lib.streams import ensure_stream from zerver.lib.streams import ensure_stream
from zerver.lib.upload import upload_avatar_image from zerver.lib.upload import upload_avatar_image
from zerver.models import Message, UserProfile, get_realm from zerver.models import Message, UserProfile, get_realm
@@ -122,8 +122,8 @@ From image editing program:
preview_message = Message.objects.get( preview_message = Message.objects.get(
id__in=message_ids, content__icontains="image previews" id__in=message_ids, content__icontains="image previews"
) )
(emoji_code, reaction_type) = emoji_name_to_emoji_code(realm, "whale") whale = get_emoji_data(realm.id, "whale")
do_add_reaction(starr, preview_message, "whale", emoji_code, reaction_type) do_add_reaction(starr, preview_message, "whale", whale.emoji_code, whale.reaction_type)
twitter_message = Message.objects.get(id__in=message_ids, content__icontains="gvanrossum") twitter_message = Message.objects.get(id__in=message_ids, content__icontains="gvanrossum")
# Setting up a twitter integration in dev is a decent amount of work. If you need # Setting up a twitter integration in dev is a decent amount of work. If you need
@@ -142,8 +142,10 @@ From image editing program:
# Put a short pause between the whale reaction and this, so that the # Put a short pause between the whale reaction and this, so that the
# thumbs_up shows up second # thumbs_up shows up second
(emoji_code, reaction_type) = emoji_name_to_emoji_code(realm, "thumbs_up") thumbs_up = get_emoji_data(realm.id, "thumbs_up")
do_add_reaction(starr, preview_message, "thumbs_up", emoji_code, reaction_type) do_add_reaction(
starr, preview_message, "thumbs_up", thumbs_up.emoji_code, thumbs_up.reaction_type
)
def handle(self, *args: Any, **options: str) -> None: def handle(self, *args: Any, **options: str) -> None:
self.add_message_formatting_conversation() self.add_message_formatting_conversation()