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

Feat/frontend/chat/invite pong #227

Merged
merged 9 commits into from
Jan 23, 2024
Merged

Feat/frontend/chat/invite pong #227

merged 9 commits into from
Jan 23, 2024

Conversation

kotto5
Copy link
Collaborator

@kotto5 kotto5 commented Jan 23, 2024

frontend で、invite を実行できるようにしました

  • chat の user 右クリックから invite できます。
  • invite すると相手のuser に通知が行きます( message と一緒 )

TODO
(code rabbit)

  • Acknowledgment を用いた リアルタイム処理
  • Acknowledgment を用いた エラーハンドリング
  • Acknowledgment について知る

image

image

Copy link
Contributor

coderabbitai bot commented Jan 23, 2024

Warning

Rate Limit Exceeded

@kotto5 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 27 minutes and 27 seconds before requesting another review.

How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.
Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
Please see our FAQ for further information.

Commits Files that changed from the base of the PR and between 0d32392 and 2fb2178.

Walkthrough

The recent updates focus on enhancing chat functionality, particularly around invitation handling in a pong game context. New error handling and event emission logic have been added to the backend, improving the user experience during invite cancellation. The frontend now includes additional states and functions to manage pong match invitations and responses, along with new toast notifications. Changes also reflect a shift from private to public user entities in the chat service, affecting how users are stored and message entities are constructed.

Changes

File(s) Change Summary
backend/src/chat/chat.gateway.ts, backend/test/chat-gateway.e2e-spec.ts Added error handling and invite-cancel-pong event emission in ChatGateway. Updated test descriptions and added new tests for invite-cancel functionality.
backend/src/chat/chat.service.ts, backend/src/chat/entities/message.entity.ts Imported PublicUserEntity and updated users map type. Renamed PrivateUserEntity to PublicUserEntity and modified MessageEntity constructor.
frontend/app/lib/client-socket-provider.tsx, frontend/app/room/[id]/sidebar-item.tsx, frontend/app/lib/dtos.ts Expanded SocketProvider to handle pong game events and added new types. Updated SidebarItem to manage invitation state and functions, and modified MessageEvent type.

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>.
    • Generate unit-tests for this file.
  • 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 tests 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 generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

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 as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • 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/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Comment on lines +1 to +2
export class PublicUserEntity {
constructor(partial: Partial<PublicUserEntity>) {
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 23, 2024

Choose a reason for hiding this comment

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

The constructor of PublicUserEntity does not check for the presence of partial before accessing its properties. This could lead to a runtime error if partial is null or undefined.

  constructor(partial: Partial<PublicUserEntity>) {
+   if (!partial) {
+     throw new Error('PublicUserEntity constructor requires a partial object');
+   }
    this.id = partial.id;
    this.name = partial.name;
    this.avatarURL = partial.avatarURL;
  }

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.

Suggested change
export class PublicUserEntity {
constructor(partial: Partial<PublicUserEntity>) {
export class PublicUserEntity {
constructor(partial: Partial<PublicUserEntity>) {
if (!partial) {
throw new Error('PublicUserEntity constructor requires a partial object');
}
this.id = partial.id;
this.name = partial.name;
this.avatarURL = partial.avatarURL;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

null が入ったらどうなるのが正解なのか。
undefined だったら別に 全部のメンバがundefined でよさそうだけど
runtime error になっちゃうのか

Copy link
Contributor

Choose a reason for hiding this comment

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

@kotto5 通常、TypeScriptではPartial<Type>を使用すると、Typeのすべてのプロパティがオプショナルになります。しかし、nullを渡すと、nullはプロパティを持たないため、Partial<Type>の契約を満たしません。undefinedが渡された場合、すべてのメンバ変数がundefinedになることは許容されるかもしれませんが、これは意図的な設計かどうかによります。

コンストラクタがnullundefinedを許容し、すべてのメンバ変数をundefinedに設定することが適切か、それとも将来的な問題を防ぐためにエラーを投げるべきかは、アプリケーションの設計とエラーハンドリング戦略に依存します。もしPublicUserEntityが有効な状態でのみ存在するべきなら、nullundefinedを渡すことによるランタイムエラーを防ぐために、引数のチェックを行うことが推奨されます。それにより、不正な状態でのオブジェクトの作成を防ぎ、デバッグを容易にすることができます。

もしundefinedでのメンバ変数の初期化が許容される場合は、コンストラクタの引数チェックを省略し、partialundefinedまたはnullの場合には、すべてのメンバ変数をundefinedで初期化することができます。ただし、これは後のコードで予期せぬundefinedの扱いによるエラーを引き起こす可能性があるため、注意が必要です。

どちらのアプローチを取るかは、アプリケーションの要件とエラーハンドリングの方針によります。もしエラーチェックを行う方針であれば、提案したコードが適切です。もしundefinedを許容する方針であれば、コンストラクタの引数チェックを省略することができます。どちらの方針を取るかについてご意見をいただければ、それに基づいてさらにサポートさせていただきます。

Comment on lines +65 to +67
export type InviteEvent = {
userId: number;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

The InviteEvent type only includes userId but does not specify the type of invitation or any other context. This might be insufficient for handling different types of invitations in the future.

Consider adding more context to the InviteEvent type to make it more extensible.

frontend/app/lib/dtos.ts Show resolved Hide resolved
frontend/app/lib/dtos.ts Outdated Show resolved Hide resolved
Comment on lines +27 to +55
const showInvitePongToast = (message: InviteEvent) => {
toast({
title: `user id: ${message.userId}`,
description: ` invited you to play pong!`,
action: (
<ToastAction altText="approve" asChild>
<>
<button
onClick={() => {
socket.emit("approve-pong", {
userId: message.userId,
});
}}
>
approve
</button>
<button
onClick={() => {
socket.emit("deny-pong", {
userId: message.userId,
});
}}
>
Deny
</button>
</>
</ToastAction>
),
});
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 23, 2024

Choose a reason for hiding this comment

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

The showInvitePongToast function emits socket events without any error handling or acknowledgment mechanism. This could lead to unhandled errors or a lack of user feedback if the socket operation fails.

Implement an acknowledgment callback for the socket events to handle potential errors and provide user feedback.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

acknowledgment って返信みたいに捉えてるけどどうやって実装されているんだろう
ws.on(event) の event も emit する情報に含まれてる?

ちなみにerror 処理ってやること何があるんですか?

Copy link
Contributor

Choose a reason for hiding this comment

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

@kotto5 Acknowledgment in the context of WebSocket events, especially when using libraries like socket.io, is typically implemented by providing a callback function when emitting an event. This callback is called by the server to acknowledge the receipt of the event. Here's a simplified example of how you might implement it:

socket.emit("approve-pong", { userId: message.userId }, (response) => {
  // Handle acknowledgment here
  if (response.error) {
    // Handle the error case
  } else {
    // Proceed with the success case
  }
});

In this example, the third argument to emit is a callback function that the server should call to acknowledge the event. The server-side code would need to support this by calling the callback with the appropriate response.

Regarding error handling, there are a few things you might consider:

  1. Handling errors when the socket connection fails, which could involve retrying the connection or informing the user that the service is currently unavailable.
  2. Handling cases where the server does not acknowledge the event within a certain timeframe, which could involve setting a timeout and providing feedback to the user.
  3. Dealing with exceptions that may occur during the event handling process, which could involve try-catch blocks and user-friendly error messages.

Each of these would require additional code to handle the various scenarios that could occur during WebSocket communication.

Comment on lines +78 to +81
const showDenyPongToast = (data: DenyEvent) => {
toast({
title: `Your invite was denied`,
});
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 23, 2024

Choose a reason for hiding this comment

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

The showDenyPongToast function displays a generic denial message without specifying which invitation was denied. This could be confusing for users with multiple pending invitations.

Include information about the specific invitation that was denied to provide clearer feedback to the user.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

いや、invite は同時に一人しかできないので大丈夫
でも、invite が同時に一人しか出来ないのは user から全く確認できないようになってるなあ

Copy link
Contributor

Choose a reason for hiding this comment

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

@kotto5 了解しました。ユーザーが同時に一人だけに招待を送れるという制限は理解しました。ユーザーインターフェースでこの制限がユーザーに明確でないという点については、将来的にユーザーにもっと透明にすることを検討する価値があるかもしれませんね。フィードバックありがとうございます。

frontend/app/lib/client-socket-provider.tsx Outdated Show resolved Hide resolved
backend/src/chat/chat.gateway.ts Show resolved Hide resolved
@@ -71,6 +71,7 @@ export default function SidebarItem({
const [isMuted, setIsMuted] = useState(
mutedUsers?.some((u: PublicUserEntity) => u.id === user.userId),
);
const [isInviting, setIsInviting] = useState(false);
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 23, 2024

Choose a reason for hiding this comment

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

The isInviting state is used to manage the invitation status, but there is no error handling or acknowledgment mechanism for the invitation process. This could lead to a mismatch between the UI state and the actual invitation status.

Implement an acknowledgment mechanism for the invitation process to ensure the UI state accurately reflects the actual status.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

確かに。
でもどう解決するのかわからない
state のチェックは backend に api 飛ばした返り値で更新するとか?
invite/who みたいな

Copy link
Contributor

Choose a reason for hiding this comment

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

@kotto5 確かに、フロントエンドの状態をバックエンドのAPIの応答で更新することは一つの解決策です。しかし、リアルタイムのアプリケーションでは、WebSocketのイベントリスナーやacknowledgmentを使用してUIの状態を更新するのが一般的です。例えば、socket.emitで招待を送信する際に、第三引数としてacknowledgment callbackを提供することができます。これにより、サーバーからの応答をリアルタイムで受け取り、状態を適切に更新することが可能になります。

const invite = () => {
  socket.emit("invite-pong", { userId: user.userId }, (response) => {
    // ここでサーバーからの応答に基づいてUIの状態を更新
    if (response.status === "success") {
      setIsInviting(true);
    } else {
      // エラーハンドリング
    }
  });
};

または、サーバーからの特定のイベントをリッスンして、それに応じて状態を更新することもできます。

useEffect(() => {
  socket.on("invite-pong-ack", (data) => {
    if (data.userId === user.userId) {
      setIsInviting(data.isInviting);
    }
  });

  // 必要に応じてイベントリスナーを解除
  return () => {
    socket.off("invite-pong-ack");
  };
}, [user.userId]);

これらの方法を使用すると、ユーザーのアクションに対する即時のフィードバックをUIに提供し、状態の不一致を防ぐことができます。

Comment on lines +115 to +121
const invite = () => {
socket.emit("invite-pong", { userId: user.userId });
setIsInviting(true);
};
const cancelInvite = () => {
socket.emit("invite-cancel-pong", { userId: user.userId });
setIsInviting(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

The invite and cancelInvite functions emit socket events without any error handling or acknowledgment mechanism. This could lead to unhandled errors or a lack of user feedback if the socket operation fails.

Implement an acknowledgment callback for the socket events to handle potential errors and provide user feedback.

@kotto5 kotto5 force-pushed the feat/frontend/chat/invite-pong branch from b7037c2 to 79157f5 Compare January 23, 2024 12:13
@kotto5 kotto5 force-pushed the feat/frontend/chat/invite-pong branch from 79157f5 to c6db1ec Compare January 23, 2024 12:28
@kotto5 kotto5 force-pushed the feat/frontend/chat/invite-pong branch from 563980c to 3d131eb Compare January 23, 2024 12:48
@kotto5 kotto5 linked an issue Jan 23, 2024 that may be closed by this pull request
@kotto5 kotto5 merged commit f2aa2fa into main Jan 23, 2024
4 checks passed
@kotto5 kotto5 deleted the feat/frontend/chat/invite-pong branch January 23, 2024 12:53
Copy link
Collaborator

@lim396 lim396 left a comment

Choose a reason for hiding this comment

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

PRがfrontendとなっていますが、backendも変更されているのでPRの名前は変えた方がよいかもです。(マージ時の変更忘れだったらどんまいです!)
frontendを読む気力が残っていないので、一旦backendだけコメントを書いておきます。
また、コードを読んだだけで実際の動作の確認はまだ出来ていません。


class PrivateUserEntity {
constructor(partial: Partial<PrivateUserEntity>) {
export class PublicUserEntity {
Copy link
Collaborator

Choose a reason for hiding this comment

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

PublicUserEntityという命名はuser/entities/public-user.entity.tsに重複する名前のものがあるため別のものにした方が良いです。

this.content = partial.content;
this.roomId = partial.roomId;
this.user = new PrivateUserEntity(user);
this.user = user;
Copy link
Collaborator

@lim396 lim396 Jan 23, 2024

Choose a reason for hiding this comment

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

MessageEntityのコードは元のコードの方が良いと思います。
MessageEntityで除外できていた要素をMessageEntityの外で処理しなければいけなくなるので。

canceler.ws.emit('invite-cancel-pong');
});
it('should receive an error when canceling without an existing invite', () =>
errorCtx);
Copy link
Collaborator

@lim396 lim396 Jan 23, 2024

Choose a reason for hiding this comment

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

1423行目のテストは主語がないと誰がerrorを受け取るのか少し分かりづらいかもです。(cancelingをした人であることは自明ではあるのですが一応)
また、inviteeがinvite-cancel-pongのイベントを受け取らないことを確認するテストもあるとより良いと思います。

@kotto5 kotto5 added the enhancement New feature or request label Feb 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

invite to play
2 participants