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

Add update message capability in the SDK, react hooks, and demo app #378

Open
wants to merge 6 commits into
base: materialisation/implement-message-deleting
Choose a base branch
from

Conversation

vladvelici
Copy link
Contributor

@vladvelici vladvelici commented Oct 22, 2024

Context

Add update message capability in the SDK, react hooks, and demo app.

Checklist

  • QA'd by the author.
  • Unit tests created (if applicable).
  • Integration tests created (if applicable).
  • Follow coding style guidelines found here.
  • TypeDoc updated (if applicable).
  • (Optional) Update documentation for new features.
  • Browser tests created (if applicable).
  • In repo demo app updated (if applicable).

Testing Instructions (Optional)

  • Explain how to test the changes in this PR.
  • Provide specific steps or commands to execute.

Summary by CodeRabbit

  • New Features

    • Users can now update existing messages directly by clicking on them.
    • Enhanced message handling allows for better error management during updates and deletions.
    • New pencil icon added for editing messages.
    • Buttons for editing and deleting messages are now visible on hover.
  • Bug Fixes

    • Improved state management ensures that updates and deletions only occur when the relevant messages exist.
  • Documentation

    • Updated interfaces and methods to support new message update functionality.
  • Chores

    • Refined initialization of the chat client for local development.

@vladvelici vladvelici requested a review from a team October 22, 2024 14:52
Copy link

coderabbitai bot commented Oct 22, 2024

Walkthrough

The changes include modifications to the MessageComponent and Chat components to support message editing functionality. The MessageProps interface has been updated to accept a Message object instead of a string ID. The Chat component now includes an updateMessage function for editing messages, along with refined logic for handling message updates and deletions. Additionally, new interfaces and methods have been introduced in the ChatApi, messages, and use-messages modules to facilitate message updates, enhancing the overall message handling capabilities.

Changes

File Path Change Summary
demo/src/components/MessageComponent/MessageComponent.tsx Updated MessageProps to accept a Message object; adjusted handleMessageClick and handleDelete functions.
demo/src/containers/Chat/Chat.tsx Added handleUpdatedMessage and onUpdateMessage functions; modified message handling for updates and deletions; assigned onMessageClick to onUpdateMessage.
src/core/chat-api.ts Introduced UpdateMessageParams and UpdateMessageResponse interfaces; added updateMessage method; modified deleteMessage method.
src/core/messages.ts Added UpdateMessageParams and UpdateMessageDetails interfaces; introduced update method in Messages interface.
src/react/hooks/use-messages.ts Added update method to UseMessagesResponse interface; updated useMessages hook to include update method.
demo/src/main.tsx Modified initialization of the Ably Realtime client for local development; commented out old configuration.
test/react/hooks/use-messages.integration.test.tsx Added test case for message updates; modified assertion for message deletion test.
demo/src/index.css Added CSS rules for .chat-message to control visibility of buttons on hover.

Assessment against linked issues

Objective Addressed Explanation
Implement message editing functionality (CHA-513)

🐰 In a world of messages, we hop and play,
With edits and updates, we brighten the day.
A click on the message, a change in the air,
Our chat now dances, with love and with care.
So let’s send our whispers, both near and far,
For every small change, we’re a message star! 🌟


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

github-actions bot commented Oct 22, 2024

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 92.88% (🎯 92%) 2860 / 3079
🟢 Statements 92.88% (🎯 92%) 2860 / 3079
🟢 Functions 92.37% (🎯 92%) 218 / 236
🟢 Branches 93.23% (🎯 93%) 786 / 843
File Coverage
File Stmts % Branch % Funcs % Lines Uncovered Lines
Changed Files
src/core/chat-api.ts 100% 76.19% 100% 100%
src/core/messages.ts 94.18% 86.2% 100% 94.18% 451-453, 486-490, 502-506, 648-650
src/react/hooks/use-messages.ts 85.52% 95% 14.28% 85.52% 131-141
Generated in workflow #1647 for commit b37291f by the Vitest Coverage Report Action

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: 6

🧹 Outside diff range and nitpick comments (7)
demo/src/main.tsx (2)

36-41: Consider removing commented-out code and documenting configuration options.

The commented-out Ably client initialization contains valuable information about alternative configuration options. However, keeping commented code in the codebase can lead to confusion and maintenance issues over time.

Consider the following actions:

  1. Remove the commented-out code.
  2. Document the configuration options in a separate file or in the project's README.
  3. If these options are still relevant, implement them as part of the conditional setup suggested earlier.

Example documentation:

## Ably Client Configuration

The Ably Realtime client can be configured with the following options:

- `authUrl`: URL for token authentication
- `restHost`: Custom REST host (set via VITE_ABLY_HOST environment variable)
- `realtimeHost`: Custom Realtime host (set via VITE_ABLY_HOST environment variable)
- `clientId`: Unique identifier for the client

For local development, additional options are available:
- `port`: 8081
- `environment`: 'local'
- `tls`: false

30-42: Summary: Local development configuration added with room for improvement.

The changes in this file focus on configuring the Ably Realtime client for local development. While this is beneficial for testing, there are a few points to consider:

  1. The local development configuration (lines 30-32) should be applied conditionally to prevent unintended use in production.
  2. The commented-out code (lines 36-41) contains valuable information about alternative configurations, which should be documented properly rather than left as comments.
  3. The overall structure of the client initialization could be improved to handle different environments more elegantly.

These changes align with the PR objective of enhancing the SDK and demo app capabilities, particularly in terms of facilitating local development and testing of new features like message editing.

Consider refactoring the Ably client initialization to use a configuration object that can be easily switched between environments (local, staging, production). This would improve maintainability and make it easier to add or modify environment-specific settings in the future.

demo/src/components/MessageComponent/MessageComponent.tsx (1)

33-35: Consider optimizing useCallback dependencies

The id dependency in the useCallback hook for handleMessageClick might be unnecessary now that we're using the message object. Consider removing it to optimize performance:

 const handleMessageClick = useCallback(() => {
   onMessageClick?.(message);
-}, [id, onMessageClick]);
+}, [message, onMessageClick]);

This change could prevent unnecessary re-renders when only the id changes but the message object remains the same.

src/core/message-parser.ts (1)

Line range hint 1-99: Overall assessment of changes

The modifications to parseMessage function align well with the PR objectives of enhancing message handling capabilities. The addition of new cases for empty string, undefined, and null actions improves the robustness of the function.

However, there are opportunities to enhance code quality and maintainability:

  1. Consider adding logging or telemetry for unexpected action types to aid in debugging and monitoring.
  2. Ensure consistent use of braces in switch cases for better readability and maintainability.
  3. Prefer type guards or type narrowing over type assertions for improved type safety.

These changes will contribute to a more robust and maintainable codebase, aligning with the overall goals of the chat system enhancements.

Consider implementing a more extensible pattern for handling message actions, such as a strategy pattern or a map of action handlers. This could improve maintainability as new action types are added in the future.

🧰 Tools
🪛 GitHub Check: lint

[failure] 94-94:
Missing braces in case clause


[failure] 96-96:
Missing braces in case clause


[failure] 98-98:
Missing braces in case clause


[failure] 98-98:
Use undefined instead of null

src/core/chat-api.ts (3)

68-69: Clarify property name to avoid confusion

Both message.metadata and metadata exist in UpdateMessageParams, which might be confusing. Consider renaming the metadata field to operationMetadata to distinguish it from the message's metadata.

Apply this diff to improve clarity:

interface UpdateMessageParams {
  ...
-  /** Metadata of the update operation */
-  metadata?: Record<string, string>;
+  /** Metadata of the update operation */
+  operationMetadata?: Record<string, string>;
}

149-152: Consistent parameter naming for serial identifiers

In the deleteMessage method, the parameter is named timeserial, whereas in updateMessage, it's named serial. For consistency and clarity, consider using the same parameter name across methods.

Option 1: Rename serial to timeserial in updateMessage:

- async updateMessage(roomId: string, serial: string, params: UpdateMessageParams): Promise<UpdateMessageResponse> {
-   const encodedSerial = encodeURIComponent(serial);
+ async updateMessage(roomId: string, timeserial: string, params: UpdateMessageParams): Promise<UpdateMessageResponse> {
+   const encodedSerial = encodeURIComponent(timeserial);

Option 2: Rename timeserial to serial in deleteMessage:

- async deleteMessage(
-   roomId: string,
-   timeserial: string,
-   params?: DeleteMessageParams,
- ): Promise<DeleteMessageResponse> {
-   const encodedSerial = encodeURIComponent(timeserial);
+ async deleteMessage(
+   roomId: string,
+   serial: string,
+   params?: DeleteMessageParams,
+ ): Promise<DeleteMessageResponse> {
+   const encodedSerial = encodeURIComponent(serial);

Choose the option that best fits your naming conventions.


149-152: Add error handling for the updateMessage method

Currently, the updateMessage method relies on _makeAuthorizedRequest, which throws an exception on failure. Consider adding error handling or logging within updateMessage to provide more context in case of failures.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 53172cc and 2aadce5.

📒 Files selected for processing (7)
  • demo/src/components/MessageComponent/MessageComponent.tsx (2 hunks)
  • demo/src/containers/Chat/Chat.tsx (5 hunks)
  • demo/src/main.tsx (1 hunks)
  • src/core/chat-api.ts (3 hunks)
  • src/core/message-parser.ts (1 hunks)
  • src/core/messages.ts (3 hunks)
  • src/react/hooks/use-messages.ts (3 hunks)
🧰 Additional context used
🪛 GitHub Check: lint
src/core/message-parser.ts

[failure] 94-94:
Missing braces in case clause


[failure] 96-96:
Missing braces in case clause


[failure] 98-98:
Missing braces in case clause


[failure] 98-98:
Use undefined instead of null

src/core/messages.ts

[failure] 147-147:
An interface declaring no members is equivalent to its supertype

src/react/hooks/use-messages.ts

[warning] 109-109:
React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead

🔇 Additional comments (13)
demo/src/main.tsx (2)

30-32: Verify local development configuration and consider conditional setup.

The new parameters (port: 8081, environment: 'local', tls: false) appear to be intended for local development. While this is useful for testing, it's important to ensure that these settings are not used in production.

Consider implementing a conditional setup based on the environment:

const isLocalDev = process.env.NODE_ENV === 'development';

const realtimeClient = new Ably.Realtime({
  authUrl: `/api/ably-token-request?clientId=${clientId}`,
  ...(isLocalDev ? {
    port: 8081,
    environment: 'local',
    tls: false,
  } : {}),
  clientId,
});

This approach ensures that the local development settings are only applied in the development environment.


42-42: LGTM: Improved code readability.

The addition of an empty line improves code readability by clearly separating different sections of the configuration.

demo/src/components/MessageComponent/MessageComponent.tsx (3)

21-21: Improved prop type for onMessageClick

The change from (id: string) => void to (message: Message) => void for the onMessageClick prop is a good improvement. It allows passing the entire message object to the click handler, providing more context and flexibility for operations like message editing.


Line range hint 1-105: Summary of changes

The modifications to MessageComponent.tsx successfully implement the necessary changes to support message editing functionality. The updates to the onMessageClick prop type and the corresponding handleMessageClick function improve the component's flexibility and align with the PR objectives. These changes lay the groundwork for implementing message editing in the demo app.

A minor optimization suggestion was made regarding the useCallback dependencies. Overall, the changes are well-implemented and consistent with the project's goals.


34-34: Updated handleMessageClick to pass message object

The change in handleMessageClick to pass message instead of id is consistent with the updated onMessageClick prop type. This ensures that the entire message object is available in the click handler, which is beneficial for operations like message editing.

To ensure this change is consistently applied throughout the codebase, let's verify the usage of onMessageClick:

src/core/message-parser.ts (1)

94-99: ⚠️ Potential issue

Improve robustness and code style in switch statement

While handling additional cases improves robustness, there are some concerns:

  1. Empty case blocks might hide potential issues.
  2. Type assertions (as Ably.MessageAction) could be unsafe.
  3. Inconsistent use of braces in case clauses.
  4. Use of null as a type is discouraged in TypeScript.

Consider the following improvements:

switch (message.action) {
  case ChatMessageActions.MessageCreate:
    break;
  case '':
  case undefined:
  case null:
    console.warn(`Received message with unexpected action: ${message.action}`);
    break;
  // ... other cases ...
}

Also, consider using a type guard or type narrowing instead of type assertions:

if (message.action === '' || message.action == null) {
  console.warn(`Received message with unexpected action: ${message.action}`);
  break;
}

To ensure this change doesn't introduce regressions, please run the following command:

✅ Verification successful

Review Comment Verified: No Other message.action Switch Statements Found

The shell script confirmed that the only switch statement handling message.action is in src/core/message-parser.ts at line 90.

No further action is required.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other switch statements handling message.action
rg -p "switch.*message.action" --type ts

Length of output: 100

🧰 Tools
🪛 GitHub Check: lint

[failure] 94-94:
Missing braces in case clause


[failure] 96-96:
Missing braces in case clause


[failure] 98-98:
Missing braces in case clause


[failure] 98-98:
Use undefined instead of null

src/react/hooks/use-messages.ts (3)

30-34: LGTM: Addition of update method to UseMessagesResponse interface

The new update method is well-documented and correctly typed. This addition aligns with the PR objectives to implement message editing functionality.


161-161: LGTM: Addition of update method to hook's return object

The update method is correctly added to the object returned by the useMessages hook, making it accessible to consumers of the hook. This change is consistent with the interface modifications and aligns with the PR objectives.


Line range hint 1-173: Summary of changes in use-messages.ts

The changes in this file successfully implement the message editing functionality as per the PR objectives. The update method has been added to the UseMessagesResponse interface and the useMessages hook, allowing consumers to update messages.

Key points:

  1. The interface and documentation changes are well-implemented and clear.
  2. The update method is correctly added to the hook's return object.
  3. A suggestion has been made to refactor the update method implementation to improve performance and address a static analysis warning.

Overall, the changes are good, with room for a minor improvement in the update method implementation.

src/core/chat-api.ts (1)

124-124: Ensure consistent encoding of serial identifiers

You've introduced encodedSerial by encoding timeserial using encodeURIComponent. Ensure that all methods constructing URLs with timeserial or serial encode these values consistently to handle special characters properly.

Run the following script to verify the encoding in other methods:

Expected: All URLs should use encodeURIComponent on timeserial or serial parameters.

demo/src/containers/Chat/Chat.tsx (1)

221-221: Ensure onMessageClick prop aligns with MessageComponent expectations

Verify that the onMessageClick prop in MessageComponent accepts a function that matches the updateMessage signature. This ensures that message updates are handled correctly when a message is clicked.

src/core/messages.ts (2)

274-274: Method Signature Looks Correct

The update method is appropriately added to the Messages interface with clear documentation and correct parameter definitions.


557-579: Implementation of update Method Appears Correct

The update method correctly calls _chatApi.updateMessage with the appropriate parameters and constructs a new DefaultMessage instance with updated properties. The handling of optional parameters and default values is appropriate.

src/react/hooks/use-messages.ts Outdated Show resolved Hide resolved
src/core/chat-api.ts Outdated Show resolved Hide resolved
demo/src/containers/Chat/Chat.tsx Outdated Show resolved Hide resolved
demo/src/containers/Chat/Chat.tsx Outdated Show resolved Hide resolved
demo/src/containers/Chat/Chat.tsx Outdated Show resolved Hide resolved
src/core/messages.ts Show resolved Hide resolved
@@ -91,6 +91,12 @@ export function parseMessage(roomId: string | undefined, inboundMessage: Ably.In
case ChatMessageActions.MessageCreate: {
break;
}
case '' as Ably.MessageAction:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this to be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

clientId,
});

// const realtimeClient = new Ably.Realtime({
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm guessing this was just missed, but should probably be reverted? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup

@@ -101,6 +106,7 @@ export const useMessages = (params?: UseMessagesParams): UseMessagesResponse =>
[room],
);
const get = useCallback((options: QueryOptions) => room.messages.get(options), [room]);
const update = useCallback(room.messages.update.bind(room.messages), [room])
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a need to use bind here, or can we keep it consistent with the other two callbacks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated to be consistent

@@ -120,6 +146,11 @@ export class ChatApi {
return this._makeAuthorizedRequest<CreateMessageResponse>(`/chat/v2/rooms/${roomId}/messages`, 'POST', body);
}

async updateMessage(roomId: string, serial: string, params: UpdateMessageParams): Promise<UpdateMessageResponse> {
const encodedSerial = encodeURIComponent(serial);
Copy link
Contributor

Choose a reason for hiding this comment

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

Lucky for us, gorilla mux handles URI encoding/decoding, which is why this wasn't an issue. That being said, its probably good practice to have this in. Should we also encode the roomId too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should encode roomId too actually, will update.

@vladvelici vladvelici force-pushed the materialisation/update-message branch from 2aadce5 to 281e94a Compare October 23, 2024 18:42
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 (1)
src/core/chat-api.ts (1)

54-77: LGTM! Consider using MessageDetailsMetadata type for consistency.

The interfaces are well-structured and documented. For better type consistency across the codebase, consider using MessageDetailsMetadata type (used in DeleteMessageParams) instead of Record<string, string> for the metadata field in UpdateMessageParams.

  /** Metadata of the update operation */
-  metadata?: Record<string, string>;
+  metadata?: MessageDetailsMetadata;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 2aadce5 and 281e94a.

📒 Files selected for processing (7)
  • demo/src/components/MessageComponent/MessageComponent.tsx (2 hunks)
  • demo/src/containers/Chat/Chat.tsx (5 hunks)
  • demo/src/main.tsx (1 hunks)
  • src/core/chat-api.ts (3 hunks)
  • src/core/message-parser.ts (1 hunks)
  • src/core/messages.ts (3 hunks)
  • src/react/hooks/use-messages.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • demo/src/components/MessageComponent/MessageComponent.tsx
  • demo/src/main.tsx
🧰 Additional context used
🪛 GitHub Check: lint
src/core/message-parser.ts

[failure] 94-94:
Missing braces in case clause


[failure] 96-96:
Missing braces in case clause


[failure] 98-98:
Missing braces in case clause


[failure] 98-98:
Use undefined instead of null

src/core/messages.ts

[failure] 157-157:
An interface declaring no members is equivalent to its supertype

src/react/hooks/use-messages.ts

[warning] 109-109:
React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead

🔇 Additional comments (13)
src/core/message-parser.ts (1)

94-99: ⚠️ Potential issue

Improve type safety and error handling for invalid actions.

The current implementation has several concerns:

  1. Type casting using as unknown as bypasses TypeScript's type system, which could mask potential runtime issues.
  2. Silently handling invalid actions (empty string, undefined, null) might hide bugs in the message handling logic.
  3. Code style issues with missing braces in case clauses.

Consider this safer implementation:

-    case '' as Ably.MessageAction:
-      break;
-    case undefined as unknown as Ably.MessageAction:
-      break;
-    case null as unknown as Ably.MessageAction:
-      break;  
+    case '' as Ably.MessageAction: {
+      throw new Ably.ErrorInfo('received incoming message with empty action', 50000, 500);
+    }
+    case undefined as Ably.MessageAction: {
+      throw new Ably.ErrorInfo('received incoming message with undefined action', 50000, 500);
+    }

Remove the null case as it's redundant with undefined in TypeScript.

Rationale:

  • Explicit error handling helps identify issues in message processing
  • Consistent with the error handling pattern used in other cases
  • Follows TypeScript best practices by avoiding unknown type casting
  • Maintains code style consistency with braces

Let's verify if there are any other places in the codebase where message actions are set:

✅ Verification successful

Let me try a different search strategy to verify the message action handling across the codebase.

This should help us understand:

  1. How MessageAction is used across the codebase
  2. The specific action handling patterns in the message parser
  3. Where the MessageAction type is defined

The results will help verify if the suggested changes are consistent with the codebase's patterns and type safety requirements.


Based on the search results, I can now generate a final response as we have sufficient context about how message actions are handled in the codebase.

Improve error handling consistency for invalid message actions

The current implementation should be modified to align with the existing error handling patterns in the codebase. Looking at the search results, we can see that:

  1. The codebase already uses ChatMessageActions enum for valid actions (MessageCreate, MessageUpdate, MessageDelete)
  2. Other cases in the same switch statement (like MessageUpdate and MessageDelete) throw explicit errors with Ably.ErrorInfo
  3. There's a message action to event mapping that expects valid actions

Suggested changes:

-    case '' as Ably.MessageAction:
-      break;
-    case undefined as unknown as Ably.MessageAction:
-      break;
-    case null as unknown as Ably.MessageAction:
-      break;  
+    case '' as Ably.MessageAction: {
+      throw new Ably.ErrorInfo('received incoming message with empty action', 50000, 500);
+    }
+    case undefined as Ably.MessageAction: {
+      throw new Ably.ErrorInfo('received incoming message with undefined action', 50000, 500);
+    }

Rationale:

  • Follows the existing error handling pattern seen in MessageUpdate and MessageDelete cases
  • Maintains consistency with the codebase's error reporting using Ably.ErrorInfo
  • Removes the redundant null case as the codebase uses undefined for missing values
  • Adds braces for consistency with other case blocks
  • Removes unsafe type casting with unknown
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for places where message actions are set
rg -l "action.*=.*MessageActions\." 

Length of output: 37


Script:

#!/bin/bash
# Search for MessageAction type usage and assignments
rg -A 2 "MessageAction" 

# Also search for action-related patterns in the message parser
rg -A 2 "action.*:" src/core/message-parser.ts

# Look for the Ably.MessageAction type definition
fd -e ts -e d.ts -x grep -l "MessageAction"

Length of output: 4530

🧰 Tools
🪛 GitHub Check: lint

[failure] 94-94:
Missing braces in case clause


[failure] 96-96:
Missing braces in case clause


[failure] 98-98:
Missing braces in case clause


[failure] 98-98:
Use undefined instead of null

src/react/hooks/use-messages.ts (4)

30-34: LGTM! Well-documented interface addition.

The new update method is properly typed and documented, maintaining consistency with the existing codebase style.


109-109: Implement update callback consistently with other methods.

The current implementation differs from the pattern used in other callbacks (send, deleteMessage, get). Let's maintain consistency and resolve the static analysis warning.

🧰 Tools
🪛 GitHub Check: lint

[warning] 109-109:
React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead


161-161: LGTM! Proper integration of the update method.

The update method is correctly integrated into the return object, maintaining the logical grouping of message operations.


109-109: Verify update method integration with demo app.

Let's ensure the new update method is properly integrated with the demo app's Chat component.

✅ Verification successful

Let me gather more information about the update functionality in the Chat component to complete the verification.


I have enough information to generate the final response now.

Integration of update method is properly implemented

The verification shows that:

  • The update method from useMessages hook is correctly bound and used in the Chat component
  • The update functionality is properly integrated through the updateMessage callback
  • The message update flow is complete:
    1. Receives message via onMessageClick prop in MessageComponent
    2. Handles updates via updateMessage callback in Chat component
    3. Uses the bound update method from useMessages hook
    4. Maintains message state via setMessages
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the integration of the update method in the demo app

# Test 1: Check usage of update method in Chat component
echo "Checking usage in Chat component..."
rg -A 5 "update.*=.*useMessages" demo/

# Test 2: Verify message update handler implementation
echo "Checking message update handler..."
rg -A 10 "onMessageClick" demo/

Length of output: 3131


Script:

#!/bin/bash
# Check the implementation of updateMessage in Chat.tsx
echo "Checking updateMessage implementation..."
rg -A 10 "updateMessage.*=" demo/src/containers/Chat/Chat.tsx

# Check if useMessages hook is used in Chat.tsx
echo "Checking useMessages usage..."
rg -A 5 "useMessages" demo/src/containers/Chat/Chat.tsx

# Check for any message update related state
echo "Checking message update related state..."
rg -A 5 "const.*message.*State" demo/src/containers/Chat/Chat.tsx

Length of output: 1606

🧰 Tools
🪛 GitHub Check: lint

[warning] 109-109:
React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead

demo/src/containers/Chat/Chat.tsx (5)

29-29: LGTM: Clean addition of update hook.

The update method is correctly destructured from the useMessages hook.


38-47: LGTM: Efficient message deletion implementation.

The deletion logic is well-implemented with:

  • Efficient filtering
  • State update optimization
  • Clear comments explaining the logic

50-68: Skipping comment as there's an existing valid review.


220-220: LGTM: Clean integration of message update handler.

The updateMessage callback is correctly wired up to the MessageComponent.


171-180: ⚠️ Potential issue

Add message dependency to useCallback.

The message parameter is used in the console.log, but the callback isn't re-created when it changes. This could lead to stale message references.

Apply this diff to fix the dependency array:

  const updateMessage = useCallback((message: Message) => {
    const newText = prompt('Enter new text');
    if (!newText) { return; }
    const updateOp = update(message, {
      text: newText,
      metadata: message.metadata,
      headers: message.headers,
    });
    console.log("message ", message.timeserial, " updated. op=", updateOp);
-  }, [update]);
+  }, [update, message]);

Likely invalid or redundant comment.

src/core/messages.ts (3)

149-157: Simplify redundant interface declaration.

The interface UpdateMessageParams is identical to its supertype SendMessageParams. While the documentation is clear and helpful, we can simplify this using a type alias.

🧰 Tools
🪛 GitHub Check: lint

[failure] 157-157:
An interface declaring no members is equivalent to its supertype


159-172: LGTM! Well-structured metadata interface.

The UpdateMessageDetails interface provides a clean separation between message content and operation metadata, with clear documentation.


270-284: LGTM! Well-documented method signature.

The update method declaration is well-designed with:

  • Clear parameter types
  • Optional details parameter
  • Thorough documentation explaining the behavior and timing considerations

src/core/chat-api.ts Outdated Show resolved Hide resolved
src/core/chat-api.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 (2)
src/core/chat-api.ts (1)

Line range hint 82-89: Consider architectural improvements.

A few suggestions to enhance the API's maintainability and documentation:

  1. Consider making the API version configurable via constructor instead of hardcoding
  2. Document rate limiting considerations for the update operation
  3. Update README to document the new message update capability

Would you like me to help with updating the documentation or implementing any of these suggestions?

src/core/messages.ts (1)

172-172: Consider using a more specific type for metadata.

The metadata field in UpdateMessageDetails uses a generic Record<string, string>. Consider using DetailsMetadata type that's already imported and used elsewhere in the codebase for consistency.

-  metadata?: Record<string, string>;
+  metadata?: DetailsMetadata;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 281e94a and afa7c59.

📒 Files selected for processing (4)
  • src/core/chat-api.ts (4 hunks)
  • src/core/message-parser.ts (1 hunks)
  • src/core/messages.ts (3 hunks)
  • src/react/hooks/use-messages.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/core/message-parser.ts
  • src/react/hooks/use-messages.ts
🔇 Additional comments (3)
src/core/chat-api.ts (2)

54-77: LGTM! Well-structured interfaces for message updates.

The interfaces are well-designed with clear separation of concerns and appropriate types.


93-93: LGTM! Consistent URL encoding implementation.

The changes properly implement URL encoding for all path parameters across methods, which is a security best practice.

Also applies to: 125-126, 147-147, 158-158

src/core/messages.ts (1)

271-286: LGTM! Well-documented interface method.

The method signature is clear and the documentation thoroughly explains the behavior, especially the note about promise resolution timing.

src/core/chat-api.ts Outdated Show resolved Hide resolved
src/core/messages.ts Outdated Show resolved Hide resolved
@vladvelici vladvelici force-pushed the materialisation/update-message branch from 533be9b to 8588cb3 Compare October 24, 2024 08:12
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 (2)
src/core/chat-api.ts (1)

151-159: Consider adding rate limiting and concurrency control.

The message update implementation could benefit from:

  1. Rate limiting to prevent abuse
  2. Optimistic concurrency control to handle concurrent updates (e.g., using ETags or version numbers)

These improvements would enhance the robustness of the message editing feature.

src/core/messages.ts (1)

163-173: Add JSDoc examples for UpdateMessageDetails interface.

Consider adding examples in the JSDoc to illustrate typical usage of description and metadata fields.

 export interface UpdateMessageDetails {
   /**
    * An optional description for the update operation.
+   * @example
+   * ```ts
+   * {
+   *   description: "Fixed typo in message"
+   * }
+   * ```
    */
   description?: string;

   /**
    * Optional metadata for the update operation.
+   * @example
+   * ```ts
+   * {
+   *   metadata: {
+   *     editReason: "spelling_correction",
+   *     editedFrom: "Hello wrold!",
+   *   }
+   * }
+   * ```
    */
   metadata?: Record<string, string>;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between afa7c59 and cfd1462.

📒 Files selected for processing (4)
  • demo/src/containers/Chat/Chat.tsx (4 hunks)
  • src/core/chat-api.ts (4 hunks)
  • src/core/messages.ts (3 hunks)
  • src/react/hooks/use-messages.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/react/hooks/use-messages.ts
🔇 Additional comments (8)
src/core/chat-api.ts (2)

93-93: LGTM! URL encoding is now consistently applied.

The changes properly encode both roomId and message serial parameters across all methods, improving security and robustness.

Also applies to: 125-126, 147-147, 162-162


151-159: Verify consistent error handling across API methods.

The implementation looks good, but let's verify that error handling is consistent with other message operations.

✅ Verification successful

Let me gather more specific information about the message operations to ensure a complete verification.

This will help us see all message operation implementations and any additional error handling patterns that might exist outside the common _makeAuthorizedRequest wrapper.


Let me do one final check to specifically look at all message operations and their error handling pattern.

This will help us see the complete implementation of message operations and confirm they all use the same error handling pattern through _makeAuthorizedRequest.


Error handling is consistent across message operations

Based on the verification results, all message operations (getMessages, deleteMessage, sendMessage, and updateMessage) consistently use the centralized error handling through _makeAuthorizedRequest or _makeAuthorizedPaginatedRequest. These wrapper methods properly handle errors by:

  • Logging errors with detailed context
  • Throwing standardized Ably.ErrorInfo with proper error codes
  • Using consistent parameter encoding

The new updateMessage method follows this same pattern, ensuring uniform error handling across the API.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check error handling consistency across message operations
# Expected: All message operations should use _makeAuthorizedRequest and handle errors similarly

# Search for message operation methods and their error handling
ast-grep --pattern 'async $methodName($$$): Promise<$_> {
  $$$
  return this._makeAuthorizedRequest<$_>($$$);
}'

# Check for any direct Ably API calls bypassing _makeAuthorizedRequest
rg -A 2 'this\._realtime\.' --type ts --glob '!**/*test*' --glob '!**/*spec*'

Length of output: 877


Script:

#!/bin/bash
# Search for all message-related methods in chat-api.ts
ast-grep --pattern 'async $methodName($params): Promise<$returnType> {
  $$$
}' src/core/chat-api.ts

# Also check for any error handling patterns outside _makeAuthorizedRequest
rg -A 3 'catch|throw|Error' --type ts src/core/chat-api.ts

Length of output: 433


Script:

#!/bin/bash
# Get the full context of message-related methods and _makeAuthorizedRequest
rg -B 2 -A 10 "async (updateMessage|sendMessage|deleteMessage|getMessage|_makeAuthorizedRequest)" src/core/chat-api.ts

Length of output: 2575

demo/src/containers/Chat/Chat.tsx (3)

29-29: LGTM: Update function correctly added to useMessages hook.

The addition of the update function aligns with the PR objective of implementing message editing functionality.


33-66: LGTM: Message event handlers implemented correctly.

The implementation follows React best practices:

  • Immutable state updates
  • Proper validation before state changes
  • Error logging for unknown message types

221-221: LGTM: MessageComponent correctly configured for message updates.

The updateMessage callback is properly passed to the MessageComponent's onMessageClick prop.

src/core/messages.ts (3)

271-286: LGTM! Well-documented method declaration.

The method signature and JSDoc documentation clearly explain the purpose, parameters, and behavior of the update operation.


568-591: LGTM! Robust implementation with proper state handling.

The implementation correctly:

  • Preserves message history (createdAt, deletedAt, etc.)
  • Handles optional fields with nullish coalescing
  • Sets updatedAt and updatedBy fields

568-591: Verify event handling for message updates.

Let's ensure that message update events are properly handled throughout the codebase.

✅ Verification successful

Based on the shell script results, I can now generate the final response as there's sufficient evidence to verify the message update event handling implementation.

Message update functionality is properly integrated across the codebase

The verification shows a complete implementation of message update functionality:

  • Event handling is properly implemented:
    • MessageEvents.Updated is defined and handled in the core messages system
    • Update events are properly mapped to actions via ChatMessageActions.MessageUpdate
    • The demo app correctly handles updates by finding and updating the relevant message in the UI
  • Update method integration is complete:
    • Core implementation in src/core/messages.ts
    • React hook wrapper in src/react/hooks/use-messages.ts
    • Message parser handles update actions with proper validation
    • Test coverage exists in test/core/messages.test.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify message update event handling implementation

# Test 1: Check for message update event handlers
echo "Checking for message update event handlers..."
rg -A 3 "MessageEvents\.Updated" --type ts

# Test 2: Check for update message action handling
echo "Checking for update message action handling..."
rg -A 3 "MessageActions\.MessageUpdate" --type ts

# Test 3: Check for update method usage
echo "Checking for update method usage..."
rg -A 3 "\.update\(.*message.*\)" --type ts

Length of output: 3200

Comment on lines 68 to 69
/** Metadata of the update operation */
metadata?: Record<string, string>;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider using consistent metadata types.

The metadata field uses Record<string, string> while other places in the codebase use MessageDetailsMetadata. Consider using MessageDetailsMetadata for consistency.

-  metadata?: Record<string, string>;
+  metadata?: MessageDetailsMetadata;

Committable suggestion was skipped due to low confidence.

Comment on lines 167 to 181
const updateMessage = useCallback(
(message: Message) => {
const newText = prompt('Enter new text');
if (!newText) {
return;
}
const updateOp = update(message, {
text: newText,
metadata: message.metadata,
headers: message.headers,
});
console.log('message ', message.timeserial, ' updated. op=', updateOp);
},
[update],
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle the Promise returned by the update operation.

The update operation returns a Promise, but it's not being properly handled. This could lead to missed error cases and make it difficult to provide feedback to users when updates fail.

Apply this diff to properly handle the Promise:

 const updateMessage = useCallback(
   async (message: Message) => {
     const newText = prompt('Enter new text');
     if (!newText) {
       return;
     }
-    const updateOp = update(message, {
-      text: newText,
-      metadata: message.metadata,
-      headers: message.headers,
-    });
-    console.log('message ', message.timeserial, ' updated. op=', updateOp);
+    try {
+      const updatedMessage = await update(message, {
+        text: newText,
+        metadata: message.metadata,
+        headers: message.headers,
+      });
+      console.log('Message updated successfully:', updatedMessage);
+    } catch (error) {
+      console.error('Failed to update message:', error);
+      // TODO: Add user feedback for failed updates
+    }
   },
   [update],
 );
📝 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
const updateMessage = useCallback(
(message: Message) => {
const newText = prompt('Enter new text');
if (!newText) {
return;
}
const updateOp = update(message, {
text: newText,
metadata: message.metadata,
headers: message.headers,
});
console.log('message ', message.timeserial, ' updated. op=', updateOp);
},
[update],
);
const updateMessage = useCallback(
async (message: Message) => {
const newText = prompt('Enter new text');
if (!newText) {
return;
}
try {
const updatedMessage = await update(message, {
text: newText,
metadata: message.metadata,
headers: message.headers,
});
console.log('Message updated successfully:', updatedMessage);
} catch (error) {
console.error('Failed to update message:', error);
// TODO: Add user feedback for failed updates
}
},
[update],
);

- Updated package dependencies to Ably 2.5.0. Introduced additional message attributes including serial, action, and updated/deleted details.

- Introduced new RealtimeMessageNames which will allow filtering of chat message types.

- Fixed test so they work with the new fields.
- This commit introduces the ability to delete messages in the chat API with both soft and hard delete options via `DeleteMessageParams`.It exposes this through the Messages feature as a new `delete` method.
- Updated related testing and documentation.
- Some minor improvements to messages.test.ts for clarity.

- The useMessage hook now also supports this functionality via a `deleteMessage` method returned from the hook.
- Updated related testing and documentation.

- The demo app has been updated to show the new deletion mechanic.
It uses the `react-icons` to display a bin icon, clicking this will delete the highlighted message.
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)
test/react/hooks/use-messages.integration.test.tsx (1)

155-162: Add comments explaining update parameters

The update operation takes two parameter objects, but their purpose isn't immediately clear. Consider adding comments to explain the difference between message updates and update metadata.

 void update(message, {
+  // New message content
   text: 'hello universe',
   metadata: { icon: 'universe' },
   headers: { awesome: 'yes' },
 }, {
+  // Update operation metadata
   description: "make it better",
   metadata: { something: 'else' },
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between cfd1462 and 121075e.

📒 Files selected for processing (1)
  • test/react/hooks/use-messages.integration.test.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: lint
test/react/hooks/use-messages.integration.test.tsx

[failure] 186-186:
Forbidden non-null assertion

🔇 Additional comments (1)
test/react/hooks/use-messages.integration.test.tsx (1)

127-127: Verify API change from isDeleted() method to isDeleted property

The change from isDeleted() to isDeleted suggests an API change in the Message interface. Let's verify this change is consistent across the codebase.

// expect a message to be received by the second room
await waitForMessages(updatesRoomTwo, 1);
expect(updatesRoomTwo.length).toBe(1);
const update = updatesRoomTwo[0]!;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace non-null assertion with runtime check

The non-null assertion (!) on updatesRoomTwo[0]! should be replaced with a runtime check for better type safety.

-const update = updatesRoomTwo[0]!;
+const update = updatesRoomTwo[0];
+if (!update) {
+  throw new Error('Expected update message to be defined');
+}
📝 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
const update = updatesRoomTwo[0]!;
const update = updatesRoomTwo[0];
if (!update) {
throw new Error('Expected update message to be defined');
}
🧰 Tools
🪛 GitHub Check: lint

[failure] 186-186:
Forbidden non-null assertion

Comment on lines +131 to +192
it('should update messages correctly', async () => {
// create new clients
const chatClientOne = newChatClient() as unknown as ChatClient;
const chatClientTwo = newChatClient() as unknown as ChatClient;

// create a second room and attach it, so we can listen for deletions
const roomId = randomRoomId();
const roomTwo = chatClientTwo.rooms.get(roomId, RoomOptionsDefaults);
await roomTwo.attach();

// start listening for deletions
const updatesRoomTwo: Message[] = [];
roomTwo.messages.subscribe((message) => {
if (message.type === MessageEvents.Updated) {
updatesRoomTwo.push(message.message);
}
});

const TestComponent = () => {
const { send, update, roomStatus } = useMessages();

useEffect(() => {
if (roomStatus === RoomLifecycle.Attached) {
void send({ text: 'hello world' }).then((message) => {
void update(message, {
text: 'hello universe',
metadata: { icon: 'universe' },
headers: { awesome: 'yes' },
}, {
description: "make it better",
metadata: { something: 'else' },
});
});
}
}, [roomStatus]);

return null;
};

const TestProvider = () => (
<ChatClientProvider client={chatClientOne}>
<ChatRoomProvider
id={roomId}
options={RoomOptionsDefaults}
>
<TestComponent />
</ChatRoomProvider>
</ChatClientProvider>
);

render(<TestProvider />);

// expect a message to be received by the second room
await waitForMessages(updatesRoomTwo, 1);
expect(updatesRoomTwo.length).toBe(1);
const update = updatesRoomTwo[0]!;
expect(update.isUpdated).toBe(true);
expect(update.updatedBy).toBe(chatClientOne.clientId);
expect(update.text).toBe('hello universe');
expect(update.metadata).toEqual({ icon: 'universe' });
expect(update.updateDetail?.description).toBe('make it better');
expect(update.updateDetail?.metadata).toEqual({ something: 'else' });
}, 10000);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Test structure looks good, but could use some improvements

The test case provides good coverage of the message update functionality. However, there are a few suggestions for improvement:

  1. The test follows the same setup pattern as other tests. Consider extracting the common setup code into a helper function to reduce duplication.
  2. The test verifies text, metadata, and update details, but misses verification of the headers property that was set in the update.

Add the missing assertion:

 expect(update.text).toBe('hello universe');
 expect(update.metadata).toEqual({ icon: 'universe' });
+expect(update.headers).toEqual({ awesome: 'yes' });
 expect(update.updateDetail?.description).toBe('make it better');
📝 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
it('should update messages correctly', async () => {
// create new clients
const chatClientOne = newChatClient() as unknown as ChatClient;
const chatClientTwo = newChatClient() as unknown as ChatClient;
// create a second room and attach it, so we can listen for deletions
const roomId = randomRoomId();
const roomTwo = chatClientTwo.rooms.get(roomId, RoomOptionsDefaults);
await roomTwo.attach();
// start listening for deletions
const updatesRoomTwo: Message[] = [];
roomTwo.messages.subscribe((message) => {
if (message.type === MessageEvents.Updated) {
updatesRoomTwo.push(message.message);
}
});
const TestComponent = () => {
const { send, update, roomStatus } = useMessages();
useEffect(() => {
if (roomStatus === RoomLifecycle.Attached) {
void send({ text: 'hello world' }).then((message) => {
void update(message, {
text: 'hello universe',
metadata: { icon: 'universe' },
headers: { awesome: 'yes' },
}, {
description: "make it better",
metadata: { something: 'else' },
});
});
}
}, [roomStatus]);
return null;
};
const TestProvider = () => (
<ChatClientProvider client={chatClientOne}>
<ChatRoomProvider
id={roomId}
options={RoomOptionsDefaults}
>
<TestComponent />
</ChatRoomProvider>
</ChatClientProvider>
);
render(<TestProvider />);
// expect a message to be received by the second room
await waitForMessages(updatesRoomTwo, 1);
expect(updatesRoomTwo.length).toBe(1);
const update = updatesRoomTwo[0]!;
expect(update.isUpdated).toBe(true);
expect(update.updatedBy).toBe(chatClientOne.clientId);
expect(update.text).toBe('hello universe');
expect(update.metadata).toEqual({ icon: 'universe' });
expect(update.updateDetail?.description).toBe('make it better');
expect(update.updateDetail?.metadata).toEqual({ something: 'else' });
}, 10000);
it('should update messages correctly', async () => {
// create new clients
const chatClientOne = newChatClient() as unknown as ChatClient;
const chatClientTwo = newChatClient() as unknown as ChatClient;
// create a second room and attach it, so we can listen for deletions
const roomId = randomRoomId();
const roomTwo = chatClientTwo.rooms.get(roomId, RoomOptionsDefaults);
await roomTwo.attach();
// start listening for deletions
const updatesRoomTwo: Message[] = [];
roomTwo.messages.subscribe((message) => {
if (message.type === MessageEvents.Updated) {
updatesRoomTwo.push(message.message);
}
});
const TestComponent = () => {
const { send, update, roomStatus } = useMessages();
useEffect(() => {
if (roomStatus === RoomLifecycle.Attached) {
void send({ text: 'hello world' }).then((message) => {
void update(message, {
text: 'hello universe',
metadata: { icon: 'universe' },
headers: { awesome: 'yes' },
}, {
description: "make it better",
metadata: { something: 'else' },
});
});
}
}, [roomStatus]);
return null;
};
const TestProvider = () => (
<ChatClientProvider client={chatClientOne}>
<ChatRoomProvider
id={roomId}
options={RoomOptionsDefaults}
>
<TestComponent />
</ChatRoomProvider>
</ChatClientProvider>
);
render(<TestProvider />);
// expect a message to be received by the second room
await waitForMessages(updatesRoomTwo, 1);
expect(updatesRoomTwo.length).toBe(1);
const update = updatesRoomTwo[0]!;
expect(update.isUpdated).toBe(true);
expect(update.updatedBy).toBe(chatClientOne.clientId);
expect(update.text).toBe('hello universe');
expect(update.metadata).toEqual({ icon: 'universe' });
expect(update.headers).toEqual({ awesome: 'yes' });
expect(update.updateDetail?.description).toBe('make it better');
expect(update.updateDetail?.metadata).toEqual({ something: 'else' });
}, 10000);
🧰 Tools
🪛 GitHub Check: lint

[failure] 186-186:
Forbidden non-null assertion

- Move DetailsMetadata type to its own file.
- Refactored message deletion to use map for message actions to events.
- Fixed typos and improved logging for message operations.
- Updated isDeleted and isUpdated to getters.
- Fix tests
@splindsay-92 splindsay-92 force-pushed the materialisation/implement-message-deleting branch from 0cede1c to f56bdd4 Compare October 24, 2024 11:08
@vladvelici vladvelici force-pushed the materialisation/update-message branch from 121075e to 0b47db3 Compare October 24, 2024 18:15
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 (2)
demo/src/containers/Chat/Chat.tsx (1)

221-221: Consider adding visual indicators for editable messages.

While the click-to-edit functionality is implemented correctly, users might not intuitively know they can click messages to edit them. Consider adding:

  • A hover effect on editable messages
  • An edit icon or tooltip
  • Documentation or first-time user guidance

Example implementation:

 <MessageComponent
   id={msg.timeserial}
   key={msg.timeserial}
   self={msg.clientId === clientId}
   message={msg}
+  className={msg.clientId === clientId ? 'cursor-pointer hover:bg-gray-100' : ''}
+  title={msg.clientId === clientId ? 'Click to edit message' : ''}
   onMessageDelete={(msg) => {
src/core/messages.ts (1)

267-281: Consider enhancing method documentation with error scenarios.

The documentation is comprehensive for the happy path, but it would be helpful to document potential error scenarios (e.g., message not found, permission denied) that consumers should handle.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 121075e and 0b47db3.

📒 Files selected for processing (7)
  • demo/src/components/MessageComponent/MessageComponent.tsx (2 hunks)
  • demo/src/containers/Chat/Chat.tsx (4 hunks)
  • demo/src/main.tsx (1 hunks)
  • src/core/chat-api.ts (4 hunks)
  • src/core/messages.ts (3 hunks)
  • src/react/hooks/use-messages.ts (4 hunks)
  • test/react/hooks/use-messages.integration.test.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • demo/src/components/MessageComponent/MessageComponent.tsx
  • demo/src/main.tsx
  • src/react/hooks/use-messages.ts
🧰 Additional context used
🪛 GitHub Check: lint
test/react/hooks/use-messages.integration.test.tsx

[failure] 185-185:
Forbidden non-null assertion

🔇 Additional comments (8)
src/core/chat-api.ts (3)

93-93: LGTM: Proper URL encoding of roomId parameter.

The implementation correctly encodes the roomId parameter to handle special characters safely, consistent with best practices.

Also applies to: 147-147


125-128: LGTM: Complete URL parameter encoding implementation.

The changes properly encode both the timeserial and roomId parameters, improving security and robustness of the API calls.


54-70: ⚠️ Potential issue

Consider using consistent metadata types across interfaces.

The metadata field in UpdateMessageParams uses Record<string, string> while other interfaces in the codebase use MessageDetailsMetadata. For consistency and type safety, consider using MessageDetailsMetadata.

  /** Metadata of the update operation */
-  metadata?: Record<string, string>;
+  metadata?: MessageDetailsMetadata;

Likely invalid or redundant comment.

demo/src/containers/Chat/Chat.tsx (2)

33-66: LGTM! Well-structured message event handling implementation.

The message event handling logic is well-implemented with proper state management for all events (Created, Updated, Deleted) and includes appropriate error logging.


167-181: Previous review comments are still applicable.

The implementation still needs to address the following previously identified issues:

  1. Replace prompt with a custom input component
  2. Add proper error handling
  3. Handle the Promise returned by the update operation
test/react/hooks/use-messages.integration.test.tsx (1)

148-178: LGTM! Comprehensive test coverage for message updates

The test component implementation thoroughly verifies the message update functionality, including:

  • Message content updates (text)
  • Metadata modifications
  • Header changes
  • Update details (description and metadata)
src/core/messages.ts (2)

159-169: LGTM! Well-structured interface for update operation details.

The UpdateMessageDetails interface is well-documented and provides a clear structure for optional metadata about message updates.


548-571: Consider potential race conditions in update operation.

The implementation correctly preserves message metadata, but there's a potential race condition if multiple updates occur simultaneously. Consider adding optimistic concurrency control or version checking to prevent lost updates.

Let's check if there are any concurrent update scenarios in the codebase:

Comment on lines +130 to +146
it('should update messages correctly', async () => {
// create new clients
const chatClientOne = newChatClient() as unknown as ChatClient;
const chatClientTwo = newChatClient() as unknown as ChatClient;

// create a second room and attach it, so we can listen for deletions
const roomId = randomRoomId();
const roomTwo = chatClientTwo.rooms.get(roomId, RoomOptionsDefaults);
await roomTwo.attach();

// start listening for deletions
const updatesRoomTwo: Message[] = [];
roomTwo.messages.subscribe((message) => {
if (message.type === MessageEvents.Updated) {
updatesRoomTwo.push(message.message);
}
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract common test setup into helper functions

The test setup code is duplicated across multiple test cases. Consider extracting the common setup into helper functions:

async function setupTestClients() {
  const chatClientOne = newChatClient() as unknown as ChatClient;
  const chatClientTwo = newChatClient() as unknown as ChatClient;
  return { chatClientOne, chatClientTwo };
}

async function setupTestRoom(client: ChatClient) {
  const roomId = randomRoomId();
  const room = client.rooms.get(roomId, RoomOptionsDefaults);
  await room.attach();
  return { roomId, room };
}

Comment on lines +182 to +192
// expect a message to be received by the second room
await waitForMessages(updatesRoomTwo, 1);
expect(updatesRoomTwo.length).toBe(1);
const update = updatesRoomTwo[0]!;
expect(update.isUpdated).toBe(true);
expect(update.updatedBy).toBe(chatClientOne.clientId);
expect(update.text).toBe('hello universe');
expect(update.metadata).toEqual({ icon: 'universe' });
expect(update.updateDetail?.description).toBe('make it better');
expect(update.updateDetail?.metadata).toEqual({ something: 'else' });
}, 10000);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing headers assertion and improve type safety

  1. Replace the non-null assertion with a runtime check:
-const update = updatesRoomTwo[0]!;
+const update = updatesRoomTwo[0];
+if (!update) {
+  throw new Error('Expected update message to be defined');
+}
  1. Add the missing headers assertion:
 expect(update.metadata).toEqual({ icon: 'universe' });
+expect(update.headers).toEqual({ awesome: 'yes' });
 expect(update.updateDetail?.description).toBe('make it better');

Committable suggestion was skipped due to low confidence.

🧰 Tools
🪛 GitHub Check: lint

[failure] 185-185:
Forbidden non-null assertion

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

🧹 Outside diff range and nitpick comments (3)
demo/src/index.css (1)

65-65: Remove empty line within CSS rule.

There's an unnecessary empty line within the CSS rule block.

 .chat-message:hover .buttons {
   display: block;
-
 }
src/core/chat-api.ts (1)

54-70: Consider using consistent metadata types across interfaces.

The metadata field in UpdateMessageParams uses MessageMetadata while the operation metadata uses MessageDetailsMetadata. Consider documenting the reason for this difference or using consistent types if they serve the same purpose.

demo/src/containers/Chat/Chat.tsx (1)

183-184: Provide user feedback when message update fails

Currently, the error during message update is logged to the console. Consider displaying an error message to the user to inform them that the update failed.

You can update the catch block as follows:

  .catch((error: unknown) => {
    console.warn('failed to update message', error);
+   // Display an error message to the user
+   alert('Failed to update the message. Please try again.');
  });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0b47db3 and b37291f.

📒 Files selected for processing (4)
  • demo/src/components/MessageComponent/MessageComponent.tsx (4 hunks)
  • demo/src/containers/Chat/Chat.tsx (4 hunks)
  • demo/src/index.css (1 hunks)
  • src/core/chat-api.ts (4 hunks)
🔇 Additional comments (3)
demo/src/components/MessageComponent/MessageComponent.tsx (1)

43-48: Verify integration with Chat SDK

The component implementation looks good but let's verify it properly integrates with the Chat SDK's message update capability.

✅ Verification successful

Let me try a different approach to verify the Chat SDK integration by searching without file type restrictions and checking for message update related code.


Message update integration is properly implemented

The verification shows a complete integration chain:

  • MessageComponent correctly implements the onMessageUpdate prop and handler
  • Parent Chat component connects it to onUpdateMessage
  • Backend integration exists through chat-api.ts and messages.ts with proper updateMessage implementation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Chat SDK message update integration
echo "Checking Chat SDK integration..."
rg -l "updateMessage|onMessageUpdate" --type typescript

# Verify message update handler implementation in parent components
echo "Checking parent component implementations..."
ast-grep --pattern 'onMessageUpdate={$_}'

Length of output: 310


Script:

#!/bin/bash
# Search for message update related code without file type restrictions
echo "Checking message update integration..."
rg "updateMessage|onMessageUpdate" -A 3

# Search for Chat SDK imports and usage
echo "Checking Chat SDK imports..."
rg "import.*[Cc]hat.*SDK" -A 1

# Look for files containing MessageComponent usage
echo "Checking MessageComponent usage..."
rg "MessageComponent" -A 3

Length of output: 3645

src/core/chat-api.ts (1)

93-93: LGTM! Consistent URL encoding implementation.

The changes properly implement URL encoding for all path parameters across methods, which improves security by preventing URL injection vulnerabilities.

Also applies to: 125-126, 147-147, 162-162

demo/src/containers/Chat/Chat.tsx (1)

25-35: LGTM!

The handleUpdatedMessage function correctly updates the message state when a message is edited.

Comment on lines +59 to +61
.chat-message .buttons {
display: none;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using opacity or visibility for smoother transitions.

The current implementation using display: none/block might cause layout shifts when the buttons appear. Consider using opacity or visibility with opacity for a smoother transition:

 .chat-message .buttons {
-  display: none;
+  visibility: hidden;
+  opacity: 0;
+  transition: opacity 0.2s ease-in-out;
 }

 .chat-message:hover .buttons {
-  display: block;
+  visibility: visible;
+  opacity: 1;
 }

Additionally, consider adding touch device support since hover states don't work well on mobile:

@media (hover: none) {
  .chat-message .buttons {
    visibility: visible;
    opacity: 1;
  }
}

Also applies to: 63-66

Comment on lines +25 to +41
function shortDate(date: Date): string {
if (Date.now() - date.getTime() < 1000 * 60 * 60 * 24) {
// last 24h show the time
displayCreatedAt = twoDigits(message.createdAt.getHours()) + ':' + twoDigits(message.createdAt.getMinutes());
return twoDigits(date.getHours()) + ':' + twoDigits(date.getMinutes());
} else {
// older, show full date
displayCreatedAt =
message.createdAt.getDate() +
return date.getDate() +
'/' +
message.createdAt.getMonth() +
date.getMonth() +
'/' +
message.createdAt.getFullYear() +
date.getFullYear() +
' ' +
twoDigits(message.createdAt.getHours()) +
twoDigits(date.getHours()) +
':' +
twoDigits(message.createdAt.getMinutes());
twoDigits(date.getMinutes());
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Fix date formatting issues in shortDate function

There are several issues with the date formatting implementation:

  1. date.getMonth() returns 0-based month (0-11), leading to incorrect month display
  2. The function doesn't handle different locales gracefully

Apply this fix:

 function shortDate(date: Date): string {
   if (Date.now() - date.getTime() < 1000 * 60 * 60 * 24) {
     return twoDigits(date.getHours()) + ':' + twoDigits(date.getMinutes());
   } else {
     return date.getDate() +
       '/' +
-      date.getMonth() +
+      (date.getMonth() + 1) +
       '/' +
       date.getFullYear() +
       ' ' +
       twoDigits(date.getHours()) +
       ':' +
       twoDigits(date.getMinutes());
   }
 }

Consider using Intl.DateTimeFormat for locale-aware date formatting:

function shortDate(date: Date): string {
  if (Date.now() - date.getTime() < 1000 * 60 * 60 * 24) {
    return new Intl.DateTimeFormat('default', {
      hour: '2-digit',
      minute: '2-digit'
    }).format(date);
  }
  return new Intl.DateTimeFormat('default', {
    day: '2-digit',
    month: '2-digit',
    year: 'numeric',
    hour: '2-digit',
    minute: '2-digit'
  }).format(date);
}

Comment on lines +72 to +76
{message.isUpdated && (<> &middot; Edited <span className="sent-at-time">
<span className="short">{shortDate(message.updatedAt!)}</span>
<span className="long">{message.createdAt.toLocaleString()}</span>
</span>
</>)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null check for updatedAt timestamp

The code assumes updatedAt is always present when isUpdated is true, but TypeScript indicates it's optional.

Add proper null checking:

- {message.isUpdated && (<> &middot; Edited <span className="sent-at-time">
-   <span className="short">{shortDate(message.updatedAt!)}</span>
-   <span className="long">{message.createdAt.toLocaleString()}</span>
- </span>
- </>)}
+ {message.isUpdated && message.updatedAt && (
+   <> &middot; Edited <span className="sent-at-time">
+     <span className="short">{shortDate(message.updatedAt)}</span>
+     <span className="long">{message.updatedAt.toLocaleString()}</span>
+   </span>
+   </>
+ )}
📝 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
{message.isUpdated && (<> &middot; Edited <span className="sent-at-time">
<span className="short">{shortDate(message.updatedAt!)}</span>
<span className="long">{message.createdAt.toLocaleString()}</span>
</span>
</>)}
{message.isUpdated && message.updatedAt && (
<> &middot; Edited <span className="sent-at-time">
<span className="short">{shortDate(message.updatedAt)}</span>
<span className="long">{message.updatedAt.toLocaleString()}</span>
</span>
</>
)}

Comment on lines +86 to +93
<div className="buttons"
><FaPencil
className="cursor-pointer text-gray-100 m-1 hover:text-gray-500 inline-block"
onClick={handleMessageUpdate}
></FaPencil><FaTrash
className="cursor-pointer text-red-500 m-1 hover:text-red-700 inline-block"
onClick={handleMessageDelete}
/></div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve accessibility for edit/delete buttons

The icon buttons lack proper accessibility attributes and semantic HTML.

Add proper button semantics and aria labels:

- <div className="buttons"
-   ><FaPencil
-     className="cursor-pointer text-gray-100 m-1 hover:text-gray-500 inline-block"
-     onClick={handleMessageUpdate}
-   ></FaPencil><FaTrash
-     className="cursor-pointer text-red-500 m-1 hover:text-red-700 inline-block"
-     onClick={handleMessageDelete}
-   /></div>
+ <div className="buttons" role="group" aria-label="Message actions">
+   <button
+     type="button"
+     className="p-1 hover:bg-gray-100 rounded"
+     onClick={handleMessageUpdate}
+     aria-label="Edit message"
+   >
+     <FaPencil className="text-gray-100 hover:text-gray-500" />
+   </button>
+   <button
+     type="button"
+     className="p-1 hover:bg-gray-100 rounded"
+     onClick={handleMessageDelete}
+     aria-label="Delete message"
+   >
+     <FaTrash className="text-red-500 hover:text-red-700" />
+   </button>
+ </div>
📝 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
<div className="buttons"
><FaPencil
className="cursor-pointer text-gray-100 m-1 hover:text-gray-500 inline-block"
onClick={handleMessageUpdate}
></FaPencil><FaTrash
className="cursor-pointer text-red-500 m-1 hover:text-red-700 inline-block"
onClick={handleMessageDelete}
/></div>
<div className="buttons" role="group" aria-label="Message actions">
<button
type="button"
className="p-1 hover:bg-gray-100 rounded"
onClick={handleMessageUpdate}
aria-label="Edit message"
>
<FaPencil className="text-gray-100 hover:text-gray-500" />
</button>
<button
type="button"
className="p-1 hover:bg-gray-100 rounded"
onClick={handleMessageDelete}
aria-label="Delete message"
>
<FaTrash className="text-red-500 hover:text-red-700" />
</button>
</div>

Comment on lines 49 to 55
const handleMessageUpdate = useCallback((e : React.UIEvent) => {
e.stopPropagation(); onMessageUpdate?.(message);
}, [ message ]);

const handleDelete = useCallback(() => {
// Add your delete handling logic here
onMessageDelete?.(message);
}, [message, onMessageDelete]);
const handleMessageDelete = useCallback((e : React.UIEvent) => {
e.stopPropagation(); onMessageDelete?.(message);
}, [ message ]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve event handlers implementation

The event handlers need improvements for better error handling and accessibility:

  1. Missing preventDefault() for UI events
  2. No feedback when callbacks are not provided
  3. Missing error boundaries

Apply these improvements:

- const handleMessageUpdate = useCallback((e : React.UIEvent) => {
-   e.stopPropagation(); onMessageUpdate?.(message);
- }, [ message ]);
+ const handleMessageUpdate = useCallback((e: React.MouseEvent) => {
+   e.preventDefault();
+   e.stopPropagation();
+   if (onMessageUpdate) {
+     onMessageUpdate(message);
+   }
+ }, [message, onMessageUpdate]);

- const handleMessageDelete = useCallback((e : React.UIEvent) => {
-   e.stopPropagation(); onMessageDelete?.(message);
- }, [ message ]);
+ const handleMessageDelete = useCallback((e: React.MouseEvent) => {
+   e.preventDefault();
+   e.stopPropagation();
+   if (onMessageDelete) {
+     onMessageDelete(message);
+   }
+ }, [message, onMessageDelete]);
📝 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
const handleMessageUpdate = useCallback((e : React.UIEvent) => {
e.stopPropagation(); onMessageUpdate?.(message);
}, [ message ]);
const handleDelete = useCallback(() => {
// Add your delete handling logic here
onMessageDelete?.(message);
}, [message, onMessageDelete]);
const handleMessageDelete = useCallback((e : React.UIEvent) => {
e.stopPropagation(); onMessageDelete?.(message);
}, [ message ]);
const handleMessageUpdate = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (onMessageUpdate) {
onMessageUpdate(message);
}
}, [message, onMessageUpdate]);
const handleMessageDelete = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (onMessageDelete) {
onMessageDelete(message);
}
}, [message, onMessageDelete]);

Comment on lines +151 to +159
async updateMessage(roomId: string, serial: string, params: UpdateMessageParams): Promise<UpdateMessageResponse> {
const encodedSerial = encodeURIComponent(serial);
roomId = encodeURIComponent(roomId);
return this._makeAuthorizedRequest<UpdateMessageResponse>(
`/chat/v2/rooms/${roomId}/messages/${encodedSerial}`,
'PUT',
params,
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation and error documentation.

While the implementation is solid, consider these improvements:

  1. Add input validation for required fields
  2. Document possible error cases in JSDoc

Apply this improvement:

+ /**
+  * Updates a message in a chat room.
+  * @param roomId - The ID of the room containing the message
+  * @param serial - The timeserial of the message to update
+  * @param params - The update parameters
+  * @throws {Error} If message text is empty or contains only whitespace
+  * @throws {Ably.ErrorInfo} If the request fails
+  * @returns {Promise<UpdateMessageResponse>} The update response containing the timestamp
+  */
  async updateMessage(roomId: string, serial: string, params: UpdateMessageParams): Promise<UpdateMessageResponse> {
+   if (!params?.message?.text?.trim()) {
+     throw new Error('Message text cannot be empty');
+   }
    const encodedSerial = encodeURIComponent(serial);
    roomId = encodeURIComponent(roomId);
    return this._makeAuthorizedRequest<UpdateMessageResponse>(
      `/chat/v2/rooms/${roomId}/messages/${encodedSerial}`,
      'PUT',
      params
    );
  }
📝 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
async updateMessage(roomId: string, serial: string, params: UpdateMessageParams): Promise<UpdateMessageResponse> {
const encodedSerial = encodeURIComponent(serial);
roomId = encodeURIComponent(roomId);
return this._makeAuthorizedRequest<UpdateMessageResponse>(
`/chat/v2/rooms/${roomId}/messages/${encodedSerial}`,
'PUT',
params,
);
}
/**
* Updates a message in a chat room.
* @param roomId - The ID of the room containing the message
* @param serial - The timeserial of the message to update
* @param params - The update parameters
* @throws {Error} If message text is empty or contains only whitespace
* @throws {Ably.ErrorInfo} If the request fails
* @returns {Promise<UpdateMessageResponse>} The update response containing the timestamp
*/
async updateMessage(roomId: string, serial: string, params: UpdateMessageParams): Promise<UpdateMessageResponse> {
if (!params?.message?.text?.trim()) {
throw new Error('Message text cannot be empty');
}
const encodedSerial = encodeURIComponent(serial);
roomId = encodeURIComponent(roomId);
return this._makeAuthorizedRequest<UpdateMessageResponse>(
`/chat/v2/rooms/${roomId}/messages/${encodedSerial}`,
'PUT',
params
);
}

Comment on lines +171 to +185
const onUpdateMessage = (message: Message) => {
const newText = prompt('Enter new text');
if (!newText) {
return;
}
update(message, {
text: newText,
metadata: message.metadata,
headers: message.headers,
}).then((updatedMessage: Message) => {
handleUpdatedMessage(updatedMessage);
}).catch((error: unknown) => {
console.warn("failed to update message", error);
});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Wrap onUpdateMessage with useCallback to optimize rendering

Since onUpdateMessage is passed as a prop to MessageComponent, wrapping it with useCallback will prevent unnecessary re-renders of child components.

Apply this diff to wrap the function with useCallback:

- const onUpdateMessage = (message: Message) => {
+ const onUpdateMessage = useCallback((message: Message) => {
    const newText = prompt('Enter new text');
    if (!newText) {
      return;
    }
    update(message, {
      text: newText,
      metadata: message.metadata,
      headers: message.headers,
    })
      .then((updatedMessage: Message) => {
        handleUpdatedMessage(updatedMessage);
      })
      .catch((error: unknown) => {
        console.warn('failed to update message', error);
      });
- };
+ }, [update]);

Committable suggestion was skipped due to low confidence.

@splindsay-92 splindsay-92 force-pushed the materialisation/implement-message-deleting branch 2 times, most recently from a35c6b1 to e8eeb74 Compare October 25, 2024 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants