refactor: Introduce bugdown.MentionData class.

We now have a MentionData class that encapsulates
the users who are possibly mentioned in a message.

Not that the rendering code may not keep all the mentions,
since things like backticks will suppress the mention.

We populate this now in do_send_messages, so that we can use
the info earlier in the message-sending process.  This info
now gets passed down the call stack as an optional parameter.

Note that bugdown.convert() still populates the data when its
callers decline to pass in a MentionData object.

This is mostly a preparatory commit, as we don't take advantage
of the data yet in do_send_messages.
This commit is contained in:
Steve Howell
2017-10-23 17:47:09 -07:00
committed by Tim Abbott
parent 45e8ce559d
commit 8ac26dfb9b
3 changed files with 43 additions and 17 deletions

View File

@@ -762,8 +762,8 @@ def send_welcome_bot_response(message):
"Feel free to continue using this space to practice your new messaging "
"skills. Or, try clicking on some of the stream names to your left!")
def render_incoming_message(message, content, user_ids, realm):
# type: (Message, Text, Set[int], Realm) -> Text
def render_incoming_message(message, content, user_ids, realm, mention_data=None):
# type: (Message, Text, Set[int], Realm, Optional[bugdown.MentionData]) -> Text
realm_alert_words = alert_words_in_realm(realm)
try:
rendered_content = render_markdown(
@@ -772,6 +772,7 @@ def render_incoming_message(message, content, user_ids, realm):
realm=realm,
realm_alert_words=realm_alert_words,
user_ids=user_ids,
mention_data=mention_data,
)
except BugdownRenderingException:
raise JsonableError(_('Unable to render message'))
@@ -1021,6 +1022,12 @@ def do_send_messages(messages_maybe_none):
message['realm'] = message.get('realm', message['message'].sender.realm)
for message in messages:
mention_data = bugdown.MentionData(
realm_id=message['realm'].id,
content=message['message'].content,
)
message['mention_data'] = mention_data
if message['message'].recipient.type == Recipient.STREAM:
stream_id = message['message'].recipient.type_id
stream_topic = StreamTopicTarget(
@@ -1047,11 +1054,14 @@ def do_send_messages(messages_maybe_none):
# Render our messages.
for message in messages:
assert message['message'].rendered_content is None
rendered_content = render_incoming_message(
message['message'],
message['message'].content,
message['active_user_ids'],
message['realm'])
message['realm'],
mention_data=message['mention_data'],
)
message['message'].rendered_content = rendered_content
message['message'].rendered_content_version = bugdown_version
links_for_embed |= message['message'].links_for_preview

View File

@@ -1148,7 +1148,7 @@ class UserMentionPattern(markdown.inlinepatterns.Pattern):
name = match
wildcard = mention.user_mention_matches_wildcard(name)
user = db_data['full_name_info'].get(name.lower(), None)
user = db_data['mention_data'].get_user(name)
if wildcard:
current_message.mentions_wildcard = True
@@ -1549,6 +1549,16 @@ def get_full_name_info(realm_id, full_names):
}
return dct
class MentionData(object):
def __init__(self, realm_id, content):
# type: (int, Text) -> None
full_names = possible_mentions(content)
self.full_name_info = get_full_name_info(realm_id, full_names)
def get_user(self, name):
# type: (Text) -> Optional[FullNameInfo]
return self.full_name_info.get(name.lower(), None)
def get_stream_name_info(realm, stream_names):
# type: (Realm, Set[Text]) -> Dict[Text, FullNameInfo]
if not stream_names:
@@ -1575,8 +1585,8 @@ def get_stream_name_info(realm, stream_names):
return dct
def do_convert(content, message=None, message_realm=None, possible_words=None, sent_by_bot=False):
# type: (Text, Optional[Message], Optional[Realm], Optional[Set[Text]], Optional[bool]) -> Text
def do_convert(content, message=None, message_realm=None, possible_words=None, sent_by_bot=False, mention_data=None):
# type: (Text, Optional[Message], Optional[Realm], Optional[Set[Text]], Optional[bool], Optional[MentionData]) -> Text
"""Convert Markdown to HTML, with Zulip-specific settings and hacks."""
# This logic is a bit convoluted, but the overall goal is to support a range of use cases:
# * Nothing is passed in other than content -> just run default options (e.g. for docs)
@@ -1623,8 +1633,9 @@ def do_convert(content, message=None, message_realm=None, possible_words=None, s
# if there is syntax in the message that might use them, since
# the fetches are somewhat expensive and these types of syntax
# are uncommon enough that it's a useful optimization.
full_names = possible_mentions(content)
full_name_info = get_full_name_info(message_realm.id, full_names)
if mention_data is None:
mention_data = MentionData(message_realm.id, content)
emails = possible_avatar_emails(content)
email_info = get_email_info(message_realm.id, emails)
@@ -1640,7 +1651,7 @@ def do_convert(content, message=None, message_realm=None, possible_words=None, s
db_data = {
'possible_words': possible_words,
'email_info': email_info,
'full_name_info': full_name_info,
'mention_data': mention_data,
'realm_emoji': realm_emoji,
'sent_by_bot': sent_by_bot,
'stream_names': stream_name_info,
@@ -1694,9 +1705,9 @@ def bugdown_stats_finish():
bugdown_total_requests += 1
bugdown_total_time += (time.time() - bugdown_time_start)
def convert(content, message=None, message_realm=None, possible_words=None, sent_by_bot=False):
# type: (Text, Optional[Message], Optional[Realm], Optional[Set[Text]], Optional[bool]) -> Text
def convert(content, message=None, message_realm=None, possible_words=None, sent_by_bot=False, mention_data=None):
# type: (Text, Optional[Message], Optional[Realm], Optional[Set[Text]], Optional[bool], Optional[MentionData]) -> Text
bugdown_stats_start()
ret = do_convert(content, message, message_realm, possible_words, sent_by_bot)
ret = do_convert(content, message, message_realm, possible_words, sent_by_bot, mention_data)
bugdown_stats_finish()
return ret

View File

@@ -461,8 +461,8 @@ def access_message(user_profile, message_id):
# stream in your realm, so return the message, user_message pair
return (message, user_message)
def render_markdown(message, content, realm=None, realm_alert_words=None, user_ids=None):
# type: (Message, Text, Optional[Realm], Optional[RealmAlertWords], Optional[Set[int]]) -> Text
def render_markdown(message, content, realm=None, realm_alert_words=None, user_ids=None, mention_data=None):
# type: (Message, Text, Optional[Realm], Optional[RealmAlertWords], Optional[Set[int]], Optional[bugdown.MentionData]) -> Text
"""Return HTML for given markdown. Bugdown may add properties to the
message object such as `mentions_user_ids` and `mentions_wildcard`.
These are only on this Django object and are not saved in the
@@ -497,9 +497,14 @@ def render_markdown(message, content, realm=None, realm_alert_words=None, user_i
sent_by_bot = get_user_profile_by_id(message.sender_id).is_bot
# DO MAIN WORK HERE -- call bugdown to convert
rendered_content = bugdown.convert(content, message=message, message_realm=realm,
rendered_content = bugdown.convert(
content,
message=message,
message_realm=realm,
possible_words=possible_words,
sent_by_bot=sent_by_bot)
sent_by_bot=sent_by_bot,
mention_data=mention_data,
)
if message is not None:
message.user_ids_with_alert_words = set()