[Customer Portal][Web] Integrate Change Request Stats API & Add Deployment Filter to All Cases - #289
Conversation
Introduce a new hook useGetProjectChangeRequestStats to fetch and map change request stats from the backend (with response type ChangeRequestStatsResponse). Add a new ApiQueryKeys entry (CHANGE_REQUEST_STATS). Integrate the hook into ChangeRequestsPage: import and call the hook, remove the previous mocked stats, use the hook's loading/error states for the stat cards, and ensure exports wait for stats to be available. Includes mapping logic to derive scheduled, inProgress, and completed counts from API state counts and basic error/log handling.
Reformat useGetProjectChangeRequestStats.ts for readability (multi-line imports, function params, arrays, and logger calls) and clean up whitespace; no behavioral changes to the mapping logic. In ChangeRequestsPage.tsx, wrap setIsExporting(false) in setTimeout(..., 0) when handling export success/error to defer the state update and avoid synchronous React state updates during render or while unmounting.
Wire project deployments into the all-cases filters so the deployment dropdown uses real deployment items instead of generic metadata. Added optional deployments props to AllCasesFilters and AllCasesSearchBar, imported the ProjectDeploymentItem type, and updated AllCasesFilters to build options from deployments (label = type.label || name, value = id) when the filter id is "deployment". AllCasesPage now fetches deployments via useGetDeployments and passes them through to the search bar; when deployments are not present, the component falls back to the existing metadata-based options.
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a typed React Query hook to fetch project change-request stats, threads deployment data into cases UI for deployment-based filters, wires real stats into ChangeRequestsPage export/UX, and propagates an optional conversationId through case creation and chat navigation. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ChangeRequestsPage
participant AuthState
participant useGetProjectChangeRequestStats
participant APIServer
participant StatCards
User->>ChangeRequestsPage: open page with projectId
ChangeRequestsPage->>AuthState: read isSignedIn / isLoading
AuthState-->>ChangeRequestsPage: auth ready/values
ChangeRequestsPage->>useGetProjectChangeRequestStats: init hook (projectId)
Note over useGetProjectChangeRequestStats: enabled when signed in & auth not loading
useGetProjectChangeRequestStats->>APIServer: GET /projects/{id}/stats/change-requests (Authorization)
APIServer-->>useGetProjectChangeRequestStats: ChangeRequestStatsResponse (JSON)
useGetProjectChangeRequestStats->>useGetProjectChangeRequestStats: mapChangeRequestStats()
useGetProjectChangeRequestStats-->>ChangeRequestsPage: ChangeRequestStats / error
ChangeRequestsPage->>StatCards: render with data / loading / error
StatCards-->>User: display stats
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
apps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsx (1)
168-200: Consider using a ref or restructuring to avoidsetTimeoutfor state updates.The
setTimeout(..., 0)pattern at lines 174 and 189 works but is fragile. It's used to avoid synchronous state updates during the effect execution. A cleaner approach would be to track export completion with a separate ref or restructure the effect to avoid the timing issue.That said, the current implementation is functional and the condition at line 179-180 (
infiniteData && stats) correctly ensures all data is available before PDF generation.💡 Optional: Use a ref to track pending state change
+ const pendingExportReset = useRef(false); + + useEffect(() => { + if (pendingExportReset.current) { + pendingExportReset.current = false; + setIsExporting(false); + } + }); useEffect(() => { if (isExporting) { if (isInfiniteError) { console.error("Failed to fetch change requests for export"); - setTimeout(() => setIsExporting(false), 0); + pendingExportReset.current = true; } else if ( !isInfiniteLoading && !hasNextPage && !isFetchingNextPage && infiniteData && stats ) { const allChangeRequests = infiniteData.pages.flatMap( (page: { changeRequests: ChangeRequestItem[] }) => page.changeRequests, ) || []; generateChangeRequestsSchedulePdf(allChangeRequests, stats); - setTimeout(() => setIsExporting(false), 0); + pendingExportReset.current = true; } } }, [/* ... */]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsx` around lines 168 - 200, The effect handling export completion uses setTimeout(..., 0) to avoid a synchronous state update; replace that pattern by tracking the pending export with a ref or by restructuring effects so setIsExporting(false) is called outside the same synchronous render pass: e.g., introduce an exportingRef (useRef<boolean>) that you set true when starting export and clear to false when the export work finishes (after generateChangeRequestsSchedulePdf returns), or move the setIsExporting(false) call into a separate useEffect that watches a completed flag (e.g., a new exportCompleted state or the exportingRef) so you can call setIsExporting(false) without setTimeout; update references to isExporting, setIsExporting, useEffect, infiniteData, stats, isInfiniteError, and generateChangeRequestsSchedulePdf accordingly.apps/customer-portal/webapp/src/api/useGetProjectChangeRequestStats.ts (1)
33-65: Consider using IDs instead of hardcoded label strings for state matching.The mapping logic relies on exact string matches for state labels (e.g.,
"Scheduled","Implement","Customer Approval"). If the API changes label text (e.g., localization, typo fixes), the mapping will silently return 0 for affected states.Since
stateCountitems include anidfield, consider matching by ID if those IDs are stable, or adding a fallback/warning when expected labels aren't found.💡 Optional: Add logging when expected states are missing
function mapChangeRequestStats( response: ChangeRequestStatsResponse, ): ChangeRequestStats { const { totalCount, stateCount } = response; + // Log warning if expected states are missing + const allExpectedLabels = [ + "Scheduled", + "Implement", + "Review", + "Customer Approval", + "Customer Review", + "Closed", + "Canceled", + "Rollback", + ]; + const foundLabels = stateCount.map((s) => s.label); + const missing = allExpectedLabels.filter((l) => !foundLabels.includes(l)); + if (missing.length > 0) { + console.warn( + `[mapChangeRequestStats] Expected state labels not found: ${missing.join(", ")}`, + ); + } // Find scheduled count (label: "Scheduled") const scheduled = stateCount.find((state) => state.label === "Scheduled")?.count ?? 0;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/api/useGetProjectChangeRequestStats.ts` around lines 33 - 65, The mapChangeRequestStats function currently matches states by hardcoded label strings (stateCount items), which is brittle; update mapChangeRequestStats to match by state.id instead (e.g., replace inProgressLabels/scheduled/completed label arrays with corresponding stable ID arrays like inProgressIds/scheduledIds/completedIds and use state.id for filter/reduce), keep a backward-compatible fallback to label matching if IDs are missing, and optionally emit a warning (e.g., console.warn or your app logger) when expected IDs/labels are not found so missing states are detectable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/customer-portal/webapp/src/api/useGetProjectChangeRequestStats.ts`:
- Around line 33-65: The mapChangeRequestStats function currently matches states
by hardcoded label strings (stateCount items), which is brittle; update
mapChangeRequestStats to match by state.id instead (e.g., replace
inProgressLabels/scheduled/completed label arrays with corresponding stable ID
arrays like inProgressIds/scheduledIds/completedIds and use state.id for
filter/reduce), keep a backward-compatible fallback to label matching if IDs are
missing, and optionally emit a warning (e.g., console.warn or your app logger)
when expected IDs/labels are not found so missing states are detectable.
In `@apps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsx`:
- Around line 168-200: The effect handling export completion uses
setTimeout(..., 0) to avoid a synchronous state update; replace that pattern by
tracking the pending export with a ref or by restructuring effects so
setIsExporting(false) is called outside the same synchronous render pass: e.g.,
introduce an exportingRef (useRef<boolean>) that you set true when starting
export and clear to false when the export work finishes (after
generateChangeRequestsSchedulePdf returns), or move the setIsExporting(false)
call into a separate useEffect that watches a completed flag (e.g., a new
exportCompleted state or the exportingRef) so you can call setIsExporting(false)
without setTimeout; update references to isExporting, setIsExporting, useEffect,
infiniteData, stats, isInfiniteError, and generateChangeRequestsSchedulePdf
accordingly.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/customer-portal/webapp/src/api/useGetProjectChangeRequestStats.tsapps/customer-portal/webapp/src/components/support/all-cases/AllCasesFilters.tsxapps/customer-portal/webapp/src/components/support/all-cases/AllCasesSearchBar.tsxapps/customer-portal/webapp/src/constants/apiConstants.tsapps/customer-portal/webapp/src/models/responses.tsapps/customer-portal/webapp/src/pages/AllCasesPage.tsxapps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsx
Add an optional conversationId to the CreateCaseRequest model and thread it through the UI flow so chat conversations can be linked to newly created cases. CreateCasePage: accept conversationId from location state and include it in the case payload when present. NoveraChatPage: include conversationId in navigation state when routing to the create-case page and add it to the useCallback dependency array. These changes ensure the chat conversation ID is preserved and sent with case creation requests.
Fetch deployments in CasesTable and use them to populate the Deployment filter options. Adds useGetDeployments import and hook call (useGetDeployments(projectId)), replaces previous filtersMetadata.deploymentTypes usage with deploymentsData.deployments, and falls back to d.type?.label || d.name for the option label. This ensures the filter reflects actual deployments for the project.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesTable.tsx (1)
52-54: Consider including deployments loading and error states for consistent UX.The
useGetDeploymentshook exposesisFetchingandisError, but these are not destructured here. If the deployments API is slower than the filters API, the FilterPopover could open while deployment options are still loading (showing an empty dropdown). Similarly, a failed deployments fetch would silently result in an empty dropdown.Consider including these states and propagating them to the FilterPopover:
♻️ Suggested improvement
// Fetch deployments for the deployment filter - const { data: deploymentsData } = useGetDeployments(projectId); + const { + data: deploymentsData, + isFetching: isFetchingDeployments, + isError: isErrorDeployments, + } = useGetDeployments(projectId);Then update the FilterPopover props (around lines 249-250):
- isLoading={isFetchingFilters || isAuthLoading} - isError={isErrorFilters} + isLoading={isFetchingFilters || isFetchingDeployments || isAuthLoading} + isError={isErrorFilters || isErrorDeployments}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesTable.tsx` around lines 52 - 54, The deployments fetch currently only destructures data from useGetDeployments (deploymentsData) and ignores isFetching/isError which can cause an empty or silent-failure dropdown; update the useGetDeployments call to also destructure isFetching and isError, then pass those flags into the FilterPopover props (e.g., loading/deploymentsLoading and error/deploymentsError) so the FilterPopover can show a loading state or an error message when useGetDeployments (function name) is still fetching or failed; ensure any prop names you add align with FilterPopover's expected props or add new props to FilterPopover to consume these states.
🤖 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/pages/CreateCasePage.tsx`:
- Line 209: conversationId is read only from location.state so it is lost on
refresh; update CreateCasePage to initialize conversationId from location.state
|| sessionStorage and persist any conversationId changes back to sessionStorage
before you build the case payload (affects the same logic used when constructing
the payload in the create case handler/buildPayload). Specifically: when
initializing the component state for conversationId, check sessionStorage for a
saved value if location.state?.conversationId is undefined; whenever
conversationId is set or updated (the state setter used in CreateCasePage and
before calling the function that builds the payload/createCase handler), write
it into sessionStorage so the value survives a page refresh.
---
Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesTable.tsx`:
- Around line 52-54: The deployments fetch currently only destructures data from
useGetDeployments (deploymentsData) and ignores isFetching/isError which can
cause an empty or silent-failure dropdown; update the useGetDeployments call to
also destructure isFetching and isError, then pass those flags into the
FilterPopover props (e.g., loading/deploymentsLoading and
error/deploymentsError) so the FilterPopover can show a loading state or an
error message when useGetDeployments (function name) is still fetching or
failed; ensure any prop names you add align with FilterPopover's expected props
or add new props to FilterPopover to consume these states.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesTable.tsxapps/customer-portal/webapp/src/models/requests.tsapps/customer-portal/webapp/src/pages/CreateCasePage.tsxapps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
Use mapSeverityToDisplay when building severity filter options in CasesTable so severity values are rendered with display-friendly labels. Added import from @utils/support and applied the mapper to the severity metadata mapping.
Remove the ExternalLink import and its rendered icon from apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesList.tsx. Cleans up an unused import and removes the external link icon displayed next to case titles.
Add CONVERSATION_ID_STORAGE_KEY and manage conversationId state so it survives page refreshes. Initialize conversationId from locationState or sessionStorage, sync updates to sessionStorage via useEffect, and prefer the stored conversationId when building the case payload. Also remove the stored conversationId on successful case creation and add console.error logging for sessionStorage failures.
Import and use the useLogger hook in CreateCasePage.tsx, replacing direct console.error calls with logger.error for centralized logging and better observability. Also add logger to relevant useEffect dependency arrays to satisfy hook dependency rules and avoid stale closures.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsx (1)
169-199:⚠️ Potential issue | 🟠 MajorHandle stats-query failure in export flow to avoid stuck “Exporting...” state.
At Line 171, only infinite-query errors reset
isExporting. If stats fails (Line 179 remains falsy), export never completes and the button can stay disabled.🔧 Proposed fix
useEffect(() => { if (isExporting) { - if (isInfiniteError) { + if (isInfiniteError || isStatsError) { // eslint-disable-next-line react-hooks/set-state-in-effect setIsExporting(false); } else if ( !isInfiniteLoading && !hasNextPage && !isFetchingNextPage && infiniteData && stats ) { // Success case - all data fetched const allChangeRequests = infiniteData.pages.flatMap( (page: { changeRequests: ChangeRequestItem[] }) => page.changeRequests, ) || []; generateChangeRequestsSchedulePdf(allChangeRequests, stats); setTimeout(() => setIsExporting(false), 0); } } }, [ isExporting, isInfiniteLoading, isInfiniteError, + isStatsError, hasNextPage, isFetchingNextPage, infiniteData, stats, ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsx` around lines 169 - 199, The export useEffect currently only clears isExporting on infinite-query errors (isInfiniteError) but never handles failures of the stats query, so when stats is falsy the export can hang; update the effect that watches isExporting to also detect the stats failure state (or "not loading and errored" for the stats query) and call setIsExporting(false) in that branch, or treat missing stats as an error path before calling generateChangeRequestsSchedulePdf; ensure you reference the same symbols (useEffect, isExporting, isInfiniteError, isInfiniteLoading, hasNextPage, isFetchingNextPage, infiniteData, stats, setIsExporting, generateChangeRequestsSchedulePdf) so the logic aborts the export when stats failed instead of waiting indefinitely.
🧹 Nitpick comments (1)
apps/customer-portal/webapp/src/pages/CreateCasePage.tsx (1)
181-181: Use the centralized logger consistently for storage errors.
useLoggeris introduced, but Line 239 and Line 252 still useconsole.error, which splits error reporting.Suggested patch
- } catch (e) { - console.error("Failed to parse stored classification data", e); + } catch (e) { + logger.error("Failed to parse stored classification data", e); return undefined; } @@ - } catch (e) { - console.error( - "Failed to store classification data in sessionStorage", - e, - ); + } catch (e) { + logger.error( + "Failed to store classification data in sessionStorage", + e, + ); } @@ - }, [locationState?.classificationResponse, STORAGE_KEY]); + }, [locationState?.classificationResponse, STORAGE_KEY, logger]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/pages/CreateCasePage.tsx` at line 181, The file introduces const logger = useLogger() in the CreateCasePage component but still uses console.error for storage failures; replace those console.error calls with logger.error and pass the error object and a clear contextual message (e.g., "failed saving draft" or "failed uploading attachment") so all storage errors use the centralized logger (look for the console.error usages in CreateCasePage and update them to logger.error with the error and context).
🤖 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/pages/CreateCasePage.tsx`:
- Around line 295-306: The useEffect for conversationId only writes to
sessionStorage when conversationId is truthy, leaving stale values behind;
update the effect in CreateCasePage.tsx to handle both cases by calling
sessionStorage.setItem(CONVERSATION_ID_STORAGE_KEY, conversationId) when
conversationId is defined/truthy and
sessionStorage.removeItem(CONVERSATION_ID_STORAGE_KEY) (inside the same
try/catch that logs via logger.error) when conversationId is falsy/undefined, so
the stored value is cleared; keep the existing error handling around both
operations and reference the existing conversationId,
CONVERSATION_ID_STORAGE_KEY, logger, and the useEffect callback.
- Around line 727-728: The sessionStorage cleanup in the create-case success
handler can throw and interrupt the onSuccess flow; wrap the calls that remove
STORAGE_KEY and CONVERSATION_ID_STORAGE_KEY in a safe guard (e.g., check typeof
sessionStorage !== "undefined" and/or wrap
sessionStorage.removeItem(STORAGE_KEY) and
sessionStorage.removeItem(CONVERSATION_ID_STORAGE_KEY) in a try/catch) inside
the onSuccess handler so any error is caught and logged but does not prevent the
subsequent navigation or success logic.
---
Outside diff comments:
In `@apps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsx`:
- Around line 169-199: The export useEffect currently only clears isExporting on
infinite-query errors (isInfiniteError) but never handles failures of the stats
query, so when stats is falsy the export can hang; update the effect that
watches isExporting to also detect the stats failure state (or "not loading and
errored" for the stats query) and call setIsExporting(false) in that branch, or
treat missing stats as an error path before calling
generateChangeRequestsSchedulePdf; ensure you reference the same symbols
(useEffect, isExporting, isInfiniteError, isInfiniteLoading, hasNextPage,
isFetchingNextPage, infiniteData, stats, setIsExporting,
generateChangeRequestsSchedulePdf) so the logic aborts the export when stats
failed instead of waiting indefinitely.
---
Nitpick comments:
In `@apps/customer-portal/webapp/src/pages/CreateCasePage.tsx`:
- Line 181: The file introduces const logger = useLogger() in the CreateCasePage
component but still uses console.error for storage failures; replace those
console.error calls with logger.error and pass the error object and a clear
contextual message (e.g., "failed saving draft" or "failed uploading
attachment") so all storage errors use the centralized logger (look for the
console.error usages in CreateCasePage and update them to logger.error with the
error and context).
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesList.tsxapps/customer-portal/webapp/src/components/dashboard/cases-table/CasesTable.tsxapps/customer-portal/webapp/src/constants/apiConstants.tsapps/customer-portal/webapp/src/models/requests.tsapps/customer-portal/webapp/src/models/responses.tsapps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsxapps/customer-portal/webapp/src/pages/CreateCasePage.tsxapps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
💤 Files with no reviewable changes (1)
- apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesList.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/customer-portal/webapp/src/constants/apiConstants.ts
- apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesTable.tsx
- apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
- apps/customer-portal/webapp/src/models/requests.ts
Replace console.error with logger.error in CreateCasePage and add logger to effect deps. Make sessionStorage interactions more robust by wrapping reads/writes/removals in try/catch, removing the conversationId key when undefined, and logging failures during cleanup after case creation. In ChangeRequestsPage, include stats loading/error flags in the export completion effect and add them to the effect dependency list so exports wait for stats and properly handle stats errors. These changes improve error reporting and resilience for sessionStorage and export flows.
3456bdc
into
wso2-open-operations:customer-portal-milestone-1
Description
This pull request adds a new API hook for fetching change request statistics and integrates it into the change requests page, replacing the previous mock stats implementation. It also introduces support for a deployment filter in the all cases search bar and filter components by fetching deployments from the API and passing them as props.
Change Request Stats API Integration
useGetProjectChangeRequestStatsto fetch change request statistics from the backend, including mapping logic for response data. (apps/customer-portal/webapp/src/api/useGetProjectChangeRequestStats.ts, apps/customer-portal/webapp/src/api/useGetProjectChangeRequestStats.tsR1-R127)ChangeRequestsPage, replacing the previous mock stats logic, and updated the export logic to use real stats. (apps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsx, [1] [2] [3] [4]CHANGE_REQUEST_STATSkey toApiQueryKeysfor consistent query management. (apps/customer-portal/webapp/src/constants/apiConstants.ts, apps/customer-portal/webapp/src/constants/apiConstants.tsR54)apps/customer-portal/webapp/src/models/responses.ts, apps/customer-portal/webapp/src/models/responses.tsR371-R380)Deployment Filter Support in All Cases
useGetDeploymentshook and passed them to the all cases search bar and filter components. (apps/customer-portal/webapp/src/pages/AllCasesPage.tsx, [1] [2] [3]AllCasesSearchBarandAllCasesFiltersto support and render deployment filter options. (apps/customer-portal/webapp/src/components/support/all-cases/AllCasesSearchBar.tsx, [1] [2] [3] [4];apps/customer-portal/webapp/src/components/support/all-cases/AllCasesFilters.tsx, [5] [6] [7] [8]Summary by CodeRabbit
New Features
Bug Fixes