[CS TOOLS] Sync dev with latest changes from main - #490
Conversation
[Customer Portal][BE] Add instance-based search endpoints for projects, deployments, and products
Changes from main to dev
[Customer Portal][BE] Remove cache and update deployed product search
[Customer Portal][BE] Update openapi.yaml file
…rtal-v1.0.x Changes from dev to main
Add apps/customer-portal/webapp/src/assets/error/error-401.svg — an SVG illustration for the 401 (Unauthorized) error page used by the customer portal webapp.
Import Error401Page, Error403Page, and Error404Page and register routes for /401, /403, and /404 in the app router. Replace the previous wildcard redirect to /home with rendering Error404Page so unknown routes display the 404 UI (and allow direct navigation to error pages for testing and consistent error handling).
Add apps/customer-portal/webapp/src/assets/error/error-403.svg — a 2400x1792 SVG illustration for the 403 (Forbidden) error page used by the customer-portal webapp.
Add a new SVG illustration for the 404 error page at apps/customer-portal/webapp/src/assets/error/error-404.svg. The file contains vector path data used by the customer-portal webapp to display a 404 error graphic.
Create apps/customer-portal/webapp/src/components/common/error/index.ts to re-export ErrorPage, Error401Page, Error403Page, and Error404Page for simpler imports. File includes the Apache-2.0 license header.
Add Error401Page component at apps/customer-portal/webapp/src/components/common/error/Error401Page.tsx. The new component uses the existing ErrorPage wrapper, supplies a 401 SVG illustration, alt text, title and user-facing description prompting authentication. File includes project license header and is exported as the default component.
Add a new Error403Page component at apps/customer-portal/webapp/src/components/common/error/Error403Page.tsx. The component composes the shared ErrorPage, supplying a 403 illustration asset, alt text, title and user-facing description. Includes file header with Apache 2.0 license.
Introduce Error404Page.tsx which renders a 404-specific ErrorPage with an illustration asset, alt text, title and description. Adds license header and exports a default functional component at apps/customer-portal/webapp/src/components/common/error/Error404Page.tsx.
Introduce a reusable ErrorPage React component at apps/customer-portal/webapp/src/components/common/error/ErrorPage.tsx. The component accepts illustration, illustrationAlt, title and description props and renders a responsive centered layout using @wso2/oxygen-ui (Box, Stack, Typography, Button). It uses react-router's useNavigate to provide "Go back" and "Go home" actions. File includes Apache-2.0 license header and is intended to provide a consistent error UI for the customer portal.
Import ProjectGuard and apply it to the parent "projects/:projectId" route so all nested project-specific routes render within the guard. This ensures project-level checks/layout are enforced for dashboard, support, updates and other child routes.
Import ApiError and replace generic Error with ApiError when the projects fetch fails. Attempt to parse a JSON error body to extract a server-provided message (ignoring non-JSON bodies), then throw ApiError(status, statusText, message) falling back to a default message. Improves error details returned from the API.
Replace generic Error with ApiError and attempt to extract a detailed message from the response JSON. This change imports ApiError, safely parses the response body for a message (ignoring non-JSON bodies), and throws ApiError(status, statusText, apiMessage|fallback) so callers can access HTTP status and any API-provided error text.
Add an optional message prop to Error401Page so callers can override the default description. Introduce Error401PageProps and use message ?? <default> to preserve the existing fallback text, keeping backward compatibility.
Introduce Error403PageProps with an optional message prop and update Error403Page to accept it. The component now passes message to ErrorPage.description, falling back to a shorter default: "You don't have permission to access this page." (removes the previous 'Contact your administrator...' sentence)
Introduce ProjectGuard.tsx to wrap routes under `projects/:projectId`. The guard fetches project details via useGetProjectDetails at the layout boundary and uses ApiError helpers to render Error401/403/404 pages with the API-provided message when appropriate, avoiding duplication in child routes. If no relevant error occurs it renders the child Outlet. File includes license header and necessary imports.
[Customer portal][Be] Remove unused fields and update openapi.yaml
Replace mock of @components/common/error-state/ErrorStateIcon with @components/common/error/Error500Page in ProjectDeployments.test.tsx. Keeps the same test id output so tests continue to assert on the mocked error component after the component was moved/renamed.
Remove the large ErrorStateIcon SVG component and introduce a new Error500Page component. Rename the related test from ErrorStateIcon.test.tsx to Error500Page.test.tsx and update the common error index export. Adjust usages and tests across the app (SearchBar and its test, ProjectDeployments, TimeTrackingErrorState, SecurityReportAnalysis) to import/use the new error component and accommodate any API/prop changes.
Add the missing `number` field (set to "CR-TEST") to mock CallRequest objects in multiple test files so mocks match the updated CallRequest shape. Also replace imports/usages of ErrorStateIcon with Error500Page in CaseDetailsDetailsPanel, ChangeRequestsCalendarView, and ChangeRequestsList to standardize the error UI.
Add call number and meetingLink to CallRequest model and display them in the Calls UI: show call.number, add isScheduled check, render a Join meeting button (ExternalLink icon) when scheduled. Replace ErrorStateIcon with Error500Page across multiple pages/components and update tests to expect the new error component (check for img and text). Add ProductItem.class property. Update subscriptionUtils to set explicit permissions for SUBSCRIPTION and prevent fallthrough for evaluation project types. Update related tests to include the new call number field.
[Customer Portal][FE][Web] add centralized API error handling with dedicated error pages and project route guard
[Customer Portal][BE] Fix products get endpoint issue
Render a CaseDetailsActionRow in AnnouncementDetailsPanel to allow closing announcements from the announcement view. Adds projectId and caseId props to AnnouncementDetailsPanel and passes them from AnnouncementDetailsPage. Introduces a restrictToCloseOnly prop in CaseDetailsActionRow to filter available actions to only the "Closed" action when used, and adjusts the success message text to "State updated successfully." The action row is only rendered when the announcement status is not already closed.
[Customer Portal][FE][Web] enable case actions in details panel with close-only option and improved messaging
Import PRODUCT_CLASS and add a 'class' query parameter set to PRODUCT_CLASS.PRODUCT_MODEL in useGetProducts.ts. This ensures the products API request is filtered by the correct product model class when fetching product lists.
Introduce PRODUCT_CLASS in commonConstants.ts to mirror the backend entity ProductClass. Adds PRODUCT_MODEL = "product_model" with a const assertion so the frontend uses the same literal value as the backend for product class checks.
Add an explicit type check for assignedEngineer before accessing .name to avoid runtime errors when assignedEngineer is not an object (e.g., a string or null). Keeps the existing logic of splitting the name and falling back to an empty string if no initial is available.
Only derive engineerInitials when assignedEngineer is an object with a name. The code now checks typeof data.assignedEngineer === "object" and that a name exists before splitting to get the first initial, otherwise it falls back to an empty string. This prevents unsafe property access for cases where assignedEngineer may be a non-object or missing name.
Update apps/customer-portal/webapp/package.json version from 1.0.0-rc.2 to 1.0.0-rc.3 to mark the next release candidate for the webapp. No other changes were made.
Introduce UseGetConversationStatsOptions with createdByMe and enabled flags and accept an optional options parameter in useGetConversationStats. The queryKey now includes createdByMe, and the request URL appends ?createdBy=me when createdByMe is true. The hook's enabled condition is also controllable via options.enabled (defaults to true). These changes allow fetching user-filtered stats and optionally disabling the query.
Replace the plain text message for an empty outstanding cases list with a centered empty state UI. Imports EmptyIcon and wraps the message in a Box with vertical layout, padding, and an icon (120px width) above the typography to improve visual feedback when there are no cases.
Improve the empty state for the chat history list by adding an EmptyIcon and centering it. The change imports EmptyIcon, wraps the message in a Box with column layout, centered alignment and vertical padding, and displays a responsive icon above the existing "No chat history." text to provide a clearer visual cue when there are no items.
Adjust USAGE_LINE_CHART_MARGIN.bottom from 5 to 40 to provide extra space for x-axis labels/ticks and prevent clipping in the customer portal usage charts.
When there are no chat history items, render a centered empty-state view instead of plain text. Imports EmptyIcon and wraps the message in a Box with column-centered layout and spacing; the icon is sized and placed above the "No chat history." text to improve UX. (apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx)
Add formatDateForChart to render human-friendly chart labels (e.g. "Jan 1" or "Jan 1\n2024") and make labels responsive to small screens. Replace prior date.slice(5) usage in buildTrendFromUsages and buildDailyCoreTrend with this formatter. The new function parses ISO YYYY-MM-DD, outputs month-name/day and places the year on a new line for larger screens, with a safe fallback to MM-DD on parse error.
Adjust USAGE_LINE_CHART_MARGIN in usageMetricsConstants.ts: right margin increased from 30 to 40 and bottom margin from 5 to 40. This provides extra spacing for labels/axis/legend on the usage line chart.
Render a CallsListSkeleton when isFetchingNextPage is true instead of immediately showing CallRequestList. The change conditionally wraps CallRequestList with an isFetchingNextPage check so the skeleton displays during pagination loads; pagination UI remains unchanged.
[Customer Portal][FE][Web] Introduce createdByMe filters, improve UX empty states, and make charts responsive
📝 WalkthroughWalkthroughThis pull request introduces comprehensive error handling infrastructure with new error page components and route guards, adds product class filtering across backend and frontend, extends call request features with scheduling support, refactors error UI components throughout the webapp, and enhances the customer-service integration with deployment search endpoints and OAuth2 authentication. Changes
Sequence DiagramsequenceDiagram
participant Client as Browser/App
participant App as App.tsx Router
participant ProjectGuard as ProjectGuard Layout
participant ProjectAPI as useGetProjectDetails
participant Backend as API Backend
participant ErrorPage as Error Page Component
Client->>App: Navigate to /projects/:projectId/...
App->>ProjectGuard: Render route with ProjectGuard wrapper
ProjectGuard->>ProjectAPI: useGetProjectDetails(projectId)
ProjectAPI->>Backend: GET /projects/:projectId
alt Unauthorized (401)
Backend-->>ProjectAPI: 401 Error Response
ProjectAPI-->>ProjectGuard: ApiError(401, "Unauthorized", message?)
ProjectGuard->>ErrorPage: Render Error401Page with message
ErrorPage-->>Client: Display "Sign in required" page
else Forbidden (403)
Backend-->>ProjectAPI: 403 Error Response
ProjectAPI-->>ProjectGuard: ApiError(403, "Forbidden", message?)
ProjectGuard->>ErrorPage: Render Error403Page with message
ErrorPage-->>Client: Display "Access denied" page
else Not Found (404)
Backend-->>ProjectAPI: 404 Error Response
ProjectAPI-->>ProjectGuard: ApiError(404, "Not Found", message?)
ProjectGuard->>ErrorPage: Render Error404Page with message
ErrorPage-->>Client: Display "Not found" page
else Success (200)
Backend-->>ProjectAPI: Project Details
ProjectAPI-->>ProjectGuard: Success state
ProjectGuard->>Client: Render nested route/Outlet
Client-->>Client: Display project content
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
apps/customer-portal/backend/service.bal (1)
2777-2796:⚠️ Potential issue | 🟠 MajorAdd pagination input validation and map client input errors to 400.
Line 2777 introduces user-controlled
offset/limit, and Lines 2789-2796 forward them directly. Invalid values can currently fall into the generic error path and become 500s instead of client errors.💡 Suggested fix
- resource function get products(http:RequestContext ctx, entity:ProductClass? 'class, int? offset, int? 'limit) - returns entity:ProductsResponse|http:InternalServerError { + resource function get products(http:RequestContext ctx, entity:ProductClass? 'class, int? offset, int? 'limit) + returns entity:ProductsResponse|http:BadRequest|http:InternalServerError { authorization:UserInfoPayload|error userInfo = ctx.getWithType(authorization:HEADER_USER_INFO); if userInfo is error { return <http:InternalServerError>{ body: { message: ERR_MSG_USER_INFO_HEADER_NOT_FOUND } }; } + if isInvalidLimitOffset('limit, offset) { + return <http:BadRequest>{ + body: { + message: ERR_LIMIT_OFFSET_INVALID + } + }; + } + entity:ProductsResponse|error response = entity:getProducts(userInfo.idToken, { filters: {'class}, pagination: { offset: offset ?: DEFAULT_OFFSET, 'limit: 'limit ?: DEFAULT_LIMIT } }); if response is error { + if getStatusCode(response) == http:STATUS_BAD_REQUEST { + return <http:BadRequest>{ + body: { + message: ERR_LIMIT_OFFSET_INVALID + } + }; + } string customError = "Failed to retrieve products."; log:printError(customError, response); return <http:InternalServerError>{ body: { message: customError } }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/backend/service.bal` around lines 2777 - 2796, Validate the user-controlled pagination params in the resource function get products: check that offset and 'limit are numeric (non-negative integers) and within acceptable bounds (e.g., >=0 for offset, >0 and <= a max for 'limit), apply DEFAULT_OFFSET/DEFAULT_LIMIT when missing, and if validation fails return an http:BadRequest (400) with a clear message instead of letting entity:getProducts errors surface as 500; perform these checks before calling entity:getProducts and map any client-input validation failures to a 400 response.apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallsErrorState.tsx (1)
35-43:⚠️ Potential issue | 🟡 MinorUpdate the illustration sizing selector after switching to
Error500Page.The container still targets
& svg; withError500Page, that selector is likely stale and can leave sizing uncontrolled.♻️ Proposed fix
<Box sx={{ width: 160, maxWidth: "100%", - "& svg": { width: "100%", height: "auto" }, + "& img, & svg": { width: "100%", height: "auto" }, }} aria-hidden > <Error500Page /> </Box>🤖 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/case-details/calls-tab/CallsErrorState.tsx` around lines 35 - 43, The current sx in CallsErrorState is styling "& svg" which no longer applies after swapping in Error500Page; update the selector so the container constrains the illustration element(s) rendered by Error500Page (for example replace "& svg" with a broader "& > *" or "& *" rule) and keep width: "100%" and height: "auto" so the Error500Page root child scales to the Box (reference sx prop on the Box wrapping Error500Page and the Error500Page component name).apps/customer-portal/webapp/src/pages/AllCasesPage.tsx (2)
101-104:⚠️ Potential issue | 🟠 MajorGate deployments search by readiness and permissions.
The deployments query is still enabled on
!!projectIdalone, which can fire avoidable requests (including 403s) before project permissions are known.Based on learnings: In React pages under `apps/customer-portal/webapp/src/pages/`, enable deployment-search hooks with `!!projectId && projectDetailsReady && permissions.hasDeployments` to avoid unnecessary requests and permission errors.Suggested fix
const deploymentsQuery = usePostProjectDeploymentsSearchInfinite(projectId || "", { pageSize: 10, - enabled: !!projectId, + enabled: !!projectId && projectDetailsReady && permissions.hasDeployments, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/pages/AllCasesPage.tsx` around lines 101 - 104, The deployments search is enabled too early; update the usePostProjectDeploymentsSearchInfinite call so its enabled flag uses projectId plus readiness and permission checks: replace the current enabled: !!projectId with enabled: !!projectId && projectDetailsReady && permissions.hasDeployments (referencing usePostProjectDeploymentsSearchInfinite, projectId, projectDetailsReady, and permissions.hasDeployments) to prevent requests/403s before project details and permissions are known.
156-159:⚠️ Potential issue | 🟠 MajorPrevent indefinite page loader on stats failure.
isStatsLoadingdoes not stop when stats are in error state, so initial loader can remain stuck if the query fails before producing data.Based on learnings: For page-level loading derived from `!hasStatsResponse`, include `&& !isStatsError` so the UI can transition out of loading on error.Suggested fix
const isStatsLoading = isProjectContextLoading || isStatsQueryLoading || - (!!projectId && !hasStatsResponse); + (!!projectId && !hasStatsResponse && !isStatsError);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/pages/AllCasesPage.tsx` around lines 156 - 159, The computed isStatsLoading currently treats missing stats as loading and never clears on error; update its boolean expression (the const isStatsLoading) to also check the query error flag by adding && !isStatsError to the hasStatsResponse check so that when isStatsError is true the page-level loader can stop; locate the declaration of isStatsLoading and include the isStatsError symbol alongside isProjectContextLoading, isStatsQueryLoading, projectId, and hasStatsResponse.apps/customer-portal/webapp/src/components/project-details/deployments/ProjectDeployments.tsx (1)
59-81:⚠️ Potential issue | 🟠 MajorAvoid blank content while fetching an unloaded page.
When a user switches to a page not yet fetched,
currentDeploymentsis empty andshowLoadingstays false duringisFetchingNextPage, so the grid can render blank temporarily.Based on learnings: In `apps/customer-portal/webapp/src/components/project-details/deployments/ProjectDeployments.tsx`, include unloaded-page fetch state in loading logic (or keep last non-empty page) to prevent blank pagination states.Suggested fix
- const showLoading = deploymentsQuery.isLoading || deploymentsQuery.isPending; + const showLoading = + deploymentsQuery.isLoading || + deploymentsQuery.isPending || + (!isPageLoaded && deploymentsQuery.isFetchingNextPage);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/project-details/deployments/ProjectDeployments.tsx` around lines 59 - 81, The grid can go blank when the user navigates to a page that hasn't been fetched because currentDeployments is empty while showLoading is false; update the loading logic to consider unloaded-page fetch state by including deploymentsQuery.isFetchingNextPage for pages not yet loaded (use the existing isPageLoaded/currentPageIndex/clampedPage checks) so showLoading becomes true when !isPageLoaded && deploymentsQuery.isFetchingNextPage; alternatively, keep the last non-empty page as a fallback for currentDeployments when the target page is not yet available using deploymentsQuery.data?.pages to find the most recent non-empty deployments.apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeTrackingErrorState.tsx (1)
34-43:⚠️ Potential issue | 🟡 MinorUpdate wrapper selector for
Error500Pageimage rendering.The container still styles
& svg, butError500Pagerenders an<img>. The intended responsive sizing no longer applies.Suggested fix
<Box sx={{ width: 160, maxWidth: "100%", - "& svg": { width: "100%", height: "auto" }, + "& img": { width: "100%", height: "auto" }, }} aria-hidden >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeTrackingErrorState.tsx` around lines 34 - 43, The Box wrapper in TimeTrackingErrorState.tsx targets "& svg" but Error500Page renders an <img>, so responsive styling isn't applied; update the Box sx selector to include images (e.g., "& img, & svg" or at least "& img") so the rendered <img> from Error500Page receives the width/height rules and scales responsively while keeping existing selectors and aria-hidden usage intact.
🧹 Nitpick comments (18)
apps/customer-portal/webapp/src/components/support/announcements/AnnouncementDetailsPanel.tsx (1)
261-266: Use the sharedgetInitialsutility instead of inline parsing.This avoids duplicated logic and keeps initials behavior consistent with other case-details surfaces.
♻️ Suggested refactor
import { formatUtcToLocalNoTimezone, + getInitials, getStatusColor, getStatusIconElement, resolveColorFromTheme, } from "@utils/support"; @@ - engineerInitials={ - typeof data.assignedEngineer === "object" && - data.assignedEngineer?.name - ? data.assignedEngineer.name.split(" ")[0]?.[0] ?? "" - : "" - } + engineerInitials={getInitials(data.assignedEngineer)}🤖 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/announcements/AnnouncementDetailsPanel.tsx` around lines 261 - 266, Replace the inline initials parsing with the shared utility: call getInitials(data.assignedEngineer) for the engineerInitials prop instead of manually splitting the name; add the appropriate import for getInitials from the shared utilities module and ensure you pass the same assignedEngineer object (data.assignedEngineer) so behavior matches other case-details surfaces (update any type/use sites if needed).apps/customer-portal/backend/openapi.yaml (1)
4350-4356: Consider constrainingProduct.classwithProductClassenum.You introduced
ProductClass, butProduct.classis still a plain string. Reusing the enum in theProductschema would keep request/response contracts consistent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/backend/openapi.yaml` around lines 4350 - 4356, The Product schema currently defines the class property as a plain string; update the Product schema to constrain its class field to the existing ProductClass enum by referencing ProductClass (use $ref or equivalent schema reference) so Product.class uses ProductClass instead of a free-form string; locate the Product schema and replace the class property's type with a reference to ProductClass (or set its enum to ProductClass) to ensure responses/requests validate against the ProductClass enum.apps/customer-portal/webapp/src/api/useGetProjectDetails.ts (1)
70-74: UseapiMessagedirectly and letApiErrorhandle default text.Line 73’s synthetic fallback text makes
getApiErrorMessagebehave as if a specific API message always exists. Prefer passingapiMessageonly.💡 Suggested refactor
throw new ApiError( response.status, response.statusText, - apiMessage ?? `Error fetching project details: ${response.statusText}`, + apiMessage, );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/api/useGetProjectDetails.ts` around lines 70 - 74, The throw constructs in useGetProjectDetails.ts currently pass a synthetic fallback string to ApiError (in the throw new ApiError(...) call), which masks absence of an API-provided message; change the throw to pass apiMessage directly (i.e., use apiMessage as the message parameter) and remove the synthetic `Error fetching project details: ${response.statusText}` fallback so that ApiError’s own default text-handling applies; update only the throw in the function that constructs ApiError (where response.status, response.statusText, apiMessage are passed).apps/customer-portal/webapp/src/api/useGetCaseDetails.ts (1)
61-65: Propagate backendmessagefor non-OK responses instead of only synthetic text.Line 61 currently always creates a generic custom message; this loses any API-provided body message and weakens the new
ApiErrorUX path.💡 Suggested refactor
if (!response.ok) { - throw new ApiError( - response.status, - response.statusText, - `Error fetching case details: ${response.status} ${response.statusText}`, - ); + let apiMessage: string | undefined; + try { + const errBody = await response.json(); + if (typeof errBody?.message === "string") { + apiMessage = errBody.message; + } + } catch { + // ignore – body may not be JSON + } + throw new ApiError(response.status, response.statusText, apiMessage); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/api/useGetCaseDetails.ts` around lines 61 - 65, The error construction in useGetCaseDetails currently always uses a synthetic message and drops any backend-provided message; update the non-OK branch that throws ApiError to attempt to parse the response body (e.g., await response.json() or text) and extract a meaningful message (e.g., body.message || body.error || parsedText) and pass that into the ApiError constructor (use ApiError(response.status, response.statusText, extractedMessage) with a fallback to the existing synthetic text) so backend messages are propagated to callers.apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallsPanel.test.tsx (1)
160-160: Consider centralizing call-request fixture creation.The same
CallRequestshape is repeated in many tests; a small factory/helper will reduce drift when fields change again.♻️ Suggested refactor
+const buildCallRequest = (overrides: Partial<CallRequest> = {}): CallRequest => ({ + id: "call-1", + case: { id: "case-1", label: "CS0438719" }, + reason: "Test notes", + preferredTimes: [], + durationMin: 60, + number: "CR-TEST", + scheduleTime: "", + createdOn: "2024-10-29 10:00:00", + updatedOn: "2024-10-29 10:00:00", + state: { id: "1", label: "Pending" }, + ...overrides, +});Also applies to: 211-211, 278-278, 318-318, 376-376, 408-408
🤖 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/case-details/calls-tab/__tests__/CallsPanel.test.tsx` at line 160, Tests repeat the same CallRequest object; add a centralized factory like createCallRequest(defaults?: Partial<CallRequest>) in the CallsPanel test module or a shared test utils file and replace inline literals with calls to createCallRequest({...}) to reduce duplication and ease future changes; update usages in CallsPanel.test.tsx (where the current literal appears and the other instances at the indicated lines) to call createCallRequest and pass overrides only for fields that differ.apps/customer-portal/webapp/src/api/useGetProjects.ts (1)
110-110: Strengthen fallback message whenstatusTextis empty.Use a status-code-based fallback so messages stay informative even if
statusTextis blank.🔧 Small tweak
- apiMessage ?? `Error fetching projects: ${response.statusText}`, + apiMessage ?? + `Error fetching projects (${response.status}${response.statusText ? `: ${response.statusText}` : ""})`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/api/useGetProjects.ts` at line 110, The fallback error message uses response.statusText which can be empty; update the fallback in useGetProjects (the expression building apiMessage) to include the numeric status when statusText is falsy (e.g., use response.statusText || response.status) so the message stays informative — replace the current apiMessage ?? `Error fetching projects: ${response.statusText}` usage with a template that falls back to the status code.apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/ProjectDeployments.test.tsx (1)
134-136: Optional: rename mocked test id to matchError500Page.Keeping
data-testid="error-state-icon"after migrating toError500Pageis a bit misleading in tests.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/ProjectDeployments.test.tsx` around lines 134 - 136, The mocked component for Error500Page in the vi.mock call still uses data-testid="error-state-icon", which is misleading after migrating to Error500Page; update the mock in ProjectDeployments.test.tsx (the vi.mock("@components/common/error/Error500Page", ...) entry) to use a clearer test id such as data-testid="error-500-page" (or "Error500Page") and then update any assertions in the test that reference "error-state-icon" to the new id.apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallRequestCard.test.tsx (1)
28-28: Add an assertion for rendered call number.Now that the fixture includes
number, this test should explicitly assert that the number label is shown to prevent silent regressions.✅ Suggested assertion
expect(screen.getByText(/Test notes for the call/i)).toBeInTheDocument(); expect(screen.getByText(/60 minutes/i)).toBeInTheDocument(); + expect(screen.getByText(/number\s*:\s*CR-TEST/i)).toBeInTheDocument();Also applies to: 139-139
🤖 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/case-details/calls-tab/__tests__/CallRequestCard.test.tsx` at line 28, Add an assertion in the CallRequestCard.test.tsx unit test to verify the rendered call number from the fixture (the "number" field, e.g., "CR-TEST") is displayed; locate the test for CallRequestCard in this file and after rendering (and after any queries for labels like request type/date) add an assertion that the element containing the call number label/text is present (using the same query strategy used elsewhere in the test, e.g., getByText or getByRole) so the test explicitly checks that the call number is shown.apps/customer-portal/webapp/src/components/common/header/__tests__/SearchBar.test.tsx (1)
50-52: Align theError500Pagemock with the real element type.Line 51 mocks
Error500Pageas<svg>, but runtime renders an<img>. Using an<img>mock keeps this test closer to real behavior.♻️ Proposed fix
vi.mock("@components/common/error/Error500Page", () => ({ - default: () => <svg data-testid="error-state-icon" />, + default: (props: any) => ( + <img data-testid="error-state-icon" alt="" aria-hidden="true" {...props} /> + ), }));🤖 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/header/__tests__/SearchBar.test.tsx` around lines 50 - 52, The Error500Page mock in SearchBar.test.tsx uses an <svg> but the real component renders an <img>, so update the mock for "Error500Page" to return an <img data-testid="error-state-icon" /> instead to better match runtime behavior and avoid mismatches in tests referring to the error-state-icon.apps/customer-portal/webapp/src/pages/__tests__/ProjectHub.test.tsx (1)
88-90: Prefer an<img>mock forError500Pagehere too.Line 89 currently returns a
<div>, which diverges from the real component’s<img>contract.♻️ Proposed fix
vi.mock("@components/common/error/Error500Page", () => ({ - default: () => <div data-testid="error-state-icon" />, + default: (props: any) => ( + <img data-testid="error-state-icon" alt="" aria-hidden="true" {...props} /> + ), }));🤖 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__/ProjectHub.test.tsx` around lines 88 - 90, The test's mock for Error500Page currently returns a <div>, which doesn't match the real component's <img> contract; update the vi.mock for "@components/common/error/Error500Page" (the default export mock) so it returns an <img> element with the same data-testid ("error-state-icon") and any minimal required attributes (e.g., alt) to mirror the real component's shape and avoid prop/type mismatches in ProjectHub.test.tsx.apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallRequestCard.tsx (1)
88-88: Prefer a stable state identifier forisScheduled.This new branch is keyed off
call.state.label, so a backend label tweak will silently disable the scheduled-only UI. If the API exposes a scheduled state ID, derive this fromcall.state.id; otherwise normalize the label before comparing.Based on learnings, avoid deriving UI logic from raw backend status label strings because minor backend label changes can break behavior.
🤖 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/case-details/calls-tab/CallRequestCard.tsx` at line 88, The isScheduled boolean should not be derived from the raw backend label; update the logic in CallRequestCard so it checks a stable identifier (preferably call.state.id) against the scheduled state ID (e.g., compare call.state.id to the scheduled ID constant in CallRequestStatus or a new SCHEDULED_ID) and only fallback to a normalized label comparison (trim().toLowerCase() vs CallRequestStatus.SCHEDULED normalized) if an ID is not available; change the reference from call.state.label to call.state.id (with a safe fallback normalization) where isScheduled is computed to avoid brittle UI breaks if label text changes.apps/customer-portal/webapp/src/pages/DashboardPage.tsx (1)
76-80: UseisForbiddento gate downstream stats queries as well.Now that forbidden is computed here, consider deriving a shared eligibility flag (e.g.,
canLoadDashboardData) and using it inenabledoptions for dashboard data hooks to avoid avoidable 403/noise requests.♻️ Suggested refactor pattern
const isForbidden = isForbiddenError(projectsError) || isForbiddenError(projectDetailsError); const forbiddenMessage = getForbiddenMessage(projectsError) ?? getForbiddenMessage(projectDetailsError); + const canLoadDashboardData = + !!projectId && resolvedProject !== undefined && !isForbidden;- } = useGetProjectCasesStats(projectId || "", { ... , enabled: !!projectId }); + } = useGetProjectCasesStats(projectId || "", { ... , enabled: canLoadDashboardData }); - } = useGetProjectCasesStats(projectId || "", { ... , enabled: !!projectId && showOpsChart }); + } = useGetProjectCasesStats(projectId || "", { ... , enabled: canLoadDashboardData && showOpsChart }); - } = useGetProjectChangeRequestsStats(projectId || "", { enabled: !!projectId && includeCrStats }); + } = useGetProjectChangeRequestsStats(projectId || "", { enabled: canLoadDashboardData && includeCrStats });Based on learnings: in React pages under
apps/customer-portal/webapp/src/pages/, queryenabledflags should include readiness/permission guards (not only!!projectId) to prevent unnecessary requests and avoidable 403s.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/pages/DashboardPage.tsx` around lines 76 - 80, Compute a shared eligibility flag (e.g., canLoadDashboardData) that combines project readiness and permission by using existing values like resolvedProject (projectFromList/projectDetails), !!resolvedProject?.id, and the already-computed isForbidden (derived from projectsError and projectDetailsError); then pass that flag into the enabled option for all downstream dashboard data hooks (instead of only checking !!projectId) so hooks only run when a project exists AND the user is not forbidden, preventing unnecessary 403/noise requests.integrations/customer-service/modules/entity/utils.bal (1)
17-21: Avoid duplicating the user-token header name.This hardcodes
"x-user-id-token"again even though the root module already defines the same value inintegrations/customer-service/constants.bal. If one side changes later, inbound extraction and outbound forwarding can drift silently. Please move the header name to a shared constant both modules consume.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/customer-service/modules/entity/utils.bal` around lines 17 - 21, Replace the hardcoded header literal in generateHeaders with the shared constant from the root constants module: import the exported header constant (the name defined in integrations/customer-service/constants.bal) into integrations/customer-service/modules/entity/utils.bal and use that constant instead of "x-user-id-token" in the generateHeaders function; ensure the constant is exported from constants.bal and referenced by its symbol name in generateHeaders so both inbound extraction and outbound forwarding use the same source of truth.integrations/customer-service/modules/entity/constants.bal (1)
28-31: Make the sales max page size configurable.
SALES_MAX_RECORD_LIMIT = 1000bakes the ceiling into code, so larger search/backfill use cases will require a code change and redeploy. Prefer a configurable upper bound and keep the constant only for defaults.Based on learnings, avoid enforcing
MAX_RECORD_LIMITas a hard constraint; keep pagination limits flexible via configuration and document acceptable upper bounds.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/customer-service/modules/entity/constants.bal` around lines 28 - 31, The SALES_MAX_RECORD_LIMIT constant currently hardcodes a 1000 ceiling; change the code to remove/enforce this hard constraint by reading a configurable max page size (e.g., from config/env) and use the constant only as a default fallback; update usages that reference SALES_MAX_RECORD_LIMIT to read from a new config value (e.g., sales.maxRecordLimit) with SALES_DEFAULT_RECORD_LIMIT and SALES_DEFAULT_RECORD_OFFSET remaining as defaults, and document the acceptable upper bound in configuration docs rather than enforcing it in code (adjust functions that validate pagination to allow the configured limit instead of the hardcoded SALES_MAX_RECORD_LIMIT).integrations/customer-service/modules/entity/client.bal (1)
39-40: Reconsider disabling keep-alive for the CS client.The
keepAlive: http:KEEPALIVE_NEVERsetting oncsEntityClientforces a new connection for every CS entity request, incurring TCP/TLS overhead on repeated calls (e.g., pagination, filtering). The default behavior (http:KEEPALIVE_AUTO) would reuse connections and reduce overhead. Unless the upstream service explicitly requires connection closure, consider using the default keep-alive behavior or make this configurable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/customer-service/modules/entity/client.bal` around lines 39 - 40, The csEntityClient currently forces connection closure by setting http1Settings: {keepAlive: http:KEEPALIVE_NEVER}; change this to use the default keep-alive behavior (http:KEEPALIVE_AUTO) or make the keepAlive value configurable so repeated requests reuse TCP/TLS connections; update the csEntityClient initialization where httpVersion and http1Settings are defined to remove or replace KEEPALIVE_NEVER with KEEPALIVE_AUTO (or wire a config flag) and ensure any config parsing logic exposes this option.integrations/customer-service/service.bal (1)
151-159: Consider extracting token extraction into a helper function.The token extraction and unauthorized response logic is duplicated between
deployments/search(lines 151-159) anddeployed-products/search(lines 177-185). If more resources are added that require the same pattern, this duplication will grow.♻️ Proposed helper function to reduce duplication
Add a helper function:
isolated function extractUserIdToken(http:Request req) returns string|http:Unauthorized { string|http:HeaderNotFoundError token = req.getHeader(USER_ID_TOKEN); if token is http:HeaderNotFoundError { log:printError(string `${ERR_MSG_CUSTOMER_SERVICE} ${ERR_MSG_INVOKER_HEADER}`); return <http:Unauthorized>{ body: { message: string `${ERR_MSG_CUSTOMER_SERVICE} ${ERR_MSG_INVOKER_HEADER}` } }; } return token; }Then simplify the resources:
resource function post deployments/search(http:Request req, entity:DeploymentSearchPayload payload) returns http:Ok|HttpErrorResponse { - string|http:HeaderNotFoundError token = req.getHeader(USER_ID_TOKEN); - if token is http:HeaderNotFoundError { - log:printError(string `${ERR_MSG_CUSTOMER_SERVICE} ${ERR_MSG_INVOKER_HEADER}`); - return <http:Unauthorized>{ - body: { - message: string `${ERR_MSG_CUSTOMER_SERVICE} ${ERR_MSG_INVOKER_HEADER}` - } - }; - } + string|http:Unauthorized tokenResult = extractUserIdToken(req); + if tokenResult is http:Unauthorized { + return tokenResult; + } + string token = tokenResult; entity:DeploymentsResponse|error deployments = entity:searchDeployments(token, payload);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/customer-service/service.bal` around lines 151 - 159, Extract the duplicated token extraction into an isolated helper function named extractUserIdToken(req) that returns string|http:Unauthorized: call req.getHeader(USER_ID_TOKEN), if the result is http:HeaderNotFoundError log the combined error using ERR_MSG_CUSTOMER_SERVICE and ERR_MSG_INVOKER_HEADER and return an <http:Unauthorized> with the same message body, otherwise return the token string; then replace the duplicated blocks in the deployments/search and deployed-products/search resources with a call to extractUserIdToken and handle the returned union (if http:Unauthorized, return it immediately; otherwise use the returned string token).integrations/customer-service/modules/entity/types.bal (1)
67-71: Date regex allows semantically invalid dates.The regex
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$validates the format but allows impossible dates like2026-02-30or2026-04-31. If strict date validation is required, consider parsing/validating at the application layer rather than relying solely on regex constraints.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/customer-service/modules/entity/types.bal` around lines 67 - 71, The Date type's regex only enforces YYYY-MM-DD format but allows semantically invalid dates (e.g., 2026-02-30); remove or relax the strict pattern on the public type Date in types.bal and instead perform semantic validation at runtime where dates are created or accepted (e.g., in constructors, factories, or input parsing code) by parsing the string with the runtime date parser (use the language's/time library parse function) and returning a validation error if parsing fails; ensure all usages that previously relied on the regex (references to the Date type) call this parser/validator before persisting or using the date value.integrations/customer-service/utils.bal (1)
64-89: Consider adding more HTTP status code mappings.The match statement handles common status codes but doesn't cover some that may be returned by downstream services (e.g.,
409 Conflict,422 Unprocessable Entity,429 Too Many Requests). While the defaultInternalServerErrorfallback is safe, it may obscure the actual error cause from clients.♻️ Proposed enhancement to add additional status codes
http:STATUS_GATEWAY_TIMEOUT => { return <http:GatewayTimeout>{body}; } + http:STATUS_CONFLICT => { + return <http:Conflict>{body}; + } + http:STATUS_UNPROCESSABLE_ENTITY => { + return <http:UnprocessableEntity>{body}; + } + http:STATUS_TOO_MANY_REQUESTS => { + return <http:TooManyRequests>{body}; + } _ => { return <http:InternalServerError>{body}; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@integrations/customer-service/utils.bal` around lines 64 - 89, Update the match statusCode block in integrations/customer-service/utils.bal to explicitly handle additional HTTP status codes (add cases for http:STATUS_CONFLICT -> <http:Conflict>{body}, http:STATUS_UNPROCESSABLE_ENTITY -> <http:UnprocessableEntity>{body}, and http:STATUS_TOO_MANY_REQUESTS -> <http:TooManyRequests>{body}); keep the existing default branch returning <http:InternalServerError>{body} so unknown codes still fall back safely and ensure the added case labels match the Ballerina http status constants used elsewhere in the file.
🤖 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/backend/openapi.yaml`:
- Around line 2849-2852: Add a minimum constraint to the OpenAPI schema for
CallRequestUpdatePayload.durationInMinutes: locate the durationInMinutes
property in the CallRequestUpdatePayload definition and add "minimum: 1"
alongside the existing "type: integer" and "format: int64" so the spec matches
backend validation and prevents values below 1.
In
`@apps/customer-portal/webapp/src/components/project-details/usage-metrics/UsageOverviewPanel.tsx`:
- Around line 64-81: The formatDateForChart function can produce "undefined NaN"
for invalid ISO strings because new Date(...) doesn't throw; add an explicit
validity check after constructing date (e.g., if Number.isNaN(date.getTime()))
and return the fallback (isoDate.slice(5) or another safe label) before calling
date.getUTCMonth(), getUTCDate(), or getUTCFullYear(); keep the existing
small/large screen formatting logic and references to monthNames, month, day,
year, and isSmallScreen.
In
`@apps/customer-portal/webapp/src/components/support/announcements/AnnouncementDetailsPanel.tsx`:
- Line 255: In AnnouncementDetailsPanel update the JSX guard that checks
data.status?.label so it normalizes the label before comparing to "closed"
(e.g., use a null-safe expression that calls .trim().toLowerCase() on
data.status.label) to prevent values like "Closed " or different casing from
bypassing the check; locate the conditional rendering expression referencing
data.status?.label in the component and replace the raw comparison with a
trimmed, lowercased comparison to "closed".
In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallRequestCard.tsx`:
- Around line 302-309: CallRequestCard currently binds call.meetingLink directly
to Button.href; validate the value before rendering by checking the URL scheme
is http or https (e.g., parse or test /^https?:\/\//i) in the render branch that
currently reads "{call.meetingLink ? (...)}" and only pass
href={call.meetingLink} when it passes; otherwise render a fallback (e.g., plain
text "--" or a disabled Button without href). Ensure you update the conditional
that renders the Button (the block using ExternalLink and Button) to perform
this allowlist check so untrusted schemes like "javascript:" are never placed
into href.
In `@apps/customer-portal/webapp/src/layouts/ProjectGuard.tsx`:
- Around line 45-59: The guard currently returns <Outlet /> while
useGetProjectDetails(projectId) is still loading because error is undefined;
change ProjectGuard to block rendering the Outlet until the hook reports success
(or explicitly notFound/forbidden/unauthorized), by checking the hook's loading
and success states (e.g., useGetProjectDetails returning
isLoading/isSuccess/isError) and: show a loading indicator while isLoading,
handle isError by mapping to
isUnauthorizedError/isForbiddenError/isNotFoundError (rendering
Error401Page/Error403Page/Error404Page with getApiErrorMessage) and render a
generic error UI for other failures, and only return <Outlet /> when isSuccess
is true so nested project-scoped queries wait for project-details readiness.
In `@apps/customer-portal/webapp/src/pages/ConversationDetailsPage.tsx`:
- Around line 309-312: Replace the Error500Page illustration used in
ConversationDetailsPage for KB suggestions with a non-error empty/coming-soon
state: create or reuse a neutral component (e.g., ComingSoonEmptyState or
EmptyIllustration) and render that instead of Error500Page inside the KB
suggestions block, and keep or tweak the Typography copy to something like "KB
article suggestions coming soon." Update the JSX where Error500Page is
referenced so the new neutral component (or existing EmptyState) is imported and
used (locate the Error500Page usage in the ConversationDetailsPage render).
---
Outside diff comments:
In `@apps/customer-portal/backend/service.bal`:
- Around line 2777-2796: Validate the user-controlled pagination params in the
resource function get products: check that offset and 'limit are numeric
(non-negative integers) and within acceptable bounds (e.g., >=0 for offset, >0
and <= a max for 'limit), apply DEFAULT_OFFSET/DEFAULT_LIMIT when missing, and
if validation fails return an http:BadRequest (400) with a clear message instead
of letting entity:getProducts errors surface as 500; perform these checks before
calling entity:getProducts and map any client-input validation failures to a 400
response.
In
`@apps/customer-portal/webapp/src/components/project-details/deployments/ProjectDeployments.tsx`:
- Around line 59-81: The grid can go blank when the user navigates to a page
that hasn't been fetched because currentDeployments is empty while showLoading
is false; update the loading logic to consider unloaded-page fetch state by
including deploymentsQuery.isFetchingNextPage for pages not yet loaded (use the
existing isPageLoaded/currentPageIndex/clampedPage checks) so showLoading
becomes true when !isPageLoaded && deploymentsQuery.isFetchingNextPage;
alternatively, keep the last non-empty page as a fallback for currentDeployments
when the target page is not yet available using deploymentsQuery.data?.pages to
find the most recent non-empty deployments.
In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeTrackingErrorState.tsx`:
- Around line 34-43: The Box wrapper in TimeTrackingErrorState.tsx targets "&
svg" but Error500Page renders an <img>, so responsive styling isn't applied;
update the Box sx selector to include images (e.g., "& img, & svg" or at least
"& img") so the rendered <img> from Error500Page receives the width/height rules
and scales responsively while keeping existing selectors and aria-hidden usage
intact.
In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallsErrorState.tsx`:
- Around line 35-43: The current sx in CallsErrorState is styling "& svg" which
no longer applies after swapping in Error500Page; update the selector so the
container constrains the illustration element(s) rendered by Error500Page (for
example replace "& svg" with a broader "& > *" or "& *" rule) and keep width:
"100%" and height: "auto" so the Error500Page root child scales to the Box
(reference sx prop on the Box wrapping Error500Page and the Error500Page
component name).
In `@apps/customer-portal/webapp/src/pages/AllCasesPage.tsx`:
- Around line 101-104: The deployments search is enabled too early; update the
usePostProjectDeploymentsSearchInfinite call so its enabled flag uses projectId
plus readiness and permission checks: replace the current enabled: !!projectId
with enabled: !!projectId && projectDetailsReady && permissions.hasDeployments
(referencing usePostProjectDeploymentsSearchInfinite, projectId,
projectDetailsReady, and permissions.hasDeployments) to prevent requests/403s
before project details and permissions are known.
- Around line 156-159: The computed isStatsLoading currently treats missing
stats as loading and never clears on error; update its boolean expression (the
const isStatsLoading) to also check the query error flag by adding &&
!isStatsError to the hasStatsResponse check so that when isStatsError is true
the page-level loader can stop; locate the declaration of isStatsLoading and
include the isStatsError symbol alongside isProjectContextLoading,
isStatsQueryLoading, projectId, and hasStatsResponse.
---
Nitpick comments:
In `@apps/customer-portal/backend/openapi.yaml`:
- Around line 4350-4356: The Product schema currently defines the class property
as a plain string; update the Product schema to constrain its class field to the
existing ProductClass enum by referencing ProductClass (use $ref or equivalent
schema reference) so Product.class uses ProductClass instead of a free-form
string; locate the Product schema and replace the class property's type with a
reference to ProductClass (or set its enum to ProductClass) to ensure
responses/requests validate against the ProductClass enum.
In `@apps/customer-portal/webapp/src/api/useGetCaseDetails.ts`:
- Around line 61-65: The error construction in useGetCaseDetails currently
always uses a synthetic message and drops any backend-provided message; update
the non-OK branch that throws ApiError to attempt to parse the response body
(e.g., await response.json() or text) and extract a meaningful message (e.g.,
body.message || body.error || parsedText) and pass that into the ApiError
constructor (use ApiError(response.status, response.statusText,
extractedMessage) with a fallback to the existing synthetic text) so backend
messages are propagated to callers.
In `@apps/customer-portal/webapp/src/api/useGetProjectDetails.ts`:
- Around line 70-74: The throw constructs in useGetProjectDetails.ts currently
pass a synthetic fallback string to ApiError (in the throw new ApiError(...)
call), which masks absence of an API-provided message; change the throw to pass
apiMessage directly (i.e., use apiMessage as the message parameter) and remove
the synthetic `Error fetching project details: ${response.statusText}` fallback
so that ApiError’s own default text-handling applies; update only the throw in
the function that constructs ApiError (where response.status,
response.statusText, apiMessage are passed).
In `@apps/customer-portal/webapp/src/api/useGetProjects.ts`:
- Line 110: The fallback error message uses response.statusText which can be
empty; update the fallback in useGetProjects (the expression building
apiMessage) to include the numeric status when statusText is falsy (e.g., use
response.statusText || response.status) so the message stays informative —
replace the current apiMessage ?? `Error fetching projects:
${response.statusText}` usage with a template that falls back to the status
code.
In
`@apps/customer-portal/webapp/src/components/common/header/__tests__/SearchBar.test.tsx`:
- Around line 50-52: The Error500Page mock in SearchBar.test.tsx uses an <svg>
but the real component renders an <img>, so update the mock for "Error500Page"
to return an <img data-testid="error-state-icon" /> instead to better match
runtime behavior and avoid mismatches in tests referring to the
error-state-icon.
In
`@apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/ProjectDeployments.test.tsx`:
- Around line 134-136: The mocked component for Error500Page in the vi.mock call
still uses data-testid="error-state-icon", which is misleading after migrating
to Error500Page; update the mock in ProjectDeployments.test.tsx (the
vi.mock("@components/common/error/Error500Page", ...) entry) to use a clearer
test id such as data-testid="error-500-page" (or "Error500Page") and then update
any assertions in the test that reference "error-state-icon" to the new id.
In
`@apps/customer-portal/webapp/src/components/support/announcements/AnnouncementDetailsPanel.tsx`:
- Around line 261-266: Replace the inline initials parsing with the shared
utility: call getInitials(data.assignedEngineer) for the engineerInitials prop
instead of manually splitting the name; add the appropriate import for
getInitials from the shared utilities module and ensure you pass the same
assignedEngineer object (data.assignedEngineer) so behavior matches other
case-details surfaces (update any type/use sites if needed).
In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallRequestCard.test.tsx`:
- Line 28: Add an assertion in the CallRequestCard.test.tsx unit test to verify
the rendered call number from the fixture (the "number" field, e.g., "CR-TEST")
is displayed; locate the test for CallRequestCard in this file and after
rendering (and after any queries for labels like request type/date) add an
assertion that the element containing the call number label/text is present
(using the same query strategy used elsewhere in the test, e.g., getByText or
getByRole) so the test explicitly checks that the call number is shown.
In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallsPanel.test.tsx`:
- Line 160: Tests repeat the same CallRequest object; add a centralized factory
like createCallRequest(defaults?: Partial<CallRequest>) in the CallsPanel test
module or a shared test utils file and replace inline literals with calls to
createCallRequest({...}) to reduce duplication and ease future changes; update
usages in CallsPanel.test.tsx (where the current literal appears and the other
instances at the indicated lines) to call createCallRequest and pass overrides
only for fields that differ.
In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallRequestCard.tsx`:
- Line 88: The isScheduled boolean should not be derived from the raw backend
label; update the logic in CallRequestCard so it checks a stable identifier
(preferably call.state.id) against the scheduled state ID (e.g., compare
call.state.id to the scheduled ID constant in CallRequestStatus or a new
SCHEDULED_ID) and only fallback to a normalized label comparison
(trim().toLowerCase() vs CallRequestStatus.SCHEDULED normalized) if an ID is not
available; change the reference from call.state.label to call.state.id (with a
safe fallback normalization) where isScheduled is computed to avoid brittle UI
breaks if label text changes.
In `@apps/customer-portal/webapp/src/pages/__tests__/ProjectHub.test.tsx`:
- Around line 88-90: The test's mock for Error500Page currently returns a <div>,
which doesn't match the real component's <img> contract; update the vi.mock for
"@components/common/error/Error500Page" (the default export mock) so it returns
an <img> element with the same data-testid ("error-state-icon") and any minimal
required attributes (e.g., alt) to mirror the real component's shape and avoid
prop/type mismatches in ProjectHub.test.tsx.
In `@apps/customer-portal/webapp/src/pages/DashboardPage.tsx`:
- Around line 76-80: Compute a shared eligibility flag (e.g.,
canLoadDashboardData) that combines project readiness and permission by using
existing values like resolvedProject (projectFromList/projectDetails),
!!resolvedProject?.id, and the already-computed isForbidden (derived from
projectsError and projectDetailsError); then pass that flag into the enabled
option for all downstream dashboard data hooks (instead of only checking
!!projectId) so hooks only run when a project exists AND the user is not
forbidden, preventing unnecessary 403/noise requests.
In `@integrations/customer-service/modules/entity/client.bal`:
- Around line 39-40: The csEntityClient currently forces connection closure by
setting http1Settings: {keepAlive: http:KEEPALIVE_NEVER}; change this to use the
default keep-alive behavior (http:KEEPALIVE_AUTO) or make the keepAlive value
configurable so repeated requests reuse TCP/TLS connections; update the
csEntityClient initialization where httpVersion and http1Settings are defined to
remove or replace KEEPALIVE_NEVER with KEEPALIVE_AUTO (or wire a config flag)
and ensure any config parsing logic exposes this option.
In `@integrations/customer-service/modules/entity/constants.bal`:
- Around line 28-31: The SALES_MAX_RECORD_LIMIT constant currently hardcodes a
1000 ceiling; change the code to remove/enforce this hard constraint by reading
a configurable max page size (e.g., from config/env) and use the constant only
as a default fallback; update usages that reference SALES_MAX_RECORD_LIMIT to
read from a new config value (e.g., sales.maxRecordLimit) with
SALES_DEFAULT_RECORD_LIMIT and SALES_DEFAULT_RECORD_OFFSET remaining as
defaults, and document the acceptable upper bound in configuration docs rather
than enforcing it in code (adjust functions that validate pagination to allow
the configured limit instead of the hardcoded SALES_MAX_RECORD_LIMIT).
In `@integrations/customer-service/modules/entity/types.bal`:
- Around line 67-71: The Date type's regex only enforces YYYY-MM-DD format but
allows semantically invalid dates (e.g., 2026-02-30); remove or relax the strict
pattern on the public type Date in types.bal and instead perform semantic
validation at runtime where dates are created or accepted (e.g., in
constructors, factories, or input parsing code) by parsing the string with the
runtime date parser (use the language's/time library parse function) and
returning a validation error if parsing fails; ensure all usages that previously
relied on the regex (references to the Date type) call this parser/validator
before persisting or using the date value.
In `@integrations/customer-service/modules/entity/utils.bal`:
- Around line 17-21: Replace the hardcoded header literal in generateHeaders
with the shared constant from the root constants module: import the exported
header constant (the name defined in
integrations/customer-service/constants.bal) into
integrations/customer-service/modules/entity/utils.bal and use that constant
instead of "x-user-id-token" in the generateHeaders function; ensure the
constant is exported from constants.bal and referenced by its symbol name in
generateHeaders so both inbound extraction and outbound forwarding use the same
source of truth.
In `@integrations/customer-service/service.bal`:
- Around line 151-159: Extract the duplicated token extraction into an isolated
helper function named extractUserIdToken(req) that returns
string|http:Unauthorized: call req.getHeader(USER_ID_TOKEN), if the result is
http:HeaderNotFoundError log the combined error using ERR_MSG_CUSTOMER_SERVICE
and ERR_MSG_INVOKER_HEADER and return an <http:Unauthorized> with the same
message body, otherwise return the token string; then replace the duplicated
blocks in the deployments/search and deployed-products/search resources with a
call to extractUserIdToken and handle the returned union (if http:Unauthorized,
return it immediately; otherwise use the returned string token).
In `@integrations/customer-service/utils.bal`:
- Around line 64-89: Update the match statusCode block in
integrations/customer-service/utils.bal to explicitly handle additional HTTP
status codes (add cases for http:STATUS_CONFLICT -> <http:Conflict>{body},
http:STATUS_UNPROCESSABLE_ENTITY -> <http:UnprocessableEntity>{body}, and
http:STATUS_TOO_MANY_REQUESTS -> <http:TooManyRequests>{body}); keep the
existing default branch returning <http:InternalServerError>{body} so unknown
codes still fall back safely and ensure the added case labels match the
Ballerina http status constants used elsewhere in the file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| durationInMinutes: | ||
| type: integer | ||
| description: New preferred duration for the call | ||
| format: int64 |
There was a problem hiding this comment.
Add minimum validation for durationInMinutes.
Line 2849 defines durationInMinutes but omits minimum: 1. This weakens the schema vs backend validation expectations and can produce client/server contract drift.
🛠️ Proposed fix
durationInMinutes:
+ minimum: 1
type: integer
description: New preferred duration for the call
format: int64Based on learnings: CallRequestUpdatePayload.durationInMinutes should include minimum: 1 and format: int64 to match server-side validation.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| durationInMinutes: | |
| type: integer | |
| description: New preferred duration for the call | |
| format: int64 | |
| durationInMinutes: | |
| minimum: 1 | |
| type: integer | |
| description: New preferred duration for the call | |
| format: int64 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/customer-portal/backend/openapi.yaml` around lines 2849 - 2852, Add a
minimum constraint to the OpenAPI schema for
CallRequestUpdatePayload.durationInMinutes: locate the durationInMinutes
property in the CallRequestUpdatePayload definition and add "minimum: 1"
alongside the existing "type: integer" and "format: int64" so the spec matches
backend validation and prevents values below 1.
| /** Format ISO date (YYYY-MM-DD) to date format for the chart, responsive to screen size. */ | ||
| function formatDateForChart(isoDate: string, isSmallScreen: boolean = false): string { | ||
| try { | ||
| const date = new Date(`${isoDate}T00:00:00Z`); | ||
| const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; | ||
| const month = monthNames[date.getUTCMonth()]; | ||
| const day = date.getUTCDate(); | ||
| const year = date.getUTCFullYear(); | ||
|
|
||
| // Small screens: show only month-day (MM-DD format) | ||
| if (isSmallScreen) { | ||
| return `${month} ${day}`; | ||
| } | ||
| // Large screens: show month-day with year on new line | ||
| return `${month} ${day}\n${year}`; | ||
| } catch { | ||
| return isoDate.slice(5); // Fallback to MM-DD | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In JavaScript, does new Date("not-a-date")throw an exception, or return anInvalid Date object?
💡 Result:
new Date("not-a-date") does not throw. It returns a Date object whose internal time value is NaN—commonly displayed as Invalid Date (e.g., d.toString() === "Invalid Date" and isNaN(d.getTime()) === true). Parsing failures correspond to Date.parse(...) returning NaN. [1][2][3]
It will only throw later if you call certain methods that require a valid time value (e.g., toISOString() can throw a RangeError on an invalid date). [1]
Sources: [1] (developer.mozilla.org) [2] (developer.mozilla.org) [3] (web.mit.edu)
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_date?utm_source=openai
- 2: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date?utm_source=openai
- 3: https://web.mit.edu/jwalden/www/ECMA-262%205.1.pdf?utm_source=openai
🏁 Script executed:
# Locate and inspect the UsageOverviewPanel.tsx file
find . -name "UsageOverviewPanel.tsx" -type fRepository: wso2-open-operations/cs-tools
Length of output: 170
🏁 Script executed:
# Read the file around lines 64-81 to confirm the code snippet
cat -n "apps/customer-portal/webapp/src/components/project-details/usage-metrics/UsageOverviewPanel.tsx" | sed -n '60,90p'Repository: wso2-open-operations/cs-tools
Length of output: 1484
🏁 Script executed:
# Search for usages of formatDateForChart to understand the context
rg -n "formatDateForChart" --max-count 20Repository: wso2-open-operations/cs-tools
Length of output: 604
🏁 Script executed:
# Look at the context around lines 114 and 149 to understand data source
cat -n "apps/customer-portal/webapp/src/components/project-details/usage-metrics/UsageOverviewPanel.tsx" | sed -n '100,160p'Repository: wso2-open-operations/cs-tools
Length of output: 3077
🏁 Script executed:
# Check what deriveAggregatedMetrics returns to understand the data structure
rg -B5 -A10 "deriveAggregatedMetrics" "apps/customer-portal/webapp/src/components/project-details/usage-metrics/UsageOverviewPanel.tsx"Repository: wso2-open-operations/cs-tools
Length of output: 1468
🏁 Script executed:
# Look for the interface/type of the data being passed to understand if dates are validated upstream
rg -B3 -A3 "useMemo.*deriveAggregatedMetrics" "apps/customer-portal/webapp/src/components/project-details/usage-metrics/UsageOverviewPanel.tsx"Repository: wso2-open-operations/cs-tools
Length of output: 55
🏁 Script executed:
# Find type definitions for InstanceUsageEntry and InstanceMetricEntry
rg -n "interface InstanceUsageEntry|type InstanceUsageEntry" --max-count 5Repository: wso2-open-operations/cs-tools
Length of output: 366
🏁 Script executed:
# Find type definitions for InstanceMetricEntry
rg -n "interface InstanceMetricEntry|type InstanceMetricEntry" --max-count 5Repository: wso2-open-operations/cs-tools
Length of output: 168
🏁 Script executed:
# Read the InstanceUsageEntry interface to see period format
cat -n "apps/customer-portal/webapp/src/models/responses.ts" | sed -n '1313,1340p'Repository: wso2-open-operations/cs-tools
Length of output: 1042
🏁 Script executed:
# Read the InstanceMetricEntry interface to see date format
cat -n "apps/customer-portal/webapp/src/models/responses.ts" | sed -n '1342,1380p'Repository: wso2-open-operations/cs-tools
Length of output: 690
🏁 Script executed:
# Find InstancePeriodSummary to understand period format
rg -n "interface InstancePeriodSummary|type InstancePeriodSummary" --max-count 5Repository: wso2-open-operations/cs-tools
Length of output: 170
🏁 Script executed:
# Read the InstancePeriodSummary interface
cat -n "apps/customer-portal/webapp/src/models/responses.ts" | sed -n '1250,1312p'Repository: wso2-open-operations/cs-tools
Length of output: 2086
🏁 Script executed:
# Check if there's any validation of period/date strings before they reach formatDateForChart
rg -n "period|date" "apps/customer-portal/webapp/src/components/project-details/usage-metrics/UsageOverviewPanel.tsx" | grep -i "valid\|check\|format" | head -20Repository: wso2-open-operations/cs-tools
Length of output: 322
🏁 Script executed:
# Check if ServiceNow backend guarantees date format
rg -n "startDate|endDate" "apps/customer-portal/webapp/src/models/responses.ts" | head -5Repository: wso2-open-operations/cs-tools
Length of output: 194
🏁 Script executed:
# Check if there are any tests for formatDateForChart to understand expected behavior
rg -n "formatDateForChart" "apps/customer-portal/webapp/src/components/project-details/usage-metrics/" -A 2 -B 2Repository: wso2-open-operations/cs-tools
Length of output: 2151
🏁 Script executed:
# Look for any API backend code that returns these date/period strings to understand if validation exists upstream
rg -n "period|\.date" "apps/customer-portal/backend" --type bal | grep -i "period\|date" | head -10Repository: wso2-open-operations/cs-tools
Length of output: 100
🏁 Script executed:
# Look for backend code that constructs period or date strings
find "apps/customer-portal/backend" -name "*.bal" -o -name "*.java" -o -name "*.ts" | head -20Repository: wso2-open-operations/cs-tools
Length of output: 1358
🏁 Script executed:
# Search for where period strings are generated in backend
rg -n "period" "apps/customer-portal/backend" --max-count 10Repository: wso2-open-operations/cs-tools
Length of output: 648
🏁 Script executed:
# Check how the period is constructed in the backend
cat -n "apps/customer-portal/backend/modules/entity/types.bal" | sed -n '620,630p'Repository: wso2-open-operations/cs-tools
Length of output: 388
🏁 Script executed:
# Look at the utils.bal to see how periods are generated
rg -n "period\|Period" "apps/customer-portal/backend/utils.bal" -B 3 -A 3 | head -40Repository: wso2-open-operations/cs-tools
Length of output: 55
🏁 Script executed:
# Search for where period summaries are created with actual date formatting
rg -n "InstancePeriodSummary\|periodSummaries" "apps/customer-portal/backend" -B 5 -A 5 | head -50Repository: wso2-open-operations/cs-tools
Length of output: 55
🏁 Script executed:
# Look for date string generation in backend for instance usage
rg -n "startDate\|endDate\|dateTime" "apps/customer-portal/backend/modules/entity/utils.bal" | head -20Repository: wso2-open-operations/cs-tools
Length of output: 55
🏁 Script executed:
# Search for period string construction in backend files
rg -n "period" "apps/customer-portal/backend/modules/entity/client.bal" -B 2 -A 2 | head -40Repository: wso2-open-operations/cs-tools
Length of output: 55
🏁 Script executed:
# Check the main backend file
cat -n "apps/customer-portal/backend/utils.bal" | sed -n '980,1000p'Repository: wso2-open-operations/cs-tools
Length of output: 1033
🏁 Script executed:
# Look for date format patterns in the codebase
rg -n "YYYY-MM-DD|yyyy-MM-dd|ISO.*date|isoDate" "apps/customer-portal" --max-count 15Repository: wso2-open-operations/cs-tools
Length of output: 5865
Add validity check for parsed dates to prevent malformed output.
On line 67, new Date(...) with an invalid ISO date produces an Invalid Date object (time value NaN) without throwing an exception. The catch block won't protect this path. When getUTCMonth(), getUTCDate(), or getUTCFullYear() are called on an invalid date, they return NaN, causing monthNames[NaN] to return undefined and producing output like "undefined NaN" instead of a valid date label.
Add an explicit validity check using Number.isNaN(date.getTime()) before accessing date components:
Suggested fix
function formatDateForChart(isoDate: string, isSmallScreen: boolean = false): string {
try {
const date = new Date(`${isoDate}T00:00:00Z`);
+ if (Number.isNaN(date.getTime())) {
+ return isoDate.slice(5);
+ }
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** Format ISO date (YYYY-MM-DD) to date format for the chart, responsive to screen size. */ | |
| function formatDateForChart(isoDate: string, isSmallScreen: boolean = false): string { | |
| try { | |
| const date = new Date(`${isoDate}T00:00:00Z`); | |
| const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; | |
| const month = monthNames[date.getUTCMonth()]; | |
| const day = date.getUTCDate(); | |
| const year = date.getUTCFullYear(); | |
| // Small screens: show only month-day (MM-DD format) | |
| if (isSmallScreen) { | |
| return `${month} ${day}`; | |
| } | |
| // Large screens: show month-day with year on new line | |
| return `${month} ${day}\n${year}`; | |
| } catch { | |
| return isoDate.slice(5); // Fallback to MM-DD | |
| } | |
| /** Format ISO date (YYYY-MM-DD) to date format for the chart, responsive to screen size. */ | |
| function formatDateForChart(isoDate: string, isSmallScreen: boolean = false): string { | |
| try { | |
| const date = new Date(`${isoDate}T00:00:00Z`); | |
| if (Number.isNaN(date.getTime())) { | |
| return isoDate.slice(5); | |
| } | |
| const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; | |
| const month = monthNames[date.getUTCMonth()]; | |
| const day = date.getUTCDate(); | |
| const year = date.getUTCFullYear(); | |
| // Small screens: show only month-day (MM-DD format) | |
| if (isSmallScreen) { | |
| return `${month} ${day}`; | |
| } | |
| // Large screens: show month-day with year on new line | |
| return `${month} ${day}\n${year}`; | |
| } catch { | |
| return isoDate.slice(5); // Fallback to MM-DD | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@apps/customer-portal/webapp/src/components/project-details/usage-metrics/UsageOverviewPanel.tsx`
around lines 64 - 81, The formatDateForChart function can produce "undefined
NaN" for invalid ISO strings because new Date(...) doesn't throw; add an
explicit validity check after constructing date (e.g., if
Number.isNaN(date.getTime())) and return the fallback (isoDate.slice(5) or
another safe label) before calling date.getUTCMonth(), getUTCDate(), or
getUTCFullYear(); keep the existing small/large screen formatting logic and
references to monthNames, month, day, year, and isSmallScreen.
| </Stack> | ||
| </Paper> | ||
|
|
||
| {data && data.status?.label?.toLowerCase() !== "closed" && ( |
There was a problem hiding this comment.
Normalize the status label before the "closed" comparison.
At Line 255, values like "Closed " will bypass this guard and incorrectly render the action row.
🔧 Suggested fix
- {data && data.status?.label?.toLowerCase() !== "closed" && (
+ {data &&
+ data.status?.label?.trim().toLowerCase() !== "closed" && (Based on learnings: Avoid deriving UI logic from raw backend status label strings without normalization; if label-based checks are unavoidable, normalize before comparing.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {data && data.status?.label?.toLowerCase() !== "closed" && ( | |
| {data && | |
| data.status?.label?.trim().toLowerCase() !== "closed" && ( |
🤖 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/announcements/AnnouncementDetailsPanel.tsx`
at line 255, In AnnouncementDetailsPanel update the JSX guard that checks
data.status?.label so it normalizes the label before comparing to "closed"
(e.g., use a null-safe expression that calls .trim().toLowerCase() on
data.status.label) to prevent values like "Closed " or different casing from
bypassing the check; locate the conditional rendering expression referencing
data.status?.label in the component and replace the raw comparison with a
trimmed, lowercased comparison to "closed".
| {call.meetingLink ? ( | ||
| <Button | ||
| variant="text" | ||
| size="small" | ||
| href={call.meetingLink} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| startIcon={<ExternalLink size={14} />} |
There was a problem hiding this comment.
Validate meetingLink before binding it to href.
This trusts an API-provided URL verbatim. If a bad value like javascript:... slips through, clicking the button will execute it in the browser. Please allowlist http:/https: before rendering the link and fall back to -- for anything else.
🤖 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/case-details/calls-tab/CallRequestCard.tsx`
around lines 302 - 309, CallRequestCard currently binds call.meetingLink
directly to Button.href; validate the value before rendering by checking the URL
scheme is http or https (e.g., parse or test /^https?:\/\//i) in the render
branch that currently reads "{call.meetingLink ? (...)}" and only pass
href={call.meetingLink} when it passes; otherwise render a fallback (e.g., plain
text "--" or a disabled Button without href). Ensure you update the conditional
that renders the Button (the block using ExternalLink and Button) to perform
this allowlist check so untrusted schemes like "javascript:" are never placed
into href.
| const { error } = useGetProjectDetails(projectId ?? ""); | ||
|
|
||
| if (isUnauthorizedError(error)) { | ||
| return <Error401Page message={getApiErrorMessage(error)} />; | ||
| } | ||
|
|
||
| if (isForbiddenError(error)) { | ||
| return <Error403Page message={getApiErrorMessage(error)} />; | ||
| } | ||
|
|
||
| if (isNotFoundError(error)) { | ||
| return <Error404Page message={getApiErrorMessage(error)} />; | ||
| } | ||
|
|
||
| return <Outlet />; |
There was a problem hiding this comment.
Block the outlet until the project lookup succeeds.
Right now error is undefined while useGetProjectDetails() is still in flight, so the guard renders <Outlet /> immediately. That lets nested project pages mount and start their own project-scoped queries before we know whether the project is accessible or even exists, which recreates the same avoidable request/403 pattern this PR is trying to centralize. Please gate on the hook’s loading/success state, and render a generic error state for non-401/403/404 failures instead of falling through to the outlet.
Based on learnings, project-scoped queries should wait for project-details readiness to avoid avoidable requests and premature 403s.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/customer-portal/webapp/src/layouts/ProjectGuard.tsx` around lines 45 -
59, The guard currently returns <Outlet /> while useGetProjectDetails(projectId)
is still loading because error is undefined; change ProjectGuard to block
rendering the Outlet until the hook reports success (or explicitly
notFound/forbidden/unauthorized), by checking the hook's loading and success
states (e.g., useGetProjectDetails returning isLoading/isSuccess/isError) and:
show a loading indicator while isLoading, handle isError by mapping to
isUnauthorizedError/isForbiddenError/isNotFoundError (rendering
Error401Page/Error403Page/Error404Page with getApiErrorMessage) and render a
generic error UI for other failures, and only return <Outlet /> when isSuccess
is true so nested project-scoped queries wait for project-details readiness.
| <Error500Page width={160} height={110} /> | ||
| <Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}> | ||
| KB article suggestions are not available yet. | ||
| </Typography> |
There was a problem hiding this comment.
Use a non-error empty/coming-soon state for KB suggestions.
This section says suggestions are “not available yet,” which is not a 500 error scenario. Reusing the 500 illustration here is misleading.
💡 Suggested direction
- <Error500Page width={160} height={110} />
+ {/* Replace with a neutral empty-state illustration/component */}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/customer-portal/webapp/src/pages/ConversationDetailsPage.tsx` around
lines 309 - 312, Replace the Error500Page illustration used in
ConversationDetailsPage for KB suggestions with a non-error empty/coming-soon
state: create or reuse a neutral component (e.g., ComingSoonEmptyState or
EmptyIllustration) and render that instead of Error500Page inside the KB
suggestions block, and keep or tweak the Typography copy to something like "KB
article suggestions coming soon." Update the JSX where Error500Page is
referenced so the new neutral component (or existing EmptyState) is imported and
used (locate the Error500Page usage in the ConversationDetailsPage render).
Purpose
This PR merges the latest changes from the main branch into the dev branch.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Version