From 6a2977364297a8c91a2e4d8bb3604bdfec02e64f Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Wed, 21 Jan 2026 11:27:11 -0500 Subject: [PATCH 01/21] Migrate TaintsModal from createModalLauncher to useOverlay Remove React Router wrapper from taints modal to unblock Router v7 upgrade. Part of CONSOLE-5012 to eliminate all createModalLauncher usage. Assisted by: Claude Code --- .../src/actions/hooks/useCommonActions.ts | 6 ++-- frontend/public/components/modals/index.ts | 2 +- .../public/components/modals/taints-modal.tsx | 31 +++++++++++++++---- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts b/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts index 29623fc0f41..90b88b2500f 100644 --- a/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts @@ -7,10 +7,10 @@ import { LazyAnnotationsModalOverlay, LazyDeleteModalOverlay, LazyLabelsModalOverlay, - taintsModal, tolerationsModal, } from '@console/internal/components/modals'; import { useConfigureCountModal } from '@console/internal/components/modals/configure-count-modal'; +import TaintsModalProvider from '@console/internal/components/modals/taints-modal'; import { asAccessReview } from '@console/internal/components/utils/rbac'; import { resourceObjPath } from '@console/internal/components/utils/resource-link'; import { referenceFor, K8sModel, K8sResourceKind } from '@console/internal/module/k8s'; @@ -141,7 +141,7 @@ export const useCommonActions = ( id: 'edit-taints', label: t('console-app~Edit taints'), cta: () => - taintsModal({ + launchModal(TaintsModalProvider, { resourceKind: kind, resource, }), @@ -163,7 +163,7 @@ export const useCommonActions = ( // to prevent unnecessary re-renders // TODO: remove once all Modals have been updated to useOverlay // eslint-disable-next-line react-hooks/exhaustive-deps - [kind, resource, t, message, actualEditPath, launchModal], + [kind, resource, t, message, actualEditPath, launchModal] ); const result = useMemo((): [ActionObject, boolean] => { diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index 246bc9f2c1c..6164a84ebc7 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -88,7 +88,7 @@ export const clusterUpdateModal = (props) => ); export const taintsModal = (props) => - import('./taints-modal' /* webpackChunkName: "taints-modal" */).then((m) => m.taintsModal(props)); + import('./taints-modal' /* webpackChunkName: "taints-modal" */).then((m) => m.default(props)); export const tolerationsModal = (props) => import('./tolerations-modal' /* webpackChunkName: "tolerations-modal" */).then((m) => diff --git a/frontend/public/components/modals/taints-modal.tsx b/frontend/public/components/modals/taints-modal.tsx index f57db32da83..4b6e6d04850 100644 --- a/frontend/public/components/modals/taints-modal.tsx +++ b/frontend/public/components/modals/taints-modal.tsx @@ -9,15 +9,16 @@ import { useTranslation } from 'react-i18next'; import { ConsoleSelect } from '@console/internal/components/utils/console-select'; import { EmptyBox } from '../utils/status-box'; -import { K8sKind, k8sPatch, NodeKind, Taint } from '../../module/k8s'; +import { K8sKind, K8sResourceKind, k8sPatch, Taint } from '../../module/k8s'; import { - createModalLauncher, ModalBody, ModalComponentProps, ModalSubmitFooter, ModalTitle, + ModalWrapper, } from '../factory'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; const TaintsModal = (props: TaintsModalProps) => { const [taints, setTaints] = useState(props.resource.spec.taints || []); @@ -92,7 +93,7 @@ const TaintsModal = (props: TaintsModalProps) => { - {_.map(taints, (c, i) => ( + {_.map(taints, (c, i: number) => ( @@ -159,10 +160,28 @@ const TaintsModal = (props: TaintsModalProps) => { ); }; -export const taintsModal = createModalLauncher(TaintsModal); - export type TaintsModalProps = { resourceKind: K8sKind; - resource: NodeKind; + resource: K8sResourceKind; close: () => void; } & ModalComponentProps; + +type TaintsModalProviderProps = { + resourceKind: K8sKind; + resource: K8sResourceKind; +}; + +const TaintsModalProvider: OverlayComponent = (props) => { + return ( + + + + ); +}; + +export default TaintsModalProvider; From e980afa795ecdd50399f81b33b3d6b999e25e12d Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Thu, 22 Jan 2026 12:29:47 -0500 Subject: [PATCH 02/21] Migrate OLM UpdateStrategyModal from createModalLauncher to useOverlay Converts the modal to use the new overlay pattern, replacing createModalLauncher with useOverlay hook. Assisted by: Claude Code --- .../spec/configure-update-strategy.tsx | 24 +++++++++------ .../src/components/descriptors/spec/index.tsx | 18 ++++++------ .../modals/update-strategy-modal.tsx | 29 +++++++++++++++++-- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/configure-update-strategy.tsx b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/configure-update-strategy.tsx index 9d70009afff..8f5090c1ee2 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/configure-update-strategy.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/configure-update-strategy.tsx @@ -1,22 +1,28 @@ +import { useCallback } from 'react'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import i18n from '@console/internal/i18n'; import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; -import { updateStrategyModal } from '../../modals/update-strategy-modal'; +import UpdateStrategyModalProvider from '../../modals/update-strategy-modal'; import { Descriptor } from '../types'; import { getPatchPathFromDescriptor } from '../utils'; -export const configureUpdateStrategyModal = ({ +export const useConfigureUpdateStrategyModal = ({ kindObj, resource, specDescriptor, specValue, }: ConfigureUpdateStrategyModalProps) => { - return updateStrategyModal({ - resourceKind: kindObj, - resource, - defaultValue: specValue, - title: i18n.t('olm~Edit {{item}}', { item: specDescriptor.displayName }), - path: `/spec/${getPatchPathFromDescriptor(specDescriptor)}`, - }); + const launchOverlay = useOverlay(); + + return useCallback(() => { + return launchOverlay(UpdateStrategyModalProvider, { + resourceKind: kindObj, + resource, + defaultValue: specValue, + title: i18n.t('olm~Edit {{item}}', { item: specDescriptor.displayName }), + path: `/spec/${getPatchPathFromDescriptor(specDescriptor)}`, + }); + }, [launchOverlay, kindObj, resource, specValue, specDescriptor]); }; type ConfigureUpdateStrategyModalProps = { diff --git a/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/index.tsx b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/index.tsx index a5145dc1d06..5032474f98f 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/index.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/index.tsx @@ -26,7 +26,7 @@ import { DefaultCapability, K8sResourceLinkCapability, SecretCapability } from ' import { CapabilityProps, SpecCapability, Error } from '../types'; import { getPatchPathFromDescriptor, getValidCapabilitiesForValue } from '../utils'; import { useConfigureSizeModal } from './configure-size'; -import { configureUpdateStrategyModal } from './configure-update-strategy'; +import { useConfigureUpdateStrategyModal } from './configure-update-strategy'; import { EndpointList, EndpointListProps } from './endpoint'; import { ResourceRequirementsModalLink } from './resource-requirements'; @@ -287,19 +287,19 @@ const UpdateStrategy: FC = ({ value, }) => { const { t } = useTranslation(); + const launchUpdateStrategyModal = useConfigureUpdateStrategyModal({ + kindObj: model, + resource: obj, + specDescriptor: descriptor, + specValue: value, + }); + return ( - configureUpdateStrategyModal({ - kindObj: model, - resource: obj, - specDescriptor: descriptor, - specValue: value, - }) - } + onEdit={launchUpdateStrategyModal} path={fullPath} > {value?.type ?? t('public~None')} diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/update-strategy-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/update-strategy-modal.tsx index 52eb451b181..3a5b768f02c 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/update-strategy-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/update-strategy-modal.tsx @@ -2,11 +2,12 @@ import type { FC, FormEvent } from 'react'; import { useState, useCallback } from 'react'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { - createModalLauncher, ModalBody, ModalSubmitFooter, ModalTitle, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { ConfigureUpdateStrategy, @@ -84,7 +85,31 @@ export const UpdateStrategyModal: FC = ({ ); }; -export const updateStrategyModal = createModalLauncher(UpdateStrategyModal); +type UpdateStrategyModalProviderProps = { + defaultValue: any; + path: string; + resource: K8sResourceKind; + resourceKind: K8sKind; + title: string; +}; + +const UpdateStrategyModalProvider: OverlayComponent = (props) => { + return ( + + + + ); +}; + +export default UpdateStrategyModalProvider; UpdateStrategyModal.displayName = 'UpdateStrategyModal'; From fcaef0002c53223bf07464fe55ce622a38d53cdc Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Fri, 23 Jan 2026 12:30:15 -0500 Subject: [PATCH 03/21] Migrate resource-requirements modal --- .../__tests__/resource-requirements.spec.tsx | 92 ++++--------- .../spec/resource-requirements.tsx | 122 +++++++++++------- 2 files changed, 101 insertions(+), 113 deletions(-) diff --git a/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/__tests__/resource-requirements.spec.tsx b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/__tests__/resource-requirements.spec.tsx index afb86765694..4476045ed96 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/__tests__/resource-requirements.spec.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/__tests__/resource-requirements.spec.tsx @@ -1,7 +1,5 @@ import { screen, waitFor, fireEvent } from '@testing-library/react'; -import { Map as ImmutableMap } from 'immutable'; import * as k8sResourceModule from '@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource'; -import * as modal from '@console/internal/components/factory/modal'; import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import { testResourceInstance, testModel } from '../../../../../mocks'; @@ -11,18 +9,23 @@ import { ResourceRequirementsModalLink, } from '../resource-requirements'; +const useK8sModelMock = jest.fn(); +const useOverlayMock = jest.fn(); + jest.mock('@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource', () => ({ ...jest.requireActual('@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource'), k8sUpdate: jest.fn(), })); -jest.mock('@console/internal/components/factory/modal', () => ({ - ...jest.requireActual('@console/internal/components/factory/modal'), - createModalLauncher: jest.fn(), +jest.mock('@console/shared/src/hooks/useK8sModel', () => ({ + useK8sModel: (...args) => useK8sModelMock(...args), +})); + +jest.mock('@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay', () => ({ + useOverlay: (...args) => useOverlayMock(...args), })); const k8sUpdateMock = k8sResourceModule.k8sUpdate as jest.Mock; -const createModalLauncherMock = modal.createModalLauncher as jest.Mock; describe('ResourceRequirementsModal', () => { const title = 'TestResource Resource Requests'; @@ -94,6 +97,7 @@ describe('ResourceRequirementsModal', () => { describe('ResourceRequirementsModalLink', () => { let obj: K8sResourceKind; + let launchOverlayMock: jest.Mock; beforeEach(() => { obj = { @@ -106,23 +110,17 @@ describe('ResourceRequirementsModalLink', () => { }, }, }; + + launchOverlayMock = jest.fn(); + useK8sModelMock.mockReturnValue([testModel, false]); + useOverlayMock.mockReturnValue(launchOverlayMock); + jest.clearAllMocks(); }); it('should render button link with resource requests', () => { renderWithProviders( , - { - initialState: { - k8s: ImmutableMap({ - RESOURCES: ImmutableMap({ - models: ImmutableMap({ - 'testapp.coreos.com~v1alpha1~TestResource': testModel, - }), - }), - }), - }, - }, ); const { memory, cpu, 'ephemeral-storage': storage } = obj.spec.resources.requests; @@ -132,20 +130,7 @@ describe('ResourceRequirementsModalLink', () => { }); it('should render button link with resource limits', () => { - renderWithProviders( - , - { - initialState: { - k8s: ImmutableMap({ - RESOURCES: ImmutableMap({ - models: ImmutableMap({ - 'testapp.coreos.com~v1alpha1~TestResource': testModel, - }), - }), - }), - }, - }, - ); + renderWithProviders(); const { memory, cpu, 'ephemeral-storage': storage } = obj.spec.resources.limits; expect( @@ -165,17 +150,6 @@ describe('ResourceRequirementsModalLink', () => { }; renderWithProviders( , - { - initialState: { - k8s: ImmutableMap({ - RESOURCES: ImmutableMap({ - models: ImmutableMap({ - 'testapp.coreos.com~v1alpha1~TestResource': testModel, - }), - }), - }), - }, - }, ); expect( @@ -186,38 +160,22 @@ describe('ResourceRequirementsModalLink', () => { }); it('should open resource requirements modal when button is clicked', async () => { - const modalSpy = jest.fn(); - createModalLauncherMock.mockReturnValue(modalSpy); - - renderWithProviders( - , - { - initialState: { - k8s: ImmutableMap({ - RESOURCES: ImmutableMap({ - models: ImmutableMap({ - 'testapp.coreos.com~v1alpha1~TestResource': testModel, - }), - }), - }), - }, - }, - ); + renderWithProviders(); fireEvent.click(screen.getByRole('button')); await waitFor(() => { - expect(modalSpy).toHaveBeenCalled(); + expect(launchOverlayMock).toHaveBeenCalled(); }); - const modalArgs = modalSpy.mock.calls[0][0]; - expect(modalArgs.title).toEqual(`${obj.kind} Resource Limits`); - expect(modalArgs.description).toEqual( + const [, modalProps] = launchOverlayMock.mock.calls[0]; + expect(modalProps.title).toEqual(`${obj.kind} Resource Limits`); + expect(modalProps.description).toEqual( 'Define the resource limits for this TestResource instance.', ); - expect(modalArgs.obj).toEqual(obj); - expect(modalArgs.model).toEqual(testModel); - expect(modalArgs.type).toEqual('limits'); - expect(modalArgs.path).toEqual('resources'); + expect(modalProps.obj).toEqual(obj); + expect(modalProps.model).toEqual(testModel); + expect(modalProps.type).toEqual('limits'); + expect(modalProps.path).toEqual('resources'); }); }); diff --git a/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/resource-requirements.tsx b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/resource-requirements.tsx index cccb47c6cfa..357209cb5c1 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/resource-requirements.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/resource-requirements.tsx @@ -4,16 +4,17 @@ import { Button, Grid, GridItem } from '@patternfly/react-core'; import { PencilAltIcon } from '@patternfly/react-icons/dist/esm/icons/pencil-alt-icon'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { - createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { k8sUpdate, referenceFor, K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; -import { RootState } from '@console/internal/redux'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; +import { useK8sModel } from '@console/shared/src/hooks/useK8sModel'; export const ResourceRequirements: FC = (props) => { const { t } = useTranslation(); @@ -85,7 +86,7 @@ export const ResourceRequirements: FC = (props) => { export const ResourceRequirementsModal = (props: ResourceRequirementsModalProps) => { const { t } = useTranslation(); const [handlePromise, inProgress, errorMessage] = usePromiseHandler(); - const { obj, path, type, model, close } = props; + const { obj, path, type, model, close, cancel } = props; const [cpu, setCPU] = useState(_.get(obj.spec, `${path}.${type}.cpu`, '')); const [memory, setMemory] = useState(_.get(obj.spec, `${path}.${type}.memory`, '')); const [storage, setStorage] = useState( @@ -127,56 +128,86 @@ export const ResourceRequirementsModal = (props: ResourceRequirementsModalProps) errorMessage={errorMessage} inProgress={inProgress} submitText={t('public~Save')} - cancel={props.cancel} + cancel={cancel} /> ); }; -const stateToProps = ({ k8s }: RootState, { obj }) => ({ - model: k8s.getIn(['RESOURCES', 'models', referenceFor(obj)]) as K8sKind, -}); +const ResourceRequirementsModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; -export const ResourceRequirementsModalLink = connect(stateToProps)( - (props: ResourceRequirementsModalLinkProps) => { - const { obj, type, path, model } = props; - const { t } = useTranslation(); - const none = t('public~None'); - const { cpu, memory, 'ephemeral-storage': storage } = _.get(obj.spec, `${path}.${type}`, {}); +export const ResourceRequirementsModalLink: FC = (props) => { + const { obj, type, path } = props; + const { t } = useTranslation(); + const launchOverlay = useOverlay(); + const [model] = useK8sModel(referenceFor(obj)); + const none = t('public~None'); + const { cpu, memory, 'ephemeral-storage': storage } = _.get(obj.spec, `${path}.${type}`, {}); - const onClick = () => { - const modal = createModalLauncher(ResourceRequirementsModal); - const description = t('olm~Define the resource {{type}} for this {{kind}} instance.', { - type, - kind: obj.kind, - }); - const title = t('olm~{{kind}} Resource {{type}}', { - kind: obj.kind, - type: _.capitalize(type), - }); + const onClick = () => { + const description = t('olm~Define the resource {{type}} for this {{kind}} instance.', { + type, + kind: obj.kind, + }); + const title = t('olm~{{kind}} Resource {{type}}', { + kind: obj.kind, + type: _.capitalize(type), + }); - return modal({ title, description, obj, model, type, path }); - }; + launchOverlay(ResourceRequirementsModalProvider, { + title, + description, + obj, + model, + type, + path, + }); + }; - return ( - - ); - }, -); + return ( + + ); +}; + +type ResourceRequirementsModalProviderProps = { + title: string; + description: string; + obj: K8sResourceKind; + model: K8sKind; + type: 'requests' | 'limits'; + path: string; +}; export type ResourceRequirementsModalProps = { title: string; @@ -201,7 +232,6 @@ export type ResourceRequirementsProps = { export type ResourceRequirementsModalLinkProps = { obj: K8sResourceKind; - model: K8sKind; type: 'requests' | 'limits'; path: string; }; From 2a42822fdddff3f17a04cbc3d5a09166ece35d93 Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Fri, 23 Jan 2026 16:22:52 -0500 Subject: [PATCH 04/21] Migrate EditDefaultSourcesModal from createModalLauncher to useOverlay --- .../modals/edit-default-sources-modal.tsx | 28 ++++++++++++++++--- .../operator-hub/operator-hub-details.tsx | 6 ++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx index 136385f1737..c649f83d0ba 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/edit-default-sources-modal.tsx @@ -2,13 +2,13 @@ import type { FC, FormEvent } from 'react'; import { useState, useCallback } from 'react'; import { Alert, Form, FormGroup } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { Checkbox } from '@console/internal/components/checkbox'; import { ModalTitle, ModalBody, - createModalLauncher, - ModalComponentProps, ModalSubmitFooter, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { k8sPatch } from '@console/internal/module/k8s'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; @@ -109,8 +109,28 @@ const EditDefaultSourcesModal: FC = ({ ); }; -export const editDefaultSourcesModal = createModalLauncher(EditDefaultSourcesModal); +const EditDefaultSourcesModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; + +type EditDefaultSourcesModalProviderProps = { + operatorHub: OperatorHubKind; +}; type EditDefaultSourcesModalProps = { operatorHub: OperatorHubKind; -} & ModalComponentProps; + close?: () => void; + cancel?: () => void; +}; + +export default EditDefaultSourcesModalProvider; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-details.tsx b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-details.tsx index 081366efad4..c790a31fa6c 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-details.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-details.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react'; import { DescriptionList, Grid, GridItem } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { DetailsPage, DetailsPageProps } from '@console/internal/components/factory'; import { navFactory, @@ -12,11 +13,12 @@ import { import PaneBody from '@console/shared/src/components/layout/PaneBody'; import { OperatorHubModel } from '../../models'; import { CatalogSourceListPage, CatalogSourceListPageProps } from '../catalog-source'; -import { editDefaultSourcesModal } from '../modals/edit-default-sources-modal'; +import EditDefaultSourcesModalProvider from '../modals/edit-default-sources-modal'; import { OperatorHubKind } from '.'; const OperatorHubDetails: FC = ({ obj: operatorHub }) => { const { t } = useTranslation(); + const launchOverlay = useOverlay(); const canEditDefaultSources = useAccessReview({ group: OperatorHubModel.apiGroup, @@ -42,7 +44,7 @@ const OperatorHubDetails: FC = ({ obj: operatorHub }) = obj={operatorHub} path="status.sources" canEdit={canEditDefaultSources} - onEdit={() => editDefaultSourcesModal({ operatorHub })} + onEdit={() => launchOverlay(EditDefaultSourcesModalProvider, { operatorHub })} editAsGroup hideEmpty > From 41dd7fa1022c7e68d62c746d9893e978d3431bef Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Fri, 23 Jan 2026 17:27:13 -0500 Subject: [PATCH 05/21] Migrate DisableDefaultSourceModal from createModalLauncher to useOverlay --- .../providers/catalog-source-provider.ts | 13 ++++++-- .../modals/disable-default-source-modal.tsx | 32 ++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/frontend/packages/operator-lifecycle-manager/src/actions/providers/catalog-source-provider.ts b/frontend/packages/operator-lifecycle-manager/src/actions/providers/catalog-source-provider.ts index d006f648a66..4c8885fac3b 100644 --- a/frontend/packages/operator-lifecycle-manager/src/actions/providers/catalog-source-provider.ts +++ b/frontend/packages/operator-lifecycle-manager/src/actions/providers/catalog-source-provider.ts @@ -4,10 +4,11 @@ import { CommonActionCreator } from '@console/app/src/actions/hooks/types'; import { useCommonActions } from '@console/app/src/actions/hooks/useCommonActions'; import { useCommonResourceActions } from '@console/app/src/actions/hooks/useCommonResourceActions'; import { Action } from '@console/dynamic-plugin-sdk/src'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { asAccessReview } from '@console/internal/components/utils/rbac'; import { referenceFor } from '@console/internal/module/k8s'; import { useK8sModel } from '@console/shared/src/hooks/useK8sModel'; -import { disableDefaultSourceModal } from '../../components/modals/disable-default-source-modal'; +import DisableDefaultSourceModalProvider from '../../components/modals/disable-default-source-modal'; import { OperatorHubKind } from '../../components/operator-hub'; import { DEFAULT_SOURCE_NAMESPACE } from '../../const'; import { OperatorHubModel } from '../../models'; @@ -16,16 +17,22 @@ import useOperatorHubConfig from '../../utils/useOperatorHubConfig'; const useDisableSourceAction = (operatorHub: OperatorHubKind, sourceName: string): Action[] => { const { t } = useTranslation(); + const launchOverlay = useOverlay(); const factory = useMemo( () => ({ disableSource: () => ({ id: 'disable-source', label: t('olm~Disable'), - cta: () => disableDefaultSourceModal({ kind: OperatorHubModel, operatorHub, sourceName }), + cta: () => + launchOverlay(DisableDefaultSourceModalProvider, { + kind: OperatorHubModel, + operatorHub, + sourceName, + }), accessReview: asAccessReview(OperatorHubModel, operatorHub, 'patch'), }), }), - [t, operatorHub, sourceName], + [t, operatorHub, sourceName, launchOverlay], ); const action = useMemo(() => [factory.disableSource()], [factory]); return action; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/disable-default-source-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/disable-default-source-modal.tsx index d82645c76f9..e32f5268b31 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/disable-default-source-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/disable-default-source-modal.tsx @@ -2,12 +2,12 @@ import type { FC, FormEvent } from 'react'; import { useCallback } from 'react'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { - createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, - ModalComponentProps, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { k8sPatch, K8sKind } from '@console/internal/module/k8s'; import { YellowExclamationTriangleIcon } from '@console/shared'; @@ -71,10 +71,34 @@ const DisableDefaultSourceModal: FC = ({ ); }; +const DisableDefaultSourceModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; + +type DisableDefaultSourceModalProviderProps = { + kind: K8sKind; + operatorHub: OperatorHubKind; + sourceName: string; +}; + type DisableSourceModalProps = { kind: K8sKind; operatorHub: OperatorHubKind; sourceName: string; -} & ModalComponentProps; + close?: () => void; + cancel?: () => void; +}; -export const disableDefaultSourceModal = createModalLauncher(DisableDefaultSourceModal); +export default DisableDefaultSourceModalProvider; From 52756914c739929adb4f81a6cab4b3f4e3e602ec Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Tue, 27 Jan 2026 16:46:33 -0500 Subject: [PATCH 06/21] Migrate InstallPlanApprovalModal from createModalLauncher to useOverlay --- .../installplan-approval-modal.spec.tsx | 1 + .../modals/installplan-approval-modal.tsx | 28 ++++++++++++++++--- .../src/components/subscription.tsx | 18 ++++++++---- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx index 5f0da26d30d..053f5ebe9ef 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx @@ -20,6 +20,7 @@ jest.mock('@console/internal/components/factory/modal', () => ({ ModalTitle: jest.fn(({ children }) => children), ModalBody: jest.fn(({ children }) => children), ModalSubmitFooter: jest.fn(() => null), + ModalWrapper: jest.fn(({ children }) => children), })); const mockModelFor = modelFor as jest.Mock; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-approval-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-approval-modal.tsx index 9faf411e29c..339bed3da31 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-approval-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-approval-modal.tsx @@ -3,11 +3,12 @@ import { useState, useCallback } from 'react'; import { Grid, GridItem, Radio } from '@patternfly/react-core'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { - createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { K8sKind, @@ -108,9 +109,26 @@ export const InstallPlanApprovalModal: FC = ({ ); }; -export const createInstallPlanApprovalModal = createModalLauncher( - InstallPlanApprovalModal, -); +const InstallPlanApprovalModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; + +type InstallPlanApprovalModalProviderProps = { + obj: InstallPlanKind | SubscriptionKind; + k8sUpdate: (kind: K8sKind, newObj: K8sResourceKind) => Promise; + // closeOverlay is added automatically by OverlayComponent wrapper +}; export type InstallPlanApprovalModalProps = { cancel?: () => void; @@ -118,3 +136,5 @@ export type InstallPlanApprovalModalProps = { k8sUpdate: (kind: K8sKind, newObj: K8sResourceKind) => Promise; obj: InstallPlanKind | SubscriptionKind; }; + +export default InstallPlanApprovalModalProvider; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx b/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx index 92cc029428a..7446616a030 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-use-before-define */ import type { FC } from 'react'; -import { useRef, useState, useEffect, useMemo } from 'react'; +import { useRef, useState, useEffect, useMemo, useCallback } from 'react'; import { Alert, Button, @@ -23,6 +23,7 @@ import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { Link, useParams } from 'react-router-dom-v5-compat'; import { ResourceStatus, StatusIconAndText } from '@console/dynamic-plugin-sdk'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { getGroupVersionKindForModel, K8sResourceKind, @@ -97,7 +98,7 @@ import { DeprecatedOperatorWarningIcon, findDeprecatedOperator, } from './deprecated-operator-warnings/deprecated-operator-warnings'; -import { createInstallPlanApprovalModal } from './modals/installplan-approval-modal'; +import InstallPlanApprovalModalProvider from './modals/installplan-approval-modal'; import { createSubscriptionChannelModal } from './modals/subscription-channel-modal'; import { useUninstallOperatorModal } from './modals/uninstall-operator-modal'; import { requireOperatorGroup } from './operator-group'; @@ -564,6 +565,7 @@ export const SubscriptionUpdates: FC = ({ subscriptions, }) => { const { t } = useTranslation(); + const launchOverlay = useOverlay(); const prevInstallPlanApproval = useRef(obj?.spec?.installPlanApproval); const prevChannel = useRef(obj?.spec?.channel); const [waitingForUpdate, setWaitingForUpdate] = useState(false); @@ -581,11 +583,17 @@ export const SubscriptionUpdates: FC = ({ } }, [obj, waitingForUpdate]); - const k8sUpdateAndWait = (kind: K8sKind, resource: K8sResourceCommon) => - k8sUpdate(kind, resource).then(() => setWaitingForUpdate(true)); + const k8sUpdateAndWait = useCallback( + (kind: K8sKind, resource: K8sResourceCommon) => + k8sUpdate(kind, resource).then(() => setWaitingForUpdate(true)), + [setWaitingForUpdate], + ); const channelModal = () => createSubscriptionChannelModal({ subscription: obj, pkg, k8sUpdate: k8sUpdateAndWait }); - const approvalModal = () => createInstallPlanApprovalModal({ obj, k8sUpdate: k8sUpdateAndWait }); + const approvalModal = useCallback( + () => launchOverlay(InstallPlanApprovalModalProvider, { obj, k8sUpdate: k8sUpdateAndWait }), + [obj, k8sUpdateAndWait, launchOverlay], + ); const installPlanPhase = useMemo(() => { if (installPlan) { switch (installPlan.status?.phase as InstallPlanPhase) { From b4092b243853b25f21ca6577283e0a9af70ecbe8 Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Tue, 27 Jan 2026 17:45:12 -0500 Subject: [PATCH 07/21] Migrate SubscriptionChannelModal from createModalLauncher to useOverlay --- .../subscription-channel-modal.spec.tsx | 12 ++++++++ .../modals/subscription-channel-modal.tsx | 30 ++++++++++++++++--- .../src/components/subscription.tsx | 13 ++++++-- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/subscription-channel-modal.spec.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/subscription-channel-modal.spec.tsx index 35d23c3a78c..d64a2c32eec 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/subscription-channel-modal.spec.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/subscription-channel-modal.spec.tsx @@ -9,6 +9,18 @@ import { SubscriptionChannelModalProps, } from '../subscription-channel-modal'; +jest.mock('@console/internal/components/factory/modal', () => ({ + ...jest.requireActual('@console/internal/components/factory/modal'), + ModalTitle: jest.fn(({ children }) => children), + ModalBody: jest.fn(({ children }) => children), + ModalSubmitFooter: jest.fn(({ submitText, submitDisabled }) => ( + + )), + ModalWrapper: jest.fn(({ children }) => children), +})); + describe('SubscriptionChannelModal', () => { let subscriptionChannelModalProps: SubscriptionChannelModalProps; let k8sUpdate: jest.Mock; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/subscription-channel-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/subscription-channel-modal.tsx index 51b15edd1c0..af0afd881b4 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/subscription-channel-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/subscription-channel-modal.tsx @@ -2,11 +2,12 @@ import type { FC, FormEvent } from 'react'; import { useState, useCallback } from 'react'; import { Radio, Form, FormGroup } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { - createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { ResourceLink } from '@console/internal/components/utils'; import { K8sKind, K8sResourceKind, referenceForModel } from '@console/internal/module/k8s'; @@ -96,9 +97,28 @@ export const SubscriptionChannelModal: FC = ({ ); }; -export const createSubscriptionChannelModal = createModalLauncher( - SubscriptionChannelModal, -); +const SubscriptionChannelModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; + +type SubscriptionChannelModalProviderProps = { + subscription: SubscriptionKind; + pkg: PackageManifestKind; + k8sUpdate: (kind: K8sKind, newObj: K8sResourceKind) => Promise; + // closeOverlay is added automatically by OverlayComponent wrapper +}; export type SubscriptionChannelModalProps = { cancel?: () => void; @@ -107,3 +127,5 @@ export type SubscriptionChannelModalProps = { subscription: SubscriptionKind; pkg: PackageManifestKind; }; + +export default SubscriptionChannelModalProvider; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx b/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx index 7446616a030..31040ebc329 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx @@ -99,7 +99,7 @@ import { findDeprecatedOperator, } from './deprecated-operator-warnings/deprecated-operator-warnings'; import InstallPlanApprovalModalProvider from './modals/installplan-approval-modal'; -import { createSubscriptionChannelModal } from './modals/subscription-channel-modal'; +import SubscriptionChannelModalProvider from './modals/subscription-channel-modal'; import { useUninstallOperatorModal } from './modals/uninstall-operator-modal'; import { requireOperatorGroup } from './operator-group'; import { getManualSubscriptionsInNamespace, NamespaceIncludesManualApproval } from './index'; @@ -588,8 +588,15 @@ export const SubscriptionUpdates: FC = ({ k8sUpdate(kind, resource).then(() => setWaitingForUpdate(true)), [setWaitingForUpdate], ); - const channelModal = () => - createSubscriptionChannelModal({ subscription: obj, pkg, k8sUpdate: k8sUpdateAndWait }); + const channelModal = useCallback( + () => + launchOverlay(SubscriptionChannelModalProvider, { + subscription: obj, + pkg, + k8sUpdate: k8sUpdateAndWait, + }), + [obj, pkg, k8sUpdateAndWait, launchOverlay], + ); const approvalModal = useCallback( () => launchOverlay(InstallPlanApprovalModalProvider, { obj, k8sUpdate: k8sUpdateAndWait }), [obj, k8sUpdateAndWait, launchOverlay], From 44ee117f1fba84fee49b8f4b88510d8e8dc5795e Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Wed, 28 Jan 2026 15:01:57 -0500 Subject: [PATCH 08/21] Migrate InstallPlanPreviewModal from createModalLauncher to useOverlay --- .../src/components/install-plan.tsx | 12 +++++++--- .../modals/installplan-preview-modal.tsx | 24 +++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/frontend/packages/operator-lifecycle-manager/src/components/install-plan.tsx b/frontend/packages/operator-lifecycle-manager/src/components/install-plan.tsx index a8a20957461..8dcc0f52888 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/install-plan.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/install-plan.tsx @@ -1,5 +1,5 @@ import type { FC, ReactNode } from 'react'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { Alert, Button, @@ -68,7 +68,7 @@ import { CatalogSourceModel, } from '../models'; import { InstallPlanKind, InstallPlanApproval, Step } from '../types'; -import { installPlanPreviewModal } from './modals/installplan-preview-modal'; +import InstallPlanPreviewModalProvider from './modals/installplan-preview-modal'; import { requireOperatorGroup } from './operator-group'; import { InstallPlanReview, referenceForStepResource } from './index'; @@ -417,6 +417,12 @@ export const InstallPlanPreview: FC = ({ obj, hideAppro (ref) => referenceForOwnerRef(ref) === referenceForModel(SubscriptionModel), ); + const previewModal = useCallback( + (stepResource: Step['resource']) => + launchModal(InstallPlanPreviewModalProvider, { stepResource }), + [launchModal], + ); + const plan = obj?.status?.plan || []; const stepsByCSV = plan .reduce( @@ -514,7 +520,7 @@ export const InstallPlanPreview: FC = ({ obj, hideAppro