Skip to content

Conversation

anmolsinghbhatia
Copy link
Collaborator

@anmolsinghbhatia anmolsinghbhatia commented Sep 1, 2025

Description

This PR resolves issues with comments and reactions made by external users.

Type of Change

  • Bug fix
  • Improvement

Summary by CodeRabbit

  • New Features

    • Reactions and comments now include and surface an explicit display name, improving name visibility across the app.
    • Comment headers show clear display names, include “Bot” for automated actors, and append “(External User)” when applicable.
    • Avatars consistently render with improved name/thumbnail selection.
  • Bug Fixes

    • Fixed missing reaction usernames by reliably falling back to actor display names when user details are unavailable.

Copy link
Contributor

coderabbitai bot commented Sep 1, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-external-user-comment-and-reaction

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

❤️ Share

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

Copy link

makeplane bot commented Sep 1, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

@NarayanBavisetti NarayanBavisetti marked this pull request as ready for review September 4, 2025 07:39
@Copilot Copilot AI review requested due to automatic review settings September 4, 2025 07:39
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes issues with comments and reactions made by external users by ensuring proper display name fallbacks and appropriate labeling in the UI.

  • Added display_name field to issue and comment reaction types
  • Updated reaction components to fall back to display_name when user details are unavailable
  • Enhanced comment block component to handle external users with proper labeling and display name fallbacks

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/types/src/issues/issue_reaction.ts Added display_name field to TIssueReaction type
packages/types/src/issues/activity/issue_comment_reaction.ts Added display_name field to TIssueCommentReaction type
apps/web/core/components/issues/issue-detail/reactions/issue.tsx Updated reaction display logic to fallback to display_name when user details unavailable
apps/web/core/components/issues/issue-detail/reactions/issue-comment.tsx Updated comment reaction display logic with display_name fallback
apps/web/ce/components/comments/comment-block.tsx Enhanced comment display with external user labeling and improved fallback logic
apps/api/plane/app/serializers/issue.py Added display_name field to reaction serializers and explicitly defined fields

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/web/core/components/issues/issue-detail/reactions/issue-comment.tsx (2)

60-67: Missing await on remove → false-positive success + unhandled rejection.

Promise rejection won’t be caught; success toast may show even on failure.

-          removeCommentReaction(workspaceSlug, projectId, commentId, reaction, currentUser.id);
+          await removeCommentReaction(workspaceSlug, projectId, commentId, reaction, currentUser.id);

108-133: Fix React list keys.

Key is on the inner button; the fragment returned by map lacks a key and will warn.

-              <>
-                <Tooltip tooltipContent={getReactionUsers(reaction)}>
+              <Tooltip key={reaction} tooltipContent={getReactionUsers(reaction)}>
                   <button
                     type="button"
                     onClick={() => !disabled && issueCommentReactionOperations.react(reaction)}
-                    key={reaction}
                     className={cn(
                       "flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100",
                       userReactions.includes(reaction) ? "bg-custom-primary-100/10" : "bg-custom-background-80",
                       {
                         "cursor-not-allowed": disabled,
                       }
                     )}
                   >
                     <span>{renderEmoji(reaction)}</span>
                     <span className={userReactions.includes(reaction) ? "text-custom-primary-100" : ""}>
                       {(reactionIds || {})[reaction].length}{" "}
                     </span>
                   </button>
-                </Tooltip>
-              </>
+              </Tooltip>
apps/web/core/components/issues/issue-detail/reactions/issue.tsx (1)

108-127: Fix React list keys.

Same key placement issue as in comment reactions.

-              <>
-                <Tooltip tooltipContent={getReactionUsers(reaction)}>
+              <Tooltip key={reaction} tooltipContent={getReactionUsers(reaction)}>
                   <button
                     type="button"
                     onClick={() => !disabled && issueReactionOperations.react(reaction)}
-                    key={reaction}
                     className={cn(
                       "flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100",
                       userReactions.includes(reaction) ? "bg-custom-primary-100/10" : "bg-custom-background-80",
                       {
                         "cursor-not-allowed": disabled,
                       }
                     )}
                   >
                     <span>{renderEmoji(reaction)}</span>
                     <span className={userReactions.includes(reaction) ? "text-custom-primary-100" : ""}>
                       {(reactionIds || {})[reaction].length}{" "}
                     </span>
                   </button>
-                </Tooltip>
-              </>
+              </Tooltip>
🧹 Nitpick comments (6)
apps/api/plane/app/serializers/issue.py (1)

670-675: Prefer non-null display_name from serializer.

If actor.display_name can be empty/null, tooltips may end up blank. Consider a safe fallback via a method field.

-class IssueReactionLiteSerializer(DynamicBaseSerializer):
-    display_name = serializers.CharField(source="actor.display_name", read_only=True)
+class IssueReactionLiteSerializer(DynamicBaseSerializer):
+    display_name = serializers.SerializerMethodField(read_only=True)
+
+    def get_display_name(self, obj):
+        actor = getattr(obj, "actor", None)
+        return getattr(actor, "display_name", None) or getattr(actor, "email", None) or ""

(Replicate for CommentReactionSerializer if desired.)

apps/web/core/components/issues/issue-detail/reactions/issue-comment.tsx (1)

18-24: Avoid name collision with domain types.

Local props type name shadows the shared TIssueCommentReaction domain type; rename for clarity.

-export type TIssueCommentReaction = {
+export type TIssueCommentReactionProps = {
@@
-export const IssueCommentReaction: FC<TIssueCommentReaction> = observer((props) => {
+export const IssueCommentReaction: FC<TIssueCommentReactionProps> = observer((props) => {
apps/web/core/components/issues/issue-detail/reactions/issue.tsx (1)

40-82: Simplify self-referential ops to avoid circular reference.

Define helpers first; return them from useMemo for cleaner closures.

-  const issueReactionOperations = useMemo(
-    () => ({
-      create: async (reaction: string) => {
+  const issueReactionOperations = useMemo(() => {
+    const create = async (reaction: string) => {
         try {
           if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields");
           await createReaction(workspaceSlug, projectId, issueId, reaction);
           setToast({
             title: "Success!",
             type: TOAST_TYPE.SUCCESS,
             message: "Reaction created successfully",
           });
         } catch (error) {
           setToast({
             title: "Error!",
             type: TOAST_TYPE.ERROR,
             message: "Reaction creation failed",
           });
         }
-      },
-      remove: async (reaction: string) => {
+      };
+    const remove = async (reaction: string) => {
         try {
           if (!workspaceSlug || !projectId || !issueId || !currentUser?.id) throw new Error("Missing fields");
           await removeReaction(workspaceSlug, projectId, issueId, reaction, currentUser.id);
           setToast({
             title: "Success!",
             type: TOAST_TYPE.SUCCESS,
             message: "Reaction removed successfully",
           });
         } catch (error) {
           setToast({
             title: "Error!",
             type: TOAST_TYPE.ERROR,
             message: "Reaction remove failed",
           });
         }
-      },
-      react: async (reaction: string) => {
-        if (userReactions.includes(reaction)) await issueReactionOperations.remove(reaction);
-        else await issueReactionOperations.create(reaction);
-      },
-    }),
-    [workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, userReactions]
-  );
+      };
+    const react = async (reaction: string) => {
+      if (userReactions.includes(reaction)) await remove(reaction);
+      else await create(reaction);
+    };
+    return { create, remove, react };
+  }, [workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, userReactions]);
apps/web/ce/components/comments/comment-block.tsx (3)

29-34: Harden name/avatar fallbacks to avoid “undefined” in UI; verify avatar field name.
If display_name is absent, the UI can render “undefined”. Also, confirm whether the store returns avatar_url (not avatar) consistently.

Proposed minimal, robust fallback chain:

-  const displayName = comment?.actor_detail?.is_bot
-    ? comment?.actor_detail?.first_name + ` ${t("bot")}`
-    : (userDetails?.display_name ?? comment?.actor_detail?.display_name);
-
-  const avatarUrl = userDetails?.avatar_url ?? comment?.actor_detail?.avatar_url;
+  const baseName =
+    userDetails?.display_name ??
+    comment?.actor_detail?.display_name ??
+    [comment?.actor_detail?.first_name, comment?.actor_detail?.last_name].filter(Boolean).join(" ") ||
+    comment?.actor_detail?.email ??
+    t("unknown_user");
+
+  const displayName = comment?.actor_detail?.is_bot ? `${baseName} ${t("bot")}` : baseName;
+
+  const avatarUrl =
+    userDetails?.avatar_url ??
+    comment?.actor_detail?.avatar_url ??
+    null;

If getUserDetails uses avatar (no _url), extend the fallback with that key as needed.


35-35: Move the null-guard above derived values.
Avoid calling getUserDetails(comment?.actor) and computing name/url when comment is falsy.


51-51: Guard Avatar props to handle missing name/src gracefully.
Prevents passing undefined to getFileURL and ensures a readable fallback name.

-        <Avatar size="base" name={displayName} src={getFileURL(avatarUrl)} className="flex-shrink-0" />
+        <Avatar
+          size="base"
+          name={displayName ?? t("unknown_user")}
+          src={avatarUrl ? getFileURL(avatarUrl) : undefined}
+          className="flex-shrink-0"
+        />

If getFileURL already handles undefined safely, feel free to keep it simple; otherwise this guards it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 473535f and 7b55679.

📒 Files selected for processing (6)
  • apps/api/plane/app/serializers/issue.py (1 hunks)
  • apps/web/ce/components/comments/comment-block.tsx (3 hunks)
  • apps/web/core/components/issues/issue-detail/reactions/issue-comment.tsx (1 hunks)
  • apps/web/core/components/issues/issue-detail/reactions/issue.tsx (1 hunks)
  • packages/types/src/issues/activity/issue_comment_reaction.ts (1 hunks)
  • packages/types/src/issues/issue_reaction.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/api/plane/app/serializers/issue.py (3)
apps/api/plane/space/serializer/issue.py (1)
  • CommentReactionSerializer (419-423)
apps/api/plane/api/serializers/base.py (1)
  • BaseSerializer (5-117)
apps/api/plane/db/models/issue.py (1)
  • CommentReaction (624-650)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and lint web apps
🔇 Additional comments (1)
apps/web/ce/components/comments/comment-block.tsx (1)

5-6: Imports look correct and align with the updated types/UI kit.

@sriramveeraghanta sriramveeraghanta merged commit 56cd0fc into preview Sep 9, 2025
6 of 10 checks passed
@sriramveeraghanta sriramveeraghanta deleted the fix-external-user-comment-and-reaction branch September 9, 2025 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants