Skip to content

api: Mark messages as read (updateMessageFlags and updateMessageFlagsForNarrow) #361

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

Merged
merged 3 commits into from
Nov 6, 2023

Conversation

sirpengi
Copy link
Contributor

@sirpengi sirpengi commented Nov 1, 2023

This PR adds all the api routes related to marking messages as read.

The complexities around the response of https://zulip.com/api/mark-all-as-read are ignored as it is deprecated (it is also ignored in zulip-mobile). On an examination of lib/api/core.dart it doesn't look like we check the value of the result field (where the status code is 200) so if the server returns partially_completed for the affected FLs it doesn't raise any errors (although we would treat the operation as completed).

Fixes part of #130

@sirpengi sirpengi requested a review from gnprice November 1, 2023 20:07
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks @sirpengi! Comments below on the first three commits:
ba5e3dd api [nfc]: Refactor out logic that supports serializing legacy DmNarrow
58be9c9 api [nfc]: Pull out a method Anchor.toJson
346e94c api: Add updateMessageFlags and updateMessageFlagsForNarrow routes

Let's split out the remaining commit to a separate followup PR:
eabae44 api: Add markAllAsRead, markStreamAsRead, and markTopicAsRead routes

just to keep each PR simpler, since GitHub's UI doesn't do great as threads get longer.

typedef ApiNarrow = List<ApiNarrowElement>;

/// Convenience function to resolve legacy elements of a narrow.
Copy link
Member

Choose a reason for hiding this comment

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

The description "resolve legacy elements" doesn't sound quite right to me — the input isn't expected to have legacy elements (rather it may have unresolved ApiNarrowDm elements), and the resolution is just as likely or more so to produce modern elements as to produce legacy elements.

Here's a version:

Suggested change
/// Convenience function to resolve legacy elements of a narrow.
/// Resolve any [ApiNarrowDm] elements appropriately.

And then resolveDmElements would work as a name.

(If we later have a second legacy/modern split to resolve, this name and description won't cover that, but we can handle that if it comes up.)

Comment on lines 10 to 11
ApiNarrow resolveLegacyElements(ApiNarrow narrow, ApiConnection connection) {
final supportsOperatorDm = connection.zulipFeatureLevel! >= 177; // TODO(server-7)
Copy link
Member

Choose a reason for hiding this comment

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

Let's have it take an int zulipFeatureLevel instead — that feels more like the right layer for this to be operating at.

Comment on lines -93 to -95
if (narrow.any((element) => element is ApiNarrowDm)) {
final supportsOperatorDm = connection.zulipFeatureLevel! >= 177; // TODO(server-7)
narrow = narrow.map((element) => switch (element) {
Copy link
Member

Choose a reason for hiding this comment

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

The narrow.any conditional here is an optimization that avoids unnecessarily copying the list when there's nothing to change. Let's preserve that in the refactor.

AnchorCode.oldest => RawParameter('oldest'),
AnchorCode.firstUnread => RawParameter('first_unread'),
},
'narrow': resolveLegacyElements(narrow, connection),
Copy link
Member

Choose a reason for hiding this comment

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

api [nfc]: Refactor out logic that supports serializing legacy DmNarrow

s/Refactor/Factor/

For me at least "refactor out" isn't a term; there's "refactor" for a general category of things you might do, one of which is to "factor out" something, but no "refactor out".

Also "legacy DmNarrow" doesn't quite make sense; DmNarrow itself isn't legacy, and nor is there such a thing as a legacy value of DmNarrow vs. a non-legacy value. (If talking about ApiNarrowDm, then there are modern and legacy versions of that — but because "DmNarrow" is spelled in a way that isn't normal English, it can only refer to the identifier with that name, not to the general concept of a "DM narrow".)

I think "supports serializing ApiNarrowDm" covers it. A necessary part of serializing one of those is to resolve it to modern or legacy.

});
}

/// The type of [updateMessageFlags.op] and [updateMessageFlagsForNarrow.op].
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// The type of [updateMessageFlags.op] and [updateMessageFlagsForNarrow.op].
/// An `op` value for [updateMessageFlags] or [updateMessageFlagsForNarrow].

The dotted syntax doesn't work for referring to a function parameter — basically because there's no such syntax in Dart itself for referring to such a thing.

return result;
}
final fakeResult = UpdateMessageFlagsForNarrowResult(
processedCount: 11, updatedCount: 11, foundOldest: true,
Copy link
Member

Choose a reason for hiding this comment

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

nit: make updatedCount less than processedCount, because it often might be

connection.prepare(json: fakeResult.toJson());
await checkUpdateMessageFlagsForNarrow(connection,
anchor: const NumericAnchor(42),
numBefore: 0, numAfter: 20,
Copy link
Member

Choose a reason for hiding this comment

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

nit: if numBefore is 0, then the real server behavior will be for foundOldest to be false (because it won't look to see if there are any older); so the test data should reflect that

(I'm not sure if that's still true when anchor is oldest, as above. Could do a quick experiment to find out. If not, then fakeResult could become a function taking {required bool foundOldest}.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirmed by both a quick experiment and looking in zerver source that anchor=oldest does send true for foundOldest.

});
});

test('narrow uses resolveLegacyElements to encode', () {
Copy link
Member

Choose a reason for hiding this comment

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

nit: put the "numeric anchor" and "narrow uses …" tests in the same order here as for getMessages, for ease of comparison

'op': 'add',
'flag': 'read',
});
connection.zulipFeatureLevel = eg.futureZulipFeatureLevel;
Copy link
Member

Choose a reason for hiding this comment

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

Let's take this line out; I feel like it's likely to just confuse someone about the semantics of FakeApiConnection.with_, which are that it's a fresh connection that's thrown away.

Comment on lines 553 to 554
return FakeApiConnection.with_((connection) async {
connection.zulipFeatureLevel = 176;
Copy link
Member

Choose a reason for hiding this comment

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

Oh in fact better yet: pass the zulipFeatureLevel to with_, rather than mutating it later. See the getMessageCompat tests for examples.

@sirpengi sirpengi force-pushed the pr-api-mark-messages-as-read branch from eabae44 to 4d2c4bd Compare November 2, 2023 14:57
@sirpengi sirpengi changed the title Api: mark messages as read Api: mark messages as read (updateMessageFlags and updateMessageFlagsForNarrow) Nov 2, 2023
@sirpengi
Copy link
Contributor Author

sirpengi commented Nov 2, 2023

@gnprice split off the last commit to #363, comments here handled and ready for another round!

@sirpengi sirpengi changed the title Api: mark messages as read (updateMessageFlags and updateMessageFlagsForNarrow) api: mark messages as read (updateMessageFlags and updateMessageFlagsForNarrow) Nov 2, 2023
@sirpengi sirpengi changed the title api: mark messages as read (updateMessageFlags and updateMessageFlagsForNarrow) api: Mark messages as read (updateMessageFlags and updateMessageFlagsForNarrow) Nov 2, 2023
@sirpengi sirpengi mentioned this pull request Nov 3, 2023
Copy link
Member

@gnprice gnprice left a 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! Generally looks good — just a few small comments below.

{'operator': 'pm-with', 'operand': [123, 234]},
]));
connection.zulipFeatureLevel = eg.futureZulipFeatureLevel;
test('narrow uses resolveLegacyElements to encode', () {
Copy link
Member

Choose a reason for hiding this comment

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

test name no longer matches code

Comment on lines 7 to 13
final supportsOperatorDm = zulipFeatureLevel >= 177; // TODO(server-7)
return (narrow.any((element) => element is ApiNarrowDm))
? narrow.map((element) => switch (element) {
ApiNarrowDm() => element.resolve(legacy: !supportsOperatorDm),
_ => element,
}).toList()
: narrow;
Copy link
Member

Choose a reason for hiding this comment

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

This feels a bit tangly to read, as I think often happens with the ?: operator when one or both branches are more than a line or two long. Let's leave it as full statement-level control flow, like it is in the existing code:

Suggested change
final supportsOperatorDm = zulipFeatureLevel >= 177; // TODO(server-7)
return (narrow.any((element) => element is ApiNarrowDm))
? narrow.map((element) => switch (element) {
ApiNarrowDm() => element.resolve(legacy: !supportsOperatorDm),
_ => element,
}).toList()
: narrow;
if (!narrow.any((element) => element is ApiNarrowDm)) {
return narrow;
}
final supportsOperatorDm = zulipFeatureLevel >= 177; // TODO(server-7)
return narrow.map((element) => switch (element) {
ApiNarrowDm() => element.resolve(legacy: !supportsOperatorDm),
_ => element,
}).toList();

return result;
}

mkResult({required bool foundOldest}) =>
Copy link
Member

Choose a reason for hiding this comment

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

function definition needs explicit return type

gnprice added a commit to gnprice/zulip-flutter that referenced this pull request Nov 4, 2023
…icit

This came up in a code review here:
  zulip#361 (comment)

and it'd be good to have written down in a more accessible place.

There are some existing parts of the API bindings -- particularly the
parts written in the very first week or two of the prototype, before I
thought through this pattern -- that don't follow this.  I'll fix
those up next.
chrisbobbe pushed a commit that referenced this pull request Nov 4, 2023
…icit

This came up in a code review here:
  #361 (comment)

and it'd be good to have written down in a more accessible place.

There are some existing parts of the API bindings -- particularly the
parts written in the very first week or two of the prototype, before I
thought through this pattern -- that don't follow this.  I'll fix
those up next.
@sirpengi sirpengi force-pushed the pr-api-mark-messages-as-read branch from 4d2c4bd to 0d16270 Compare November 6, 2023 14:51
@sirpengi
Copy link
Contributor Author

sirpengi commented Nov 6, 2023

@gnprice this is ready again!

@sirpengi sirpengi force-pushed the pr-api-mark-messages-as-read branch from 0d16270 to dfcc6ca Compare November 6, 2023 15:06
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

(Looks like I had a small followup comment pending which I'd meant to post.)

Comment on lines 367 to 368
this.firstProcessedId,
this.lastProcessedId,
Copy link
Member

Choose a reason for hiding this comment

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

I think the existing API bindings aren't 100% consistent about this

#367.

@gnprice
Copy link
Member

gnprice commented Nov 6, 2023

Thanks for the revision! Looks good; merging.

@gnprice gnprice merged commit dfcc6ca into zulip:main Nov 6, 2023
@sirpengi sirpengi deleted the pr-api-mark-messages-as-read branch January 23, 2024 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants