diff --git a/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts b/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts index 29623fc0f41..04849ecfbff 100644 --- a/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts @@ -7,13 +7,13 @@ import { LazyAnnotationsModalOverlay, LazyDeleteModalOverlay, LazyLabelsModalOverlay, - taintsModal, tolerationsModal, } from '@console/internal/components/modals'; import { useConfigureCountModal } from '@console/internal/components/modals/configure-count-modal'; +import { TaintsModalOverlay } 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'; +import { referenceFor, K8sModel, K8sResourceKind, NodeKind } from '@console/internal/module/k8s'; import { CommonActionCreator, ActionObject } from './types'; /** @@ -141,9 +141,9 @@ export const useCommonActions = ( id: 'edit-taints', label: t('console-app~Edit taints'), cta: () => - taintsModal({ + launchModal(TaintsModalOverlay, { resourceKind: kind, - resource, + resource: resource as NodeKind, }), accessReview: asAccessReview(kind as K8sModel, resource as K8sResourceKind, 'patch'), }), @@ -159,9 +159,12 @@ export const useCommonActions = ( accessReview: asAccessReview(kind as K8sModel, resource as K8sResourceKind, 'patch'), }), }), - // Excluding stable modal launcher functions (tolerationsModal, taintsModal) - // to prevent unnecessary re-renders - // TODO: remove once all Modals have been updated to useOverlay + // Excluding legacy modal launcher functions (labelsModalLauncher, annotationsModalLauncher, + // podSelectorModal, tolerationsModal) from dependencies because: + // 1. These are created by createModalLauncher() and are referentially stable + // 2. They don't change between renders, so excluding them prevents unnecessary re-renders + // 3. Once these modals are migrated to useOverlay, they'll use launchModal instead + // TODO: Remove this eslint-disable once all modals have been migrated to useOverlay pattern // eslint-disable-next-line react-hooks/exhaustive-deps [kind, resource, t, message, actualEditPath, launchModal], ); diff --git a/frontend/packages/operator-lifecycle-manager/src/actions/hooks/useSubscriptionActions.ts b/frontend/packages/operator-lifecycle-manager/src/actions/hooks/useSubscriptionActions.ts index 06d7ab1ec17..654ccd3c70e 100644 --- a/frontend/packages/operator-lifecycle-manager/src/actions/hooks/useSubscriptionActions.ts +++ b/frontend/packages/operator-lifecycle-manager/src/actions/hooks/useSubscriptionActions.ts @@ -5,7 +5,7 @@ import { useCommonActions } from '@console/app/src/actions/hooks/useCommonAction import { Action } from '@console/dynamic-plugin-sdk'; import { useDeepCompareMemoize } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useDeepCompareMemoize'; import { asAccessReview } from '@console/internal/components/utils'; -import { referenceFor, k8sKill, k8sGet, k8sPatch } from '@console/internal/module/k8s'; +import { referenceFor } from '@console/internal/module/k8s'; import { useK8sModel } from '@console/shared/src/hooks/useK8sModel'; import { useUninstallOperatorModal } from '../../components/modals/uninstall-operator-modal'; import { ClusterServiceVersionModel } from '../../models'; @@ -31,16 +31,12 @@ export const useSubscriptionActions = ( const { t } = useTranslation(); const [model] = useK8sModel(referenceFor(obj)); const [commonActions] = useCommonActions(model, obj, [CommonActionCreator.Edit]); - const uninstallOperatorModal = useUninstallOperatorModal({ - k8sKill, - k8sGet, - k8sPatch, - subscription: obj, - }); const memoizedFilterActions = useDeepCompareMemoize(filterActions); const installedCSV = obj.status?.installedCSV; + const uninstallOperatorModal = useUninstallOperatorModal(obj); + const factory = useMemo( () => ({ [SubscriptionActionCreator.RemoveSubscription]: () => ({ @@ -59,8 +55,7 @@ export const useSubscriptionActions = ( }; }, }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [installedCSV, model, obj, t], + [installedCSV, model, obj, t, uninstallOperatorModal], ); // filter and initialize requested actions or construct list of all SubscriptionActions 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..92438fbf676 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 { LazyDisableDefaultSourceModalOverlay } from '../../components/modals'; 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 launchModal = useOverlay(); const factory = useMemo( () => ({ disableSource: () => ({ id: 'disable-source', label: t('olm~Disable'), - cta: () => disableDefaultSourceModal({ kind: OperatorHubModel, operatorHub, sourceName }), + cta: () => + launchModal(LazyDisableDefaultSourceModalOverlay, { + kind: OperatorHubModel, + operatorHub, + sourceName, + }), accessReview: asAccessReview(OperatorHubModel, operatorHub, 'patch'), }), }), - [t, operatorHub, sourceName], + [t, operatorHub, sourceName, launchModal], ); const action = useMemo(() => [factory.disableSource()], [factory]); return action; diff --git a/frontend/packages/operator-lifecycle-manager/src/actions/useOperatorActions.ts b/frontend/packages/operator-lifecycle-manager/src/actions/useOperatorActions.ts index 083eb957fd9..7020e6ca34d 100644 --- a/frontend/packages/operator-lifecycle-manager/src/actions/useOperatorActions.ts +++ b/frontend/packages/operator-lifecycle-manager/src/actions/useOperatorActions.ts @@ -4,18 +4,19 @@ import { useTranslation } from 'react-i18next'; import { K8S_VERB_DELETE } from '@console/dynamic-plugin-sdk/src/api/constants'; import { Action } from '@console/dynamic-plugin-sdk/src/extensions/actions'; import { useOverlay } from '@console/dynamic-plugin-sdk/src/lib-core'; -import { k8sGet, k8sKill, k8sPatch } from '@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource'; import { DeleteModalOverlay } from '@console/internal/components/modals/delete-modal'; import { asAccessReview } from '@console/internal/components/utils/rbac'; import { resourceObjPath } from '@console/internal/components/utils/resource-link'; import { referenceFor } from '@console/internal/module/k8s'; -import { UninstallOperatorOverlay } from '../components/modals/uninstall-operator-modal'; +import { useUninstallOperatorModal } from '../components/modals/uninstall-operator-modal'; import { ClusterServiceVersionModel, SubscriptionModel } from '../models'; const useOperatorActions = ({ resource, subscription }): [Action[], boolean, any] => { const { t } = useTranslation(); const launchModal = useOverlay(); + const uninstallOperatorModal = useUninstallOperatorModal(subscription, resource); + const actions = useMemo(() => { if (!resource) { return []; @@ -47,19 +48,11 @@ const useOperatorActions = ({ resource, subscription }): [Action[], boolean, any { id: 'uninstall-operator', label: t('olm~Uninstall Operator'), - cta: () => - launchModal(UninstallOperatorOverlay, { - k8sKill, - k8sGet, - k8sPatch, - subscription, - csv: resource, - blocking: true, - }), + cta: () => uninstallOperatorModal(), accessReview: asAccessReview(SubscriptionModel, subscription, K8S_VERB_DELETE), }, ]; - }, [resource, subscription, t, launchModal]); + }, [resource, subscription, t, launchModal, uninstallOperatorModal]); return [actions, true, null]; }; 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..6d2cedbbfb2 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 launchModalMock: jest.Mock; beforeEach(() => { obj = { @@ -106,23 +110,17 @@ describe('ResourceRequirementsModalLink', () => { }, }, }; + + launchModalMock = jest.fn(); + useK8sModelMock.mockReturnValue([testModel, false]); + useOverlayMock.mockReturnValue(launchModalMock); + 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(launchModalMock).toHaveBeenCalled(); }); - const modalArgs = modalSpy.mock.calls[0][0]; - expect(modalArgs.title).toEqual(`${obj.kind} Resource Limits`); - expect(modalArgs.description).toEqual( + const [, modalProps] = launchModalMock.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/configure-update-strategy.tsx b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/configure-update-strategy.tsx index 9d70009afff..4e8c1d3d3d4 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 { LazyUpdateStrategyModalOverlay } from '../../modals'; 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 launchModal = useOverlay(); + + return useCallback(() => { + return launchModal(LazyUpdateStrategyModalOverlay, { + resourceKind: kindObj, + resource, + defaultValue: specValue, + title: i18n.t('olm~Edit {{item}}', { item: specDescriptor.displayName }), + path: `/spec/${getPatchPathFromDescriptor(specDescriptor)}`, + }); + }, [launchModal, 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/descriptors/spec/resource-requirements.tsx b/frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/resource-requirements.tsx index cccb47c6cfa..48d7b968d1b 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,91 @@ 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, -}); - -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}`, {}); - - 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), - }); - - return modal({ title, description, obj, model, type, path }); - }; - - return ( - - ); - }, -); +const ResourceRequirementsModalOverlay: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; + +export const ResourceRequirementsModalLink: FC = (props) => { + const { obj, type, path } = props; + const { t } = useTranslation(); + const launchModal = useOverlay(); + const [model, inFlight] = useK8sModel(referenceFor(obj)); + const none = t('public~None'); + const { cpu, memory, 'ephemeral-storage': storage } = _.get(obj.spec, `${path}.${type}`, {}); + + const onClick = () => { + if (!model) { + return; + } + + 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), + }); + + launchModal(ResourceRequirementsModalOverlay, { + title, + description, + obj, + model, + type, + path, + }); + }; + + return ( + + ); +}; + +type ResourceRequirementsModalOverlayProps = { + title: string; + description: string; + obj: K8sResourceKind; + model: K8sKind; + type: 'requests' | 'limits'; + path: string; +}; export type ResourceRequirementsModalProps = { title: string; @@ -201,7 +237,6 @@ export type ResourceRequirementsProps = { export type ResourceRequirementsModalLinkProps = { obj: K8sResourceKind; - model: K8sKind; type: 'requests' | 'limits'; path: string; }; 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..f19821a8b27 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 { LazyInstallPlanPreviewModalOverlay } from './modals'; 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(LazyInstallPlanPreviewModalOverlay, { stepResource }), + [launchModal], + ); + const plan = obj?.status?.plan || []; const stepsByCSV = plan .reduce( @@ -514,7 +520,7 @@ export const InstallPlanPreview: FC = ({ obj, hideAppro + )), + 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/__tests__/uninstall-operator-modal.spec.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/uninstall-operator-modal.spec.tsx index 0f92a62a59b..5cd56091048 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/uninstall-operator-modal.spec.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/uninstall-operator-modal.spec.tsx @@ -29,8 +29,17 @@ jest.mock('react-i18next', () => ({ Trans: () => null, })); +const mockNavigate = jest.fn(); +jest.mock('react-router-dom-v5-compat', () => ({ + ...jest.requireActual('react-router-dom-v5-compat'), + useNavigate: () => mockNavigate, +})); + +const mockK8sKill = jest.fn(); + jest.mock('@console/dynamic-plugin-sdk/src/utils/k8s', () => ({ k8sGetResource: jest.fn(), + k8sKill: (...args) => mockK8sKill(...args), })); describe(UninstallOperatorModal.name, () => { @@ -38,15 +47,13 @@ describe(UninstallOperatorModal.name, () => { beforeEach(() => { jest.clearAllMocks(); + mockK8sKill.mockResolvedValue({}); uninstallOperatorModalProps = { subscription: { ..._.cloneDeep(testSubscription), status: { installedCSV: 'testapp.v1.0.0' }, }, - k8sKill: jest.fn().mockResolvedValue({}), - k8sGet: jest.fn().mockResolvedValue({}), - k8sPatch: jest.fn().mockResolvedValue({}), close: jest.fn(), cancel: jest.fn(), }; @@ -69,13 +76,14 @@ describe(UninstallOperatorModal.name, () => { fireEvent.submit(screen.getByRole('form')); await waitFor(() => { - expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledTimes(2); + expect(mockK8sKill).toHaveBeenCalledTimes(2); }); - expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledWith( + expect(mockK8sKill).toHaveBeenCalledWith( SubscriptionModel, uninstallOperatorModalProps.subscription, {}, + {}, expect.objectContaining({ kind: 'DeleteOptions', apiVersion: 'v1', @@ -90,10 +98,10 @@ describe(UninstallOperatorModal.name, () => { fireEvent.submit(screen.getByRole('form')); await waitFor(() => { - expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledTimes(2); + expect(mockK8sKill).toHaveBeenCalledTimes(2); }); - expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledWith( + expect(mockK8sKill).toHaveBeenCalledWith( ClusterServiceVersionModel, expect.objectContaining({ metadata: expect.objectContaining({ @@ -102,6 +110,7 @@ describe(UninstallOperatorModal.name, () => { }), }), {}, + {}, expect.objectContaining({ kind: 'DeleteOptions', apiVersion: 'v1', @@ -118,7 +127,7 @@ describe(UninstallOperatorModal.name, () => { fireEvent.submit(screen.getByRole('form')); await waitFor(() => { - expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledTimes(1); + expect(mockK8sKill).toHaveBeenCalledTimes(1); }); }); diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/delete-catalog-source-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/delete-catalog-source-modal.tsx deleted file mode 100644 index cd1db8bf00d..00000000000 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/delete-catalog-source-modal.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import type { FC, KeyboardEvent, FormEvent } from 'react'; -import { useState, useCallback } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { - createModalLauncher, - ModalTitle, - ModalBody, - ModalSubmitFooter, - ModalComponentProps, -} from '@console/internal/components/factory/modal'; -import { k8sKill, K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; -import { YellowExclamationTriangleIcon } from '@console/shared'; -import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; - -const DeleteCatalogSourceModal: FC = ({ - kind, - resource, - close, - cancel, -}) => { - const [handlePromise, inProgress, errorMessage] = usePromiseHandler(); - const { t } = useTranslation(); - const [confirmed, setConfirmed] = useState(false); - const isConfirmed = (e: KeyboardEvent) => { - setConfirmed(e.currentTarget.value === resource.metadata.name); - }; - - const submit = useCallback( - (event: FormEvent): void => { - event.preventDefault(); - handlePromise(k8sKill(kind, resource)) - .then(() => { - close(); - }) - .catch(() => {}); - }, - [close, handlePromise, kind, resource], - ); - - return ( -
- - {' '} - {t('olm~Delete CatalogSource?')} - - -

