Add submessages to message payloads.

This commit is contained in:
Steve Howell 2018-02-11 08:09:17 -05:00 committed by Tim Abbott
parent ff097623fa
commit 4332fd64f7
8 changed files with 56 additions and 12 deletions

View File

@ -450,6 +450,13 @@ def flush_message(sender: Any, **kwargs: Any) -> None:
message = kwargs['instance'] message = kwargs['instance']
cache_delete(to_dict_cache_key_id(message.id)) cache_delete(to_dict_cache_key_id(message.id))
def flush_submessage(sender: Any, **kwargs: Any) -> None:
submessage = kwargs['instance']
# submessages are not cached directly, they are part of their
# parent messages
message_id = submessage.message_id
cache_delete(to_dict_cache_key_id(message_id))
DECORATOR = Callable[[Callable[..., Any]], Callable[..., Any]] DECORATOR = Callable[[Callable[..., Any]], Callable[..., Any]]
def ignore_unhashable_lru_cache(maxsize: int=128, typed: bool=False) -> DECORATOR: def ignore_unhashable_lru_cache(maxsize: int=128, typed: bool=False) -> DECORATOR:

View File

@ -37,6 +37,7 @@ from zerver.models import (
Realm, Realm,
Recipient, Recipient,
Stream, Stream,
SubMessage,
Subscription, Subscription,
UserProfile, UserProfile,
UserMessage, UserMessage,
@ -121,6 +122,20 @@ def sew_messages_and_reactions(messages: List[Dict[str, Any]],
return list(converted_messages.values()) return list(converted_messages.values())
def sew_messages_and_submessages(messages: List[Dict[str, Any]],
submessages: List[Dict[str, Any]]) -> None:
# This is super similar to sew_messages_and_reactions.
for message in messages:
message['submessages'] = []
message_dict = {message['id']: message for message in messages}
for submessage in submessages:
message_id = submessage['message_id']
if message_id in message_dict:
message = message_dict[message_id]
message['submessages'].append(submessage)
def extract_message_dict(message_bytes: bytes) -> Dict[str, Any]: def extract_message_dict(message_bytes: bytes) -> Dict[str, Any]:
return ujson.loads(zlib.decompress(message_bytes).decode("utf-8")) return ujson.loads(zlib.decompress(message_bytes).decode("utf-8"))
@ -205,7 +220,8 @@ class MessageDict:
recipient_id = message.recipient.id, recipient_id = message.recipient.id,
recipient_type = message.recipient.type, recipient_type = message.recipient.type,
recipient_type_id = message.recipient.type_id, recipient_type_id = message.recipient.type_id,
reactions = Reaction.get_raw_db_rows([message.id]) reactions = Reaction.get_raw_db_rows([message.id]),
submessages = SubMessage.get_raw_db_rows([message.id]),
) )
@staticmethod @staticmethod
@ -229,11 +245,10 @@ class MessageDict:
'sender__realm_id', 'sender__realm_id',
] ]
messages = Message.objects.filter(id__in=needed_ids).values(*fields) messages = Message.objects.filter(id__in=needed_ids).values(*fields)
"""Adding one-many or Many-Many relationship in values results in N X
results.
Link: https://docs.djangoproject.com/en/1.8/ref/models/querysets/#values submessages = SubMessage.get_raw_db_rows(needed_ids)
""" sew_messages_and_submessages(messages, submessages)
reactions = Reaction.get_raw_db_rows(needed_ids) reactions = Reaction.get_raw_db_rows(needed_ids)
return sew_messages_and_reactions(messages, reactions) return sew_messages_and_reactions(messages, reactions)
@ -259,7 +274,8 @@ class MessageDict:
recipient_id = row['recipient_id'], recipient_id = row['recipient_id'],
recipient_type = row['recipient__type'], recipient_type = row['recipient__type'],
recipient_type_id = row['recipient__type_id'], recipient_type_id = row['recipient__type_id'],
reactions=row['reactions'] reactions=row['reactions'],
submessages=row['submessages'],
) )
@staticmethod @staticmethod
@ -279,7 +295,8 @@ class MessageDict:
recipient_id: int, recipient_id: int,
recipient_type: int, recipient_type: int,
recipient_type_id: int, recipient_type_id: int,
reactions: List[Dict[str, Any]] reactions: List[Dict[str, Any]],
submessages: List[Dict[str, Any]]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
obj = dict( obj = dict(
@ -343,6 +360,7 @@ class MessageDict:
obj['reactions'] = [ReactionDict.build_dict_from_raw_db_row(reaction) obj['reactions'] = [ReactionDict.build_dict_from_raw_db_row(reaction)
for reaction in reactions] for reaction in reactions]
obj['submessages'] = submessages
return obj return obj
@staticmethod @staticmethod

View File

@ -22,7 +22,7 @@ from zerver.lib.cache import cache_with_key, flush_user_profile, flush_realm, \
display_recipient_cache_key, cache_delete, active_user_ids_cache_key, \ display_recipient_cache_key, cache_delete, active_user_ids_cache_key, \
get_stream_cache_key, realm_user_dicts_cache_key, \ get_stream_cache_key, realm_user_dicts_cache_key, \
bot_dicts_in_realm_cache_key, realm_user_dict_fields, \ bot_dicts_in_realm_cache_key, realm_user_dict_fields, \
bot_dict_fields, flush_message, bot_profile_cache_key bot_dict_fields, flush_message, flush_submessage, bot_profile_cache_key
from zerver.lib.utils import make_safe_digest, generate_random_token from zerver.lib.utils import make_safe_digest, generate_random_token
from django.db import transaction from django.db import transaction
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
@ -1318,6 +1318,8 @@ class SubMessage(models.Model):
query = query.order_by('message_id', 'id') query = query.order_by('message_id', 'id')
return list(query) return list(query)
post_save.connect(flush_submessage, sender=SubMessage)
class Reaction(models.Model): class Reaction(models.Model):
user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) # type: UserProfile user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) # type: UserProfile
message = models.ForeignKey(Message, on_delete=CASCADE) # type: Message message = models.ForeignKey(Message, on_delete=CASCADE) # type: Message

View File

@ -637,6 +637,7 @@ class EventsRegisterTest(ZulipTestCase):
('stream_id', check_int), ('stream_id', check_int),
('subject', check_string), ('subject', check_string),
('subject_links', check_list(None)), ('subject_links', check_list(None)),
('submessages', check_list(None)),
('timestamp', check_int), ('timestamp', check_int),
('type', check_string), ('type', check_string),
])), ])),

