Skip to content

Commit 6453d26

Browse files
committed
buttons: Add support for PM narrow links in MessageLinkButton.
Currently webapp supports several url types, such as: narrow/pm-with/1,2-pm narrow/pm-with/1,2-group narrow/pm-with/1,2,3-pm narrow/pm-with/1,2,3-group narrow/pm-with/1-user1 narrow/pm-with/1-bot-name (near parameter supported for each) This commit adds support for narrow using above mentioned url types. Helper method added: _decode_pm_data(). TypeDict added: DecodedPM(Literal, List) Tests added and amended.
1 parent 8263ab9 commit 6453d26

File tree

2 files changed

+155
-4
lines changed

2 files changed

+155
-4
lines changed

tests/ui_tools/test_buttons.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Any, Dict
22

33
import pytest
4+
from typing_extensions import Literal
45
from urwid import AttrMap, Overlay
56

67
from zulipterminal.config.keys import keys_for_command
@@ -406,6 +407,34 @@ def test__decode_stream_data(self, stream_data, expected_response):
406407

407408
assert return_value == expected_response
408409

410+
@pytest.mark.parametrize('pm_data, expected_response', [
411+
('1,2-pm', dict(recipient_ids=[1, 2], type=Literal['pm'])),
412+
('1,2-group', dict(recipient_ids=[1, 2], type=Literal['pm'])),
413+
('1,2,3-pm', dict(recipient_ids=[1, 2, 3],
414+
type=Literal['group'])),
415+
('1,2,3-group', dict(recipient_ids=[1, 2, 3],
416+
type=Literal['group'])),
417+
('1-user1', dict(recipient_ids=[1], type=Literal['pm'])),
418+
('1-user2', dict(recipient_ids=[1], type=Literal['pm'])),
419+
('1-bot-name', dict(recipient_ids=[1], type=Literal['pm'])),
420+
('1-bot;name', dict(recipient_ids=[1], type=Literal['pm'])),
421+
],
422+
ids=[
423+
'pm_with_two_recipients',
424+
'group_pm_with_two_recipients',
425+
'pm_with_more_than_two_recipients',
426+
'group_pm_with_more_than_two_recipients',
427+
'pm_exposed_format_1_ordinary',
428+
'pm_exposed_format_1_ambigous',
429+
'pm_with_bot_exposed_format_1_ordinary',
430+
'pm_with_bot_exposed_format_1_ambigous',
431+
]
432+
)
433+
def test__decode_pm_data(self, pm_data, expected_response):
434+
return_value = MessageLinkButton._decode_pm_data(pm_data)
435+
436+
assert return_value == expected_response
437+
409438
@pytest.mark.parametrize('message_id, expected_return_value', [
410439
('1', 1),
411440
('foo', None),
@@ -431,6 +460,27 @@ def test__decode_message_id(self, message_id, expected_return_value):
431460
(SERVER_URL + '/#narrow/stream/1-Stream-1/topic/foo/near/1',
432461
{'narrow': 'stream:topic:near', 'topic_name': 'foo', 'message_id': 1,
433462
'stream': {'stream_id': 1, 'stream_name': None}}),
463+
(SERVER_URL + '/#narrow/pm-with/1,2-pm',
464+
{'narrow': 'pm-with',
465+
'pm_with': {'type': Literal['pm'], 'recipient_ids': [1, 2]}}),
466+
(SERVER_URL + '/#narrow/pm-with/1,2-group',
467+
{'narrow': 'pm-with',
468+
'pm_with': {'type': Literal['pm'], 'recipient_ids': [1, 2]}}),
469+
(SERVER_URL + '/#narrow/pm-with/1-user1',
470+
{'narrow': 'pm-with',
471+
'pm_with': {'type': Literal['pm'], 'recipient_ids': [1]}}),
472+
(SERVER_URL + '/#narrow/pm-with/1-bot-name',
473+
{'narrow': 'pm-with',
474+
'pm_with': {'type': Literal['pm'], 'recipient_ids': [1]}}),
475+
(SERVER_URL + '/#narrow/pm-with/1,2-pm/near/1',
476+
{'narrow': 'pm-with:near', 'message_id': 1,
477+
'pm_with': {'type': Literal['pm'], 'recipient_ids': [1, 2]}}),
478+
(SERVER_URL + '/#narrow/pm-with/1,2,3-pm',
479+
{'narrow': 'pm-with',
480+
'pm_with': {'type': Literal['group'], 'recipient_ids': [1, 2, 3]}}),
481+
(SERVER_URL + '/#narrow/pm-with/1,2,3-group',
482+
{'narrow': 'pm-with',
483+
'pm_with': {'type': Literal['group'], 'recipient_ids': [1, 2, 3]}}),
434484
(SERVER_URL + '/#narrow/foo',
435485
{}),
436486
(SERVER_URL + '/#narrow/stream/',
@@ -448,6 +498,13 @@ def test__decode_message_id(self, message_id, expected_return_value):
448498
'topic_narrow_link',
449499
'stream_near_narrow_link',
450500
'topic_near_narrow_link',
501+
'pm_with_two_recipients_narrow_link',
502+
'group_pm_with_two_recipients_narrow_link',
503+
'pm_exposed_format_1_narrow_link',
504+
'pm_with_bot_exposed_format_1_narrow_link',
505+
'common_pm_near_narrow_link',
506+
'pm_with_more_than_two_recipients_narrow_link',
507+
'group_pm_with_more_than_two_recipients_narrow_link',
451508
'invalid_narrow_link_1',
452509
'invalid_narrow_link_2',
453510
'invalid_narrow_link_3',
@@ -630,44 +687,83 @@ def test__validate_and_patch_stream_data(self, stream_dict, parsed_link,
630687
'parsed_link',
631688
'narrow_to_stream_called',
632689
'narrow_to_topic_called',
690+
'narrow_to_user_called',
633691
],
634692
[
635693
({'narrow': 'stream',
636694
'stream': {'stream_id': 1, 'stream_name': 'Stream 1'}},
637695
True,
696+
False,
638697
False),
639698
({'narrow': 'stream:topic', 'topic_name': 'Foo',
640699
'stream': {'stream_id': 1, 'stream_name': 'Stream 1'}},
641700
False,
642-
True),
701+
True,
702+
False),
643703
({'narrow': 'stream:near', 'message_id': 1,
644704
'stream': {'stream_id': 1, 'stream_name': 'Stream 1'}},
645705
True,
706+
False,
646707
False),
647708
({'narrow': 'stream:topic:near', 'topic_name': 'Foo',
648709
'message_id': 1,
649710
'stream': {'stream_id': 1, 'stream_name': 'Stream 1'}},
650711
False,
712+
True,
713+
False),
714+
({'narrow': 'pm-with',
715+
'pm_with': {'type': Literal['pm'], 'recipient_ids': [1, 2]}},
716+
False,
717+
False,
718+
True),
719+
({'narrow': 'pm-with',
720+
'pm_with': {'type': Literal['group'],
721+
'recipient_ids': [1, 2, 3]}},
722+
False,
723+
False,
724+
True),
725+
({'narrow': 'pm-with:near', 'message_id': 1,
726+
'pm_with': {'type': Literal['pm'], 'recipient_ids': [1, 2]}},
727+
False,
728+
False,
729+
True),
730+
({'narrow': 'pm-with:near', 'message_id': 1,
731+
'pm_with': {'type': Literal['group'],
732+
'recipient_ids': [1, 2, 3]}},
733+
False,
734+
False,
651735
True),
652736
],
653737
ids=[
654738
'stream_narrow',
655739
'topic_narrow',
656740
'stream_near_narrow',
657741
'topic_near_narrow',
742+
'pm_narrow',
743+
'group_pm_narrow',
744+
'pm_near_narrow',
745+
'group_pm_near_narrow',
658746
]
659747
)
660748
def test__switch_narrow_to(self, parsed_link, narrow_to_stream_called,
661-
narrow_to_topic_called,
749+
narrow_to_topic_called, narrow_to_user_called,
662750
):
663751
mocked_button = self.message_link_button()
752+
# For PM narrow switch
753+
mocked_button.model.user_id_email_dict = {
754+
755+
756+
757+
}
664758

665759
mocked_button._switch_narrow_to(parsed_link)
666760

667761
assert (mocked_button.controller.narrow_to_stream.called
668762
== narrow_to_stream_called)
669763
assert (mocked_button.controller.narrow_to_topic.called
670764
== narrow_to_topic_called)
765+
assert (mocked_button.controller.narrow_to_user.called
766+
== narrow_to_user_called)
671767

672768
@pytest.mark.parametrize(['error', 'set_footer_text_called',
673769
'_switch_narrow_to_called',

zulipterminal/ui_tools/buttons.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import re
22
from functools import partial
3-
from typing import Any, Callable, Dict, Optional, Tuple, Union, cast
3+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast
44
from urllib.parse import urljoin, urlparse
55

66
import urwid
7-
from typing_extensions import TypedDict
7+
from typing_extensions import Literal, TypedDict
88

99
from zulipterminal.config.keys import is_command_key, primary_key_for_command
1010
from zulipterminal.config.symbols import (
@@ -306,9 +306,15 @@ class DecodedStream(TypedDict):
306306
stream_name: Optional[str]
307307

308308

309+
class DecodedPM(TypedDict):
310+
type: Literal['pm', 'group']
311+
recipient_ids: List[int]
312+
313+
309314
class ParsedNarrowLink(TypedDict, total=False):
310315
narrow: str
311316
stream: DecodedStream
317+
pm_with: DecodedPM
312318
topic_name: str
313319
message_id: Optional[int]
314320

@@ -359,6 +365,22 @@ def _decode_stream_data(encoded_stream_data: str) -> DecodedStream:
359365
stream_name = hash_util_decode(encoded_stream_data)
360366
return DecodedStream(stream_id=None, stream_name=stream_name)
361367

368+
@staticmethod
369+
def _decode_pm_data(encoded_pm_data: str) -> DecodedPM:
370+
"""
371+
Returns a dict with PM type and IDs of PM recipients.
372+
"""
373+
# Recipient ids (seperated by `,`) are of prime interest.
374+
recipient_ids, *_ = encoded_pm_data.split('-')
375+
recipient_ids_list = list(map(int, recipient_ids.split(',')))
376+
377+
# Currently webapp uses `pm` and `group` suffix interchangeably.
378+
# Treat more-than-2 pms to group pms to avoid confusion.
379+
pm_type = 'pm' if len(recipient_ids_list) < 3 else 'group'
380+
381+
return DecodedPM(type=Literal[pm_type],
382+
recipient_ids=recipient_ids_list)
383+
362384
@staticmethod
363385
def _decode_message_id(message_id: str) -> Optional[int]:
364386
"""
@@ -384,6 +406,12 @@ def _parse_narrow_link(cls, link: str) -> ParsedNarrowLink:
384406
# {encoded.20topic.20name}
385407
# d. narrow/stream/[{stream_id}-]{stream-name}/topic/
386408
# {encoded.20topic.20name}/near/{message_id}
409+
# e. narrow/pm-with/[{recipient_ids},]-{pm-type}
410+
# f. narrow/pm-with/[{recipient_ids},]-{pm-type}/near/{message_id}
411+
# g. narrow/pm-with/{user_id}-user{user_id}
412+
# h. narrow/pm-with/{user_id}-user{user_id}/near/{message_id}
413+
# i. narrow/pm-with/{bot_id}-{bot-name}
414+
# j. narrow/pm-with/{bot_id}-{bot-name}/near/{message_id}
387415
fragments = urlparse(link.rstrip('/')).fragment.split('/')
388416
len_fragments = len(fragments)
389417
parsed_link = ParsedNarrowLink()
@@ -414,6 +442,17 @@ def _parse_narrow_link(cls, link: str) -> ParsedNarrowLink:
414442
parsed_link = dict(narrow='stream:topic:near', stream=stream_data,
415443
topic_name=topic_name, message_id=message_id)
416444

445+
elif (len_fragments == 5 and fragments[1] == 'pm-with'
446+
and fragments[3] == 'near'):
447+
pm_data = cls._decode_pm_data(fragments[2])
448+
message_id = cls._decode_message_id(fragments[4])
449+
parsed_link = dict(narrow='pm-with:near', pm_with=pm_data,
450+
message_id=message_id)
451+
452+
elif len_fragments == 3 and fragments[1] == 'pm-with':
453+
pm_data = cls._decode_pm_data(fragments[2])
454+
parsed_link = dict(narrow='pm-with', pm_with=pm_data)
455+
417456
return parsed_link
418457

419458
def _validate_and_patch_stream_data(self,
@@ -503,6 +542,22 @@ def _switch_narrow_to(self, parsed_link: ParsedNarrowLink) -> None:
503542
topic_name=parsed_link['topic_name'],
504543
contextual_message_id=parsed_link['message_id'],
505544
)
545+
elif 'pm-with:near' == narrow:
546+
emails = list(
547+
self.model.user_id_email_dict.get(user_id)
548+
for user_id in parsed_link['pm_with']['recipient_ids']
549+
)
550+
self.controller.narrow_to_user(
551+
recipient_emails=emails,
552+
contextual_message_id=parsed_link['message_id'],
553+
)
554+
555+
elif 'pm-with' == narrow:
556+
emails = list(
557+
self.model.user_id_email_dict.get(user_id)
558+
for user_id in parsed_link['pm_with']['recipient_ids']
559+
)
560+
self.controller.narrow_to_user(recipient_emails=emails)
506561

507562
def handle_narrow_link(self) -> None:
508563
"""

0 commit comments

Comments
 (0)