-
-
Notifications
You must be signed in to change notification settings - Fork 251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
messages: WIP poll widget #1477
Conversation
e91468a
to
cb6bfc8
Compare
43631a4
to
7bd95e0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsashank This looks to work well as a first version in practice 👍
I left some detailed feedback, but essentially separating the model and messages code will keep things more organized and easier to test, as well as make it easier to move forward from this point. The sooner we can handle submessage events, the more dynamic and useful polls will be.
zulipterminal/ui_tools/messages.py
Outdated
# If message contains submessages (like polls, todo), process them | ||
# and update the message content. | ||
if self.message["submessages"]: | ||
try: | ||
first_submessage_content = json.loads( | ||
self.message["submessages"][0]["content"] | ||
) | ||
except (json.JSONDecodeError, TypeError): | ||
first_submessage_content = {} | ||
|
||
if ( | ||
"widget_type" in first_submessage_content | ||
and first_submessage_content["widget_type"] == "poll" | ||
): | ||
question, votes_for_option = self.process_poll_data( | ||
self.message["submessages"] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of this code (733-749) is related to rendering/UI, which is primarily what the message-box code is concerned with.
Specifically, this code is only concerned with handling the (sub)messages, including in .process_poll_data
, so it could easily belong somewhere else - it doesn't touch this object itself, and the new method is a class method (and doesn't use cls
).
As I noted at the end of March in the channel, it'd be cleaner to separate submessage processing into the model, and have that return the equivalent kind of data as from process_poll_data
, or possibly in the form of a new object. Did you understand my comment there?
The lines here could basically become something like
submessage_content = model.processed_submessages_for_message(message_id)
if submessage_content is not None:
if submessage_content.type == "poll":
...
We also may want the processing in the model since:
- the model will also need to handle updating of submessages (eg. new votes coming in, questions)
- the model is where we have historically wrapped sending requests to the server (eg. active user voting in other polls)
- the processed data could be cached in the model, if necessary (only updated on events, not recalculated)
It may be reasonable to place some of the processing code into another file instead of the model, like we do with rendering tables, but let's see what we end up with first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extending Neil's comment, since this function is to be added in the model file, the code patch for process_poll_data
and its test can be shifted to a separate commit which comes before this commit.
zulipterminal/ui_tools/messages.py
Outdated
if question: | ||
self.message[ | ||
"content" | ||
] = f"<strong>Poll Question: {question}</strong>\n" | ||
else: | ||
self.message["content"] = "<strong>Add Poll Question</strong>\n" | ||
for option, voters in votes_for_option.items(): | ||
voter_count = len(voters) | ||
count_text = f"<strong>[{voter_count:^3}]</strong>" | ||
option_text = f"{option}" | ||
self.message["content"] += f"{count_text} {option_text}\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This formatting looks fine for now, just for testing 👍
For various future iterations, possibly in order of importance:
- Each poll appears to have a blank line trailing, which is likely since you're adding a
"...\n"
each time, rather than"\n".join(list_of_strings)
; check the difference if you're not familiar with that :) - It may be enough to prefix the title/question with only
Poll:
? - Keeping the
Poll:
styling strong and distinct from a not-strong title/question might make it clearer that it's a poll? (and confirm the original text) - Until we support updates, having 'Add Poll Question' looks somewhat odd when it's not possible, so maybe have
(no title)
or skip the line? - Similarly it may be worth showing some indicator that there are no options if that's the case, eg.
(no options available)
- Counts of eg.
0
and11
don't line up correctly, or look oddly spaced; if we want a space either side of all numbers, we maybe want the width of 3 to be dynamic instead - We could treat voter names, etc. like we do with limiting reacting user names beyond a certain number, at least in the message itself; that said, I suspect a poll or widget popup will be likely, so that information can be shown there, as you said.
As per my note in March, while we overwrite the content for /me
, I'd prefer we avoid that pattern, since that modifies the original message - if only slightly in that case. It would be reasonable to refactor before this change to pass eg. a new content_to_render
to transform_content
, which might initially be the direct message content or the f-string from /me
. Then you can add a further alternative in this work, which would set that string to the one you build above.
tests/ui_tools/test_messages.py
Outdated
ids=[ | ||
"poll_with_votes", | ||
"poll_without_question", | ||
"poll_without_options", | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please have ids inline - there are only 3 cases, but they're each quite long.
tests/ui_tools/test_messages.py
Outdated
"message_id": 1813721, | ||
"sender_id": 27294, | ||
"msg_type": "widget", | ||
"content": '{"widget_type": "poll", "extra_data": {"question": "Can you view polls on Zulip Terminal?", "options": ["Yes", "No"]}}', # noqa: E501 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to ask why you didn't just let black handle this, rather than use noqa, but this is related to the content being json. I agree that noqa could be easier for pasting test cases in reliably - pasting the line, just adding noqa - but it's still kinda annoying to read.
I'd suggest we could split the string and rely on automatic string concatenation, which could be easier to read; for example, that allows this:
assert (
"string1 "
"string2"
) == "string1 string2"
Alternatively, particularly as your cases become more complex, you may wish to use a factory fixture to generate the submessages (or even a full message with submessages). One reason for this is that the message_id
should be identical for submessages, I'd expect, and msg_type
is likely widget
for all polls - and widget_type
is poll
, etc. That all needs confirming, but could allow you to have something in a test like the following which would generate data from certain templates:
submessages = poll_factory(
{"question": "what?", "options": ["cheese"], "sender_id": 5},
{"vote", ..., "sender_id": 6},
message_id=1813721 # or just rely on a default
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @rsashank for taking this up! It will be great to have this feature on ZT
I tested this on my system and it worked as per expectations. I didn't face any glitches.
Apart from the test function that you have added for process_poll_data
, I think it will be a good idea from integration test point of view to add a test case for transform_content
which tests for poll data. I'll take this up in the stream.
zulipterminal/ui_tools/messages.py
Outdated
# If message contains submessages (like polls, todo), process them | ||
# and update the message content. | ||
if self.message["submessages"]: | ||
try: | ||
first_submessage_content = json.loads( | ||
self.message["submessages"][0]["content"] | ||
) | ||
except (json.JSONDecodeError, TypeError): | ||
first_submessage_content = {} | ||
|
||
if ( | ||
"widget_type" in first_submessage_content | ||
and first_submessage_content["widget_type"] == "poll" | ||
): | ||
question, votes_for_option = self.process_poll_data( | ||
self.message["submessages"] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extending Neil's comment, since this function is to be added in the model file, the code patch for process_poll_data
and its test can be shifted to a separate commit which comes before this commit.
zulipterminal/ui_tools/messages.py
Outdated
@@ -729,6 +730,36 @@ def main_view(self) -> List[Any]: | |||
"/me", f"<strong>{self.message['sender_full_name']}</strong>", 1 | |||
) | |||
|
|||
# If message contains submessages (like polls, todo), process them | |||
# and update the message content. | |||
if self.message["submessages"]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed this point.
A lot of test cases failed due to KeyError
at this line. You can try using get()
instead of using key to fetch the data from the dictionary
@rsashank To ensure my feedback is visible, following up from some points elsewhere, my suggestions at this point would be to:
|
Added find_widget_type to identify the type of widget present in the message (e.g., polls, todo, etc.). Added test.
Added process_poll_widget function to process submessages containing a poll widget. Returns the question of the poll along with options and their voters in a dict.
What does this PR do, and why?
Implementation for the polls widget WIP
Previous:
Current PR:
Outstanding aspect(s)
External discussion & connections
Polls - Widget Support #T986 #T1477
How did you test this?
Self-review checklist for each commit
Visual changes