-
Notifications
You must be signed in to change notification settings - Fork 341
Implement basic keyword search #1662
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
Conversation
ab2bdb6
to
30989af
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.
Exciting! Here's a review of the current version, omitting the points you mentioned as TODOs above.
lib/widgets/message_list.dart
Outdated
|
||
@override | ||
Widget build(BuildContext context) { | ||
AppBar _buildAppBar(BuildContext context) { |
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.
msglist [nfc]: Pull out _buildAppBar helper
How about making it a separate widget entirely? Seems like that'd make the separation a bit crisper; and it doesn't look like this method is interacting with this widget's state beyond looking at narrow
.
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 might be feasible, but the reason I didn't is that Scaffold's appBar
param is actually typed as PreferredSizeWidget?
, not Widget?
. Maybe I can subclass ZulipAppBar
(which extends AppBar
/ PreferredSizeWidget
) for this app bar?
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.
Hmm I see, yeah. That bit of the Scaffold/AppBar API is kind of a pain.
I don't think there's a great way to do it by subclassing, either. Most of what this build-helper method is doing is computing values that get passed to the AppBar constructor, so those computations would have to happen in the subclass's constructor… and they're using the context, so that's no good.
I have an idea which I'll send as an added commit, with 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.
ac6c750 msglist [nfc]: Move _buildAppBar to a widget-like class
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.
Then I also went into a bit of a digression and sent a PR upstream that would clean up AppBar's API in this respect:
- AppBar resists extending by composition, because of preferredSize flutter/flutter#171603
- Add static for AppBar.preferredSize, to enable extending by composition flutter/flutter#171604
And I have a small draft commit that would use that to turn _MessageListAppBar
into a proper widget class:
3ecadf2 wip use AppBar.preferredSizeFor to clean up _MessageListAppBar
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.
Interesting, thanks for doing that!
&& narrow.containsMessage(outboxMessage) | ||
&& narrow.containsMessage(outboxMessage) == true |
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 app-code changes show just small behavior changes for a
message-list page in a search narrow, which we'll implement soon:
- Message events are ignored
- Outbox messages never appear
nit: s/changes/choices/ — before this commit, there was no answer to the question of how these would behave on a null containsMessage
result, because the types ruled that out
/// Set [narrow] to [newNarrow], reset, [notifyListeners], and [fetchInitial]. | ||
void renarrowAndFetch(Narrow newNarrow) { |
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.
nit: mention this from the [narrow] doc, since it's a lot like a setter for that property
lib/api/model/narrow.dart
Outdated
class ApiNarrowKeywordSearch extends ApiNarrowElement { | ||
@override String get operator => 'search'; |
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.
nit: let's call this ApiNarrowSearch
, to keep it closely aligned with the underlying API
(unlike the Narrow subclass, which is more about how we organize the UI within the app)
lib/api/model/narrow.dart
Outdated
); | ||
} | ||
|
||
class ApiNarrowKeywordSearch extends ApiNarrowElement { |
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.
nit: let's put this before with
and id
; those feel more specific than this because they point to an individual message, and naturally go at the end of a list of filters
(I guess that calls for with
to go after is
, too, next to id
.)
/// | ||
/// Will be null for search narrows. | ||
bool? containsMessage(MessageBase message); |
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'd like this to give a bit more of a semantics for what it means for this to be null:
/// | |
/// Will be null for search narrows. | |
bool? containsMessage(MessageBase message); | |
/// | |
/// Null when the client is unable to predict whether the message | |
/// satisfies the filters of this narrow, e.g. when this is a search narrow. | |
bool? containsMessage(MessageBase message); |
lib/widgets/message_list.dart
Outdated
controller: _controller, | ||
autocorrect: false, | ||
|
||
// Current servers seem to require straight quotes for the "exact match"- |
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.
lib/widgets/message_list.dart
Outdated
smartQuotesType: SmartQuotesType.disabled, | ||
|
||
autofocus: true, | ||
onSubmitted: (value) => widget.onSubmitted(_valueToNarrow(value)), |
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.
nit: maybe have one method do both these steps (since they always occur together):
onSubmitted: (value) => widget.onSubmitted(_valueToNarrow(value)), | |
onSubmitted: _handleSubmitted, |
lib/widgets/message_list.dart
Outdated
border: OutlineInputBorder( | ||
borderRadius: BorderRadius.circular(10), | ||
borderSide: BorderSide.none), | ||
hintStyle: TextStyle(color: designVariables.labelSearchPrompt))); |
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.
nit: should go next to hintText
test/model/message_list_test.dart
Outdated
case MentionsNarrow(): | ||
case StarredMessagesNarrow(): | ||
check(model.narrow.containsMessage(message)).isNotNull().isTrue(); | ||
case KeywordSearchNarrow(): | ||
check(model.narrow.containsMessage(message)).isNull(); |
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.
If it's a KeywordSearchNarrow, then there's nothing that needs to be checked here; it's a fact about that narrow type, not about the list of messages, that containsMessage
will always be null.
Then I think also this is one place where we don't need to be considering for each new narrow type how it should be handled. We can let the default be that the check is done; if at some point we add another narrow type where the check would fail, then we'll naturally notice at test time, and can fix it then. So this could look like:
if (model.narrow is! KeywordSearchNarrow) {
check(model.narrow.containsMessage(message)).isNotNull().isTrue();
}
Hmm or maybe better yet: this could just check that containsMessage
is either null or true. If it's null, we're unable to confirm whether the message really belongs here, so checkInvariants
has to let it slide. What it's really looking for is cases where it's false, and therefore MessageListView really should have omitted the message.
(All the comments above are small, except #1662 (comment) about … Yeah, confirmed: the app only ever calls that function for generating a message link, so with a SendableNarrow.) |
Just a simple "No search results." is the default web app message for no results. We also show stopwords that were excluded, but I don't know if we exclude stopwords in mobile searches? |
The stopwords behavior is server-side (I believe it happens within the database, even), so it'll be the same for mobile as web. |
As suggested by Greg: zulip#1662 (comment)
ac6c750
to
0ef4fd6
Compare
Thanks for the reviews! Revision pushed. I still intend to write some tests today, but I can do that while you're reviewing this revision. Also @alya I put screenshots in the issue description.
Works for me for now, but I think ideally we'd show a different message (or no message?) if you haven't requested results yet, i.e. you haven't submitted a search query. |
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 for the revision! All looks good, modulo nits below, and it'd be good to have those tests you're working on.
I also haven't yet read the commit at the start which is #1658. I'll do that next.
Widget build(BuildContext context) { | ||
final store = PerAccountStoreWidget.of(context); | ||
final messageListTheme = MessageListTheme.of(context); | ||
final zulipLocalizations = ZulipLocalizations.of(context); | ||
|
||
final Color? appBarBackgroundColor; |
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.
nit: commit message needs edit after squashing
msglist [nfc]: Move _buildAppBar to a widget-like class
Conceptually this should be a widget, but can't quite be, for the
reasons explained in the comment. Still we can organize the code
in nearly the same way as if it were a widget.
// The messages were and remain in this narrow. | ||
// TODO(#421): … except they may have become muted or not. | ||
// The messages didn't enter or leave this narrow. | ||
// TODO(#1255): … except they may have become muted or not. |
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.
msglist [nfc]: Renumber a TODO
The UserTopic part of this is done, but handling channel
muting/unmuting is still TODO.
Looks like there's a couple other TODO comments for this issue that should get the same update:
lib/model/channel.dart
325: // TODO(#421) update [MessageListView] if affected
357: // TODO(#421) update [MessageListView] if affected
… Or actually I guess only the first of those two is about channel mutes; the other is topic mutes. Is the latter resolved, then?
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.
also nit: make the summary line a bit more specific, like "a TODO about muting"
/// Page title for the 'Search' message view. | ||
/// | ||
/// In en, this message translates to: | ||
/// **'Search'** | ||
String get searchMessagesPageTitle; |
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.
nit: these look to be happening in the wrong commit
lib/api/model/model.dart
Outdated
final String? matchContent; | ||
@JsonKey(name: 'match_subject') | ||
final String? matchTopic; | ||
// TODO(#1663) matchContent and matchTopic |
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.
Let's leave some kind of breadcrumb in this TODO comment to say there's a commit we'll want to revert to restore these. (It has some useful bits in it beyond what one gets mechanically from the API docs: the rename, and the test for that.)
_messagesMovedInternally(messageIds); | ||
|
||
|
||
case ChannelNarrow(:final streamId): |
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.
nit: double blank line
lib/widgets/message_list.dart
Outdated
fillColor: designVariables.bgSearchInput, | ||
border: OutlineInputBorder( | ||
borderRadius: BorderRadius.circular(10), | ||
borderSide: BorderSide.none),)); |
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.
nit:
borderSide: BorderSide.none),)); | |
borderSide: BorderSide.none), | |
)); |
final String message; | ||
|
||
if (widget.narrow is KeywordSearchNarrow) { |
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.
nit: group declaration with initializing code
final String message; | |
if (widget.narrow is KeywordSearchNarrow) { | |
final String message; | |
if (widget.narrow is KeywordSearchNarrow) { |
Conceptually this should be a widget, but can't quite be, for the reasons explained in the comment. Still we can organize the code in nearly the same way as if it were a widget. Co-authored-by: Greg Price <[email protected]>
Done as preparation to implement a new Narrow subclass for keyword search. We can't really implement a client-side "contains-message" predicate for search; it's up to the server to decide whether a message matches the search query. A simple path to representing that, done here, is to just change Narrow.containsMessage's contract to say that it always returns null if the narrow is a search narrow. The app-code changes show just small behavior choices for a message-list page in a search narrow, which we'll implement soon: - Message events are ignored - Outbox messages never appear Various TODOs in tests; we'll handle all of those as part of adding the keyword-search Narrow subclass.
…ning Seeing that the tests were expecting a notifyListeners call already, we found that one is already done, in MessageStore.handleUpdateMessageEvent, so this is redundant. But because the new call is done synchronously with the existing call, it doesn't trigger any extra rebuilds, and it helps the reader by making it clear locally that listeners do indeed get notified.
We'll use this in keyword search, coming up, for renarrowing when the user submits a new keyword.
As suggested by Greg: zulip#1662 (comment)
…ions It looks like this comment was written for CombinedFeedNarrow and not updated when the other two cases were added. (Unlike with CombinedFeedNarrow, it's possible that some of `messageIds` weren't starred or mentioned in the first place, so it was inaccurate to say they "were" in the narrow.)
We already handle muted topics, but we still need to update the message list on muting/unmuting a channel, which is zulip#1255.
We plan to revert this when we do zulip#1663.
Fixes zulip#252. For timeliness, tests are left as a followup: zulip#1667
0ef4fd6
to
a6104aa
Compare
Thanks! Revision pushed, and filed #1667 for tests as a followup, for timeliness. |
Thanks! Looks good; merging. |
Fixes #252.
Stacked on #1658
Still TODO before shipping:
Screenshots