Skip to content

CONSOLE-4447: Migrate operator-lifecycle-manager modals to PatternFly v6#16122

Open
rhamilto wants to merge 4 commits intoopenshift:mainfrom
rhamilto:CONSOLE-4447-olm
Open

CONSOLE-4447: Migrate operator-lifecycle-manager modals to PatternFly v6#16122
rhamilto wants to merge 4 commits intoopenshift:mainfrom
rhamilto:CONSOLE-4447-olm

Conversation

@rhamilto
Copy link
Member

@rhamilto rhamilto commented Mar 9, 2026

Summary

This PR migrates all OLM modals from deprecated PatternFly modal components to PatternFly v6 Modal components, continuing the work from #16015.

  • Migrate 9 modals from ModalTitle/ModalSubmitFooter/ModalWrapper to Modal/ModalHeader/ModalBody/ModalFooter
  • Use PatternFly Form component with external submit button pattern (form attribute)
  • Replace deprecated @console/internal Checkbox with PatternFly Checkbox where applicable
  • Add proper data-test-id attributes for Cypress integration tests
  • Update 3 test files to work with new modal structure
  • Use ModalFooterWithAlerts for error handling where applicable

Migrated modals

  • disable-default-source-modal.tsx
  • edit-default-sources-modal.tsx
  • installplan-approval-modal.tsx
  • installplan-preview-modal.tsx
  • subscription-channel-modal.tsx
  • uninstall-operator-modal.tsx
  • update-strategy-modal.tsx
  • operator-hub-community-provider-modal.tsx (note this is orphaned and will be removed in the future)
  • resource-requirements.tsx

Test plan

  • Unit tests pass: cd frontend && yarn test packages/operator-lifecycle-manager/src/components/modals/__tests__
  • Integration tests pass for uninstall operator modal (uses Cypress)
  • Manual testing of each modal:
    • Disable default source: Navigate to OperatorHub → Sources → Disable a source
    • Edit default sources: Navigate to OperatorHub → Sources → Edit
    • InstallPlan approval: Navigate to an InstallPlan → Edit approval strategy
    • InstallPlan preview: Navigate to an InstallPlan → Preview
    • Subscription channel: Navigate to an installed operator → Subscription tab → Edit channel
    • Uninstall operator: Navigate to an installed operator → Actions → Uninstall
    • Update strategy: Navigate to an installed operator → Edit update strategy
    • Community provider warning: Navigate to OperatorHub → Show community operators
    • Resource requirements: Navigate to an operand instance → Edit resource requests/limits

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Refactor

    • Modernized modal dialogs throughout the operator management interface to use PatternFly design patterns, improving error messaging display, form accessibility, and overall consistency.
    • Updated internal modal component structure for better error handling and visual presentation.
  • Tests

    • Updated modal component tests to align with refactored modal structure and submission flows.

Migrate all OLM modals from deprecated PatternFly modal components
(ModalTitle, ModalSubmitFooter, ModalWrapper) to PatternFly v6 Modal
components (Modal, ModalHeader, ModalBody, ModalFooter/ModalFooterWithAlerts).

Changes:
- Use PatternFly Form component with external submit button pattern
- Replace deprecated Checkbox with PatternFly Checkbox
- Add proper data-test-id attributes for Cypress integration tests
- Update test files to work with new modal structure
- Use ModalFooterWithAlerts for error handling where applicable

Migrated modals:
- disable-default-source-modal.tsx
- edit-default-sources-modal.tsx
- installplan-approval-modal.tsx
- installplan-preview-modal.tsx
- subscription-channel-modal.tsx
- uninstall-operator-modal.tsx
- update-strategy-modal.tsx
- operator-hub-community-provider-modal.tsx
- resource-requirements.tsx

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Mar 9, 2026
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 9, 2026

@rhamilto: This pull request references CONSOLE-4447 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Summary

This PR migrates all OLM modals from deprecated PatternFly modal components to PatternFly v6 Modal components, continuing the work from #16015.

  • Migrate 9 modals from ModalTitle/ModalSubmitFooter/ModalWrapper to Modal/ModalHeader/ModalBody/ModalFooter
  • Use PatternFly Form component with external submit button pattern (form attribute)
  • Replace deprecated @console/internal Checkbox with PatternFly Checkbox where applicable
  • Add proper data-test-id attributes for Cypress integration tests
  • Update 3 test files to work with new modal structure
  • Use ModalFooterWithAlerts for error handling where applicable

Migrated modals

  • disable-default-source-modal.tsx
  • edit-default-sources-modal.tsx
  • installplan-approval-modal.tsx
  • installplan-preview-modal.tsx
  • subscription-channel-modal.tsx
  • uninstall-operator-modal.tsx
  • update-strategy-modal.tsx
  • operator-hub-community-provider-modal.tsx
  • resource-requirements.tsx

Test plan

  • Unit tests pass: cd frontend && yarn test packages/operator-lifecycle-manager/src/components/modals/__tests__
  • Integration tests pass for uninstall operator modal (uses Cypress)
  • Manual testing of each modal:
  • Disable default source: Navigate to OperatorHub → Sources → Disable a source
  • Edit default sources: Navigate to OperatorHub → Sources → Edit
  • InstallPlan approval: Navigate to an InstallPlan → Edit approval strategy
  • InstallPlan preview: Navigate to an InstallPlan → Preview
  • Subscription channel: Navigate to an installed operator → Subscription tab → Edit channel
  • Uninstall operator: Navigate to an installed operator → Actions → Uninstall
  • Update strategy: Navigate to an installed operator → Edit update strategy
  • Community provider warning: Navigate to OperatorHub → Show community operators
  • Resource requirements: Navigate to an operand instance → Edit resource requests/limits

🤖 Generated with Claude Code

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci bot requested review from TheRealJon and cajieh March 9, 2026 16:23
@openshift-ci openshift-ci bot added the component/olm Related to OLM label Mar 9, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 9, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: rhamilto

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 9, 2026
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 9, 2026

@rhamilto: This pull request references CONSOLE-4447 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Summary

This PR migrates all OLM modals from deprecated PatternFly modal components to PatternFly v6 Modal components, continuing the work from #16015.

  • Migrate 9 modals from ModalTitle/ModalSubmitFooter/ModalWrapper to Modal/ModalHeader/ModalBody/ModalFooter
  • Use PatternFly Form component with external submit button pattern (form attribute)
  • Replace deprecated @console/internal Checkbox with PatternFly Checkbox where applicable
  • Add proper data-test-id attributes for Cypress integration tests
  • Update 3 test files to work with new modal structure
  • Use ModalFooterWithAlerts for error handling where applicable

Migrated modals

  • disable-default-source-modal.tsx
  • edit-default-sources-modal.tsx
  • installplan-approval-modal.tsx
  • installplan-preview-modal.tsx
  • subscription-channel-modal.tsx
  • uninstall-operator-modal.tsx
  • update-strategy-modal.tsx
  • operator-hub-community-provider-modal.tsx (note this is orphaned and will be removed in the future)
  • resource-requirements.tsx

Test plan

  • Unit tests pass: cd frontend && yarn test packages/operator-lifecycle-manager/src/components/modals/__tests__
  • Integration tests pass for uninstall operator modal (uses Cypress)
  • Manual testing of each modal:
  • Disable default source: Navigate to OperatorHub → Sources → Disable a source
  • Edit default sources: Navigate to OperatorHub → Sources → Edit
  • InstallPlan approval: Navigate to an InstallPlan → Edit approval strategy
  • InstallPlan preview: Navigate to an InstallPlan → Preview
  • Subscription channel: Navigate to an installed operator → Subscription tab → Edit channel
  • Uninstall operator: Navigate to an installed operator → Actions → Uninstall
  • Update strategy: Navigate to an installed operator → Edit update strategy
  • Community provider warning: Navigate to OperatorHub → Show community operators
  • Resource requirements: Navigate to an operand instance → Edit resource requests/limits

🤖 Generated with Claude Code

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 9, 2026

@rhamilto: This pull request references CONSOLE-4447 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Summary

This PR migrates all OLM modals from deprecated PatternFly modal components to PatternFly v6 Modal components, continuing the work from #16015.

  • Migrate 9 modals from ModalTitle/ModalSubmitFooter/ModalWrapper to Modal/ModalHeader/ModalBody/ModalFooter
  • Use PatternFly Form component with external submit button pattern (form attribute)
  • Replace deprecated @console/internal Checkbox with PatternFly Checkbox where applicable
  • Add proper data-test-id attributes for Cypress integration tests
  • Update 3 test files to work with new modal structure
  • Use ModalFooterWithAlerts for error handling where applicable