View File

@ -623,7 +623,7 @@ class StreamMessagesTest(ZulipTestCase):
body=content, body=content,
) )
self.assert_length(queries, 13) self.assert_length(queries, 14)
def test_stream_message_dict(self) -> None: def test_stream_message_dict(self) -> None:
user_profile = self.example_user('iago') user_profile = self.example_user('iago')
@ -867,7 +867,7 @@ class MessageDictTest(ZulipTestCase):
# slower. # slower.
error_msg = "Number of ids: {}. Time delay: {}".format(num_ids, delay) error_msg = "Number of ids: {}. Time delay: {}".format(num_ids, delay)
self.assertTrue(delay < 0.0015 * num_ids, error_msg) self.assertTrue(delay < 0.0015 * num_ids, error_msg)
self.assert_length(queries, 6) self.assert_length(queries, 7)
self.assertEqual(len(rows), num_ids) self.assertEqual(len(rows), num_ids)
def test_applying_markdown(self) -> None: def test_applying_markdown(self) -> None:

View File

@ -364,7 +364,7 @@ class LoginTest(ZulipTestCase):
with queries_captured() as queries: with queries_captured() as queries:
self.register(self.nonreg_email('test'), "test") self.register(self.nonreg_email('test'), "test")
# Ensure the number of queries we make is not O(streams) # Ensure the number of queries we make is not O(streams)
self.assert_length(queries, 70) self.assert_length(queries, 71)
user_profile = self.nonreg_user('test') user_profile = self.nonreg_user('test')
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id) self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
self.assertFalse(user_profile.enable_stream_desktop_notifications) self.assertFalse(user_profile.enable_stream_desktop_notifications)

View File

@ -1,6 +1,11 @@
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.message import (
MessageDict,
)
from zerver.models import ( from zerver.models import (
Message,
SubMessage, SubMessage,
) )
@ -57,3 +62,14 @@ class TestBasics(ZulipTestCase):
] ]
self.assertEqual(get_raw_rows(), expected_data) self.assertEqual(get_raw_rows(), expected_data)
message = Message.objects.get(id=message_id)
message_json = MessageDict.wide_dict(message)
rows = message_json['submessages']
rows.sort(key=lambda r: r['id'])
self.assertEqual(rows, expected_data)
msg_rows = MessageDict.get_raw_db_rows([message_id])
rows = msg_rows[0]['submessages']
rows.sort(key=lambda r: r['id'])
self.assertEqual(rows, expected_data)

View File

@ -1995,7 +1995,7 @@ class SubscriptionAPITest(ZulipTestCase):
streams_to_sub, streams_to_sub,
dict(principals=ujson.dumps([user1.email, user2.email])), dict(principals=ujson.dumps([user1.email, user2.email])),
) )
self.assert_length(queries, 42) self.assert_length(queries, 43)
self.assert_length(events, 7) self.assert_length(events, 7)
for ev in [x for x in events if x['event']['type'] not in ('message', 'stream')]: for ev in [x for x in events if x['event']['type'] not in ('message', 'stream')]: