Skip to content

Commit 7bd95e0

Browse files
committed
messages: WIP poll widget.
1 parent 440a3fa commit 7bd95e0

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

tests/ui_tools/test_messages.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from collections import OrderedDict, defaultdict
22
from datetime import date
3+
from typing import Any, Dict, List
34

45
import pytest
56
import pytz
@@ -1429,6 +1430,72 @@ def test_keypress_EDIT_MESSAGE(
14291430
else:
14301431
report_error.assert_called_once_with([expect_footer_text[message_type]])
14311432

1433+
@pytest.mark.parametrize(
1434+
"submessages, expected_question, expected_voters_for_options",
1435+
[
1436+
(
1437+
[
1438+
{
1439+
"id": 10981,
1440+
"message_id": 1813721,
1441+
"sender_id": 27294,
1442+
"msg_type": "widget",
1443+
"content": '{"widget_type": "poll", "extra_data": {"question": "Can you view polls on Zulip Terminal?", "options": ["Yes", "No"]}}', # noqa: E501
1444+
},
1445+
{
1446+
"id": 10982,
1447+
"message_id": 1813721,
1448+
"sender_id": 27294,
1449+
"msg_type": "widget",
1450+
"content": '{"type":"vote","key":"canned,0","vote":1}',
1451+
},
1452+
],
1453+
"Can you view polls on Zulip Terminal?",
1454+
{"Yes": [27294], "No": []},
1455+
),
1456+
(
1457+
[
1458+
{
1459+
"id": 10983,
1460+
"message_id": 1813729,
1461+
"sender_id": 27294,
1462+
"msg_type": "widget",
1463+
"content": '{"widget_type": "poll", "extra_data": {"question": "", "options": ["Option 1", "Option 2"]}}', # noqa: E501
1464+
}
1465+
],
1466+
"",
1467+
{"Option 1": [27294], "Option 2": [27294]},
1468+
),
1469+
(
1470+
[
1471+
{
1472+
"id": 10996,
1473+
"message_id": 1813730,
1474+
"sender_id": 27294,
1475+
"msg_type": "widget",
1476+
"content": '{"widget_type": "poll", "extra_data": {"question": "How is the weather today?", "options": []}}', # noqa: E501
1477+
}
1478+
],
1479+
"How is the weather today?",
1480+
{},
1481+
),
1482+
],
1483+
ids=[
1484+
"poll_with_votes",
1485+
"poll_without_question",
1486+
"poll_without_options",
1487+
],
1488+
)
1489+
def test_process_poll_data(
1490+
self,
1491+
submessages: Any,
1492+
expected_question: str,
1493+
expected_voters_for_options: Dict[str, List[int]],
1494+
):
1495+
question, voters_for_options = MessageBox.process_poll_data(submessages)
1496+
assert question == expected_question
1497+
assert voters_for_options == expected_voters_for_options
1498+
14321499
@pytest.mark.parametrize(
14331500
"raw_html, expected_content",
14341501
[

zulipterminal/ui_tools/messages.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
UI to render a Zulip message for display, and respond contextually to actions
33
"""
44

5+
import json
56
import typing
67
from collections import defaultdict
78
from datetime import date, datetime
@@ -729,6 +730,36 @@ def main_view(self) -> List[Any]:
729730
"/me", f"<strong>{self.message['sender_full_name']}</strong>", 1
730731
)
731732

733+
# If message contains submessages (like polls, todo), process them
734+
# and update the message content.
735+
if self.message["submessages"]:
736+
try:
737+
first_submessage_content = json.loads(
738+
self.message["submessages"][0]["content"]
739+
)
740+
except (json.JSONDecodeError, TypeError):
741+
first_submessage_content = {}
742+
743+
if (
744+
"widget_type" in first_submessage_content
745+
and first_submessage_content["widget_type"] == "poll"
746+
):
747+
question, votes_for_option = self.process_poll_data(
748+
self.message["submessages"]
749+
)
750+
751+
if question:
752+
self.message[
753+
"content"
754+
] = f"<strong>Poll Question: {question}</strong>\n"
755+
else:
756+
self.message["content"] = "<strong>Add Poll Question</strong>\n"
757+
for option, voters in votes_for_option.items():
758+
voter_count = len(voters)
759+
count_text = f"<strong>[{voter_count:^3}]</strong>"
760+
option_text = f"{option}"
761+
self.message["content"] += f"{count_text} {option_text}\n"
762+
732763
# Transform raw message content into markup (As needed by urwid.Text)
733764
content, self.message_links, self.time_mentions = self.transform_content(
734765
self.message["content"], self.model.server_url
@@ -812,6 +843,46 @@ def update_message_author_status(self) -> bool:
812843

813844
return author_is_present
814845

846+
@classmethod
847+
def process_poll_data(cls, poll_data: Any) -> Tuple[str, Dict[str, List[int]]]:
848+
question = ""
849+
votes_for_option: Dict[str, List[int]] = {}
850+
851+
for submessage in poll_data:
852+
content = submessage.get("content", {})
853+
if isinstance(content, str):
854+
try:
855+
content = json.loads(content)
856+
except json.JSONDecodeError:
857+
continue
858+
859+
if "widget_type" in content and content["widget_type"] == "poll":
860+
question = content["extra_data"]["question"]
861+
votes_for_option = {
862+
option: [] for option in content["extra_data"]["options"]
863+
}
864+
865+
elif "type" in content and content["type"] == "new_option":
866+
option = content["option"]
867+
votes_for_option[option] = []
868+
869+
elif "type" in content and content["type"] == "vote":
870+
key = content["key"]
871+
index_str = key.split(",")[1]
872+
if index_str.isdigit():
873+
index = int(index_str)
874+
if index < len(votes_for_option):
875+
option = list(votes_for_option.keys())[index]
876+
voter_id = submessage["sender_id"]
877+
if content["vote"] == -1:
878+
if voter_id in votes_for_option[option]:
879+
votes_for_option[option].remove(voter_id)
880+
else:
881+
if voter_id not in votes_for_option[option]:
882+
votes_for_option[option].append(voter_id)
883+
884+
return question, votes_for_option
885+
815886
@classmethod
816887
def transform_content(
817888
cls, content: Any, server_url: str

0 commit comments

Comments
 (0)