Repository navigation
[Customer Portal][FE][Web] Enhance AI Case Classification, Rich Text Editor Reactivity, and API Model Alignment - #203
Conversation
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.
|
Note Reviews pausedUse the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
sessionStorageclassification 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 afterrender()— misleading structure.Lines 153-223 contain
vi.mock()calls that appear to execute afterrender(), but Vitest hoists allvi.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 othervi.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: MoveErrorBannerProviderimport to the top import block.The import on line 30 is placed after the
vi.mock()blocks (lines 21–28). While Vitest hoistsvi.mockcalls 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
fetchDeploymentProductscall fails,res.dataisundefinedand 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 emptyenvProductspayload 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 thevalueprop. 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: SharedQueryClientinstance across tests without cache reset.
queryClientis instantiated once at module level (line 133) and reused across all tests, butbeforeEachonly callsvi.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
CircularProgressappears when products are still loading (line 277), but the finalfireEvent.changeon line 279 doesn't lead to any assertion that classification eventually proceeds or navigation occurs afterisLoadingtransitions tofalse. 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:performClassificationis recreated on every new message.
messagesis in the dependency array, so each chat message recreates this callback. SincehandleCreateCaseand the waiting effect both depend onperformClassification, every message will also cause those to be recreated. This is functionally correct but could be optimized by capturingmessagesvia 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:locationStatetype assertion duplicates theCaseClassificationResponse["caseInfo"]shape.The inline type on lines 132-142 mirrors the
CaseClassificationResponseinterface fromresponses.tsbut with all fields optional. Consider reusingPartial<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.
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.
|
@coderabbitai stop |
|
🧠 Learnings used✅ Actions performedReviews 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.
741d9a9
into
wso2-open-operations:customer-portal-milestone-1
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:
CreateCasePagenow 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]CreateCasePage.Editor improvements and testing:
Editor.tsx) now updates its content when thevalueprop changes from empty, and only sets initial HTML if the editor is empty or contains the default placeholder. It also adjusts styling based on thedisabledstate. [1] [2] [3]valueprop changes. [1] [2] [3]API model and test alignment:
CaseClassificationRequestandCaseClassificationResponseinterfaces have been updated to useenvProductsandcaseInfo(camelCase) respectively, matching the backend API. Associated test mocks and usages have been updated for consistency. [1] [2] [3] [4]assignedEngineerfield in case models now supports both string and object types for better API compatibility. [1] [2]Deployment products utility:
useAllDeploymentProducts, to fetch and map products for all deployments, improving data retrieval for case creation.UI/UX tweaks:
Tooling:
testscript using Vitest topackage.jsonfor running tests.Summary by CodeRabbit
Release Notes
New Features
Improvements
Tests