[Customer Portal][FE][WEb] Revamp Service Requests Page and Details View with Unified Case Experience - #452
Conversation
Replace bespoke ServiceRequestsList/SearchBar/StatCards with shared AllCases components and unify list behavior with All Cases patterns. Add project details, filters metadata, deployments and stats hooks; introduce server-driven filtering, sorting, and pagination state; respect project permissions and S0 exclusion. Remove local status-classification code and adjust loader/error handling to account for combined stats & cases loading.
Replace the SR-specific ServiceRequestDetailContent with the shared CaseDetailsContent so service requests use the case-details shell (tabs, header, actions). Add handleOpenRelatedCase to navigate to the create-related-case flow with a prefilled relatedCase state, and pass onOpenRelatedCase and isServiceRequest props. Move basePath determination into the back handler, tidy up useGetCaseDetails call formatting, and update the file header comment/URL to reflect support|operations routing.
Extend the CaseDetails interface in apps/customer-portal/webapp/src/models/responses.ts with several optional fields: changeRequests (IdLabelRef[]), createdBy (string | null), duration (string | null), assignedTeam (IdLabelRef | null), engagementStartDate (string | null), and engagementEndDate (string | null). These additions expose extra case metadata (change requests, creator, assignment, duration and engagement window) to the customer portal frontend.
Add an optional statEntityName prop to AllCasesStatCards (defaulting to "case") and pass it to SupportStatGrid's entityName prop. Updates the AllCasesStatCardsProps interface and component signature so the displayed entity label can be customized instead of being hardcoded to "case".
Wrap CaseDetailsDetailsPanel tests in MemoryRouter/Routes so route params are available, and import formatDateOnly for date assertions. Update render helper to pass isServiceRequest prop and use formatted dates in expectations. Add a new test to cover service request overview and request detail fields (including change request link href verification).
Expose an optional `rightAction?: ReactNode` prop and update the header layout so a control can be rendered on the far right. The icon and title are wrapped in a nested Stack and the parent header Stack uses `justifyContent: 'space-between'` to position `rightAction` opposite the title. This enables placing buttons or other actions in the card header without changing existing children rendering.
Introduce an isServiceRequest prop to toggle service-request behavior (hide knowledge base tab and adjust error labels). Replace the previous useEffect-based tab clamping with a visibleTabs array and a clampedActiveTab to ensure activeTab always maps to a visible tab. Update resolved panel index to use visibleTabs, pass hideKnowledgeBaseTab and isServiceRequest to child components, and remove the unused useEffect import.
Introduce isServiceRequest handling to CaseDetailsDetailsPanel: add a prop to toggle service-request specific UI and adjust overview/ID labels accordingly. Use react-router params/location to build a top-right action button linking to a related change request for service requests. Surface additional service-request fields (internalId, request type, created by, duration, assigned team, engagement dates, catalog/catalog item) and conditionally render category/CS Manager fields. Render description content for service requests as sanitized HTML using DOMPurify; for non-service requests render request variables with HTML stripped. Compute a productDisplayName fallback for product rendering and update the closed card title when displaying requests. Add necessary imports (Button, Link, useLocation, useParams, DOMPurify, FileText, ExternalLink, and stripHtml util) and minor UI adjustments for these flows.
Update tests for CaseDetailsActionRow to reflect changed loading behavior: rename two test cases to focus on 'Manage State' and action visibility, remove assertions for engineer name and Support Engineer label, and drop checks for Skeleton elements and container usage. Tests now assert that Manage State and action buttons remain visible when loading and that Escalate Case is not shown.
Remove the assigned-engineer display and related logic from CaseDetailsActionRow. This eliminates the avatar, skeleton, divider and the "No engineer assigned" Paper early-return, and removes usages of helper utilities and the AssignedEngineerValue type. The assignedEngineer prop is relaxed to unknown and several variables are voided to silence unused warnings. Imports were cleaned up accordingly.
Remove the assigned-engineer placeholder and vertical divider from the action row, simplifying the skeleton layout. Also remove the unused Divider import and add a void statement for hideAssignedEngineer to satisfy linting. Update the action row comment to reflect the change and adjust stack children accordingly.
Expose a new boolean prop `isServiceRequest` on CaseDetailsTabPanels: add it to the props interface, provide a default value (false) in the function signature, and pass it through to the rendered panel component(s). This propagates service-request state to child components while preserving existing behavior by default.
Introduce a new prop `hideKnowledgeBaseTab` (default false) to CaseDetailsTabs and update tab filtering logic to exclude the Calls and Knowledge Base tabs when their respective hide flags are set. Replaces the previous ternary for Calls with a unified filter function for clarity.
Remove external import and define ServiceRequestStatusFilter locally in ServiceRequestsSearchBar.tsx. Adds a brief doc comment and explicit union variants ("all", "pending", "in_progress", "completed") to decouple the search bar from @pages/ServiceRequestsPage and clarify allowed status buckets.
📝 WalkthroughWalkthroughThis pull request extends the case-details UI to support service requests by adding service-request-aware props and conditional rendering throughout the component hierarchy, refactoring ServiceRequestsPage to use the shared AllCases data pattern, extending the CaseDetails model with new fields (changeRequests, assignedTeam, duration, engagement dates), and removing assigned-engineer display logic from action/header components. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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.
Actionable comments posted: 3
🧹 Nitpick comments (5)
apps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsSkeleton.tsx (1)
20-26: Same pattern:hideAssignedEngineerprop is declared but voided.Consistent with
CaseDetailsActionRow, this prop is kept in the interface (line 25) but voided at line 69. The JSDoc comment at line 24 ("omits engineer avatar/name skeleton") describes behavior that no longer occurs. Either remove the prop from the interface or update the documentation to reflect its deprecated status.♻️ Suggested fix
export interface CaseDetailsSkeletonProps { /** When true, hides the action row (manage status section). */ hideActionRow?: boolean; showEngineerOnly?: boolean; - /** When true, omits engineer avatar/name skeleton (security report analysis). */ - hideAssignedEngineer?: boolean; }And update the function signature:
export default function CaseDetailsSkeleton({ hideActionRow = false, showEngineerOnly = false, - hideAssignedEngineer = false, }: CaseDetailsSkeletonProps = {}): JSX.Element { - void hideAssignedEngineer;Also applies to: 64-69
🤖 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/header/CaseDetailsSkeleton.tsx` around lines 20 - 26, The CaseDetailsSkeletonProps interface declares hideAssignedEngineer but the prop is unused/voided in CaseDetailsSkeleton; either remove hideAssignedEngineer from the CaseDetailsSkeletonProps interface and from the CaseDetailsSkeleton component signature/props destructuring (and any related default values/usage), or if you intend to keep it for compatibility mark it deprecated and update the JSDoc to state it's ignored; update the JSDoc comment above CaseDetailsSkeletonProps and the CaseDetailsSkeleton function signature to reflect the chosen change so the interface and component stay consistent.apps/customer-portal/webapp/src/components/support/case-details/header/__tests__/CaseDetailsActionRow.test.tsx (1)
68-81: Test name is misleading after the refactor.This test asserts that "Support Engineer" is not in the document when
assignedEngineeris undefined, but the engineer section is now always hidden regardless of the prop value (since the prop is voided). The test passes by coincidence rather than testing the stated condition.Consider either:
- Removing this test since the behavior it describes no longer exists
- Renaming it to reflect the actual current behavior (e.g., "should not render engineer section")
♻️ Suggested fix: consolidate or rename the test
- it("should hide engineer section when assignedEngineer is null/undefined", () => { + it("should not render engineer section", () => { render( <ThemeProvider theme={createTheme()}> <CaseDetailsActionRow - assignedEngineer={undefined} - engineerInitials="--" + assignedEngineer="Jane Doe" + engineerInitials="JD" statusLabel="Open" /> </ThemeProvider>, ); + // Engineer section was removed from this component expect(screen.queryByText("Support Engineer")).not.toBeInTheDocument(); expect(screen.getByText("Manage State")).toBeInTheDocument(); expect(screen.getByText("Close")).toBeInTheDocument(); });🤖 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/header/__tests__/CaseDetailsActionRow.test.tsx` around lines 68 - 81, The test CaseDetailsActionRow.test.tsx currently asserts visibility based on the assignedEngineer prop but the component now always hides the engineer section; update the test to reflect actual behavior by renaming the spec from "should hide engineer section when assignedEngineer is null/undefined" to "should not render engineer section" (or remove the test entirely if redundant), and keep assertions against CaseDetailsActionRow rendering with assignedEngineer={undefined} / engineerInitials to verify "Support Engineer" is not in the document while "Manage State" and "Close" remain present; update the test title and any comments to reference CaseDetailsActionRow and the assignedEngineer prop accordingly.apps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsActionRow.tsx (2)
125-128: Voiding parameters is a code smell indicating dead code.Using
voidto silence unused-variable warnings for four props suggests these should either be removed or the removal should be tracked explicitly. This pattern makes it easy for future developers to mistakenly assume these props affect rendering.🤖 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/header/CaseDetailsActionRow.tsx` around lines 125 - 128, The four voided props (assignedEngineer, engineerInitials, hideAssignedEngineer, isLoading) are dead code; remove them from the CaseDetailsActionRow component props/interface and from any JSX/parent calls that pass them, or if they are still required by callers replace with a documented opt-in prop (or rename to _assignedEngineer to indicate intentionally unused) and update tests. Specifically, delete the "void assignedEngineer; void engineerInitials; void hideAssignedEngineer; void isLoading;" lines, remove those identifiers from the component's props type and parameter list, and search for and remove or update any places that pass those props so types and runtime calls remain consistent.
69-84: Consider removing unused props from the interface or adding a deprecation comment.The interface declares
assignedEngineer,engineerInitials,hideAssignedEngineer, andisLoadingprops that are immediately voided (lines 125-128) and have no effect on rendering. This creates a confusing API contract where callers (e.g.,CaseDetailsContent.tsx) still pass these values expecting them to be used.Additionally, weakening
assignedEngineertounknown(line 70) loses type safety without benefit since the value is discarded.If this is an intermediate refactoring step, consider adding a
@deprecatedJSDoc annotation or TODO comment explaining the plan. Otherwise, remove these props from the interface and update callers to stop passing them.♻️ Suggested approach
Option A: Remove unused props entirely:
export interface CaseDetailsActionRowProps { - assignedEngineer: unknown; - engineerInitials: string; statusLabel?: string | null; /** When case is closed, used to hide "Open Related Case" after 2 months. */ closedOn?: string | null; onOpenRelatedCase?: () => void; /** Project ID for useGetProjectFilters and usePatchCase. */ projectId?: string; /** Case ID for PATCH case state. */ caseId?: string; - isLoading?: boolean; showOnlyEngineer?: boolean; - /** When true, hides assigned engineer (e.g. security report analysis). */ - hideAssignedEngineer?: boolean; }Option B: Add deprecation comments if removal is planned for a subsequent PR:
export interface CaseDetailsActionRowProps { - assignedEngineer: unknown; + /** `@deprecated` No longer rendered; will be removed in a future PR. */ + assignedEngineer?: unknown;🤖 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/header/CaseDetailsActionRow.tsx` around lines 69 - 84, The CaseDetailsActionRowProps interface declares unused props (assignedEngineer, engineerInitials, hideAssignedEngineer, isLoading) and weakens assignedEngineer to unknown; either remove these props from CaseDetailsActionRowProps or mark them `@deprecated` with a clear TODO explaining planned removal, and then update all callers (e.g., CaseDetailsContent.tsx) to stop passing them; if you remove them, also delete any related unused references in the CaseDetailsActionRow component and restore a stronger type for assignedEngineer where it is still used elsewhere.apps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsTabs.tsx (1)
49-57: Prefer stable tab IDs instead of label-prefix checks for hide logic.Using
startsWith("Calls")/startsWith("Knowledge Base")makes visibility dependent on display text. A label rename/localization can break tab hiding unexpectedly. Prefer filtering by a stable tab identifier (e.g.,id: "calls" | "knowledgeBase"inCASE_DETAILS_TABS).♻️ Suggested direction
- const tabs = CASE_DETAILS_TABS.filter((t) => { - if (hideCallsTab && t.label.startsWith("Calls")) { + const tabs = CASE_DETAILS_TABS.filter((t) => { + if (hideCallsTab && t.id === "calls") { return false; } - if (hideKnowledgeBaseTab && t.label.startsWith("Knowledge Base")) { + if (hideKnowledgeBaseTab && t.id === "knowledgeBase") { return false; } return true; });Based on learnings: Avoid deriving UI logic from raw label strings and prefer stable IDs/enums for resilient 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/header/CaseDetailsTabs.tsx` around lines 49 - 57, The current tab-filtering logic in CASE_DETAILS_TABS uses label.startsWith(...) which is brittle; update CASE_DETAILS_TABS to include a stable identifier property (e.g., id: "calls" | "knowledgeBase" for each tab) and change the filter in CaseDetailsTabs.tsx to check t.id against hideCallsTab and hideKnowledgeBaseTab instead of t.label, keeping the existing boolean flags hideCallsTab and hideKnowledgeBaseTab and preserving other filter behavior for all other tabs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/customer-portal/webapp/src/components/support/case-details/details-tab/CaseDetailsDetailsPanel.tsx`:
- Around line 297-330: Remove the duplicate "Assigned team" rendering: delete
the earlier unconditional block that renders Typography with label "Assigned
team" and value formatValue(data?.assignedTeam ?? null) so only the later
conditional block (isServiceRequest && data?.assignedTeam) that uses
formatValue(data.assignedTeam) remains; this keeps the field rendered only when
assignedTeam has a value within CaseDetailsDetailsPanel (references:
isServiceRequest, data.assignedTeam, formatValue).
In `@apps/customer-portal/webapp/src/pages/ServiceRequestsPage.tsx`:
- Around line 147-150: The isStatsLoading expression (used to show the loader)
doesn't account for errors and can stay true when hasStatsResponse is false;
update the composite condition that defines isStatsLoading to include &&
!isStatsError so it becomes false when an error occurs—locate the constant
isStatsLoading (which currently uses isProjectContextLoading,
isStatsQueryLoading, projectId, and hasStatsResponse) and add the isStatsError
guard to that boolean expression.
- Around line 186-196: The UI shows a mismatched total because
filteredAndSearchedCases removes S0 cases client-side while totalItems uses
apiTotalRecords; fix by making the filter server-side (add an excludeS0 param to
the API request and return a correct apiTotalRecords so filteredAndSearchedCases
and totalItems align), or if server-side filtering isn't possible, change the
totalItems calculation in this component to account for excludeS0 (e.g., when
excludeS0 is true use filteredAndSearchedCases.length for totalItems instead of
apiTotalRecords) — update references in this file to filteredAndSearchedCases,
totalItems, apiTotalRecords, excludeS0, paginatedCases, and totalPages
accordingly.
---
Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/support/case-details/header/__tests__/CaseDetailsActionRow.test.tsx`:
- Around line 68-81: The test CaseDetailsActionRow.test.tsx currently asserts
visibility based on the assignedEngineer prop but the component now always hides
the engineer section; update the test to reflect actual behavior by renaming the
spec from "should hide engineer section when assignedEngineer is null/undefined"
to "should not render engineer section" (or remove the test entirely if
redundant), and keep assertions against CaseDetailsActionRow rendering with
assignedEngineer={undefined} / engineerInitials to verify "Support Engineer" is
not in the document while "Manage State" and "Close" remain present; update the
test title and any comments to reference CaseDetailsActionRow and the
assignedEngineer prop accordingly.
In
`@apps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsActionRow.tsx`:
- Around line 125-128: The four voided props (assignedEngineer,
engineerInitials, hideAssignedEngineer, isLoading) are dead code; remove them
from the CaseDetailsActionRow component props/interface and from any JSX/parent
calls that pass them, or if they are still required by callers replace with a
documented opt-in prop (or rename to _assignedEngineer to indicate intentionally
unused) and update tests. Specifically, delete the "void assignedEngineer; void
engineerInitials; void hideAssignedEngineer; void isLoading;" lines, remove
those identifiers from the component's props type and parameter list, and search
for and remove or update any places that pass those props so types and runtime
calls remain consistent.
- Around line 69-84: The CaseDetailsActionRowProps interface declares unused
props (assignedEngineer, engineerInitials, hideAssignedEngineer, isLoading) and
weakens assignedEngineer to unknown; either remove these props from
CaseDetailsActionRowProps or mark them `@deprecated` with a clear TODO explaining
planned removal, and then update all callers (e.g., CaseDetailsContent.tsx) to
stop passing them; if you remove them, also delete any related unused references
in the CaseDetailsActionRow component and restore a stronger type for
assignedEngineer where it is still used elsewhere.
In
`@apps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsSkeleton.tsx`:
- Around line 20-26: The CaseDetailsSkeletonProps interface declares
hideAssignedEngineer but the prop is unused/voided in CaseDetailsSkeleton;
either remove hideAssignedEngineer from the CaseDetailsSkeletonProps interface
and from the CaseDetailsSkeleton component signature/props destructuring (and
any related default values/usage), or if you intend to keep it for compatibility
mark it deprecated and update the JSDoc to state it's ignored; update the JSDoc
comment above CaseDetailsSkeletonProps and the CaseDetailsSkeleton function
signature to reflect the chosen change so the interface and component stay
consistent.
In
`@apps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsTabs.tsx`:
- Around line 49-57: The current tab-filtering logic in CASE_DETAILS_TABS uses
label.startsWith(...) which is brittle; update CASE_DETAILS_TABS to include a
stable identifier property (e.g., id: "calls" | "knowledgeBase" for each tab)
and change the filter in CaseDetailsTabs.tsx to check t.id against hideCallsTab
and hideKnowledgeBaseTab instead of t.label, keeping the existing boolean flags
hideCallsTab and hideKnowledgeBaseTab and preserving other filter behavior for
all other tabs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c1332ac0-f8ac-46a0-b581-b5d473ffb58f
📒 Files selected for processing (14)
apps/customer-portal/webapp/src/components/support/all-cases/AllCasesStatCards.tsxapps/customer-portal/webapp/src/components/support/case-details/details-tab/CaseDetailsCard.tsxapps/customer-portal/webapp/src/components/support/case-details/details-tab/CaseDetailsContent.tsxapps/customer-portal/webapp/src/components/support/case-details/details-tab/CaseDetailsDetailsPanel.tsxapps/customer-portal/webapp/src/components/support/case-details/details-tab/__tests__/CaseDetailsDetailsPanel.test.tsxapps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsActionRow.tsxapps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsSkeleton.tsxapps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsTabPanels.tsxapps/customer-portal/webapp/src/components/support/case-details/header/CaseDetailsTabs.tsxapps/customer-portal/webapp/src/components/support/case-details/header/__tests__/CaseDetailsActionRow.test.tsxapps/customer-portal/webapp/src/components/support/service-requests/ServiceRequestsSearchBar.tsxapps/customer-portal/webapp/src/models/responses.tsapps/customer-portal/webapp/src/pages/ServiceRequestDetailsPage.tsxapps/customer-portal/webapp/src/pages/ServiceRequestsPage.tsx
9be52e9
into
wso2-open-operations:dev-app-customer-portal-v1.0.x
Description
This PR introduces a complete revamp of the Service Requests listing and details pages, aligning them with the existing Case Management experience for consistency, scalability, and improved UX.
Key Changes
Service Requests Listing Page
AllCasesListAllCasesSearchBarAllCasesStatCardsuseGetProjectCasesStatsService Request Details Page
CaseDetailsContentlayoutisServiceRequestflag to drive SR-specific UI behaviorShared Component Enhancements
CaseDetailsContent,CaseDetailsDetailsPanel, and related components updated to support:CaseDetailsCardenhanced withrightActionsupportAllCasesStatCardsnow accepts dynamicentityNameCleanup & Simplifications
CaseDetailsActionRowby removing engineer displayformatDateOnlyOutcome
Summary by CodeRabbit
Release Notes
New Features
Improvements