Skip to content

[Customer Portal][FE][Web] Enhance AI Case Classification, Rich Text Editor Reactivity, and API Model Alignment - #203

Merged
Rashmika998 merged 14 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/fix-case-classification
Feb 19, 2026
Merged

Rashmika998 merged 14 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/fix-case-classification

Conversation

@dileepapeiris

@dileepapeiris dileepapeiris commented Feb 19, 2026 •

Copy link
Copy Markdown
Contributor

Description

This pull request introduces several improvements and fixes to the customer portal webapp, focusing on enhanced case creation with AI classification, improved editor usability, and API consistency. The main changes include supporting AI-generated case classification data throughout the case creation flow, updating the rich text editor's behavior and tests, aligning API models and test mocks, and adding a new hook for deployment products.

Screen.Recording.2026-02-19.at.17.19.30.mov

AI-powered case creation enhancements:

  • The CreateCasePage now supports pre-filling form fields using AI-generated classification data, which is passed via navigation state or persisted in session storage. The form auto-populates title, description, severity, deployment, product, and issue type based on this data, and maps severity levels to internal constants. [1] [2] [3] [4]
  • Utilities and constants for case classification mapping and HTML escaping are now imported and used in CreateCasePage.
  • Added state to track the classification product label and ensure it is available for form logic.

Editor improvements and testing:

  • The rich text editor (Editor.tsx) now updates its content when the value prop changes from empty, and only sets initial HTML if the editor is empty or contains the default placeholder. It also adjusts styling based on the disabled state. [1] [2] [3]
  • Expanded editor tests to verify that content updates correctly when the value prop changes. [1] [2] [3]

API model and test alignment:

  • The CaseClassificationRequest and CaseClassificationResponse interfaces have been updated to use envProducts and caseInfo (camelCase) respectively, matching the backend API. Associated test mocks and usages have been updated for consistency. [1] [2] [3] [4]
  • The assignedEngineer field in case models now supports both string and object types for better API compatibility. [1] [2]

Deployment products utility:

  • Added a new hook, useAllDeploymentProducts, to fetch and map products for all deployments, improving data retrieval for case creation.

UI/UX tweaks:

  • The label on the escalation banner's button has been changed from "Creating..." to "Processing" for clarity.
  • Minor code formatting and logic cleanups in case creation and file signature generation. [1] [2]

Tooling:

  • Added a test script using Vitest to package.json for running tests.

Summary by CodeRabbit

Release Notes

  • New Features

    • AI-assisted case creation workflow: Chat history is now analyzed and used to pre-populate case details like title, description, and deployment information before submission.
  • Improvements

    • Rich text editor now properly handles content updates and respects disabled states.
    • Loading feedback improved with "Processing" label for case operations.
  • Tests

    • Added test coverage for AI classification and dynamic content population flows.

Add a "test": "vitest" npm script to apps/customer-portal/webapp/package.json so tests can be run via npm/yarn. This enables running the Vitest test runner for the customer portal webapp.
Fetches project deployments and deployment products, formats chat history, and attempts automated case classification before navigating to the create-case page. Adds hooks: useGetProjectDeployments, useAllDeploymentProducts, and usePostCaseClassifications, plus utility use of formatChatHistoryForClassification and buildEnvProducts. Introduces isCreateCaseLoading / isWaitingForClassification state, performClassification logic (calls classifyCase and navigates with classification response when available), and wait logic to delay classification until products finish loading. Also wires isCreateCaseLoading into the ChatInput component.
Change buildClassificationProductLabel to accept a Partial caseInfo type and rename param to caseInfo to better reflect incoming classification responses. Add formatChatHistoryForClassification to format chat messages for the classification API, and add buildEnvProducts to produce envProducts mapping from deployment product data. Also update a unit test description to match the renamed parameter.
Read classification data from location.state or sessionStorage and apply it to the create-case form. Persist incoming classificationResponse to sessionStorage, map caseInfo (title, description, product, environment), resolve issue type and severity (with level-to-label mapping), and add a classification-derived product option when appropriate. Clear stored classification data after successful case creation. Also add useLocation import, small formatting cleanups, and related helpers/refs to ensure classification is applied only once.
Update unit tests to match API shape and component context: change request body to use envProducts mapping and rename mocked response field from case_info to caseInfo in usePostCaseClassifications tests. In Editor tests, add waitFor import, wrap Editor with ErrorBannerProvider (and update render helper), and add a new test to assert the editor updates when the value prop changes from empty to populated. These changes align tests with recent API/UX changes and add a regression test for content updates.
Refactor CaseClassificationRequest to replace the separate 'environments' and 'productDetails' arrays with a single 'envProducts: Record<string, string[]>' mapping environments to product lists, making the request shape represent products grouped by environment. Also adjusted the CaseDetails.assignedEngineer type formatting/union in responses.ts.
Change EscalationBanner button label from "Creating..." to "Processing" for clearer UI feedback. Rename CaseClassificationResponse property case_info to camelCase caseInfo in models/responses.ts for consistency with codebase conventions — update any code that accesses the old case_info field to avoid runtime errors.
Reformat the assignedEngineer union types in CaseListItem and CaseDetails to use multiline unions for improved readability. Also remove a duplicate JSDoc comment in CaseDetails. No functional changes; only styling/clarity updates to apps/customer-portal/webapp/src/models/responses.ts.
Remove the isFirstRender gating and instead only set initialHtml when the editor is empty (trimmed) or contains the placeholder text to avoid overwriting user content. Update effect dependencies accordingly. Also tweak editor styles so hover/border and text color respect the disabled state (use action.disabled/text.disabled) and change the editor typography to body1 for consistency.
Introduce a new React hook useAllDeploymentProducts that fetches products for multiple deployments using react-query's useQueries and the authenticated API client. The hook accepts an array of project deployments, issues a query per deployment (enabled when IDs and the fetch function are available) with a 5-minute staleTime, and returns a productsByDeploymentId map plus a combined isLoading flag. Results are memoized and typed with DeploymentProductItem.
Add a hoisted mock for useAllDeploymentProducts and register the module mock. Set a default mockReturnValue in beforeEach and update the expected bot response text. Reformat fetchDeploymentProducts mock and introduce a new test that simulates an initial loading state followed by loaded products to verify the page waits for products before classifying/creating a case (including asserting the loading spinner). These changes ensure deterministic behavior for tests relying on deployment-product loading.
Refactor CreateCasePage test to use MemoryRouter/Routes and pass navigation state, enabling testing of route-driven prefill. Replace simple stubs with enhanced mocks for BasicInformationSection and CaseDetailsSection to assert received props. Rework and relocate API hook mocks (useGetProjectDetails, useGetCasesFilters, useGetProjectDeployments, useGetDeploymentsProducts, usePostCase, usePostAttachments) and remove older redundant mocks, improving isolation. Add a new test that verifies the form is populated from classification state and keep assertion that the Create Support Case button renders.
@dileepapeiris
dileepapeiris marked this pull request as ready for review February 19, 2026 11:41
@coderabbitai

coderabbitai Bot commented Feb 19, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR integrates AI case classification into the customer portal by refactoring API request/response shapes, introducing a hook to fetch deployment-specific products, adding utility functions for classification workflows, and updating the create case and chat pages to leverage classification data for pre-populating case fields. The Editor component is also improved to handle dynamic value updates.

Changes

Cohort / File(s) Summary
Build Configuration
apps/customer-portal/webapp/package.json
Added "test": "vitest" npm script to the scripts section.
API Models
apps/customer-portal/webapp/src/models/requests.ts, apps/customer-portal/webapp/src/models/responses.ts
Consolidated environments and productDetails arrays into a single envProducts: Record<string, string[]> field in CaseClassificationRequest. Renamed case_info property to caseInfo in CaseClassificationResponse. Reformatted assignedEngineer union types to multiline format in CaseListItem and CaseDetails interfaces.
Hooks & Utilities
apps/customer-portal/webapp/src/hooks/useAllDeploymentProducts.ts, apps/customer-portal/webapp/src/utils/caseCreation.ts
New hook useAllDeploymentProducts fetches products for multiple deployments in parallel via useQueries. New utilities formatChatHistoryForClassification and buildEnvProducts added to format chat history and map deployment names to product labels. Updated buildClassificationProductLabel signature to accept partial caseInfo.
Rich Text Editor
apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx, apps/customer-portal/webapp/src/components/common/rich-text-editor/__tests__/Editor.test.tsx
Refactored InitialValuePlugin to remove first-render state guard in favor of content-aware checks. Added disabled state styling for hover border and input text colors. Updated input typography from body2 to body1 when enabled. Tests now wrap Editor with ErrorBannerProvider and add test for value prop updates.
Support Components
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/EscalationBanner.tsx
Changed button label from "Creating..." to "Processing" when loading.
Pages & Integration
apps/customer-portal/webapp/src/pages/CreateCasePage.tsx, apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
CreateCasePage now integrates with React Router location state to retrieve and persist classification data in sessionStorage. Adds effects to auto-populate form fields (title, description, deployment, product, issue type, severity) from classification response. NoveraChatPage now triggers classification API call, computes envProducts via hook, and navigates to create case with classification response.
Tests
apps/customer-portal/webapp/src/pages/__tests__/CreateCasePage.test.tsx, apps/customer-portal/webapp/src/pages/__tests__/NoveraChatPage.test.tsx, apps/customer-portal/webapp/src/api/__tests__/usePostCaseClassifications.test.tsx, apps/customer-portal/webapp/src/utils/__tests__/caseCreation.test.ts
Replaced BrowserRouter with MemoryRouter for routing tests. Enhanced component mocks to render props with test IDs. Added tests for classification data population flow and async product loading. Updated test data shapes and assertions to reflect renamed fields (case_info → caseInfo).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant NoveraChatPage
    participant ClassifyAPI as Classification API
    participant CreateCasePage
    participant DeploymentAPI as Deployment Products API

    User->>NoveraChatPage: Add messages to chat
    User->>NoveraChatPage: Click "Create Case"
    
    activate NoveraChatPage
    NoveraChatPage->>DeploymentAPI: Fetch products for deployments
    activate DeploymentAPI
    DeploymentAPI-->>NoveraChatPage: Return products by deployment
    deactivate DeploymentAPI
    
    NoveraChatPage->>ClassifyAPI: Call classification with chat history + envProducts
    activate ClassifyAPI
    ClassifyAPI-->>NoveraChatPage: Return case classification (caseInfo)
    deactivate ClassifyAPI
    
    NoveraChatPage->>CreateCasePage: Navigate with classificationResponse in state
    deactivate NoveraChatPage
    
    activate CreateCasePage
    CreateCasePage->>CreateCasePage: Extract caseInfo from location.state
    CreateCasePage->>CreateCasePage: Auto-populate title, description, deployment, product, severity
    CreateCasePage->>CreateCasePage: Persist classification data to sessionStorage
    CreateCasePage->>User: Display pre-filled case form
    deactivate CreateCasePage
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

Type/Improvement, Type/UX, App/Customer Portal, Area/Frontend, Platform/Web

Suggested reviewers

  • Rashmika998
  • v15a1

Poem

🐰 A hop through the chat, a classification flow,
Pre-fill the form with data we know,
Deployment products dance side by side,
Creating cases with ease and with pride,
The rabbit hops fast—the workflow's just right! 🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the three main improvement areas: AI Case Classification enhancement, Rich Text Editor Reactivity fixes, and API Model Alignment.
Description check ✅ Passed The description covers multiple required template sections (Purpose, Goals, Approach, User stories), provides clear explanations of changes with references, and discusses implementation details and testing comprehensively.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% 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 19, 2026
@dileepapeiris dileepapeiris added Type/New Feature Represents a request or task for a new feature Type/Improvement Marks enhancements or improvements to existing features Type/UX Refers to user experience-related tasks or issues App/Customer Portal Area/Frontend Platform/Web labels Feb 19, 2026
@dileepapeiris dileepapeiris moved this from Todo to Done in Customer Portal Development Feb 19, 2026

Copilot AI 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.

Pull request overview

This PR enhances the customer portal's AI-powered case creation flow by integrating AI classification throughout the user journey. The changes enable automatic pre-population of case details based on chat conversations with the AI assistant, improve rich text editor reactivity to AI-generated content, and align API models with backend specifications.

Changes:

  • Implemented end-to-end AI case classification flow from chat interface to case creation form with automatic field population
  • Enhanced rich text editor to update content reactively when receiving AI-generated descriptions
  • Refactored API models to use camelCase naming (envProducts, caseInfo) for backend consistency
  • Added useAllDeploymentProducts hook to fetch product data for all deployments in parallel for classification

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx Integrated case classification API call with product fetching and loading states
apps/customer-portal/webapp/src/pages/CreateCasePage.tsx Added support for pre-filling form fields from AI classification data via navigation state and session storage
apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx Updated InitialValuePlugin to reactively set content when value prop changes
apps/customer-portal/webapp/src/hooks/useAllDeploymentProducts.ts New hook to fetch products for all deployments in parallel
apps/customer-portal/webapp/src/utils/caseCreation.ts Added utility functions for chat formatting, environment products building, and product label construction
apps/customer-portal/webapp/src/models/requests.ts Updated CaseClassificationRequest to use envProducts (Record<string, string[]>)
apps/customer-portal/webapp/src/models/responses.ts Updated CaseClassificationResponse to use caseInfo (camelCase)
apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-page/EscalationBanner.tsx Changed loading button label from "Creating..." to "Processing"
apps/customer-portal/webapp/src/pages/tests/CreateCasePage.test.tsx Added test for classification data population, refactored mock structure
apps/customer-portal/webapp/src/pages/tests/NoveraChatPage.test.tsx Added test for waiting on product loading before classification
apps/customer-portal/webapp/src/components/common/rich-text-editor/tests/Editor.test.tsx Added test for value prop change reactivity
apps/customer-portal/webapp/src/api/tests/usePostCaseClassifications.test.tsx Updated test mocks to match new API model structure
apps/customer-portal/webapp/src/utils/tests/caseCreation.test.ts Updated test description to use camelCase caseInfo
apps/customer-portal/webapp/package.json Added test script for running Vitest

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx Outdated
Comment thread apps/customer-portal/webapp/src/pages/CreateCasePage.tsx
Comment thread apps/customer-portal/webapp/src/pages/CreateCasePage.tsx Outdated
Comment thread apps/customer-portal/webapp/src/utils/caseCreation.ts Outdated
Comment thread apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx Outdated
Comment thread apps/customer-portal/webapp/src/pages/CreateCasePage.tsx
Comment thread apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx Outdated
Comment thread apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
Comment thread apps/customer-portal/webapp/src/hooks/useAllDeploymentProducts.ts

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

Caution

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

⚠️ Outside diff range comments (2)
apps/customer-portal/webapp/src/pages/CreateCasePage.tsx (1)

430-448: ⚠️ Potential issue | 🟠 Major

sessionStorage classification data not cleared when case is created with attachments.

sessionStorage.removeItem(STORAGE_KEY) is only called in the no-attachments success path (line 447). In the with-attachments path (lines 430-444), navigation happens at line 444 without clearing the stored classification data. On subsequent visits, the stale data will be re-hydrated.

🐛 Proposed fix: clear sessionStorage in both paths
           } finally {
             setIsUploadingAttachments(false);
           }
+          sessionStorage.removeItem(STORAGE_KEY);
           navigate(`/${projectId}/support/cases/${caseId}`);
         } else {
           showSuccess("Case created successfully");
           sessionStorage.removeItem(STORAGE_KEY);
           navigate(`/${projectId}/support/cases/${caseId}`);
         }
🤖 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 430 -
448, The stored classification under STORAGE_KEY isn't cleared when creating a
case with attachments; add a call to sessionStorage.removeItem(STORAGE_KEY) in
the attachments-success branch (the block that handles upload results and calls
setIsUploadingAttachments(false) and navigate). Specifically, in the scope that
processes attachment uploads (around the try/finally that sets
setIsUploadingAttachments and then calls navigate), invoke
sessionStorage.removeItem(STORAGE_KEY) before navigate so the stored data is
cleared similarly to the no-attachments path that currently calls
sessionStorage.removeItem(STORAGE_KEY).
apps/customer-portal/webapp/src/pages/__tests__/CreateCasePage.test.tsx (1)

137-231: 🛠️ Refactor suggestion | 🟠 Major

vi.mock() calls placed inside the test body after render() — misleading structure.

Lines 153-223 contain vi.mock() calls that appear to execute after render(), but Vitest hoists all vi.mock() calls to the top of the file at compile time. The code works, but the placement is highly misleading — it suggests mocks are set up post-render, and other developers may not realize these mocks apply globally to all tests in this file. Move them to file-level scope alongside the other vi.mock() calls (lines 26-127).

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

In `@apps/customer-portal/webapp/src/pages/__tests__/CreateCasePage.test.tsx`
around lines 137 - 231, The vi.mock() calls for modules like
"../../api/useGetProjectDetails", "../../api/useGetCasesFilters",
"@providers/MockConfigProvider", "../../api/useGetProjectDeployments",
"../../api/useGetDeploymentsProducts", "../../api/usePostCase", and
"../../api/usePostAttachments" are currently inside the test after render and
should be moved to file-level scope (alongside the other vi.mock() calls near
the top of the file) because Vitest hoists mocks; remove them from the "should
render all sections correctly" test body and place equivalent vi.mock(...)
declarations outside any test/describe so they clearly apply before render and
are not misleadingly placed after the render call. Ensure the mock
implementations remain identical when moved so test behavior is unchanged.
🧹 Nitpick comments (9)
apps/customer-portal/webapp/src/components/common/rich-text-editor/__tests__/Editor.test.tsx (1)

16-40: Move ErrorBannerProvider import to the top import block.

The import on line 30 is placed after the vi.mock() blocks (lines 21–28). While Vitest hoists vi.mock calls so this doesn't cause a runtime issue, splitting imports across mock declarations is non-idiomatic and harder to scan.

♻️ Proposed import reorganization
+import { ErrorBannerProvider } from "@context/error-banner/ErrorBannerContext";
 import { render, screen, waitFor } from "@testing-library/react";
 import { describe, expect, it, vi } from "vitest";
 import { ThemeProvider, createTheme } from "@wso2/oxygen-ui";
 import Editor from "@components/common/rich-text-editor/Editor";

 vi.mock("@hooks/useLogger", () => ({
   ...
 }));
-
-import { ErrorBannerProvider } from "@context/error-banner/ErrorBannerContext";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/common/rich-text-editor/__tests__/Editor.test.tsx`
around lines 16 - 40, The import for ErrorBannerProvider is placed after the
vi.mock() block which splits the import section; move the import statement for
ErrorBannerProvider (from "@context/error-banner/ErrorBannerContext") up into
the top import block alongside other imports (above the vi.mock() call) so all
imports are grouped together and the vi.mock() calls remain contiguous; this
keeps the file imports consistent when editing functions like renderEditor and
the mocked useLogger.
apps/customer-portal/webapp/src/hooks/useAllDeploymentProducts.ts (1)

67-69: Missing error state — silent failures will masquerade as empty product lists.

When a fetchDeploymentProducts call fails, res.data is undefined and the map returns [] for that deployment ID. The caller has no signal to distinguish "no products" from "failed to load products", which can silently produce incorrect classification results (e.g., sending an empty envProducts payload to the classification API).

Consider exposing isError / errors:

♻️ Proposed addition
  const isLoading = results.some((r) => r.isLoading);
+ const isError = results.some((r) => r.isError);

- return { productsByDeploymentId, isLoading };
+ return { productsByDeploymentId, isLoading, isError };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/hooks/useAllDeploymentProducts.ts` around
lines 67 - 69, The hook useAllDeploymentProducts currently returns
productsByDeploymentId and isLoading but hides fetch failures (from
fetchDeploymentProducts) because res.data undefined becomes [] — update the hook
to aggregate error state from the query results (inspect results array for
r.isError and r.error) and return an isError boolean plus an errors or
errorsByDeploymentId map (keyed by deploymentId) alongside
productsByDeploymentId and isLoading so callers can distinguish “no products” vs
“failed to load”; ensure you use the existing results variable and
productsByDeploymentId population logic but record errors when res.error or
res.data is undefined instead of silently producing an empty array.
apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx (1)

75-86: The second guard condition is dead code and should be removed.

The string "Please describe your issue here." would only match if it's set as actual editor content via the value prop. However, after classification or updates, the description is wrapped in HTML (<p>...</p>), so the plain string comparison never matches in normal flows. The first guard (currentContent.trim() === "") already handles the empty-editor case. Additionally, the RichTextPlugin placeholder ("Enter description..." at line 322) is a React overlay and never part of the editor's text content.

Remove the second condition for clarity:

Suggested cleanup
         if (
-          currentContent.trim() === "" ||
-          currentContent === "Please describe your issue here."
-        ) {
+          currentContent.trim() === ""
+        ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx`
around lines 75 - 86, Remove the dead string-comparison guard in the editor
initialization: in the block that reads currentContent via currentContent =
root.getTextContent(), delete the second condition checking currentContent ===
"Please describe your issue here." and leave only the empty-string check
(currentContent.trim() === ""). This affects the initialization logic around
root.clear(), root.append(...nodes) and the DOM parse path that uses initialHtml
and $generateNodesFromDOM(editor, dom); no other behavior needs to change.
apps/customer-portal/webapp/src/pages/__tests__/NoveraChatPage.test.tsx (2)

133-135: Shared QueryClient instance across tests without cache reset.

queryClient is instantiated once at module level (line 133) and reused across all tests, but beforeEach only calls vi.clearAllMocks() — it doesn't clear the query cache. Stale cached data from one test can leak into another, causing flaky results.

♻️ Proposed fix: clear the query cache in beforeEach
   beforeEach(() => {
     vi.clearAllMocks();
+    queryClient.clear();
     useAllDeploymentProductsMock.mockReturnValue({
       productsByDeploymentId: {},
       isLoading: false,
     });
     window.HTMLElement.prototype.scrollIntoView = vi.fn();
   });

Also applies to: 154-161

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

In `@apps/customer-portal/webapp/src/pages/__tests__/NoveraChatPage.test.tsx`
around lines 133 - 135, The shared QueryClient instance (queryClient) is created
at module scope and its cache can leak between tests; update the test setup so
each test starts with a clean cache—either create a fresh QueryClient inside
beforeEach or call queryClient.clear() (or
queryClient.removeQueries()/queryClient.invalidateQueries() as appropriate)
inside beforeEach in addition to vi.clearAllMocks(); ensure changes reference
the existing queryClient symbol and the beforeEach block so no stale query cache
persists across tests.

241-280: New test asserts loading spinner but doesn't verify classification completes.

The test verifies that a CircularProgress appears when products are still loading (line 277), but the final fireEvent.change on line 279 doesn't lead to any assertion that classification eventually proceeds or navigation occurs after isLoading transitions to false. This leaves the happy-path completion untested.

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

In `@apps/customer-portal/webapp/src/pages/__tests__/NoveraChatPage.test.tsx`
around lines 241 - 280, The test currently asserts the loading spinner appears
but never verifies that classification completes after
useAllDeploymentProductsMock transitions to isLoading:false; update the test
around NoveraChatPage to wait for the spinner (getByTestId("circular-progress"))
to disappear (e.g., using waitForElementToBeRemoved or waitFor) and then assert
the expected post-classification outcome (for example, that the classification
result text appears, navigation occurred, or a specific element is rendered).
Ensure you keep the mock sequence on useAllDeploymentProductsMock (first
isLoading:true then isLoading:false) and add an assertion after waiting that
confirms the happy-path (e.g., findByText for the classification result or
expect(navigate/mockRouter).toHaveBeenCalled).
apps/customer-portal/webapp/src/pages/__tests__/CreateCasePage.test.tsx (1)

128-128: Stale placeholder comment.

// ... existing imports and other mocks ... appears to be a leftover from scaffolding. Remove it to avoid confusion.

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

In `@apps/customer-portal/webapp/src/pages/__tests__/CreateCasePage.test.tsx` at
line 128, Remove the stale placeholder comment "// ... existing imports and
other mocks ..." from the CreateCasePage.test.tsx test file; locate the comment
near the top of the test file (in the imports/mocks section) and simply delete
that line so only real imports and mock declarations remain, leaving no
placeholder scaffolding comments behind.
apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx (1)

84-124: performClassification is recreated on every new message.

messages is in the dependency array, so each chat message recreates this callback. Since handleCreateCase and the waiting effect both depend on performClassification, every message will also cause those to be recreated. This is functionally correct but could be optimized by capturing messages via a ref if performance becomes an issue.

🤖 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 84 -
124, performClassification is re-created on every new message because messages
is in its useCallback dependency list, causing dependent callbacks/effects like
handleCreateCase and the waiting effect to also re-create; to fix, create a ref
(e.g., messagesRef) and update it whenever messages change, then remove messages
from the performClassification dependency array and have performClassification
read messagesRef.current instead; update any callers (handleCreateCase, related
effects) to keep using performClassification and ensure messagesRef is kept in
sync via a useEffect that sets messagesRef.current = messages.
apps/customer-portal/webapp/src/pages/CreateCasePage.tsx (2)

130-143: locationState type assertion duplicates the CaseClassificationResponse["caseInfo"] shape.

The inline type on lines 132-142 mirrors the CaseClassificationResponse interface from responses.ts but with all fields optional. Consider reusing Partial<CaseClassificationResponse> to avoid drift if the model changes.

♻️ Proposed refactor: reuse existing type
+import type { CaseClassificationResponse } from "@models/responses";

-  const locationState = location.state as {
-    messages?: ChatMessageForClassification[];
-    classificationResponse?: {
-      issueType?: string;
-      severityLevel?: string;
-      caseInfo?: {
-        description?: string;
-        shortDescription?: string;
-        productName?: string;
-        productVersion?: string;
-        environment?: string;
-      };
-    };
-  } | null;
+  const locationState = location.state as {
+    messages?: ChatMessageForClassification[];
+    classificationResponse?: Partial<CaseClassificationResponse>;
+  } | null;
🤖 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 130 -
143, The inline type for locationState duplicates the CaseClassificationResponse
shape—replace the big inline object with a reuse of the existing type by
changing the asserted type to something like { messages?:
ChatMessageForClassification[]; classificationResponse?:
Partial<CaseClassificationResponse>; } | null; update imports to include
CaseClassificationResponse from responses.ts if not already imported and keep
message typing as ChatMessageForClassification[] so the code references
locationState, ChatMessageForClassification, and CaseClassificationResponse
rather than duplicating fields.

238-244: Fragile HTML detection heuristic for classification descriptions.

text.startsWith("<") && text.endsWith(">") on lines 241-242 treats any string wrapped in angle brackets as HTML. A plain-text description like "<see attached logs>" would be injected as raw HTML. Since classification API output format is controlled, this may be acceptable now, but consider a more robust check (e.g., regex for common HTML tags) if the API contract isn't strict.

🤖 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 238 -
244, The current heuristic in CreateCasePage (the block reading
info.description, using text.startsWith("<") && text.endsWith(">") before
calling setDescription) treats any angle-bracket wrapped string as HTML and can
allow plain text like "<see attached logs>" to be injected; replace that check
by adding a robust isHtml detection (e.g., a small helper used before
setDescription that matches common HTML tags or balanced tags via a regex such
as detecting an opening tag name like /^\s*<([a-zA-Z][\w-]*)(\s|>)/ and ensuring
either a corresponding closing tag or a self-closing form) and fall back to
escapeHtml and wrapping in <p> only when isHtml(text) returns true; update the
code paths around info.description, escapeHtml, and setDescription accordingly.
🤖 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/common/rich-text-editor/Editor.tsx`:
- Around line 267-269: The sx block currently sets fontSize:
oxygenTheme.typography.body2.fontSize and then typography: "body1", which causes
the typography shorthand to override the explicit fontSize; decide which sizing
you want and remove the conflicting declaration: if body1 sizing is intended
delete the fontSize: oxygenTheme.typography.body2.fontSize line, otherwise
remove typography: "body1" and explicitly apply the body2 values (fontSize,
fontWeight, lineHeight, etc.) from oxygenTheme.typography.body2 so the style
reflects body2.

In `@apps/customer-portal/webapp/src/pages/CreateCasePage.tsx`:
- Around line 262-267: The current flow lets classification-set products be
assigned via setProduct and shown through extraProductOptions but
resolveProductId only searches allDeploymentProducts causing validation to fail;
update the logic so resolveProductId (or the call site that validates before
submit) also considers products present in extraProductOptions (i.e., search the
union of allDeploymentProducts and extraProductOptions for a matching id/label)
or prevent setProduct from assigning a product that is not in the selected
deployment's product list by validating against
baseDeploymentOptions/allDeploymentProducts before setting; modify either
resolveProductId to accept an additional extraOptions parameter or ensure
setProduct only accepts deployment-scoped products so the submit validation
succeeds.

In `@apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx`:
- Around line 96-103: The classification call in NoveraChatPage.tsx currently
hardcodes region: "EU" and tier: "Tier 1" when calling classifyCase with
chatHistory and envProducts; replace these literals by deriving region and tier
from the current project/subscription metadata (e.g., read from project details
or user subscription context/state used elsewhere in the page) and pass those
variables to classifyCase instead; ensure you handle missing values with
sensible defaults and reuse existing helpers or context providers (project
metadata getter, subscription service, or props) rather than adding new
hardcoded strings.
- Around line 112-118: Remove the duplicated navigation call in the else branch:
locate the block that calls navigate(`/${projectId}/support/chat/create-case`, {
state: { messages } }) twice and delete the redundant call so navigate is
invoked only once (keep the single navigate call that uses projectId and
messages).

In `@apps/customer-portal/webapp/src/utils/caseCreation.ts`:
- Around line 180-190: formatChatHistoryForClassification currently filters on
the prefixed line length which biases against "User" vs "Assistant" prefixes;
change the logic to trim and check the message content length (e.g., const text
= (m.text || "").trim()) and filter using text.length (use >0 or your desired
minimum) before adding the role prefix, then build the final string as `${role}:
${text}` in the map/join flow; update references in the function
(formatChatHistoryForClassification, m, role, text) accordingly.

---

Outside diff comments:
In `@apps/customer-portal/webapp/src/pages/__tests__/CreateCasePage.test.tsx`:
- Around line 137-231: The vi.mock() calls for modules like
"../../api/useGetProjectDetails", "../../api/useGetCasesFilters",
"@providers/MockConfigProvider", "../../api/useGetProjectDeployments",
"../../api/useGetDeploymentsProducts", "../../api/usePostCase", and
"../../api/usePostAttachments" are currently inside the test after render and
should be moved to file-level scope (alongside the other vi.mock() calls near
the top of the file) because Vitest hoists mocks; remove them from the "should
render all sections correctly" test body and place equivalent vi.mock(...)
declarations outside any test/describe so they clearly apply before render and
are not misleadingly placed after the render call. Ensure the mock
implementations remain identical when moved so test behavior is unchanged.

In `@apps/customer-portal/webapp/src/pages/CreateCasePage.tsx`:
- Around line 430-448: The stored classification under STORAGE_KEY isn't cleared
when creating a case with attachments; add a call to
sessionStorage.removeItem(STORAGE_KEY) in the attachments-success branch (the
block that handles upload results and calls setIsUploadingAttachments(false) and
navigate). Specifically, in the scope that processes attachment uploads (around
the try/finally that sets setIsUploadingAttachments and then calls navigate),
invoke sessionStorage.removeItem(STORAGE_KEY) before navigate so the stored data
is cleared similarly to the no-attachments path that currently calls
sessionStorage.removeItem(STORAGE_KEY).

---

Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/common/rich-text-editor/__tests__/Editor.test.tsx`:
- Around line 16-40: The import for ErrorBannerProvider is placed after the
vi.mock() block which splits the import section; move the import statement for
ErrorBannerProvider (from "@context/error-banner/ErrorBannerContext") up into
the top import block alongside other imports (above the vi.mock() call) so all
imports are grouped together and the vi.mock() calls remain contiguous; this
keeps the file imports consistent when editing functions like renderEditor and
the mocked useLogger.

In
`@apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx`:
- Around line 75-86: Remove the dead string-comparison guard in the editor
initialization: in the block that reads currentContent via currentContent =
root.getTextContent(), delete the second condition checking currentContent ===
"Please describe your issue here." and leave only the empty-string check
(currentContent.trim() === ""). This affects the initialization logic around
root.clear(), root.append(...nodes) and the DOM parse path that uses initialHtml
and $generateNodesFromDOM(editor, dom); no other behavior needs to change.

In `@apps/customer-portal/webapp/src/hooks/useAllDeploymentProducts.ts`:
- Around line 67-69: The hook useAllDeploymentProducts currently returns
productsByDeploymentId and isLoading but hides fetch failures (from
fetchDeploymentProducts) because res.data undefined becomes [] — update the hook
to aggregate error state from the query results (inspect results array for
r.isError and r.error) and return an isError boolean plus an errors or
errorsByDeploymentId map (keyed by deploymentId) alongside
productsByDeploymentId and isLoading so callers can distinguish “no products” vs
“failed to load”; ensure you use the existing results variable and
productsByDeploymentId population logic but record errors when res.error or
res.data is undefined instead of silently producing an empty array.

In `@apps/customer-portal/webapp/src/pages/__tests__/CreateCasePage.test.tsx`:
- Line 128: Remove the stale placeholder comment "// ... existing imports and
other mocks ..." from the CreateCasePage.test.tsx test file; locate the comment
near the top of the test file (in the imports/mocks section) and simply delete
that line so only real imports and mock declarations remain, leaving no
placeholder scaffolding comments behind.

In `@apps/customer-portal/webapp/src/pages/__tests__/NoveraChatPage.test.tsx`:
- Around line 133-135: The shared QueryClient instance (queryClient) is created
at module scope and its cache can leak between tests; update the test setup so
each test starts with a clean cache—either create a fresh QueryClient inside
beforeEach or call queryClient.clear() (or
queryClient.removeQueries()/queryClient.invalidateQueries() as appropriate)
inside beforeEach in addition to vi.clearAllMocks(); ensure changes reference
the existing queryClient symbol and the beforeEach block so no stale query cache
persists across tests.
- Around line 241-280: The test currently asserts the loading spinner appears
but never verifies that classification completes after
useAllDeploymentProductsMock transitions to isLoading:false; update the test
around NoveraChatPage to wait for the spinner (getByTestId("circular-progress"))
to disappear (e.g., using waitForElementToBeRemoved or waitFor) and then assert
the expected post-classification outcome (for example, that the classification
result text appears, navigation occurred, or a specific element is rendered).
Ensure you keep the mock sequence on useAllDeploymentProductsMock (first
isLoading:true then isLoading:false) and add an assertion after waiting that
confirms the happy-path (e.g., findByText for the classification result or
expect(navigate/mockRouter).toHaveBeenCalled).

In `@apps/customer-portal/webapp/src/pages/CreateCasePage.tsx`:
- Around line 130-143: The inline type for locationState duplicates the
CaseClassificationResponse shape—replace the big inline object with a reuse of
the existing type by changing the asserted type to something like { messages?:
ChatMessageForClassification[]; classificationResponse?:
Partial<CaseClassificationResponse>; } | null; update imports to include
CaseClassificationResponse from responses.ts if not already imported and keep
message typing as ChatMessageForClassification[] so the code references
locationState, ChatMessageForClassification, and CaseClassificationResponse
rather than duplicating fields.
- Around line 238-244: The current heuristic in CreateCasePage (the block
reading info.description, using text.startsWith("<") && text.endsWith(">")
before calling setDescription) treats any angle-bracket wrapped string as HTML
and can allow plain text like "<see attached logs>" to be injected; replace that
check by adding a robust isHtml detection (e.g., a small helper used before
setDescription that matches common HTML tags or balanced tags via a regex such
as detecting an opening tag name like /^\s*<([a-zA-Z][\w-]*)(\s|>)/ and ensuring
either a corresponding closing tag or a self-closing form) and fall back to
escapeHtml and wrapping in <p> only when isHtml(text) returns true; update the
code paths around info.description, escapeHtml, and setDescription accordingly.

In `@apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx`:
- Around line 84-124: performClassification is re-created on every new message
because messages is in its useCallback dependency list, causing dependent
callbacks/effects like handleCreateCase and the waiting effect to also
re-create; to fix, create a ref (e.g., messagesRef) and update it whenever
messages change, then remove messages from the performClassification dependency
array and have performClassification read messagesRef.current instead; update
any callers (handleCreateCase, related effects) to keep using
performClassification and ensure messagesRef is kept in sync via a useEffect
that sets messagesRef.current = messages.

Comment thread apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx Outdated
Comment thread apps/customer-portal/webapp/src/pages/CreateCasePage.tsx
Comment thread apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
Comment thread apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
Comment thread apps/customer-portal/webapp/src/utils/caseCreation.ts
Editor: simplify initial-content check, don’t treat placeholder as content, and apply full typography styles from theme.
CreateCasePage: wrap sessionStorage.setItem in try/catch with error logging; improve detection of HTML vs plain text when setting description; remove classification data from sessionStorage after creating a case.
NoveraChatPage: remove duplicate navigate call.
Utils: normalizeProductLabel now lowercases for consistent matching; formatChatHistoryForClassification trims message text and skips empty lines.
Tests: updated expectations to reflect case-insensitive normalization and added tests for product ID resolution and classification option matching.
@dileepapeiris

Copy link
Copy Markdown
Contributor Author

@coderabbitai stop

@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

@dileepapeiris: I've paused the automatic reviews for this PR. You can resume them anytime by asking me to resume.


🧠 Learnings used
Learnt from: dileepapeiris
Repo: wso2-open-operations/cs-tools PR: 92
File: apps/customer-portal/webapp/src/models/mockData.ts:243-274
Timestamp: 2026-02-05T05:58:59.918Z
Learning: In apps/customer-portal/webapp/src/models/mockData.ts, the mockCaseMetadata labels (including "Solution Proposed t" with id "1008" and "Icident" with id "b0f6ef1047f6e910a0a29cd3846d43f8") are copied directly from the actual backend API response and should match the API exactly for testing consistency.

Learnt from: dileepapeiris
Repo: wso2-open-operations/cs-tools PR: 128
File: apps/customer-portal/webapp/src/pages/CreateCasePage.tsx:89-90
Timestamp: 2026-02-11T18:01:50.009Z
Learning: In apps/customer-portal/webapp/src/pages/CreateCasePage.tsx, the hardcoded index `[1]` for initialSeverity (filters?.severities?.[1]?.id || metadata.severityLevels?.[1]?.id || "") is intentional for mock UI purposes to default to S1 severity level during demos. This is temporary code as indicated by the TODO comment and will be removed once the mock interface is removed.
✅ Actions performed

Reviews paused.

Remove specific example values from the JSDoc for the statusLabel parameter in getStatusIcon (apps/customer-portal/webapp/src/utils/support.ts) to make the comment more generic and avoid misleading or stale examples. No functional code changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

App/Customer Portal Area/Frontend Platform/Web Type/Improvement Marks enhancements or improvements to existing features Type/New Feature Represents a request or task for a new feature Type/UX Refers to user experience-related tasks or issues

Projects

Status: Staging Deployed

Development

Successfully merging this pull request may close these issues.

3 participants