Skip to content

Commit 4ab806c

Browse files
committed
messages: WIP poll widget.
1 parent 440a3fa commit 4ab806c

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-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: 70 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,35 @@ def main_view(self) -> List[Any]:
729730
"/me", f"<strong>{self.message['sender_full_name']}</strong>", 1
730731
)
731732

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

813843
return author_is_present
814844

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

0 commit comments

Comments
 (0)