Skip to content

fix: dynamically calculate unread count in /subscriptions.get for iframe integration#37159

Open
JatinMehta007 wants to merge 4 commits into
RocketChat:developfrom
JatinMehta007:fix/subscriptions-unread-count
Open

fix: dynamically calculate unread count in /subscriptions.get for iframe integration#37159
JatinMehta007 wants to merge 4 commits into
RocketChat:developfrom
JatinMehta007:fix/subscriptions-unread-count

Conversation

@JatinMehta007
Copy link
Copy Markdown

@JatinMehta007 JatinMehta007 commented Oct 7, 2025

Closes : #23977
Description :
In /api/v1/subscriptions.get, the unread count was always 0 in iframe integrations, even if the channel had unread messages.

Solution :

  • Calculated unread messages dynamically for each subscription using Messages collection.
  • Preserved the API response structure: { update: [...], remove: [] }.
  • Works for iframe users where Subscriptions.unread was not updated correctly.

How Lumyst helped :
I used Lumyst to explore the Rocket.Chat codebase, identify the flow of subscription data, and understand how unread counts are calculated. It helped trace the path from API endpoint → getSubscriptions → Subscriptions collection → unreadMessages logic.

Suggestions for Lumyst :

  • Ability to search and navigate code based on file paths, not just functions or keywords. Often, open source issues reference a specific file path, so direct file-based search would speed up understanding.
  • Currently, only the first ~20,000 lines of code are inserted for analysis, which works well. In the future, it would be helpful to give users a choice to insert either a whole folder or a specific file. This would make it easier to focus on relevant parts of a codebase without having to load everything.For example, when an issue references a specific file path, the user could just insert that file for quicker analysis.

Summary by CodeRabbit

  • New Features

    • Subscriptions API now includes a per-subscription unread count in list results.
  • Refactor

    • Array responses now return an object with an update array of enriched subscriptions and an empty remove array; non-array responses return the enriched payload directly.
  • Bug Fixes

    • Stronger validation of the updatedSince query parameter with clear error responses for invalid dates.

@JatinMehta007 JatinMehta007 requested a review from a team as a code owner October 7, 2025 18:01
@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot Bot commented Oct 7, 2025

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Oct 7, 2025

⚠️ No Changeset found

Latest commit: 913f0ce

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Oct 7, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 7, 2025

Walkthrough

The subscriptions.get handler now strictly validates an optional updatedSince date, computes per-subscription unread counts only for array results (attaching an unread field), and normalizes array responses to { update, remove: [] } while returning non-array results directly. Minor formatting adjustments applied.

Changes

Cohort / File(s) Summary of edits
Subscriptions API (server v1)
apps/meteor/app/api/server/v1/subscriptions.ts
- Validate updatedSince strictly and throw error-invalid-date on invalid input
- Build updatedSinceDate only when valid
- When result is an array, map subscriptions (via Promise.all) to attach per-subscription unread counts from unreadMessages and return { update: ..., remove: [] }
- Leave non-array results unchanged (returned directly)
- Minor formatting/indentation tweaks

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant API as subscriptions.get (Server)
  participant DB as Data Layer

  C->>API: GET /api/v1/subscriptions.get?updatedSince=...
  API->>API: Validate `updatedSince` (strict)
  alt invalid date
    API-->>C: Meteor.error (code: error-invalid-date)
  else valid or absent
    API->>DB: Query subscriptions (user, optional updatedSince)
    DB-->>API: Subscriptions result (array or non-array)

    alt result is array
      loop parallel mapping (Promise.all)
        API->>DB: Compute unread count per subscription
        DB-->>API: unread number
      end
      API-->>C: { update: [subscriptions with `unread`], remove: [] }
    else non-array result
      API->>DB: (optional) compute unread
      DB-->>API: unread number
      API-->>C: return payload directly
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I twitch my ears at unread skies,
I count each dot where quiet lies—
Subscriptions now show what’s new,
Dates checked true, responses too.
Hop, attach, and send — a rabbit’s cue. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: dynamically calculate unread count in /subscriptions.get for iframe integration" directly and clearly summarizes the main change in the changeset. It accurately identifies the primary objective: reworking the subscriptions.get handler to dynamically compute and attach unread counts for iframe integrations, which addresses the core issue described in PR #23977 where unread counts were incorrectly returning 0. The title is specific, concise, and provides sufficient context (the endpoint, the capability being added, and the use case) for a developer scanning the history to understand the primary purpose of the change without being overly verbose.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a0d5072 and 12f5cf3.

📒 Files selected for processing (1)
  • apps/meteor/app/api/server/v1/subscriptions.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/app/api/server/v1/subscriptions.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/meteor/app/api/server/v1/subscriptions.ts (1)

38-48: Introduce batch unread count API for scalability.

The current countByRoomIdAndUserId call issues one DB query per subscription; for users with many subscriptions this may degrade performance. Consider adding a bulk method (e.g., countByRoomIdsAndUserId) to fetch counts for multiple rooms in a single operation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fd4f9b2 and 9e740bd.

📒 Files selected for processing (1)
  • apps/meteor/app/api/server/v1/subscriptions.ts (1 hunks)
🔇 Additional comments (1)
apps/meteor/app/api/server/v1/subscriptions.ts (1)

50-57: LGTM! Response structure preserved correctly.

The return statement correctly maintains the API response structure { update: [...], remove: [] } while enriching subscriptions with the unread count, ensuring backward compatibility.

Comment thread apps/meteor/app/api/server/v1/subscriptions.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/meteor/app/api/server/v1/subscriptions.ts (1)

50-57: Eliminate redundant array check.

The condition Array.isArray(result) is evaluated twice (lines 38 and 51). Since you've already determined the result type when computing subscriptionsWithUnread, you can simplify this logic.

Apply this diff to remove the redundant check:

-      const subscriptionsWithUnread = Array.isArray(result)
-        ? await Promise.all(
-            result.map(async (sub) => {
-              const unreadCount = await unreadMessages.countByRoomIdAndUserId(sub.rid, this.userId);
-              return {
-                ...sub,
-                unread: unreadCount,
-              };
-            })
-          )
-        : result;
+      const isArrayResult = Array.isArray(result);
+      const subscriptionsWithUnread = isArrayResult
+        ? await Promise.all(
+            result.map(async (sub) => {
+              const unreadCount = await unreadMessages.countByRoomIdAndUserId(sub.rid, this.userId);
+              return {
+                ...sub,
+                unread: unreadCount,
+              };
+            })
+          )
+        : result;

       return API.v1.success(
-        Array.isArray(result)
+        isArrayResult
           ? {
               update: subscriptionsWithUnread,
               remove: [],
             }
           : subscriptionsWithUnread
       );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9e740bd and a0d5072.

📒 Files selected for processing (1)
  • apps/meteor/app/api/server/v1/subscriptions.ts (1 hunks)
🔇 Additional comments (2)
apps/meteor/app/api/server/v1/subscriptions.ts (2)

25-34: Date validation looks good.

The explicit validation and error handling for the updatedSince parameter is appropriate, and the error message correctly references the parameter name.


38-48: Now let me search the codebase directly for the subscriptions.ts file to examine the actual context:

N+1 query pattern confirmed - significant performance concern for high-volume subscriptions.

After thorough investigation, there is no easy way to retrieve unread counts for multiple rooms without creating multiple requests. The code at lines 38–48 calls unreadMessages.countByRoomIdAndUserId() once per subscription in the array, creating as many database queries as there are subscriptions. For users with dozens or hundreds of subscriptions, this creates corresponding database load.

Based on codebase analysis:

  • No batch-capable method exists for counting unread messages across multiple rooms
  • This is a known limitation acknowledged in the project's issue history
  • Alternative approaches to consider:
    1. Implement a batch method in the unreadMessages service that accepts multiple room IDs
    2. Use aggregation pipeline to compute all unread counts in a single query
    3. Pre-fetch and cache unread counts at a higher level before iterating subscriptions

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.

Private message unread count 0 for normal user

2 participants