From 234ba098154b6bb464f472ad4acffb8270f25017 Mon Sep 17 00:00:00 2001 From: Steve Goodwin Date: Wed, 11 Feb 2026 18:14:34 -0500 Subject: [PATCH] Migrate DeleteResourceModal and router-dependent modals to React Router v6 Update DeleteResourceModal, dev-console actions, and helm-plugin actions from history.push() to useNavigate() for React Router v7 compatibility. Assisted by: Claude Code --- .../components/modals/DeleteResourceModal.tsx | 50 +++++++++----- .../src/components/modals/index.ts | 11 ++-- .../dev-console/src/actions/context-menu.ts | 57 +++++++++------- .../dev-console/src/actions/providers.tsx | 8 +-- .../helm-plugin/src/actions/creators.ts | 66 +++++++++++-------- .../helm-plugin/src/actions/providers.ts | 9 +-- .../packages/helm-plugin/src/actions/types.ts | 2 +- 7 files changed, 122 insertions(+), 81 deletions(-) diff --git a/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx b/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx index 2d4112d7c2a..5081558412f 100644 --- a/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx +++ b/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx @@ -2,29 +2,20 @@ import type { FC } from 'react'; import { TextInputTypes } from '@patternfly/react-core'; import { Formik, FormikProps, FormikValues } from 'formik'; import { useTranslation, Trans } from 'react-i18next'; +import { useNavigate } from 'react-router-dom-v5-compat'; +import type { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { - createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, + ModalWrapper, } from '@console/internal/components/factory/modal'; -import { history } from '@console/internal/components/utils/router'; +import type { ModalComponentProps } from '@console/internal/components/factory/modal'; import { K8sResourceKind } from '@console/internal/module/k8s'; import { usePromiseHandler } from '../../hooks/promise-handler'; import { InputField } from '../formik-fields'; import { YellowExclamationTriangleIcon } from '../status'; -type DeleteResourceModalProps = { - resourceName: string; - resourceType: string; - actionLabel?: string; // Used to send translated strings as action label. - actionLabelKey?: string; // Used to send translation key for action label. - redirect?: string; - onSubmit: (values: FormikValues) => Promise; - cancel?: () => void; - close?: () => void; -}; - const DeleteResourceForm: FC & DeleteResourceModalProps> = ({ handleSubmit, resourceName, @@ -73,6 +64,7 @@ const DeleteResourceForm: FC & DeleteResourceModalProp const DeleteResourceModal: FC = (props) => { const [handlePromise] = usePromiseHandler(); + const navigate = useNavigate(); const handleSubmit = (values: FormikValues, actions) => { const { onSubmit, close, redirect } = props; @@ -82,7 +74,9 @@ const DeleteResourceModal: FC = (props) => { handlePromise(onSubmit(values)) .then(() => { close(); - redirect && history.push(redirect); + if (redirect) { + navigate(redirect); + } }) .catch((errorMessage) => { actions.setStatus({ submitError: errorMessage }); @@ -101,6 +95,28 @@ const DeleteResourceModal: FC = (props) => { ); }; -export const deleteResourceModal = createModalLauncher((props: DeleteResourceModalProps) => ( - -)); +export const DeleteResourceModalOverlay: OverlayComponent = (props) => { + return ( + + + + ); +}; + +type DeleteResourceModalProps = ModalComponentProps & { + resourceName: string; + resourceType: string; + actionLabel?: string; // Used to send translated strings as action label. + actionLabelKey?: string; // Used to send translation key for action label. + redirect?: string; + onSubmit: (values: FormikValues) => Promise; +}; diff --git a/frontend/packages/console-shared/src/components/modals/index.ts b/frontend/packages/console-shared/src/components/modals/index.ts index ac902b242de..f82a427c114 100644 --- a/frontend/packages/console-shared/src/components/modals/index.ts +++ b/frontend/packages/console-shared/src/components/modals/index.ts @@ -1,9 +1,12 @@ +import { lazy } from 'react'; + export const consolePluginModal = (props) => import('./ConsolePluginModal' /* webpackChunkName: "shared-modals" */).then((m) => m.consolePluginModal(props), ); -export const deleteResourceModal = (props) => - import('./DeleteResourceModal' /* webpackChunkName: "shared-modals" */).then((m) => - m.deleteResourceModal(props), - ); +export const LazyDeleteResourceModalOverlay = lazy(() => + import('./DeleteResourceModal' /* webpackChunkName: "delete-resource-modal" */).then((m) => ({ + default: m.DeleteResourceModalOverlay, + })), +); diff --git a/frontend/packages/dev-console/src/actions/context-menu.ts b/frontend/packages/dev-console/src/actions/context-menu.ts index d699730c181..c87c1624204 100644 --- a/frontend/packages/dev-console/src/actions/context-menu.ts +++ b/frontend/packages/dev-console/src/actions/context-menu.ts @@ -1,5 +1,4 @@ import { useMemo } from 'react'; -import i18next from 'i18next'; import { useTranslation } from 'react-i18next'; import { Action, K8sModel } from '@console/dynamic-plugin-sdk'; import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; @@ -7,36 +6,44 @@ import { TopologyApplicationObject } from '@console/dynamic-plugin-sdk/src/exten import { LazyDeleteModalOverlay } from '@console/internal/components/modals'; import { asAccessReview } from '@console/internal/components/utils'; import { K8sResourceKind } from '@console/internal/module/k8s'; -import { deleteResourceModal } from '@console/shared'; +import { LazyDeleteResourceModalOverlay } from '@console/shared'; import { ApplicationModel } from '@console/topology/src/models'; import { cleanUpWorkload } from '@console/topology/src/utils'; -export const DeleteApplicationAction = ( +export const useDeleteApplicationAction = ( application: TopologyApplicationObject, resourceModel: K8sModel, ): Action => { - // accessReview needs a resource but group is not a k8s resource, - // so currently picking the first resource to do the rbac checks (might change in future) - const primaryResource = application.resources[0].resource; - return { - id: 'delete-application', - label: i18next.t('devconsole~Delete application'), - cta: () => { - const reqs = []; - deleteResourceModal({ - blocking: true, - resourceName: application.name, - resourceType: ApplicationModel.label, - onSubmit: () => { - application.resources.forEach((resource) => { - reqs.push(cleanUpWorkload(resource.resource)); - }); - return Promise.all(reqs); - }, - }); - }, - accessReview: asAccessReview(resourceModel, primaryResource, 'delete'), - }; + const { t } = useTranslation(); + const launchModal = useOverlay(); + + return useMemo(() => { + if (!application?.resources?.[0]?.resource) { + return null; + } + + // accessReview needs a resource but group is not a k8s resource, + // so currently picking the first resource to do the rbac checks (might change in future) + const primaryResource = application.resources[0].resource; + return { + id: 'delete-application', + label: t('devconsole~Delete application'), + cta: () => { + const reqs = []; + launchModal(LazyDeleteResourceModalOverlay, { + resourceName: application.name, + resourceType: ApplicationModel.label, + onSubmit: () => { + application.resources.forEach((resource) => { + reqs.push(cleanUpWorkload(resource.resource)); + }); + return Promise.all(reqs); + }, + }); + }, + accessReview: asAccessReview(resourceModel, primaryResource, 'delete'), + }; + }, [application, resourceModel, t, launchModal]); }; export const useDeleteResourceAction = ( diff --git a/frontend/packages/dev-console/src/actions/providers.tsx b/frontend/packages/dev-console/src/actions/providers.tsx index 645cd92e7c2..4d11cb7a097 100644 --- a/frontend/packages/dev-console/src/actions/providers.tsx +++ b/frontend/packages/dev-console/src/actions/providers.tsx @@ -36,7 +36,7 @@ import { ADD_TO_PROJECT, } from '../const'; import { AddActions, disabledActionsFilter } from './add-resources'; -import { DeleteApplicationAction } from './context-menu'; +import { useDeleteApplicationAction } from './context-menu'; import { EditImportApplication } from './creators'; type TopologyActionProvider = (data: { @@ -297,6 +297,7 @@ export const useTopologyApplicationActionProvider: TopologyActionProvider = ({ ); const primaryResource = appData.resources?.[0]?.resource || {}; const [kindObj, inFlight] = useK8sModel(referenceFor(primaryResource)); + const deleteApplicationAction = useDeleteApplicationAction(appData, kindObj); return useMemo(() => { if (element.getType() === TYPE_APPLICATION_GROUP) { @@ -307,7 +308,7 @@ export const useTopologyApplicationActionProvider: TopologyActionProvider = ({ ? `${referenceFor(sourceObj)}/${sourceObj?.metadata?.name}` : undefined; const actions = [ - ...(connectorSource ? [] : [DeleteApplicationAction(appData, kindObj)]), + ...(connectorSource ? [] : [deleteApplicationAction]), AddActions.FromGit(namespace, application, sourceReference, path, !isImportResourceAccess), AddActions.ContainerImage( namespace, @@ -354,13 +355,12 @@ export const useTopologyApplicationActionProvider: TopologyActionProvider = ({ element, inFlight, connectorSource, - appData, - kindObj, namespace, application, isImportResourceAccess, isCatalogImageResourceAccess, isServerlessEnabled, isJavaImageStreamEnabled, + deleteApplicationAction, ]); }; diff --git a/frontend/packages/helm-plugin/src/actions/creators.ts b/frontend/packages/helm-plugin/src/actions/creators.ts index 0005844c59e..9e42f5d956e 100644 --- a/frontend/packages/helm-plugin/src/actions/creators.ts +++ b/frontend/packages/helm-plugin/src/actions/creators.ts @@ -1,38 +1,52 @@ +import { useMemo } from 'react'; import { TFunction } from 'i18next'; import { Action, K8sKind } from '@console/dynamic-plugin-sdk'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { coFetchJSON } from '@console/internal/co-fetch'; import { K8sResourceKind, referenceFor } from '@console/internal/module/k8s'; -import { deleteResourceModal } from '@console/shared'; +import { LazyDeleteResourceModalOverlay } from '@console/shared'; import { ProjectHelmChartRepositoryModel } from '../models'; import { HelmActionsScope } from './types'; -export const getHelmDeleteAction = ( - { - release: { name: releaseName, namespace, version: releaseVersion }, - redirect, - }: HelmActionsScope, - t: TFunction, -): Action => ({ - id: 'delete-helm', - label: t('helm-plugin~Delete Helm Release'), - cta: () => { - deleteResourceModal({ - blocking: true, - resourceName: releaseName, - resourceType: 'Helm Release', - actionLabel: t('helm-plugin~Delete'), +export const useHelmDeleteAction = (scope: HelmActionsScope, t: TFunction): Action => { + const launchModal = useOverlay(); + + return useMemo(() => { + if (!scope?.release) { + return { + id: 'delete-helm', + label: t('helm-plugin~Delete Helm Release'), + cta: () => {}, + }; + } + + const { + release: { name: releaseName, namespace, version: releaseVersion }, redirect, - onSubmit: () => { - return coFetchJSON.delete( - `/api/helm/release/async?name=${releaseName}&ns=${namespace}&version=${releaseVersion}`, - null, - null, - -1, - ); + } = scope; + + return { + id: 'delete-helm', + label: t('helm-plugin~Delete Helm Release'), + cta: () => { + launchModal(LazyDeleteResourceModalOverlay, { + resourceName: releaseName, + resourceType: 'Helm Release', + actionLabel: t('helm-plugin~Delete'), + redirect, + onSubmit: () => { + return coFetchJSON.delete( + `/api/helm/release/async?name=${releaseName}&ns=${namespace}&version=${releaseVersion}`, + null, + null, + -1, + ); + }, + }); }, - }); - }, -}); + }; + }, [scope, t, launchModal]); +}; export const getHelmUpgradeAction = ( { release: { name: releaseName, namespace }, actionOrigin }: HelmActionsScope, diff --git a/frontend/packages/helm-plugin/src/actions/providers.ts b/frontend/packages/helm-plugin/src/actions/providers.ts index 86b0d1e1083..a14c66a485b 100644 --- a/frontend/packages/helm-plugin/src/actions/providers.ts +++ b/frontend/packages/helm-plugin/src/actions/providers.ts @@ -17,7 +17,7 @@ import { TYPE_HELM_RELEASE } from '../topology/components/const'; import { HelmReleaseStatus } from '../types/helm-types'; import { AddHelmChartAction } from './add-resources'; import { - getHelmDeleteAction, + useHelmDeleteAction, getHelmRollbackAction, getHelmUpgradeAction, editChartRepository, @@ -26,25 +26,26 @@ import { HelmActionsScope } from './types'; export const useHelmActionProvider = (scope: HelmActionsScope) => { const { t } = useTranslation(); + const helmDeleteAction = useHelmDeleteAction(scope, t); const result = useMemo(() => { if (!scope) return [[], true, undefined]; switch (scope?.release?.info?.status) { case HelmReleaseStatus.PendingInstall: case HelmReleaseStatus.PendingRollback: case HelmReleaseStatus.PendingUpgrade: - return [[getHelmDeleteAction(scope, t)], true, undefined]; + return [[helmDeleteAction], true, undefined]; default: return [ [ getHelmUpgradeAction(scope, t), ...(Number(scope.release.version) > 1 ? [getHelmRollbackAction(scope, t)] : []), - getHelmDeleteAction(scope, t), + helmDeleteAction, ], true, undefined, ]; } - }, [scope, t]); + }, [scope, t, helmDeleteAction]); return result; }; diff --git a/frontend/packages/helm-plugin/src/actions/types.ts b/frontend/packages/helm-plugin/src/actions/types.ts index 64a0b7d5114..b8b00552560 100644 --- a/frontend/packages/helm-plugin/src/actions/types.ts +++ b/frontend/packages/helm-plugin/src/actions/types.ts @@ -10,5 +10,5 @@ type HelmActionObj = { export type HelmActionsScope = { release: HelmRelease | HelmActionObj; actionOrigin?: string; - redirect?: boolean; + redirect?: string; };