FIX: Serialize unread_count for direct message channels (#222)

FIX: Serialize unread_count for direct message channels (#222)

diff --git a/assets/javascripts/discourse/components/chat-live-pane.js b/assets/javascripts/discourse/components/chat-live-pane.js
index 54cad3a..f55d03e 100644
--- a/assets/javascripts/discourse/components/chat-live-pane.js
+++ b/assets/javascripts/discourse/components/chat-live-pane.js
@@ -573,15 +573,21 @@ export default Component.extend({
         if (this.messages?.length) {
           messageId = this.messages[this.messages.length - 1]?.id;
         }
-        // Make sure new messages have come in. Do not keep pinging server with read updates
-        // if no new messages came in since last read update was sent.
+        const hasUnreadMessage =
+          messageId && messageId !== this.lastSendReadMessageId;
+
         if (
-          document.hasFocus() &&
-          this.expanded &&
-          !this.floatHidden &&
-          messageId &&
-          messageId !== this.lastSendReadMessageId
+          !hasUnreadMessage &&
+          this.currentUser.chat_channel_tracking_state[this.chatChannel.id]
+            .unread_count > 0
         ) {
+          // Weird state here where the chat_channel_tracking_state is wrong. Need to reset it.
+          this.chat.resetTrackingStateForChannel(this.chatChannel.id);
+        }
+
+        // Make sure new messages have come in. Do not keep pinging server with read updates
+        // if no new messages came in since last read update was sent.
+        if (this._floatOpenAndFocused() && hasUnreadMessage) {
           this.set("lastSendReadMessageId", messageId);
           ajax(`/chat/${this.chatChannel.id}/read/${messageId}.json`, {
             method: "PUT",
@@ -596,6 +602,10 @@ export default Component.extend({
     );
   },
 
+  _floatOpenAndFocused() {
+    return document.hasFocus() && this.expanded && !this.floatHidden;
+  },
+
   _stopLastReadRunner() {
     cancel(this._updateReadTimer);
   },
diff --git a/assets/javascripts/discourse/services/chat.js b/assets/javascripts/discourse/services/chat.js
index d8406c3..0716326 100644
--- a/assets/javascripts/discourse/services/chat.js
+++ b/assets/javascripts/discourse/services/chat.js
@@ -472,6 +472,16 @@ export default Service.extend({
     );
   },
 
+  resetTrackingStateForChannel(channelId) {
+    const trackingState = this.currentUser.chat_channel_tracking_state[
+      channelId
+    ];
+    if (trackingState) {
+      trackingState.unread_count = 0;
+      this.userChatChannelTrackingStateChanged();
+    }
+  },
+
   userChatChannelTrackingStateChanged() {
     this._recalculateUnreadMessages();
   },
diff --git a/lib/chat_channel_fetcher.rb b/lib/chat_channel_fetcher.rb
index e817e2b..4f047fc 100644
--- a/lib/chat_channel_fetcher.rb
+++ b/lib/chat_channel_fetcher.rb
@@ -69,20 +69,12 @@ module DiscourseChat::ChatChannelFetcher
 
       membership = memberships.detect { |m| m.chat_channel_id == channel.id }
       if membership
-        channel.last_read_message_id = membership.last_read_message_id
-        channel.muted = membership.muted
-        if (!channel.muted)
-          channel.unread_count = channel.chat_messages.count { |message|
-            message.user_id != guardian.user.id && message.id > (membership.last_read_message_id || 0)
-          }
-        end
-        channel.unread_mentions = mention_notification_data.count { |data|
-          data["chat_channel_id"] == channel.id &&
-            data["chat_message_id"] > (membership.last_read_message_id || 0)
-        }
-        channel.following = membership.following
-        channel.desktop_notification_level = membership.desktop_notification_level
-        channel.mobile_notification_level = membership.mobile_notification_level
+        channel = decorate_channel_from_membership(
+          guardian.user.id,
+          channel,
+          membership,
+          mention_notification_data
+        )
       end
 
       secured.push(channel)
@@ -90,14 +82,43 @@ module DiscourseChat::ChatChannelFetcher
     secured
   end
 
+  def self.decorate_channel_from_membership(user_id, channel, membership, mention_notification_data = nil)
+    channel.last_read_message_id = membership.last_read_message_id
+    channel.muted = membership.muted
+    if (!channel.muted)
+      channel.unread_count = channel.chat_messages.count { |message|
+        message.user_id != user_id && message.id > (membership.last_read_message_id || 0)
+      }
+    end
+    if mention_notification_data
+      channel.unread_mentions = mention_notification_data.count { |data|
+        data["chat_channel_id"] == channel.id &&
+          data["chat_message_id"] > (membership.last_read_message_id || 0)
+      }
+    end
+    channel.following = membership.following
+    channel.desktop_notification_level = membership.desktop_notification_level
+    channel.mobile_notification_level = membership.mobile_notification_level
+    channel
+  end
+
   def self.secured_direct_message_channels(user_id, memberships, include_chatables: false)
-    channels = ChatChannel
+    channels = ChatChannel.includes(:chat_messages)
     channels = channels.includes(chatable: { direct_message_users: :user }) if include_chatables
-    channels
+    channels = channels
       .joins(:user_chat_channel_memberships)
       .where(user_chat_channel_memberships: { user_id: user_id, following: true })
       .where(chatable_type: "DirectMessageChannel")
       .order(updated_at: :desc)
       .limit(10)
+      .to_a
+
+    channels.map do |channel|
+      decorate_channel_from_membership(
+        user_id,
+        channel,
+        memberships.detect { |m| m.user_id == user_id && m.chat_channel_id == channel.id }
+      )
+    end
   end
 end
diff --git a/spec/requests/chat_channel_controller_spec.rb b/spec/requests/chat_channel_controller_spec.rb
index 6a78bc9..33d108f 100644
--- a/spec/requests/chat_channel_controller_spec.rb
+++ b/spec/requests/chat_channel_controller_spec.rb
@@ -169,6 +169,18 @@ RSpec.describe DiscourseChat::ChatChannelsController do
           expect(response.parsed_body["direct_message_channels"].map { |c| c["id"] })
             .to match_array([@dm2.id, @dm3.id, @dm4.id])
         end
+
+        it "correctly set unread_count for DMs" do
+          sign_in(user3)
+          DiscourseChat::ChatMessageCreator.create(
+            chat_channel: @dm2,
+            user: user1,
+            content: "What's going on?!"
+          )
+          get "/chat/chat_channels.json"
+          dm2_response = response.parsed_body["direct_message_channels"].detect { |c| c["id"] == @dm2.id }
+          expect(dm2_response["unread_count"]).to eq(1)
+        end
       end
     end
   end

GitHub sha: 0f7fa043525be12d833579a84986d10833544cc3

This commit appears in #222 which was approved by eviltrout. It was merged by markvanlan.