Skip to content

Commit cd32514

Browse files
committed
Made confirm modal generic and added tests
1 parent ae0a928 commit cd32514

File tree

9 files changed

+210
-79
lines changed

9 files changed

+210
-79
lines changed

frontend/src/__tests__/cypress/cypress/pages/modelServing.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,18 @@ class ModelServingRow extends TableRow {
564564
findInternalServicePopover() {
565565
return cy.findByTestId('internal-service-popover');
566566
}
567+
568+
findConfirmStopModal() {
569+
return cy.findByTestId('stop-model-modal');
570+
}
571+
572+
findConfirmStopModalButton() {
573+
return this.findConfirmStopModal().findByTestId('stop-model-button');
574+
}
575+
576+
findConfirmStopModalCheckbox() {
577+
return this.findConfirmStopModal().findByTestId('dont-show-again-checkbox');
578+
}
567579
}
568580

569581
class ModelMeshRow extends ModelServingRow {

frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/runtime/servingRuntimeList.cy.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,10 @@ describe('Serving Runtime List', () => {
13401340
}),
13411341
],
13421342
});
1343+
cy.clearLocalStorage('odh.dashboard.modelServing.stop.modal.preference');
1344+
cy.window().then((win) =>
1345+
win.localStorage.setItem('odh.dashboard.modelServing.stop.modal.preference', 'false'),
1346+
);
13431347
projectDetails.visitSection('test-project', 'model-server');
13441348

13451349
const kserveRow = modelServingSection.getKServeRow('test-model');
@@ -1373,8 +1377,20 @@ describe('Serving Runtime List', () => {
13731377
);
13741378

13751379
kserveRow.findStateActionToggle().should('have.text', 'Stop').click();
1380+
kserveRow.findConfirmStopModal().should('exist');
1381+
kserveRow.findConfirmStopModalCheckbox().should('exist');
1382+
kserveRow.findConfirmStopModalCheckbox().should('not.be.checked');
1383+
kserveRow.findConfirmStopModalCheckbox().click();
1384+
kserveRow.findConfirmStopModalCheckbox().should('be.checked');
1385+
kserveRow.findConfirmStopModalButton().click();
13761386
cy.wait(['@stopModelPatch', '@getStoppedModel']);
13771387
kserveRow.findStateActionToggle().should('have.text', 'Start');
1388+
cy.window().then((win) => {
1389+
const preference = win.localStorage.getItem(
1390+
'odh.dashboard.modelServing.stop.modal.preference',
1391+
);
1392+
expect(preference).to.equal('true');
1393+
});
13781394

13791395
const runningInferenceService = mockInferenceServiceK8sResource({
13801396
name: 'test-model',
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from 'react';
2+
import { Button } from '@patternfly/react-core';
3+
import ConfirmStopModal from '#~/pages/projects/components/ConfirmStopModal';
4+
import useStopModelModalAvailability from './useStopModelModalAvailability';
5+
6+
type ModelServingStopModalProps = {
7+
modelName: string;
8+
title: string;
9+
onClose: (confirmStatus: boolean) => void;
10+
};
11+
12+
const ModelServingStopModal: React.FC<ModelServingStopModalProps> = ({
13+
modelName,
14+
title,
15+
onClose,
16+
}) => {
17+
const [dontShowModalValue, setDontShowModalValue] = useStopModelModalAvailability();
18+
19+
const onBeforeClose = (confirmStatus: boolean) => {
20+
if (!confirmStatus) {
21+
setDontShowModalValue(false);
22+
}
23+
onClose(confirmStatus);
24+
};
25+
const modalActions = [
26+
<Button
27+
key="confirm"
28+
variant="primary"
29+
onClick={() => onBeforeClose(true)}
30+
data-testid="stop-model-button"
31+
>
32+
Stop
33+
</Button>,
34+
<Button
35+
key="cancel"
36+
variant="secondary"
37+
onClick={() => onBeforeClose(false)}
38+
data-testid="cancel-stop-model-button"
39+
>
40+
Cancel
41+
</Button>,
42+
];
43+
return (
44+
<ConfirmStopModal
45+
message={
46+
<>
47+
Are you sure you want to stop <strong>{modelName}</strong>?
48+
</>
49+
}
50+
modalActions={modalActions}
51+
onBeforeClose={onBeforeClose}
52+
title={title}
53+
dataTestId="stop-model-modal"
54+
dontShowModalValue={dontShowModalValue}
55+
setDontShowModalValue={setDontShowModalValue}
56+
/>
57+
);
58+
};
59+
60+
export default ModelServingStopModal;

frontend/src/pages/modelServing/screens/global/InferenceServiceTableRow.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { isProjectNIMSupported } from '#~/pages/modelServing/screens/projects/ni
1414
import useServingPlatformStatuses from '#~/pages/modelServing/useServingPlatformStatuses';
1515
import StateActionToggle from '#~/components/StateActionToggle';
1616
import { patchInferenceServiceStoppedStatus } from '#~/api/k8s/inferenceServices';
17+
import useStopModelModalAvailability from '#~/pages/modelServing/useStopModelModalAvailability';
18+
import ModelServingStopModal from '#~/pages/modelServing/ModelServingStopModal';
1719
import InferenceServiceEndpoint from './InferenceServiceEndpoint';
1820
import InferenceServiceProject from './InferenceServiceProject';
1921
import InferenceServiceStatus from './InferenceServiceStatus';
@@ -40,6 +42,8 @@ const InferenceServiceTableRow: React.FC<InferenceServiceTableRowProps> = ({
4042
isGlobal,
4143
columnNames,
4244
}) => {
45+
const [dontShowModalValue] = useStopModelModalAvailability();
46+
const [isOpenConfirm, setOpenConfirm] = React.useState(false);
4347
const { projects } = React.useContext(ProjectsContext);
4448
const project = projects.find(byName(inferenceService.metadata.namespace)) ?? null;
4549
const isKServeNIMEnabled = project ? isProjectNIMSupported(project) : false;
@@ -62,8 +66,12 @@ const InferenceServiceTableRow: React.FC<InferenceServiceTableRowProps> = ({
6266
}, [inferenceService, refresh]);
6367

6468
const onStop = React.useCallback(() => {
65-
patchInferenceServiceStoppedStatus(inferenceService, 'true').then(refresh);
66-
}, [inferenceService, refresh]);
69+
if (dontShowModalValue) {
70+
patchInferenceServiceStoppedStatus(inferenceService, 'true').then(refresh);
71+
} else {
72+
setOpenConfirm(true);
73+
}
74+
}, [dontShowModalValue, inferenceService, refresh]);
6775

6876
return (
6977
<>
@@ -148,6 +156,18 @@ const InferenceServiceTableRow: React.FC<InferenceServiceTableRowProps> = ({
148156
/>
149157
</Td>
150158
)}
159+
{isOpenConfirm && (
160+
<ModelServingStopModal
161+
modelName={displayName}
162+
title="Stop model?"
163+
onClose={(confirmStatus) => {
164+
if (confirmStatus) {
165+
patchInferenceServiceStoppedStatus(inferenceService, 'true').then(refresh);
166+
}
167+
setOpenConfirm(false);
168+
}}
169+
/>
170+
)}
151171
</>
152172
);
153173
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { useBrowserStorage } from '#~/components/browserStorage/BrowserStorageContext';
2+
3+
const useStopModelModalAvailability = (): [boolean, (v: boolean) => void] =>
4+
useBrowserStorage<boolean>('odh.dashboard.modelServing.stop.modal.preference', false);
5+
6+
export default useStopModelModalAvailability;

frontend/src/pages/notebookController/screens/server/StopServerModal.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import * as React from 'react';
22
import { Button } from '@patternfly/react-core';
33
import { Notebook } from '#~/types';
4-
import StopWorkbenchModal from '#~/pages/projects/notebook/StopWorkbenchModal';
54
import useStopNotebookModalAvailability from '#~/pages/projects/notebook/useStopNotebookModalAvailability';
5+
import ConfirmStopModal from '#~/pages/projects/components/ConfirmStopModal.tsx';
66

77
type StopServerModalProps = {
88
notebooksToStop: Notebook[];
@@ -17,7 +17,7 @@ const StopServerModal: React.FC<StopServerModalProps> = ({
1717
isDeleting,
1818
onNotebooksStop,
1919
}) => {
20-
const [, setDontShowModalValue] = useStopNotebookModalAvailability();
20+
const [dontShowModalValue, setDontShowModalValue] = useStopNotebookModalAvailability();
2121

2222
if (!notebooksToStop.length) {
2323
return null;
@@ -94,13 +94,21 @@ const StopServerModal: React.FC<StopServerModalProps> = ({
9494
];
9595

9696
return (
97-
<StopWorkbenchModal
98-
workbenchName={getWorkbenchName()}
97+
<ConfirmStopModal
98+
message={
99+
<>
100+
Any unsaved changes to the <strong>{getWorkbenchName()}</strong> will be lost.
101+
</>
102+
}
99103
isRunning
100104
modalActions={modalActions}
101105
link={displayLink()}
102106
onBeforeClose={onBeforeClose}
103107
title={getWorkbenchModalTitle()}
108+
dontShowModalValue={dontShowModalValue}
109+
setDontShowModalValue={setDontShowModalValue}
110+
saveChanges
111+
dataTestId="stop-server-modal"
104112
/>
105113
);
106114
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import {
2+
ModalBody,
3+
Modal,
4+
StackItem,
5+
ModalHeader,
6+
Stack,
7+
ModalFooter,
8+
Checkbox,
9+
FlexItem,
10+
Flex,
11+
} from '@patternfly/react-core';
12+
import React from 'react';
13+
14+
type ConfirmStopModalProps = {
15+
modalActions: React.ReactNode[];
16+
onBeforeClose: (confirmStatus: boolean) => void;
17+
title: string;
18+
dataTestId: string;
19+
message: React.ReactNode;
20+
dontShowModalValue: boolean;
21+
setDontShowModalValue: (value: boolean) => void;
22+
isRunning?: boolean;
23+
link?: React.ReactNode;
24+
saveChanges?: boolean;
25+
};
26+
27+
const ConfirmStopModal: React.FC<ConfirmStopModalProps> = ({
28+
modalActions,
29+
onBeforeClose,
30+
title = 'Stop?',
31+
dataTestId = 'confirm-stop-modal',
32+
message,
33+
dontShowModalValue,
34+
setDontShowModalValue,
35+
isRunning = false,
36+
link,
37+
saveChanges = false,
38+
}) => (
39+
<Modal variant="small" data-testid={dataTestId} isOpen onClose={() => onBeforeClose(false)}>
40+
<ModalHeader title={title} />
41+
<ModalBody>
42+
<Stack hasGutter>
43+
<StackItem>{message}</StackItem>
44+
{saveChanges && isRunning && link && (
45+
<StackItem>
46+
<Flex>
47+
<FlexItem spacer={{ default: 'spacerXs' }}>To save changes, </FlexItem>
48+
<FlexItem spacer={{ default: 'spacerNone' }}>{link}</FlexItem>
49+
<FlexItem spacer={{ default: 'spacerNone' }}>.</FlexItem>
50+
</Flex>
51+
</StackItem>
52+
)}
53+
<StackItem>
54+
<Checkbox
55+
id="dont-show-again"
56+
data-testid="dont-show-again-checkbox"
57+
label="Don't show again"
58+
isChecked={dontShowModalValue}
59+
onChange={(e, checked) => setDontShowModalValue(checked)}
60+
/>
61+
</StackItem>
62+
</Stack>
63+
</ModalBody>
64+
<ModalFooter>{modalActions}</ModalFooter>
65+
</Modal>
66+
);
67+
68+
export default ConfirmStopModal;

frontend/src/pages/projects/notebook/StopNotebookConfirmModal.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as React from 'react';
22
import { Button } from '@patternfly/react-core';
33
import { getDisplayNameFromK8sResource } from '#~/concepts/k8s/utils';
4+
import ConfirmStopModal from '#~/pages/projects/components/ConfirmStopModal.tsx';
45
import NotebookRouteLink from './NotebookRouteLink';
56
import useStopNotebookModalAvailability from './useStopNotebookModalAvailability';
67
import { NotebookState } from './types';
7-
import StopWorkbenchModal from './StopWorkbenchModal';
88

99
type StopNotebookConfirmProps = {
1010
notebookState: NotebookState;
@@ -16,7 +16,7 @@ const StopNotebookConfirmModal: React.FC<StopNotebookConfirmProps> = ({
1616
onClose,
1717
}) => {
1818
const { notebook, isRunning } = notebookState;
19-
const [, setDontShowModalValue] = useStopNotebookModalAvailability();
19+
const [dontShowModalValue, setDontShowModalValue] = useStopNotebookModalAvailability();
2020
const onBeforeClose = (confirmStatus: boolean) => {
2121
if (!confirmStatus) {
2222
// Disable the choice -- we were in this modal and they checked and then cancelled -- so undo it
@@ -41,12 +41,22 @@ const StopNotebookConfirmModal: React.FC<StopNotebookConfirmProps> = ({
4141
];
4242

4343
return (
44-
<StopWorkbenchModal
45-
workbenchName={<strong>{getDisplayNameFromK8sResource(notebook)}</strong>}
44+
<ConfirmStopModal
45+
message={
46+
<>
47+
Any unsaved changes to the <strong>{getDisplayNameFromK8sResource(notebook)}</strong> will
48+
be lost.
49+
</>
50+
}
4651
isRunning={isRunning}
4752
modalActions={modalActions}
4853
link={<NotebookRouteLink label="open the workbench" notebook={notebook} isRunning />}
4954
onBeforeClose={onBeforeClose}
55+
dataTestId="stop-notebook-modal"
56+
title="Stop workbench?"
57+
saveChanges
58+
dontShowModalValue={dontShowModalValue}
59+
setDontShowModalValue={setDontShowModalValue}
5060
/>
5161
);
5262
};

frontend/src/pages/projects/notebook/StopWorkbenchModal.tsx

Lines changed: 0 additions & 69 deletions
This file was deleted.

0 commit comments

Comments
 (0)