Skip to content

Commit 35e3e03

Browse files
authored
EDM-2216: Improve UX of showing results after resuming all devices (#358)
1 parent 9364082 commit 35e3e03

3 files changed

Lines changed: 135 additions & 57 deletions

File tree

libs/i18n/locales/en/translation.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -659,12 +659,15 @@
659659
"No suspended devices match the specified labels.": "No suspended devices match the specified labels.",
660660
"Resume devices failed": "Resume devices failed",
661661
"Resume successful": "Resume successful",
662-
"{{ resumedCount }} devices were resumed": "{{ resumedCount }} devices were resumed",
662+
"{{ count }} devices were resumed_one": "{{ count }} device was resumed",
663+
"{{ count }} devices were resumed_other": "{{ count }} devices were resumed",
663664
"Resumed with warnings": "Resumed with warnings",
664665
"{{ expectedCount }} devices to resume, and {{ resumedCount }} resumed successfully": "{{ expectedCount }} devices to resume, and {{ resumedCount }} resumed successfully",
665-
"Resume all {{ deviceCount }} devices?": "Resume all {{ deviceCount }} devices?",
666+
"Resume all {{ count }} devices?_one": "Resume {{ count }} device?",
667+
"Resume all {{ count }} devices?_other": "Resume all {{ count }} devices?",
668+
"Resume devices result": "Resume devices result",
666669
"Resume all devices": "Resume all devices",
667-
"Resume devices?_one": "Resume devices?",
670+
"Resume devices?_one": "Resume device?",
668671
"Resume devices?_other": "Resume devices?",
669672
"Resume": "Resume",
670673
"This action will resolve the configuration conflict and allow the devices to receive new updates from the server. This action is irreversible, please ensure the devices' assigned configuration is correct before proceeding._one": "This action will resolve the configuration conflict and allow the device to receive new updates from the server. This action is irreversible, please ensure the device's assigned configuration is correct before proceeding.",

libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/MassResumeDevicesModal.tsx

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -130,22 +130,18 @@ const MassResumeDevicesModalContent = ({ onClose }: MassResumeDevicesModalProps)
130130
setSubmitError(undefined);
131131

132132
try {
133-
let labels: FlightCtlLabel[];
133+
let labels: FlightCtlLabel[] = [];
134134

135-
if (values.mode === SelectionMode.ALL) {
136-
labels = [];
137-
} else {
138-
if (values.mode === SelectionMode.FLEET) {
139-
labels = getSelectedFleetLabels(fleets, values.fleetId);
140-
} else {
141-
labels = values.labels;
142-
}
143-
// This shouldn't happen due to validations, but since an empty label selector would target all existing devices,
144-
// wake sure the UI does't accidentally submit a request with an empty selector
145-
const emptySelection = labels.every((label) => label.key === '');
146-
if (labels.length === 0 || emptySelection) {
147-
throw new Error('The current selection would target all devices.');
148-
}
135+
if (values.mode === SelectionMode.FLEET) {
136+
labels = getSelectedFleetLabels(fleets, values.fleetId);
137+
} else if (values.mode === SelectionMode.LABELS) {
138+
labels = values.labels;
139+
}
140+
// This shouldn't happen due to validations, but since an empty label selector would target all existing devices,
141+
// wake sure the UI does't accidentally submit a request with an empty selector
142+
const emptySelection = labels.every((label) => label.key === '');
143+
if (labels.length === 0 || emptySelection) {
144+
throw new Error('The current selection would target all devices.');
149145
}
150146

151147
const resumeRequest: DeviceResumeRequest = {
@@ -411,7 +407,7 @@ const MassResumeDevicesModalContent = ({ onClose }: MassResumeDevicesModalProps)
411407
{resumedCount !== undefined && hasResumedAllExpected && (
412408
<StackItem>
413409
<Alert isInline variant="success" title={t('Resume successful')}>
414-
{t('{{ resumedCount }} devices were resumed', { resumedCount })}
410+
{t('{{ count }} devices were resumed', { count: resumedCount })}
415411
</Alert>
416412
</StackItem>
417413
)}
@@ -429,12 +425,12 @@ const MassResumeDevicesModalContent = ({ onClose }: MassResumeDevicesModalProps)
429425
</Stack>
430426
{showResumeAllConfirmation && (
431427
<ResumeAllDevicesConfirmationDialog
432-
deviceCountNum={deviceCountNum}
433-
onClose={(doConfirm) => {
428+
devicesToResume={deviceCountNum}
429+
onClose={(resumedCount) => {
430+
setResumedCount(resumedCount);
431+
setIsCountLoading(false);
432+
setCountError(null);
434433
setShowResumeAllConfirmation(false);
435-
if (doConfirm) {
436-
performResume();
437-
}
438434
}}
439435
/>
440436
)}

libs/ui-components/src/components/modals/massModals/ResumeDevicesModal/ResumeAllDevicesConfirmationDialog.tsx

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,132 @@
11
import * as React from 'react';
2-
import { Button, Modal, ModalVariant, Stack, StackItem, Text, TextContent } from '@patternfly/react-core';
32
import { Trans } from 'react-i18next';
3+
import { Alert, Button, Modal, ModalVariant, Stack, StackItem, Text, TextContent } from '@patternfly/react-core';
4+
import { DeviceResumeRequest, DeviceResumeResponse } from '@flightctl/types';
45

56
import { useTranslation } from '../../../../hooks/useTranslation';
7+
import { useFetch } from '../../../../hooks/useFetch';
8+
import { getErrorMessage } from '../../../../utils/error';
69

710
interface ResumeAllDevicesConfirmationModalProps {
8-
deviceCountNum: number;
9-
onClose: (doConfirm: boolean) => void;
11+
devicesToResume: number;
12+
onClose: (resumedCount: number | undefined) => void;
1013
isSubmitting?: boolean;
1114
}
1215

13-
const ResumeAllDevicesConfirmationModal = ({ deviceCountNum, onClose }: ResumeAllDevicesConfirmationModalProps) => {
16+
const ModalContentBeforeResume = ({ devicesToResume }: { devicesToResume: number }) => {
1417
const { t } = useTranslation();
1518

16-
const deviceCount = deviceCountNum.toString();
19+
const deviceCount = devicesToResume.toString();
20+
return (
21+
<Stack hasGutter>
22+
<StackItem>
23+
<TextContent>
24+
<Text>
25+
<Trans t={t} count={devicesToResume}>
26+
You are about to resume all <strong>{deviceCount}</strong> suspended devices.
27+
</Trans>
28+
</Text>
29+
</TextContent>
30+
<TextContent>
31+
<Text>
32+
{t(
33+
'This action is irreversible and will allow all affected devices to receive new configuration updates from the server.',
34+
)}
35+
</Text>
36+
</TextContent>
37+
</StackItem>
38+
</Stack>
39+
);
40+
};
41+
42+
const ModalContentAfterResume = ({
43+
hasResumedAll,
44+
resumedCount,
45+
submitError,
46+
}: {
47+
hasResumedAll: boolean;
48+
resumedCount: number;
49+
submitError: string | undefined;
50+
}) => {
51+
const { t } = useTranslation();
52+
if (submitError) {
53+
return (
54+
<Alert isInline variant="danger" title={t('Resume devices failed')}>
55+
{submitError}
56+
</Alert>
57+
);
58+
}
1759

60+
const alertType = hasResumedAll ? 'success' : 'warning';
61+
const title = hasResumedAll ? t('Resume successful') : t('Resumed with warnings');
1862
return (
19-
<Modal
20-
variant={ModalVariant.small}
21-
title={t('Resume all {{ deviceCount }} devices?', { deviceCount })}
22-
isOpen
23-
onClose={() => onClose(false)}
24-
actions={[
25-
<Button key="confirm" variant="primary" onClick={() => onClose(true)}>
63+
<Alert isInline variant={alertType} title={title}>
64+
{t('{{ count }} devices were resumed', { count: resumedCount })}
65+
</Alert>
66+
);
67+
};
68+
69+
const ResumeAllDevicesConfirmationModal = ({ devicesToResume, onClose }: ResumeAllDevicesConfirmationModalProps) => {
70+
const { t } = useTranslation();
71+
const { post } = useFetch();
72+
73+
const [resumedCount, setResumedCount] = React.useState<number | undefined>(undefined);
74+
const [isSubmitting, setIsSubmitting] = React.useState(false);
75+
const [submitError, setSubmitError] = React.useState<string | undefined>(undefined);
76+
77+
const isBeforeResume = resumedCount === undefined;
78+
const title = isBeforeResume
79+
? t('Resume all {{ count }} devices?', { count: devicesToResume })
80+
: t('Resume devices result');
81+
82+
const buttons = isBeforeResume
83+
? [
84+
<Button
85+
key="confirm"
86+
variant="primary"
87+
isDisabled={isSubmitting}
88+
onClick={async () => {
89+
setIsSubmitting(true);
90+
try {
91+
const resumeRequest: DeviceResumeRequest = {
92+
labelSelector: '', // All devices
93+
};
94+
const resumeResponse = await post<DeviceResumeRequest, DeviceResumeResponse>(
95+
'deviceactions/resume',
96+
resumeRequest,
97+
);
98+
setResumedCount(resumeResponse.resumedDevices || 0);
99+
} catch (error) {
100+
setResumedCount(0);
101+
setSubmitError(getErrorMessage(error));
102+
} finally {
103+
setIsSubmitting(false);
104+
}
105+
}}
106+
>
26107
{t('Resume all devices')}
27108
</Button>,
28-
<Button key="cancel" variant="link" onClick={() => onClose(false)}>
109+
<Button key="cancel" variant="link" isDisabled={isSubmitting} onClick={() => onClose(undefined)}>
29110
{t('Cancel')}
30111
</Button>,
31-
]}
32-
>
33-
<Stack hasGutter>
34-
<StackItem>
35-
<TextContent>
36-
<Text>
37-
<Trans t={t} count={deviceCountNum}>
38-
You are about to resume all <strong>{deviceCount}</strong> suspended devices.
39-
</Trans>
40-
</Text>
41-
</TextContent>
42-
<TextContent>
43-
<Text>
44-
{t(
45-
'This action is irreversible and will allow all affected devices to receive new configuration updates from the server.',
46-
)}
47-
</Text>
48-
</TextContent>
49-
</StackItem>
50-
</Stack>
112+
]
113+
: [
114+
<Button key="close" variant="primary" onClick={() => onClose(resumedCount)}>
115+
{t('Close')}
116+
</Button>,
117+
];
118+
119+
return (
120+
<Modal variant={ModalVariant.small} title={title} isOpen onClose={() => onClose(undefined)} actions={buttons}>
121+
{isBeforeResume ? (
122+
<ModalContentBeforeResume devicesToResume={devicesToResume} />
123+
) : (
124+
<ModalContentAfterResume
125+
resumedCount={resumedCount}
126+
hasResumedAll={resumedCount === devicesToResume}
127+
submitError={submitError}
128+
/>
129+
)}
51130
</Modal>
52131
);
53132
};

0 commit comments

Comments
 (0)