url_encoding: Add support for with message links.

We abstract away "near" vs "with" from the function names and
allow callers to specify whether they want a conversation_link,
ie, use the "with" operator. The default choice is "near".
This commit is contained in:
Kislay Verma
2025-06-29 13:42:33 +05:30
committed by Tim Abbott
parent 9b24e54c54
commit 6bd6f23549
7 changed files with 49 additions and 25 deletions

View File

@@ -79,7 +79,7 @@ from zerver.lib.topic import (
) )
from zerver.lib.topic_link_util import get_stream_topic_link_syntax from zerver.lib.topic_link_util import get_stream_topic_link_syntax
from zerver.lib.types import DirectMessageEditRequest, EditHistoryEvent, StreamMessageEditRequest from zerver.lib.types import DirectMessageEditRequest, EditHistoryEvent, StreamMessageEditRequest
from zerver.lib.url_encoding import near_stream_message_url from zerver.lib.url_encoding import stream_message_url
from zerver.lib.user_message import bulk_insert_all_ums from zerver.lib.user_message import bulk_insert_all_ums
from zerver.lib.user_topics import get_users_with_user_topic_visibility_policy from zerver.lib.user_topics import get_users_with_user_topic_visibility_policy
from zerver.lib.widget import is_widget_message from zerver.lib.widget import is_widget_message
@@ -337,7 +337,7 @@ def send_message_moved_breadcrumbs(
"display_recipient": new_stream.name, "display_recipient": new_stream.name,
"topic": new_topic_name, "topic": new_topic_name,
} }
moved_message_link = near_stream_message_url(target_message.realm, message) moved_message_link = stream_message_url(target_message.realm, message)
if new_thread_notification_string is not None: if new_thread_notification_string is not None:
with override_language(new_stream.realm.default_language): with override_language(new_stream.realm.default_language):

View File

@@ -18,7 +18,7 @@ from zerver.lib.message_cache import MessageDict
from zerver.lib.outgoing_http import OutgoingSession from zerver.lib.outgoing_http import OutgoingSession
from zerver.lib.queue import retry_event from zerver.lib.queue import retry_event
from zerver.lib.topic import get_topic_from_message_info from zerver.lib.topic import get_topic_from_message_info
from zerver.lib.url_encoding import near_message_url from zerver.lib.url_encoding import message_link_url
from zerver.lib.users import check_can_access_user, check_user_can_access_all_users from zerver.lib.users import check_can_access_user, check_user_can_access_all_users
from zerver.models import Realm, Service, UserProfile from zerver.models import Realm, Service, UserProfile
from zerver.models.bots import GENERIC_INTERFACE, SLACK_INTERFACE from zerver.models.bots import GENERIC_INTERFACE, SLACK_INTERFACE
@@ -245,7 +245,7 @@ def get_message_url(event: dict[str, Any]) -> str:
message = event["message"] message = event["message"]
realm = bot_user.realm realm = bot_user.realm
return near_message_url( return message_link_url(
realm=realm, realm=realm,
message=message, message=message,
) )

View File

@@ -6,7 +6,7 @@ from zerver.lib.mention import silent_mention_syntax_for_user
from zerver.lib.message import truncate_content from zerver.lib.message import truncate_content
from zerver.lib.message_cache import MessageDict from zerver.lib.message_cache import MessageDict
from zerver.lib.topic_link_util import get_message_link_syntax from zerver.lib.topic_link_util import get_message_link_syntax
from zerver.lib.url_encoding import near_message_url from zerver.lib.url_encoding import message_link_url
from zerver.models import Message, Stream, UserProfile from zerver.models import Message, Stream, UserProfile
@@ -33,7 +33,7 @@ def get_reminder_formatted_content(message: Message, current_user: UserProfile)
content += "\n\n" content += "\n\n"
content += _("{user_silent_mention} [said]({conversation_url}):").format( content += _("{user_silent_mention} [said]({conversation_url}):").format(
user_silent_mention=silent_mention_syntax_for_user(message.sender), user_silent_mention=silent_mention_syntax_for_user(message.sender),
conversation_url=near_message_url(current_user.realm, MessageDict.wide_dict(message)), conversation_url=message_link_url(current_user.realm, MessageDict.wide_dict(message)),
) )
content += "\n" content += "\n"
fence = get_unused_fence(content) fence = get_unused_fence(content)

View File

@@ -48,22 +48,32 @@ def topic_narrow_url(*, realm: Realm, stream: Stream, topic_name: str) -> str:
return f"{base_url}{encode_stream(stream.id, stream.name)}/topic/{hash_util_encode(topic_name)}" return f"{base_url}{encode_stream(stream.id, stream.name)}/topic/{hash_util_encode(topic_name)}"
def near_message_url(realm: Realm, message: dict[str, Any]) -> str: def message_link_url(
realm: Realm, message: dict[str, Any], *, conversation_link: bool = False
) -> str:
if message["type"] == "stream": if message["type"] == "stream":
url = near_stream_message_url( url = stream_message_url(
realm=realm, realm=realm,
message=message, message=message,
conversation_link=conversation_link,
) )
return url return url
url = near_pm_message_url( url = pm_message_url(
realm=realm, realm=realm,
message=message, message=message,
conversation_link=conversation_link,
) )
return url return url
def near_stream_message_url(realm: Realm, message: dict[str, Any]) -> str: def stream_message_url(
realm: Realm, message: dict[str, Any], *, conversation_link: bool = False
) -> str:
if conversation_link:
with_or_near = "with"
else:
with_or_near = "near"
message_id = str(message["id"]) message_id = str(message["id"])
stream_id = message["stream_id"] stream_id = message["stream_id"]
stream_name = message["display_recipient"] stream_name = message["display_recipient"]
@@ -78,14 +88,21 @@ def near_stream_message_url(realm: Realm, message: dict[str, Any]) -> str:
encoded_stream, encoded_stream,
"topic", "topic",
encoded_topic_name, encoded_topic_name,
"near", with_or_near,
message_id, message_id,
] ]
full_url = "/".join(parts) full_url = "/".join(parts)
return full_url return full_url
def near_pm_message_url(realm: Realm, message: dict[str, Any]) -> str: def pm_message_url(
realm: Realm, message: dict[str, Any], *, conversation_link: bool = False
) -> str:
if conversation_link:
with_or_near = "with"
else:
with_or_near = "near"
message_id = str(message["id"]) message_id = str(message["id"])
str_user_ids = [str(recipient["id"]) for recipient in message["display_recipient"]] str_user_ids = [str(recipient["id"]) for recipient in message["display_recipient"]]
@@ -98,7 +115,7 @@ def near_pm_message_url(realm: Realm, message: dict[str, Any]) -> str:
"#narrow", "#narrow",
"dm", "dm",
pm_str, pm_str,
"near", with_or_near,
message_id, message_id,
] ]
full_url = "/".join(parts) full_url = "/".join(parts)

View File

@@ -51,7 +51,7 @@ from zerver.lib.test_helpers import HostRequestMock, get_user_messages, queries_
from zerver.lib.topic import MATCH_TOPIC, RESOLVED_TOPIC_PREFIX, TOPIC_NAME, messages_for_topic from zerver.lib.topic import MATCH_TOPIC, RESOLVED_TOPIC_PREFIX, TOPIC_NAME, messages_for_topic
from zerver.lib.types import UserDisplayRecipient from zerver.lib.types import UserDisplayRecipient
from zerver.lib.upload import create_attachment from zerver.lib.upload import create_attachment
from zerver.lib.url_encoding import near_message_url from zerver.lib.url_encoding import message_link_url
from zerver.lib.user_groups import get_recursive_membership_groups from zerver.lib.user_groups import get_recursive_membership_groups
from zerver.lib.user_topics import set_topic_visibility_policy from zerver.lib.user_topics import set_topic_visibility_policy
from zerver.models import ( from zerver.models import (
@@ -5637,8 +5637,8 @@ class MessageVisibilityTest(ZulipTestCase):
m.assert_called_once_with(realm) m.assert_called_once_with(realm)
class PersonalMessagesNearTest(ZulipTestCase): class PersonalMessagesTest(ZulipTestCase):
def test_near_pm_message_url(self) -> None: def test_pm_message_url(self) -> None:
realm = get_realm("zulip") realm = get_realm("zulip")
message = dict( message = dict(
type="personal", type="personal",
@@ -5648,8 +5648,15 @@ class PersonalMessagesNearTest(ZulipTestCase):
dict(id=80), dict(id=80),
], ],
) )
url = near_message_url( url = message_link_url(
realm=realm, realm=realm,
message=message, message=message,
) )
self.assertEqual(url, "http://zulip.testserver/#narrow/dm/77,80-pm/near/555") self.assertEqual(url, "http://zulip.testserver/#narrow/dm/77,80-pm/near/555")
url = message_link_url(
realm=realm,
message=message,
conversation_link=True,
)
self.assertEqual(url, "http://zulip.testserver/#narrow/dm/77,80-pm/with/555")

View File

@@ -20,7 +20,7 @@ from zerver.lib.test_classes import ZulipTestCase, get_topic_messages
from zerver.lib.test_helpers import queries_captured from zerver.lib.test_helpers import queries_captured
from zerver.lib.topic import RESOLVED_TOPIC_PREFIX from zerver.lib.topic import RESOLVED_TOPIC_PREFIX
from zerver.lib.types import UserGroupMembersData from zerver.lib.types import UserGroupMembersData
from zerver.lib.url_encoding import near_stream_message_url from zerver.lib.url_encoding import stream_message_url
from zerver.lib.user_groups import UserGroupMembershipDetails from zerver.lib.user_groups import UserGroupMembershipDetails
from zerver.models import Message, NamedUserGroup, Stream, UserMessage, UserProfile from zerver.models import Message, NamedUserGroup, Stream, UserMessage, UserProfile
from zerver.models.groups import SystemGroups from zerver.models.groups import SystemGroups
@@ -717,7 +717,7 @@ class MessageMoveStreamTest(ZulipTestCase):
"display_recipient": new_stream.name, "display_recipient": new_stream.name,
"topic": "test", "topic": "test",
} }
moved_message_link = near_stream_message_url(messages[1].realm, message) moved_message_link = stream_message_url(messages[1].realm, message)
self.assert_length(messages, 2) self.assert_length(messages, 2)
self.assertEqual(messages[0].id, msg_id_later) self.assertEqual(messages[0].id, msg_id_later)
self.assertEqual( self.assertEqual(
@@ -762,7 +762,7 @@ class MessageMoveStreamTest(ZulipTestCase):
"display_recipient": new_stream.name, "display_recipient": new_stream.name,
"topic": "test", "topic": "test",
} }
moved_message_link = near_stream_message_url(messages[2].realm, message) moved_message_link = stream_message_url(messages[2].realm, message)
self.assert_length(messages, 3) self.assert_length(messages, 3)
self.assertEqual(messages[0].id, msg_id_later) self.assertEqual(messages[0].id, msg_id_later)
self.assertEqual( self.assertEqual(
@@ -1597,7 +1597,7 @@ class MessageMoveStreamTest(ZulipTestCase):
"display_recipient": stream.name, "display_recipient": stream.name,
"topic": "edited", "topic": "edited",
} }
moved_message_link = near_stream_message_url(messages[1].realm, message) moved_message_link = stream_message_url(messages[1].realm, message)
self.assert_length(messages, 2) self.assert_length(messages, 2)
self.assertEqual(messages[0].content, "First") self.assertEqual(messages[0].content, "First")
self.assertEqual( self.assertEqual(
@@ -1644,7 +1644,7 @@ class MessageMoveStreamTest(ZulipTestCase):
"display_recipient": stream.name, "display_recipient": stream.name,
"topic": "edited", "topic": "edited",
} }
moved_message_link = near_stream_message_url(messages[0].realm, message) moved_message_link = stream_message_url(messages[0].realm, message)
self.assert_length(messages, 2) self.assert_length(messages, 2)
self.assertEqual(messages[0].content, "First") self.assertEqual(messages[0].content, "First")
self.assertEqual( self.assertEqual(

View File

@@ -17,7 +17,7 @@ from zerver.lib.outgoing_webhook import (
) )
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.topic import TOPIC_NAME from zerver.lib.topic import TOPIC_NAME
from zerver.lib.url_encoding import near_message_url from zerver.lib.url_encoding import message_link_url
from zerver.lib.users import add_service from zerver.lib.users import add_service
from zerver.models import Recipient, Service, UserProfile from zerver.models import Recipient, Service, UserProfile
from zerver.models.realms import get_realm from zerver.models.realms import get_realm
@@ -518,7 +518,7 @@ class TestOutgoingWebhookMessaging(ZulipTestCase):
"id": message_id, "id": message_id,
"type": "", "type": "",
} }
message_url = near_message_url(realm, message) message_url = message_link_url(realm, message)
last_message = self.get_last_message() last_message = self.get_last_message()
self.assertEqual( self.assertEqual(
@@ -602,7 +602,7 @@ class TestOutgoingWebhookMessaging(ZulipTestCase):
"id": sent_message_id, "id": sent_message_id,
"type": "stream", "type": "stream",
} }
message_url = near_message_url(realm, message_dict) message_url = message_link_url(realm, message_dict)
self.assertEqual( self.assertEqual(
last_message.content, last_message.content,
f"[A message]({message_url}) to your bot @_**{bot.full_name}** triggered an outgoing webhook.\n" f"[A message]({message_url}) to your bot @_**{bot.full_name}** triggered an outgoing webhook.\n"