-
Notifications
You must be signed in to change notification settings - Fork 392
Handle internal message moves causing message muting/unmuting #2079
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
base: main
Are you sure you want to change the base?
Conversation
90e1fd0 to
01e3432
Compare
chrisbobbe
left a comment
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! Comments below, from reading the implementation but not yet the tests.
Also a small commit-message nit, for the first two commits: "keyword" has a special meaning, so I'd avoid saying '"channel" keyword'.
lib/model/message_list.dart
Outdated
| /// The messages are moved into this narrow from another narrow, or are moved | ||
| /// internally but got unmuted by the move. | ||
| void _messagesMovedIntoNarrow() { |
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 appreciate not wanting to use a method against its interface—I see the added dartdoc—but this change causes confusion in the code. The method's name is also part of its interface, and in the new case (messages moved internally but got unmuted) it's very confusing that the name contradicts what's actually happening. For what it means for a message to be in a narrow, see Narrow.containsMessage; in particular:
/// This does not necessarily mean the message list would show this message
/// when navigated to this narrow; in particular it does not address the
/// question of whether the stream or topic […] is muted.Some options to fix:
- Make the name, dartdoc if needed, and callers agree on what the method is for.
- Make a new method just for the new case.
- Just write the code inline, as we do for the corresponding code handling
MutedUsersEvents.
Similarly for _messagesMovedFromNarrow.
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.
Agree. TBH, I was hesitant to reuse these methods, and in fact, the first revisions had the code inlined. 🙂 Anyway, renamed these methods to _messagesMoved(From/Into)MessageList. I think the callers can now agree on the new names.
lib/model/message_list.dart
Outdated
| _handleMessagesMovedInternally(messageMove, messageIds); | ||
|
|
||
| case MentionsNarrow(): | ||
| case StarredMessagesNarrow(): | ||
| // The messages didn't enter or leave this narrow. | ||
| // TODO(#1255): … except they may have become muted or not. | ||
| // We'll handle that at the same time as we handle muting itself changing. | ||
| // Recipient headers, and downstream of those, may change, though. | ||
| _messagesMovedInternally(messageIds); |
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'm looking at the two methods _handleMessagesMovedInternally and _messagesMovedInternally. Those names are very similar, and the difference ("handle" in the first, not in the second) doesn't effectively carry meaning for me as a reader. This invites confusion and wasted time, when readers either
- don't notice that two different methods are being called, or
- feel pressure to load both implementations into their heads, because they're not satisfied with some aspect of the system—even an aspect unrelated to the feature you're implementing—and there aren't any clues that it's correct to call one method instead of the other, except by understanding the implementations.
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.
here aren't any clues that it's correct to call one method instead of the other, except by understanding the implementations
In fact here's a concrete example: _messagesMovedInternally is called in the MentionsNarrow, StarredMessagesNarrow, and KeywordSearchNarrow cases, but _handleMessagesMovedInternally isn't. This difference isn't explained by the fact that your method's name starts with _handle and the other one doesn't, so I have to go read the implementations.
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.
_messagesMovedInternallyis called in theMentionsNarrow,StarredMessagesNarrow, andKeywordSearchNarrowcases, but_handleMessagesMovedInternallyisn't.
I see from _messageVisible that MentionsNarrow, StarredMessagesNarrow, and KeywordSearchNarrow don't filter out messages by channel/topic muting, so there would be nothing for _handleMessagesMovedInternally to do in those cases, and I guess that's what led to the apparent inconsistency.
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.
Does the new method _handleMessagesMovedInternally, and its helper _messageInternalMoveCanAffectVisibility, need to exist? What if we just cut those out, inlining the switches on MessageMoveVisibilityEffect right there in messagesMoved?
case CombinedFeedNarrow():
switch (store.mightAffectIsTopicVisible(messageMove)) {
case .unmutedToMuted: // etc.
case .mutedToUnmuted: // etc.
case .unmutedToUnmuted: _messagesMovedInternally(messageIds);
case .mutedToMuted: return;
}(and the appropriate thing for the ChannelNarrow case below)
| /// Whether the given message move will change the result of | ||
| /// [isTopicVisibleInChannel] for the channel and topic of its moved messages, | ||
| /// compared to the current state. | ||
| MessageMoveVisibilityEffect mightAffectIsTopicVisibleInChannel(UpdateMessageMoveData messageMove) { | ||
| final UpdateMessageMoveData( | ||
| :origStreamId, :newStreamId, :origTopic, :newTopic) = messageMove; | ||
| return MessageMoveVisibilityEffect._fromBeforeAfter( | ||
| isTopicVisibleInChannel(origStreamId, origTopic), | ||
| isTopicVisibleInChannel(newStreamId, newTopic)); | ||
| } |
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.
The dartdoc is incoherent: "moved messages" don't have a "channel and topic"; they have an original channel/topic and a new channel/topic. I worry about bugs caused by calling this with a messageMove where the channel changes.
Let's focus this method on what we need it to do, making it impossible to call except in the case we care about: messages having just their topic changed, in the context of a specific channel focused in the UI. Here's a suggestion:
/// following a topic rename in the given channel.
MessageMoveVisibilityEffect mightAffectIsTopicVisibleInChannel({
required int channelId,
required TopicName origTopic,
required TopicName newTopic,
}) {
return MessageMoveVisibilityEffect._fromBeforeAfter(
isTopicVisibleInChannel(channelId, origTopic),
isTopicVisibleInChannel(channelId, newTopic));
}Alternatively, instead of origTopic and newTopic, it could take the whole UpdateMessageMoveData, but with asserts and dartdoc saying it can't be a move between channels.
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.
Looking carefully at this, I think we can allow this method to be used with a message move where the channel changes. I agree that this method is related to a specific channel being focused in the UI, but a message can be moved from/into the current focused channel. In fact, this method is used for such cases in a commit that I recently added:
1be11a3 msglist: Optimize cross-channel message moves in channel narrow
lib/model/channel.dart
Outdated
| /// Whether the given message move will change the result of | ||
| /// [isTopicVisibleInChannel] for the channel and topic of its moved messages, | ||
| /// compared to the current state. | ||
| MessageMoveVisibilityEffect mightAffectIsTopicVisibleInChannel(UpdateMessageMoveData messageMove) { |
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.
We have these names:
ChannelStore.willChangeIfTopicVisibleInChannelChannelStore.willChangeIfTopicVisibleUserStore.mightChangeShouldMuteDmConversation
and now we have these new names:
ChannelStore.mightAffectIsTopicVisibleInChannelChannelStore.mightAffectIsTopicVisible
I'd like to settle on a consistent pattern, since all of these have similar jobs, rather than varying between "will" and "might", and between "change" and "affect". Could you add a prep commit that makes the existing names consistent, then change your commit to follow that pattern? You'll need to disambiguate your new methods from the existing ones, and we might as well do that semantically. For example:
ChannelStore.muteEventWillAffectIfTopicVisibleInChannelChannelStore.muteEventWillAffectIfTopicVisibleChannelStore.messageMoveWillAffectIfTopicVisibleInChannelChannelStore.messageMoveWillAffectIfTopicVisible
This includes renaming the method(s), renaming their parameters/local variables, modifying their docs, and modifying their test names to replace the legacy "stream" term with "channel". This will make it consistent with other similar methods that we'll add in the next commits which will use the "channel" term.
… methods This includes renaming the method(s), renaming their local variables, modifying their docs, and modifying their test names to replace the legacy "stream" term with "channel". This will make it consistent with other similar methods that we'll add in the next commits which will use the "channel" term.
Rename them to topicEventWillAffectIfTopicVisible/InChannel to set up a consistent pattern for other similar methods that will be added in the next commits (and in future).
01e3432 to
e5ccc07
Compare
|
Thanks for the review @chrisbobbe. Pushed new changes, PTAL. |
Rename it to willAffectShouldMuteDmConversation to match the pattern used by similar methods in ChannelStore, such as topicEventWillAffectIfTopicVisible.
Rename them to _messagesMoved(From/Into)MessageList. I think "message list" is a better term here compared to "narrow" as in the model code we actually deal with the messages being moved from/into the message list. This will become more accurate in the next commit(s) where a message move will mute/unmute some messages in a narrow, in which case, they're not moved from/into the narrow, but instead moved from/into the message list.
This optimization handles cases where moved messages become muted/unmuted:
- If messages are moved into the narrow but end up muted by the move,
they will never appear in the message list, so no refetch is needed.
- If messages are moved out of the narrow but were already muted,
there is no need to look them up and try to remove them from the
message list, since they could not have appeared there in the first place.
This avoids an unnecessary lookup with time complexity `O(m log n)`,
where `m` is the number of moved messages and `n` is the number of
messages currently in the message list.
e5ccc07 to
1a31979
Compare
Fixes: #1476
In addition to addressing the main problem stated in #1476's description, which is about removing messages in some narrows after becoming muted by a message move, this PR also addresses a closely related problem: showing messages in the same narrows after becoming unmuted by a message move.