Skip to content

Novera chat Integration - #232

Merged
Rashmika998 merged 9 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/Add-update-Page
Feb 23, 2026
Merged

Rashmika998 merged 9 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/Add-update-Page

Conversation

@dileepapeiris

@dileepapeiris dileepapeiris commented Feb 23, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc.

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

Approach

Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here.

User stories

Summary of user stories addressed by this change>

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter �N/A� plus brief explanation of why there�s no doc impact

Training

Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable

Certification

Type �Sent� when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type �N/A� and explain why.

Marketing

Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable

Automation tests

  • Unit tests

    Code coverage information

  • Integration tests

    Details about the test cases and coverage

Security checks

Samples

Provide high-level details about the samples related to this feature

Related PRs

List any other related PRs

Migrations (if applicable)

Describe migration steps and platforms on which migration has been tested

Test environment

List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested

Learning

Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem.

Summary by CodeRabbit

  • New Features

    • Rich-text chat input, copy-to-clipboard for messages, animated loading indicator, and backend message send/receive integration.
  • Bug Fixes

    • Improved product and deployment label matching (more robust matching) and clearer error propagation when sending messages.
  • Style

    • Updated case-type colors and increased editor height.
  • Navigation

    • "Start New Chat" now leads into the Describe Issue flow.
  • Tests

    • Expanded unit tests for chat UI, loading states, copy behavior, and API flows.

NoveraChatPage: integrate conversation APIs and implement a real send flow — add usePostConversations/usePostConversationMessages, sendToApi helper, and updated handleSendMessage to post user input (using htmlToPlainText), show a loading bot message, and handle success/error states (isSending, isLoading, isError). Remove legacy timeout mocks and introduce resetTrigger and new message id scheme; adjust ChatInput props accordingly.

caseCreation.ts: add findMatchingDeploymentLabel and findMatchingProductLabel helpers and make resolveDeploymentMatch case-insensitive (match by type label or deployment name, and filter ids/labels).

Tests: extend caseCreation tests to cover the new matching helpers and a case-insensitive deployment name match.
Introduce usePostConversationMessages hook to POST follow-up messages to a conversation (uses auth client, Asgardeo auth checks, and logs requests). Add unit tests for the hook. Update ActivityCommentInput placeholder to a shorter hint and change NoveraChatBanner to navigate to "chat/describe-issue" (and update its test accordingly).
Introduce LoadingDotsBubble component and render it in ChatMessageList for messages with isLoading. Improve CreateCasePage product/deployment selection by using findMatchingDeploymentLabel and findMatchingProductLabel when setting state and resolving product IDs. Simplify DescribeIssuePage back navigation, shorten back button text, and increase editor minHeight. Update NoveraChatPage tests with editor and rich-text mocks, API response mocks, icon mocks, and adjusted assertions to match the new behavior.
Swap the plain TextField for the rich text Editor in ChatInput, adding htmlToPlainText-based send disabling, isSending and resetTrigger props, and editor-specific props (toolbarVariant, minHeight, onSubmitKeyDown). Remove the escalation banner / create-case props from ChatInput and wire the send button to the new editor state. Update ChatMessageBubble to unify avatar rendering, show error state text/styling, add a clipboard Copy button with temporary feedback, hide create-case action on errors, and adjust layout. Add a unit test for LoadingDotsBubble with component mocks.
Update unit tests for the Novera AI chat page to reflect component refactors and add necessary mocks. Key changes:

- Replace mocked TextField with a mocked rich text Editor component and mock htmlToPlainText to match new editor usage in ChatInput tests.
- Adjust ChatInput tests to assert on the editor placeholder and submit behavior; remove the escalation-banner test.
- Add/extend mocks for UI components and icons (Button, IconButton, CircularProgress, User, FileText, Copy) used by ChatMessageBubble and ChatMessageList tests.
- Mock react-markdown to simplify message rendering in tests.
- Add new assertions covering bot error state and loading message rendering in ChatMessageBubble and ChatMessageList.

These changes align tests with recent component changes (rich-text editor and markdown rendering) and add coverage for loading/error states.
Update color assignments for dashboard case-type UI and fix TypeScript typing in tests. Changes: replace grey/indigo/purple fallbacks with themed palette values for Service Request and Change Request chips (use colors.purple for outstanding entries; switch chip backgrounds/text/borders to yellow and blue variants respectively). Also add explicit `as typeof window.config` casts in usePostConversationMessages tests to satisfy TypeScript typings.
@coderabbitai

coderabbitai Bot commented Feb 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@dileepapeiris has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 35 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

Reviewing files that changed from the base of the PR and between 1c0acb0 and 44ab12e.

📒 Files selected for processing (1)
  • apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts
📝 Walkthrough

Walkthrough

Adds a new React Query mutation hook to post conversation messages and integrates API-driven chat flows: rich-text editor input, loading/error states, copy-to-clipboard, animated loading bubble, label-matching utilities, minor UI/navigation and color updates, plus corresponding tests.

Changes

Cohort / File(s) Summary
API Hook & Tests
apps/customer-portal/webapp/src/api/usePostConversationMessages.ts, apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx
New React Query mutation hook to POST conversation messages with auth and base-URL validation; tests for success, missing config, unauthenticated user, and server errors.
Chat Input / Editor
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatInput.tsx, apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/__tests__/ChatInput.test.tsx
Replaced single-line TextField with rich-text Editor; removed escalation-banner props; added isSending and resetTrigger props; updated tests and editor/html-to-plain-text mocks.
Chat Page Flow
apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx, apps/customer-portal/webapp/src/pages/__tests__/NoveraChatPage.test.tsx
Integrates usePostConversations/usePostConversationMessages; sends messages to API, tracks conversationId, appends loading bot message, handles success/error and create-case action; tests updated to mock API and routes.
Message Rendering & Loading
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx, .../ChatMessageList.tsx, .../LoadingDotsBubble.tsx, .../__tests__/*
Added copy-to-clipboard, error-state text and styling, avatar refactor; conditional rendering of LoadingDotsBubble for loading messages; new LoadingDotsBubble component and tests; message tests/mocks updated.
Utilities & Create Case
apps/customer-portal/webapp/src/utils/caseCreation.ts, apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts, apps/customer-portal/webapp/src/pages/CreateCasePage.tsx
Added findMatchingProductLabel and findMatchingDeploymentLabel helpers; enhanced deployment/product matching logic; CreateCasePage updated to use new match helpers.
UI Text, Navigation & Styling
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-banner/NoveraChatBanner.tsx, apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-banner/__tests__/NoveraChatBanner.test.tsx, apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx, apps/customer-portal/webapp/src/constants/dashboardConstants.ts, apps/customer-portal/webapp/src/pages/DescribeIssuePage.tsx
Start-chat route changed to chat/describe-issue; ActivityCommentInput placeholder shortened and caption added; dashboard case-type color adjustments; DescribeIssuePage back/navigation and flex/layout tweaks.
Small Additions
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/chatConstants.ts
Added AVATAR_ICON_COLOR constant for chat avatar icon color.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (Chat UI)
    participant Editor as Editor (Rich-text)
    participant ChatPage as NoveraChatPage
    participant APIHook as usePostConversationMessages / usePostConversations
    participant Backend as Backend Service

    User->>Editor: Type message & press Enter
    Editor->>ChatPage: onSubmitKeyDown (HTML content)
    ChatPage->>ChatPage: htmlToPlainText, validate, append user + loading bot messages
    ChatPage->>APIHook: POST /projects/{projectId}/conversations[/conversationId]/messages
    APIHook->>Backend: HTTP POST
    Backend-->>APIHook: ConversationResponse or Error
    APIHook-->>ChatPage: Response / Error
    ChatPage->>ChatPage: update conversationId, replace loading message, set isError/showCreateCaseAction
    ChatPage->>Editor: clear input, increment resetTrigger
    ChatPage->>User: Display bot response or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Type/New Feature, Type/Task, Type/UX, App/Customer Portal, Area/Frontend, Platform/Web

Suggested reviewers

  • Rashmika998

Poem

🐰 I hopped in, typed a tiny line,
Dots danced while the backend whirred in time.
Copied, errored, or opened a case,
Chat now hums with a friendlier face.
Hooray — a carrot-coded chat sublime!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description uses the required template structure but all content sections (Purpose, Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Automation tests, Security checks, Samples, Related PRs, Migrations, Test environment, and Learning) are empty placeholder text without substantive information. Complete all required sections with specific content: describe the problems/issues, implementation solutions, approach details, relevant user stories, release notes, documentation links, test coverage details, security validation status, and any other applicable sections.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Novera chat Integration' is directly related to the main changes in this PR, which implement Novera AI chat functionality integration with new components, hooks, and API endpoints.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@dileepapeiris dileepapeiris self-assigned this Feb 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (8)
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx (1)

139-145: setTimeout in handleCopy is not cleaned up on unmount.

If the component unmounts within the 1.5 s window, the deferred setCopyLabel("Copy") call is a no-op in React 18 but leaves a dangling timer. Store and clear the timer ID for correctness.

♻️ Proposed fix
+  const copyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+  useEffect(() => () => { if (copyTimerRef.current) clearTimeout(copyTimerRef.current); }, []);
+
   const handleCopy = useCallback(async () => {
     const ok = await copyToClipboard(message.text);
     if (ok) {
       setCopyLabel("Copied");
-      setTimeout(() => setCopyLabel("Copy"), 1500);
+      copyTimerRef.current = setTimeout(() => setCopyLabel("Copy"), 1500);
     }
   }, [message.text]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx`
around lines 139 - 145, handleCopy currently starts a 1.5s setTimeout but never
stores or clears the timer, leaving a dangling timer on unmount; update the
component to store the timeout ID in a ref (e.g., copyTimeoutRef) and, inside
handleCopy (which is defined with useCallback), clear any existing timeout
before creating a new one, set copyTimeoutRef.current = setTimeout(...), and add
a useEffect cleanup that calls clearTimeout(copyTimeoutRef.current) to ensure
the timer is cleared on unmount and when handleCopy runs again; keep using
setCopyLabel but ensure you clear the ref after timeout fires.
apps/customer-portal/webapp/src/pages/CreateCasePage.tsx (1)

302-308: Redundant fallback — the includes check is dead code.

findMatchingDeploymentLabel already performs case-insensitive matching, which strictly subsumes the exact baseDeploymentOptions.includes(deploymentLabel) check. If findMatchingDeploymentLabel returns undefined, the label is not in baseDeploymentOptions under any casing, so the includes branch can never be true.

♻️ Proposed simplification
-    const matched =
-      findMatchingDeploymentLabel(deploymentLabel, baseDeploymentOptions) ??
-      (baseDeploymentOptions.includes(deploymentLabel) ? deploymentLabel : null);
-    return matched ?? prev;
+    const matched = findMatchingDeploymentLabel(deploymentLabel, baseDeploymentOptions);
+    return matched ?? prev;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/pages/CreateCasePage.tsx` around lines 302 -
308, The fallback includes check is redundant because
findMatchingDeploymentLabel already does case-insensitive matching; update the
setDeployment callback to remove the dead branch: call
findMatchingDeploymentLabel(deploymentLabel, baseDeploymentOptions) and if it
returns a match use that, otherwise return prev (no need to test
baseDeploymentOptions.includes(deploymentLabel)). Modify the anonymous setter
passed to setDeployment (the function referencing deploymentLabel,
baseDeploymentOptions and findMatchingDeploymentLabel) to drop the includes(...)
condition and simply return matched ?? prev.
apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts (1)

284-311: findMatchingProductLabel tests are missing a case-insensitive scenario.

The findMatchingDeploymentLabel suite explicitly tests case-insensitive lookup (e.g., "STAGING" → "Staging"), but findMatchingProductLabel only tests normalization (hyphen vs. space). Since normalizeProductLabel lowercases the input, pure case-insensitive matching is also supported — a test like findMatchingProductLabel("wso2 api manager 3.2.0", baseOptions) → "WSO2 API Manager 3.2.0" would document this behavior explicitly.

✅ Suggested additional test case
+    it("returns matching base option (case-insensitive)", () => {
+      expect(
+        findMatchingProductLabel("wso2 api manager 3.2.0", baseOptions),
+      ).toBe("WSO2 API Manager 3.2.0");
+    });
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts` around
lines 284 - 311, Add a case-insensitive unit test for findMatchingProductLabel
to document that matching ignores case: call findMatchingProductLabel with a
lowercased product string like "wso2 api manager 3.2.0" against the existing
baseOptions and assert it returns "WSO2 API Manager 3.2.0"; this mirrors the
behavior exercised in findMatchingDeploymentLabel and relies on
normalizeProductLabel/internal lowercase normalization used by
findMatchingProductLabel.
apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx (1)

73-107: Missing coverage for the auth-failure and HTTP-error branches.

The hook has two additional error paths (see usePostConversationMessages.ts lines 50–53 and 67–70) that aren't exercised:

  1. isSignedIn === false (or isAuthLoading === true) → throws "User must be signed in to send messages"
  2. response.ok === false → throws "Conversation messages API error: <status> <statusText>"
✅ Suggested additional test cases
+  it("should throw when user is not signed in", async () => {
+    vi.mocked(vi.importMock("@asgardeo/react")).useAsgardeo = () => ({
+      isSignedIn: false,
+      isLoading: false,
+    });
+    // or re-mock for this test using a factory override
+  });
+
+  it("should throw on non-ok HTTP response", async () => {
+    mockAuthFetch.mockResolvedValueOnce(
+      new Response("Bad Request", { status: 400, statusText: "Bad Request" }),
+    );
+    const { result } = renderHook(() => usePostConversationMessages(), { wrapper });
+    await expect(result.current.mutateAsync(requestParams)).rejects.toThrow(
+      "Conversation messages API error: 400 Bad Request",
+    );
+  });

Note: the auth-failure case requires overriding the useAsgardeo mock per-test (e.g., via a hoisted vi.fn() variable, similar to useAllDeploymentProductsMock in NoveraChatPage.test.tsx).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx`
around lines 73 - 107, Add two tests for usePostConversationMessages: one that
mocks useAsgardeo to return isSignedIn: false (or isAuthLoading: true) and
asserts result.current.mutateAsync(requestParams) rejects with "User must be
signed in to send messages", and another that mocks mockAuthFetch to return a
Response-like object with ok: false and specific status/statusText and asserts
the hook throws "Conversation messages API error: <status> <statusText>". Update
the per-test mocks (hoist a vi.fn() for useAsgardeo similar to
useAllDeploymentProductsMock) so you can override its return values inside each
test, and reuse requestParams and mockAuthFetch to simulate the HTTP error
response for the second case.
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx (1)

64-67: Extract the keyframe animation using keyframes() from @emotion/react and share the resulting variable across all three dots.

MUI/Emotion do inject @keyframes from an sx prop globally, making this work in practice. However, defining the keyframe only in the first dot's sx creates fragile coupling—if the first dot is removed or reordered, the animation breaks silently. The recommended approach by MUI/Emotion is to use the keyframes helper to generate unique, scoped animation names.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx`
around lines 64 - 67, Extract the inline "@keyframes loadingDotPulse" into a
shared keyframes animation by importing keyframes from "@emotion/react" and
creating a constant (e.g., loadingDotPulse) in the LoadingDotsBubble component,
then replace the per-dot sx animation string with a reference to that keyframes
constant (use the keyframes variable with the same duration/easing/repeat: e.g.,
`${loadingDotPulse} 1.4s ease-in-out infinite both`) for all three dot elements
so they all use the same scoped animation and removing the fragile inline
`@keyframes` from the first dot.
apps/customer-portal/webapp/src/api/usePostConversationMessages.ts (1)

72-79: Inconsistency with usePostConversations: missing await and debug log for response.

usePostConversations awaits the JSON parse and logs a debug message before returning:

const data: ConversationResponse = await response.json();
logger.debug("[usePostConversations] Data received");
return data;

This hook directly returns response.json() without awaiting or logging. While functionally equivalent (the implicit promise unwrap works), the missing await means a JSON parse failure would produce a less descriptive rejection, and the absent debug log reduces observability compared to the sibling hook.

Suggested fix for consistency
-      return response.json();
+      const data: ConversationResponse = await response.json();
+      logger.debug("[usePostConversationMessages] Data received");
+      return data;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/api/usePostConversationMessages.ts` around
lines 72 - 79, In usePostConversationMessages, make its response handling
consistent with usePostConversations by awaiting the JSON parse and adding a
debug log; replace the direct return of response.json() with: await
response.json() assigned to a variable (e.g., data: ConversationResponse), call
logger.debug("[usePostConversationMessages] Data received") before returning,
and keep the existing error check path intact so JSON parse failures produce
descriptive rejections and observability matches usePostConversations.
apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx (1)

52-53: Consider making isLoading and isError mutually exclusive via a discriminated union or status field.

Currently both isLoading and isError are independent optional booleans on Message, which allows invalid combinations (e.g., both true). A single status?: "loading" | "error" | "success" field would make the states explicit and prevent such inconsistencies.

This is a nice-to-have for maintainability if the Message type grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx` around lines 52 -
53, Replace the two optional booleans on the Message type (isLoading, isError)
with a single discriminated status field to avoid invalid combinations: update
the Message interface/type to use status?: "loading" | "error" | "success" (or
similar), update all usages and checks that read isLoading/isError to read
message.status === "loading"/"error"/"success" (including rendering logic and
any conditional branches), and add a default or handling for undefined status
where appropriate to preserve current behavior; ensure functions that construct
Message objects (e.g., where new messages are created or transformed) set the
new status instead of isLoading/isError.
apps/customer-portal/webapp/src/utils/caseCreation.ts (1)

113-133: Case-insensitive enhancement looks correct.

One subtle point: because exact and case-insensitive checks are combined in a single find() predicate with ||, the first array element satisfying any condition wins. If an earlier element matches only case-insensitively while a later element matches exactly, the case-insensitive match is returned. In practice this is unlikely to matter, but if strict "prefer exact match" semantics are needed, a two-pass approach (exact first, then fallback to case-insensitive) would be safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/utils/caseCreation.ts` around lines 113 -
133, The current combined predicate in the find calls can return an earlier
case-insensitive match before a later exact match; change both searches to a
two-pass approach: first search projectDeployments for an exact match using
typeLabel === label or depName === label and return that (set fromProject), and
only if not found run a second find using typeLabel?.toLowerCase() ===
labelLower or depName?.toLowerCase() === labelLower; apply the same two-pass
logic for filterDeployments (first id === label || d.label === label, then
fallback to id?.toLowerCase() === labelLower || d.label?.toLowerCase() ===
labelLower) so exact matches are preferred over case-insensitive ones.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx`:
- Line 90: ActivityCommentInput's placeholder was simplified and lost the
"Enter/Shift+Enter" send vs newline hint; restore discoverability by adding a
keyboard shortcut hint (either back into the placeholder or as a small
caption/tooltip next to the input) in ActivityCommentInput.tsx. Use the same UI
pattern/component used in DescribeIssuePage (the caption/tooltip at the comment
editor) to add text like "Press Enter to send, Shift+Enter for newline" adjacent
to the Textarea/Editor in ActivityCommentInput so users can see the shortcut
without cluttering the main placeholder.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx`:
- Around line 264-297: The two branches conditioned on isUser and !isUser in
ChatMessageBubble.tsx render identical JSX (the copy IconButton, Copy icon and
Copied Typography using handleCopy and copyLabel); remove the redundant
conditional branching and render that copy button block unconditionally inside
the existing showCopy check so it appears regardless of isUser, keeping the same
handlers and props (IconButton onClick={handleCopy}, aria-label={copyLabel},
<Copy />, and the Copied Typography when copyLabel === "Copied").
- Around line 140-148: The copy button currently writes message.text (which is
empty for errors) and shows "Copied" incorrectly; update handleCopy to copy the
rendered text instead by using displayText (the computed value based on
message.isError) when calling copyToClipboard/writeText, and keep showCopy as
currently computed; reference handleCopy, displayText, copyToClipboard,
showCopy, and setCopyLabel so the copy action copies meaningful text (e.g.,
"Something went wrong") rather than the raw empty message.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx`:
- Around line 63-90: In LoadingDotsBubble.tsx update the middle dot Box so its
bgcolor uses the theme token like the others: replace the hardcoded "#EAB308"
bgcolor on the second <Box> with "warning.main" so all three dots consistently
reference the theme (look for the three Box elements with width/height 8 and
animation "loadingDotPulse" and change the middle one's bgcolor property).

In `@apps/customer-portal/webapp/src/constants/dashboardConstants.ts`:
- Around line 125-128: OUTSTANDING_CASE_TYPE_ENTRIES currently sets both entries
(serviceRequest and securityReportAnalysis) to colors.purple[500], making the
chart series indistinguishable; update the serviceRequest entry inside
OUTSTANDING_CASE_TYPE_ENTRIES to use a distinct color (for example
colors.yellow[500]) so it matches the chip palette and differentiates it from
securityReportAnalysis while leaving the securityReportAnalysis color unchanged.
- Around line 200-202: The Service Request chip fallbacks use purple hexes
instead of yellow; update the bgColor/textColor/borderColor entries (the
expressions colors.yellow?.[100] ?? "#F3E8FF", colors.yellow?.[800] ??
"#6B21A8", colors.yellow?.[200] ?? "#E9D5FF") to use the correct yellow fallback
hex values from our design palette (replace the three purple literals with the
corresponding yellow hexes for yellow[100], yellow[800], and yellow[200] so the
chip renders yellow when colors.yellow is absent).

In `@apps/customer-portal/webapp/src/pages/DescribeIssuePage.tsx`:
- Around line 72-74: The back handler currently calls navigate(-1) which can
exit the app if the history stack is empty; update the handleBack in
DescribeIssuePage to use useLocation (add it to the existing hook destructuring)
and inspect location.key (the default key is "default") before navigating—if
location.key === "default" or no history exists, call navigate("/" or a safe
fallback route) instead of navigate(-1); otherwise call navigate(-1). Ensure you
reference and update the handleBack and useLocation usage together.

In `@apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx`:
- Around line 202-227: sendToApi currently includes envProducts in every payload
even when empty; update sendToApi (the async callback) to mirror
performClassification by only adding envProducts when
Object.keys(envProducts).length > 0 or by gating the API call until envProducts
are loaded, so early messages aren't sent with an empty envProducts field;
adjust the payload construction in sendToApi (and its uses of
postConversation/postConversationMessages) to conditionally include envProducts
when non-empty.

---

Nitpick comments:
In
`@apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx`:
- Around line 73-107: Add two tests for usePostConversationMessages: one that
mocks useAsgardeo to return isSignedIn: false (or isAuthLoading: true) and
asserts result.current.mutateAsync(requestParams) rejects with "User must be
signed in to send messages", and another that mocks mockAuthFetch to return a
Response-like object with ok: false and specific status/statusText and asserts
the hook throws "Conversation messages API error: <status> <statusText>". Update
the per-test mocks (hoist a vi.fn() for useAsgardeo similar to
useAllDeploymentProductsMock) so you can override its return values inside each
test, and reuse requestParams and mockAuthFetch to simulate the HTTP error
response for the second case.

In `@apps/customer-portal/webapp/src/api/usePostConversationMessages.ts`:
- Around line 72-79: In usePostConversationMessages, make its response handling
consistent with usePostConversations by awaiting the JSON parse and adding a
debug log; replace the direct return of response.json() with: await
response.json() assigned to a variable (e.g., data: ConversationResponse), call
logger.debug("[usePostConversationMessages] Data received") before returning,
and keep the existing error check path intact so JSON parse failures produce
descriptive rejections and observability matches usePostConversations.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx`:
- Around line 139-145: handleCopy currently starts a 1.5s setTimeout but never
stores or clears the timer, leaving a dangling timer on unmount; update the
component to store the timeout ID in a ref (e.g., copyTimeoutRef) and, inside
handleCopy (which is defined with useCallback), clear any existing timeout
before creating a new one, set copyTimeoutRef.current = setTimeout(...), and add
a useEffect cleanup that calls clearTimeout(copyTimeoutRef.current) to ensure
the timer is cleared on unmount and when handleCopy runs again; keep using
setCopyLabel but ensure you clear the ref after timeout fires.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx`:
- Around line 64-67: Extract the inline "@keyframes loadingDotPulse" into a
shared keyframes animation by importing keyframes from "@emotion/react" and
creating a constant (e.g., loadingDotPulse) in the LoadingDotsBubble component,
then replace the per-dot sx animation string with a reference to that keyframes
constant (use the keyframes variable with the same duration/easing/repeat: e.g.,
`${loadingDotPulse} 1.4s ease-in-out infinite both`) for all three dot elements
so they all use the same scoped animation and removing the fragile inline
`@keyframes` from the first dot.

In `@apps/customer-portal/webapp/src/pages/CreateCasePage.tsx`:
- Around line 302-308: The fallback includes check is redundant because
findMatchingDeploymentLabel already does case-insensitive matching; update the
setDeployment callback to remove the dead branch: call
findMatchingDeploymentLabel(deploymentLabel, baseDeploymentOptions) and if it
returns a match use that, otherwise return prev (no need to test
baseDeploymentOptions.includes(deploymentLabel)). Modify the anonymous setter
passed to setDeployment (the function referencing deploymentLabel,
baseDeploymentOptions and findMatchingDeploymentLabel) to drop the includes(...)
condition and simply return matched ?? prev.

In `@apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx`:
- Around line 52-53: Replace the two optional booleans on the Message type
(isLoading, isError) with a single discriminated status field to avoid invalid
combinations: update the Message interface/type to use status?: "loading" |
"error" | "success" (or similar), update all usages and checks that read
isLoading/isError to read message.status === "loading"/"error"/"success"
(including rendering logic and any conditional branches), and add a default or
handling for undefined status where appropriate to preserve current behavior;
ensure functions that construct Message objects (e.g., where new messages are
created or transformed) set the new status instead of isLoading/isError.

In `@apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts`:
- Around line 284-311: Add a case-insensitive unit test for
findMatchingProductLabel to document that matching ignores case: call
findMatchingProductLabel with a lowercased product string like "wso2 api manager
3.2.0" against the existing baseOptions and assert it returns "WSO2 API Manager
3.2.0"; this mirrors the behavior exercised in findMatchingDeploymentLabel and
relies on normalizeProductLabel/internal lowercase normalization used by
findMatchingProductLabel.

In `@apps/customer-portal/webapp/src/utils/caseCreation.ts`:
- Around line 113-133: The current combined predicate in the find calls can
return an earlier case-insensitive match before a later exact match; change both
searches to a two-pass approach: first search projectDeployments for an exact
match using typeLabel === label or depName === label and return that (set
fromProject), and only if not found run a second find using
typeLabel?.toLowerCase() === labelLower or depName?.toLowerCase() ===
labelLower; apply the same two-pass logic for filterDeployments (first id ===
label || d.label === label, then fallback to id?.toLowerCase() === labelLower ||
d.label?.toLowerCase() === labelLower) so exact matches are preferred over
case-insensitive ones.
ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3340268 and 92b060e.

📒 Files selected for processing (20)
  • apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx
  • apps/customer-portal/webapp/src/api/usePostConversationMessages.ts
  • apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-banner/NoveraChatBanner.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-banner/__tests__/NoveraChatBanner.test.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatInput.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageList.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/__tests__/ChatInput.test.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/__tests__/ChatMessageBubble.test.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/__tests__/ChatMessageList.test.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/__tests__/LoadingDotsBubble.test.tsx
  • apps/customer-portal/webapp/src/constants/dashboardConstants.ts
  • apps/customer-portal/webapp/src/pages/CreateCasePage.tsx
  • apps/customer-portal/webapp/src/pages/DescribeIssuePage.tsx
  • apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
  • apps/customer-portal/webapp/src/pages/__tests__/NoveraChatPage.test.tsx
  • apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts
  • apps/customer-portal/webapp/src/utils/caseCreation.ts

Comment thread apps/customer-portal/webapp/src/constants/dashboardConstants.ts
Comment thread apps/customer-portal/webapp/src/constants/dashboardConstants.ts Outdated
Comment thread apps/customer-portal/webapp/src/pages/DescribeIssuePage.tsx Outdated
Comment thread apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
Multiple improvements and fixes across the customer portal:

- tests: add new tests for usePostConversationMessages including unsigned user and API error cases; mock useAsgardeo for better control.
- api: log and return parsed conversation response in usePostConversationMessages.
- chat UI: unify copy button UI in ChatMessageBubble, fix copy timeout handling with useRef, use display text for errors, and clear timeout on unmount.
- loading UI: use theme token for loading dot color in LoadingDotsBubble.
- activity input: show caption hint for Enter vs Shift+Enter in ActivityCommentInput.
- dashboard constants: adjust serviceRequest color and yellow palette values for chip styling.
- create case flow: simplify deployment label matching in CreateCasePage state update.
- describe issue: handle back navigation robustly by checking history and location.key; import useLocation.
- novera chat payload: only include envProducts when present to avoid sending empty objects.
- utils: add a case-insensitive product label test and make resolveDeploymentMatch prefer exact matches, then case-insensitive matches for project deployments and filter deployments.

These changes improve robustness, fix UI/UX issues, and add coverage for error conditions.
@dileepapeiris dileepapeiris changed the title Feat/add update page Novera chat Integration Feb 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx (1)

166-184: Extract the shared avatar icon colour into a named constant.

#C2410C appears three times across two files (User icon, Bot icon here, and Bot icon in LoadingDotsBubble.tsx). A single named constant prevents the values from drifting independently.

♻️ Proposed refactor

Add near the top of each file (or in a shared constants module):

+/** Shared colour for avatar icons (orange-700). */
+const AVATAR_ICON_COLOR = "#C2410C";

Then replace each occurrence:

-        <User size={16} color="#C2410C" />
+        <User size={16} color={AVATAR_ICON_COLOR} />
       ) : (
-        <Bot size={16} color="#C2410C" />
+        <Bot size={16} color={AVATAR_ICON_COLOR} />

And in LoadingDotsBubble.tsx:

-        <Bot size={16} color="#C2410C" />
+        <Bot size={16} color={AVATAR_ICON_COLOR} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx`
around lines 166 - 184, Extract the hardcoded color "#C2410C" into a single
named constant (e.g., AVATAR_ICON_COLOR) and use it wherever the avatar icon
color is set: replace the inline color props in the User and Bot usages inside
ChatMessageBubble (symbol: avatarIcon, component: ChatMessageBubble) and the Bot
usage in LoadingDotsBubble (component/file: LoadingDotsBubble) with that
constant; define the constant near the top of each file or in a shared constants
module and import it where needed so all three occurrences reference the same
identifier.
apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts (1)

143-150: Add coverage for the case-insensitive filter-deployment fallback path.

Step 4 of the updated resolveDeploymentMatch (the case-insensitive match against filterDeployments, lines 138–144 of caseCreation.ts) has no test. A regression there would be invisible. Add a case like "production" (lowercase) expecting { id: "f1" }.

➕ Suggested additional test
+    it("matches by filter deployment label (case-insensitive)", () => {
+      const result = resolveDeploymentMatch(
+        "production",
+        projectDeployments,
+        filterDeployments,
+      );
+      expect(result).toEqual({ id: "f1" });
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts` around
lines 143 - 150, The test suite for resolveDeploymentMatch is missing coverage
for the case-insensitive fallback that matches against filterDeployments; add a
unit test in caseCreation.test.ts that calls resolveDeploymentMatch with a
lowercase environment like "production" (or similar) against the existing
projectDeployments and filterDeployments fixtures and asserts the result equals
{ id: "f1" } to validate the case-insensitive matching path in the function.
apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx (1)

230-288: inputValue in useCallback deps causes handleSendMessage (and onSend) to be recreated on every keystroke.

inputValue changes on every character, so handleSendMessage gets a new reference on every render, which is passed as onSend to ChatInput. If ChatInput is (or ever becomes) memoized, it re-renders on every keystroke. A useRef for the latest value avoids this:

♻️ Optional refactor
+  const inputValueRef = useRef(inputValue);
+  useEffect(() => {
+    inputValueRef.current = inputValue;
+  }, [inputValue]);

   const handleSendMessage = useCallback(async () => {
-    const text = htmlToPlainText(inputValue).trim();
+    const text = htmlToPlainText(inputValueRef.current).trim();
     if (!text || isSending || !projectId) return;
     ...
-  }, [inputValue, isSending, projectId, sendToApi]);
+  }, [isSending, projectId, sendToApi]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx` around lines 230 -
288, The callback handleSendMessage is re-created on every keystroke because
inputValue is in its dependency array; remove inputValue from the useCallback
deps and use a ref to hold the latest input text instead: create a inputValueRef
(useRef), update inputValueRef.current whenever setInputValue is called or
onChange runs in the ChatInput-controlled input, then have handleSendMessage
read text from inputValueRef.current (call htmlToPlainText on that) so
handleSendMessage (passed as onSend to ChatInput) remains stable; keep other
deps (isSending, projectId, sendToApi) and ensure you still clear state via
setInputValue("") and update the ref when sending.
apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx (1)

110-122: Add a test case for the isLoading: true branch of the auth guard.

The hook throws "User must be signed in to send messages" when !isSignedIn || isLoading. The current test only covers isSignedIn: false, isLoading: false; the isLoading: true path (auth still initializing) is untested.

➕ Suggested additional test
+  it("should throw when auth is still loading", async () => {
+    mockUseAsgardeo.mockReturnValueOnce({
+      isSignedIn: true,
+      isLoading: true,
+    });
+    const { result } = renderHook(() => usePostConversationMessages(), {
+      wrapper,
+    });
+
+    await expect(result.current.mutateAsync(requestParams)).rejects.toThrow(
+      "User must be signed in to send messages",
+    );
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx`
around lines 110 - 122, Add a new unit test in
usePostConversationMessages.test.tsx that covers the auth "loading" branch by
mocking mockUseAsgardeo to return { isSignedIn: false, isLoading: true } and
asserting that calling result.current.mutateAsync(requestParams) rejects with
the same error ("User must be signed in to send messages"); place it alongside
the existing test that uses usePostConversationMessages and wrapper so you
exercise the isLoading path of the auth guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx`:
- Around line 93-99: The caption Typography is currently inside the same
position:"relative" container that houses the absolute-positioned send button
(in ActivityCommentInput), causing the button (style bottom: 8, right: 8,
zIndex: 1) to overlap the caption; move the caption Typography out of that
relative Box so the relative container only wraps the Editor (or alternatively
move the inner Box with the absolute button to be a sibling of the caption) so
the send button is anchored only to the editor area and no longer obscures the
caption.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx`:
- Line 21: The JSDoc for the LoadingDotsBubble component is stale — it says
"orange, yellow, orange" while the implementation uses the same "warning.main"
colour for all three dots; update the comment above the LoadingDotsBubble
component (or the LoadingDotsBubble function/class JSDoc) to accurately describe
the UI (e.g., "three amber pulsing dots" or "three warning-colored pulsing
dots") so the documentation matches the current styling.

---

Nitpick comments:
In
`@apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx`:
- Around line 110-122: Add a new unit test in
usePostConversationMessages.test.tsx that covers the auth "loading" branch by
mocking mockUseAsgardeo to return { isSignedIn: false, isLoading: true } and
asserting that calling result.current.mutateAsync(requestParams) rejects with
the same error ("User must be signed in to send messages"); place it alongside
the existing test that uses usePostConversationMessages and wrapper so you
exercise the isLoading path of the auth guard.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx`:
- Around line 166-184: Extract the hardcoded color "#C2410C" into a single named
constant (e.g., AVATAR_ICON_COLOR) and use it wherever the avatar icon color is
set: replace the inline color props in the User and Bot usages inside
ChatMessageBubble (symbol: avatarIcon, component: ChatMessageBubble) and the Bot
usage in LoadingDotsBubble (component/file: LoadingDotsBubble) with that
constant; define the constant near the top of each file or in a shared constants
module and import it where needed so all three occurrences reference the same
identifier.

In `@apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx`:
- Around line 230-288: The callback handleSendMessage is re-created on every
keystroke because inputValue is in its dependency array; remove inputValue from
the useCallback deps and use a ref to hold the latest input text instead: create
a inputValueRef (useRef), update inputValueRef.current whenever setInputValue is
called or onChange runs in the ChatInput-controlled input, then have
handleSendMessage read text from inputValueRef.current (call htmlToPlainText on
that) so handleSendMessage (passed as onSend to ChatInput) remains stable; keep
other deps (isSending, projectId, sendToApi) and ensure you still clear state
via setInputValue("") and update the ref when sending.

In `@apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts`:
- Around line 143-150: The test suite for resolveDeploymentMatch is missing
coverage for the case-insensitive fallback that matches against
filterDeployments; add a unit test in caseCreation.test.ts that calls
resolveDeploymentMatch with a lowercase environment like "production" (or
similar) against the existing projectDeployments and filterDeployments fixtures
and asserts the result equals { id: "f1" } to validate the case-insensitive
matching path in the function.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92b060e and ea03f85.

📒 Files selected for processing (11)
  • apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx
  • apps/customer-portal/webapp/src/api/usePostConversationMessages.ts
  • apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx
  • apps/customer-portal/webapp/src/constants/dashboardConstants.ts
  • apps/customer-portal/webapp/src/pages/CreateCasePage.tsx
  • apps/customer-portal/webapp/src/pages/DescribeIssuePage.tsx
  • apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
  • apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts
  • apps/customer-portal/webapp/src/utils/caseCreation.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/customer-portal/webapp/src/api/usePostConversationMessages.ts
  • apps/customer-portal/webapp/src/pages/CreateCasePage.tsx
  • apps/customer-portal/webapp/src/pages/DescribeIssuePage.tsx
  • apps/customer-portal/webapp/src/constants/dashboardConstants.ts

Centralize avatar color and improve chat input handling, plus add tests.

- Add chatConstants.ts exporting AVATAR_ICON_COLOR and update ChatMessageBubble and LoadingDotsBubble to use it.
- Refactor NoveraChatPage to keep inputValue in a ref (inputValueRef) and introduce setInputValueAndRef to avoid stale closures; handleSendMessage now reads from the ref and dependencies updated; pass new setter to ChatInput.
- Move the "Press Enter to send, Shift+Enter for newline" hint in ActivityCommentInput to a slightly different location for layout consistency.
- Tests: add an auth-loading test to usePostConversationMessages and a case-insensitive deployment label match test in caseCreation utils.

These changes centralize styling, fix a potential race/stale-input bug when sending messages, and improve test coverage.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx (1)

29-41: Optional: extract a shared ChatAvatar component to eliminate duplication.

The Paper + icon avatar block here (lines 29–41) is structurally identical to the avatarIcon element in ChatMessageBubble.tsx (lines 167–185). Both use the same sizing, borderRadius: "50%", flexShrink: 0, and AVATAR_ICON_COLOR. A small parameterized ChatAvatar component would consolidate both.

♻️ Suggested shared component
// ChatAvatar.tsx (new file)
+import { Paper } from "@wso2/oxygen-ui";
+import { Bot, User } from "@wso2/oxygen-ui-icons-react";
+import { AVATAR_ICON_COLOR } from "./chatConstants";
+
+export default function ChatAvatar({ isUser }: { isUser: boolean }) {
+  return (
+    <Paper
+      sx={{
+        width: (theme) => theme.spacing(4),
+        height: (theme) => theme.spacing(4),
+        borderRadius: "50%",
+        display: "flex",
+        alignItems: "center",
+        justifyContent: "center",
+        flexShrink: 0,
+      }}
+    >
+      {isUser ? (
+        <User size={16} color={AVATAR_ICON_COLOR} />
+      ) : (
+        <Bot size={16} color={AVATAR_ICON_COLOR} />
+      )}
+    </Paper>
+  );
+}

Then in both LoadingDotsBubble and ChatMessageBubble:

-      <Paper sx={{ ... }}>
-        <Bot size={16} color={AVATAR_ICON_COLOR} />
-      </Paper>
+      <ChatAvatar isUser={false} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx`
around lines 29 - 41, The Paper+icon avatar block in LoadingDotsBubble (the Bot
avatar) duplicates the avatarIcon in ChatMessageBubble; extract a small
parameterized ChatAvatar component that renders the Paper wrapper (width/height
from a size prop or spacing value, borderRadius: "50%", flexShrink: 0, centered
content) and accepts children (the Bot or other icon) and color prop (default
AVATAR_ICON_COLOR), then replace the inline Paper+Bot in LoadingDotsBubble and
the avatarIcon usage in ChatMessageBubble to use ChatAvatar (keep the Bot size
and AVATAR_ICON_COLOR passed through).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts`:
- Around line 266-326: Add tests that assert whitespace-only labels are treated
like empty inputs for both helpers: add spec cases in the existing describe
blocks for findMatchingDeploymentLabel and findMatchingProductLabel (in
caseCreation.test.ts) that call findMatchingDeploymentLabel("   ", baseOptions)
and findMatchingProductLabel("   ", baseOptions) and expect undefined; this
exercises the !label?.trim() guard and documents the boundary behavior.

---

Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx`:
- Around line 29-41: The Paper+icon avatar block in LoadingDotsBubble (the Bot
avatar) duplicates the avatarIcon in ChatMessageBubble; extract a small
parameterized ChatAvatar component that renders the Paper wrapper (width/height
from a size prop or spacing value, borderRadius: "50%", flexShrink: 0, centered
content) and accepts children (the Bot or other icon) and color prop (default
AVATAR_ICON_COLOR), then replace the inline Paper+Bot in LoadingDotsBubble and
the avatarIcon usage in ChatMessageBubble to use ChatAvatar (keep the Bot size
and AVATAR_ICON_COLOR passed through).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea03f85 and 1c0acb0.

📒 Files selected for processing (7)
  • apps/customer-portal/webapp/src/api/__tests__/usePostConversationMessages.test.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/ChatMessageBubble.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/LoadingDotsBubble.tsx
  • apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/chatConstants.ts
  • apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
  • apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/customer-portal/webapp/src/api/tests/usePostConversationMessages.test.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx

Add unit tests in caseCreation.test.ts to verify that findMatchingDeploymentLabel and findMatchingProductLabel return undefined for whitespace-only input strings. These tests guard against treating inputs containing only spaces as valid matches and live in apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts.

@Rashmika998 Rashmika998 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's address the comments in a later PR

@Rashmika998
Rashmika998 merged commit a8d1898 into wso2-open-operations:customer-portal-milestone-1 Feb 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants