Skip to content

Commit f1774b4

Browse files
committed
Migrate UninstallOperatorModal to React Router v6 and hook pattern
Create useUninstallOperatorModal hook, replace history.push with useNavigate, and update all consumers to use new hook pattern. Assisted by: Claude Code
1 parent 10fb5f8 commit f1774b4

5 files changed

Lines changed: 54 additions & 64 deletions

File tree

frontend/packages/operator-lifecycle-manager/src/actions/hooks/useSubscriptionActions.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import { useTranslation } from 'react-i18next';
33
import { CommonActionCreator } from '@console/app/src/actions/hooks/types';
44
import { useCommonActions } from '@console/app/src/actions/hooks/useCommonActions';
55
import { Action } from '@console/dynamic-plugin-sdk';
6-
import { useOverlay } from '@console/dynamic-plugin-sdk/src/lib-core';
76
import { useDeepCompareMemoize } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useDeepCompareMemoize';
87
import { asAccessReview } from '@console/internal/components/utils';
9-
import { referenceFor, k8sKill, k8sGet, k8sPatch } from '@console/internal/module/k8s';
8+
import { referenceFor } from '@console/internal/module/k8s';
109
import { useK8sModel } from '@console/shared/src/hooks/useK8sModel';
11-
import { LazyUninstallOperatorModalOverlay } from '../../components/modals';
10+
import { useUninstallOperatorModal } from '../../components/modals/uninstall-operator-modal';
1211
import { ClusterServiceVersionModel } from '../../models';
1312
import { SubscriptionKind } from '../../types';
1413
import { SubscriptionActionCreator } from './types';
@@ -32,23 +31,18 @@ export const useSubscriptionActions = (
3231
const { t } = useTranslation();
3332
const [model] = useK8sModel(referenceFor(obj));
3433
const [commonActions] = useCommonActions(model, obj, [CommonActionCreator.Edit]);
35-
const launchModal = useOverlay();
3634

3735
const memoizedFilterActions = useDeepCompareMemoize(filterActions);
3836
const installedCSV = obj.status?.installedCSV;
3937

38+
const uninstallOperatorModal = useUninstallOperatorModal(obj);
39+
4040
const factory = useMemo(
4141
() => ({
4242
[SubscriptionActionCreator.RemoveSubscription]: () => ({
4343
id: 'remove-subscription',
4444
label: t('olm~Remove Subscription'),
45-
cta: () =>
46-
launchModal(LazyUninstallOperatorModalOverlay, {
47-
k8sKill,
48-
k8sGet,
49-
k8sPatch,
50-
subscription: obj,
51-
}),
45+
cta: () => uninstallOperatorModal(),
5246
accessReview: asAccessReview(model, obj, 'delete'),
5347
}),
5448
[SubscriptionActionCreator.ViewClusterServiceVersion]: () => {
@@ -61,7 +55,7 @@ export const useSubscriptionActions = (
6155
};
6256
},
6357
}),
64-
[installedCSV, model, obj, t, launchModal],
58+
[installedCSV, model, obj, t, uninstallOperatorModal],
6559
);
6660

6761
// filter and initialize requested actions or construct list of all SubscriptionActions

frontend/packages/operator-lifecycle-manager/src/actions/useOperatorActions.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import { useTranslation } from 'react-i18next';
44
import { K8S_VERB_DELETE } from '@console/dynamic-plugin-sdk/src/api/constants';
55
import { Action } from '@console/dynamic-plugin-sdk/src/extensions/actions';
66
import { useOverlay } from '@console/dynamic-plugin-sdk/src/lib-core';
7-
import { k8sKill, k8sGet, k8sPatch } from '@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource';
87
import { DeleteOverlay } from '@console/internal/components/modals/delete-modal';
98
import { asAccessReview } from '@console/internal/components/utils/rbac';
109
import { resourceObjPath } from '@console/internal/components/utils/resource-link';
1110
import { referenceFor } from '@console/internal/module/k8s';
12-
import { LazyUninstallOperatorModalOverlay } from '../components/modals';
11+
import { useUninstallOperatorModal } from '../components/modals/uninstall-operator-modal';
1312
import { ClusterServiceVersionModel, SubscriptionModel } from '../models';
1413

1514
const useOperatorActions = ({ resource, subscription }): [Action[], boolean, any] => {
1615
const { t } = useTranslation();
1716
const launchModal = useOverlay();
1817

18+
const uninstallOperatorModal = useUninstallOperatorModal(subscription, resource);
19+
1920
const actions = useMemo(() => {
2021
if (!resource) {
2122
return [];
@@ -47,18 +48,11 @@ const useOperatorActions = ({ resource, subscription }): [Action[], boolean, any
4748
{
4849
id: 'uninstall-operator',
4950
label: t('olm~Uninstall Operator'),
50-
cta: () =>
51-
launchModal(LazyUninstallOperatorModalOverlay, {
52-
k8sKill,
53-
k8sGet,
54-
k8sPatch,
55-
subscription,
56-
csv: resource,
57-
}),
51+
cta: () => uninstallOperatorModal(),
5852
accessReview: asAccessReview(SubscriptionModel, subscription, K8S_VERB_DELETE),
5953
},
6054
];
61-
}, [resource, subscription, t, launchModal]);
55+
}, [resource, subscription, t, launchModal, uninstallOperatorModal]);
6256
return [actions, true, null];
6357
};
6458

frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/uninstall-operator-modal.spec.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,31 @@ jest.mock('react-i18next', () => ({
2929
Trans: () => null,
3030
}));
3131

32+
const mockNavigate = jest.fn();
33+
jest.mock('react-router-dom-v5-compat', () => ({
34+
...jest.requireActual('react-router-dom-v5-compat'),
35+
useNavigate: () => mockNavigate,
36+
}));
37+
38+
const mockK8sKill = jest.fn();
39+
3240
jest.mock('@console/dynamic-plugin-sdk/src/utils/k8s', () => ({
3341
k8sGetResource: jest.fn(),
42+
k8sKill: (...args) => mockK8sKill(...args),
3443
}));
3544

3645
describe(UninstallOperatorModal.name, () => {
3746
let uninstallOperatorModalProps: UninstallOperatorModalProps;
3847

3948
beforeEach(() => {
4049
jest.clearAllMocks();
50+
mockK8sKill.mockResolvedValue({});
4151

4252
uninstallOperatorModalProps = {
4353
subscription: {
4454
..._.cloneDeep(testSubscription),
4555
status: { installedCSV: 'testapp.v1.0.0' },
4656
},
47-
k8sKill: jest.fn().mockResolvedValue({}),
48-
k8sGet: jest.fn().mockResolvedValue({}),
49-
k8sPatch: jest.fn().mockResolvedValue({}),
5057
close: jest.fn(),
5158
cancel: jest.fn(),
5259
};
@@ -69,13 +76,14 @@ describe(UninstallOperatorModal.name, () => {
6976
fireEvent.submit(screen.getByRole('form'));
7077

7178
await waitFor(() => {
72-
expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledTimes(2);
79+
expect(mockK8sKill).toHaveBeenCalledTimes(2);
7380
});
7481

75-
expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledWith(
82+
expect(mockK8sKill).toHaveBeenCalledWith(
7683
SubscriptionModel,
7784
uninstallOperatorModalProps.subscription,
7885
{},
86+
{},
7987
expect.objectContaining({
8088
kind: 'DeleteOptions',
8189
apiVersion: 'v1',
@@ -90,10 +98,10 @@ describe(UninstallOperatorModal.name, () => {
9098
fireEvent.submit(screen.getByRole('form'));
9199

92100
await waitFor(() => {
93-
expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledTimes(2);
101+
expect(mockK8sKill).toHaveBeenCalledTimes(2);
94102
});
95103

96-
expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledWith(
104+
expect(mockK8sKill).toHaveBeenCalledWith(
97105
ClusterServiceVersionModel,
98106
expect.objectContaining({
99107
metadata: expect.objectContaining({
@@ -102,6 +110,7 @@ describe(UninstallOperatorModal.name, () => {
102110
}),
103111
}),
104112
{},
113+
{},
105114
expect.objectContaining({
106115
kind: 'DeleteOptions',
107116
apiVersion: 'v1',
@@ -118,7 +127,7 @@ describe(UninstallOperatorModal.name, () => {
118127
fireEvent.submit(screen.getByRole('form'));
119128

120129
await waitFor(() => {
121-
expect(uninstallOperatorModalProps.k8sKill).toHaveBeenCalledTimes(1);
130+
expect(mockK8sKill).toHaveBeenCalledTimes(1);
122131
});
123132
});
124133

frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { useState, useEffect, useCallback } from 'react';
44
import { Alert, Progress, ProgressSize, Title } from '@patternfly/react-core';
55
import * as _ from 'lodash';
66
import { Trans, useTranslation } from 'react-i18next';
7+
import { useNavigate } from 'react-router-dom-v5-compat';
78
import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
9+
import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay';
810
import { k8sGetResource } from '@console/dynamic-plugin-sdk/src/utils/k8s';
911
import { settleAllPromises } from '@console/dynamic-plugin-sdk/src/utils/promise';
1012
import { getActiveNamespace } from '@console/internal/actions/ui';
@@ -18,7 +20,6 @@ import {
1820
ModalComponentProps,
1921
} from '@console/internal/components/factory/modal';
2022
import {
21-
history,
2223
LinkifyExternal,
2324
ResourceLink,
2425
resourceListPathFromModel,
@@ -28,9 +29,9 @@ import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watc
2829
import { useAccessReview } from '@console/internal/components/utils/rbac';
2930
import { ConsoleOperatorConfigModel } from '@console/internal/models';
3031
import {
31-
K8sKind,
3232
K8sResourceCommon,
3333
K8sResourceKind,
34+
k8sKill,
3435
modelFor,
3536
referenceFor,
3637
k8sPatch,
@@ -59,12 +60,10 @@ export const UninstallOperatorModal: FC<UninstallOperatorModalProps> = ({
5960
cancel,
6061
close,
6162
csv,
62-
k8sKill,
63-
k8sGet: _k8sGet, // eslint-disable-line @typescript-eslint/no-unused-vars
64-
k8sPatch: _k8sPatch, // eslint-disable-line @typescript-eslint/no-unused-vars
6563
subscription,
6664
}) => {
6765
const { t } = useTranslation();
66+
const navigate = useNavigate();
6867
const [
6968
handleOperatorUninstallPromise,
7069
operatorUninstallInProgress,
@@ -151,7 +150,7 @@ export const UninstallOperatorModal: FC<UninstallOperatorModalProps> = ({
151150
};
152151

153152
const operatorUninstallPromises = [
154-
k8sKill(SubscriptionModel, subscription, {}, deleteOptions),
153+
k8sKill(SubscriptionModel, subscription, {}, {}, deleteOptions),
155154
...(subscription?.status?.installedCSV && (await clusterServiceVersionExists())
156155
? [
157156
k8sKill(
@@ -163,6 +162,7 @@ export const UninstallOperatorModal: FC<UninstallOperatorModalProps> = ({
163162
},
164163
},
165164
{},
165+
{},
166166
deleteOptions,
167167
),
168168
]
@@ -183,7 +183,6 @@ export const UninstallOperatorModal: FC<UninstallOperatorModalProps> = ({
183183
consoleOperatorConfig,
184184
enabledPlugins,
185185
handleOperatorUninstallPromise,
186-
k8sKill,
187186
removePlugins,
188187
subscription,
189188
]);
@@ -229,9 +228,9 @@ export const UninstallOperatorModal: FC<UninstallOperatorModalProps> = ({
229228
window.location.pathname.split('/').includes(subscription.metadata.name) ||
230229
window.location.pathname.split('/').includes(subscription?.status?.installedCSV)
231230
) {
232-
history.push(resourceListPathFromModel(ClusterServiceVersionModel, getActiveNamespace()));
231+
navigate(resourceListPathFromModel(ClusterServiceVersionModel, getActiveNamespace()));
233232
}
234-
}, [close, subscription]);
233+
}, [close, subscription, navigate]);
235234

236235
useEffect(() => {
237236
if (isSubmitFinished && !hasSubmitErrors) {
@@ -264,7 +263,7 @@ export const UninstallOperatorModal: FC<UninstallOperatorModalProps> = ({
264263
setOperandsRemaining(operands.length);
265264
const operandDeletionPromises = operands.map((operand: K8sResourceCommon) => {
266265
const model = modelFor(referenceFor(operand));
267-
return k8sKill(model, operand, {}, deleteOptions);
266+
return k8sKill(model, operand, {}, {}, deleteOptions);
268267
});
269268
// eslint-disable-next-line promise/catch-or-return
270269
settleAllPromises(operandDeletionPromises).then(([, , results]) => {
@@ -647,14 +646,19 @@ export const UninstallOperatorModalOverlay: OverlayComponent<UninstallOperatorMo
647646
);
648647
};
649648

649+
export const useUninstallOperatorModal = (subscription: K8sResourceKind, csv?: K8sResourceKind) => {
650+
const launchModal = useOverlay();
651+
return useCallback(
652+
() =>
653+
launchModal<UninstallOperatorModalProps>(UninstallOperatorModalOverlay, {
654+
subscription,
655+
csv,
656+
}),
657+
[launchModal, subscription, csv],
658+
);
659+
};
660+
650661
export type UninstallOperatorModalProps = {
651-
k8sKill: (kind: K8sKind, resource: K8sResourceKind, options: any, json: any) => Promise<any>;
652-
k8sGet: (kind: K8sKind, name: string, namespace: string) => Promise<K8sResourceKind>;
653-
k8sPatch: (
654-
kind: K8sKind,
655-
resource: K8sResourceKind,
656-
data: { op: string; path: string; value: any }[],
657-
) => Promise<any>;
658662
subscription: K8sResourceKind;
659663
csv?: K8sResourceKind;
660664
blocking?: boolean;

frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ import {
4747
} from '@console/internal/components/utils';
4848
import { useQueryParamsMutator } from '@console/internal/components/utils/router';
4949
import {
50-
k8sKill,
51-
k8sGet,
52-
k8sPatch,
5350
K8sKind,
5451
K8sModel,
5552
K8sResourceCommon,
@@ -98,11 +95,8 @@ import {
9895
DeprecatedOperatorWarningIcon,
9996
findDeprecatedOperator,
10097
} from './deprecated-operator-warnings/deprecated-operator-warnings';
101-
import {
102-
LazyInstallPlanApprovalModalOverlay,
103-
LazySubscriptionChannelModalOverlay,
104-
LazyUninstallOperatorModalOverlay,
105-
} from './modals';
98+
import { LazyInstallPlanApprovalModalOverlay, LazySubscriptionChannelModalOverlay } from './modals';
99+
import { useUninstallOperatorModal } from './modals/uninstall-operator-modal';
106100
import { requireOperatorGroup } from './operator-group';
107101
import { getManualSubscriptionsInNamespace, NamespaceIncludesManualApproval } from './index';
108102

@@ -424,7 +418,7 @@ export const SubscriptionDetails: FC<SubscriptionDetailsProps> = ({
424418
}) => {
425419
const { t } = useTranslation();
426420
const { removeQueryArgument } = useQueryParamsMutator();
427-
const launchModal = useOverlay();
421+
const uninstallOperatorModal = useUninstallOperatorModal(obj);
428422
const { source, sourceNamespace } = obj?.spec ?? {};
429423
const catalogHealth = obj?.status?.catalogHealth?.find(
430424
(ch) => ch.catalogSourceRef.name === source,
@@ -435,15 +429,10 @@ export const SubscriptionDetails: FC<SubscriptionDetailsProps> = ({
435429

436430
useEffect(() => {
437431
if (new URLSearchParams(window.location.search).has('showDelete')) {
438-
launchModal(LazyUninstallOperatorModalOverlay, {
439-
k8sKill,
440-
k8sGet,
441-
k8sPatch,
442-
subscription: obj,
443-
});
432+
uninstallOperatorModal();
444433
removeQueryArgument('showDelete');
445434
}
446-
}, [launchModal, obj, removeQueryArgument]);
435+
}, [uninstallOperatorModal, removeQueryArgument]);
447436

448437
const { deprecatedPackage, deprecatedChannel, deprecatedVersion } = findDeprecatedOperator(obj);
449438

0 commit comments

Comments
 (0)