messages: Simplify API for use_first_unread_anchor.

Now that we have the type situation of having anchor support passing a
string, this is a much more natural way to implement
use_first_unread_anchor.

We still support the old interface to avoid breaking compatibility
with legacy versions of the mobile apps.
This commit is contained in:
Tim Abbott
2020-01-28 18:29:15 -08:00
parent 7bf3312114
commit b25fea24e7
7 changed files with 43 additions and 35 deletions

View File

@@ -188,7 +188,6 @@ run_test('basics', () => {
cont: opts.cont, cont: opts.cont,
pre_scroll_cont: opts.pre_scroll_cont, pre_scroll_cont: opts.pre_scroll_cont,
anchor: 1000, anchor: 1000,
use_first_unread_anchor: false,
}); });
}; };

View File

@@ -162,9 +162,6 @@ exports.load_messages = function (opts) {
if (opts.msg_list === home_msg_list && page_params.narrow_stream !== undefined) { if (opts.msg_list === home_msg_list && page_params.narrow_stream !== undefined) {
data.narrow = JSON.stringify(page_params.narrow); data.narrow = JSON.stringify(page_params.narrow);
} }
if (opts.use_first_unread_anchor) {
data.use_first_unread_anchor = true;
}
if (opts.num_before > 0) { if (opts.num_before > 0) {
opts.msg_list.fetch_status.start_older_batch(); opts.msg_list.fetch_status.start_older_batch();
@@ -226,7 +223,6 @@ exports.load_messages_for_narrow = function (opts) {
num_before: consts.narrow_before, num_before: consts.narrow_before,
num_after: consts.narrow_after, num_after: consts.narrow_after,
msg_list: msg_list, msg_list: msg_list,
use_first_unread_anchor: opts.use_first_unread_anchor,
pre_scroll_cont: opts.pre_scroll_cont, pre_scroll_cont: opts.pre_scroll_cont,
cont: function () { cont: function () {
message_scroll.hide_indicators(); message_scroll.hide_indicators();

View File

@@ -222,7 +222,6 @@ exports.activate = function (raw_operators, opts) {
(function fetch_messages() { (function fetch_messages() {
let anchor; let anchor;
let use_first_unread;
// Either we're trying to center the narrow around a // Either we're trying to center the narrow around a
// particular message ID (which could be max_int), or we're // particular message ID (which could be max_int), or we're
@@ -230,15 +229,12 @@ exports.activate = function (raw_operators, opts) {
// unread message is, and center the narrow around that. // unread message is, and center the narrow around that.
if (id_info.final_select_id !== undefined) { if (id_info.final_select_id !== undefined) {
anchor = id_info.final_select_id; anchor = id_info.final_select_id;
use_first_unread = false;
} else { } else {
anchor = -1; anchor = "first_unread";
use_first_unread = true;
} }
message_fetch.load_messages_for_narrow({ message_fetch.load_messages_for_narrow({
anchor: anchor, anchor: anchor,
use_first_unread_anchor: use_first_unread,
cont: function () { cont: function () {
if (!select_immediately) { if (!select_immediately) {
exports.update_selection({ exports.update_selection({

View File

@@ -1371,7 +1371,7 @@ class MessageDictTest(ZulipTestCase):
'/json/messages?use_first_unread_anchor=false&num_before=1&num_after=1') '/json/messages?use_first_unread_anchor=false&num_before=1&num_after=1')
self.assert_json_error( self.assert_json_error(
result, "Missing 'anchor' argument (or set 'use_first_unread_anchor'=True).") result, "Missing 'anchor' argument.")
def test_invalid_anchor(self) -> None: def test_invalid_anchor(self) -> None:
self.login(self.example_email("hamlet")) self.login(self.example_email("hamlet"))

View File

@@ -2447,8 +2447,7 @@ class GetOldMessagesTest(ZulipTestCase):
# search still gets the first message sent to Hamlet (before he # search still gets the first message sent to Hamlet (before he
# subscribed) and other recent messages to the stream. # subscribed) and other recent messages to the stream.
query_params = dict( query_params = dict(
use_first_unread_anchor='true', anchor="first_unread",
anchor=0,
num_before=10, num_before=10,
num_after=10, num_after=10,
narrow='[["stream", "England"]]' narrow='[["stream", "England"]]'
@@ -2484,8 +2483,7 @@ class GetOldMessagesTest(ZulipTestCase):
self.send_personal_message(self.example_email("othello"), self.example_email("iago")) self.send_personal_message(self.example_email("othello"), self.example_email("iago"))
query_params = dict( query_params = dict(
use_first_unread_anchor='true', anchor="first_unread",
anchor=0,
num_before=10, num_before=10,
num_after=10, num_after=10,
narrow='[]' narrow='[]'
@@ -2532,8 +2530,7 @@ class GetOldMessagesTest(ZulipTestCase):
self.send_personal_message(self.example_email("othello"), self.example_email("iago")) self.send_personal_message(self.example_email("othello"), self.example_email("iago"))
query_params = dict( query_params = dict(
use_first_unread_anchor='true', anchor="first_unread",
anchor=0,
num_before=10, num_before=10,
num_after=10, num_after=10,
narrow='[]' narrow='[]'
@@ -2563,8 +2560,7 @@ class GetOldMessagesTest(ZulipTestCase):
user_profile = self.example_user('hamlet') user_profile = self.example_user('hamlet')
query_params = dict( query_params = dict(
use_first_unread_anchor='true', anchor="first_unread",
anchor=0,
num_before=10, num_before=10,
num_after=10, num_after=10,
narrow='[]' narrow='[]'
@@ -2616,8 +2612,7 @@ class GetOldMessagesTest(ZulipTestCase):
set_topic_mutes(user_profile, muted_topics) set_topic_mutes(user_profile, muted_topics)
query_params = dict( query_params = dict(
use_first_unread_anchor='true', anchor="first_unread",
anchor=0,
num_before=0, num_before=0,
num_after=0, num_after=0,
narrow='[["stream", "Scotland"]]' narrow='[["stream", "Scotland"]]'

View File

@@ -110,12 +110,18 @@ class PointerTest(ZulipTestCase):
# If we call get_messages with use_first_unread_anchor=True, we # If we call get_messages with use_first_unread_anchor=True, we
# should get the message we just sent # should get the message we just sent
messages_response = self.get_messages_response(
anchor="first_unread", num_before=0, num_after=1)
self.assertEqual(messages_response['messages'][0]['id'], new_message_id)
self.assertEqual(messages_response['anchor'], new_message_id)
# Test with the old way of expressing use_first_unread_anchor=True
messages_response = self.get_messages_response( messages_response = self.get_messages_response(
anchor=0, num_before=0, num_after=1, use_first_unread_anchor=True) anchor=0, num_before=0, num_after=1, use_first_unread_anchor=True)
self.assertEqual(messages_response['messages'][0]['id'], new_message_id) self.assertEqual(messages_response['messages'][0]['id'], new_message_id)
self.assertEqual(messages_response['anchor'], new_message_id) self.assertEqual(messages_response['anchor'], new_message_id)
# We want to get the message_id of an arbitrar old message. We can # We want to get the message_id of an arbitrary old message. We can
# call get_messages with use_first_unread_anchor=False and simply # call get_messages with use_first_unread_anchor=False and simply
# save the first message we're returned. # save the first message we're returned.
messages = self.get_messages( messages = self.get_messages(
@@ -145,7 +151,7 @@ class PointerTest(ZulipTestCase):
# Now if we call get_messages with use_first_unread_anchor=True, # Now if we call get_messages with use_first_unread_anchor=True,
# we should get the old message we just set to unread # we should get the old message we just set to unread
messages_response = self.get_messages_response( messages_response = self.get_messages_response(
anchor=0, num_before=0, num_after=1, use_first_unread_anchor=True) anchor="first_unread", num_before=0, num_after=1)
self.assertEqual(messages_response['messages'][0]['id'], old_message_id) self.assertEqual(messages_response['messages'][0]['id'], old_message_id)
self.assertEqual(messages_response['anchor'], old_message_id) self.assertEqual(messages_response['anchor'], old_message_id)
@@ -167,7 +173,7 @@ class PointerTest(ZulipTestCase):
# we should not get the old unread message (because it's before the # we should not get the old unread message (because it's before the
# pointer), and instead should get the newly sent unread message # pointer), and instead should get the newly sent unread message
messages_response = self.get_messages_response( messages_response = self.get_messages_response(
anchor=0, num_before=0, num_after=1, use_first_unread_anchor=True) anchor="first_unread", num_before=0, num_after=1)
self.assertEqual(messages_response['messages'][0]['id'], new_message_id) self.assertEqual(messages_response['messages'][0]['id'], new_message_id)
self.assertEqual(messages_response['anchor'], new_message_id) self.assertEqual(messages_response['anchor'], new_message_id)
@@ -182,25 +188,25 @@ class PointerTest(ZulipTestCase):
"test") "test")
messages_response = self.get_messages_response( messages_response = self.get_messages_response(
anchor=0, num_before=0, num_after=1, use_first_unread_anchor=True) anchor="first_unread", num_before=0, num_after=1)
self.assertEqual(messages_response['messages'][0]['id'], new_message_id) self.assertEqual(messages_response['messages'][0]['id'], new_message_id)
self.assertEqual(messages_response['anchor'], new_message_id) self.assertEqual(messages_response['anchor'], new_message_id)
with mock.patch('zerver.views.messages.get_first_visible_message_id', return_value=new_message_id): with mock.patch('zerver.views.messages.get_first_visible_message_id', return_value=new_message_id):
messages_response = self.get_messages_response( messages_response = self.get_messages_response(
anchor=0, num_before=0, num_after=1, use_first_unread_anchor=True) anchor="first_unread", num_before=0, num_after=1)
self.assertEqual(messages_response['messages'][0]['id'], new_message_id) self.assertEqual(messages_response['messages'][0]['id'], new_message_id)
self.assertEqual(messages_response['anchor'], new_message_id) self.assertEqual(messages_response['anchor'], new_message_id)
with mock.patch('zerver.views.messages.get_first_visible_message_id', return_value=new_message_id + 1): with mock.patch('zerver.views.messages.get_first_visible_message_id', return_value=new_message_id + 1):
messages_reponse = self.get_messages_response( messages_reponse = self.get_messages_response(
anchor=0, num_before=0, num_after=1, use_first_unread_anchor=True) anchor="first_unread", num_before=0, num_after=1)
self.assert_length(messages_reponse['messages'], 0) self.assert_length(messages_reponse['messages'], 0)
self.assertIn('anchor', messages_reponse) self.assertIn('anchor', messages_reponse)
with mock.patch('zerver.views.messages.get_first_visible_message_id', return_value=new_message_id - 1): with mock.patch('zerver.views.messages.get_first_visible_message_id', return_value=new_message_id - 1):
messages = self.get_messages( messages = self.get_messages(
anchor=0, num_before=0, num_after=1, use_first_unread_anchor=True) anchor="first_unread", num_before=0, num_after=1)
self.assert_length(messages, 1) self.assert_length(messages, 1)
class UnreadCountTests(ZulipTestCase): class UnreadCountTests(ZulipTestCase):

View File

@@ -764,13 +764,29 @@ def zcommand_backend(request: HttpRequest, user_profile: UserProfile,
command: str=REQ('command')) -> HttpResponse: command: str=REQ('command')) -> HttpResponse:
return json_success(process_zcommands(command, user_profile)) return json_success(process_zcommands(command, user_profile))
def parse_anchor_value(anchor_val: Optional[str]) -> Optional[int]: def parse_anchor_value(anchor_val: Optional[str],
if anchor_val is None: use_first_unread_anchor: bool) -> Optional[int]:
"""Given the anchor and use_first_unread_anchor parameters passed by
the client, computes what anchor value the client requested,
handling backwards-compatibility and the various string-valued
fields. We encode use_first_unread_anchor as anchor=None.
"""
if use_first_unread_anchor:
# Backwards-compatibility: Before we added support for the
# special string-typed anchor values, clients would pass
# anchor=None and use_first_unread_anchor=True to indicate
# what is now expressed as anchor="first_unread".
return None return None
if anchor_val is None:
# Throw an exception if neither an anchor argument not
# use_first_unread_anchor was specified.
raise JsonableError(_("Missing 'anchor' argument."))
if anchor_val == "oldest": if anchor_val == "oldest":
return 0 return 0
if anchor_val == "newest": if anchor_val == "newest":
return LARGER_THAN_MAX_MESSAGE_ID return LARGER_THAN_MAX_MESSAGE_ID
if anchor_val == "first_unread":
return None
try: try:
# We don't use `.isnumeric()` to support negative numbers for # We don't use `.isnumeric()` to support negative numbers for
# anchor. We don't recommend it in the API (if you want the # anchor. We don't recommend it in the API (if you want the
@@ -791,12 +807,11 @@ def get_messages_backend(request: HttpRequest, user_profile: UserProfile,
num_before: int=REQ(converter=to_non_negative_int), num_before: int=REQ(converter=to_non_negative_int),
num_after: int=REQ(converter=to_non_negative_int), num_after: int=REQ(converter=to_non_negative_int),
narrow: OptionalNarrowListT=REQ('narrow', converter=narrow_parameter, default=None), narrow: OptionalNarrowListT=REQ('narrow', converter=narrow_parameter, default=None),
use_first_unread_anchor: bool=REQ(validator=check_bool, default=False), use_first_unread_anchor_val: bool=REQ('use_first_unread_anchor',
validator=check_bool, default=False),
client_gravatar: bool=REQ(validator=check_bool, default=False), client_gravatar: bool=REQ(validator=check_bool, default=False),
apply_markdown: bool=REQ(validator=check_bool, default=True)) -> HttpResponse: apply_markdown: bool=REQ(validator=check_bool, default=True)) -> HttpResponse:
anchor = parse_anchor_value(anchor_val) anchor = parse_anchor_value(anchor_val, use_first_unread_anchor_val)
if anchor is None and not use_first_unread_anchor:
return json_error(_("Missing 'anchor' argument (or set 'use_first_unread_anchor'=True)."))
if num_before + num_after > MAX_MESSAGES_PER_FETCH: if num_before + num_after > MAX_MESSAGES_PER_FETCH:
return json_error(_("Too many messages requested (maximum %s).") return json_error(_("Too many messages requested (maximum %s).")
% (MAX_MESSAGES_PER_FETCH,)) % (MAX_MESSAGES_PER_FETCH,))
@@ -850,7 +865,8 @@ def get_messages_backend(request: HttpRequest, user_profile: UserProfile,
sa_conn = get_sqlalchemy_connection() sa_conn = get_sqlalchemy_connection()
if use_first_unread_anchor: if anchor is None:
# The use_first_unread_anchor code path
anchor = find_first_unread_anchor( anchor = find_first_unread_anchor(
sa_conn, sa_conn,
user_profile, user_profile,