events: Add functionality to mark messages as unread.

Co-authored-by: Steve Howell <showell@zulip.com>
Co-authored-by: Tim Abbott <tabbott@zulip.com>

This commit adds the backend functionality to
mark messages as unread through update_message_flags
with `unread` flag and `remove` operation.

We also manage incoming events in the webapp.

Tweaked by tabbott to simplify the implementation and add an API
feature level update to the documentation.

This commit was originally drafted by showell, and showell
also finalized the changes.  Many thanks to Suyash here for
the main work here, which was to get all the tests and
documentation work moving forward.
This commit is contained in:
Suyash Vardhan Mathur
2021-06-09 11:31:39 +00:00
committed by Tim Abbott
parent bf890cf91a
commit 20a97bdb05
16 changed files with 993 additions and 15 deletions

View File

@@ -56,6 +56,15 @@ from zerver.models import (
)
class MessageDetailsDict(TypedDict, total=False):
type: str
mentioned: bool
user_ids: List[int]
stream_id: int
topic: str
unmuted_stream_msg: bool
class RawReactionRow(TypedDict):
emoji_code: str
emoji_name: str
@@ -976,7 +985,9 @@ def get_starred_message_ids(user_profile: UserProfile) -> List[int]:
)
def get_raw_unread_data(user_profile: UserProfile) -> RawUnreadMessagesResult:
def get_raw_unread_data(
user_profile: UserProfile, message_ids: Optional[List[int]] = None
) -> RawUnreadMessagesResult:
excluded_recipient_ids = get_inactive_recipient_ids(user_profile)
user_msgs = (
@@ -986,9 +997,6 @@ def get_raw_unread_data(user_profile: UserProfile) -> RawUnreadMessagesResult:
.exclude(
message__recipient_id__in=excluded_recipient_ids,
)
.extra(
where=[UserMessage.where_unread()],
)
.values(
"message_id",
"message__sender_id",
@@ -1001,6 +1009,16 @@ def get_raw_unread_data(user_profile: UserProfile) -> RawUnreadMessagesResult:
.order_by("-message_id")
)
if message_ids is not None:
# When users are marking just a few messages as unread, we just need
# those ids, and we know they're unread.
user_msgs = user_msgs.filter(message_id__in=message_ids)
else:
# At page load we need all unread messages.
user_msgs = user_msgs.extra(
where=[UserMessage.where_unread()],
)
# Limit unread messages for performance reasons.
user_msgs = list(user_msgs[:MAX_UNREAD_MESSAGES])
@@ -1283,6 +1301,98 @@ def remove_message_id_from_unread_mgs(state: RawUnreadMessagesResult, message_id
state["mentions"].discard(message_id)
def format_unread_message_details(
my_user_id: int,
raw_unread_data: RawUnreadMessagesResult,
) -> Dict[str, MessageDetailsDict]:
unread_data = {}
for message_id, private_message_details in raw_unread_data["pm_dict"].items():
other_user_id = private_message_details["other_user_id"]
if other_user_id == my_user_id:
user_ids = []
else:
user_ids = [other_user_id]
# Note that user_ids excludes ourself, even for the case we send messages
# to ourself.
message_details = MessageDetailsDict(
type="private",
user_ids=user_ids,
)
if message_id in raw_unread_data["mentions"]:
message_details["mentioned"] = True
unread_data[str(message_id)] = message_details
for message_id, stream_message_details in raw_unread_data["stream_dict"].items():
if message_id in raw_unread_data["unmuted_stream_msgs"]:
unmuted_stream_msg = True
else:
unmuted_stream_msg = False
message_details = MessageDetailsDict(
type="stream",
stream_id=stream_message_details["stream_id"],
topic=stream_message_details["topic"],
# Clients don't need this detail, but we need it internally for apply_events.
unmuted_stream_msg=unmuted_stream_msg,
)
if message_id in raw_unread_data["mentions"]:
message_details["mentioned"] = True
unread_data[str(message_id)] = message_details
for message_id, huddle_message_details in raw_unread_data["huddle_dict"].items():
# The client wants a list of user_ids in the conversation, excluding ourself,
# that is sorted in numerical order.
user_ids = [int(s) for s in huddle_message_details["user_ids_string"].split(",")]
user_ids = [user_id for user_id in user_ids if user_id != my_user_id]
user_ids.sort()
message_details = MessageDetailsDict(
type="private",
user_ids=user_ids,
)
if message_id in raw_unread_data["mentions"]:
message_details["mentioned"] = True
unread_data[str(message_id)] = message_details
return unread_data
def add_message_to_unread_msgs(
my_user_id: int,
state: RawUnreadMessagesResult,
message_id: int,
message_details: MessageDetailsDict,
) -> None:
if message_details.get("mentioned"):
state["mentions"].add(message_id)
if message_details["type"] == "private":
user_ids: List[int] = message_details["user_ids"]
user_ids = [user_id for user_id in user_ids if user_id != my_user_id]
if user_ids == []:
state["pm_dict"][message_id] = RawUnreadPrivateMessageDict(
other_user_id=my_user_id,
)
elif len(user_ids) == 1:
state["pm_dict"][message_id] = RawUnreadPrivateMessageDict(
other_user_id=user_ids[0],
)
else:
user_ids.append(my_user_id)
user_ids_string = ",".join(str(user_id) for user_id in sorted(user_ids))
state["huddle_dict"][message_id] = RawUnreadHuddleDict(
user_ids_string=user_ids_string,
)
elif message_details["type"] == "stream":
state["stream_dict"][message_id] = RawUnreadStreamDict(
stream_id=message_details["stream_id"],
topic=message_details["topic"],
)
if message_details["unmuted_stream_msg"]:
state["unmuted_stream_msgs"].add(message_id)
def estimate_recent_messages(realm: Realm, hours: int) -> int:
stat = COUNT_STATS["messages_sent:is_bot:hour"]
d = timezone_now() - datetime.timedelta(hours=hours)