Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: read receipts on clear chat requests #744

Merged
merged 2 commits into from
Nov 26, 2024

Conversation

FleetAdmiralJakob
Copy link
Owner

@FleetAdmiralJakob FleetAdmiralJakob commented Nov 26, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced message reading functionality with improved logic for marking messages as read.
    • Introduced new relationships for tracking read requests associated with messages and clear requests.
  • Bug Fixes

    • Simplified conditions for determining read status, improving clarity and accuracy in message handling.
  • Documentation

    • Updated schema to reflect new relationships and edges for better data tracking.
  • Style

    • Adjusted layout and styling of message components for a consistent user experience.

Copy link

vercel bot commented Nov 26, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
chat-io ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 26, 2024 0:38am

Copy link

coderabbitai bot commented Nov 26, 2024

Walkthrough

The pull request introduces modifications across several files, primarily focusing on enhancing the functionality of chat and message handling. Key changes include optimizing the getChats query to efficiently retrieve user read statuses, updating the createClearRequest mutation to include a readBy property, and restructuring the markMessageRead mutation to handle both message and clear request IDs. Additionally, the schema is updated to strengthen relationships between users and clear requests, while the message component's rendering logic is refined for clarity and usability.

Changes

File Change Summary
convex/chats.ts Updated getChats query handler to optimize data fetching with Promise.all, modified the structure of the returned request object to include readBy data, and refined logic for determining message read status.
convex/clearRequests.ts Modified createClearRequest mutation to include a new readBy property initialized with the convexUser ID. No changes to other mutations.
convex/messages.ts Updated markMessageRead mutation to accept a union type for messageId, restructuring logic to process both message and clear request IDs, with consistent error handling for unauthenticated calls.
convex/schema.ts Added readRequests edge in users entity and readBy edge in clearRequests entity to enhance relationship mapping for read statuses.
src/components/message.tsx Simplified conditional logic for marking messages as read, refined rendering logic for deleted messages, and streamlined modal handling for message actions while maintaining overall control flow for user interactions.

Possibly related PRs

  • feat: edit messages #650: The changes in convex/messages.ts related to the editMessage mutation are relevant as they enhance message handling, which is closely tied to the modifications in the getChats function in the main PR.
  • feat: reply functionality #676: The updates in convex/messages.ts regarding the createMessage function and the handling of replies are related to the changes in the getChats function, as both involve message management and user interactions.
  • feat: show how long till expiring #737: The addition of the getTimeRemaining function in src/components/message.tsx enhances the user interface for pending requests, which is relevant to the changes made in the getChats function that also deals with message states and user interactions.

🐇 In the chat where messages flow,
A rabbit hops, and changes grow.
With read requests now in sight,
Clear requests shine, oh what a delight!
Simplified logic, a clearer way,
In the world of chats, we leap and play! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 3

🧹 Outside diff range and nitpick comments (5)
convex/schema.ts (1)

40-44: Consider grouping similar edge types together

While the edge definition is correct, consider moving it after the single edges (user and privateChat) to maintain a consistent pattern of grouping similar edge types together. This would improve code organization and readability.

  clearRequests: defineEnt({})
    .field(
      "status",
      v.union(
        v.literal("pending"),
        v.literal("rejected"),
        v.literal("expired"),
      ),
    )
-   .edges("readBy", {
-     to: "users",
-     inverseField: "readRequests",
-     table: "readRequests",
-   })
    .edge("user")
    .edge("privateChat")
+   .edges("readBy", {
+     to: "users",
+     inverseField: "readRequests",
+     table: "readRequests",
+   })
convex/clearRequests.ts (1)

Line range hint 134-162: Consider read receipt handling in request state changes

The rejectClearRequest and acceptClearRequest mutations don't interact with the new readBy field. Consider whether:

  1. Changing a request's status should affect its read status
  2. Historical read data should be preserved when clearing chat history

This might impact analytics or user experience features that rely on read receipt data.

src/components/message.tsx (3)

Line range hint 1-607: Consider breaking down the Message component for better maintainability

The component handles multiple responsibilities and could benefit from being split into smaller, focused components:

  • MessageActions (context menu)
  • MessageContent
  • ReadReceipt
  • ClearRequestActions

This would improve code organization and make it easier to maintain and test each piece independently.

Example structure:

// MessageActions.tsx
export const MessageActions = ({ 
  message, 
  isOpen, 
  position, 
  onClose, 
  onDelete,
  onEdit,
  onReply 
}: MessageActionsProps) => {
  return (
    <Portal>
      <div className="rounded-sm border-2 border-secondary-foreground">
        {/* Action buttons */}
      </div>
    </Portal>
  );
};

// Message.tsx
export const Message = ({ message, ...props }) => {
  return (
    <div className="flex">
      <MessageContent message={message} />
      <ReadReceipt message={message} />
      <MessageActions message={message} />
      {message.type !== 'message' && (
        <ClearRequestActions message={message} />
      )}
    </div>
  );
};

Line range hint 214-246: Simplify date formatting logic using dayjs

The sentInfo function has complex date formatting logic that could be simplified using dayjs's built-in formatting.

- const sentInfo = () => {
-   return (
-     <>
-       Sent at {dayjs(message._creationTime).hour()}:
-       {dayjs(message._creationTime).minute() < 10
-         ? "0" + dayjs(message._creationTime).minute()
-         : dayjs(message._creationTime).minute()}
-       {", "}
-       {dayjs(message._creationTime).date() < 10
-         ? "0" + dayjs(message._creationTime).date()
-         : dayjs(message._creationTime).date()}
-       .
-       {dayjs(message._creationTime).month() + 1 < 10
-         ? "0" + (dayjs(message._creationTime).month() + 1).toString()
-         : dayjs(message._creationTime).month() + 1}
-       .{dayjs(message._creationTime).year()}
-     </>
-   );
- };
+ const sentInfo = () => (
+   <>
+     Sent at {dayjs(message._creationTime).format('HH:mm, DD.MM.YYYY')}
+   </>
+ );

Line range hint 67-89: Extract mobile detection logic to a custom hook

The mobile detection logic could be moved to a reusable custom hook for better code organization and reusability.

// hooks/useDeviceDetection.ts
export const useDeviceDetection = () => {
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const checkIfMobile = () => {
      const userAgent = navigator.userAgent;
      setIsMobile(
        /android/i.test(userAgent) || 
        /iPad|iPhone|iPod/.test(userAgent)
      );
    };

    checkIfMobile();
  }, []);

  return { isMobile };
};

// Usage in Message.tsx
const { isMobile } = useDeviceDetection();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between bdd9e0e and 44135e0.

📒 Files selected for processing (5)
  • convex/chats.ts (2 hunks)
  • convex/clearRequests.ts (1 hunks)
  • convex/messages.ts (2 hunks)
  • convex/schema.ts (2 hunks)
  • src/components/message.tsx (1 hunks)
🔇 Additional comments (9)
convex/schema.ts (2)

24-28: LGTM: Well-structured edge definition for tracking read status

The new readRequests edge follows the established pattern and correctly establishes the many-to-many relationship between users and clear requests.


24-28: Verify schema usage in mutation handlers

The schema changes look good and properly establish the relationships needed for read receipts. Let's verify that the mutation handlers are correctly using these new edges.

Also applies to: 40-44

✅ Verification successful

Let me gather more context about the implementation details of these mutations.


Schema changes are properly utilized in mutation handlers

The schema relationships are correctly implemented in both mutation handlers:

  • createClearRequest initializes the readBy field with the creator's ID (line 60)
  • markMessageRead properly handles both messages and clearRequests, using the readBy field to track read status (lines 223-225 and 241-243)
  • The edge relationships defined in the schema are being used correctly for querying (lines 45-46)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if mutation handlers properly use the new schema relationships

# Check for readBy usage in clearRequests mutations
echo "Checking clearRequests mutations..."
rg -A 5 "createClearRequest.*mutation" convex/

# Check for readRequests usage in message read mutations
echo "Checking message read mutations..."
rg -A 5 "markMessageRead.*mutation" convex/

Length of output: 942


Script:

#!/bin/bash
# Check the full implementation of both mutations to verify schema usage

echo "Checking createClearRequest implementation..."
ast-grep --pattern 'export const createClearRequest = mutation({
  $$$
  handler: async (ctx, args) => {
    $$$
  }
});'

echo "Checking markMessageRead implementation..."
ast-grep --pattern 'export const markMessageRead = mutation({
  $$$
  handler: async (ctx, args) => {
    $$$
  }
});'

Length of output: 6589

convex/clearRequests.ts (2)

60-60: Consider if creator should be marked as having read their own request

The readBy array is initialized with the creator's ID, implying they've read their own clear request. While this is logically sound (as creating implies awareness), you might want to verify if this aligns with the broader read receipt system's behavior.

Let's check how message creation handles read receipts:

✅ Verification successful

Marking clear requests as read by creator aligns with message behavior

The verification confirms that regular messages are also marked as read by their creator (via readBy: [convexUser._id]), making this implementation consistent with the broader read receipt system's behavior.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if messages are marked as read by their creator
ast-grep --pattern 'await ctx.table("messages").insert({
  $$$
  readBy: $_,
  $$$
})'

Length of output: 553


Line range hint 1-62: Verify schema constraints for the readBy field

The mutation looks correct and properly authenticated. However, since this adds a new relationship between users and clear requests, we should verify the schema constraints.

Let's check the schema definition:

✅ Verification successful

Schema constraints for readBy field are correctly defined

The schema definition shows that the readBy field in clearRequests is properly defined as a many-to-many edge relationship with users table through the readRequests join table. The relationship is bidirectional with readRequests as the inverse field in the users table. This matches the usage in the mutation where readBy is initialized as an array containing the requesting user's ID.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check schema definition for clearRequests table
rg -A 5 'clearRequests:.*{' convex/schema.ts

Length of output: 144


Script:

#!/bin/bash
# Let's try a simpler search pattern to find schema definitions
rg "clearRequests" convex/schema.ts

Length of output: 180


Script:

#!/bin/bash
# Let's also check the entire schema file content to understand the structure
cat convex/schema.ts

Length of output: 1938


Script:

#!/bin/bash
# Search for any files that might contain schema definitions
fd schema.ts

Length of output: 31

convex/chats.ts (2)

Line range hint 121-154: Overall implementation looks solid

The changes effectively implement read receipts for clear chat requests while maintaining proper handling of regular messages. The parallel fetching optimization in the clear requests handling is a nice performance improvement.

Key improvements made:

  1. Efficient parallel fetching of read status data
  2. Proper differentiation between message types for read status
  3. Accurate counting of unread messages considering deletion status

147-154: Verify read status logic for deleted messages

The current implementation treats read status differently for messages vs. clear requests. Please confirm if this is the intended behavior:

  • Regular messages: Considered unread if deleted
  • Clear requests: Read status independent of deletion

Let's verify this behavior across the codebase:

Consider making the logic more explicit:

-          if (message.type === "message" && message.deleted) {
+          const isDeletedMessage = message.type === "message" && message.deleted;
+          if (isDeletedMessage) {
             deletedCount++;
           }
           const isReadMessage =
             message.readBy.some(
               (user) => user.clerkId === identity.tokenIdentifier,
             ) &&
-            (message.type !== "message" || !message.deleted);
+            (!isDeletedMessage || message.type !== "message");
✅ Verification successful

Read status logic for deleted messages is correctly implemented

The codebase consistently shows that:

  1. Regular messages (type === "message"):
    • Have separate read/sent/sending status display logic when not deleted
    • Cannot be replied to when deleted
    • Cannot be edited when deleted
  2. Clear requests:
    • Are tracked in a separate schema with their own read status
    • Don't have deletion-related constraints on read status

The current implementation aligns with these patterns, where deleted regular messages are treated as unread while clear requests maintain their read status regardless of deletion state.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for related message read status handling
rg -l "readBy|deleted.*message" --type ts

# Check for any similar patterns in tests
rg -l "test.*message.*read|test.*deleted" --type ts

Length of output: 282


Script:

#!/bin/bash
# Check message read status handling in relevant files
echo "=== Checking message.tsx ==="
rg "readBy|deleted.*message" src/components/message.tsx -C 2

echo -e "\n=== Checking messages.ts ==="
rg "readBy|deleted.*message" convex/messages.ts -C 2

echo -e "\n=== Checking schema.ts ==="
rg "readBy|deleted.*message" convex/schema.ts -C 2

echo -e "\n=== Checking clearRequests.ts ==="
rg "readBy|deleted.*message" convex/clearRequests.ts -C 2

Length of output: 3003

convex/messages.ts (2)

191-191: LGTM: Type-safe union for handling both message and clear request IDs

The signature change correctly uses v.union() to handle both types of IDs while maintaining type safety.


Line range hint 191-248: Verify integration points and schema support

Let's verify that all callers are updated and the schema supports these changes.

✅ Verification successful

Schema and usage are properly aligned with the changes

The verification confirms that:

  1. The schema properly supports readBy for both messages and clearRequests tables through edge definitions with appropriate inverse fields
  2. The caller in src/components/message.tsx doesn't rely on any return value from markMessageRead, so it's compatible with the changes
  3. Both tables use the same edge structure for readBy, making the union type in the mutation's args appropriate

The implementation is consistent with the schema design and existing usage patterns in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify callers and schema support for markMessageRead changes

# Check for markMessageRead callers
echo "Checking markMessageRead callers..."
rg -A 2 "markMessageRead" --type ts

# Check schema for clearRequests table
echo "Checking schema definition..."
rg "clearRequests.*readBy" --type ts

Length of output: 657


Script:

#!/bin/bash
# Let's check the schema definition for clearRequests table
echo "Checking clearRequests schema..."
ast-grep --pattern 'export const clearRequests = defineTable({
  $$$
})'

# Let's also check the messages table schema for comparison
echo "Checking messages schema..."
ast-grep --pattern 'export const messages = defineTable({
  $$$
})'

Length of output: 284


Script:

#!/bin/bash
# Let's try a different approach to find schema definitions
echo "Searching for schema definitions..."
rg "defineTable.*clearRequests" -B 1 -A 10 --type ts

echo "Searching for readBy field usage in schema..."
rg "readBy.*:" -B 2 -A 2 --type ts

# Also check for any files that might contain schema definitions
echo "Looking for schema files..."
fd "schema" --type f

Length of output: 2104


Script:

#!/bin/bash
# Let's check the schema.ts file content
echo "Checking schema.ts content..."
cat convex/schema.ts

# Let's also check for any return type handling in the message component
echo "Checking message component handling..."
rg -A 5 "markRead\(" --type ts

Length of output: 2454

src/components/message.tsx (1)

Line range hint 193-198: LGTM: Simplified read receipt logic

The simplified condition for marking messages as read now correctly handles both regular messages and clear chat requests, which aligns with the PR objectives.

convex/chats.ts Show resolved Hide resolved
convex/messages.ts Outdated Show resolved Hide resolved
convex/messages.ts Outdated Show resolved Hide resolved
Copy link

@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: 2

🧹 Outside diff range and nitpick comments (1)
convex/messages.ts (1)

191-191: Add return type safety

Consider adding a return type to the mutation for better type safety and consistency.

  export const markMessageRead = mutation({
-   args: { messageId: v.union(v.id("messages"), v.id("clearRequests")) },
+   args: { messageId: v.union(v.id("messages"), v.id("clearRequests")) },
+   returns: v.union(v.null(), v.object({ success: v.boolean() })),
    handler: async (ctx, args) => {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 44135e0 and e775c4d.

📒 Files selected for processing (1)
  • convex/messages.ts (2 hunks)
🔇 Additional comments (2)
convex/messages.ts (2)

191-191: LGTM: Well-structured type definition

The union type correctly represents that the function can handle both message and clear request IDs, aligning with the PR's objective.


234-264: LGTM: Well-structured clear request handling

The clear request handling logic is well-implemented with proper:

  • ID normalization
  • Error handling
  • Authorization checks
  • Read status updates

Comment on lines +219 to +227
const chat = await ctx.table("privateChats").getX(message.privateChatId);
const usersInChat = await chat.edge("users");
if (
!usersInChat.some((user) => user.clerkId === identity.tokenIdentifier)
) {
throw new ConvexError(
"User not authorized to mark messages in this chat",
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reduce code duplication in authorization checks

The authorization check logic is duplicated between message and clear request handling. Consider extracting it into a reusable function.

+ async function validateUserInChat(ctx, chatId, identity) {
+   const chat = await ctx.table("privateChats").getX(chatId);
+   const usersInChat = await chat.edge("users");
+   if (!usersInChat.some((user) => user.clerkId === identity.tokenIdentifier)) {
+     throw new ConvexError("User not authorized to mark messages in this chat");
+   }
+   return chat;
+ }

  // In message handling:
- const chat = await ctx.table("privateChats").getX(message.privateChatId);
- const usersInChat = await chat.edge("users");
- if (!usersInChat.some((user) => user.clerkId === identity.tokenIdentifier)) {
-   throw new ConvexError("User not authorized to mark messages in this chat");
- }
+ await validateUserInChat(ctx, message.privateChatId, identity);

  // In clear request handling:
- const chat = await ctx.table("privateChats").getX(request.privateChatId);
- const usersInChat = await chat.edge("users");
- if (!usersInChat.some((user) => user.clerkId === identity.tokenIdentifier)) {
-   throw new ConvexError("User not authorized to mark messages in this chat");
- }
+ await validateUserInChat(ctx, request.privateChatId, identity);

Also applies to: 244-254

Comment on lines +215 to +217
if (!message) {
return null;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Inconsistent error handling

For consistency with the clear request handling (line 262), consider returning { success: false } instead of null when the message is not found.

  if (!message) {
-   return null;
+   return { success: false };
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!message) {
return null;
}
if (!message) {
return { success: false };
}

@FleetAdmiralJakob FleetAdmiralJakob merged commit 18e45e1 into main Nov 26, 2024
8 checks passed
@FleetAdmiralJakob FleetAdmiralJakob deleted the fix-clear-chat-request-read-receipts branch November 26, 2024 13:24
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.

1 participant