Migrated modals

  • disable-default-source-modal.tsx
  • edit-default-sources-modal.tsx
  • installplan-approval-modal.tsx
  • installplan-preview-modal.tsx
  • subscription-channel-modal.tsx
  • uninstall-operator-modal.tsx
  • update-strategy-modal.tsx
  • operator-hub-community-provider-modal.tsx (note this is orphaned and will be removed in the future)
  • resource-requirements.tsx

Test plan

  • Unit tests pass: cd frontend && yarn test packages/operator-lifecycle-manager/src/components/modals/__tests__
  • Integration tests pass for uninstall operator modal (uses Cypress)
  • Manual testing of each modal:
  • Disable default source: Navigate to OperatorHub → Sources → Disable a source
  • Edit default sources: Navigate to OperatorHub → Sources → Edit
  • InstallPlan approval: Navigate to an InstallPlan → Edit approval strategy
  • InstallPlan preview: Navigate to an InstallPlan → Preview
  • Subscription channel: Navigate to an installed operator → Subscription tab → Edit channel
  • Uninstall operator: Navigate to an installed operator → Actions → Uninstall
  • Update strategy: Navigate to an installed operator → Edit update strategy
  • Community provider warning: Navigate to OperatorHub → Show community operators
  • Resource requirements: Navigate to an operand instance → Edit resource requests/limits

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Refactor

  • Modernized modal dialogs throughout the operator management interface to use PatternFly design patterns, improving error messaging display, form accessibility, and overall consistency.

  • Updated internal modal component structure for better error handling and visual presentation.

  • Tests

  • Updated modal component tests to align with refactored modal structure and submission flows.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

This pull request refactors modal UI across multiple operator lifecycle manager components to standardize on PatternFly Modal composition patterns. Legacy ModalWrapper, ModalTitle, and ModalSubmitFooter components are replaced with PatternFly ModalHeader, ModalBody, and ModalFooterWithAlerts. Forms are now explicitly structured with id attributes and linked via form attributes on submit buttons. Checkbox and Radio components are updated to use PatternFly prop signatures. Test files are updated to mock ModalFooterWithAlerts and adjust submission from form.submit() to button clicks. One new public type, OperatorHubCommunityProviderModalProps, is introduced. Overall logic and navigation flows remain unchanged.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: a migration of operator-lifecycle-manager modals to PatternFly v6. It directly corresponds to the bulk of changes across 13 files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
frontend/packages/operator-lifecycle-manager/src/components/modals/disable-default-source-modal.tsx (1)

64-75: Redundant onClick handler alongside external submit pattern.

The PR uses the external submit button pattern (type="submit" + form="disable-default-source-form"), which links this button to the form's onSubmit. However, the onClick={submit} handler intercepts the click and calls preventDefault(), effectively bypassing the form's submission mechanism entirely.

This is functional but inconsistent—pick one approach:

  • Option A (preferred): Remove onClick and rely on the form's onSubmit={submit}. This is cleaner, keeps keyboard submission working via Enter, and aligns with the stated pattern.
  • Option B: Remove type="submit" and form="...", keep only onClick, and remove onSubmit from the Form.
♻️ Suggested refactor (Option A)
         <Button
           type="submit"
           variant="danger"
-          onClick={submit}
           form="disable-default-source-form"
           isLoading={inProgress}
           isDisabled={inProgress}
           data-test="confirm-action"
           id="confirm-action"
         >
           {t('public~Disable')}
         </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/disable-default-source-modal.tsx`
around lines 64 - 75, The Button uses both type="submit" with
form="disable-default-source-form" and an onClick={submit} handler that calls
preventDefault(), which bypasses the form's onSubmit; remove the redundant
onClick from the Button and rely on the form's onSubmit={submit} instead so
keyboard Enter works and the external submit pattern is consistent—update the
Button component (id="confirm-action", data-test="confirm-action") to remove
onClick and keep type="submit" and form="disable-default-source-form".
frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx (2)

75-79: Verify the checkbox group is actually labelled.

fieldId="enabled-default-sources" does not match any rendered control in this block—the checkboxes each get id={source.name} instead. If FormGroup still ties its label to fieldId, assistive tech loses the “Enabled default sources” group context. I’d wire this as a real fieldset/legend or add explicit group labelling with aria-labelledby.

Also applies to: 85-93

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx`
around lines 75 - 79, The FormGroup label "Enabled default sources" is not tied
to the checkbox controls because fieldId="enabled-default-sources" doesn't match
the checkboxes' ids (they use id={source.name}); update the markup so the group
label is programmatically associated with the controls by either (a) turning the
FormGroup into a proper fieldset/legend (wrap checkboxes in a <fieldset> with a
<legend> that contains the label) or (b) keep FormGroup and give the label an id
(e.g., "enabled-default-sources-label") and add
aria-labelledby="enabled-default-sources-label" on the checkbox container (or
add aria-describedby/aria-labelledby on each checkbox to reference that label),
adjusting the ids used in the map (where source.name is used for id) accordingly
so assistive tech receives the group context for the checkboxes rendered by the
component that maps source.name to checkbox ids.

107-116: Let the form own submission.

With type="submit" and form="edit-default-sources-form" already set, onClick={submit} creates a second submit path and calls preventDefault() on the click event rather than the form submit event. Dropping onClick keeps native submit semantics intact and avoids drifting this handler into a button-click shape later.

♻️ Suggested cleanup
         <Button
           type="submit"
           variant="primary"
-          onClick={submit}
           form="edit-default-sources-form"
           isLoading={inProgress}
           isDisabled={inProgress}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx`
around lines 107 - 116, The Button currently has both type="submit" and
form="edit-default-sources-form" but also onClick={submit}, which creates a
secondary submit path and calls preventDefault on the click event; remove the
onClick prop from the Button (leave type, form, id, isLoading/isDisabled and
data-test intact) and ensure the form element with id
"edit-default-sources-form" uses the submit handler (submit) via its onSubmit so
the form owns submission semantics; verify the submit function is wired to the
form's onSubmit and that any event.preventDefault() is applied to the form
submit event handler, not a button click.
frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-community-provider-modal.tsx (1)

43-47: Good use of titleIconVariant for informational modal.

The titleIconVariant="info" prop on ModalHeader is the correct PF v6 approach for indicating the modal's intent. However, I notice there's also an explicit InfoCircleIcon rendered in the body (Lines 52-54). This creates visual redundancy - you have an info icon in both the header and the body.

Consider removing the manual icon in the body since the header variant already communicates the informational nature:

♻️ Consider removing redundant icon
         <Form id="community-provider-form" onSubmit={submit}>
           <Split hasGutter>
-            <SplitItem>
-              <Icon size="xl" status="info">
-                <InfoCircleIcon />
-              </Icon>
-            </SplitItem>
             <SplitItem>
               <Content component={ContentVariants.p}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-community-provider-modal.tsx`
around lines 43 - 47, The ModalHeader already uses titleIconVariant="info" to
indicate intent, so remove the redundant manual InfoCircleIcon rendered in the
modal body (the JSX that imports/uses InfoCircleIcon) to avoid duplicate icons;
locate the InfoCircleIcon usage in the operator-hub-community-provider-modal
component and delete that element and any adjacent wrapper solely used for
presenting the icon while keeping the surrounding explanatory text and
translations intact.
frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-approval-modal.tsx (1)

67-96: Simplify Radio onChange handlers using hardcoded values.

PatternFly v6's Radio onChange signature is (event: React.FormEvent<HTMLInputElement>, checked: boolean) => void. The current implementation extracts value via (e.target as HTMLInputElement).value, which works but is unnecessarily verbose. Since each Radio already has its value available as a constant, use a closure instead:

♻️ Suggested refactoring
             <Radio
               id="approval-strategy-automatic"
               name="approval-strategy"
               value={InstallPlanApproval.Automatic}
               label={`${t(`olm~Automatic`)} (${t('public~default')})`}
               description={t('olm~New updates will be installed as soon as they become available.')}
-              onChange={(e) =>
-                setSelectedApprovalStrategy(
-                  (e.target as HTMLInputElement).value as InstallPlanApproval,
-                )
-              }
+              onChange={() => setSelectedApprovalStrategy(InstallPlanApproval.Automatic)}
               isChecked={selectedApprovalStrategy === InstallPlanApproval.Automatic}
               data-checked-state={selectedApprovalStrategy === InstallPlanApproval.Automatic}
             />
             <Radio
               id="approval-strategy-manual"
               name="approval-strategy"
               value={InstallPlanApproval.Manual}
               label={t('olm~Manual')}
               description={t(
                 'olm~New updates need to be manually approved before installation begins.',
               )}
-              onChange={(e) =>
-                setSelectedApprovalStrategy(
-                  (e.target as HTMLInputElement).value as InstallPlanApproval,
-                )
-              }
+              onChange={() => setSelectedApprovalStrategy(InstallPlanApproval.Manual)}
               isChecked={selectedApprovalStrategy === InstallPlanApproval.Manual}
               data-checked-state={selectedApprovalStrategy === InstallPlanApproval.Manual}
             />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-approval-modal.tsx`
around lines 67 - 96, The two Radio onChange handlers for the approval strategy
(the Radio components with id "approval-strategy-automatic" and
"approval-strategy-manual") should be simplified to use closures that set the
known constant values instead of reading e.target.value; update the onChange for
the Automatic radio to call
setSelectedApprovalStrategy(InstallPlanApproval.Automatic) and for the Manual
radio to call setSelectedApprovalStrategy(InstallPlanApproval.Manual), matching
the PatternFly v6 signature (event, checked) => void and avoiding the cast
(e.target as HTMLInputElement).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/resource-requirements.tsx`:
- Around line 119-139: The form id "resource-requirements-form" is hard-coded
and can collide when multiple Modal instances mount; generate a unique
per-instance id (e.g., using React's useId() or a useRef/uuid initialized once)
inside the component that renders <Form id=...> and the footer <Button
form=...>, assign it to a local const like formId and replace the literal
"resource-requirements-form" with that formId for both the Form id and the
Button form props so the submit handler (submit) always targets the correct
instance.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx`:
- Around line 16-17: The test mock for ModalFooterWithAlerts currently only
renders children and bypasses PF submit wiring; update the jest.mock for
ModalFooterWithAlerts to render a minimal submit button that forwards form
submission (e.g., render a <button type="submit">) so tests exercise the
component's real submit path; ensure the mock's exported ModalFooterWithAlerts
(used by InstallPlanApprovalModal tests) still renders children and includes the
submit button so assertions trigger via clicking the button rather than directly
submitting the form.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx`:
- Around line 386-448: The modal can be dismissed during uninstall because the
<Modal> and its onClose live in UninstallOperatorModalOverlay while the
disabling logic (isSubmitInProgress, operandsDeleteInProgress,
operatorUninstallInProgress) is inside UninstallOperatorModal; fix by either
moving the <Modal> and onClose back into UninstallOperatorModal so it can gate
closure based on those flags, or add a closeDisabled/isBlocked prop on
UninstallOperatorModalOverlay and pass a boolean (isSubmitInProgress ||
operandsDeleteInProgress || operatorUninstallInProgress) so the overlay prevents
backdrop/X/Esc dismissal while operations are in progress.

---

Nitpick comments:
In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/disable-default-source-modal.tsx`:
- Around line 64-75: The Button uses both type="submit" with
form="disable-default-source-form" and an onClick={submit} handler that calls
preventDefault(), which bypasses the form's onSubmit; remove the redundant
onClick from the Button and rely on the form's onSubmit={submit} instead so
keyboard Enter works and the external submit pattern is consistent—update the
Button component (id="confirm-action", data-test="confirm-action") to remove
onClick and keep type="submit" and form="disable-default-source-form".

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx`:
- Around line 75-79: The FormGroup label "Enabled default sources" is not tied
to the checkbox controls because fieldId="enabled-default-sources" doesn't match
the checkboxes' ids (they use id={source.name}); update the markup so the group
label is programmatically associated with the controls by either (a) turning the
FormGroup into a proper fieldset/legend (wrap checkboxes in a <fieldset> with a
<legend> that contains the label) or (b) keep FormGroup and give the label an id
(e.g., "enabled-default-sources-label") and add
aria-labelledby="enabled-default-sources-label" on the checkbox container (or
add aria-describedby/aria-labelledby on each checkbox to reference that label),
adjusting the ids used in the map (where source.name is used for id) accordingly
so assistive tech receives the group context for the checkboxes rendered by the
component that maps source.name to checkbox ids.
- Around line 107-116: The Button currently has both type="submit" and
form="edit-default-sources-form" but also onClick={submit}, which creates a
secondary submit path and calls preventDefault on the click event; remove the
onClick prop from the Button (leave type, form, id, isLoading/isDisabled and
data-test intact) and ensure the form element with id
"edit-default-sources-form" uses the submit handler (submit) via its onSubmit so
the form owns submission semantics; verify the submit function is wired to the
form's onSubmit and that any event.preventDefault() is applied to the form
submit event handler, not a button click.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-approval-modal.tsx`:
- Around line 67-96: The two Radio onChange handlers for the approval strategy
(the Radio components with id "approval-strategy-automatic" and
"approval-strategy-manual") should be simplified to use closures that set the
known constant values instead of reading e.target.value; update the onChange for
the Automatic radio to call
setSelectedApprovalStrategy(InstallPlanApproval.Automatic) and for the Manual
radio to call setSelectedApprovalStrategy(InstallPlanApproval.Manual), matching
the PatternFly v6 signature (event, checked) => void and avoiding the cast
(e.target as HTMLInputElement).

In
`@frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-community-provider-modal.tsx`:
- Around line 43-47: The ModalHeader already uses titleIconVariant="info" to
indicate intent, so remove the redundant manual InfoCircleIcon rendered in the
modal body (the JSX that imports/uses InfoCircleIcon) to avoid duplicate icons;
locate the InfoCircleIcon usage in the operator-hub-community-provider-modal
component and delete that element and any adjacent wrapper solely used for
presenting the icon while keeping the surrounding explanatory text and
translations intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62551f67-1223-4f40-a40d-51f4c3fd05f1

📥 Commits

Reviewing files that changed from the base of the PR and between 874c37e and 16ae1fc.

📒 Files selected for processing (13)
  • frontend/packages/operator-lifecycle-manager/integration-tests-cypress/views/operator.view.ts
  • frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/resource-requirements.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/subscription-channel-modal.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/uninstall-operator-modal.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/disable-default-source-modal.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-approval-modal.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-preview-modal.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/subscription-channel-modal.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/update-strategy-modal.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-community-provider-modal.tsx

Comment on lines +119 to +139
<Form id="resource-requirements-form" onSubmit={(e) => submit(e)}>
<Grid hasGutter>
<GridItem>{props.description}</GridItem>
<ResourceRequirements
cpu={cpu}
memory={memory}
storage={storage}
onChangeCPU={setCPU}
onChangeMemory={setMemory}
onChangeStorage={setStorage}
path={path}
/>
</Grid>
</Form>
</ModalBody>
<ModalSubmitFooter
errorMessage={errorMessage}
inProgress={inProgress}
submitText={t('public~Save')}
cancel={cancel}
/>
</form>
<ModalFooterWithAlerts errorMessage={errorMessage}>
<Button
type="submit"
variant="primary"
form="resource-requirements-form"
isLoading={inProgress}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid a shared form id across modal instances.

resource-requirements-form is hard-coded for every render, but the submit button depends on that id via form. If two instances of this modal are mounted at the same time, the DOM ends up with duplicate ids and the footer button can submit the wrong form. Please generate a per-instance id and reuse it for both the Form and the Button.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/resource-requirements.tsx`
around lines 119 - 139, The form id "resource-requirements-form" is hard-coded
and can collide when multiple Modal instances mount; generate a unique
per-instance id (e.g., using React's useId() or a useRef/uuid initialized once)
inside the component that renders <Form id=...> and the footer <Button
form=...>, assign it to a local const like formId and replace the literal
"resource-requirements-form" with that formId for both the Form id and the
Button form props so the submit handler (submit) always targets the correct
instance.

Comment on lines +16 to +17
jest.mock('@console/shared/src/components/modals/ModalFooterWithAlerts', () => ({
ModalFooterWithAlerts: jest.fn(({ children }) => <div>{children}</div>),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Preserve the footer submit path in this mock.

This mock reduces ModalFooterWithAlerts to a passive wrapper, so the spec now bypasses the PF v6 footer/button wiring and submits the <form> directly. That means a broken form association or disabled submit button in InstallPlanApprovalModal would still pass here. Please render a minimal submit button in the mock and drive the submit assertions through that button instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx`
around lines 16 - 17, The test mock for ModalFooterWithAlerts currently only
renders children and bypasses PF submit wiring; update the jest.mock for
ModalFooterWithAlerts to render a minimal submit button that forwards form
submission (e.g., render a <button type="submit">) so tests exercise the
component's real submit path; ensure the mock's exported ModalFooterWithAlerts
(used by InstallPlanApprovalModal tests) still renders children and includes the
submit button so assertions trigger via clicking the button rather than directly
submitting the form.

Comment on lines +386 to +448
<>
<ModalHeader
title={t('olm~Uninstall Operator?')}
titleIconVariant="warning"
data-test-id="modal-title"
/>
<ModalBody>
{showInstructions && (
<>
<p>
<Trans t={t} ns="olm">
Operator <strong>{{ name }}</strong> will be removed from{' '}
<strong>{{ namespace }}</strong>.
</Trans>
</p>
{!optedOut && <>{instructions}</>}
{uninstallMessage && (
<>
<Title headingLevel="h2" className="pf-v6-u-mb-sm">
{t('olm~Message from Operator developer')}
</Title>
<p>
<LinkifyExternal>{uninstallMessage}</LinkifyExternal>
</p>
</>
)}
{!optedOut && <>{operandsSection}</>}
</>
)}
{operandsDeleteInProgress && (
<OperandDeleteProgress total={operands.length} remaining={operandsRemaining} />
)}
{operatorUninstallInProgress && (
<div>
<p>{t('olm~Uninstalling the Operator...')}</p>
</div>
)}
{isSubmitFinished && results}
<Form id="uninstall-operator-form" onSubmit={submit}>
{showInstructions && (
<>
<p>
<Trans t={t} ns="olm">
Operator <strong>{{ name }}</strong> will be removed from{' '}
<strong>{{ namespace }}</strong>.
</Trans>
</p>
{!optedOut && <>{instructions}</>}
{uninstallMessage && (
<>
<Title headingLevel="h2" className="pf-v6-u-mb-sm">
{t('olm~Message from Operator developer')}
</Title>
<p>
<LinkifyExternal>{uninstallMessage}</LinkifyExternal>
</p>
</>
)}
{!optedOut && <>{operandsSection}</>}
</>
)}
{operandsDeleteInProgress && (
<OperandDeleteProgress total={operands.length} remaining={operandsRemaining} />
)}
{operatorUninstallInProgress && (
<div>
<p>{t('olm~Uninstalling the Operator...')}</p>
</div>
)}
{isSubmitFinished && results}
</Form>
</ModalBody>
<ModalSubmitFooter
inProgress={isSubmitInProgress}
cancel={cancel}
submitDanger={!isSubmitFinished} // if submit finished show a non-danger 'OK'
submitText={isSubmitFinished ? t('olm~OK') : t('olm~Uninstall')}
submitDisabled={isSubmitInProgress}
/>
</form>
<ModalFooterWithAlerts errorMessage={operatorUninstallErrorMessage}>
<Button
type="submit"
variant={isSubmitFinished ? 'primary' : 'danger'}
form="uninstall-operator-form"
isLoading={isSubmitInProgress}
isDisabled={isSubmitInProgress}
data-test="confirm-action"
id="confirm-action"
>
{isSubmitFinished ? t('olm~OK') : t('olm~Uninstall')}
</Button>
<Button
variant="link"
onClick={cancel}
isDisabled={isSubmitInProgress}
data-test-id="modal-cancel-action"
>
{t('public~Cancel')}
</Button>
</ModalFooterWithAlerts>
</>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the file
find . -type f -name "uninstall-operator-modal.tsx" 2>/dev/null

Repository: openshift/console

Length of output: 158


🏁 Script executed:

# Get the total line count to understand file size
wc -l frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx

Repository: openshift/console

Length of output: 160


🏁 Script executed:

# Read the file to understand the structure - focusing on key areas
cat -n frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx | head -100

Repository: openshift/console

Length of output: 4932


🏁 Script executed:

# Check around line 667 where the Modal component is allegedly moved
cat -n frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx | sed -n '650,680p'

Repository: openshift/console

Length of output: 1190


🏁 Script executed:

# Check lines 439-443 to see what's disabled
cat -n frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx | sed -n '430,450p'

Repository: openshift/console

Length of output: 811


🏁 Script executed:

# Check what triggers isSubmitInProgress to understand the operation flow
cat -n frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx | sed -n '200,250p'

Repository: openshift/console

Length of output: 2300


🏁 Script executed:

# Check the submit handler and promise handling
cat -n frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx | sed -n '300,350p'

Repository: openshift/console

Length of output: 2039


Move Modal control back into the component or gate dismissal during operations.

The <Modal> and onClose handler are currently in UninstallOperatorModalOverlay (line 667), while only button actions disable during isSubmitInProgress (line 442). This allows users to dismiss the modal via the X button, ESC key, or backdrop click while uninstall or operand-deletion polling is running. The component remains exported as self-contained but has lost dismissal control—either keep the <Modal> in UninstallOperatorModal itself, or pass a isBlocked/closeDisabled signal to the overlay to prevent closure during operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx`
around lines 386 - 448, The modal can be dismissed during uninstall because the
<Modal> and its onClose live in UninstallOperatorModalOverlay while the
disabling logic (isSubmitInProgress, operandsDeleteInProgress,
operatorUninstallInProgress) is inside UninstallOperatorModal; fix by either
moving the <Modal> and onClose back into UninstallOperatorModal so it can gate
closure based on those flags, or add a closeDisabled/isBlocked prop on
UninstallOperatorModalOverlay and pass a boolean (isSubmitInProgress ||
operandsDeleteInProgress || operatorUninstallInProgress) so the overlay prevents
backdrop/X/Esc dismissal while operations are in progress.

@rhamilto
Copy link
Member Author

rhamilto commented Mar 9, 2026

/label tide/merge-method-squash

- Use unique form IDs with useId() in ResourceRequirementsModal to prevent
  ID collisions when multiple modal instances mount
- Prevent modal dismissal during async operations in UninstallOperatorModal
  by disabling onClose when operations are in progress
- Remove redundant onClick handlers from submit buttons in
  disable-default-source-modal and edit-default-sources-modal
- Simplify Radio onChange handlers in installplan-approval-modal to use
  closures with known constant values instead of reading e.target.value
- Remove duplicate InfoCircleIcon from operator-hub-community-provider-modal
  since titleIconVariant="info" already displays the icon

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@openshift-ci openshift-ci bot added component/core Related to console core functionality kind/i18n Indicates issue or PR relates to internationalization or has content that needs to be translated tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges. labels Mar 9, 2026
…iew-modal

For read-only/informational modals with a single dismiss button, the OK
button should use variant="primary" as it's the main action.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add missing aria-labelledby attributes for WCAG 2.1 compliance and restore
historical cancel button behavior across all 8 migrated modals.

Accessibility fixes:
- Add labelId to all ModalHeader components
- Add matching aria-labelledby to all Modal wrapper components
- Ensures screen readers can properly announce modal titles

UX fixes:
- Remove isDisabled={inProgress} from all cancel buttons
- Restores historical pattern where users can cancel during async operations
- Aligns with console-wide modal behavior (see ModalSubmitFooter history)

Modals updated:
- disable-default-source-modal.tsx
- edit-default-sources-modal.tsx
- installplan-approval-modal.tsx
- installplan-preview-modal.tsx
- subscription-channel-modal.tsx
- uninstall-operator-modal.tsx
- update-strategy-modal.tsx
- operator-hub-community-provider-modal.tsx

Follows patterns from PR openshift#16123 (CONSOLE-4447-app-2).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 9, 2026

@rhamilto: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/backend a8a4b54 link true /test backend
ci/prow/frontend a8a4b54 link true /test frontend

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@rhamilto
Copy link
Member Author

rhamilto commented Mar 9, 2026

/retest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. component/core Related to console core functionality component/olm Related to OLM jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. kind/i18n Indicates issue or PR relates to internationalization or has content that needs to be translated tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants