Skip to content

CONSOLE-4447: Migrate core modals to PatternFly 6 Modal components (part 2)#16123

Open
rhamilto wants to merge 1 commit intoopenshift:mainfrom
rhamilto:CONSOLE-4447-app-2
Open

CONSOLE-4447: Migrate core modals to PatternFly 6 Modal components (part 2)#16123
rhamilto wants to merge 1 commit intoopenshift:mainfrom
rhamilto:CONSOLE-4447-app-2

Conversation

@rhamilto
Copy link
Member

@rhamilto rhamilto commented Mar 9, 2026

Summary

Migrates multiple modals from legacy createModalLauncher/ModalWrapper to the modern useOverlay pattern with PatternFly 6 Modal components.

Migrated Modals

Cluster Settings & Updates:

  • cluster-channel-modal - Update cluster channel selection
  • cluster-more-updates-modal - Display additional update paths
  • cluster-update-modal - Cluster version updates with MCP pause options

Resource Management:

  • delete-pvc-modal - Delete PersistentVolumeClaim with extension point support
  • rollback-modal - Rollback deployments and deployment configs
  • column-management-modal - Manage table column visibility

Node Configuration:

  • taints-modal - Edit node taints with key/value/effect configuration
  • tolerations-modal - Edit pod/node tolerations

Utility Modals:

  • error-modal - Generic error display modal (added useErrorModalLauncher hook)
  • configure-count-modal - Configure replica/parallelism counts

Code Review Fixes

Accessibility improvements:

  • Added aria-labelledby to all Modal components linking to ModalHeader labelId
  • Fixed FormGroup in cluster-update-modal to use role="radiogroup" instead of unused fieldId
  • Added aria-label to TextInput components in taints-modal
  • Added isDisabled to delete button while operation in progress

Error handling:

  • Added .catch(() => {}) to promise chains in cluster-update-modal and rollback-modal

i18n fixes:

  • Added missing public~ namespace prefix to Close button translation
  • Translated Tooltip content in taints-modal

Null safety:

  • Added defensive fallback for semver.parse in cluster-channel-modal using nullish coalescing

Code quality:

  • Fixed duplicate modal rendering in cluster-settings using useRef to track modal state
  • Migrated to useQueryParamsMutator hook's getQueryArgument for URL params
  • Replaced raw input elements with PatternFly TextInput components in taints-modal
  • Fixed inconsistent error checking in rollback-modal
  • Fixed typo: defalt-column-managementdefault-column-management
  • Migrated taints-modal to lazy loading pattern (LazyTaintsModalOverlay)
  • Added export for modal overlay components in modals/index.ts

Test plan

  • Verify cluster channel modal opens and functions correctly
  • Verify cluster update modal displays available updates and handles MCP pausing
  • Verify more updates modal shows additional update paths
  • Verify tolerations modal allows editing tolerations
  • Verify taints modal allows editing node taints
  • Verify delete PVC modal shows extension alerts and deletes correctly
  • Verify rollback modal works for both Deployments and DeploymentConfigs
  • Verify column management modal saves column preferences
  • Verify error modal displays correctly
  • Verify configure count modal updates counts correctly
  • Test keyboard navigation and screen reader accessibility
  • Verify all modals properly close on cancel/submit
  • Verify no duplicate modals appear when navigating with URL params
  • Verify error states display correctly
  • Run yarn i18n to update translation keys
  • All unit and E2E tests pass

🤖 Generated with Claude Code

@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

Continues PatternFly v6 modal migration by refactoring 7 modal components to use PF6 Modal, ModalHeader, ModalBody instead of legacy components.

Note: This PR depends on #15997 which should merge first.

Changes

  • Migrated modals to PF6 pattern with Modal wrapper in Overlay component
  • Replaced legacy ModalWrapper, ModalTitle, ModalSubmitFooter with PF6 equivalents
  • Replaced HTML forms with PatternFly Form components
  • Used titleIconVariant prop instead of manual icon elements where applicable
  • Fixed column-management-modal tests
  • Removed orphaned ModalErrorContent component from error-modal

Modals Updated

  • column-management-modal
  • cluster-channel-modal
  • cluster-more-updates-modal
  • cluster-update-modal
  • delete-pvc-modal
  • rollback-modal
  • taints-modal

Test Plan

  • Linter passes on all modified files
  • column-management-modal unit tests pass (6/6)
  • Manual verification: modals open/close correctly
  • Manual verification: modal functionality unchanged

🤖 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 cajieh and sg00dwin March 9, 2026 19:19
@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 component/core Related to console core functionality kind/i18n Indicates issue or PR relates to internationalization or has content that needs to be translated approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Mar 9, 2026
@rhamilto rhamilto changed the title CONSOLE-4447: Migrate modals to PatternFly 6 Modal components [WIP] CONSOLE-4447: Migrate modals to PatternFly 6 Modal components (part 2) Mar 9, 2026
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. 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

Continues PatternFly v6 modal migration by refactoring 7 modal components to use PF6 Modal, ModalHeader, ModalBody instead of legacy components.

Note: This PR depends on #15997 which should merge first.

Changes

  • Migrated modals to PF6 pattern with Modal wrapper in Overlay component
  • Replaced legacy ModalWrapper, ModalTitle, ModalSubmitFooter with PF6 equivalents
  • Replaced HTML forms with PatternFly Form components
  • Used titleIconVariant prop instead of manual icon elements where applicable
  • Fixed column-management-modal tests
  • Removed orphaned ModalErrorContent component from error-modal

Modals Updated

  • column-management-modal
  • cluster-channel-modal
  • cluster-more-updates-modal
  • cluster-update-modal
  • delete-pvc-modal
  • rollback-modal
  • taints-modal

Test Plan

  • Linter passes on all modified files
  • column-management-modal unit tests pass (6/6)
  • Manual verification: modals open/close correctly
  • Manual verification: modal functionality unchanged

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Improvements

  • Modernized the modal dialog system with consistent visual styling and improved error messaging.

  • Enhanced cluster configuration modals with clearer layouts and better organization of actions.

  • Improved display of alerts and warnings within modals for better visibility during critical operations.

  • Removed Features

  • Removed the ability to add users directly to groups from the modal interface.

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 the modal system across multiple components in the frontend. The primary change migrates modals from legacy createModalLauncher-based patterns to a unified Overlay-based component architecture. Eight modal components (cluster-channel, cluster-more-updates, cluster-update, tolerations, delete-pvc, rollback, taints, column-management, error) are restructured to use PatternFly's ModalHeader, ModalBody, and ModalFooter primitives. The cluster-settings component is updated to launch overlays via useOverlay() hooks. The AddUsersModal feature is removed entirely. Export patterns in the modals index change from function invocations to lazy-loaded OverlayComponent exports. ColumnManagementModal tests are updated with additional properties.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title accurately describes the main objective: migrating core modals to PatternFly 6 Modal components, with '(part 2)' indicating continuation of a phased effort (CONSOLE-4447).

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

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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: 7

Caution

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

⚠️ Outside diff range comments (2)
frontend/public/components/modals/rollback-modal.tsx (1)

122-124: ⚠️ Potential issue | 🟡 Minor

Add .catch() for consistency and to suppress unhandled rejection warnings.

submitDCRollback (line 93) includes .catch(() => {}) after the promise chain, but submitDeploymentRollback does not. While usePromiseHandler handles error state internally, the missing catch can produce console warnings for unhandled rejections.

🔧 Proposed fix
-    handlePromise(k8sPatch(DeploymentModel, deployment, patch)).then(() => {
-      props.close();
-    });
+    handlePromise(k8sPatch(DeploymentModel, deployment, patch))
+      .then(() => {
+        props.close();
+      })
+      .catch(() => {});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/rollback-modal.tsx` around lines 122 - 124,
The promise returned by handlePromise(k8sPatch(DeploymentModel, deployment,
patch)) in submitDeploymentRollback lacks a .catch handler, which can produce
unhandled rejection warnings; update submitDeploymentRollback to append
.catch(() => {}) (matching submitDCRollback) to the promise chain so errors are
swallowed for console noise while usePromiseHandler still manages state—locate
the handlePromise call around k8sPatch/DeploymentModel/deployment/patch and add
the .catch accordingly before finishing with props.close().
frontend/public/components/modals/cluster-channel-modal.tsx (1)

45-46: ⚠️ Potential issue | 🟠 Major

Potential null pointer dereference when semver.parse returns null.

semver.parse() returns null when the version string is invalid or unparseable. Lines 96-107 access version.major and version.minor unconditionally within the !channelsExist branch, which would throw if version is null.

Consider adding a fallback or guard:

🛡️ Proposed fix with null guard
  const version = semver.parse(getLastCompletedUpdate(cv));
+  const versionDisplay = version ? `${version.major}.${version.minor}` : 'X.Y';

Then use versionDisplay in the placeholder and helper text templates:

-                    version: `stable-${version.major}.${version.minor}`,
+                    version: `stable-${versionDisplay}`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/cluster-channel-modal.tsx` around lines 45
- 46, semver.parse(getLastCompletedUpdate(cv)) can return null so avoid
dereferencing version; change the logic in cluster-channel-modal.tsx around the
const version = semver.parse(getLastCompletedUpdate(cv)) and subsequent
!channelsExist branch to first compute a safe versionDisplay (e.g., if version
is null use a fallback like "unknown" or the original string) and only access
version.major/major/ minor when version is non-null; replace direct uses of
version.major/version.minor in placeholder/helper text with the safe
versionDisplay and add a guard before any code that assumes version is an
object.
🧹 Nitpick comments (6)
frontend/public/components/modals/rollback-modal.tsx (1)

228-228: Minor inconsistency in disabled logic vs. error display condition.

Line 208 evaluates !loadError && !deploymentError (checks existence of loadError), while line 228 checks loadError?.message || deploymentError. If loadError exists but lacks a message property, the Alert would render but the button would remain enabled.

Consider aligning these conditions:

♻️ Suggested alignment
-          isDisabled={!!(loadError?.message || deploymentError)}
+          isDisabled={!!(loadError || deploymentError)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/rollback-modal.tsx` at line 228, The
disabled condition for the action button in rollback-modal.tsx is inconsistent
with the Alert rendering: the Alert uses loadError?.message but the button uses
isDisabled={!!(loadError?.message || deploymentError)}, so if loadError exists
without a message the Alert may render while the button remains enabled; update
the logic so both use the same predicate—either change the button's isDisabled
to use !!(loadError || deploymentError) or change the Alert rendering to check
loadError?.message—ensuring the symbols referenced (loadError, deploymentError,
isDisabled) are used consistently.
frontend/public/components/modals/taints-modal.tsx (1)

107-123: Consider using PatternFly TextInput for consistency.

The current approach wraps raw <input> elements in spans with pf-v6-c-form-control class. For full PatternFly 6 alignment and better maintainability, consider migrating to TextInput components. This is optional given the migration scope.

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

In `@frontend/public/components/modals/taints-modal.tsx` around lines 107 - 123,
Replace the raw input elements wrapped in spans in the taints modal with
PatternFly TextInput components to ensure consistent styling; specifically
change the inputs rendering c.key and c.value (the elements using value={c.key}
/ value={c.value} and onChange={(e) => change(e, i, 'key'|'value')}) to use
TextInput from PatternFly, wire its value and onChange handlers to call the
existing change function (adapting the TextInput onChange signature as needed),
and remove the manual pf-v6-c-form-control spans so the component
(taints-modal.tsx) uses TextInput for both key and value fields consistently.
frontend/public/components/modals/tolerations-modal.tsx (1)

242-248: TolerationsModalOverlay uses ModalWrapper while TaintsModalOverlay uses PF Modal directly.

The TolerationsModalOverlay wraps content in ModalWrapper (which uses react-modal internally per the relevant snippet), while the similar TaintsModalOverlay uses <Modal> from PatternFly directly. This creates an architectural inconsistency.

If ModalWrapper is required for the blocking behavior, consider documenting this or aligning the pattern across all modals.

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

In `@frontend/public/components/modals/tolerations-modal.tsx` around lines 242 -
248, TolerationsModalOverlay is inconsistent with TaintsModalOverlay because it
uses ModalWrapper (which delegates to react-modal) while TaintsModalOverlay uses
PatternFly's Modal; align them by either replacing ModalWrapper with
PatternFly's Modal in TolerationsModalOverlay or by updating TaintsModalOverlay
to use ModalWrapper so both overlays share the same modal implementation. Locate
TolerationsModalOverlay and TaintsModalOverlay and decide on a single modal
component (ModalWrapper or Modal), then update the chosen overlay(s) to use that
component and ensure blocking/close behavior is preserved (prop names: blocking,
onClose/closeOverlay) and add a brief comment documenting why ModalWrapper is
required if you keep it.
frontend/packages/console-app/src/actions/hooks/useCommonActions.ts (1)

13-13: Lazy-load TaintsModalOverlay for consistency with other modals.

LazyTolerationsModalOverlay is imported as a lazy component (line 10), but TaintsModalOverlay uses a direct import (line 13). To maintain architectural consistency and enable code splitting for this modal, create a LazyTaintsModalOverlay export in modals/index.ts and update the imports and usage in useCommonActions.ts (lines 13 and 146) to use the lazy variant.

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

In `@frontend/packages/console-app/src/actions/hooks/useCommonActions.ts` at line
13, TaintsModalOverlay is directly imported while other modals use lazy
wrappers; add a LazyTaintsModalOverlay export in the modals index (matching the
pattern used by LazyTolerationsModalOverlay) and then replace the direct
TaintsModalOverlay import in the useCommonActions hook with
LazyTaintsModalOverlay and update its usage where the taints modal is opened so
the lazy component is used instead of the direct import.
frontend/public/components/modals/cluster-update-modal.tsx (2)

269-281: Minor a11y note: fieldId doesn't match any child element id.

The FormGroup specifies fieldId="update-options", but neither Radio button uses that id. PatternFly uses fieldId to associate the label with a form control. Since these are radio buttons with individual id attributes, this is functionally fine, but for strict a11y compliance, consider wrapping the radios in a role="radiogroup" with aria-labelledby pointing to the group label, or removing the fieldId since the radios self-label.

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

In `@frontend/public/components/modals/cluster-update-modal.tsx` around lines 269
- 281, The FormGroup currently sets fieldId="update-options" but no child
control uses that id; update the markup around the Radio buttons (the group
rendered inside FormGroup in cluster-update-modal.tsx) to ensure proper
accessibility by either removing the unused fieldId from FormGroup or by
wrapping the radios in a container with role="radiogroup" and aria-labelledby
that points to the FormGroup label id (e.g., the element rendered by FormGroup);
adjust the radio container and/or label id so the label is programmatically
associated with the radio group while keeping each Radio's individual id intact.

151-157: Consider adding explicit .catch() for completeness.

The handlePromise utility captures errors internally, but the .then(() => close()) chain lacks a trailing .catch(). If close() ever throws (unlikely but defensive), it would be an unhandled rejection. This is minor since closeOverlay is a simple state setter.

♻️ Optional: Add catch for defensive error handling
       handlePromise(
         Promise.all([
           k8sPatch(ClusterVersionModel, cv, patch),
           ...MCPsToResumePromises,
           ...MCPsToPausePromises,
         ]),
-      ).then(() => close());
+      )
+        .then(() => close())
+        .catch(() => {});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/cluster-update-modal.tsx` around lines 151
- 157, The Promise chain using
handlePromise(Promise.all([k8sPatch(ClusterVersionModel, cv, patch),
...MCPsToResumePromises, ...MCPsToPausePromises])).then(() => close()) should
add a trailing .catch() to defensively handle any exceptions thrown by close()
(or any unexpected errors after handlePromise resolves); update the call site
referencing handlePromise, k8sPatch, ClusterVersionModel, MCPsToResumePromises,
MCPsToPausePromises and the close function to append .catch(err => { /* handle
or log error, e.g. console.error or processLogger */ }) so rejections are not
left unhandled.
🤖 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/public/components/cluster-settings/cluster-settings.tsx`:
- Around line 912-920: The effect that checks URLSearchParams and calls
launchModal (with LazyClusterUpdateModalOverlay or
LazyClusterChannelModalOverlay) can re-run when the cv object reference changes
and re-create URLSearchParams each time, causing duplicate modals; change the
logic so the URL param is read/memoized once (e.g., compute a
hasShowVersions/hasShowChannels boolean outside or via useMemo/useRef) and add a
local ref flag (e.g., modalOpenedRef) to ensure launchModal is only invoked once
per page visit, and still call removeQueryArgument after launching; keep
launchModal, removeQueryArgument and the overlay identifiers unchanged while
guarding repeated invocations.

In `@frontend/public/components/modals/cluster-channel-modal.tsx`:
- Around line 136-147: The Modal in ClusterChannelModalOverlay is missing an
aria-labelledby attribute so screen readers won't pick up the ModalHeader title;
update the Modal element in the ClusterChannelModalOverlay component to include
aria-labelledby="cluster-channel-modal-title" (the id used by the
ClusterChannelModal/ModalHeader title) so the Modal's accessible name is
properly wired when rendering <ClusterChannelModal ... />.

In `@frontend/public/components/modals/cluster-more-updates-modal.tsx`:
- Line 84: The Close button translation call is missing the `public~` namespace
prefix; inside the ClusterMoreUpdatesModal component replace the bare t('Close')
usage with the namespaced key t('public~Close') so it matches the other
translations in this file (e.g., the other t('public~...') calls) and keeps
consistent i18n lookup for the Close button.

In `@frontend/public/components/modals/column-management-modal.tsx`:
- Line 157: The DataList id contains a typo: change the id value
"defalt-column-management" to "default-column-management" wherever it appears
(e.g., in the DataList element identified by id="defalt-column-management") to
correct the spelling for consistency with any tests or accessibility references;
update any corresponding references (tests, labels, aria attributes) that rely
on this id to use "default-column-management" as well.

In `@frontend/public/components/modals/delete-pvc-modal.tsx`:
- Around line 79-88: The Delete Button currently uses isLoading={inProgress} but
remains clickable; update the Button (the element with id="confirm-action" and
form="delete-pvc-form") to also set isDisabled={inProgress} so the button is
actually disabled during the in-flight delete action; keep the existing
isLoading prop and ensure the change is applied in delete-pvc-modal.tsx where
the Button component is rendered.
- Around line 59-64: The Modal is missing an aria-labelledby attribute to
associate it with the ModalHeader title; update the parent <Modal> component
that contains ModalHeader to include aria-labelledby="delete-pvc-modal-title"
(matching the ModalHeader's labelId) so screen readers correctly link the dialog
to its visible title; locate the Modal wrapper around the
ModalHeader/ModalFooter in delete-pvc-modal.tsx and add the aria-labelledby prop
with that ID.

In `@frontend/public/components/modals/taints-modal.tsx`:
- Line 136: The Tooltip content currently uses the literal string "Remove";
update it to use the translation function like the aria-label does so it reads
Tooltip content={t('Remove')}. Locate the Tooltip element in the TaintsModal
component (the Tooltip wrapper around the remove button) and replace the static
"Remove" text with t('Remove') to ensure i18n consistency.

---

Outside diff comments:
In `@frontend/public/components/modals/cluster-channel-modal.tsx`:
- Around line 45-46: semver.parse(getLastCompletedUpdate(cv)) can return null so
avoid dereferencing version; change the logic in cluster-channel-modal.tsx
around the const version = semver.parse(getLastCompletedUpdate(cv)) and
subsequent !channelsExist branch to first compute a safe versionDisplay (e.g.,
if version is null use a fallback like "unknown" or the original string) and
only access version.major/major/ minor when version is non-null; replace direct
uses of version.major/version.minor in placeholder/helper text with the safe
versionDisplay and add a guard before any code that assumes version is an
object.

In `@frontend/public/components/modals/rollback-modal.tsx`:
- Around line 122-124: The promise returned by
handlePromise(k8sPatch(DeploymentModel, deployment, patch)) in
submitDeploymentRollback lacks a .catch handler, which can produce unhandled
rejection warnings; update submitDeploymentRollback to append .catch(() => {})
(matching submitDCRollback) to the promise chain so errors are swallowed for
console noise while usePromiseHandler still manages state—locate the
handlePromise call around k8sPatch/DeploymentModel/deployment/patch and add the
.catch accordingly before finishing with props.close().

---

Nitpick comments:
In `@frontend/packages/console-app/src/actions/hooks/useCommonActions.ts`:
- Line 13: TaintsModalOverlay is directly imported while other modals use lazy
wrappers; add a LazyTaintsModalOverlay export in the modals index (matching the
pattern used by LazyTolerationsModalOverlay) and then replace the direct
TaintsModalOverlay import in the useCommonActions hook with
LazyTaintsModalOverlay and update its usage where the taints modal is opened so
the lazy component is used instead of the direct import.

In `@frontend/public/components/modals/cluster-update-modal.tsx`:
- Around line 269-281: The FormGroup currently sets fieldId="update-options" but
no child control uses that id; update the markup around the Radio buttons (the
group rendered inside FormGroup in cluster-update-modal.tsx) to ensure proper
accessibility by either removing the unused fieldId from FormGroup or by
wrapping the radios in a container with role="radiogroup" and aria-labelledby
that points to the FormGroup label id (e.g., the element rendered by FormGroup);
adjust the radio container and/or label id so the label is programmatically
associated with the radio group while keeping each Radio's individual id intact.
- Around line 151-157: The Promise chain using
handlePromise(Promise.all([k8sPatch(ClusterVersionModel, cv, patch),
...MCPsToResumePromises, ...MCPsToPausePromises])).then(() => close()) should
add a trailing .catch() to defensively handle any exceptions thrown by close()
(or any unexpected errors after handlePromise resolves); update the call site
referencing handlePromise, k8sPatch, ClusterVersionModel, MCPsToResumePromises,
MCPsToPausePromises and the close function to append .catch(err => { /* handle
or log error, e.g. console.error or processLogger */ }) so rejections are not
left unhandled.

In `@frontend/public/components/modals/rollback-modal.tsx`:
- Line 228: The disabled condition for the action button in rollback-modal.tsx
is inconsistent with the Alert rendering: the Alert uses loadError?.message but
the button uses isDisabled={!!(loadError?.message || deploymentError)}, so if
loadError exists without a message the Alert may render while the button remains
enabled; update the logic so both use the same predicate—either change the
button's isDisabled to use !!(loadError || deploymentError) or change the Alert
rendering to check loadError?.message—ensuring the symbols referenced
(loadError, deploymentError, isDisabled) are used consistently.

In `@frontend/public/components/modals/taints-modal.tsx`:
- Around line 107-123: Replace the raw input elements wrapped in spans in the
taints modal with PatternFly TextInput components to ensure consistent styling;
specifically change the inputs rendering c.key and c.value (the elements using
value={c.key} / value={c.value} and onChange={(e) => change(e, i,
'key'|'value')}) to use TextInput from PatternFly, wire its value and onChange
handlers to call the existing change function (adapting the TextInput onChange
signature as needed), and remove the manual pf-v6-c-form-control spans so the
component (taints-modal.tsx) uses TextInput for both key and value fields
consistently.

In `@frontend/public/components/modals/tolerations-modal.tsx`:
- Around line 242-248: TolerationsModalOverlay is inconsistent with
TaintsModalOverlay because it uses ModalWrapper (which delegates to react-modal)
while TaintsModalOverlay uses PatternFly's Modal; align them by either replacing
ModalWrapper with PatternFly's Modal in TolerationsModalOverlay or by updating
TaintsModalOverlay to use ModalWrapper so both overlays share the same modal
implementation. Locate TolerationsModalOverlay and TaintsModalOverlay and decide
on a single modal component (ModalWrapper or Modal), then update the chosen
overlay(s) to use that component and ensure blocking/close behavior is preserved
(prop names: blocking, onClose/closeOverlay) and add a brief comment documenting
why ModalWrapper is required if you keep it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c3e14f62-3642-41a1-94ff-b76592c36de2

📥 Commits

Reviewing files that changed from the base of the PR and between 874c37e and 28b122d.

📒 Files selected for processing (15)
  • frontend/packages/console-app/src/actions/hooks/useCommonActions.ts
  • frontend/public/components/cluster-settings/cluster-settings.tsx
  • frontend/public/components/modals/__tests__/column-management-modal.spec.tsx
  • frontend/public/components/modals/add-users-modal.tsx
  • frontend/public/components/modals/cluster-channel-modal.tsx
  • frontend/public/components/modals/cluster-more-updates-modal.tsx
  • frontend/public/components/modals/cluster-update-modal.tsx
  • frontend/public/components/modals/column-management-modal.tsx
  • frontend/public/components/modals/delete-pvc-modal.tsx
  • frontend/public/components/modals/error-modal.tsx
  • frontend/public/components/modals/index.ts
  • frontend/public/components/modals/rollback-modal.tsx
  • frontend/public/components/modals/taints-modal.tsx
  • frontend/public/components/modals/tolerations-modal.tsx
  • frontend/public/locales/en/public.json
💤 Files with no reviewable changes (1)
  • frontend/public/components/modals/add-users-modal.tsx

Comment on lines +912 to +920
useEffect(() => {
if (new URLSearchParams(window.location.search).has('showVersions')) {
launchModal(LazyClusterUpdateModalOverlay, { cv });
removeQueryArgument('showVersions');
} else if (new URLSearchParams(window.location.search).has('showChannels')) {
launchModal(LazyClusterChannelModalOverlay, { cv });
removeQueryArgument('showChannels');
}
}, [launchModal, cv, removeQueryArgument]);
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

Potential issue with useEffect modal launching on URL params.

The useEffect creates new URLSearchParams on each execution and has cv in the dependency array. If cv object reference changes (common with K8s watch resources), this effect will re-run. Combined with the query param removal happening asynchronously, there's a potential race condition where the modal could be launched multiple times.

Consider:

  1. Using a ref to track if the modal has already been opened for the current URL params
  2. Moving the URLSearchParams check outside the effect body or memoizing it
🛡️ Suggested defensive pattern
+ import { useRef } from 'react';
...
+ const modalLaunchedRef = useRef(false);
+
  useEffect(() => {
+   if (modalLaunchedRef.current) return;
+   const params = new URLSearchParams(window.location.search);
-   if (new URLSearchParams(window.location.search).has('showVersions')) {
+   if (params.has('showVersions')) {
+     modalLaunchedRef.current = true;
      launchModal(LazyClusterUpdateModalOverlay, { cv });
      removeQueryArgument('showVersions');
-   } else if (new URLSearchParams(window.location.search).has('showChannels')) {
+   } else if (params.has('showChannels')) {
+     modalLaunchedRef.current = true;
      launchModal(LazyClusterChannelModalOverlay, { cv });
      removeQueryArgument('showChannels');
    }
  }, [launchModal, cv, removeQueryArgument]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/cluster-settings/cluster-settings.tsx` around
lines 912 - 920, The effect that checks URLSearchParams and calls launchModal
(with LazyClusterUpdateModalOverlay or LazyClusterChannelModalOverlay) can
re-run when the cv object reference changes and re-create URLSearchParams each
time, causing duplicate modals; change the logic so the URL param is
read/memoized once (e.g., compute a hasShowVersions/hasShowChannels boolean
outside or via useMemo/useRef) and add a local ref flag (e.g., modalOpenedRef)
to ensure launchModal is only invoked once per page visit, and still call
removeQueryArgument after launching; keep launchModal, removeQueryArgument and
the overlay identifiers unchanged while guarding repeated invocations.

Comment on lines +136 to +147
export const ClusterChannelModalOverlay: OverlayComponent<ClusterChannelModalProps> = (props) => {
return (
<Modal
isOpen
onClose={props.closeOverlay}
variant={ModalVariant.small}
data-test="channel-modal"
>
<ClusterChannelModal {...props} close={props.closeOverlay} cancel={props.closeOverlay} />
</Modal>
);
};
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

🧩 Analysis chain

🌐 Web query:

PatternFly 6 Modal aria-labelledby ModalHeader integration

💡 Result:

In PatternFly 6, wire the modal’s accessible name by making the Modal’s aria-labelledby point at the title element inside ModalHeader:

import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from '@patternfly/react-core';

const titleId = 'my-modal-title';
const descId = 'my-modal-desc';

<Modal
  isOpen={isOpen}
  onClose={onClose}
  aria-labelledby={titleId}      // should include the ModalHeader labelId
  aria-describedby={descId}      // optional; should match ModalHeader descriptorId
>
  <ModalHeader
    title="Delete project?"
    labelId={titleId}            // id of the modal title element
    description="This action cannot be undone."
    descriptorId={descId}        // id of the modal description element
  />
  <ModalBody>...</ModalBody>
  <ModalFooter>...</ModalFooter>
</Modal>

Modal.aria-labelledby is explicitly documented to “include the ModalHeader labelId,” and ModalHeader.labelId is the “Id of the modal title.” [1] In the HTML/accessibility guidance, aria-labelledby is the id of the element that labels (titles) the modal; if there’s no title, use aria-label instead. [2][3]


🏁 Script executed:

cd frontend/public/components/modals && cat -n cluster-channel-modal.tsx | head -100

Repository: openshift/console

Length of output: 4680


🏁 Script executed:

cd frontend/public/components/modals && sed -n '130,150p' cluster-channel-modal.tsx

Repository: openshift/console

Length of output: 546


Add aria-labelledby to the Modal to properly wire the accessible title.

Per PatternFly 6 documentation, the Modal component requires an explicit aria-labelledby attribute that references the ModalHeader's labelId. Add aria-labelledby="cluster-channel-modal-title" to the Modal wrapper (line 138) to ensure screen readers correctly announce the modal's accessible name.

Suggested fix
<Modal
  isOpen
  onClose={props.closeOverlay}
  variant={ModalVariant.small}
  data-test="channel-modal"
  aria-labelledby="cluster-channel-modal-title"
>
  <ClusterChannelModal {...props} close={props.closeOverlay} cancel={props.closeOverlay} />
</Modal>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/cluster-channel-modal.tsx` around lines 136
- 147, The Modal in ClusterChannelModalOverlay is missing an aria-labelledby
attribute so screen readers won't pick up the ModalHeader title; update the
Modal element in the ClusterChannelModalOverlay component to include
aria-labelledby="cluster-channel-modal-title" (the id used by the
ClusterChannelModal/ModalHeader title) so the Modal's accessible name is
properly wired when rendering <ClusterChannelModal ... />.

onClick={cancel}
data-test="more-updates-modal-close-button"
>
{t('Close')}
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

Translation key missing namespace prefix.

Other translations in this file use the public~ namespace prefix (e.g., lines 36, 47, 48). For consistency, the Close button text should also use the namespace prefix.

🌐 Proposed fix
-          {t('Close')}
+          {t('public~Close')}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{t('Close')}
{t('public~Close')}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/cluster-more-updates-modal.tsx` at line 84,
The Close button translation call is missing the `public~` namespace prefix;
inside the ClusterMoreUpdatesModal component replace the bare t('Close') usage
with the namespaced key t('public~Close') so it matches the other translations
in this file (e.g., the other t('public~...') calls) and keeps consistent i18n
lookup for the Close button.

</label>
<DataList
aria-label={t('public~Default column list')}
id="defalt-column-management"
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

Typo in DataList id: "defalt" should be "default".

Minor typo that could cause issues if this id is referenced elsewhere for testing or accessibility.

✏️ Proposed fix
               <DataList
                 aria-label={t('public~Default column list')}
-                id="defalt-column-management"
+                id="default-column-management"
                 isCompact
               >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
id="defalt-column-management"
<DataList
aria-label={t('public~Default column list')}
id="default-column-management"
isCompact
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/column-management-modal.tsx` at line 157,
The DataList id contains a typo: change the id value "defalt-column-management"
to "default-column-management" wherever it appears (e.g., in the DataList
element identified by id="defalt-column-management") to correct the spelling for
consistency with any tests or accessibility references; update any corresponding
references (tests, labels, aria attributes) that rely on this id to use
"default-column-management" as well.

Comment on lines +59 to +64
<ModalHeader
title={t('public~Delete PersistentVolumeClaim')}
titleIconVariant="warning"
data-test-id="modal-title"
labelId="delete-pvc-modal-title"
/>
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

🧩 Analysis chain

🌐 Web query:

PatternFly 6 Modal aria-labelledby usage with ModalHeader labelId

💡 Result:

In PatternFly 6 React, wire the modal’s accessible name to the actual title element by using the same ID for:

  • Modal aria-labelledby
  • ModalHeader labelId (this is the ID applied to the rendered modal title) [1]

Example:

<Modal
  isOpen
  aria-labelledby="my-modal-title"
  aria-describedby="my-modal-description"
  onClose={onClose}
>
  <ModalHeader
    title="Delete project?"
    labelId="my-modal-title"
    description="This action cannot be undone."
    descriptorId="my-modal-description"
  />
  <ModalBody>...</ModalBody>
  <ModalFooter>...</ModalFooter>
</Modal>

Notes:

  • PatternFly’s docs explicitly say Modal’s aria-labelledby “should include the ModalHeader labelId” [1], and that aria-labelledby is required when a visible title is present [2][3].
  • If you don’t render a header/title, use Modal’s aria-label instead [1][2].

Sources: [1] [2] [3]


Add aria-labelledby to the Modal component for accessibility compliance.

The labelId="delete-pvc-modal-title" on ModalHeader correctly creates an id for the title element, but the parent <Modal> component needs aria-labelledby="delete-pvc-modal-title" to properly associate the dialog with its title for screen readers. This is a required PatternFly 6 pattern when a visible title is present.

Proposed fix
-    <Modal isOpen onClose={props.closeOverlay} variant={ModalVariant.small}>
+    <Modal
+      isOpen
+      onClose={props.closeOverlay}
+      variant={ModalVariant.small}
+      aria-labelledby="delete-pvc-modal-title"
+    >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/delete-pvc-modal.tsx` around lines 59 - 64,
The Modal is missing an aria-labelledby attribute to associate it with the
ModalHeader title; update the parent <Modal> component that contains ModalHeader
to include aria-labelledby="delete-pvc-modal-title" (matching the ModalHeader's
labelId) so screen readers correctly link the dialog to its visible title;
locate the Modal wrapper around the ModalHeader/ModalFooter in
delete-pvc-modal.tsx and add the aria-labelledby prop with that ID.

Comment on lines +79 to +88
<Button
type="submit"
variant="danger"
isLoading={inProgress}
form="delete-pvc-form"
data-test="confirm-action"
id="confirm-action"
>
{t('public~Delete')}
</Button>
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

🌐 Web query:

PatternFly 6 Button isLoading prop does it disable button automatically

💡 Result:

No. In PatternFly v6, Button’s isLoading only adds progress/loading styling—it does not automatically disable the button. Disabling is controlled separately via isDisabled (or isAriaDisabled). [1]

Typical pattern:

<Button isLoading={isLoading} isDisabled={isLoading}>
  Save
</Button>

[1] (staging-v6.patternfly.org)

Citations:


Add isDisabled={inProgress} to prevent double-submission during delete.

PatternFly v6's isLoading prop only adds loading styling—it does not disable the button. Users can click the Delete button multiple times while the operation is in flight, causing unintended behavior. Pair isLoading with isDisabled={inProgress}:

Required fix
         <Button
           type="submit"
           variant="danger"
           isLoading={inProgress}
+          isDisabled={inProgress}
           form="delete-pvc-form"
           data-test="confirm-action"
           id="confirm-action"
         >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
type="submit"
variant="danger"
isLoading={inProgress}
form="delete-pvc-form"
data-test="confirm-action"
id="confirm-action"
>
{t('public~Delete')}
</Button>
<Button
type="submit"
variant="danger"
isLoading={inProgress}
isDisabled={inProgress}
form="delete-pvc-form"
data-test="confirm-action"
id="confirm-action"
>
{t('public~Delete')}
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/delete-pvc-modal.tsx` around lines 79 - 88,
The Delete Button currently uses isLoading={inProgress} but remains clickable;
update the Button (the element with id="confirm-action" and
form="delete-pvc-form") to also set isDisabled={inProgress} so the button is
actually disabled during the in-flight delete action; keep the existing
isLoading prop and ensure the change is applied in delete-pvc-modal.tsx where
the Button component is rendered.

/>
</Td>
<Td isActionCell>
<Tooltip content="Remove">
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

Missing translation for Tooltip content.

The Tooltip content "Remove" should use the translation function for i18n compliance. You're already using t('Remove') for the aria-label on line 144, so this should be consistent.

🌐 Proposed fix
-                      <Tooltip content="Remove">
+                      <Tooltip content={t('Remove')}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Tooltip content="Remove">
<Tooltip content={t('Remove')}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/modals/taints-modal.tsx` at line 136, The Tooltip
content currently uses the literal string "Remove"; update it to use the
translation function like the aria-label does so it reads Tooltip
content={t('Remove')}. Locate the Tooltip element in the TaintsModal component
(the Tooltip wrapper around the remove button) and replace the static "Remove"
text with t('Remove') to ensure i18n consistency.

@rhamilto rhamilto force-pushed the CONSOLE-4447-app-2 branch from 28b122d to f71e9b8 Compare March 9, 2026 19:41
@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

Migrates cluster and toleration modals from createModalLauncher to the modern useOverlay pattern with PatternFly 6 Modal components.

Migrated Modals

  • cluster-channel-modal - Update cluster channel selection
  • cluster-more-updates-modal - Display additional update paths
  • cluster-update-modal - Cluster version updates with MCP pause options
  • tolerations-modal - Edit pod/node tolerations

Code Review Fixes

Accessibility improvements:

  • Added aria-labelledby to all Modal components linking to ModalHeader labelId
  • Fixed FormGroup in cluster-update-modal to use role="radiogroup" instead of unused fieldId
  • Added aria-label to TextInput components in taints-modal
  • Added isDisabled to delete button while operation in progress

Error handling:

  • Added .catch(() => {}) to promise chains in cluster-update-modal and rollback-modal

i18n fixes:

  • Added missing public~ namespace prefix to Close button translation
  • Translated Tooltip content in taints-modal

Null safety:

  • Added defensive fallback for semver.parse in cluster-channel-modal using nullish coalescing

Code quality:

  • Fixed duplicate modal rendering in cluster-settings using useRef to track modal state
  • Migrated to useQueryParamsMutator hook's getQueryArgument for URL params
  • Replaced raw input elements with PatternFly TextInput components in taints-modal
  • Fixed inconsistent error checking in rollback-modal
  • Fixed typo: defalt-column-managementdefault-column-management
  • Migrated taints-modal to lazy loading pattern (LazyTaintsModalOverlay)

Test plan

  • Verify cluster channel modal opens and functions correctly
  • Verify cluster update modal displays available updates and handles MCP pausing
  • Verify more updates modal shows additional update paths
  • Verify tolerations modal allows editing tolerations
  • Test keyboard navigation and screen reader accessibility
  • Verify all modals properly close on cancel/submit
  • Verify no duplicate modals appear when navigating with URL params
  • Verify error states display correctly
  • Run yarn i18n to update translation keys
  • All unit and E2E tests pass

🤖 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.

@rhamilto rhamilto force-pushed the CONSOLE-4447-app-2 branch from f71e9b8 to d5b553e Compare March 9, 2026 20:01
rhamilto added a commit to rhamilto/console that referenced this pull request Mar 9, 2026
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>
@rhamilto rhamilto force-pushed the CONSOLE-4447-app-2 branch 3 times, most recently from 91860c3 to 0007a6d Compare March 9, 2026 21:17
Addresses code review feedback with fixes for accessibility, error handling, i18n, null safety, and code quality:

Accessibility improvements:
- Add aria-labelledby to Modal components linking to ModalHeader labelId
- Fix FormGroup in cluster-update-modal to use role="radiogroup" instead of unused fieldId
- Add aria-label to TextInput components in taints-modal
- Add isDisabled to delete button while operation in progress

Error handling:
- Add .catch(() => {}) to promise chains in cluster-update-modal and rollback-modal

i18n fixes:
- Add missing 'public~' namespace prefix to Close button translation
- Translate Tooltip content in taints-modal

Null safety:
- Add defensive fallback for semver.parse in cluster-channel-modal using nullish coalescing

Code quality:
- Fix duplicate modal rendering in cluster-settings using useRef to track modal state
- Use useQueryParamsMutator hook's getQueryArgument for URL params
- Replace raw input elements with PatternFly TextInput components in taints-modal
- Fix inconsistent error checking in rollback-modal
- Fix typo: defalt-column-management → default-column-management
- Migrate taints-modal to lazy loading pattern (LazyTaintsModalOverlay)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@rhamilto rhamilto force-pushed the CONSOLE-4447-app-2 branch from 0007a6d to 82af86a Compare March 10, 2026 11:33
@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 10, 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

Migrates multiple modals from legacy createModalLauncher/ModalWrapper to the modern useOverlay pattern with PatternFly 6 Modal components.

Migrated Modals

Cluster Settings & Updates:

  • cluster-channel-modal - Update cluster channel selection
  • cluster-more-updates-modal - Display additional update paths
  • cluster-update-modal - Cluster version updates with MCP pause options

Resource Management:

  • delete-pvc-modal - Delete PersistentVolumeClaim with extension point support
  • rollback-modal - Rollback deployments and deployment configs
  • column-management-modal - Manage table column visibility

Node Configuration:

  • taints-modal - Edit node taints with key/value/effect configuration
  • tolerations-modal - Edit pod/node tolerations

Utility Modals:

  • error-modal - Generic error display modal (added useErrorModalLauncher hook)
  • configure-count-modal - Configure replica/parallelism counts

Code Review Fixes

Accessibility improvements:

  • Added aria-labelledby to all Modal components linking to ModalHeader labelId
  • Fixed FormGroup in cluster-update-modal to use role="radiogroup" instead of unused fieldId
  • Added aria-label to TextInput components in taints-modal
  • Added isDisabled to delete button while operation in progress

Error handling:

  • Added .catch(() => {}) to promise chains in cluster-update-modal and rollback-modal

i18n fixes:

  • Added missing public~ namespace prefix to Close button translation
  • Translated Tooltip content in taints-modal

Null safety:

  • Added defensive fallback for semver.parse in cluster-channel-modal using nullish coalescing

Code quality:

  • Fixed duplicate modal rendering in cluster-settings using useRef to track modal state
  • Migrated to useQueryParamsMutator hook's getQueryArgument for URL params
  • Replaced raw input elements with PatternFly TextInput components in taints-modal
  • Fixed inconsistent error checking in rollback-modal
  • Fixed typo: defalt-column-managementdefault-column-management
  • Migrated taints-modal to lazy loading pattern (LazyTaintsModalOverlay)
  • Added export for modal overlay components in modals/index.ts

Test plan

  • Verify cluster channel modal opens and functions correctly
  • Verify cluster update modal displays available updates and handles MCP pausing
  • Verify more updates modal shows additional update paths
  • Verify tolerations modal allows editing tolerations
  • Verify taints modal allows editing node taints
  • Verify delete PVC modal shows extension alerts and deletes correctly
  • Verify rollback modal works for both Deployments and DeploymentConfigs
  • Verify column management modal saves column preferences
  • Verify error modal displays correctly
  • Verify configure count modal updates counts correctly
  • Test keyboard navigation and screen reader accessibility
  • Verify all modals properly close on cancel/submit
  • Verify no duplicate modals appear when navigating with URL params
  • Verify error states display correctly
  • Run yarn i18n to update translation keys
  • All unit and E2E tests pass

🤖 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.

@rhamilto rhamilto changed the title [WIP] CONSOLE-4447: Migrate modals to PatternFly 6 Modal components (part 2) CONSOLE-4447: Migrate modals to PatternFly 6 Modal components (part 2) Mar 10, 2026
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 10, 2026
@rhamilto
Copy link
Member Author

Tech debt
/label docs-approved
/label px-approved

@openshift-ci openshift-ci bot added docs-approved Signifies that Docs has signed off on this PR px-approved Signifies that Product Support has signed off on this PR labels Mar 10, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 10, 2026

@rhamilto: all tests passed!

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 added a commit to rhamilto/console that referenced this pull request Mar 10, 2026
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>
@rhamilto
Copy link
Member Author

/assign @vikram-raj
/assign @yapei

rhamilto added a commit to rhamilto/console that referenced this pull request Mar 10, 2026
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>
rhamilto added a commit to rhamilto/console that referenced this pull request Mar 10, 2026
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>
@rhamilto rhamilto changed the title CONSOLE-4447: Migrate modals to PatternFly 6 Modal components (part 2) CONSOLE-4447: Migrate core modals to PatternFly 6 Modal components (part 2) Mar 10, 2026
rhamilto added a commit to rhamilto/console that referenced this pull request Mar 10, 2026
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>
rhamilto added a commit to rhamilto/console that referenced this pull request Mar 10, 2026
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>
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 docs-approved Signifies that Docs has signed off on this PR 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 px-approved Signifies that Product Support has signed off on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants