urls: Generate narrow links in backend with "channel" operator.

This commit is contained in:
Lauryn Menard 2024-10-04 16:54:16 +02:00 committed by Tim Abbott
parent 240c4d85ae
commit 70ab893d34
12 changed files with 53 additions and 53 deletions

View File

@ -2008,7 +2008,7 @@ class StreamPattern(CompiledInlineProcessor):
# provide more clarity to API clients.
# Also do the same for StreamTopicPattern.
stream_url = encode_stream(stream_id, name)
el.set("href", f"/#narrow/stream/{stream_url}")
el.set("href", f"/#narrow/channel/{stream_url}")
text = f"#{name}"
el.text = markdown.util.AtomicString(text)
return el, m.start(), m.end()
@ -2037,7 +2037,7 @@ class StreamTopicPattern(CompiledInlineProcessor):
el.set("data-stream-id", str(stream_id))
stream_url = encode_stream(stream_id, stream_name)
topic_url = hash_util_encode(topic_name)
link = f"/#narrow/stream/{stream_url}/topic/{topic_url}"
link = f"/#narrow/channel/{stream_url}/topic/{topic_url}"
el.set("href", link)
text = f"#{stream_name} > {topic_name}"
el.text = markdown.util.AtomicString(text)

View File

@ -11,7 +11,7 @@ def is_same_server_message_link(url: str) -> bool:
if hostname not in {None, settings.EXTERNAL_HOST_WITHOUT_PORT}:
return False
# A message link always has category `narrow`, section `stream`
# A message link always has category `narrow`, section `channel`
# or `dm`, and ends with `/near/<message_id>`, where <message_id>
# is a sequence of digits. The URL fragment of a message link has
# at least 5 parts. e.g. '#narrow/dm/9,15-dm/near/43'
@ -23,4 +23,4 @@ def is_same_server_message_link(url: str) -> bool:
section = fragment_parts[1]
ends_with_near_message_id = fragment_parts[-2] == "near" and fragment_parts[-1].isdigit()
return category == "narrow" and section in {"stream", "dm"} and ends_with_near_message_id
return category == "narrow" and section in {"channel", "dm"} and ends_with_near_message_id

View File

@ -39,12 +39,12 @@ def direct_message_group_narrow_url(
def stream_narrow_url(realm: Realm, stream: Stream) -> str:
base_url = f"{realm.url}/#narrow/stream/"
base_url = f"{realm.url}/#narrow/channel/"
return base_url + encode_stream(stream.id, stream.name)
def topic_narrow_url(*, realm: Realm, stream: Stream, topic_name: str) -> str:
base_url = f"{realm.url}/#narrow/stream/"
base_url = f"{realm.url}/#narrow/channel/"
return f"{base_url}{encode_stream(stream.id, stream.name)}/topic/{hash_util_encode(topic_name)}"
@ -74,7 +74,7 @@ def near_stream_message_url(realm: Realm, message: dict[str, Any]) -> str:
parts = [
realm.url,
"#narrow",
"stream",
"channel",
encoded_stream,
"topic",
encoded_topic_name,

View File

@ -488,9 +488,9 @@
},
{
"name": "fragment_link",
"input": "[foo](http://zulip.testserver/#narrow/stream/1-Denmark)",
"expected_output": "<p><a href=\"#narrow/stream/1-Denmark\">foo</a></p>",
"marked_expected_output": "<p><a href=\"http://zulip.testserver/#narrow/stream/1-Denmark\">foo</a></p>"
"input": "[foo](http://zulip.testserver/#narrow/channel/1-Denmark)",
"expected_output": "<p><a href=\"#narrow/channel/1-Denmark\">foo</a></p>",
"marked_expected_output": "<p><a href=\"http://zulip.testserver/#narrow/channel/1-Denmark\">foo</a></p>"
},
{
"name": "not_fragment_link",
@ -1050,9 +1050,9 @@
},
{
"name": "normal_quote_and_reply",
"input": "@_**Zoe|7** [said](http://zulip.testserver/#narrow/stream/13-social/topic/party/near/103):\n```quote\nHow are you?\n```\n\nGreat",
"expected_output": "<p><span class=\"user-mention silent\" data-user-id=\"7\">Zoe</span> <a href=\"#narrow/stream/13-social/topic/party/near/103\">said</a>:</p>\n<blockquote>\n<p>How are you?</p>\n</blockquote>\n<p>Great</p>",
"marked_expected_output": "<p><span class=\"user-mention silent\" data-user-id=\"7\">Zoe</span> <a href=\"http://zulip.testserver/#narrow/stream/13-social/topic/party/near/103\">said</a>:</p>\n<blockquote>\n<p>How are you?</p>\n</blockquote>\n<p>Great</p>",
"input": "@_**Zoe|7** [said](http://zulip.testserver/#narrow/channel/13-social/topic/party/near/103):\n```quote\nHow are you?\n```\n\nGreat",
"expected_output": "<p><span class=\"user-mention silent\" data-user-id=\"7\">Zoe</span> <a href=\"#narrow/channel/13-social/topic/party/near/103\">said</a>:</p>\n<blockquote>\n<p>How are you?</p>\n</blockquote>\n<p>Great</p>",
"marked_expected_output": "<p><span class=\"user-mention silent\" data-user-id=\"7\">Zoe</span> <a href=\"http://zulip.testserver/#narrow/channel/13-social/topic/party/near/103\">said</a>:</p>\n<blockquote>\n<p>How are you?</p>\n</blockquote>\n<p>Great</p>",
"text_content": "[…]\nGreat"
},
{
@ -1316,8 +1316,8 @@
""
],
[
"http://zulip.testserver/#narrow/stream/1-Denmark",
"<p><a href=\"#narrow/stream/1-Denmark\">http://zulip.testserver/#narrow/stream/1-Denmark</a></p>",
"http://zulip.testserver/#narrow/channel/1-Denmark",
"<p><a href=\"#narrow/channel/1-Denmark\">http://zulip.testserver/#narrow/channel/1-Denmark</a></p>",
""
],
[

View File

@ -11,17 +11,17 @@
},
{
"name": "stream_message_link",
"message_link": "#narrow/stream/8-design/topic/desktop/near/82",
"message_link": "#narrow/channel/8-design/topic/desktop/near/82",
"expected_output": true
},
{
"name": "stream_link",
"message_link": "#narrow/stream/8-design",
"message_link": "#narrow/channel/8-design",
"expected_output": false
},
{
"name": "topic_link",
"message_link": "#narrow/stream/8-design/topic/desktop",
"message_link": "#narrow/channel/8-design/topic/desktop",
"expected_output": false
},
{

View File

@ -4747,9 +4747,9 @@ class GoogleAuthBackendTest(SocialAuthBase):
self.assertEqual(res.status_code, 302)
self.assertEqual(res["Location"], "http://zulip.testserver/user_uploads/path_to_image")
res = test_redirect_to_next_url("/#narrow/stream/7-test-here")
res = test_redirect_to_next_url("/#narrow/channel/7-test-here")
self.assertEqual(res.status_code, 302)
self.assertEqual(res["Location"], "http://zulip.testserver/#narrow/stream/7-test-here")
self.assertEqual(res["Location"], "http://zulip.testserver/#narrow/channel/7-test-here")
def test_log_into_subdomain_when_token_is_malformed(self) -> None:
data: ExternalAuthDataDict = {
@ -5600,7 +5600,7 @@ class TestDevAuthBackend(ZulipTestCase):
# to the backend. Rather we depend upon the browser's behaviour of persisting
# hash anchors in between redirect requests. See below stackoverflow conversation
# https://stackoverflow.com/questions/5283395/url-hash-is-persisting-between-redirects
res = do_local_login("/accounts/login/local/?next=#narrow/stream/7-test-here")
res = do_local_login("/accounts/login/local/?next=#narrow/channel/7-test-here")
self.assertEqual(res.status_code, 302)
self.assertEqual(res["Location"], "http://zulip.testserver")

View File

@ -509,7 +509,7 @@ class TestDigestEmailMessages(ZulipTestCase):
realm, recently_created_streams, can_access_public=True
)
self.assertEqual(stream_count, 1)
expected_html = f"<a href='http://zulip.testserver/#narrow/stream/{stream.id}-New-stream'>New stream</a>"
expected_html = f"<a href='http://zulip.testserver/#narrow/channel/{stream.id}-New-stream'>New stream</a>"
self.assertEqual(stream_info["html"][0], expected_html)
# guests don't see our stream

View File

@ -588,10 +588,10 @@ class MarkdownLinkTest(ZulipTestCase):
sender_user_profile = self.example_user("othello")
message = Message(sender=sender_user_profile, sending_client=get_client("test"))
msg = "http://zulip.testserver/#narrow/stream/999-hello"
msg = "http://zulip.testserver/#narrow/channel/999-hello"
self.assertEqual(
markdown_convert(msg, message_realm=realm, message=message).rendered_content,
'<p><a href="#narrow/stream/999-hello">http://zulip.testserver/#narrow/stream/999-hello</a></p>',
'<p><a href="#narrow/channel/999-hello">http://zulip.testserver/#narrow/channel/999-hello</a></p>',
)
msg = f"http://zulip.testserver/user_uploads/{realm.id}/ff/file.txt"
@ -622,10 +622,10 @@ class MarkdownLinkTest(ZulipTestCase):
sender_user_profile = self.example_user("othello")
message = Message(sender=sender_user_profile, sending_client=get_client("test"))
msg = "[hello](http://zulip.testserver/#narrow/stream/999-hello)"
msg = "[hello](http://zulip.testserver/#narrow/channel/999-hello)"
self.assertEqual(
markdown_convert(msg, message_realm=realm, message=message).rendered_content,
'<p><a href="#narrow/stream/999-hello">hello</a></p>',
'<p><a href="#narrow/channel/999-hello">hello</a></p>',
)
msg = f"[hello](http://zulip.testserver/user_uploads/{realm.id}/ff/file.txt)"
@ -2984,7 +2984,7 @@ class MarkdownStreamMentionTests(ZulipTestCase):
content = "#**Denmark**"
self.assertEqual(
render_message_markdown(msg, content).rendered_content,
f'<p><a class="stream" data-stream-id="{denmark.id}" href="/#narrow/stream/{denmark.id}-Denmark">#{denmark.name}</a></p>',
f'<p><a class="stream" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark">#{denmark.name}</a></p>',
)
def test_invalid_stream_followed_by_valid_mention(self) -> None:
@ -2998,7 +2998,7 @@ class MarkdownStreamMentionTests(ZulipTestCase):
content = "#**Invalid** and #**Denmark**"
self.assertEqual(
render_message_markdown(msg, content).rendered_content,
f'<p>#<strong>Invalid</strong> and <a class="stream" data-stream-id="{denmark.id}" href="/#narrow/stream/{denmark.id}-Denmark">#{denmark.name}</a></p>',
f'<p>#<strong>Invalid</strong> and <a class="stream" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark">#{denmark.name}</a></p>',
)
def test_stream_multiple(self) -> None:
@ -3017,10 +3017,10 @@ class MarkdownStreamMentionTests(ZulipTestCase):
"<p>Look to "
'<a class="stream" '
f'data-stream-id="{denmark.id}" '
f'href="/#narrow/stream/{denmark.id}-Denmark">#{denmark.name}</a> and '
f'href="/#narrow/channel/{denmark.id}-Denmark">#{denmark.name}</a> and '
'<a class="stream" '
f'data-stream-id="{scotland.id}" '
f'href="/#narrow/stream/{scotland.id}-Scotland">#{scotland.name}</a>, '
f'href="/#narrow/channel/{scotland.id}-Scotland">#{scotland.name}</a>, '
"there something</p>",
)
@ -3032,7 +3032,7 @@ class MarkdownStreamMentionTests(ZulipTestCase):
content = "#**CaseSens**"
self.assertEqual(
render_message_markdown(msg, content).rendered_content,
f'<p><a class="stream" data-stream-id="{case_sens.id}" href="/#narrow/stream/{case_sens.id}-{case_sens.name}">#{case_sens.name}</a></p>',
f'<p><a class="stream" data-stream-id="{case_sens.id}" href="/#narrow/channel/{case_sens.id}-{case_sens.name}">#{case_sens.name}</a></p>',
)
def test_stream_case_sensitivity_nonmatching(self) -> None:
@ -3060,7 +3060,7 @@ class MarkdownStreamMentionTests(ZulipTestCase):
content = "#**Denmark>some topic**"
self.assertEqual(
render_message_markdown(msg, content).rendered_content,
f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/stream/{denmark.id}-Denmark/topic/some.20topic">#{denmark.name} &gt; some topic</a></p>',
f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/some.20topic">#{denmark.name} &gt; some topic</a></p>',
)
def test_topic_atomic_string(self) -> None:
@ -3082,7 +3082,7 @@ class MarkdownStreamMentionTests(ZulipTestCase):
content = "#**Denmark>#1234**"
self.assertEqual(
render_message_markdown(msg, content).rendered_content,
f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/stream/{denmark.id}-Denmark/topic/.231234">#{denmark.name} &gt; #1234</a></p>',
f'<p><a class="stream-topic" data-stream-id="{denmark.id}" href="/#narrow/channel/{denmark.id}-Denmark/topic/.231234">#{denmark.name} &gt; #1234</a></p>',
)
def test_topic_multiple(self) -> None:
@ -3099,11 +3099,11 @@ class MarkdownStreamMentionTests(ZulipTestCase):
render_message_markdown(msg, content).rendered_content,
"<p>This has two links: "
f'<a class="stream-topic" data-stream-id="{denmark.id}" '
f'href="/#narrow/stream/{denmark.id}-{denmark.name}/topic/some.20topic">'
f'href="/#narrow/channel/{denmark.id}-{denmark.name}/topic/some.20topic">'
f"#{denmark.name} &gt; some topic</a>"
" and "
f'<a class="stream-topic" data-stream-id="{scotland.id}" '
f'href="/#narrow/stream/{scotland.id}-{scotland.name}/topic/other.20topic">'
f'href="/#narrow/channel/{scotland.id}-{scotland.name}/topic/other.20topic">'
f"#{scotland.name} &gt; other topic</a>"
".</p>",
)
@ -3125,7 +3125,7 @@ class MarkdownStreamMentionTests(ZulipTestCase):
msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
content = "#**привет**"
quoted_name = ".D0.BF.D1.80.D0.B8.D0.B2.D0.B5.D1.82"
href = f"/#narrow/stream/{uni.id}-{quoted_name}"
href = f"/#narrow/channel/{uni.id}-{quoted_name}"
self.assertEqual(
render_message_markdown(msg, content).rendered_content,
f'<p><a class="stream" data-stream-id="{uni.id}" href="{href}">#{uni.name}</a></p>',
@ -3148,7 +3148,7 @@ class MarkdownStreamMentionTests(ZulipTestCase):
stream = self.make_stream(stream_name="Stream #1234", realm=realm)
msg = Message(sender=sender_user_profile, sending_client=get_client("test"), realm=realm)
content = "#**Stream #1234**"
href = f"/#narrow/stream/{stream.id}-Stream-.231234"
href = f"/#narrow/channel/{stream.id}-Stream-.231234"
self.assertEqual(
render_message_markdown(msg, content).rendered_content,
f'<p><a class="stream" data-stream-id="{stream.id}" href="{href}">#{stream.name}</a></p>',
@ -3290,7 +3290,7 @@ class MarkdownApiTests(ZulipTestCase):
stream_id = get_stream("Denmark", get_realm("zulip")).id
self.assertEqual(
response_dict["rendered"],
f'<p>This mentions <a class="stream" data-stream-id="{stream_id}" href="/#narrow/stream/{stream_id}-Denmark">#Denmark</a> and <span class="user-mention" data-user-id="{user_id}">@King Hamlet</span>.</p>',
f'<p>This mentions <a class="stream" data-stream-id="{stream_id}" href="/#narrow/channel/{stream_id}-Denmark">#Denmark</a> and <span class="user-mention" data-user-id="{user_id}">@King Hamlet</span>.</p>',
)

View File

@ -1220,7 +1220,7 @@ class TestMessageNotificationEmails(ZulipTestCase):
"Come and join us in #**Verona**.",
)
stream_id = get_stream("Verona", get_realm("zulip")).id
href = f"http://zulip.testserver/#narrow/stream/{stream_id}-Verona"
href = f"http://zulip.testserver/#narrow/channel/{stream_id}-Verona"
verify_body_include = [
f'<a class="stream" href="{href}" data-stream-id="{stream_id}">#Verona</a'
]
@ -1493,13 +1493,13 @@ class TestMessageNotificationEmails(ZulipTestCase):
# A narrow URL which begins with a '#'.
test_data = (
'<p><a href="#narrow/stream/test/topic/test.20topic/near/142"'
' title="#narrow/stream/test/topic/test.20topic/near/142">Conversation</a></p>'
'<p><a href="#narrow/channel/test/topic/test.20topic/near/142"'
' title="#narrow/channel/test/topic/test.20topic/near/142">Conversation</a></p>'
)
actual_output = convert(test_data)
expected_output = (
'<div><p><a href="http://example.com/#narrow/stream/test/topic/test.20topic/near/142"'
' title="http://example.com/#narrow/stream/test/topic/test.20topic/near/142">Conversation</a></p></div>'
'<div><p><a href="http://example.com/#narrow/channel/test/topic/test.20topic/near/142"'
' title="http://example.com/#narrow/channel/test/topic/test.20topic/near/142">Conversation</a></p></div>'
)
self.assertEqual(actual_output, expected_output)

View File

@ -132,13 +132,13 @@ class DoRestCallTests(ZulipTestCase):
self.assertEqual(
m.output,
[
f'WARNING:root:Message http://zulip.testserver/#narrow/stream/999-Verona/topic/Foo/near/ triggered an outgoing webhook, returning status code 500.\n Content of response (in quotes): "{final_response.text}"'
f'WARNING:root:Message http://zulip.testserver/#narrow/channel/999-Verona/topic/Foo/near/ triggered an outgoing webhook, returning status code 500.\n Content of response (in quotes): "{final_response.text}"'
],
)
bot_owner_notification = self.get_last_message()
self.assertEqual(
bot_owner_notification.content,
"""[A message](http://zulip.testserver/#narrow/stream/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
"""[A message](http://zulip.testserver/#narrow/channel/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
The webhook got a response with status code *500*.""",
)
@ -196,7 +196,7 @@ The webhook got a response with status code *500*.""",
self.assertEqual(
m.output,
[
f'WARNING:root:Message http://zulip.testserver/#narrow/stream/999-Verona/topic/Foo/near/ triggered an outgoing webhook, returning status code 400.\n Content of response (in quotes): "{final_response.text}"'
f'WARNING:root:Message http://zulip.testserver/#narrow/channel/999-Verona/topic/Foo/near/ triggered an outgoing webhook, returning status code 400.\n Content of response (in quotes): "{final_response.text}"'
],
)
@ -205,7 +205,7 @@ The webhook got a response with status code *500*.""",
bot_owner_notification = self.get_last_message()
self.assertEqual(
bot_owner_notification.content,
"""[A message](http://zulip.testserver/#narrow/stream/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
"""[A message](http://zulip.testserver/#narrow/channel/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
The webhook got a response with status code *400*.""",
)
@ -291,7 +291,7 @@ The webhook got a response with status code *400*.""",
bot_owner_notification = self.get_last_message()
self.assertEqual(
bot_owner_notification.content,
"""[A message](http://zulip.testserver/#narrow/stream/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
"""[A message](http://zulip.testserver/#narrow/channel/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
When trying to send a request to the webhook service, an exception of type RequestException occurred:
```
I'm a generic exception :(
@ -322,7 +322,7 @@ I'm a generic exception :(
bot_owner_notification = self.get_last_message()
self.assertEqual(
bot_owner_notification.content,
"""[A message](http://zulip.testserver/#narrow/stream/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
"""[A message](http://zulip.testserver/#narrow/channel/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
The outgoing webhook server attempted to send a message in Zulip, but that request resulted in the following error:
> Widgets: API programmer sent invalid JSON content\nThe response contains the following payload:\n```\n'{"content": "whatever", "widget_content": "test"}'\n```""",
)
@ -349,7 +349,7 @@ The outgoing webhook server attempted to send a message in Zulip, but that reque
bot_owner_notification = self.get_last_message()
self.assertEqual(
bot_owner_notification.content,
"""[A message](http://zulip.testserver/#narrow/stream/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
"""[A message](http://zulip.testserver/#narrow/channel/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
The outgoing webhook server attempted to send a message in Zulip, but that request resulted in the following error:
> Invalid response format\nThe response contains the following payload:\n```\n'true'\n```""",
)
@ -378,7 +378,7 @@ The outgoing webhook server attempted to send a message in Zulip, but that reque
bot_owner_notification = self.get_last_message()
self.assertEqual(
bot_owner_notification.content,
"""[A message](http://zulip.testserver/#narrow/stream/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
"""[A message](http://zulip.testserver/#narrow/channel/999-Verona/topic/Foo/near/) to your bot @_**Outgoing Webhook** triggered an outgoing webhook.
The outgoing webhook server attempted to send a message in Zulip, but that request resulted in the following error:
> Invalid JSON in response\nThe response contains the following payload:\n```\n"this isn't valid json"\n```""",
)

View File

@ -5124,7 +5124,7 @@ class TestPushNotificationsContent(ZulipTestCase):
},
{
"name": "stream_names",
"rendered_content": f'<p>Testing stream names <a class="stream" data-stream-id="{stream.id}" href="/#narrow/stream/Verona">#Verona</a>.</p>',
"rendered_content": f'<p>Testing stream names <a class="stream" data-stream-id="{stream.id}" href="/#narrow/channel/Verona">#Verona</a>.</p>',
"expected_output": "Testing stream names #Verona.",
},
]

View File

@ -4352,7 +4352,7 @@ class SubscriptionAPITest(ZulipTestCase):
self.assertEqual(msg.recipient.type_id, new_stream_announcements_stream.id)
self.assertEqual(msg.sender_id, self.notification_bot(realm).id)
stream_id = Stream.objects.latest("id").id
expected_rendered_msg = f'<p><span class="user-mention silent" data-user-id="{user.id}">{user.full_name}</span> created a new channel <a class="stream" data-stream-id="{stream_id}" href="/#narrow/stream/{stream_id}-{invite_streams[0]}">#{invite_streams[0]}</a>.</p>'
expected_rendered_msg = f'<p><span class="user-mention silent" data-user-id="{user.id}">{user.full_name}</span> created a new channel <a class="stream" data-stream-id="{stream_id}" href="/#narrow/channel/{stream_id}-{invite_streams[0]}">#{invite_streams[0]}</a>.</p>'
self.assertEqual(msg.rendered_content, expected_rendered_msg)
def test_successful_subscriptions_notifies_with_escaping(self) -> None: