Skip to content
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

Support (un)resolving topics #1544

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

rsashank
Copy link
Member

@rsashank rsashank commented Sep 5, 2024

What does this PR do, and why?

This PR adds support for (un)resolving topics in ZT via TopicInfoView popup menu when topic is highlighted in left_stream_bar and i key is pressed to toggle topic settings.

Changes with respect to completion candidate PR:

External discussion & connections

How did you test this?

  • Manually - Behavioral changes
  • Manually - Visual changes
  • Adapting existing automated tests
  • Adding automated tests for new behavior (or missing tests)
  • Existing automated tests should already cover this (only a refactor of tested code)

Self-review checklist for each commit

  • It is a minimal coherent idea
  • It has a commit summary following the documented style (title & body)
  • It has a commit summary describing the motivation and reasoning for the change
  • It individually passes linting and tests
  • It contains test additions for any new behavior
  • It flows clearly from a previous branch commit, and/or prepares for the next commit

Visual changes

image image

@zulipbot zulipbot added the size: XL [Automatic label added by zulipbot] label Sep 5, 2024
@rsashank rsashank force-pushed the un-resolve-topics branch 3 times, most recently from fb82ade to a570d28 Compare September 5, 2024 23:31
@rsashank rsashank mentioned this pull request Sep 6, 2024
18 tasks
Copy link
Collaborator

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rsashank Thanks for the followup 👍

I may have had a few more thoughts, but wanted to get these comments to you before I go to bed :)

Re co-authoring, please note that

  • Shivam's email doesn't appear to match his original commits
  • some commits look entirely to be his with no additions; can you cherry-pick those from the original PR instead to indicate that?

zulipterminal/model.py Outdated Show resolved Hide resolved
zulipterminal/model.py Outdated Show resolved Hide resolved
Comment on lines +1467 to +1540
initial_data[
"realm_community_topic_editing_limit_seconds"
] = topic_editing_limit_seconds
initial_data[
"realm_move_messages_within_stream_limit_seconds"
] = move_messages_within_stream_limit_seconds
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly this doesn't cover the ZFL 183+ (Zulip 7) situation, where the first of these parameters was removed?

We could treat the two parameters as a patch to initial data via a dict, but unfortunately that then leaves assumptions as to whether the two values are in initial_data, ie. for later versions we strictly want to remove the field.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added cases for ZFL184, where the value of realm_community_topic_editing_limit_seconds is None.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point was that some fields are actually missing at some feature levels, not that they are None, or I'd at least expect that at early server versions - and if an option is actually removed at a later version?

zulipterminal/model.py Outdated Show resolved Hide resolved
tests/model/test_model.py Outdated Show resolved Hide resolved
zulipterminal/ui_tools/views.py Outdated Show resolved Hide resolved
Comment on lines +1642 to +1651
def test_toggle_resolve_status(self) -> None:
resolve_button = self.topic_info_view.widgets[-1]
resolve_button._emit("click")

self.controller.model.toggle_topic_resolve_status.assert_called_once_with(
stream_id=self.stream_id, topic_name=self.topic
)

self.controller.exit_popup.assert_called_once()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you added this extra test, and the one above this?

I'm not sure about using _emit here, at least compared to our normal use of keypress, but that should be fine - what inspired _emit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially considered using keypress, but assumed the Enter key essentially uses ACTIVATE which triggers a click event in the context of an Urwid Button, just a NIT but thought it would be straightforward to use _emit.

I can change it back to use keypress with ACTIVATE_BUTTON

Urwid Docs

Send ‘click’ signal on ‘activate’ command.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, though I'll leave this comment unresolved since it may be useful to take this to a discussion of how best to integrate urwid and our own key handling further in this way.

@@ -312,6 +312,11 @@ class KeyBinding(TypedDict):
'help_text': 'Show/hide stream information & modify settings',
'key_category': 'stream_list',
},
'TOPIC_INFO': {
'keys': ['i'],
'help_text': 'Show/hide topic information & modify settings',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only useful for this feature right now, so perhaps we should update this slightly to mention updating the resolve status.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it alright now?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that seems fine for now 👍

zulipterminal/config/keys.py Outdated Show resolved Hide resolved
)

self.widgets.append(resolve_topic_setting)
super().__init__(controller, self.widgets, "TOPIC_INFO", popup_width, title)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TOPIC_INFO has no meaning here yet. Not sure of the ordering; let's leave it for now.

@neiljp neiljp added PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback version parity: 5 labels Sep 9, 2024
@rsashank rsashank force-pushed the un-resolve-topics branch 2 times, most recently from 7bea76f to 02963cd Compare September 30, 2024 13:14
rsashank and others added 5 commits September 30, 2024 18:46
The function calls get_latest_message_in_topic to fetch recent message
in topic to be (un)resolved. It verifies user and editing conditions
using can_user_edit_topic function and finally add or remove
RESOLVED_TOPIC_PREFIX from topic name.

Co-authored-by: Shivam Deotarse <[email protected]>
@rsashank rsashank added PR needs review PR requires feedback to proceed and removed PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback labels Sep 30, 2024
@rsashank
Copy link
Member Author

@neiljp Thanks for the feedback!

Updated this PR :)


model.toggle_topic_resolve_status(stream_id, topic_name)

report_error.assert_called_once_with(expected_footer_error)
Copy link
Collaborator

@neiljp neiljp Oct 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For comparison with the previous test, we can also assert that the update method is not called, and vice versa in the other test.

I mentioned this point indirectly when the tests were combined - each branch can assert on each element.

Comment on lines 1476 to 1550
model.get_latest_message_in_topic = mocker.Mock(
return_value={
"subject": topic_name,
"timestamp": timestamp,
"id": 1,
}
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nits: if you have arbitrary constants, it's often useful to have them different, in case they 'work' when they are accidentally used in place of one another.

It's debatable whether a message id of 1 makes sense here, but it's a parameter so easily changed or parametrized later.

Copy link
Collaborator

@neiljp neiljp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rsashank Thanks for the update 👍 My comments are mostly re tests and error handling, and many are hopefully small.

Please let me know if you have questions!

I did notice that, while I mentioned that Shivam's email for this was different, his merged commits look like another email address. This should be fine either way, as we can update the .mailmap for this if necessary.

),
],
)
def test_toggle_topic_resolve_status_no_footer_error(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We've been switching towards a double underscore between the function name and the test meaning/motivation - it makes it clearer which part is which. (same elsewhere)

Comment on lines +1490 to +1515
case(
"hi!",
0,
12,
86400,
None,
" Time limit for editing topic has been exceeded.",
id="time_limit_exceeded:Zulip2.1+:ZFL12",
),
case(
"hi!",
0,
162,
259200,
259200,
" Time limit for editing topic has been exceeded.",
id="time_limit_exceeded:Zulip7.0+:ZFL162",
),
case(
"hi!",
0,
184,
None,
86400,
" Time limit for editing topic has been exceeded.",
id="topic_resolved:Zulip2.1+:ZFL184",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at these cases alone, it's not clear to me what's different in each one?

  • Except for the last case (with incorrect id?) the id starts with time_limit_exceeded; that's currently implied by the test, though might not be, if you extend the code to handle other errors.
  • Does the topic name need to be parametrized? We're not testing unresolving as it stands, but could parametrize over that separately instead (using a nested decorator).
  • Does the latest_message_timestamp matter? It appears to be zero in every case, which is constant among the cases (can it be a constant like message_id above?), but it's unclear what that value is trying to achieve?
  • Is the error string a constant between each case? (again, it may not be if the error handling is extended)

Comment on lines +714 to +716
latest_msg = self.get_latest_message_in_topic(stream_id, topic_name)
if not self.can_user_edit_topic() or not latest_msg:
return
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should report errors, based on how the function currently works - this was the error handling I mentioned was absent, and would be more obvious if restructured to be like this.

Comment on lines +730 to +733
# ZFL < 11, community_topic_editing_limit_seconds
# was hardcoded as int value in secs eg. 86400s (1 day) or None
else:
edit_time_limit = 86400
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this comment was moved, but it doesn't map to the value being None?

My related question is whether edit_time_limit can end up being None for a well-specified server? It's checked against None below, which might just be related to the .get calls above, or if the 'seconds' values can actually have a None/null value that has a distinct meaning.

@@ -312,6 +312,11 @@ class KeyBinding(TypedDict):
'help_text': 'Show/hide stream information & modify settings',
'key_category': 'stream_list',
},
'TOPIC_INFO': {
'keys': ['i'],
'help_text': 'Show/hide topic information & modify settings',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that seems fine for now 👍

"topic_name, expected_topic_button_label",
[
("hi!", "Resolve Topic"),
(f"{RESOLVED_TOPIC_PREFIX}" + "hi!", "Unresolve Topic"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Unless you plan to extract the 'hi!', this can be a combined f-string without the addition.

Comment on lines +1642 to +1651
def test_toggle_resolve_status(self) -> None:
resolve_button = self.topic_info_view.widgets[-1]
resolve_button._emit("click")

self.controller.model.toggle_topic_resolve_status.assert_called_once_with(
stream_id=self.stream_id, topic_name=self.topic
)

self.controller.exit_popup.assert_called_once()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, though I'll leave this comment unresolved since it may be useful to take this to a discussion of how best to integrate urwid and our own key handling further in this way.

Comment on lines 70 to +71
'area:stream' : (Color.DARK0_HARD, Color.BRIGHT_BLUE),
'area:topic' : (Color.DARK0_HARD, Color.BRIGHT_BLUE),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is useful to have distinct styles to enable changing them later, but I'm wondering if we should use different colors, rather than simply having duplicates of area:stream.

Comment on lines 295 to 303
self.show_pop_up(show_stream_view, "area:stream")

def show_topic_info(self, stream_id: int, topic_name: str) -> None:
show_topic_view = TopicInfoView(self, stream_id, topic_name)
self.show_pop_up(show_topic_view, "area:topic")

def show_stream_members(self, stream_id: int) -> None:
stream_members_view = StreamMembersView(self, stream_id)
self.show_pop_up(stream_members_view, "area:stream")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This would be better not between two stream-related functions.

Comment on lines 718 to 731
# ZFL < 11, community_topic_editing_limit_seconds
# was hardcoded as int value in secs eg. 86400s (1 day) or None
if 11 <= self.server_feature_level < 162:
edit_time_limit = self.initial_data.get(
"realm_community_topic_editing_limit_seconds", None
)
# ZFL >= 162, realm_move_messages_within_stream_limit_seconds was
# introduced in place of realm_community_topic_editing_limit_seconds
elif self.server_feature_level >= 162:
edit_time_limit = self.initial_data.get(
"realm_move_messages_within_stream_limit_seconds", None
)
else:
edit_time_limit = 86400
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is OK for now, though a TODO comment above the block of code would be useful.

Note that my comments re the tests could end up splitting the resolve/unresolve and version-dependent parts, at least in terms of parametrize, which is potentially part way towards the simplification I mentioned would be possible in my previous comment.

@neiljp neiljp added PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback and removed PR needs review PR requires feedback to proceed labels Oct 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR awaiting update PR has been reviewed & is awaiting update or response to reviewer feedback size: XL [Automatic label added by zulipbot] version parity: 5
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support (un)resolving topics
4 participants