Mark chat mention notifications as read when viewing the message

Max Brunsfeld created

Change summary

crates/channel/src/channel_chat.rs            | 12 ++++++++
crates/collab/src/db/queries/channels.rs      |  2 
crates/collab/src/db/queries/contacts.rs      |  2 
crates/collab/src/db/queries/messages.rs      | 18 ++++++++++-
crates/collab/src/db/queries/notifications.rs | 31 ++++++++++++++++++--
crates/collab/src/rpc.rs                      |  7 ++++
crates/collab_ui/src/chat_panel.rs            | 10 ++++++
7 files changed, 73 insertions(+), 9 deletions(-)

Detailed changes

crates/channel/src/channel_chat.rs 🔗

@@ -370,6 +370,18 @@ impl ChannelChat {
         cursor.item().unwrap()
     }
 
+    pub fn rendered_message(&self, id: ChannelMessageId) {
+        let ChannelMessageId::Saved(id) = id else {
+            return;
+        };
+        self.rpc
+            .send(proto::AckChannelMessage {
+                channel_id: self.channel.id,
+                message_id: id,
+            })
+            .ok();
+    }
+
     pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
         let mut cursor = self.messages.cursor::<Count>();
         cursor.seek(&Count(range.start), Bias::Right, &());

crates/collab/src/db/queries/channels.rs 🔗

@@ -386,7 +386,7 @@ impl Database {
             }
 
             Ok(self
-                .respond_to_notification(
+                .mark_notification_as_read_with_response(
                     user_id,
                     &rpc::Notification::ChannelInvitation {
                         channel_id: channel_id.to_proto(),

crates/collab/src/db/queries/contacts.rs 🔗

@@ -321,7 +321,7 @@ impl Database {
 
             let mut notifications = Vec::new();
             notifications.extend(
-                self.respond_to_notification(
+                self.mark_notification_as_read_with_response(
                     responder_id,
                     &rpc::Notification::ContactRequest {
                         sender_id: requester_id.to_proto(),

crates/collab/src/db/queries/messages.rs 🔗

@@ -1,5 +1,6 @@
 use super::*;
 use futures::Stream;
+use rpc::Notification;
 use sea_orm::TryInsertResult;
 use time::OffsetDateTime;
 
@@ -326,11 +327,24 @@ impl Database {
         channel_id: ChannelId,
         user_id: UserId,
         message_id: MessageId,
-    ) -> Result<()> {
+    ) -> Result<NotificationBatch> {
         self.transaction(|tx| async move {
             self.observe_channel_message_internal(channel_id, user_id, message_id, &*tx)
                 .await?;
-            Ok(())
+            let mut batch = NotificationBatch::default();
+            batch.extend(
+                self.mark_notification_as_read(
+                    user_id,
+                    &Notification::ChannelMessageMention {
+                        message_id: message_id.to_proto(),
+                        sender_id: Default::default(),
+                        channel_id: Default::default(),
+                    },
+                    &*tx,
+                )
+                .await?,
+            );
+            Ok(batch)
         })
         .await
     }

crates/collab/src/db/queries/notifications.rs 🔗

@@ -129,22 +129,47 @@ impl Database {
 
     /// Populate the response for the notification with the given kind and
     /// entity id.
-    pub async fn respond_to_notification(
+    pub async fn mark_notification_as_read_with_response(
         &self,
         recipient_id: UserId,
         notification: &Notification,
         response: bool,
         tx: &DatabaseTransaction,
+    ) -> Result<Option<(UserId, proto::Notification)>> {
+        self.mark_notification_as_read_internal(recipient_id, notification, Some(response), tx)
+            .await
+    }
+
+    pub async fn mark_notification_as_read(
+        &self,
+        recipient_id: UserId,
+        notification: &Notification,
+        tx: &DatabaseTransaction,
+    ) -> Result<Option<(UserId, proto::Notification)>> {
+        self.mark_notification_as_read_internal(recipient_id, notification, None, tx)
+            .await
+    }
+
+    async fn mark_notification_as_read_internal(
+        &self,
+        recipient_id: UserId,
+        notification: &Notification,
+        response: Option<bool>,
+        tx: &DatabaseTransaction,
     ) -> Result<Option<(UserId, proto::Notification)>> {
         if let Some(id) = self
-            .find_notification(recipient_id, notification, tx)
+            .find_notification(recipient_id, notification, &*tx)
             .await?
         {
             let row = notification::Entity::update(notification::ActiveModel {
                 id: ActiveValue::Unchanged(id),
                 recipient_id: ActiveValue::Unchanged(recipient_id),
-                response: ActiveValue::Set(Some(response)),
                 is_read: ActiveValue::Set(true),
+                response: if let Some(response) = response {
+                    ActiveValue::Set(Some(response))
+                } else {
+                    ActiveValue::NotSet
+                },
                 ..Default::default()
             })
             .exec(tx)

crates/collab/src/rpc.rs 🔗

@@ -3061,11 +3061,16 @@ async fn acknowledge_channel_message(
 ) -> Result<()> {
     let channel_id = ChannelId::from_proto(request.channel_id);
     let message_id = MessageId::from_proto(request.message_id);
-    session
+    let notifications = session
         .db()
         .await
         .observe_channel_message(channel_id, session.user_id, message_id)
         .await?;
+    send_notifications(
+        &*session.connection_pool().await,
+        &session.peer,
+        notifications,
+    );
     Ok(())
 }
 

crates/collab_ui/src/chat_panel.rs 🔗

@@ -357,8 +357,16 @@ impl ChatPanel {
             let is_continuation = last_message.id != this_message.id
                 && this_message.sender.id == last_message.sender.id;
 
+            if this_message
+                .mentions
+                .iter()
+                .any(|(_, user_id)| Some(*user_id) == self.client.user_id())
+            {
+                active_chat.rendered_message(this_message.id);
+            }
+
             (
-                active_chat.message(ix).clone(),
+                this_message.clone(),
                 is_continuation,
                 active_chat.message_count() == ix + 1,
                 is_admin,