- {t( - 'olm~By deleting a CatalogSource, any Operator that has been installed from this source will no longer receive updates.', - )} -

-

- - Confirm deletion by typing{' '} - {{ name: resource.metadata.name }} below: - -

- - - -
- - - ); -}; - -type DeleteCatalogSourceModalProps = { - kind: K8sKind; - resource: K8sResourceKind; -} & ModalComponentProps; - -export const deleteCatalogSourceModal = createModalLauncher(DeleteCatalogSourceModal); 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..9bed273ced2 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,19 +2,20 @@ 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, + ModalSubmitFooter, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { k8sPatch, K8sKind } from '@console/internal/module/k8s'; import { YellowExclamationTriangleIcon } from '@console/shared'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; import { OperatorHubKind } from '../operator-hub'; -const DisableDefaultSourceModal: FC = ({ +const DisableDefaultSourceModal: FC = ({ kind, operatorHub, sourceName, @@ -71,10 +72,22 @@ const DisableDefaultSourceModal: FC = ({ ); }; -type DisableSourceModalProps = { +export const DisableDefaultSourceModalOverlay: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; + +export type DisableDefaultSourceModalProps = { kind: K8sKind; operatorHub: OperatorHubKind; sourceName: string; } & ModalComponentProps; - -export const disableDefaultSourceModal = createModalLauncher(DisableDefaultSourceModal); 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..2f34eb6522b 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,14 @@ 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, + ModalComponentProps, } from '@console/internal/components/factory/modal'; import { k8sPatch } from '@console/internal/module/k8s'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; @@ -109,7 +110,15 @@ const EditDefaultSourcesModal: FC = ({ ); }; -export const editDefaultSourcesModal = createModalLauncher(EditDefaultSourcesModal); +export const EditDefaultSourcesModalOverlay: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; type EditDefaultSourcesModalProps = { operatorHub: OperatorHubKind; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/index.ts b/frontend/packages/operator-lifecycle-manager/src/components/modals/index.ts new file mode 100644 index 00000000000..4ea8efdf477 --- /dev/null +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/index.ts @@ -0,0 +1,71 @@ +import { lazy } from 'react'; + +// Lazy-loaded OverlayComponent for InstallPlan Approval Modal +export const LazyInstallPlanApprovalModalOverlay = lazy(() => + import('./installplan-approval-modal' /* webpackChunkName: "installplan-approval-modal" */).then( + (m) => ({ + default: m.InstallPlanApprovalModalOverlay, + }), + ), +); + +// Lazy-loaded OverlayComponent for Subscription Channel Modal +export const LazySubscriptionChannelModalOverlay = lazy(() => + import('./subscription-channel-modal' /* webpackChunkName: "subscription-channel-modal" */).then( + (m) => ({ + default: m.SubscriptionChannelModalOverlay, + }), + ), +); + +// Lazy-loaded OverlayComponent for Uninstall Operator Modal +export const LazyUninstallOperatorModalOverlay = lazy(() => + import('./uninstall-operator-modal' /* webpackChunkName: "uninstall-operator-modal" */).then( + (m) => ({ + default: m.UninstallOperatorModalOverlay, + }), + ), +); + +// Lazy-loaded OverlayComponent for Disable Default Source Modal +export const LazyDisableDefaultSourceModalOverlay = lazy(() => + import( + './disable-default-source-modal' /* webpackChunkName: "disable-default-source-modal" */ + ).then((m) => ({ + default: m.DisableDefaultSourceModalOverlay, + })), +); + +// Lazy-loaded OverlayComponent for Edit Default Sources Modal +export const LazyEditDefaultSourcesModalOverlay = lazy(() => + import('./edit-default-sources-modal' /* webpackChunkName: "edit-default-sources-modal" */).then( + (m) => ({ + default: m.EditDefaultSourcesModalOverlay, + }), + ), +); + +// Lazy-loaded OverlayComponent for InstallPlan Preview Modal +export const LazyInstallPlanPreviewModalOverlay = lazy(() => + import('./installplan-preview-modal' /* webpackChunkName: "installplan-preview-modal" */).then( + (m) => ({ + default: m.InstallPlanPreviewModalOverlay, + }), + ), +); + +// Lazy-loaded OverlayComponent for Update Strategy Modal +export const LazyUpdateStrategyModalOverlay = lazy(() => + import('./update-strategy-modal' /* webpackChunkName: "update-strategy-modal" */).then((m) => ({ + default: m.UpdateStrategyModalOverlay, + })), +); + +// Lazy-loaded OverlayComponent for Community Operator Warning Modal +export const LazyCommunityOperatorWarningModalOverlay = lazy(() => + import( + '../operator-hub/operator-hub-community-provider-modal' /* webpackChunkName: "community-operator-warning-modal" */ + ).then((m) => ({ + default: m.CommunityOperatorWarningModalOverlay, + })), +); 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..a24caff4dcc 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,13 @@ 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, + ModalComponentProps, ModalSubmitFooter, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { K8sKind, @@ -108,13 +110,17 @@ export const InstallPlanApprovalModal: FC = ({ ); }; -export const createInstallPlanApprovalModal = createModalLauncher( - InstallPlanApprovalModal, -); - export type InstallPlanApprovalModalProps = { - cancel?: () => void; - close?: () => void; k8sUpdate: (kind: K8sKind, newObj: K8sResourceKind) => Promise; obj: InstallPlanKind | SubscriptionKind; +} & ModalComponentProps; + +export const InstallPlanApprovalModalOverlay: OverlayComponent = ( + props, +) => { + return ( + + + + ); }; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-preview-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-preview-modal.tsx index fb158ee8545..e0a56d4e3e0 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-preview-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/installplan-preview-modal.tsx @@ -2,17 +2,19 @@ import type { FC } from 'react'; import { ActionGroup, Button } from '@patternfly/react-core'; import { safeDump } from 'js-yaml'; import { useTranslation } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { - createModalLauncher, ModalTitle, ModalBody, + ModalComponentProps, ModalFooter, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { ResourceLink, CopyToClipboard } from '@console/internal/components/utils'; import { StepResource } from '../../types'; import { referenceForStepResource } from '../index'; -const InstallPlanPreview: FC = ({ cancel, stepResource }) => { +export const InstallPlanPreview: FC = ({ cancel, stepResource }) => { const { t } = useTranslation(); return (
@@ -38,14 +40,18 @@ const InstallPlanPreview: FC = ({ cancel, stepReso ); }; -export const installPlanPreviewModal = createModalLauncher( - InstallPlanPreview, -); - export type InstallPlanPreviewModalProps = { stepResource: StepResource; - cancel?: () => void; - close?: () => void; +} & ModalComponentProps; + +export const InstallPlanPreviewModalOverlay: OverlayComponent = ( + props, +) => { + return ( + + + + ); }; InstallPlanPreview.displayName = 'InstallPlanPreview'; 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..add89f8053e 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,13 @@ 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, + ModalComponentProps, 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,14 +98,18 @@ export const SubscriptionChannelModal: FC = ({ ); }; -export const createSubscriptionChannelModal = createModalLauncher( - SubscriptionChannelModal, -); - export type SubscriptionChannelModalProps = { - cancel?: () => void; - close?: () => void; k8sUpdate: (kind: K8sKind, newObj: K8sResourceKind) => Promise; subscription: SubscriptionKind; pkg: PackageManifestKind; +} & ModalComponentProps; + +export const SubscriptionChannelModalOverlay: OverlayComponent = ( + props, +) => { + return ( + + + + ); }; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx index 0c3919d6188..3c0d287be58 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx @@ -1,17 +1,10 @@ /* eslint-disable @typescript-eslint/no-use-before-define */ import type { FC, FormEvent } from 'react'; -import { useState, useCallback, useEffect } from 'react'; -import { - Alert, - Backdrop, - Modal, - ModalVariant, - Progress, - ProgressSize, - Title, -} from '@patternfly/react-core'; +import { useState, useEffect, useCallback } from 'react'; +import { Alert, Progress, ProgressSize, Title } from '@patternfly/react-core'; import * as _ from 'lodash'; import { Trans, useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom-v5-compat'; 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 { k8sGetResource } from '@console/dynamic-plugin-sdk/src/utils/k8s'; @@ -20,15 +13,13 @@ import { getActiveNamespace } from '@console/internal/actions/ui'; import { coFetchJSON } from '@console/internal/co-fetch'; import { Checkbox } from '@console/internal/components/checkbox'; import { - createModalLauncher, - ModalComponentProps, ModalTitle, ModalBody, ModalSubmitFooter, ModalWrapper, + ModalComponentProps, } from '@console/internal/components/factory/modal'; import { - history, LinkifyExternal, ResourceLink, resourceListPathFromModel, @@ -38,9 +29,9 @@ import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watc import { useAccessReview } from '@console/internal/components/utils/rbac'; import { ConsoleOperatorConfigModel } from '@console/internal/models'; import { - K8sKind, K8sResourceCommon, K8sResourceKind, + k8sKill, modelFor, referenceFor, k8sPatch, @@ -69,10 +60,10 @@ export const UninstallOperatorModal: FC = ({ cancel, close, csv, - k8sKill, subscription, }) => { const { t } = useTranslation(); + const navigate = useNavigate(); const [ handleOperatorUninstallPromise, operatorUninstallInProgress, @@ -159,7 +150,7 @@ export const UninstallOperatorModal: FC = ({ }; const operatorUninstallPromises = [ - k8sKill(SubscriptionModel, subscription, {}, deleteOptions), + k8sKill(SubscriptionModel, subscription, {}, {}, deleteOptions), ...(subscription?.status?.installedCSV && (await clusterServiceVersionExists()) ? [ k8sKill( @@ -171,6 +162,7 @@ export const UninstallOperatorModal: FC = ({ }, }, {}, + {}, deleteOptions, ), ] @@ -191,7 +183,6 @@ export const UninstallOperatorModal: FC = ({ consoleOperatorConfig, enabledPlugins, handleOperatorUninstallPromise, - k8sKill, removePlugins, subscription, ]); @@ -237,9 +228,9 @@ export const UninstallOperatorModal: FC = ({ window.location.pathname.split('/').includes(subscription.metadata.name) || window.location.pathname.split('/').includes(subscription?.status?.installedCSV) ) { - history.push(resourceListPathFromModel(ClusterServiceVersionModel, getActiveNamespace())); + navigate(resourceListPathFromModel(ClusterServiceVersionModel, getActiveNamespace())); } - }, [close, subscription]); + }, [close, subscription, navigate]); useEffect(() => { if (isSubmitFinished && !hasSubmitErrors) { @@ -272,7 +263,7 @@ export const UninstallOperatorModal: FC = ({ setOperandsRemaining(operands.length); const operandDeletionPromises = operands.map((operand: K8sResourceCommon) => { const model = modelFor(referenceFor(operand)); - return k8sKill(model, operand, {}, deleteOptions); + return k8sKill(model, operand, {}, {}, deleteOptions); }); // eslint-disable-next-line promise/catch-or-return settleAllPromises(operandDeletionPromises).then(([, , results]) => { @@ -434,18 +425,6 @@ export const UninstallOperatorModal: FC = ({ ); }; -export const UninstallOperatorOverlay: FC = (props) => { - const [isOpen, setIsOpen] = useState(true); - const closeOverlay = () => setIsOpen(false); - return isOpen ? ( - - - ; - - - ) : null; -}; - const OperandDeleteProgress: FC<{ total: number; remaining: number; @@ -657,42 +636,33 @@ const OperandErrorList: FC = ({ operandErrors, csvName, c ); }; -export const createUninstallOperatorModal = createModalLauncher(UninstallOperatorModal); - -const UninstallOperatorModalProvider: OverlayComponent = ( +export const UninstallOperatorModalOverlay: OverlayComponent = ( props, ) => { return ( - + ); }; -export const useUninstallOperatorModal = (props: UninstallOperatorModalProps) => { - const launcher = useOverlay(); +export const useUninstallOperatorModal = (subscription: K8sResourceKind, csv?: K8sResourceKind) => { + const launchModal = useOverlay(); return useCallback( - () => launcher(UninstallOperatorModalProvider, props), - [launcher, props], + () => + launchModal(UninstallOperatorModalOverlay, { + subscription, + csv, + }), + [launchModal, subscription, csv], ); }; -type UninstallOperatorModalProviderProps = UninstallOperatorModalProps & ModalComponentProps; - export type UninstallOperatorModalProps = { - cancel?: () => void; - close?: () => void; - k8sKill: (kind: K8sKind, resource: K8sResourceKind, options: any, json: any) => Promise; - k8sGet: (kind: K8sKind, name: string, namespace: string) => Promise; - k8sPatch: ( - kind: K8sKind, - resource: K8sResourceKind, - data: { op: string; path: string; value: any }[], - ) => Promise; subscription: K8sResourceKind; csv?: K8sResourceKind; blocking?: boolean; -}; +} & ModalComponentProps; type OperandsTableProps = { operands: K8sResourceCommon[]; 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..945fe157541 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,13 @@ 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, + ModalComponentProps, ModalSubmitFooter, ModalTitle, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { ConfigureUpdateStrategy, @@ -84,7 +86,13 @@ export const UpdateStrategyModal: FC = ({ ); }; -export const updateStrategyModal = createModalLauncher(UpdateStrategyModal); +export const UpdateStrategyModalOverlay: OverlayComponent = (props) => { + return ( + + + + ); +}; UpdateStrategyModal.displayName = 'UpdateStrategyModal'; @@ -94,6 +102,4 @@ export type UpdateStrategyModalProps = { resource: K8sResourceKind; resourceKind: K8sKind; title: string; - cancel?: () => void; - close?: () => void; -}; +} & ModalComponentProps; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-community-provider-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-community-provider-modal.tsx index 4455e1d7813..59b84de9130 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-community-provider-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-community-provider-modal.tsx @@ -3,17 +3,20 @@ import { useState, useCallback } from 'react'; import { Checkbox, Content, ContentVariants, Icon, Split, SplitItem } from '@patternfly/react-core'; import { InfoCircleIcon } from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; import { useTranslation } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { - createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, + ModalWrapper, + ModalComponentProps, } from '@console/internal/components/factory/modal'; import { RH_OPERATOR_SUPPORT_POLICY_LINK } from '@console/shared'; import { ExternalLink } from '@console/shared/src/components/links/ExternalLink'; export const OperatorHubCommunityProviderModal: FC = ({ close, + cancel, showCommunityOperators, }) => { const { t } = useTranslation(); @@ -66,7 +69,7 @@ export const OperatorHubCommunityProviderModal: FC ); @@ -74,7 +77,18 @@ export const OperatorHubCommunityProviderModal: FC void; - close?: () => void; -}; +} & ModalComponentProps; -export const communityOperatorWarningModal = createModalLauncher(OperatorHubCommunityProviderModal); +export const CommunityOperatorWarningModalOverlay: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; 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..7f81ed741cd 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 { LazyEditDefaultSourcesModalOverlay } from '../modals'; import { OperatorHubKind } from '.'; const OperatorHubDetails: FC = ({ obj: operatorHub }) => { const { t } = useTranslation(); + const launchModal = 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={() => launchModal(LazyEditDefaultSourcesModalOverlay, { operatorHub })} editAsGroup hideEmpty > diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx index 6880bb3babb..f17d88919c6 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx @@ -13,6 +13,7 @@ import { css } from '@patternfly/react-styles'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { Link, useSearchParams } from 'react-router-dom-v5-compat'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { getQueryArgument } from '@console/internal/components/utils'; import { history } from '@console/internal/components/utils/router'; import { TileViewPage } from '@console/internal/components/utils/tile-view-page'; @@ -29,7 +30,7 @@ import { isModifiedEvent } from '@console/shared/src/utils'; import { DefaultCatalogSource } from '../../const'; import { SubscriptionModel } from '../../models'; import { DeprecatedOperatorWarningBadge } from '../deprecated-operator-warnings/deprecated-operator-warnings'; -import { communityOperatorWarningModal } from './operator-hub-community-provider-modal'; +import { LazyCommunityOperatorWarningModalOverlay } from '../modals'; import { OperatorHubItemDetails } from './operator-hub-item-details'; import { capabilityLevelSort, @@ -747,6 +748,8 @@ export const OperatorHubTileView: FC = (props) => { } }, [filteredItems, searchParams]); + const launchModal = useOverlay(); + const showCommunityOperator = (item: OperatorHubItem) => (ignoreWarning = false) => { const params = new URLSearchParams(window.location.search); params.set('details-item', item.uid); @@ -775,7 +778,7 @@ export const OperatorHubTileView: FC = (props) => { const openOverlay = (item: OperatorHubItem) => { if (!ignoreOperatorWarning && item.catalogSource === DefaultCatalogSource.CommunityOperators) { - communityOperatorWarningModal({ + launchModal(LazyCommunityOperatorWarningModalOverlay, { showCommunityOperators: (ignore) => showCommunityOperator(item)(ignore), }); } else { diff --git a/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx b/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx index 92cc029428a..0179cd5a4d5 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, @@ -46,11 +47,8 @@ import { } from '@console/internal/components/utils'; import { useQueryParamsMutator } from '@console/internal/components/utils/router'; import { - k8sGet, - k8sKill, K8sKind, K8sModel, - k8sPatch, K8sResourceCommon, k8sUpdate, referenceFor, @@ -97,8 +95,7 @@ import { DeprecatedOperatorWarningIcon, findDeprecatedOperator, } from './deprecated-operator-warnings/deprecated-operator-warnings'; -import { createInstallPlanApprovalModal } from './modals/installplan-approval-modal'; -import { createSubscriptionChannelModal } from './modals/subscription-channel-modal'; +import { LazyInstallPlanApprovalModalOverlay, LazySubscriptionChannelModalOverlay } from './modals'; import { useUninstallOperatorModal } from './modals/uninstall-operator-modal'; import { requireOperatorGroup } from './operator-group'; import { getManualSubscriptionsInNamespace, NamespaceIncludesManualApproval } from './index'; @@ -421,6 +418,7 @@ export const SubscriptionDetails: FC = ({ }) => { const { t } = useTranslation(); const { removeQueryArgument } = useQueryParamsMutator(); + const uninstallOperatorModal = useUninstallOperatorModal(obj); const { source, sourceNamespace } = obj?.spec ?? {}; const catalogHealth = obj?.status?.catalogHealth?.find( (ch) => ch.catalogSourceRef.name === source, @@ -428,16 +426,13 @@ export const SubscriptionDetails: FC = ({ const installedCSV = installedCSVForSubscription(clusterServiceVersions, obj); const installPlan = installPlanForSubscription(installPlans, obj); const pkg = packageForSubscription(packageManifests, obj); - const uninstallOperatorModal = useUninstallOperatorModal({ - k8sKill, - k8sGet, - k8sPatch, - subscription: obj, - }); - if (new URLSearchParams(window.location.search).has('showDelete')) { - uninstallOperatorModal(); - removeQueryArgument('showDelete'); - } + + useEffect(() => { + if (new URLSearchParams(window.location.search).has('showDelete')) { + uninstallOperatorModal(); + removeQueryArgument('showDelete'); + } + }, [uninstallOperatorModal, removeQueryArgument]); const { deprecatedPackage, deprecatedChannel, deprecatedVersion } = findDeprecatedOperator(obj); @@ -564,6 +559,7 @@ export const SubscriptionUpdates: FC = ({ subscriptions, }) => { const { t } = useTranslation(); + const launchModal = useOverlay(); const prevInstallPlanApproval = useRef(obj?.spec?.installPlanApproval); const prevChannel = useRef(obj?.spec?.channel); const [waitingForUpdate, setWaitingForUpdate] = useState(false); @@ -581,11 +577,24 @@ export const SubscriptionUpdates: FC = ({ } }, [obj, waitingForUpdate]); - const k8sUpdateAndWait = (kind: K8sKind, resource: K8sResourceCommon) => - k8sUpdate(kind, resource).then(() => setWaitingForUpdate(true)); - const channelModal = () => - createSubscriptionChannelModal({ subscription: obj, pkg, k8sUpdate: k8sUpdateAndWait }); - const approvalModal = () => createInstallPlanApprovalModal({ obj, k8sUpdate: k8sUpdateAndWait }); + const k8sUpdateAndWait = useCallback( + (kind: K8sKind, resource: K8sResourceCommon) => + k8sUpdate(kind, resource).then(() => setWaitingForUpdate(true)), + [setWaitingForUpdate], + ); + const channelModal = useCallback( + () => + launchModal(LazySubscriptionChannelModalOverlay, { + subscription: obj, + pkg, + k8sUpdate: k8sUpdateAndWait, + }), + [obj, pkg, k8sUpdateAndWait, launchModal], + ); + const approvalModal = useCallback( + () => launchModal(LazyInstallPlanApprovalModalOverlay, { obj, k8sUpdate: k8sUpdateAndWait }), + [obj, k8sUpdateAndWait, launchModal], + ); const installPlanPhase = useMemo(() => { if (installPlan) { switch (installPlan.status?.phase as InstallPlanPhase) { diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index 246bc9f2c1c..95588f4d679 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -87,9 +87,6 @@ export const clusterUpdateModal = (props) => m.clusterUpdateModal(props), ); -export const taintsModal = (props) => - import('./taints-modal' /* webpackChunkName: "taints-modal" */).then((m) => m.taintsModal(props)); - export const tolerationsModal = (props) => import('./tolerations-modal' /* webpackChunkName: "tolerations-modal" */).then((m) => m.tolerationsModal(props), diff --git a/frontend/public/components/modals/taints-modal.tsx b/frontend/public/components/modals/taints-modal.tsx index f57db32da83..2b4d2f81143 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, NodeKind, 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,17 @@ const TaintsModal = (props: TaintsModalProps) => { ); }; -export const taintsModal = createModalLauncher(TaintsModal); - export type TaintsModalProps = { resourceKind: K8sKind; resource: NodeKind; - close: () => void; } & ModalComponentProps; + +const TaintsModalOverlay: OverlayComponent = (props) => { + return ( + + + + ); +}; + +export { TaintsModalOverlay };