From 994728b8fd62cbf95e983d686b1321f63519bf1c Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Tue, 19 May 2026 17:24:59 +0200 Subject: [PATCH 1/2] Fix stuck one-bus SC loader on workspace switch Signed-off-by: Ayoub LABIDI --- ...e-one-bus-shortcircuit-analysis-loader.tsx | 136 ------------------ .../single-line-diagram-content.tsx | 66 +++++---- .../diagrams/sld/substation-panel-content.tsx | 1 - .../sld/voltage-level-panel-content.tsx | 1 - src/redux/actions.ts | 47 +++--- src/redux/reducer.ts | 27 +--- src/redux/reducer.type.ts | 9 +- 7 files changed, 66 insertions(+), 221 deletions(-) delete mode 100644 src/components/grid-layout/cards/diagrams/singleLineDiagram/hooks/use-one-bus-shortcircuit-analysis-loader.tsx diff --git a/src/components/grid-layout/cards/diagrams/singleLineDiagram/hooks/use-one-bus-shortcircuit-analysis-loader.tsx b/src/components/grid-layout/cards/diagrams/singleLineDiagram/hooks/use-one-bus-shortcircuit-analysis-loader.tsx deleted file mode 100644 index 6ec397d7af..0000000000 --- a/src/components/grid-layout/cards/diagrams/singleLineDiagram/hooks/use-one-bus-shortcircuit-analysis-loader.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (c) 2023, 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 { ReactElement, useCallback, useMemo } from 'react'; -import { useIntl } from 'react-intl'; -import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from 'redux/reducer.type'; -import { Chip, darken, lighten } from '@mui/material'; -import { type MuiStyles, NotificationsUrlKeys, useNotificationsListener } from '@gridsuite/commons-ui'; -import { resetOneBusShortcircuitAnalysisDiagram, setOneBusShortcircuitAnalysisDiagram } from 'redux/actions'; -import { AppDispatch } from 'redux/store'; -import { - CommonStudyEventData, - isOneBusShortCircuitFailedNotification, - isOneBusShortCircuitResultNotification, - parseEventData, -} from 'types/notification-types'; - -const styles = { - loaderMessage: (theme) => ({ - display: 'flex', - position: 'relative', - width: 'fit-content', - margin: '5px auto', - backgroundColor: - theme.palette.mode === 'light' - ? darken(theme.palette.background.paper, 0.1) - : lighten(theme.palette.background.paper, 0.2), - }), -} as const satisfies MuiStyles; - -//Here's the rundown of the signature : the ReactElement is related to the loader JSX component, the boolean indicated wether the loader should be active, -//the first function submits the sld data on hand to the redux store and the second function reset the redux store state -type oneBusShortcircuitAnalysisLoader = [ReactElement, boolean, () => void, () => void]; - -/** - * A hook that handles the logic behind the diagram one bus shortcircuit analysis loader - * - * @param {string} diagramId - Identifier for the diagram which launched the computation - * - * @returns {oneBusShortcircuitAnalysisLoader} array which contains the controls necessary for the one bus - * shortcircuit analysis loader. It also comes with a boolean to check if the loader needs to be displayed - * and the message to display for the UI - */ -export function useOneBusShortcircuitAnalysisLoader(diagramId: string): oneBusShortcircuitAnalysisLoader { - const studyUuid = useSelector((state: AppState) => state.studyUuid); - const currentNode = useSelector((state: AppState) => state.currentTreeNode); - const rootNetworkUuid = useSelector((state: AppState) => state.currentRootNetworkUuid); - const oneBusShortCircuitAnalysisDiagram = useSelector((state: AppState) => state.oneBusShortCircuitAnalysisDiagram); - - const dispatch = useDispatch(); - const intl = useIntl(); - - const displayOneBusShortcircuitAnalysisLoader = useCallback(() => { - if (!studyUuid || !currentNode?.id || !rootNetworkUuid) { - return; - } - dispatch(setOneBusShortcircuitAnalysisDiagram(diagramId, studyUuid, rootNetworkUuid, currentNode?.id)); - }, [currentNode?.id, diagramId, dispatch, rootNetworkUuid, studyUuid]); - - const resetOneBusShortcircuitAnalysisLoader = useCallback(() => { - dispatch(resetOneBusShortcircuitAnalysisDiagram()); - }, [dispatch]); - - const isDiagramRunningOneBusShortcircuitAnalysis = useMemo(() => { - if (!studyUuid || !currentNode?.id || !rootNetworkUuid) { - return false; - } - - return ( - diagramId === oneBusShortCircuitAnalysisDiagram?.diagramId && - studyUuid === oneBusShortCircuitAnalysisDiagram?.studyUuid && - rootNetworkUuid === oneBusShortCircuitAnalysisDiagram?.rootNetworkUuid && - currentNode?.id === oneBusShortCircuitAnalysisDiagram?.nodeId - ); - }, [ - currentNode?.id, - diagramId, - oneBusShortCircuitAnalysisDiagram?.diagramId, - oneBusShortCircuitAnalysisDiagram?.studyUuid, - oneBusShortCircuitAnalysisDiagram?.rootNetworkUuid, - oneBusShortCircuitAnalysisDiagram?.nodeId, - rootNetworkUuid, - studyUuid, - ]); - - const oneBusShortcircuitAnalysisLoaderMessage = useMemo(() => { - return ( - <> - {isDiagramRunningOneBusShortcircuitAnalysis && ( - - )} - - ); - }, [intl, isDiagramRunningOneBusShortcircuitAnalysis]); - - const handleEvent = useCallback( - (event: MessageEvent) => { - if (!studyUuid || !currentNode?.id || !rootNetworkUuid) { - return; - } - const eventData = parseEventData(event); - if ( - eventData && - (isOneBusShortCircuitResultNotification(eventData) || isOneBusShortCircuitFailedNotification(eventData)) - ) { - if (eventData.headers.rootNetworkUuid !== rootNetworkUuid) { - return; - } - resetOneBusShortcircuitAnalysisLoader(); - } - }, - [resetOneBusShortcircuitAnalysisLoader, rootNetworkUuid, studyUuid, currentNode?.id] - ); - - useNotificationsListener(NotificationsUrlKeys.STUDY, { - listenerCallbackMessage: handleEvent, - }); - - return [ - oneBusShortcircuitAnalysisLoaderMessage, - isDiagramRunningOneBusShortcircuitAnalysis, - displayOneBusShortcircuitAnalysisLoader, - resetOneBusShortcircuitAnalysisLoader, - ]; -} diff --git a/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx b/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx index aa400df92b..2f1712b38f 100644 --- a/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx +++ b/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx @@ -6,6 +6,7 @@ */ import { memo, useCallback, useLayoutEffect, useRef, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { RunningStatus } from '../../../../utils/running-status'; import { @@ -28,7 +29,7 @@ import { } from '@powsybl/network-viewer'; import { isNodeReadOnly } from '../../../../graph/util/model-functions'; import { useIsAnyNodeBuilding } from '../../../../utils/is-any-node-building-hook'; -import { useTheme } from '@mui/material/styles'; +import { darken, lighten, Theme, useTheme } from '@mui/material/styles'; import { ComputingType, EquipmentType, @@ -38,12 +39,18 @@ import { useSnackMessage, } from '@gridsuite/commons-ui'; import Box from '@mui/material/Box'; +import Chip from '@mui/material/Chip'; import LinearProgress from '@mui/material/LinearProgress'; + import { updateSwitchState } from '../../../../../services/study/network-modifications'; import { BusMenu } from 'components/menus/bus-menu'; import { startShortCircuitAnalysis } from '../../../../../services/study/short-circuit-analysis'; -import { useOneBusShortcircuitAnalysisLoader } from './hooks/use-one-bus-shortcircuit-analysis-loader'; -import { setComputationStarting, setComputingStatus, updateColumnFiltersAction } from '../../../../../redux/actions'; +import { + setComputationStarting, + setComputingStatus, + setOneBusShortcircuitAnalysisContext, + updateColumnFiltersAction, +} from '../../../../../redux/actions'; import { TableType } from '../../../../../types/custom-aggrid-types'; import { AppState } from 'redux/reducer.type'; import type { UUID } from 'node:crypto'; @@ -57,10 +64,20 @@ import { GenericEquipmentInfos } from 'components/tooltips/equipment-popover-typ import { GenericPopoverContent } from 'components/tooltips/generic-popover-content'; import useDebugSubscription from '../../../../../hooks/computation-debug/use-debug-subscription'; +const oneBusLoaderStyle = (theme: Theme) => ({ + display: 'flex', + position: 'relative', + width: 'fit-content', + margin: '5px auto', + backgroundColor: + theme.palette.mode === 'light' + ? darken(theme.palette.background.paper, 0.1) + : lighten(theme.palette.background.paper, 0.2), +}); + interface SingleLineDiagramContentProps { readonly showInSpreadsheet: (menu: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void; readonly studyUuid: UUID; - readonly panelId: UUID; readonly svg?: string; readonly svgMetadata?: SLDMetadata; readonly loadingState: boolean; @@ -88,7 +105,6 @@ const defaultBusMenuState: BusMenuState = { const SingleLineDiagramContent = memo(function SingleLineDiagramContent(props: SingleLineDiagramContentProps) { const { studyUuid, - panelId, visible, diagramParams, onNextVoltageLevelDiagram, @@ -119,13 +135,15 @@ const SingleLineDiagramContent = memo(function SingleLineDiagramContent(props: S const loadFlowStatus = useSelector((state: AppState) => state.computingStatus[ComputingType.LOAD_FLOW]); const shortCircuitStatus = useSelector((state: AppState) => state.computingStatus[ComputingType.SHORT_CIRCUIT]); - const [ - oneBusShortcircuitAnalysisLoaderMessage, - isDiagramRunningOneBusShortcircuitAnalysis, - displayOneBusShortcircuitAnalysisLoader, - resetOneBusShortcircuitAnalysisLoader, - ] = useOneBusShortcircuitAnalysisLoader(panelId); - + const equipmentId = + diagramParams.type === DiagramType.VOLTAGE_LEVEL ? diagramParams.voltageLevelId : diagramParams.substationId; + const isOneBusScRunning = useSelector( + (state: AppState) => + state.computingStatus[ComputingType.SHORT_CIRCUIT_ONE_BUS] === RunningStatus.RUNNING && + state.oneBusShortCircuitAnalysisContext?.equipmentId === equipmentId && + state.oneBusShortCircuitAnalysisContext?.nodeId === state.currentTreeNode?.id && + state.oneBusShortCircuitAnalysisContext?.rootNetworkUuid === state.currentRootNetworkUuid + ); /** * DIAGRAM INTERACTIVITY */ @@ -227,7 +245,7 @@ const SingleLineDiagramContent = memo(function SingleLineDiagramContent(props: S const handleRunShortcircuitAnalysis = useCallback( (busId: string, debug: boolean) => { dispatch(setComputingStatus(ComputingType.SHORT_CIRCUIT_ONE_BUS, RunningStatus.RUNNING)); - displayOneBusShortcircuitAnalysisLoader(); + dispatch(setOneBusShortcircuitAnalysisContext(equipmentId, currentRootNetworkUuid!, currentNode!.id)); dispatch(setComputationStarting(true)); startShortCircuitAnalysis(studyUuid, currentNode?.id, currentRootNetworkUuid, busId, debug) .then(() => { @@ -236,7 +254,6 @@ const SingleLineDiagramContent = memo(function SingleLineDiagramContent(props: S .catch((error) => { snackWithFallback(snackError, error, { headerId: 'startShortCircuitError' }); dispatch(setComputingStatus(ComputingType.SHORT_CIRCUIT_ONE_BUS, RunningStatus.FAILED)); - resetOneBusShortcircuitAnalysisLoader(); }) .finally(() => { dispatch(setComputationStarting(false)); @@ -244,16 +261,7 @@ const SingleLineDiagramContent = memo(function SingleLineDiagramContent(props: S dispatch(updateColumnFiltersAction(TableType.Logs, ComputingType.SHORT_CIRCUIT_ONE_BUS, [])); }); }, - [ - dispatch, - displayOneBusShortcircuitAnalysisLoader, - studyUuid, - currentNode?.id, - currentRootNetworkUuid, - snackError, - resetOneBusShortcircuitAnalysisLoader, - subscribeDebug, - ] + [dispatch, equipmentId, studyUuid, currentNode, currentRootNetworkUuid, snackError, subscribeDebug] ); const displayBusMenu = () => { @@ -450,10 +458,14 @@ const SingleLineDiagramContent = memo(function SingleLineDiagramContent(props: S return ( <> - {(loadingState || modificationInProgress || isDiagramRunningOneBusShortcircuitAnalysis) && ( - + {(loadingState || modificationInProgress || isOneBusScRunning) && } + {isOneBusScRunning && ( + } + variant="outlined" + sx={oneBusLoaderStyle} + /> )} - {oneBusShortcircuitAnalysisLoaderMessage} & - OneBusShortCircuitAnalysisDiagram & { - [key: string]: any; - }; -export function setOneBusShortcircuitAnalysisDiagram( - diagramId: OneBusShortCircuitAnalysisDiagram['diagramId'], - studyUuid: OneBusShortCircuitAnalysisDiagram['studyUuid'], - rootNetworkUuid: OneBusShortCircuitAnalysisDiagram['rootNetworkUuid'], - nodeId: OneBusShortCircuitAnalysisDiagram['nodeId'] -): SetOneBusShortcircuitAnalysisDiagramAction { +export const SET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_CONTEXT = 'SET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_CONTEXT'; +export type SetOneBusShortcircuitAnalysisContextAction = Readonly< + Action +> & + OneBusShortCircuitAnalysisContext; +export function setOneBusShortcircuitAnalysisContext( + equipmentId: string, + rootNetworkUuid: UUID, + nodeId: UUID +): SetOneBusShortcircuitAnalysisContextAction { return { - type: SET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM, - diagramId: diagramId, - studyUuid: studyUuid, - rootNetworkUuid: rootNetworkUuid, - nodeId: nodeId, - }; -} - -export const RESET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM = 'RESET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM'; -export type ResetOneBusShortcircuitAnalysisDiagramAction = Action< - typeof RESET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM -> & { - [key: string]: any; -}; -export function resetOneBusShortcircuitAnalysisDiagram(): ResetOneBusShortcircuitAnalysisDiagramAction { - return { - type: RESET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM, + type: SET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_CONTEXT, + equipmentId, + nodeId, + rootNetworkUuid, }; } diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts index 019e63209e..f72f058451 100644 --- a/src/redux/reducer.ts +++ b/src/redux/reducer.ts @@ -126,7 +126,6 @@ import { RESET_LOGS_FILTER, RESET_LOGS_PAGINATION, RESET_MAP_EQUIPMENTS, - RESET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM, RESET_PCCMIN_ANALYSIS_PAGINATION, RESET_SECURITY_ANALYSIS_PAGINATION, RESET_SENSITIVITY_ANALYSIS_PAGINATION, @@ -137,7 +136,6 @@ import { type ResetLogsFilterAction, ResetLogsPaginationAction, type ResetMapEquipmentsAction, - type ResetOneBusShortcircuitAnalysisDiagramAction, ResetPccminAnalysisPaginationAction, ResetSecurityAnalysisPaginationAction, ResetSensitivityAnalysisPaginationAction, @@ -166,7 +164,7 @@ import { SET_LAST_COMPLETED_COMPUTATION, SET_MODIFICATIONS_IN_PROGRESS, SET_MONO_ROOT_STUDY, - SET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM, + SET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_CONTEXT, SET_OPTIONAL_SERVICES, SET_PARAMS_LOADED, SET_RELOAD_MAP_NEEDED, @@ -185,7 +183,7 @@ import { type SetLastCompletedComputationAction, type SetModificationsInProgressAction, type SetMonoRootStudyAction, - type SetOneBusShortcircuitAnalysisDiagramAction, + type SetOneBusShortcircuitAnalysisContextAction, type SetOptionalServicesAction, type SetParamsLoadedAction, type SetReloadMapNeededAction, @@ -535,7 +533,7 @@ const initialState: AppState = { name: key, status: OptionalServicesStatus.Pending, })), - oneBusShortCircuitAnalysisDiagram: null, + oneBusShortCircuitAnalysisContext: null, rootNetworkIndexationStatus: RootNetworkIndexationStatus.NOT_INDEXED, // params @@ -1374,21 +1372,10 @@ export const reducer = createReducer(initialState, (builder) => { }); builder.addCase( - SET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM, - (state, action: SetOneBusShortcircuitAnalysisDiagramAction) => { - state.oneBusShortCircuitAnalysisDiagram = { - diagramId: action.diagramId, - studyUuid: action.studyUuid, - rootNetworkUuid: action.rootNetworkUuid, - nodeId: action.nodeId, - }; - } - ); - - builder.addCase( - RESET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_DIAGRAM, - (state, _action: ResetOneBusShortcircuitAnalysisDiagramAction) => { - state.oneBusShortCircuitAnalysisDiagram = null; + SET_ONE_BUS_SHORTCIRCUIT_ANALYSIS_CONTEXT, + (state, action: SetOneBusShortcircuitAnalysisContextAction) => { + const { type: _, ...context } = action; + state.oneBusShortCircuitAnalysisContext = context; } ); diff --git a/src/redux/reducer.type.ts b/src/redux/reducer.type.ts index 87440353a1..f14810cc73 100644 --- a/src/redux/reducer.type.ts +++ b/src/redux/reducer.type.ts @@ -163,12 +163,11 @@ export type NadTextMovement = { connectionShiftY: number; }; -export interface OneBusShortCircuitAnalysisDiagram { - diagramId: string; - studyUuid: UUID; +export type OneBusShortCircuitAnalysisContext = { + equipmentId: string; rootNetworkUuid: UUID; nodeId: UUID; -} +}; // ——— Clipboard ——— @@ -218,7 +217,7 @@ export interface AppState extends CommonStoreState, AppConfigState { lastCompletedComputation: ComputingType | null; computationStarting: boolean; optionalServices: IOptionalService[]; - oneBusShortCircuitAnalysisDiagram: OneBusShortCircuitAnalysisDiagram | null; + oneBusShortCircuitAnalysisContext: OneBusShortCircuitAnalysisContext | null; notificationIdList: UUID[]; globalFilterOptions: GlobalFilter[]; mapEquipments: GSMapEquipments | undefined; From c54dd927a6c600811f60aac63a7305d9869e78a4 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Tue, 19 May 2026 17:39:11 +0200 Subject: [PATCH 2/2] Fix sonar issue Signed-off-by: Ayoub LABIDI --- .../singleLineDiagram/single-line-diagram-content.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx b/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx index 2f1712b38f..c17d945bdc 100644 --- a/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx +++ b/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx @@ -244,10 +244,13 @@ const SingleLineDiagramContent = memo(function SingleLineDiagramContent(props: S const handleRunShortcircuitAnalysis = useCallback( (busId: string, debug: boolean) => { + if (!currentNode || !currentRootNetworkUuid) { + return; + } dispatch(setComputingStatus(ComputingType.SHORT_CIRCUIT_ONE_BUS, RunningStatus.RUNNING)); - dispatch(setOneBusShortcircuitAnalysisContext(equipmentId, currentRootNetworkUuid!, currentNode!.id)); + dispatch(setOneBusShortcircuitAnalysisContext(equipmentId, currentRootNetworkUuid, currentNode.id)); dispatch(setComputationStarting(true)); - startShortCircuitAnalysis(studyUuid, currentNode?.id, currentRootNetworkUuid, busId, debug) + startShortCircuitAnalysis(studyUuid, currentNode.id, currentRootNetworkUuid, busId, debug) .then(() => { debug && subscribeDebug(ComputingType.SHORT_CIRCUIT_ONE_BUS); })