Skip to content

[Customer portal] [web] Introduce Scheduled Maintenance Window card and PATCH API for change request updates - #303

Merged
Rashmika998 merged 8 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/add-user-profile
Mar 6, 2026
Merged

Rashmika998 merged 8 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/add-user-profile

Conversation

@dileepapeiris

@dileepapeiris dileepapeiris commented Mar 6, 2026 •

Copy link
Copy Markdown
Contributor

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.

image image

Feature: Scheduled Maintenance Window Card

  • Added ScheduledMaintenanceWindowCard component for displaying and editing the planned start, end, and duration of a change request's maintenance window, with inline editing and error handling.
  • Integrated the new card into the ChangeRequestDetailsPage to display scheduled maintenance information to users. [1] [2]
  • Added unit tests for ScheduledMaintenanceWindowCard to verify rendering, edit functionality, and UI elements.

API and Data Model Updates

  • Implemented usePatchChangeRequest hook to support PATCH requests for updating a change request's planned start date, including error handling and query invalidation for cache consistency.
  • Added PatchChangeRequestRequest type for PATCH payloads, and updated ChangeRequestItem and ChangeRequestDetails response types to reflect new/nullable fields and backend changes. [1] [2] [3]

Improvements and Bug Fixes

  • Improved formatDuration utility to handle string or number inputs, ensuring robust display of durations from API responses.
  • Enhanced usePostConversations to include a 40-second timeout using AbortController, with user-friendly error messages on timeout and improved error logging. [1] [2] [3]

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a scheduled maintenance window card to change request details pages, displaying planned start, planned end, and duration.
    • Enabled inline editing of planned start dates with Update and Cancel actions.
  • Improvements

    • Enhanced API request timeout management and error handling for better reliability and user feedback.

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.
@dileepapeiris dileepapeiris self-assigned this Mar 6, 2026
@dileepapeiris dileepapeiris added Type/New Feature Represents a request or task for a new feature Type/Improvement Marks enhancements or improvements to existing features Type/Task General task that does not fit into other categories Type/UX Refers to user experience-related tasks or issues App/Customer Portal Area/Frontend Platform/Web labels Mar 6, 2026
@coderabbitai

coderabbitai Bot commented Mar 6, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Introduces a new React Hook (usePatchChangeRequest) for updating change requests, adds a ScheduledMaintenanceWindowCard component for editing scheduled maintenance windows with inline date inputs, updates API request/response types, and enhances error handling in an existing conversation hook with timeout protection.

Changes

Cohort / File(s) Summary
New PATCH Mutation Hook
apps/customer-portal/webapp/src/api/usePatchChangeRequest.ts
Introduces usePatchChangeRequest hook using Tanstack React Query for PATCH requests; includes authentication validation, 40-second timeout handling via AbortController, detailed error logging, and invalidates change request details query on success.
Enhanced API Hook
apps/customer-portal/webapp/src/api/usePostConversations.ts
Adds try-catch wrapper with 40-second timeout via AbortController to throttle API requests; centralizes error logging and timeout-specific error messaging while maintaining existing validation and success paths.
New Component with Tests
apps/customer-portal/webapp/src/components/support/change-requests/ScheduledMaintenanceWindowCard.tsx, .../ScheduledMaintenanceWindowCard.test.tsx
Renders scheduled maintenance window card with Planned Start, Planned End, and Duration fields; supports inline editing of Planned Start date with Update/Cancel actions; integrates with usePatchChangeRequest for mutations and error handling via ErrorBanner context.
Request/Response Type Updates
apps/customer-portal/webapp/src/models/requests.ts, apps/customer-portal/webapp/src/models/responses.ts
Adds PatchChangeRequestRequest interface with plannedStartOn field; updates ChangeRequestItem to include assignedEngineer object, nullify case.number, make deployment.number optional, and change duration from number to string|null; removes inherited fields from ChangeRequestDetails.
Utility & Page Integration
apps/customer-portal/webapp/src/utils/support.ts, apps/customer-portal/webapp/src/pages/ChangeRequestDetailsPage.tsx
Broadens formatDuration signature to accept `number

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested labels

Type/New Feature, Type/Improvement, App/Customer Portal, Area/Frontend, Platform/Web

Suggested reviewers

  • Rashmika998
  • cloby99
  • v15a1

Poem

🐰 A maintenance window starts to glow,
With patch requests swift and dates all in flow,
Edit the start time, no refresh needed,
The cache invalidates, our hopes are exceeded!
React Query whispers, "Your changes are here,"
A scheduled update—chef's kiss—crystal clear! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive PR description is comprehensive with feature details, component additions, API changes, and improvements documented with screenshot references, but is missing structured sections from the template. Add structured sections from the template: Purpose/Issues, Goals, Release Note, Documentation, Training, Certification, Marketing, Security Checks, Test Environment, and Learning sections with specific information about this feature.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The PR title accurately summarizes the main changes: introducing a Scheduled Maintenance Window card component and a PATCH API for updating change requests, which aligns with the primary objectives and file modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🔴 Critical

Add missing PatchChangeRequestResponse type definition.

The usePatchChangeRequest.ts hook imports PatchChangeRequestResponse from @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 ChangeRequestDetails structure. Add this type alias to responses.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 using finally for clearTimeout to ensure cleanup in edge cases.

If an exception were thrown between authFetch and clearTimeout (e.g., from a future code modification), the timeout would continue running. Moving clearTimeout to a finally block 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 to usePostConversationMessages.

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 centralized formatDuration utility.

This local formatDuration function 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 durationText computation 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 resetting editValue after successful update.

When the mutation succeeds, setIsEditing(false) is called, but editValue retains the old value. If the user clicks edit again before the query cache is invalidated and refetched, they might see stale data. Consider updating editValue with 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.invalidateQueries triggers a refetch and the component will re-render with updated changeRequest.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

📥 Commits

Reviewing files that changed from the base of the PR and between 4d78d94 and a2582af.

📒 Files selected for processing (8)
  • apps/customer-portal/webapp/src/api/usePatchChangeRequest.ts
  • apps/customer-portal/webapp/src/api/usePostConversations.ts
  • apps/customer-portal/webapp/src/components/support/change-requests/ScheduledMaintenanceWindowCard.tsx
  • apps/customer-portal/webapp/src/components/support/change-requests/__tests__/ScheduledMaintenanceWindowCard.test.tsx
  • apps/customer-portal/webapp/src/models/requests.ts
  • apps/customer-portal/webapp/src/models/responses.ts
  • apps/customer-portal/webapp/src/pages/ChangeRequestDetailsPage.tsx
  • apps/customer-portal/webapp/src/utils/support.ts

@dileepapeiris dileepapeiris changed the title Feat/add user profile [Customer portal] [web] Introduce Scheduled Maintenance Window card and PATCH API for change request updates Mar 6, 2026
@Rashmika998
Rashmika998 merged commit d8e77c7 into wso2-open-operations:customer-portal-milestone-1 Mar 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

App/Customer Portal Area/Frontend Platform/Web Type/Improvement Marks enhancements or improvements to existing features Type/New Feature Represents a request or task for a new feature Type/Task General task that does not fit into other categories Type/UX Refers to user experience-related tasks or issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants