Skip to content

Commit 335ef07

Browse files
sequencing cases conversion/download (#413)
Signed-off-by: Mathieu DEHARBE <[email protected]>
1 parent 17bc306 commit 335ef07

File tree

6 files changed

+104
-57
lines changed

6 files changed

+104
-57
lines changed

src/components/menus/content-contextual-menu.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ const ContentContextualMenu = (props) => {
9090

9191
const selectedDirectory = useSelector((state) => state.selectedDirectory);
9292
const [hideMenu, setHideMenu] = useState(false);
93-
const { handleDownloadCases, handleConvertCases } = useDownloadUtils();
93+
const { handleDownloadCases, handleConvertCases, stopCasesExports } =
94+
useDownloadUtils();
9495

9596
const [languageLocal] = useParameterState(PARAM_LANGUAGE);
9697

@@ -302,6 +303,11 @@ const ContentContextualMenu = (props) => {
302303
setHideMenu(false);
303304
}, [onClose, setOpenDialog]);
304305

306+
const handleCloseExportDialog = useCallback(() => {
307+
stopCasesExports();
308+
handleCloseDialog();
309+
}, [handleCloseDialog, stopCasesExports]);
310+
305311
const [deleteError, setDeleteError] = useState('');
306312
const handleDeleteElements = useCallback(
307313
(elementsUuids) => {
@@ -706,7 +712,7 @@ const ContentContextualMenu = (props) => {
706712
case DialogsId.EXPORT:
707713
return (
708714
<ExportCaseDialog
709-
onClose={handleCloseDialog}
715+
onClose={handleCloseExportDialog}
710716
onExport={(format, formatParameters) =>
711717
handleConvertCases(
712718
selectedElements,

src/components/toolbars/content-toolbar.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const ContentToolbar = (props) => {
3030
const { snackError } = useSnackMessage();
3131
const intl = useIntl();
3232
const selectedDirectory = useSelector((state) => state.selectedDirectory);
33-
const { handleDownloadCases, handleConvertCases } = useDownloadUtils();
33+
const { handleDownloadCases, handleConvertCases, stopCasesExports } =
34+
useDownloadUtils();
3435

3536
const [openDialog, setOpenDialog] = useState(null);
3637

@@ -51,6 +52,11 @@ const ContentToolbar = (props) => {
5152
setOpenDialog(DialogsId.NONE);
5253
}, []);
5354

55+
const handleCloseExportDialog = useCallback(() => {
56+
stopCasesExports();
57+
handleCloseDialog();
58+
}, [handleCloseDialog, stopCasesExports]);
59+
5460
const moveElementErrorToString = useCallback(
5561
(HTTPStatusCode) => {
5662
if (HTTPStatusCode === 403) {
@@ -227,7 +233,7 @@ const ContentToolbar = (props) => {
227233
case DialogsId.EXPORT:
228234
return (
229235
<ExportCaseDialog
230-
onClose={handleCloseDialog}
236+
onClose={handleCloseExportDialog}
231237
onExport={(format, formatParameters) =>
232238
handleConvertCases(
233239
selectedElements,

src/components/utils/caseUtils.ts

Lines changed: 79 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77

88
import {
99
downloadCase,
10-
exportCase,
10+
fetchConvertedCase,
1111
getCaseOriginalName,
1212
} from '../../utils/rest-api';
1313
import { useIntl } from 'react-intl';
1414
import { ElementType, useSnackMessage } from '@gridsuite/commons-ui';
15+
import { useCallback, useState } from 'react';
1516

1617
const downloadCases = async (uuids: string[]) => {
1718
for (const uuid of uuids) {
@@ -31,48 +32,55 @@ const downloadCases = async (uuids: string[]) => {
3132
}
3233
};
3334

34-
const exportCases = async (
35-
cases: any[],
36-
format: string,
37-
formatParameters: {
38-
[parameterName: string]: any;
39-
},
40-
onError?: (caseElement: any, errorMsg: string) => void
41-
): Promise<void> => {
42-
const files: { name: string; blob: Blob }[] = [];
43-
for (const c of cases) {
35+
export function useDownloadUtils() {
36+
const intl = useIntl();
37+
const { snackError, snackInfo } = useSnackMessage();
38+
const capitalizeFirstLetter = (string: string) =>
39+
`${string.charAt(0).toUpperCase()}${string.slice(1)}`;
40+
const [abortController, setAbortController] = useState<AbortController>();
41+
42+
const handleCaseExportError = (caseElement: any, errorMsg: string) =>
43+
snackError({
44+
headerId: 'download.error',
45+
headerValues: { caseName: caseElement.elementName },
46+
messageTxt: errorMsg,
47+
});
48+
49+
const exportCase = async (
50+
caseElement: any,
51+
format: string,
52+
formatParameters: {
53+
[parameterName: string]: any;
54+
},
55+
abortController: AbortController
56+
): Promise<void> => {
4457
try {
45-
const result = await exportCase(
46-
c.elementUuid,
58+
const result = await fetchConvertedCase(
59+
caseElement.elementUuid,
4760
format,
48-
formatParameters
61+
formatParameters,
62+
abortController
4963
);
5064
let filename = result.headers
5165
.get('Content-Disposition')
5266
.split('filename=')[1];
5367
filename = filename.substring(1, filename.length - 1); // We remove quotes
5468
const blob = await result.blob();
55-
files.push({ name: filename, blob });
56-
} catch (e: any) {
57-
onError?.(c, e);
58-
}
59-
}
60-
for (const file of files) {
61-
const href = window.URL.createObjectURL(file.blob);
62-
const link = document.createElement('a');
63-
link.href = href;
64-
link.setAttribute('download', file.name);
65-
document.body.appendChild(link);
66-
link.click();
67-
document.body.removeChild(link);
68-
}
69-
};
7069

71-
export function useDownloadUtils() {
72-
const intl = useIntl();
73-
const { snackError, snackInfo } = useSnackMessage();
74-
const capitalizeFirstLetter = (string: string) =>
75-
`${string.charAt(0).toUpperCase()}${string.slice(1)}`;
70+
const href = window.URL.createObjectURL(blob);
71+
const link = document.createElement('a');
72+
link.href = href;
73+
link.setAttribute('download', filename);
74+
document.body.appendChild(link);
75+
link.click();
76+
document.body.removeChild(link);
77+
} catch (error: any) {
78+
if (error.name === 'AbortError') {
79+
throw error;
80+
}
81+
handleCaseExportError(caseElement, error);
82+
}
83+
};
7684

7785
const buildPartialDownloadMessage = (
7886
numberOfDownloadedCases: number,
@@ -183,13 +191,13 @@ export function useDownloadUtils() {
183191
);
184192
};
185193

186-
const handleCaseExportError = (caseElement: any, errorMsg: string) =>
187-
snackError({
188-
headerId: 'download.error',
189-
headerValues: { caseName: caseElement.elementName },
190-
messageTxt: errorMsg,
191-
});
194+
const stopCasesExports = useCallback(() => {
195+
if (abortController) {
196+
abortController.abort();
197+
}
198+
}, [abortController]);
192199

200+
// downloads converted files one after another. The downloading may be interrupted midterm with a few files downloaded already.
193201
const handleConvertCases = async (
194202
selectedElements: any[],
195203
format: string,
@@ -200,19 +208,38 @@ export function useDownloadUtils() {
200208
const cases = selectedElements.filter(
201209
(element) => element.type === ElementType.CASE
202210
);
203-
await exportCases(
204-
cases,
205-
format,
206-
formatParameters,
207-
handleCaseExportError
208-
);
209-
if (cases.length !== selectedElements.length) {
210-
snackInfo({
211-
messageTxt: buildPartialDownloadMessage(
211+
let message: string = '';
212+
213+
try {
214+
const controller = new AbortController();
215+
setAbortController(controller);
216+
217+
for (const c of cases) {
218+
await exportCase(c, format, formatParameters, controller);
219+
}
220+
} catch (error: any) {
221+
if (error.name === 'AbortError') {
222+
message = intl.formatMessage({
223+
id: 'download.stopped',
224+
});
225+
}
226+
} finally {
227+
setAbortController(undefined);
228+
229+
if (
230+
message.length === 0 &&
231+
cases.length !== selectedElements.length
232+
) {
233+
message += buildPartialDownloadMessage(
212234
cases.length,
213235
selectedElements
214-
),
215-
});
236+
);
237+
}
238+
if (message.length > 0) {
239+
snackInfo({
240+
messageTxt: message,
241+
});
242+
}
216243
}
217244
};
218245

@@ -231,5 +258,5 @@ export function useDownloadUtils() {
231258
}
232259
};
233260

234-
return { handleDownloadCases, handleConvertCases };
261+
return { handleDownloadCases, handleConvertCases, stopCasesExports };
235262
}

src/translations/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@
222222
"voltageRegulatorOn": "Voltage regulation",
223223
"PlannedActivePowerSetPoint": "Planning active power set point (MW)",
224224
"download.button": "Download",
225+
"download.stopped": "Download stopped. Only part of the files were downloaded.",
225226
"download.export.button": "Convert",
226227
"download.exportFormat": "Export format",
227228
"download.message.downloadedCase": "A case has been downloaded",

src/translations/fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@
222222
"voltageRegulatorOn": "Réglage de tension",
223223
"PlannedActivePowerSetPoint": "Puissance imposée (MW)",
224224
"download.button": "Télécharger",
225+
"download.stopped": "Téléchargement interrompu. Seule une partie des fichiers a été téléchargée.",
225226
"download.export.button": "Convertir",
226227
"download.exportFormat": "Format de l'export",
227228
"download.message.downloadedCase": "Une situation a été téléchargée.",

src/utils/rest-api.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,13 +1050,19 @@ export function deleteCase(caseUuid) {
10501050
});
10511051
}
10521052

1053-
export const exportCase = (caseUuid, format, formatParameters) =>
1053+
export const fetchConvertedCase = (
1054+
caseUuid,
1055+
format,
1056+
formatParameters,
1057+
abortController
1058+
) =>
10541059
backendFetch(
10551060
`${PREFIX_CASE_QUERIES}/v1/cases/${caseUuid}?format=${format}`,
10561061
{
10571062
method: 'post',
10581063
headers: { 'Content-Type': 'application/json' },
10591064
body: JSON.stringify(formatParameters),
1065+
signal: abortController.signal,
10601066
}
10611067
);
10621068

0 commit comments

Comments
 (0)