Skip to content

EDM-2060 Backup and restore devices#347

Merged
celdrake merged 5 commits into
flightctl:mainfrom
celdrake:EDM-2060-backup-and-restore-devices
Sep 25, 2025
Merged

EDM-2060 Backup and restore devices#347
celdrake merged 5 commits into
flightctl:mainfrom
celdrake:EDM-2060-backup-and-restore-devices

Conversation

@celdrake

@celdrake celdrake commented Sep 23, 2025

Copy link
Copy Markdown
Collaborator

Backup and restore devices working both for RHEM and RHEM for ACM.

We're missing some design decisions, so there may be related enhancements in the future.
For example, we need to know what is displayed for users that don't have permission to resume devices.

Summary by CodeRabbit

  • New Features

    • Per-device and bulk "Resume" workflows: guided modals, confirmations, outcomes, and resume actions in device, fleet, and list UIs.
    • System restore banners/alerts (global and contextual) highlighting pending-sync and suspended devices with resume triggers.
  • Enhancements

    • Resume flow surfaced across Details, Fleet, Devices, and Overview pages; new resume permission respected.
    • Status visuals updated: "Pending sync" and "Suspended" states with distinct colors; charts exclude restore-only statuses.
  • Localization

    • Extensive English translations supporting resume and system-restore messaging.

@coderabbitai

coderabbitai Bot commented Sep 23, 2025

Copy link
Copy Markdown

Walkthrough

Replaces AppContext provider alias with a component wrapping SystemRestoreProvider; adds SystemRestore context and banners, resume UI (single/mass), types/i18n for resume/events, RBAC and helpers; generalizes POST/PUT fetch helpers with request/response generics; status/chart color support.

Changes

Cohort / File(s) Summary of Changes
AppContext & Provider wiring
apps/ocp-plugin/src/components/AppContext/AppContext.tsx, apps/standalone/src/app/index.tsx, libs/ui-components/src/hooks/useSystemRestoreContext.tsx
Replace exported provider alias with OCPPluginAppContext component that wraps children with SystemRestoreProvider; add SystemRestoreProvider and useSystemRestore; wrap app roots.
Fetch / API helpers (generics)
apps/ocp-plugin/src/hooks/useFetch.ts, apps/standalone/src/app/hooks/useFetch.ts, apps/ocp-plugin/src/utils/apiCalls.ts, apps/standalone/src/app/utils/apiCalls.ts, libs/ansible/src/utils/apiCalls.ts, libs/ansible/src/hooks/useFetch.ts, libs/ui-components/src/hooks/useAppContext.tsx
Generalize post/put/putOrPostData to <TRequest, TResponse = TRequest> / <TRequest>; add putOrPostData; update useFetch post/put/patch signatures and parameter names to data/patches and delegate to new helpers.
System restore banners & alerts
libs/ui-components/src/components/SystemRestore/*, libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.css, libs/ui-components/src/components/SystemRestore/PendingSyncDevicesAlert.tsx, libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx
Add SystemRestoreBanners, GlobalSystemRestoreBanners, PendingSyncDevicesAlert, SuspendedDevicesAlert and CSS; conditional rendering by summaryStatus/pending/suspended counts; modals for resume flows.
Resume modals & mass flow
libs/ui-components/src/components/modals/ResumeDevicesModal/*, libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/*
Add ResumeDevicesModal, MassResumeDevicesModal, confirmation dialog; implement POST to deviceactions/resume; handle counts, success/warnings/errors and onClose callbacks.
Resume actions & list/detail integration
libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx, libs/ui-components/src/components/ListPage/ListPageActions.tsx, libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx, libs/ui-components/src/components/Device/DevicesPage/*, libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx, libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx
Add useResumeAction and useResumeListAction; wire resume actions into detail dropdowns, rows, tables, banners; pass refetchDevices into child table; include resume modals in modal compositions.
DetailsPage banner API & fleet banners
libs/ui-components/src/components/DetailsPage/DetailsPage.tsx, libs/ui-components/src/components/Fleet/FleetDetails/FleetRestoreBanner.tsx, libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx, libs/ui-components/src/components/Fleet/FleetsPage.tsx
DetailsPage gains optional banner?: React.ReactNode; add FleetRestoreBanner and render banners in Fleet details and fleets list; adjust YAML route filename.
Status items, charts & color handling
libs/ui-components/src/utils/status/common.ts, libs/ui-components/src/utils/status/devices.ts, libs/ui-components/src/components/charts/utils.ts, libs/ui-components/src/components/OverviewPage/Cards/Status/DeviceStatusChart.tsx, libs/ui-components/src/components/Fleet/FleetDetails/FleetDevicesCharts.tsx, libs/ui-components/src/components/Status/StatusDisplay.tsx
Add customColor?: string to StatusItem; relabel statuses ("Pending sync", "Suspended") with custom colors/icons; add getOverviewDeviceStatusItems and exclude system-restore statuses from overview charts; prefer customColor in chart color mapping; propagate customColor into StatusDisplay.
RBAC, device utils & queries
libs/ui-components/src/types/rbac.ts, libs/ui-components/src/utils/devices.ts, libs/ui-components/src/utils/query.ts, libs/ui-components/src/hooks/useAccessReview.ts
Add RESOURCE.DEVICE_RESUME; add isDeviceResumable and getResumeDisabledReason; add queries to count suspended devices (by labels / all); guard state updates in useAccessReview against post-unmount updates.
Types & models
libs/types/index.ts, libs/types/models/DeviceResumeRequest.ts, libs/types/models/DeviceResumeResponse.ts, libs/types/models/InternalTaskPermanentlyFailedDetails.ts, libs/types/models/Event.ts, libs/types/models/EventDetails.ts, libs/types/models/PatchRequest.ts
Add DeviceResumeRequest and DeviceResumeResponse; add InternalTaskPermanentlyFailedDetails; extend Event.reason with DeviceConflictResolved and InternalTaskPermanentlyFailed; include new detail type in EventDetails; add 'test' op to PatchRequest.
Translations (i18n)
libs/i18n/locales/en/translation.json
Add many resume/system-restore-related i18n keys for single and bulk resume flows, confirmations, irreversible-action warnings, success/warning/failure texts, and suspended/pending status strings (pluralized variants).
Forms & validations; CSS
libs/ui-components/src/components/form/validations.ts, libs/ui-components/src/components/form/FilterSelect.css
Add createMassResumeValidationSchema (Yup) for mass-resume form; remove fixed height from filter select group; minor CSS adjustments.
UI actions & list helpers
libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx, libs/ui-components/src/components/ListPage/ListPageActions.tsx
Add ResumeActionProps type and export useResumeAction / useResumeListAction; integrate action/modals into existing action hook patterns.
Misc (charts utils, minor refs)
libs/ui-components/src/components/charts/utils.ts, libs/ui-components/src/hooks/useAppContext.tsx, other small imports/usage updates
Prefer per-item custom color in charts; update AppContext fetch post/put generics to align with new helpers; small import/usage adjustments across components.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as Page / Action / Banner
  participant Modal as Resume Modal
  participant Fetch as useFetch / postData
  participant API as Backend

  User->>UI: Click "Resume" (single/mass)
  UI->>Modal: Open ResumeDevicesModal / MassResumeDevicesModal
  Modal->>Fetch: postData("deviceactions/resume", selector)
  Fetch->>API: HTTP POST { selector }
  API-->>Fetch: 200 { resumedDevices }
  Fetch-->>Modal: response
  Modal-->>UI: onClose(hasResumed=true)
  UI->>UI: onResumeComplete -> refetch data
  UI-->>User: Show success/warning/error
Loading
sequenceDiagram
  autonumber
  participant App as App Root
  participant SR as SystemRestoreProvider
  participant RBAC as useAccessReview
  participant DS as useDevicesSummary
  participant Consumers as Banners/Pages

  App->>SR: Mount provider
  SR->>RBAC: Check device listing permission
  alt allowed
    SR->>DS: Fetch devices summary
    DS-->>SR: summaryStatus
    SR->>Consumers: Provide summaryStatus, counts, isLoading:false
  else denied
    SR->>Consumers: Provide isLoading:false, zero counts
  end
  Consumers->>Consumers: Conditionally render banners/alerts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • rawagner

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “EDM-2060 Backup and restore devices” implies both backup and restore functionality but the changeset primarily implements device resume (restore) flows, system restore context, and related UI banners without adding any backup logic, making the title misleading and incomplete. Please update the title to accurately reflect the core changes, for example “Implement device resume and system restore UI flows,” and remove the unsupported backup reference and ticket identifier to improve clarity.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11c28fa and 8e46a7a.

📒 Files selected for processing (1)
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: integration-tests
  • GitHub Check: Lint
  • GitHub Check: Build
  • GitHub Check: Build ocp plugin

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.

fetch: { checkPermissions },
} = useAppContext();
React.useEffect(() => {
let isMounted = true;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There was an error in the console due to the hook not cleaning-up properly when it was unmounted.

@celdrake celdrake force-pushed the EDM-2060-backup-and-restore-devices branch 2 times, most recently from a124f05 to 02c660e Compare September 23, 2025 10:37
@celdrake celdrake force-pushed the EDM-2060-backup-and-restore-devices branch from 02c660e to 98b53ee Compare September 24, 2025 10:24
@celdrake celdrake marked this pull request as ready for review September 24, 2025 10:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (14)
libs/ui-components/src/components/form/validations.ts (1)

706-724: Consider simplifying the lazy validation with a direct conditional approach.

The Yup.lazy() wrapper adds complexity that may not be necessary here. Consider using a direct .when() approach for better readability and maintainability.

Apply this diff to simplify the labels validation:

-    labels: Yup.lazy(() =>
-      Yup.mixed().test('labels-validation', function (labels) {
-        const parent = this.parent as { mode?: string };
-        if (parent.mode !== 'labels') {
-          return true;
-        }
-        if (!Array.isArray(labels) || labels.length === 0) {
-          return this.createError({ message: t('At least one label is required') });
-        }
-        try {
-          validLabelsSchema(t).validateSync(labels);
-          return true;
-        } catch (error: unknown) {
-          const errorMessage = error instanceof Yup.ValidationError ? error.message : t('Invalid labels provided');
-          return this.createError({ message: errorMessage });
-        }
-      }),
-    ),
+    labels: Yup.array().when('mode', ([mode]) => {
+      if (mode === 'labels') {
+        return validLabelsSchema(t).min(1, t('At least one label is required'));
+      }
+      return Yup.array();
+    }),
libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx (1)

25-25: Consider using primary variant instead of danger for resume action.

The resume action uses variant="danger" which typically indicates destructive actions. Since resuming devices is a recovery action rather than destructive, consider using variant="primary" instead.

-        <Button key="confirm" variant="danger" onClick={() => onClose(true)}>
+        <Button key="confirm" variant="primary" onClick={() => onClose(true)}>
libs/ui-components/src/components/ListPage/ListPageActions.tsx (2)

14-14: Consider more descriptive type name for clarity.

The type name DeviceOnlyResourceType is somewhat ambiguous. Since this type specifically represents a 'Device' literal, consider renaming it to DeviceResourceLiteral or simply using the literal type directly where needed for better clarity.


122-128: Consider simplifying the onClose logic.

The conditional call to onResumeComplete could be simplified using optional chaining more consistently.

  const onClose = (hasResumed?: boolean) => {
    setDeviceId(undefined);
    setDeviceName(undefined);
-    if (hasResumed) {
-      onResumeComplete?.();
-    }
+    hasResumed && onResumeComplete?.();
  };
libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (2)

20-20: Remove default value in destructuring since it's redundant.

The expectedCount = 1 default in the destructuring is unnecessary since expectedCount is a required prop in the interface.

-const ResumeDevicesModal = ({ mode, title, selector, expectedCount = 1, onClose }: ResumeDeviceModalProps) => {
+const ResumeDevicesModal = ({ mode, title, selector, expectedCount, onClose }: ResumeDeviceModalProps) => {

26-26: Consider more explicit boolean conversion.

The hasResumed calculation could be more explicit about the conditions being checked.

-  const hasResumed = Boolean(resumedCount && resumedCount > 0);
+  const hasResumed = resumedCount !== undefined && resumedCount > 0;
libs/ui-components/src/utils/status/devices.ts (1)

122-124: Consider adding type guard for better type safety.

The type assertion could be avoided with a more explicit type guard.

  const orderedPriorityItems = priorityStatuses
    .map((status) => priorityItems.find((item) => item.id === status))
-    .filter(Boolean) as StatusItem<DeviceSummaryStatusType>[];
+    .filter((item): item is StatusItem<DeviceSummaryStatusType> => item !== undefined);
libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (4)

86-87: Improve condition readability.

The boolean logic could be clearer about what constitutes success.

-  const hasResumedAtLeastOne = Boolean(resumedCount !== undefined && resumedCount > 0);
-  const hasResumedAllExpected = hasResumedAtLeastOne && resumedCount === deviceCountNum;
+  const hasResumedAtLeastOne = resumedCount !== undefined && resumedCount > 0;
+  const hasResumedAllExpected = resumedCount === deviceCountNum && deviceCountNum > 0;

138-140: Add clarifying comment for API behavior.

The TODO comment indicates this is a temporary workaround. Consider tracking this technical debt properly.

The comment indicates the API should have an explicit way to resume all suspended devices. Would you like me to create an issue to track this API enhancement requirement?


310-312: Consider extracting device selector formatting logic.

The device selector string formatting logic is duplicated here and could be extracted to a utility function for reusability.

+// In a utils file
+export const formatDeviceSelector = (matchLabels: Record<string, string> = {}): string => {
+  return Object.entries(matchLabels)
+    .map(([key, value]) => (value ? `${key}=${value}` : key))
+    .join(',');
+};

// Then use it here:
-      const deviceSelectorStr = Object.entries(fleet.spec?.selector?.matchLabels || {})
-        .map(([key, value]) => (value ? `${key}=${value}` : key))
-        .join(',');
+      const deviceSelectorStr = formatDeviceSelector(fleet.spec?.selector?.matchLabels);

451-454: Remove empty onSubmit handler.

The empty onSubmit handler in Formik suggests the form submission is handled elsewhere. Consider adding a comment explaining why this is intentionally empty.

      onSubmit={() => {
-        // This will be handled by the inner component
+        // Form submission is handled by performResume() in the inner component
+        // This empty handler satisfies Formik's required prop
       }}
libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx (1)

96-98: Consider extracting selector creation to a utility.

The device selector creation logic could be extracted to maintain consistency across the codebase.

+// In a utils file
+export const createDeviceSelector = (deviceId: string) => ({
+  fieldSelector: `metadata.name=${deviceId}`,
+});

// Then use it here:
-  const resumeSelector = {
-    fieldSelector: `metadata.name=${deviceId}`,
-  };
+  const resumeSelector = createDeviceSelector(deviceId);
libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (1)

43-43: Consider using classnames utility for conditional CSS classes.

The manual string concatenation for CSS classes could be simplified using a utility like classnames for better maintainability.

If the classnames package is available in your project, consider:

+import classNames from 'classnames';
// ...
   return (
-    <div className={`fctl-system-restore-banners ${className}`}>
+    <div className={classNames('fctl-system-restore-banners', className)}>
libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx (1)

101-107: Consider extracting device summary logic to a utility function.

The device summary status calculation could be extracted into a reusable utility function for better maintainability and reuse across other components.

Consider creating a utility function:

// In a utils file
export const getDeviceSummaryStatus = (device: Device) => {
  const status = device?.status?.summary.status;
  return {
    [DeviceSummaryStatusType.DeviceSummaryStatusConflictPaused]:
      status === DeviceSummaryStatusType.DeviceSummaryStatusConflictPaused ? 1 : 0,
    [DeviceSummaryStatusType.DeviceSummaryStatusAwaitingReconnect]:
      status === DeviceSummaryStatusType.DeviceSummaryStatusAwaitingReconnect ? 1 : 0,
  };
};

// Then use it here:
const deviceSummary = device ? getDeviceSummaryStatus(device) : undefined;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9fc35a and 98b53ee.

📒 Files selected for processing (48)
  • apps/ocp-plugin/src/components/AppContext/AppContext.tsx (2 hunks)
  • apps/ocp-plugin/src/hooks/useFetch.ts (1 hunks)
  • apps/ocp-plugin/src/utils/apiCalls.ts (2 hunks)
  • apps/standalone/src/app/hooks/useFetch.ts (1 hunks)
  • apps/standalone/src/app/index.tsx (2 hunks)
  • apps/standalone/src/app/utils/apiCalls.ts (2 hunks)
  • libs/ansible/src/utils/apiCalls.ts (2 hunks)
  • libs/i18n/locales/en/translation.json (8 hunks)
  • libs/types/index.ts (2 hunks)
  • libs/types/models/DeviceResumeRequest.ts (1 hunks)
  • libs/types/models/DeviceResumeResponse.ts (1 hunks)
  • libs/types/models/Event.ts (2 hunks)
  • libs/types/models/EventDetails.ts (1 hunks)
  • libs/types/models/InternalTaskPermanentlyFailedDetails.ts (1 hunks)
  • libs/types/models/PatchRequest.ts (1 hunks)
  • libs/ui-components/src/components/DetailsPage/DetailsPage.tsx (3 hunks)
  • libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx (3 hunks)
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx (7 hunks)
  • libs/ui-components/src/components/Device/DevicesPage/DevicesPage.tsx (1 hunks)
  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx (4 hunks)
  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx (8 hunks)
  • libs/ui-components/src/components/Events/useEvents.ts (1 hunks)
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx (3 hunks)
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDevicesCharts.tsx (3 hunks)
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetRestoreBanner.tsx (1 hunks)
  • libs/ui-components/src/components/Fleet/FleetsPage.tsx (2 hunks)
  • libs/ui-components/src/components/ListPage/ListPageActions.tsx (4 hunks)
  • libs/ui-components/src/components/OverviewPage/Cards/Status/DeviceStatusChart.tsx (2 hunks)
  • libs/ui-components/src/components/OverviewPage/Overview.tsx (1 hunks)
  • libs/ui-components/src/components/Status/StatusDisplay.tsx (5 hunks)
  • libs/ui-components/src/components/SystemRestore/PendingSyncDevicesAlert.tsx (1 hunks)
  • libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx (1 hunks)
  • libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.css (1 hunks)
  • libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (1 hunks)
  • libs/ui-components/src/components/charts/utils.ts (1 hunks)
  • libs/ui-components/src/components/form/FilterSelect.css (1 hunks)
  • libs/ui-components/src/components/form/validations.ts (1 hunks)
  • libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (1 hunks)
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (1 hunks)
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx (1 hunks)
  • libs/ui-components/src/hooks/useAccessReview.ts (1 hunks)
  • libs/ui-components/src/hooks/useAppContext.tsx (1 hunks)
  • libs/ui-components/src/hooks/useSystemRestoreContext.tsx (1 hunks)
  • libs/ui-components/src/types/rbac.ts (1 hunks)
  • libs/ui-components/src/utils/devices.ts (2 hunks)
  • libs/ui-components/src/utils/query.ts (2 hunks)
  • libs/ui-components/src/utils/status/common.ts (1 hunks)
  • libs/ui-components/src/utils/status/devices.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-02-11T10:20:50.496Z
Learnt from: celdrake
PR: flightctl/flightctl-ui#208
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx:30-44
Timestamp: 2025-02-11T10:20:50.496Z
Learning: In the FlightCtl UI, error handling for device decommissioning is implemented in the modal component created by `useDecommissionAction`. Failed PUT requests show an error in the modal with retry/close options.

Applied to files:

  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx
  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx
📚 Learning: 2025-02-11T10:24:15.684Z
Learnt from: celdrake
PR: flightctl/flightctl-ui#208
File: libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx:142-150
Timestamp: 2025-02-11T10:24:15.684Z
Learning: In the FlightCtl UI, device tables (`EnrolledDevicesTable` and `DecommissionedDevicesTable`) are swapped based on the `onlyDecommissioned` state, so their respective switches can have hardcoded values (`false` and `true`).

Applied to files:

  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx
📚 Learning: 2025-03-19T08:55:03.335Z
Learnt from: celdrake
PR: flightctl/flightctl-ui#240
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceApplications.tsx:44-50
Timestamp: 2025-03-19T08:55:03.335Z
Learning: In FlightCtl, the name field is required for Inline applications but optional for Image-based applications. This requirement is enforced by validation logic even though TypeScript type definitions might not explicitly show this distinction.

Applied to files:

  • libs/ui-components/src/utils/query.ts
🧬 Code graph analysis (34)
libs/types/models/DeviceResumeResponse.ts (1)
libs/types/index.ts (1)
  • DeviceResumeResponse (54-54)
libs/ui-components/src/components/Fleet/FleetsPage.tsx (1)
libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (1)
  • GlobalSystemRestoreBanners (61-82)
libs/types/models/DeviceResumeRequest.ts (1)
libs/types/index.ts (1)
  • DeviceResumeRequest (53-53)
libs/ui-components/src/components/Fleet/FleetDetails/FleetRestoreBanner.tsx (3)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (1)
  • SystemRestoreBanners (25-56)
libs/ui-components/src/utils/labels.ts (2)
  • fromAPILabel (7-12)
  • labelToExactApiMatchString (25-25)
libs/ui-components/src/components/SystemRestore/PendingSyncDevicesAlert.tsx (2)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/hooks/useAppContext.tsx (1)
  • useAppContext (122-122)
libs/ui-components/src/hooks/useSystemRestoreContext.tsx (2)
libs/ui-components/src/hooks/useAccessReview.ts (1)
  • useAccessReview (8-49)
libs/ui-components/src/components/Device/DevicesPage/useDevices.ts (1)
  • useDevicesSummary (90-103)
libs/types/models/InternalTaskPermanentlyFailedDetails.ts (1)
libs/types/models/Event.ts (1)
  • Event (12-41)
libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx (3)
libs/ui-components/src/components/ListPage/types.ts (1)
  • ListAction (3-3)
libs/ui-components/src/utils/tooltip.ts (1)
  • getDisabledTooltipProps (1-2)
libs/ui-components/src/utils/devices.ts (1)
  • getResumeDisabledReason (33-38)
libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (3)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
apps/standalone/src/app/hooks/useFetch.ts (1)
  • useFetch (5-46)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
libs/types/models/EventDetails.ts (2)
libs/types/models/InternalTaskFailedDetails.ts (1)
  • InternalTaskFailedDetails (6-20)
libs/types/models/InternalTaskPermanentlyFailedDetails.ts (1)
  • InternalTaskPermanentlyFailedDetails (6-20)
apps/ocp-plugin/src/components/AppContext/AppContext.tsx (2)
libs/ui-components/src/hooks/useAppContext.tsx (2)
  • AppContextProps (48-86)
  • AppContext (88-120)
libs/ui-components/src/hooks/useSystemRestoreContext.tsx (1)
  • SystemRestoreProvider (20-44)
libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (9)
libs/ui-components/src/types/extraTypes.ts (1)
  • FlightCtlLabel (15-19)
libs/ui-components/src/utils/labels.ts (2)
  • fromAPILabel (7-12)
  • labelToExactApiMatchString (25-25)
apps/ocp-plugin/src/hooks/useFetch.ts (1)
  • useFetch (39-93)
apps/standalone/src/app/hooks/useFetch.ts (1)
  • useFetch (5-46)
libs/ui-components/src/components/Fleet/useFleets.ts (1)
  • useFleets (87-104)
libs/ui-components/src/utils/query.ts (1)
  • commonQueries (35-99)
libs/ui-components/src/utils/api.ts (1)
  • getApiListCount (45-45)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
libs/ui-components/src/components/form/validations.ts (1)
  • createMassResumeValidationSchema (699-724)
libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx (2)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/utils/tooltip.ts (1)
  • getDisabledTooltipProps (1-2)
libs/ui-components/src/utils/status/devices.ts (2)
libs/types/index.ts (1)
  • DeviceSummaryStatusType (59-59)
libs/ui-components/src/utils/status/common.ts (1)
  • StatusItem (18-24)
libs/ansible/src/utils/apiCalls.ts (2)
apps/ocp-plugin/src/utils/apiCalls.ts (2)
  • postData (97-98)
  • putData (100-101)
apps/standalone/src/app/utils/apiCalls.ts (2)
  • postData (128-129)
  • putData (131-132)
libs/ui-components/src/components/OverviewPage/Overview.tsx (3)
libs/ui-components/src/hooks/useAlertsEnabled.ts (1)
  • useAlertsEnabled (10-50)
libs/ui-components/src/hooks/useAccessReview.ts (1)
  • useAccessReview (8-49)
libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (1)
  • GlobalSystemRestoreBanners (61-82)
libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx (3)
libs/ui-components/src/components/ListPage/ListPageActions.tsx (1)
  • useResumeListAction (105-145)
libs/ui-components/src/hooks/useAccessReview.ts (1)
  • useAccessReview (8-49)
libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (1)
  • GlobalSystemRestoreBanners (61-82)
libs/ui-components/src/components/charts/utils.ts (1)
libs/ui-components/src/utils/status/common.ts (1)
  • getDefaultStatusColor (49-72)
libs/ui-components/src/components/ListPage/ListPageActions.tsx (3)
libs/ui-components/src/components/ListPage/types.ts (3)
  • ListActionProps (10-13)
  • ListActionResult (5-8)
  • ListAction (3-3)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/utils/tooltip.ts (1)
  • getDisabledTooltipProps (1-2)
apps/standalone/src/app/utils/apiCalls.ts (2)
apps/ocp-plugin/src/utils/apiCalls.ts (2)
  • postData (97-98)
  • putData (100-101)
libs/ansible/src/utils/apiCalls.ts (2)
  • postData (44-49)
  • putData (51-56)
apps/standalone/src/app/index.tsx (1)
libs/ui-components/src/hooks/useSystemRestoreContext.tsx (1)
  • SystemRestoreProvider (20-44)
apps/standalone/src/app/hooks/useFetch.ts (2)
apps/ocp-plugin/src/utils/apiCalls.ts (2)
  • postData (97-98)
  • putData (100-101)
apps/standalone/src/app/utils/apiCalls.ts (2)
  • postData (128-129)
  • putData (131-132)
libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (3)
libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx (1)
  • ResumeMode (10-10)
libs/ui-components/src/components/SystemRestore/PendingSyncDevicesAlert.tsx (1)
  • PendingSyncDevicesAlert (13-36)
libs/ui-components/src/hooks/useSystemRestoreContext.tsx (1)
  • useSystemRestore (46-54)
libs/ui-components/src/hooks/useAccessReview.ts (1)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
libs/ui-components/src/components/OverviewPage/Cards/Status/DeviceStatusChart.tsx (2)
libs/types/index.ts (1)
  • DeviceSummaryStatusType (59-59)
libs/ui-components/src/utils/status/devices.ts (1)
  • getOverviewDeviceStatusItems (104-127)
libs/ui-components/src/components/Fleet/FleetDetails/FleetDevicesCharts.tsx (1)
libs/ui-components/src/utils/status/devices.ts (1)
  • getOverviewDeviceStatusItems (104-127)
libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx (1)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/utils/devices.ts (1)
libs/types/index.ts (1)
  • DeviceSummaryStatusType (59-59)
libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx (1)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
apps/ocp-plugin/src/utils/apiCalls.ts (2)
apps/standalone/src/app/utils/apiCalls.ts (2)
  • postData (128-129)
  • putData (131-132)
libs/ansible/src/utils/apiCalls.ts (2)
  • postData (44-49)
  • putData (51-56)
libs/ui-components/src/utils/query.ts (3)
libs/ui-components/src/types/extraTypes.ts (1)
  • FlightCtlLabel (15-19)
libs/ui-components/src/utils/labels.ts (1)
  • labelToExactApiMatchString (25-25)
libs/types/index.ts (1)
  • DeviceSummaryStatusType (59-59)
libs/ui-components/src/components/Status/StatusDisplay.tsx (1)
libs/ui-components/src/utils/status/common.ts (2)
  • getDefaultStatusIcon (26-47)
  • getDefaultStatusColor (49-72)
apps/ocp-plugin/src/hooks/useFetch.ts (2)
apps/ocp-plugin/src/utils/apiCalls.ts (4)
  • postData (97-98)
  • putData (100-101)
  • deleteData (103-116)
  • patchData (118-135)
libs/types/models/PatchRequest.ts (1)
  • PatchRequest (5-18)
libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx (5)
libs/ui-components/src/utils/devices.ts (3)
  • isDeviceEnrolled (11-11)
  • getResumeDisabledReason (33-38)
  • getEditDisabledReason (13-21)
libs/ui-components/src/hooks/useAccessReview.ts (1)
  • useAccessReview (8-49)
libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx (1)
  • useResumeAction (85-119)
libs/ui-components/src/utils/tooltip.ts (1)
  • getDisabledTooltipProps (1-2)
libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (1)
  • SystemRestoreBanners (25-56)
🪛 Biome (2.1.2)
libs/ui-components/src/components/form/validations.ts

[error] 704-704: Do not add then to an object.

(lint/suspicious/noThenProperty)

🔇 Additional comments (71)
apps/standalone/src/app/index.tsx (2)

3-3: LGTM! Clean import addition.

The import is correctly placed and follows the established import organization pattern in the file.


23-25: Well-structured context provider integration.

The SystemRestoreProvider is properly positioned in the component hierarchy, nested within AppContext and AuthContext as expected. This ensures the SystemRestore context has access to both app configuration and authentication state, which aligns with the RBAC checks shown in the provider implementation.

The provider will now make system restore status data (pending sync and suspended device counts) available to all components throughout the application, enabling the display of system restore banners and alerts as mentioned in the PR summary.

libs/ui-components/src/hooks/useAccessReview.ts (4)

17-17: LGTM: Proper component cleanup implementation.

The isMounted ref initialization correctly addresses the cleanup issue mentioned in the past review comment.


20-20: LGTM: Early return prevents unnecessary async operations.

The early return check prevents the async operation from proceeding if the component has already unmounted, which is a good optimization.


25-27: LGTM: Comprehensive state update protection.

All state updates are properly guarded with isMounted checks, preventing React warnings about setting state on unmounted components. The implementation correctly handles all three state variables (setIsAllowed, setError, setIsLoading) in both success and error paths.

Also applies to: 29-32, 34-36


42-46: LGTM: Proper cleanup function implementation.

The cleanup function correctly sets isMounted to false to prevent any pending state updates after component unmount. This addresses the console error mentioned in the past review comments.

libs/ui-components/src/components/form/FilterSelect.css (2)

10-10: LGTM: Minor formatting improvements.

The indentation adjustments for .fctl-toggle-content__badge and the trailing closing brace improve code consistency.

Also applies to: 17-17


2-2: Verify dropdown behavior after removing fixed height from .fctl-filter-select__group.

.fctl-filter-select__group now only sets overflow: auto in libs/ui-components/src/components/form/FilterSelect.css and is used by libs/ui-components/src/components/form/FilterSelect.tsx (and by libs/ui-components/src/components/Device/DevicesPage/DeviceFilterSelect.tsx) — test the dropdowns (e.g., DevicesPage) near viewport edges and in constrained containers; if it grows beyond the viewport or breaks layout, restore a max-height (e.g., max-height: 18rem) with overflow:auto.

libs/ui-components/src/components/charts/utils.ts (1)

30-30: LGTM! Clean implementation of custom color support.

The fallback logic correctly prioritizes customColor when available, maintaining backward compatibility while enabling per-item color customization.

libs/ui-components/src/hooks/useAppContext.tsx (1)

74-79: LGTM! Generic signature update aligns with API evolution.

The updated generic signatures for post<TRequest, TResponse = TRequest> and put<TRequest> enable better type safety by allowing distinct request/response types for POST operations while maintaining uniform input typing for PUT operations.

libs/ansible/src/utils/apiCalls.ts (1)

20-26: LGTM! Consistent generic signature updates.

The changes align with the broader API evolution across the codebase:

  • putOrPostData<TRequest, TResponse = TRequest> enables distinct request/response typing
  • postData<TRequest, TResponse = TRequest> supports flexible response types
  • putData<TRequest> maintains uniform input typing

The implementation correctly delegates to the shared putOrPostData function with appropriate generic parameters.

Also applies to: 44-49, 51-56

libs/i18n/locales/en/translation.json (1)

93-94: LGTM! Comprehensive translation support for resume functionality.

The translation keys appropriately cover:

  • Single and bulk device resume actions with proper pluralization
  • Status messages (pending sync, suspended, conflict resolved)
  • Validation messages for resume criteria
  • Warning messages for configuration review
  • Success/failure feedback with device count details

The translations are well-structured and provide clear user guidance throughout the resume workflow.

Also applies to: 127-127, 361-361, 365-365, 468-470, 569-571, 575-575, 631-672, 786-794, 828-828, 839-841

libs/ui-components/src/utils/status/common.ts (1)

23-23: LGTM! Clean addition of customColor property.

The optional customColor property extends the StatusItem interface without breaking existing implementations, enabling per-item color customization as demonstrated in the chart utility changes.

libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.css (1)

1-6: LGTM! Appropriate layout styles for system restore banners.

The CSS class provides proper spacing and flexbox layout for banner components with consistent gap between items.

libs/ui-components/src/components/OverviewPage/Overview.tsx (1)

8-8: LGTM! Clean integration of system restore banners.

The changes integrate the GlobalSystemRestoreBanners component properly:

  • Import is correctly added
  • alertsEnabled initialization moved before access review (good practice)
  • Banner is rendered with appropriate padding override

The integration maintains the existing component structure while adding the new system restore functionality.

Also applies to: 15-15, 21-21

libs/types/models/PatchRequest.ts (1)

17-17: LGTM! JSON Patch 'test' operation addition is correct.

The 'test' operation is a standard part of RFC 6902 (JSON Patch) specification that "tests that a value at the target location is equal to a specified value". The expanded union type now includes all six standard JSON Patch operations: 'add', 'remove', 'replace', 'move', 'copy', and 'test'.

libs/ui-components/src/components/Events/useEvents.ts (1)

81-82: LGTM! Event reason mappings correctly added for system restore functionality.

The new event reason mappings align with the system restore feature being implemented:

  • DEVICE_CONFLICT_RESOLVED maps to "Device conflict has been resolved" which supports the device resume workflow
  • INTERNAL_TASK_PERMANENTLY_FAILED maps to "Internal task permanently failed" which provides visibility into task failures during system restore operations

The localization follows the existing pattern and integrates properly with the broader system restore implementation.

Also applies to: 87-87

libs/ui-components/src/types/rbac.ts (1)

16-16: LGTM! RBAC resource addition follows established pattern.

The DEVICE_RESUME = 'devices/resume' resource follows the same naming convention as existing device-specific resources like DEVICE_CONSOLE and DEVICE_DECOMMISSION. This enables proper permission checks for the device resume functionality being implemented across the UI.

libs/ui-components/src/components/Fleet/FleetsPage.tsx (1)

40-40: LGTM! System restore banner integration is properly implemented.

The GlobalSystemRestoreBanners component is correctly imported and rendered within the ListPageBody with the onResumeComplete={refetch} callback. This ensures that when resume operations are completed through the banner, the fleet data is refreshed to reflect the updated state.

Also applies to: 126-126

libs/types/models/EventDetails.ts (1)

15-15: LGTM! EventDetails union type correctly extended.

The import and union type addition for InternalTaskPermanentlyFailedDetails is consistent with the existing pattern used for other event detail types. This enables proper typing for events related to permanently failed internal tasks during system restore operations.

Also applies to: 22-22

libs/ui-components/src/components/Device/DevicesPage/DevicesPage.tsx (1)

105-105: LGTM! Device refetch callback properly wired.

The refetchDevices={refetch} prop correctly passes the data refresh function to EnrolledDevicesTable, enabling the component to trigger device list updates when resume operations complete. This integration aligns with the system restore functionality being implemented.

libs/ui-components/src/components/DetailsPage/DetailsPage.tsx (1)

34-34: LGTM! Banner prop addition enhances DetailsPage flexibility.

The optional banner?: React.ReactNode prop and its rendering between the header and navigation sections provides a clean way to inject system restore banners into detail pages. The implementation follows React best practices and maintains backward compatibility.

Also applies to: 49-49, 91-91

libs/types/models/DeviceResumeResponse.ts (1)

1-15: LGTM! Auto-generated type definition is properly structured.

The DeviceResumeResponse type provides a clear interface for resume operation responses with a resumedDevices: number field to track the count of successfully resumed devices. The auto-generated file headers and structure follow the established pattern used throughout the types package.

libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx (4)

7-7: LGTM! Resume functionality imported correctly.

The import of getResumeDisabledReason correctly aligns with the new resume functionality pattern, following the same approach as the existing decommission functionality.


25-26: LGTM! Resume props added to interface.

The addition of canResume boolean and resumeAction ListAction props follows the established pattern used for decommission functionality.


37-38: LGTM! Resume parameters and disabled reason handling implemented correctly.

The implementation follows the established pattern for similar actions like decommission, with proper parameter destructuring and disabled reason calculation.

Also applies to: 46-46


92-100: LGTM! Resume action rendering follows established pattern.

The conditional rendering of the resume action matches the existing decommission pattern perfectly, with proper parameter passing including disabledReason for tooltip functionality.

libs/types/models/DeviceResumeRequest.ts (1)

1-21: LGTM! Well-documented type definition for device resume functionality.

The type follows TypeScript discriminated union best practices and includes clear JSDoc comments explaining the Kubernetes-style selector syntax. The optional nature of both fields allows flexibility while maintaining type safety.

libs/types/index.ts (1)

53-54: LGTM! New types properly exported from index.

The three new exports (DeviceResumeRequest, DeviceResumeResponse, and InternalTaskPermanentlyFailedDetails) are correctly added to the public API, maintaining alphabetical order and following the established export pattern.

Also applies to: 114-114

libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx (2)

18-18: LGTM! Banner integration follows proper component composition pattern.

The addition of FleetRestoreBanner to the DetailsPage via the banner prop follows React composition principles correctly, passing the necessary fleet and refetch props for functionality.

Also applies to: 49-49


103-103: LGTM! Filename prop updated for YAML editor.

The change from fleet.metadata.name to fleetId for the filename prop is logical and maintains consistency with the component's routing structure.

libs/types/models/Event.ts (1)

70-70: LGTM! New event reasons added for system restore functionality.

The addition of DEVICE_CONFLICT_RESOLVED and INTERNAL_TASK_PERMANENTLY_FAILED enum values properly extends the event system to support the new restore functionality, maintaining the established naming convention.

Also applies to: 83-83

libs/ui-components/src/components/Fleet/FleetDetails/FleetDevicesCharts.tsx (3)

4-4: LGTM! Updated imports for enhanced status handling.

The addition of DeviceSummaryStatusType and update from getDeviceStatusItems to getOverviewDeviceStatusItems correctly aligns with the enhanced status utilities for system restore functionality.

Also applies to: 6-6


22-25: LGTM! System restore statuses properly defined.

The systemRestoreStatuses constant correctly identifies the statuses related to system restore functionality (AwaitingReconnect and ConflictPaused) for exclusion logic.


86-87: LGTM! Dynamic status exclusion logic implemented correctly.

The filtering logic to exclude system restore statuses when they have no device count (falsy values) is well-implemented, and the use of getOverviewDeviceStatusItems with excludeStatuses maintains chart relevance.

libs/types/models/InternalTaskPermanentlyFailedDetails.ts (1)

6-20: LGTM! Well-structured discriminated union type for task failure details.

The type properly implements a discriminated union pattern with the detailType discriminator property, includes comprehensive JSDoc documentation, and provides all necessary fields (errorMessage, retryCount, originalEvent) for handling permanently failed internal tasks.

apps/ocp-plugin/src/hooks/useFetch.ts (3)

45-50: LGTM! Enhanced generics for distinct request/response types.

The update to support separate TRequest and TResponse generics with TResponse = TRequest default provides flexibility for API calls where request and response types differ while maintaining backward compatibility.


52-54: LGTM! PUT method maintains expected behavior.

The PUT method correctly maintains the same type for request and response (TRequest), which is typical for PUT operations that return the updated resource.


62-63: LGTM! Parameter rename improves clarity.

The change from obj to patches for the PATCH method parameter better reflects the actual data type (PatchRequest) and improves code readability.

libs/ui-components/src/components/OverviewPage/Cards/Status/DeviceStatusChart.tsx (3)

8-8: LGTM! Import updated to use the enhanced overview status helper.

The import change from getDeviceStatusItems to getOverviewDeviceStatusItems correctly aligns with the enhanced status item generation that supports filtering.


13-16: LGTM! System restore statuses properly defined.

The hardcoded array correctly defines the system restore statuses that need special handling in the overview chart.


29-30: LGTM! Filtering logic correctly excludes unavailable statuses.

The filtering logic properly excludes system restore statuses that are not present in the current device status data, ensuring the chart only shows relevant status items.

libs/ui-components/src/utils/query.ts (3)

3-3: LGTM! Import correctly added for device status type.

The import of DeviceSummaryStatusType is correctly added to support the new suspended device queries.


78-88: LGTM! Query builder correctly filters suspended devices by labels.

The function properly constructs a query to count suspended devices filtered by specific labels using exact label matching and the correct suspended status.


89-98: LGTM! Query builder correctly gets all suspended devices count.

The function properly constructs a query to count all suspended devices using the correct field selector for the suspended status.

libs/ui-components/src/utils/devices.ts (3)

1-1: LGTM! Import correctly updated to include device status type.

The import is properly updated to include DeviceSummaryStatusType needed for the resume functionality.


30-31: LGTM! Resume eligibility check correctly implemented.

The function correctly determines if a device is resumable by checking for the specific suspended status.


33-38: LGTM! Disabled reason logic correctly implemented.

The function properly provides a localized reason when a device cannot be resumed.

apps/ocp-plugin/src/components/AppContext/AppContext.tsx (2)

9-9: LGTM! Import correctly added for SystemRestoreProvider.

The import is properly added to support the system restore context integration.


29-42: LGTM! Component correctly wraps context with SystemRestoreProvider.

The component structure correctly wraps the AppContext.Provider with SystemRestoreProvider to provide system restore functionality to the OCP plugin.

libs/ui-components/src/hooks/useSystemRestoreContext.tsx (3)

7-12: LGTM! Interface correctly defines the context data structure.

The interface properly defines all the required fields for system restore context data with appropriate types.


20-44: LGTM! Provider correctly fetches and provides system restore data.

The provider properly:

  • Checks user permissions before fetching data
  • Fetches unfiltered device summary for global restore status
  • Computes pending sync and suspended device counts
  • Provides appropriate values based on user permissions

46-54: LGTM! Hook correctly validates context usage.

The hook properly validates that it's used within the SystemRestoreProvider and provides a helpful error message if not.

libs/ui-components/src/components/Fleet/FleetDetails/FleetRestoreBanner.tsx (2)

34-38: LGTM! Label selector correctly constructed from fleet match labels.

The code properly converts the fleet's matchLabels to a label selector string using the exact API match format, which will correctly scope the resume action to devices belonging to this fleet.


29-32: Pluralization verified — translation keys present

en translation file has matching _one/_other keys for this message (libs/i18n/locales/en/translation.json lines 469–470), so Trans with the count prop will pluralize correctly.

libs/ui-components/src/components/SystemRestore/PendingSyncDevicesAlert.tsx (2)

11-11: LGTM! Brand helper correctly handles RHEM vs Flight Control branding.

The helper function properly determines the brand name based on the RHEM setting.


17-29: LGTM! Message logic correctly adapts for single vs multi-device scenarios.

The function properly provides different messaging based on whether it's for a single device or multiple devices, and correctly uses brand-specific wording.

libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx (1)

37-39: Pluralization verified — keys and usage are correct.
en translations include _one/_other entries for "You are about to resume all <1>{deviceCount}</1> suspended devices" (lines ~649–650) and ResumeAllDevicesConfirmationDialog passes count={deviceCountNum} (number), so Trans pluralization will work.

libs/ui-components/src/components/ListPage/ListPageActions.tsx (1)

105-145: Good implementation of the resume action flow.

The useResumeListAction hook follows the established pattern of the other action hooks, properly manages state, handles cleanup, and integrates well with the translation system.

libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (1)

75-79: Well-structured user messaging.

The modal provides clear, context-appropriate messaging for the irreversible resume action, properly warning users about the implications.

libs/ui-components/src/utils/status/devices.ts (2)

84-97: Good implementation of the status updates.

The changes to replace "Awaiting reconnect" and "Updates paused" with "Pending sync" and "Suspended" are well-implemented, with appropriate icons and color tokens for visual differentiation.


100-127: Well-designed prioritization logic.

The getOverviewDeviceStatusItems function correctly implements the prioritization of system restore statuses, maintaining their relative order while allowing for flexible exclusion.

libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (1)

59-65: Add null check before accessing fleet properties.

The function correctly throws an error if the fleet is not found, which is good defensive programming.

libs/ui-components/src/components/DetailsPage/DetailsPageActions.tsx (1)

85-119: Clean implementation of resume action.

The useResumeAction hook follows the established patterns well, properly manages state, and correctly handles the completion callback.

apps/standalone/src/app/utils/apiCalls.ts (1)

108-126: LGTM! Clean implementation of distinct request/response type support.

The generic parameter changes allow for proper typing when API responses differ from request payloads, which is a common pattern for operations like device resume where the request and response have different structures.

apps/standalone/src/app/hooks/useFetch.ts (1)

11-20: LGTM! Consistent API surface with the underlying utils.

The hook properly delegates to the updated postData and putData functions with appropriate generic parameters.

apps/ocp-plugin/src/utils/apiCalls.ts (1)

75-101: LGTM! Maintains consistency across both deployment modes.

The implementation correctly mirrors the standalone version's generic type support, ensuring consistent behavior across OCP plugin and standalone deployments.

libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx (2)

133-134: Good placement of the system restore banners.

The banners are appropriately placed at the top of the device table and correctly wired to refresh the device list when resume operations complete.


206-206: Efficient modal rendering logic.

Using the logical OR operator to render either modal is clean and prevents unnecessary React reconciliation.

libs/ui-components/src/components/Status/StatusDisplay.tsx (1)

28-39: Well-structured custom color support implementation.

The implementation correctly handles custom colors with proper fallback logic and conditional status override. The use of CSS custom properties for dynamic color application is a clean approach.

libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx (1)

129-139: Clean integration of system restore banners.

The banner is properly conditionally rendered and correctly configured with device-specific resume action and summary status.

Comment thread libs/ui-components/src/components/form/validations.ts Outdated
Comment thread libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx Outdated
@celdrake celdrake force-pushed the EDM-2060-backup-and-restore-devices branch 2 times, most recently from a92cfb3 to 36a2e2f Compare September 24, 2025 11:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx (1)

206-206: Avoid hard-prioritizing one modal via logical OR.

Using decommissionDeviceModal || resumeDeviceModal will always prefer the left one if both are truthy, regardless of which action triggered last. Consider explicit mutual exclusivity or rendering whichever was opened last.

Example minimal tweak to render the last opened modal (store lastOpened in state when opening either modal), or if mutual exclusivity is guaranteed, leave as-is. If you prefer to render whichever is set without priority, render both conditionally:

-      {decommissionDeviceModal || resumeDeviceModal}
+      {decommissionDeviceModal}
+      {resumeDeviceModal}
libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (1)

94-126: Prevent stale updates/race conditions in count loader (add abort + cleanup).

Rapid mode/selection changes can race multiple fetches; late arrivals may clobber state after a newer selection or after unmount. Use AbortController and cleanup.

Apply:

@@
-  const loadMatchingDevicesCount = React.useCallback(
+  const countAbortRef = React.useRef<AbortController | null>(null);
+
+  // Abort any in-flight count request on unmount
+  React.useEffect(() => {
+    return () => {
+      countAbortRef.current?.abort();
+    };
+  }, []);
+
+  const loadMatchingDevicesCount = React.useCallback(
     async (criteria: { fleetId?: string; labels?: FlightCtlLabel[]; all?: boolean }) => {
       setIsCountLoading(true);
       setCountError(null);
       setDeviceCountNum(0);
 
       try {
+        // Cancel previous pending request (if any)
+        countAbortRef.current?.abort();
+        const ac = new AbortController();
+        countAbortRef.current = ac;
+
         let queryEndpoint: string;
@@
-        const deviceResult = await get<DeviceList>(queryEndpoint);
+        const deviceResult = await get<DeviceList>(queryEndpoint, ac.signal);
         await showSpinnerBriefly();
         setDeviceCountNum(getApiListCount(deviceResult) || 0);
       } catch (error) {
         await showSpinnerBriefly();
         setCountError(t('Failed to obtain the number of matching devices'));
       } finally {
         setIsCountLoading(false);
       }
     },
-    [get, t, fleets],
+    [get, t, fleets],
   );
libs/i18n/locales/en/translation.json (3)

469-471: Trans placeholders: use sequential indices for components.

The indices 1 and 4 are unusual and may not align with the component order when using . Prefer 0 and 1 (or 0 and 2) consistently to avoid rendering issues if/when i18nKey is used.

Suggested:

-  "You are about to resume all<1>{suspendedDevicesCount}</1> suspended devices in <4>{fleetId}</4>_one": "You are about to resume <1>{suspendedDevicesCount}</1> suspended device in <4>{fleetId}</4>",
-  "You are about to resume all<1>{suspendedDevicesCount}</1> suspended devices in <4>{fleetId}</4>_other": "You are about to resume all<1>{suspendedDevicesCount}</1> suspended devices in <4>{fleetId}</4>",
+  "You are about to resume all<1>{suspendedDevicesCount}</1> suspended devices in <3>{fleetId}</3>_one": "You are about to resume <1>{suspendedDevicesCount}</1> suspended device in <3>{fleetId}</3>",
+  "You are about to resume all<1>{suspendedDevicesCount}</1> suspended devices in <3>{fleetId}</3>_other": "You are about to resume all<1>{suspendedDevicesCount}</1> suspended devices in <3>{fleetId}</3>",

Or, better: provide explicit i18nKey in code and align indices with actual components.


646-647: Trans placeholders likely mismatched with component count.

These keys use indices 0 and 3; the corresponding JSX uses two components, which typically map to 0 and 1. Misaligned indices can break markup when using i18nKey.

Proposed:

-  "<0>{deviceCount}</0> suspended devices are currently associated with fleet <3>{values.fleetId}</3>.": "<0>{deviceCount}</0> suspended devices are currently associated with fleet <3>{values.fleetId}</3>.",
+  "<0>{deviceCount}</0> suspended devices are currently associated with fleet <1>{values.fleetId}</1>.": "<0>{deviceCount}</0> suspended devices are currently associated with fleet <1>{values.fleetId}</1>.",

574-575: Duplicate-ish keys for same message increase maintenance.

“You are about to resume device …” exists with different variable names. Consider consolidating to a single key to avoid drift.

libs/ansible/src/hooks/useFetch.ts (1)

26-35: Remove the unnecessary cast on post() in useFetch

postData in libs/ansible/src/utils/apiCalls.ts already returns Promise, so the as Promise<TResponse> in libs/ansible/src/hooks/useFetch.ts is redundant and inconsistent with put; replace the call with postData<TRequest, TResponse>(kind, data, serviceUrl, applyHeaders) or drop the explicit generic and cast.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98b53ee and 36a2e2f.

📒 Files selected for processing (13)
  • apps/ocp-plugin/src/components/AppContext/AppContext.tsx (2 hunks)
  • apps/ocp-plugin/src/hooks/useFetch.ts (1 hunks)
  • libs/ansible/src/hooks/useFetch.ts (1 hunks)
  • libs/ansible/src/utils/apiCalls.ts (2 hunks)
  • libs/i18n/locales/en/translation.json (8 hunks)
  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx (8 hunks)
  • libs/ui-components/src/components/Fleet/FleetsPage.tsx (2 hunks)
  • libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx (1 hunks)
  • libs/ui-components/src/components/form/validations.ts (1 hunks)
  • libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (1 hunks)
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (1 hunks)
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx (1 hunks)
  • libs/ui-components/src/utils/query.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • libs/ui-components/src/utils/query.ts
  • apps/ocp-plugin/src/components/AppContext/AppContext.tsx
  • libs/ui-components/src/components/form/validations.ts
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx
  • apps/ocp-plugin/src/hooks/useFetch.ts
  • libs/ui-components/src/components/Fleet/FleetsPage.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-02-11T10:20:50.496Z
Learnt from: celdrake
PR: flightctl/flightctl-ui#208
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx:30-44
Timestamp: 2025-02-11T10:20:50.496Z
Learning: In the FlightCtl UI, error handling for device decommissioning is implemented in the modal component created by `useDecommissionAction`. Failed PUT requests show an error in the modal with retry/close options.

Applied to files:

  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx
📚 Learning: 2025-02-11T10:24:15.684Z
Learnt from: celdrake
PR: flightctl/flightctl-ui#208
File: libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx:142-150
Timestamp: 2025-02-11T10:24:15.684Z
Learning: In the FlightCtl UI, device tables (`EnrolledDevicesTable` and `DecommissionedDevicesTable`) are swapped based on the `onlyDecommissioned` state, so their respective switches can have hardcoded values (`false` and `true`).

Applied to files:

  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx
🧬 Code graph analysis (6)
libs/ansible/src/utils/apiCalls.ts (3)
libs/types/scripts/openapi-typescript.js (1)
  • data (23-23)
apps/ocp-plugin/src/utils/apiCalls.ts (2)
  • postData (97-98)
  • putData (100-101)
apps/standalone/src/app/utils/apiCalls.ts (2)
  • postData (128-129)
  • putData (131-132)
libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx (3)
libs/ui-components/src/components/ListPage/ListPageActions.tsx (1)
  • useResumeListAction (105-145)
libs/ui-components/src/hooks/useAccessReview.ts (1)
  • useAccessReview (8-49)
libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx (1)
  • GlobalSystemRestoreBanners (61-82)
libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx (1)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ansible/src/hooks/useFetch.ts (1)
libs/ansible/src/utils/apiCalls.ts (2)
  • postData (44-49)
  • putData (51-56)
libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (8)
libs/ui-components/src/types/extraTypes.ts (1)
  • FlightCtlLabel (15-19)
libs/ui-components/src/utils/labels.ts (2)
  • fromAPILabel (7-12)
  • labelToExactApiMatchString (25-25)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
apps/standalone/src/app/hooks/useFetch.ts (1)
  • useFetch (5-46)
libs/ui-components/src/components/Fleet/useFleets.ts (1)
  • useFleets (87-104)
libs/ui-components/src/utils/query.ts (1)
  • commonQueries (35-99)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
libs/ui-components/src/components/form/validations.ts (1)
  • createMassResumeValidationSchema (699-711)
libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (3)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
apps/standalone/src/app/hooks/useFetch.ts (1)
  • useFetch (5-46)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
🔇 Additional comments (14)
libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx (5)

103-104: Nice integration of resume action with refetch.

Wiring useResumeListAction(refetchDevices) ensures list data is refreshed post‑resume.


133-134: Banners hook-up looks good.

GlobalSystemRestoreBanners with onResumeComplete={refetchDevices} aligns with the resume flow.


196-198: Forwarding resume capabilities to rows is correct.

Passing canResume and resumeAction keeps row logic simple and consistent with other actions.


116-117: RBAC check for DEVICE_RESUME: constant and client wiring present — confirm backend enforcement
RESOURCE.DEVICE_RESUME is defined (libs/ui-components/src/types/rbac.ts) and consumed via useAccessReview → fetch.checkPermissions (libs/ui-components/src/hooks/useAccessReview.ts, libs/ui-components/src/hooks/useAppContext.tsx); verify the server/API exposes and enforces a 'devices/resume' permission (mapped for VERB.UPDATE) so the resume control isn't incorrectly enabled/disabled.


47-49: Prop API change — refetchDevices already passed

DevicesPage passes refetch as refetchDevices to EnrolledDevicesTable (libs/ui-components/src/components/Device/DevicesPage/DevicesPage.tsx:91); no action required.

libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (1)

210-230: Good UX: primary action gated on validity, count load, and submission.

The isSubmitEnabled composition avoids premature submits and double-submits; the confirmation path for “ALL” is a nice touch.

libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (1)

44-63: Modal UX/readiness is solid.

  • Pluralization via count works.
  • Primary disabled after completion avoids accidental replays.
  • Error surfaced inline with getErrorMessage.
libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx (1)

93-100: Action wiring looks good.

Primary and extra actions route to the correct modal types and consolidate close handling via onResumeComplete.

libs/ansible/src/utils/apiCalls.ts (4)

20-26: LGTM! Enhanced generics for better type safety.

The addition of separate TRequest and TResponse generic parameters allows for different request and response types, which is particularly useful for backup/restore operations where the request payload may differ from the response structure.


44-49: LGTM! Consistent generic pattern implementation.

The postData function now correctly uses the enhanced generic signature with separate request and response types, maintaining consistency with the updated putOrPostData function.


51-56: LGTM! Appropriate generic constraint for PUT operations.

The putData function correctly uses a single generic parameter since PUT operations typically expect the same type for both request and response, which aligns with REST conventions.


20-56: Verify consumers handle the new generic signatures

Implementations updated in libs/ansible/src/utils/apiCalls.ts, apps/standalone/src/app/utils/apiCalls.ts, and apps/ocp-plugin/src/utils/apiCalls.ts — all define putOrPostData<TRequest, TResponse = TRequest> and matching postData/putData; grep found no residual "" usages. Search the repo for postData( and putData( callers and update any that relied on the old signatures or that must now supply serviceUrl/applyOptions (libs/ansible).

libs/ansible/src/hooks/useFetch.ts (2)

26-30: LGTM! Enhanced post method with proper generic delegation.

The updated post method now supports separate request and response types, which is essential for backup/restore operations. The explicit type casting ensures type safety while maintaining backward compatibility.


32-35: LGTM! Consistent PUT method implementation.

The put method correctly uses a single generic parameter and delegates to the updated putData function, maintaining consistency with REST conventions where PUT operations typically return the same type as the request.

@celdrake celdrake force-pushed the EDM-2060-backup-and-restore-devices branch from 36a2e2f to 11c28fa Compare September 24, 2025 12:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
libs/ansible/src/utils/apiCalls.ts (4)

6-12: Handle 204 No Content to avoid JSON parse errors

DELETE/PUT/POST may return 204; response.json() will throw. Short‑circuit 204 responses.

 const handleApiJSONResponse = async <R>(response: Response): Promise<R> => {
   if (response.ok) {
+    if (response.status === 204) {
+      // No Content
+      return undefined as R;
+    }
     const data = (await response.json()) as R;
     return data;
   }

27-35: Do not ignore applyOptions return; also include abortSignal in options

If applyOptions returns a new object (immutable pattern), ignoring it can drop auth headers. Also wire the abortSignal.

-  const options: RequestInit = {
+  let options: RequestInit = {
     headers: {
       'Content-Type': 'application/json',
     },
     method,
     body: JSON.stringify(data),
-  };
-  applyOptions(options);
+    signal: abortSignal,
+  };
+  options = applyOptions(options);

64-69: Use applyOptions return value (immutable pattern support) in DELETE

Avoid losing auth headers when applyOptions returns a new object.

-  const options: RequestInit = {
+  let options: RequestInit = {
     method: 'DELETE',
     signal: abortSignal,
   };
-  applyOptions(options);
+  options = applyOptions(options);

85-94: Use applyOptions return value (immutable pattern support) in PATCH

Same reasoning as DELETE/PUT/POST.

-  const options: RequestInit = {
+  let options: RequestInit = {
     headers: {
       'Content-Type': 'application/json-patch+json',
     },
     method: 'PATCH',
     body: JSON.stringify(data),
     signal: abortSignal,
   };
-  applyOptions(options);
+  options = applyOptions(options);
🧹 Nitpick comments (8)
libs/ansible/src/utils/apiCalls.ts (3)

20-26: Thread abortSignal through putOrPostData

Parity with GET/DELETE/PATCH improves cancellation and avoids hanging requests.

-const putOrPostData = async <TRequest, TResponse = TRequest>(
+const putOrPostData = async <TRequest, TResponse = TRequest>(
   kind: string,
   data: TRequest,
   serviceUrl: string,
   applyOptions: (options: RequestInit) => RequestInit,
-  method: 'PUT' | 'POST',
-): Promise<TResponse> => {
+  method: 'PUT' | 'POST',
+  abortSignal?: AbortSignal,
+): Promise<TResponse> => {

44-49: Propagate abortSignal in postData

Expose cancellation to callers and pass it through.

-export const postData = async <TRequest, TResponse = TRequest>(
+export const postData = async <TRequest, TResponse = TRequest>(
   kind: string,
   data: TRequest,
   serviceUrl: string,
   applyOptions: (options: RequestInit) => RequestInit,
-): Promise<TResponse> => putOrPostData<TRequest, TResponse>(kind, data, serviceUrl, applyOptions, 'POST');
+  abortSignal?: AbortSignal,
+): Promise<TResponse> =>
+  putOrPostData<TRequest, TResponse>(kind, data, serviceUrl, applyOptions, 'POST', abortSignal);

51-56: Propagate abortSignal in putData

Mirror POST for consistency and cancellation.

-export const putData = async <TRequest>(
+export const putData = async <TRequest>(
   kind: string,
   data: TRequest,
   serviceUrl: string,
   applyOptions: (options: RequestInit) => RequestInit,
-): Promise<TRequest> => putOrPostData<TRequest, TRequest>(kind, data, serviceUrl, applyOptions, 'PUT');
+  abortSignal?: AbortSignal,
+): Promise<TRequest> =>
+  putOrPostData<TRequest, TRequest>(kind, data, serviceUrl, applyOptions, 'PUT', abortSignal);
libs/ui-components/src/utils/query.ts (2)

78-88: DRY the repeated suspended status field selector.

Extract a shared constant and reuse in both functions.

Apply within these blocks:

-  searchParams.set(
-    'fieldSelector',
-    `status.summary.status=${DeviceSummaryStatusType.DeviceSummaryStatusConflictPaused}`,
-  );
+  searchParams.set('fieldSelector', SUSPENDED_FIELD_SELECTOR);

Add this once near the imports:

const SUSPENDED_FIELD_SELECTOR = `status.summary.status=${DeviceSummaryStatusType.DeviceSummaryStatusConflictPaused}`;

Also applies to: 89-98


78-88: Optional: align with existing builder options pattern.

Most builders accept options?: CommonQueryOptions. Consider supporting it here (defaulting to 1) for consistency and testability.

-  getSuspendedDeviceCountByLabels: (labels: FlightCtlLabel[]) => {
-    const searchParams = new URLSearchParams({
-      limit: '1',
-    });
+  getSuspendedDeviceCountByLabels: (labels: FlightCtlLabel[], options?: CommonQueryOptions) => {
+    const searchParams = new URLSearchParams();
+    searchParams.set('limit', String(options?.limit ?? 1));
@@
-  getAllSuspendedDevicesCount: () => {
-    const searchParams = new URLSearchParams({
-      limit: '1',
-    });
+  getAllSuspendedDevicesCount: (options?: CommonQueryOptions) => {
+    const searchParams = new URLSearchParams();
+    searchParams.set('limit', String(options?.limit ?? 1));

Also applies to: 89-98

libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx (1)

7-13: Remove or use the unused isSubmitting prop.

Currently unused. Either drop it or disable buttons/spinner when provided.

Option A — remove:

-interface ResumeAllDevicesConfirmationModalProps {
+interface ResumeAllDevicesConfirmationModalProps {
   deviceCountNum: number;
   onClose: (doConfirm: boolean) => void;
-  isSubmitting?: boolean;
 }
 
-const ResumeAllDevicesConfirmationModal = ({ deviceCountNum, onClose }: ResumeAllDevicesConfirmationModalProps) => {
+const ResumeAllDevicesConfirmationModal = ({ deviceCountNum, onClose }: ResumeAllDevicesConfirmationModalProps) => {

Option B — use it:

-const ResumeAllDevicesConfirmationModal = ({ deviceCountNum, onClose }: ResumeAllDevicesConfirmationModalProps) => {
+const ResumeAllDevicesConfirmationModal = ({ deviceCountNum, onClose, isSubmitting }: ResumeAllDevicesConfirmationModalProps) => {
...
-        <Button key="confirm" variant="primary" onClick={() => onClose(true)}>
+        <Button key="confirm" variant="primary" onClick={() => onClose(true)} isLoading={isSubmitting} isDisabled={isSubmitting}>
           {t('Resume all devices')}
         </Button>,
-        <Button key="cancel" variant="link" onClick={() => onClose(false)}>
+        <Button key="cancel" variant="link" onClick={() => onClose(false)} isDisabled={isSubmitting}>
           {t('Cancel')}
         </Button>,
libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (2)

35-39: Use nullish coalescing to keep 0 values intact.

Avoid treating 0 as falsy when assigning.

-      setResumedCount(resumeResponse.resumedDevices || 0);
+      setResumedCount(resumeResponse.resumedDevices ?? 0);

44-63: Return undefined on dismiss before submission.

The prop type allows undefined. Distinguish “canceled” from “closed after submitting”.

-      onClose={() => onClose(hasResumed)}
+      onClose={() => onClose(resumedCount === undefined ? undefined : hasResumed)}
...
-        <Button key="cancel" variant="link" onClick={() => onClose(hasResumed)} isDisabled={isSubmitting}>
+        <Button
+          key="cancel"
+          variant="link"
+          onClick={() => onClose(resumedCount === undefined ? undefined : hasResumed)}
+          isDisabled={isSubmitting}
+        >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36a2e2f and 11c28fa.

📒 Files selected for processing (13)
  • apps/ocp-plugin/src/components/AppContext/AppContext.tsx (2 hunks)
  • apps/ocp-plugin/src/hooks/useFetch.ts (1 hunks)
  • libs/ansible/src/hooks/useFetch.ts (1 hunks)
  • libs/ansible/src/utils/apiCalls.ts (2 hunks)
  • libs/i18n/locales/en/translation.json (8 hunks)
  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx (8 hunks)
  • libs/ui-components/src/components/Fleet/FleetsPage.tsx (2 hunks)
  • libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx (1 hunks)
  • libs/ui-components/src/components/form/validations.ts (1 hunks)
  • libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (1 hunks)
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx (1 hunks)
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx (1 hunks)
  • libs/ui-components/src/utils/query.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • libs/ui-components/src/components/Fleet/FleetsPage.tsx
  • apps/ocp-plugin/src/components/AppContext/AppContext.tsx
  • libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx
  • libs/ui-components/src/components/Device/DevicesPage/EnrolledDevicesTable.tsx
  • libs/ui-components/src/components/SystemRestore/SuspendedDevicesAlert.tsx
  • libs/ansible/src/hooks/useFetch.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-03-19T08:55:03.335Z
Learnt from: celdrake
PR: flightctl/flightctl-ui#240
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceApplications.tsx:44-50
Timestamp: 2025-03-19T08:55:03.335Z
Learning: In FlightCtl, the name field is required for Inline applications but optional for Image-based applications. This requirement is enforced by validation logic even though TypeScript type definitions might not explicitly show this distinction.

Applied to files:

  • libs/ui-components/src/utils/query.ts
🧬 Code graph analysis (5)
libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx (1)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/utils/query.ts (3)
libs/ui-components/src/types/extraTypes.ts (1)
  • FlightCtlLabel (15-19)
libs/ui-components/src/utils/labels.ts (1)
  • labelToExactApiMatchString (25-25)
libs/types/index.ts (1)
  • DeviceSummaryStatusType (59-59)
libs/ansible/src/utils/apiCalls.ts (2)
apps/standalone/src/app/utils/apiCalls.ts (2)
  • postData (128-129)
  • putData (131-132)
apps/ocp-plugin/src/utils/apiCalls.ts (2)
  • postData (97-98)
  • putData (100-101)
apps/ocp-plugin/src/hooks/useFetch.ts (2)
apps/ocp-plugin/src/utils/apiCalls.ts (3)
  • postData (97-98)
  • putData (100-101)
  • patchData (118-135)
libs/types/models/PatchRequest.ts (1)
  • PatchRequest (5-18)
libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (2)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/utils/error.ts (1)
  • getErrorMessage (6-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: integration-tests
  • GitHub Check: Build
  • GitHub Check: Lint
  • GitHub Check: Build ocp plugin
🔇 Additional comments (14)
apps/ocp-plugin/src/hooks/useFetch.ts (3)

45-50: LGTM! Enhanced generics improve type safety for request/response patterns.

The updated signature with TRequest and TResponse = TRequest generics provides better type safety for cases where the request and response types differ, while maintaining backward compatibility through the default generic parameter.


52-54: LGTM! Consistent generic typing for PUT operations.

The signature change from <R> to <TRequest> aligns with the updated putData function signature and maintains type consistency across the codebase.


62-63: LGTM! Parameter rename improves clarity.

Renaming the parameter from obj to patches better reflects the nature of the JSON Patch operations being performed, making the code more self-documenting.

libs/ansible/src/utils/apiCalls.ts (1)

44-56: Approve: generics refactor for POST/PUT

Return typing (TResponse = TRequest) matches the repo pattern; inspected call sites (apps/ocp-plugin, apps/standalone, libs/ansible) are compatible — no mismatched expectations found.

libs/ui-components/src/utils/query.ts (3)

3-3: DeviceSummaryStatusType is a string enum — no change required. DeviceSummaryStatusType is declared with string values (e.g. DeviceSummaryStatusConflictPaused = 'ConflictPaused' in libs/types/models/DeviceSummaryStatusType.ts), so the fieldSelector usage in libs/ui-components/src/utils/query.ts will send the expected string token.


89-98: 'Suspended' == DeviceSummaryStatusConflictPaused — keep single-value selector

Only DeviceSummaryStatusType.DeviceSummaryStatusConflictPaused is used to represent "Suspended" in this codebase (see enum and usages), and UI/actions (isDeviceResumable, SystemRestoreBanners, MassResumeDevicesModal) expect/resume ConflictPaused devices, so the existing fieldSelector=status.summary.status=DeviceSummaryStatusConflictPaused is correct. See: libs/types/models/DeviceSummaryStatusType.ts, libs/ui-components/src/utils/devices.ts, libs/ui-components/src/components/SystemRestore/SystemRestoreBanners.tsx, libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx, and libs/ui-components/src/utils/query.ts.


78-88: Confirmed — limit=1 is compatible with how totals are read

getApiListCount (libs/ui-components/src/utils/api.ts) computes totals from response.metadata.remainingItemCount; query builders in libs/ui-components/src/utils/query.ts set limit=1 and callers (e.g., libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx and libs/ui-components/src/hooks/usePendingEnrollmentRequestsCount.ts) use getApiListCount — no header-based total extraction found.

libs/i18n/locales/en/translation.json (5)

630-647: LGTM: resume flow count/status messages.

Keys cover the mass-resume UX (counts, loading states, and errors) and match component usage.


648-671: LGTM: pluralization keys for confirmation/success/warning.

use of _one/_other aligns with i18next pluralization; values read correctly.

Verify that components using these strings pass count to t/Trans and provide the needed components array for the indexed tags (e.g., <1>).


569-571: LGTM: validation error messages added.

These align with the new Yup schema in validations.ts.


93-95: LGTM: device-level resume prompts.

Wording and variable placeholders are consistent.

Also applies to: 127-127, 574-574


842-844: Confirm status label mapping.

Ensure these labels map to actual status enums/values used in UI filters and chips.

libs/ui-components/src/components/form/validations.ts (1)

699-711: LGTM: Yup conditional schema for mass resume.

  • Correct function-form .when() usage.
  • Proper conditional requirements for fleetId and labels.
libs/ui-components/src/components/modals/ResumeDevicesModal/ResumeDevicesModal.tsx (1)

28-28: Verify intentional plural forcing for fleet mode.

Using pluralCount = 2 for fleets guarantees plural text even when expectedCount === 1. Confirm this is desired.

Comment thread libs/i18n/locales/en/translation.json
Comment thread libs/i18n/locales/en/translation.json
Comment thread libs/ui-components/src/utils/query.ts
@celdrake celdrake merged commit 4850420 into flightctl:main Sep 25, 2025
6 checks passed
@celdrake celdrake deleted the EDM-2060-backup-and-restore-devices branch September 25, 2025 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants