[Customer portal] [web] Introduce Scheduled Maintenance Window card and PATCH API for change request updates - #303
Conversation
Introduce a new react-query mutation hook usePatchChangeRequest to PATCH /change-requests/:id. The hook uses the authenticated API client and Asgardeo auth state, sends JSON payloads, parses responses with robust error handling and logging, and invalidates the CHANGE_REQUEST_DETAILS query for the given id on success. Includes TypeScript generics for request/response types.
Import ScheduledMaintenanceWindowCard and render it in ChangeRequestDetailsPage just above the Deployment & Component Card. The component is passed the existing changeRequest prop so scheduled maintenance window information is displayed on the details page.
Introduce ScheduledMaintenanceWindowCard.tsx: a new React component that displays a change request's planned start, planned end, and duration using WSO2 Oxygen UI. Includes inline editing for the planned start (datetime-local), utilities to convert/format API datetimes and durations, and integrates with usePatchChangeRequest and the error banner for mutation handling and validation. Adds license header and accessibility-friendly UI elements (icons, labels, and buttons).
Add a new Vitest/React Testing Library test file for ScheduledMaintenanceWindowCard. The tests render the component in a ThemeProvider, mock usePatchChangeRequest and useErrorBanner hooks, and verify the card displays title and maintenance window fields, shows an edit button for planned start, and opens edit mode (Update/Cancel buttons) when the edit button is clicked.
Align ChangeRequest models with updated API responses: make case.number and several number fields nullable/optional, change duration from number to string|null, remove deployment.type, and expand deployedProduct into id/label/number. Move product, assignedEngineer, and assignedTeam into ChangeRequestItem and remove their duplicates from ChangeRequestDetails to reflect the new response shape and handle null values.
Allow formatDuration to accept number or string inputs (API may return strings) and tighten validation. The function now parses string values with parseInt, treats null/NaN/negative values as "Not Available", and uses the parsed numeric value for hour/minute calculation to avoid incorrect results.
Introduce a new request interface for PATCH /change-requests/:id to type the request body when updating the planned start date. Adds PatchChangeRequestRequest with a plannedStartOn string field to ensure typed API usage in the customer-portal webapp models.
Wrap the POST /conversations request in a try/catch and add an AbortController with a 40s timeout. Pass the controller.signal to authFetch and clear the timeout on success. Handle AbortError with a specific timeout message and log other errors before rethrowing. Minor restructuring: move params destructuring inside the try and retain existing auth/config validations and response checks.
📝 WalkthroughWalkthroughIntroduces a new React Hook ( Changes
Sequence DiagramsequenceDiagram
participant User
participant UI as ScheduledMaintenanceWindowCard
participant Hook as usePatchChangeRequest
participant API as Authenticated API Client
participant Cache as React Query Cache
participant Backend as Backend API
User->>UI: Click Edit on Planned Start
UI->>UI: Enter edit mode, show datetime input
User->>UI: Update date and click "Update"
UI->>Hook: Call mutation with new plannedStartOn
Hook->>Hook: Validate auth state
Hook->>API: Create PATCH request with AbortController
API->>Backend: PATCH /change-requests/{id} (plannedStartOn)
Backend-->>API: 200 OK + updated ChangeRequest
API-->>Hook: Response received
Hook->>Hook: Parse JSON response
Hook->>Cache: Invalidate change request details query
Cache->>Cache: Query re-executed
Backend-->>Cache: Fresh ChangeRequest data
Cache-->>UI: Updated changeRequest prop
UI->>UI: Exit edit mode, render new data
UI-->>User: Display updated Planned Start/End/Duration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ 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.
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/models/responses.ts (1)
340-357:⚠️ Potential issue | 🔴 CriticalAdd missing
PatchChangeRequestResponsetype definition.The
usePatchChangeRequest.tshook importsPatchChangeRequestResponsefrom@models/responses, but this type is not exported. This will cause a TypeScript compilation error.The PATCH endpoint returns the updated change request resource, which matches the
ChangeRequestDetailsstructure. Add this type alias toresponses.ts:+/** Response from PATCH /change-requests/:id. */ +export type PatchChangeRequestResponse = ChangeRequestDetails;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/models/responses.ts` around lines 340 - 357, Add a missing type alias so the hook can import the PATCH response type: declare and export PatchChangeRequestResponse as an alias of ChangeRequestDetails (i.e., export type PatchChangeRequestResponse = ChangeRequestDetails;) so that consumers like usePatchChangeRequest can import PatchChangeRequestResponse; update exports if necessary to ensure PatchChangeRequestResponse is exported alongside ChangeRequestDetails.
🧹 Nitpick comments (4)
apps/customer-portal/webapp/src/api/usePostConversations.ts (2)
68-77: Consider usingfinallyforclearTimeoutto ensure cleanup in edge cases.If an exception were thrown between
authFetchandclearTimeout(e.g., from a future code modification), the timeout would continue running. MovingclearTimeoutto afinallyblock provides more robust cleanup.♻️ Suggested refactor
const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 40_000); + try { const response = await authFetch(requestUrl, { method: "POST", signal: controller.signal, body: JSON.stringify({ message, envProducts, region, tier }), }); - - clearTimeout(timeoutId); + } finally { + clearTimeout(timeoutId); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/api/usePostConversations.ts` around lines 68 - 77, Move the clearTimeout(timeoutId) call into a finally block to guarantee the AbortController timeout is cleared even if authFetch or later code throws; wrap the authFetch call (and any subsequent logic that uses controller/timeoutId) in try { ... } catch { ... } finally { clearTimeout(timeoutId); } so the timeout is always cleaned up while keeping the controller.signal passed to authFetch unchanged.
47-103: Consider adding the same timeout protection tousePostConversationMessages.This hook now has robust timeout handling via AbortController, but the parallel hook
usePostConversationMessages(used for follow-up messages in the same chat) lacks this protection. If the conversation messages endpoint can also be slow, users could experience inconsistent timeout behavior between initial and follow-up messages.#!/bin/bash # Check if usePostConversationMessages has any timeout handling rg -n "AbortController|setTimeout|timeout" apps/customer-portal/webapp/src/api/usePostConversationMessages.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/api/usePostConversations.ts` around lines 47 - 103, Add the same timeout/abort protection used in usePostConversations to the usePostConversationMessages hook: create an AbortController before the fetch, start a setTimeout that calls controller.abort() after 40_000 ms (store id in timeoutId), pass controller.signal into the authFetch call, clearTimeout(timeoutId) after the response is received, and update the catch block to detect AbortError (error.name === "AbortError") and throw a new Error("Conversations API request timed out after 40 seconds") while preserving existing logging; look for function/usePostConversationMessages and the authFetch call to insert these changes and mirror the same error-handling pattern (timeoutId, controller, 40_000, AbortError) used in usePostConversations.apps/customer-portal/webapp/src/components/support/change-requests/ScheduledMaintenanceWindowCard.tsx (2)
72-79: Consider reusing the centralizedformatDurationutility.This local
formatDurationfunction duplicates logic from@utils/support.ts. The centralized utility now handles both string and number inputs. Using it would reduce duplication and ensure consistent formatting across the application.♻️ Proposed refactor to use centralized utility
+import { formatDuration as formatDurationUtil } from "@utils/support"; -/** - * Format minutes as "X hours Y minutes". - * - * `@param` {number} minutes - Total minutes. - * `@returns` {string} Formatted duration string. - */ -function formatDuration(minutes: number): string { - const hours = Math.floor(minutes / 60); - const mins = minutes % 60; - const parts: string[] = []; - if (hours > 0) parts.push(`${hours} hour${hours === 1 ? "" : "s"}`); - parts.push(`${mins} minute${mins === 1 ? "" : "s"}`); - return parts.join(" "); -}Then update the
durationTextcomputation to use the imported utility, or if you need the specific "X hours Y minutes" format (vs "Xh Ym"), keep the local function but rename it to avoid confusion.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/support/change-requests/ScheduledMaintenanceWindowCard.tsx` around lines 72 - 79, The local formatDuration function duplicates centralized logic—replace its usage with the shared formatDuration from `@utils/support.ts` (import it at the top) and update the durationText computation to call that shared function (handle string/number inputs per the utility); alternatively, if you need a different "X hours Y minutes" wording, rename the local function (e.g., formatDurationVerbose) to avoid colliding with the shared symbol and keep its usage limited to ScheduledMaintenanceWindowCard.
147-164: Consider resettingeditValueafter successful update.When the mutation succeeds,
setIsEditing(false)is called, buteditValueretains the old value. If the user clicks edit again before the query cache is invalidated and refetched, they might see stale data. Consider updatingeditValuewith the submitted value on success:🔧 Optional enhancement
onSuccess: () => { setIsEditing(false); + // editValue will be refreshed when component re-renders with new changeRequest data },Alternatively, if immediate consistency is needed before refetch completes:
onSuccess: () => { + setEditValue(editValue); // Keep the submitted value setIsEditing(false); },However, since
queryClient.invalidateQueriestriggers a refetch and the component will re-render with updatedchangeRequest.startDate, the current behavior should work correctly in practice.🤖 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/change-requests/ScheduledMaintenanceWindowCard.tsx` around lines 147 - 164, In handleUpdate, after a successful patchMutation you should also update the local editValue to the submitted apiValue (or map back to display format) to avoid showing stale data when re-entering edit mode; specifically, in the patchMutation.onSuccess callback (in ScheduledMaintenanceWindowCard, inside handleUpdate) setEditValue(editValueOrConvertedBack) alongside setIsEditing(false) so the component reflects the new plannedStartOn immediately (use toApiDatetime/editValue conversion helpers as appropriate), or alternatively clear editValue if you prefer fresh value from refetch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/customer-portal/webapp/src/models/responses.ts`:
- Around line 340-357: Add a missing type alias so the hook can import the PATCH
response type: declare and export PatchChangeRequestResponse as an alias of
ChangeRequestDetails (i.e., export type PatchChangeRequestResponse =
ChangeRequestDetails;) so that consumers like usePatchChangeRequest can import
PatchChangeRequestResponse; update exports if necessary to ensure
PatchChangeRequestResponse is exported alongside ChangeRequestDetails.
---
Nitpick comments:
In `@apps/customer-portal/webapp/src/api/usePostConversations.ts`:
- Around line 68-77: Move the clearTimeout(timeoutId) call into a finally block
to guarantee the AbortController timeout is cleared even if authFetch or later
code throws; wrap the authFetch call (and any subsequent logic that uses
controller/timeoutId) in try { ... } catch { ... } finally {
clearTimeout(timeoutId); } so the timeout is always cleaned up while keeping the
controller.signal passed to authFetch unchanged.
- Around line 47-103: Add the same timeout/abort protection used in
usePostConversations to the usePostConversationMessages hook: create an
AbortController before the fetch, start a setTimeout that calls
controller.abort() after 40_000 ms (store id in timeoutId), pass
controller.signal into the authFetch call, clearTimeout(timeoutId) after the
response is received, and update the catch block to detect AbortError
(error.name === "AbortError") and throw a new Error("Conversations API request
timed out after 40 seconds") while preserving existing logging; look for
function/usePostConversationMessages and the authFetch call to insert these
changes and mirror the same error-handling pattern (timeoutId, controller,
40_000, AbortError) used in usePostConversations.
In
`@apps/customer-portal/webapp/src/components/support/change-requests/ScheduledMaintenanceWindowCard.tsx`:
- Around line 72-79: The local formatDuration function duplicates centralized
logic—replace its usage with the shared formatDuration from `@utils/support.ts`
(import it at the top) and update the durationText computation to call that
shared function (handle string/number inputs per the utility); alternatively, if
you need a different "X hours Y minutes" wording, rename the local function
(e.g., formatDurationVerbose) to avoid colliding with the shared symbol and keep
its usage limited to ScheduledMaintenanceWindowCard.
- Around line 147-164: In handleUpdate, after a successful patchMutation you
should also update the local editValue to the submitted apiValue (or map back to
display format) to avoid showing stale data when re-entering edit mode;
specifically, in the patchMutation.onSuccess callback (in
ScheduledMaintenanceWindowCard, inside handleUpdate)
setEditValue(editValueOrConvertedBack) alongside setIsEditing(false) so the
component reflects the new plannedStartOn immediately (use
toApiDatetime/editValue conversion helpers as appropriate), or alternatively
clear editValue if you prefer fresh value from refetch.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 55e72bc1-0aa5-4a4e-8cc1-5b82ae12cf41
📒 Files selected for processing (8)
apps/customer-portal/webapp/src/api/usePatchChangeRequest.tsapps/customer-portal/webapp/src/api/usePostConversations.tsapps/customer-portal/webapp/src/components/support/change-requests/ScheduledMaintenanceWindowCard.tsxapps/customer-portal/webapp/src/components/support/change-requests/__tests__/ScheduledMaintenanceWindowCard.test.tsxapps/customer-portal/webapp/src/models/requests.tsapps/customer-portal/webapp/src/models/responses.tsapps/customer-portal/webapp/src/pages/ChangeRequestDetailsPage.tsxapps/customer-portal/webapp/src/utils/support.ts
d8e77c7
into
wso2-open-operations:customer-portal-milestone-1
Description
This pull request introduces a new "Scheduled Maintenance Window" card for change requests, allowing users to view and inline-edit the planned start date of a maintenance window. It also adds the supporting API hook, updates the data models to better match backend responses, and improves error handling and request timeout for conversation posting. Comprehensive unit tests are included for the new card component.
Feature: Scheduled Maintenance Window Card
ScheduledMaintenanceWindowCardcomponent for displaying and editing the planned start, end, and duration of a change request's maintenance window, with inline editing and error handling.ChangeRequestDetailsPageto display scheduled maintenance information to users. [1] [2]ScheduledMaintenanceWindowCardto verify rendering, edit functionality, and UI elements.API and Data Model Updates
usePatchChangeRequesthook to support PATCH requests for updating a change request's planned start date, including error handling and query invalidation for cache consistency.PatchChangeRequestRequesttype for PATCH payloads, and updatedChangeRequestItemandChangeRequestDetailsresponse types to reflect new/nullable fields and backend changes. [1] [2] [3]Improvements and Bug Fixes
formatDurationutility to handlestringornumberinputs, ensuring robust display of durations from API responses.usePostConversationsto include a 40-second timeout usingAbortController, with user-friendly error messages on timeout and improved error logging. [1] [2] [3]Summary by CodeRabbit
Release Notes
New Features
Improvements