-
Notifications
You must be signed in to change notification settings - Fork 4
asynch export network #3285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
asynch export network #3285
Changes from 5 commits
92b4092
ae6d188
610caf8
986af0c
87c16dd
11be0bb
ee2d847
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,17 +7,17 @@ | |
|
||
import { useCallback, useEffect, useRef, useState } from 'react'; | ||
import { | ||
deletedOrRenamedNodes, | ||
networkModificationHandleSubtree, | ||
networkModificationTreeNodeAdded, | ||
networkModificationTreeNodeMoved, | ||
networkModificationTreeNodesRemoved, | ||
networkModificationTreeNodesUpdated, | ||
removeNotificationByNode, | ||
networkModificationHandleSubtree, | ||
setNodeSelectionForCopy, | ||
resetLogsFilter, | ||
reorderNetworkModificationTreeNodes, | ||
deletedOrRenamedNodes, | ||
resetLogsFilter, | ||
resetLogsPagination, | ||
setNodeSelectionForCopy, | ||
} from '../redux/actions'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import PropTypes from 'prop-types'; | ||
|
@@ -29,20 +29,21 @@ import { BUILD_STATUS } from './network/constants'; | |
import { | ||
copySubtree, | ||
copyTreeNode, | ||
createNodeSequence, | ||
createTreeNode, | ||
cutSubtree, | ||
cutTreeNode, | ||
stashSubtree, | ||
stashTreeNode, | ||
fetchNetworkModificationSubtree, | ||
fetchNetworkModificationTreeNode, | ||
fetchStashedNodes, | ||
createNodeSequence, | ||
stashSubtree, | ||
stashTreeNode, | ||
} from '../services/study/tree-subtree'; | ||
import { buildNode, getUniqueNodeName, unbuildNode } from '../services/study/index'; | ||
import { RestoreNodesDialog } from './dialogs/restore-node-dialog'; | ||
import { CopyType } from './network-modification.type'; | ||
import { NodeSequenceType, NotificationType, PENDING_MODIFICATION_NOTIFICATION_TYPES } from 'types/notification-types'; | ||
import useExportSubscription from '../hooks/use-export-subscription.js'; | ||
|
||
const noNodeSelectionForCopy = { | ||
sourceStudyUuid: null, | ||
|
@@ -123,6 +124,12 @@ export const NetworkModificationTreePane = ({ studyUuid, currentRootNetworkUuid | |
|
||
const studyUpdatedForce = useSelector((state) => state.studyUpdated); | ||
|
||
const { subscribeExport } = useExportSubscription({ | ||
studyUuid: studyUuid, | ||
nodeUuid: currentNode?.id, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. must pass nodeUuid in the params of subscribeExport because it is activeNode.id not currentNode.id BUG when a node is not selected but we do export on this node via context menu |
||
rootNetworkUuid: currentRootNetworkUuid, | ||
}); | ||
|
||
const updateNodes = useCallback( | ||
(updatedNodesIds) => { | ||
Promise.all( | ||
|
@@ -461,7 +468,8 @@ export const NetworkModificationTreePane = ({ studyUuid, currentRootNetworkUuid | |
|
||
const [openExportDialog, setOpenExportDialog] = useState(false); | ||
|
||
const handleClickExportNodeNetwork = (url) => { | ||
const handleClickExportNodeNetwork = (url, selectedFormat, fileName) => { | ||
subscribeExport(selectedFormat, fileName); | ||
window.open(url, DownloadIframe); | ||
setOpenExportDialog(false); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,7 @@ import { fetchStudy, recreateStudyNetwork, reindexAllRootNetwork } from 'service | |
import { HttpStatusCode } from 'utils/http-status-code'; | ||
import { NodeType } from './graph/tree-node.type'; | ||
import { | ||
isExportNetworkNotification, | ||
isIndexationStatusNotification, | ||
isLoadflowResultNotification, | ||
isStateEstimationResultNotification, | ||
|
@@ -46,6 +47,7 @@ import { | |
RootNetworkIndexationStatus, | ||
} from 'types/notification-types'; | ||
import { useDiagramGridLayout } from 'hooks/use-diagram-grid-layout'; | ||
import useExportNotificationHandler from '../hooks/use-export-notification-handler.js'; | ||
|
||
function useStudy(studyUuidRequest) { | ||
const dispatch = useDispatch(); | ||
|
@@ -142,6 +144,8 @@ export function StudyContainer({ view, onChangeTab }) { | |
|
||
const { snackError, snackWarning, snackInfo } = useSnackMessage(); | ||
|
||
const { handleExportNotification } = useExportNotificationHandler(); | ||
|
||
const displayErrorNotifications = useCallback( | ||
(eventData) => { | ||
const updateTypeHeader = eventData.headers.updateType; | ||
|
@@ -263,11 +267,15 @@ export function StudyContainer({ view, onChangeTab }) { | |
sendAlert(eventData); | ||
return; // here, we do not want to update the redux state | ||
} | ||
if (isExportNetworkNotification(eventData)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sylvain has a ticket in this sprint to remove studyUpdated to redux. |
||
handleExportNotification(eventData); | ||
return; | ||
} | ||
displayErrorNotifications(eventData); | ||
dispatch(studyUpdated(eventData)); | ||
}, | ||
// Note: dispatch doesn't change | ||
[dispatch, displayErrorNotifications, sendAlert] | ||
[dispatch, displayErrorNotifications, handleExportNotification, sendAlert] | ||
); | ||
|
||
useNotificationsListener(NotificationsUrlKeys.STUDY, { listenerCallbackMessage: handleStudyUpdate }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. useExportNotification(); |
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Three files use-export-xxx can be grouped in a file unique which presents a feature or move all into a directory, for example ./use-export-network |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/** | ||
* Copyright (c) 2025, RTE (http://www.rte-france.com) | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
*/ | ||
import { UUID } from 'crypto'; | ||
import { useSnackMessage } from '@gridsuite/commons-ui'; | ||
import { useCallback } from 'react'; | ||
import { fetchExportNetworkFile } from '../services/study'; | ||
import { downloadZipFile } from '../services/utils'; | ||
import { HttpStatusCode } from '../utils/http-status-code'; | ||
|
||
export function useExportDownload() { | ||
const { snackWarning, snackError } = useSnackMessage(); | ||
|
||
const downloadExportNetworkFile = useCallback( | ||
(exportUuid: UUID) => { | ||
fetchExportNetworkFile(exportUuid) | ||
.then(async (response) => { | ||
const contentDisposition = response.headers.get('Content-Disposition'); | ||
let filename = 'export.zip'; | ||
if (contentDisposition?.includes('filename=')) { | ||
const regex = /filename="?([^"]+)"?/; | ||
const match = regex.exec(contentDisposition); | ||
if (match?.[1]) { | ||
filename = match[1]; | ||
} | ||
} | ||
|
||
const blob = await response.blob(); | ||
downloadZipFile(blob, filename); | ||
}) | ||
.catch((responseError: any) => { | ||
const error = responseError as Error & { status: number }; | ||
if (error.status === HttpStatusCode.NOT_FOUND) { | ||
snackWarning({ | ||
headerId: 'export.header.fileNotFound', | ||
}); | ||
} else { | ||
snackError({ | ||
messageTxt: error.message, | ||
headerId: 'export.header.fileError', | ||
}); | ||
} | ||
}); | ||
}, | ||
[snackWarning, snackError] | ||
); | ||
return { downloadExportNetworkFile }; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,103 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Copyright (c) 2025, RTE (http://www.rte-france.com) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
* This Source Code Form is subject to the terms of the Mozilla Public | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { UUID } from 'crypto'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useIntl } from 'react-intl'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useSnackMessage } from '@gridsuite/commons-ui'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useCallback } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useExportDownload } from './use-export-download'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useSelector } from 'react-redux'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { AppState } from '../redux/reducer'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { ExportNetworkEventData } from '../types/notification-types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function buildExportIdentifier({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
studyUuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
nodeUuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
rootNetworkUuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
format, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
fileName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
studyUuid: UUID; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
nodeUuid: UUID; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
rootNetworkUuid: UUID; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
format: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
fileName: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return `${studyUuid}|${rootNetworkUuid}|${nodeUuid}|${fileName}|${format}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
function getExportState(): Set<string> | null { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const state = sessionStorage.getItem('export-subscriptions'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return state ? new Set<string>(JSON.parse(state)) : null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
function saveExportState(state: Set<string>): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
sessionStorage.setItem('export-subscriptions', JSON.stringify([...state])); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function isExportSubscribed(identifier: string): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const exportState = getExportState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return exportState?.has(identifier) ?? false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function unsetExportSubscription(identifier: string): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const exportState = getExportState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (exportState) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
exportState.delete(identifier); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
saveExportState(exportState); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default function useExportNotificationHandler() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const intl = useIntl(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { snackWarning, snackInfo } = useSnackMessage(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { downloadExportNetworkFile } = useExportDownload(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const userId = useSelector((state: AppState) => state.user?.profile.sub); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const handleExportNotification = useCallback( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
(eventData: ExportNetworkEventData) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
studyUuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
node, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
rootNetworkUuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
format, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
userId: useId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
exportUuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
fileName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
error, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} = eventData.headers; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+62
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const exportIdentifierNotif = buildExportIdentifier({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
studyUuid: studyUuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
nodeUuid: node, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
rootNetworkUuid: rootNetworkUuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
format, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
fileName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const isSubscribed = isExportSubscribed(exportIdentifierNotif); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (isSubscribed && useId === userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
unsetExportSubscription(exportIdentifierNotif); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
snackWarning({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
messageTxt: error, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
downloadExportNetworkFile(exportUuid); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
snackInfo({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
headerTxt: intl.formatMessage({ id: 'exportNetwork' }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
messageTxt: intl.formatMessage({ id: 'export.message.downloadStarted' }, { fileName }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
[userId, snackWarning, downloadExportNetworkFile, snackInfo, intl] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { handleExportNotification }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.