From 8205b29494faee21793005970d5458e5f45ae8ff Mon Sep 17 00:00:00 2001 From: Etienne LESOT Date: Wed, 11 Mar 2026 10:27:56 +0100 Subject: [PATCH 1/3] get all computing status at once when changing node Signed-off-by: Etienne LESOT --- .../use-all-computing-status.ts | 32 +++ .../use-computing-status-at-once.ts | 232 ++++++++++++++++++ .../computing-status/use-computing-status.ts | 6 - src/services/study/study.ts | 85 ++++++- 4 files changed, 347 insertions(+), 8 deletions(-) create mode 100644 src/components/computing-status/use-computing-status-at-once.ts diff --git a/src/components/computing-status/use-all-computing-status.ts b/src/components/computing-status/use-all-computing-status.ts index 008846af77..9706f72d1d 100644 --- a/src/components/computing-status/use-all-computing-status.ts +++ b/src/components/computing-status/use-all-computing-status.ts @@ -37,6 +37,8 @@ import { fetchDynamicSecurityAnalysisStatus } from '../../services/study/dynamic import { fetchDynamicMarginCalculationStatus } from '../../services/study/dynamic-margin-calculation'; import { NotificationType } from 'types/notification-types'; import { fetchPccMinStatus } from 'services/study/pcc-min'; +import { fetchAllComputationStatus } from '../../services/study/study'; +import { useAllComputingStatusAtOnce } from './use-computing-status-at-once'; // status invalidations const loadFlowStatusInvalidations = [NotificationType.LOADFLOW_STATUS, NotificationType.LOADFLOW_FAILED]; @@ -76,6 +78,34 @@ const stateEstimationStatusInvalidations = [ const pccMinStatusInvalidations = [NotificationType.PCC_MIN_STATUS, NotificationType.PCC_MIN_FAILED]; // status completions +export function getCompletions(computingType: ComputingType) { + switch (computingType) { + case ComputingType.PCC_MIN: + return pccMinStatusCompletions; + case ComputingType.LOAD_FLOW: + return loadFlowStatusCompletions; + case ComputingType.SECURITY_ANALYSIS: + return securityAnalysisStatusCompletions; + case ComputingType.SENSITIVITY_ANALYSIS: + return sensitivityAnalysisStatusCompletions; + case ComputingType.SHORT_CIRCUIT: + return shortCircuitAnalysisStatusCompletions; + case ComputingType.SHORT_CIRCUIT_ONE_BUS: + return oneBusShortCircuitAnalysisStatusCompletions; + case ComputingType.DYNAMIC_SIMULATION: + return dynamicSimulationStatusCompletions; + case ComputingType.DYNAMIC_SECURITY_ANALYSIS: + return dynamicSecurityAnalysisStatusCompletions; + case ComputingType.DYNAMIC_MARGIN_CALCULATION: + return dynamicMarginCalculationStatusCompletions; + case ComputingType.VOLTAGE_INITIALIZATION: + return voltageInitStatusCompletions; + case ComputingType.STATE_ESTIMATION: + return stateEstimationStatusCompletions; + default: + return []; + } +} const loadFlowStatusCompletions = [NotificationType.LOADFLOW_RESULT, NotificationType.LOADFLOW_FAILED]; const securityAnalysisStatusCompletions = [ NotificationType.SECURITY_ANALYSIS_RESULT, @@ -277,4 +307,6 @@ export const useAllComputingStatus = (studyUuid: UUID, currentNodeUuid: UUID, cu undefined, pccMinAvailability ); + + useAllComputingStatusAtOnce(studyUuid, currentNodeUuid, currentRootNetworkUuid, fetchAllComputationStatus); }; diff --git a/src/components/computing-status/use-computing-status-at-once.ts b/src/components/computing-status/use-computing-status-at-once.ts new file mode 100644 index 0000000000..d237afe916 --- /dev/null +++ b/src/components/computing-status/use-computing-status-at-once.ts @@ -0,0 +1,232 @@ +/** + * 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 { RunningStatus } from 'components/utils/running-status'; +import type { UUID } from 'node:crypto'; +import { RefObject, useCallback, useEffect, useRef } from 'react'; +import { useDispatch } from 'react-redux'; +import { ComputingType, NotificationsUrlKeys, useNotificationsListener } from '@gridsuite/commons-ui'; +import { OptionalServicesStatus } from '../utils/optional-services'; +import { setComputingStatus, setComputingStatusParameters, setLastCompletedComputation } from '../../redux/actions'; +import { AppDispatch } from '../../redux/store'; +import { isParameterizedComputingType, toComputingStatusParameters } from './computing-status-utils'; +import { parseEventData, StudyUpdatedEventData } from '../../types/notification-types'; +import { + AllComputationStatusInfos, + getComputingStatusParametersFetcher, + getRunningStatusByComputingType, +} from '../../services/study/study'; +import { getCompletions } from './use-all-computing-status'; + +interface UseComputingStatusProps { + ( + studyUuid: UUID, + nodeUuid: UUID, + currentRootNetworkUuid: UUID, + computingStatusFetcher: ( + studyUuid: UUID, + nodeUuid: UUID, + currentRootNetworkUuid: UUID + ) => Promise, + optionalServiceAvailabilityStatus?: OptionalServicesStatus + ): void; +} + +interface LastUpdateProps { + eventData: StudyUpdatedEventData | null; + allComputingStatusFetcher: (studyUuid: UUID, nodeUuid: UUID, currentRootNetworkUuid: UUID) => Promise; +} + +function isWorthUpdate( + nodeUuidRef: RefObject, + rootNetworkUuidRef: RefObject, + nodeUuid: UUID, + currentRootNetworkUuid: UUID +): boolean { + if (nodeUuidRef.current !== nodeUuid) { + return true; + } + return rootNetworkUuidRef.current !== currentRootNetworkUuid; + +} + +const shouldRequestBeCanceled = ( + canceledRequest: boolean, + previousNodeUuid: UUID, + currentNodeUuid: UUID, + previousRootNetworkUuid: UUID, + currentRootNetworkUuid: UUID +) => { + return ( + canceledRequest || previousNodeUuid !== currentNodeUuid || previousRootNetworkUuid !== currentRootNetworkUuid + ); +}; + +/** + * this hook loads state into redux, then keeps it updated according to notifications + * @param studyUuid current study uuid + * @param nodeUuid current node uuid + * @param allComputingStatusFetcher method fetching all state + * @param currentRootNetworkUuid + */ +export const useAllComputingStatusAtOnce: UseComputingStatusProps = ( + studyUuid, + nodeUuid, + currentRootNetworkUuid, + allComputingStatusFetcher +) => { + const nodeUuidRef = useRef(null); + const rootNetworkUuidRef = useRef(null); + const lastUpdateRef = useRef(null); + const dispatch = useDispatch(); + + //the callback crosschecks the computation status and the content of the last update reference + //in order to determine which computation just ended + const isComputationCompleted = useCallback((status: RunningStatus, completions: string[]) => { + const eventData = lastUpdateRef.current?.eventData; + return ( + [RunningStatus.FAILED, RunningStatus.SUCCEED].includes(status) && + completions.includes(eventData?.headers?.updateType ?? '') + ); + }, []); + + const handleComputingStatusParameters = useCallback( + async ( + computationStatus: RunningStatus, + canceledRequest: boolean, + computingType: ComputingType, + computingStatusParametersFetcher: + | ((studyUuid: UUID, nodeUuid: UUID, currentRootNetworkUuid: UUID) => Promise) + | undefined + ) => { + if ( + computingStatusParametersFetcher && + computationStatus !== RunningStatus.IDLE && + isParameterizedComputingType(computingType) + ) { + nodeUuidRef.current = nodeUuid; + rootNetworkUuidRef.current = currentRootNetworkUuid; + const computingStatusParametersResult = await computingStatusParametersFetcher( + studyUuid, + nodeUuid, + currentRootNetworkUuid + ); + if ( + shouldRequestBeCanceled( + canceledRequest, + nodeUuidRef.current, + nodeUuid, + rootNetworkUuidRef.current, + currentRootNetworkUuid + ) + ) { + return; + } + dispatch( + setComputingStatusParameters( + computingType, + toComputingStatusParameters(computingStatusParametersResult, computingType) + ) + ); + } + }, + [currentRootNetworkUuid, dispatch, nodeUuid, studyUuid] + ); + + const update = useCallback(async () => { + // this is used to prevent race conditions from happening + // if another request is sent, the previous one won't do anything + let canceledRequest = false; + + //upon changing node we reset the last completed computation to prevent results misredirection + dispatch(setLastCompletedComputation()); + + nodeUuidRef.current = nodeUuid; + rootNetworkUuidRef.current = currentRootNetworkUuid; + try { + // fetch computing status + const computingStatusResult: AllComputationStatusInfos | null = await allComputingStatusFetcher( + studyUuid, + nodeUuid, + currentRootNetworkUuid + ); + if ( + shouldRequestBeCanceled( + canceledRequest, + nodeUuidRef.current, + nodeUuid, + rootNetworkUuidRef.current, + currentRootNetworkUuid + ) + ) { + return; + } + // if request has not been canceled for any reason, fetch if necessary computingStatusParameters + const allStatusInfos = computingStatusResult; + if (allStatusInfos != null) { + // for each status + for (const computingType of Object.values(ComputingType)) { + const status = getRunningStatusByComputingType(allStatusInfos, computingType); + dispatch(setComputingStatus(computingType, status)); + if (isComputationCompleted(status, getCompletions(computingType))) { + dispatch(setLastCompletedComputation(computingType)); + } + await handleComputingStatusParameters( + status, + canceledRequest, + computingType, + getComputingStatusParametersFetcher(computingType) + ); + } + } + } catch (e: any) { + if (!canceledRequest) { + // for each status + for (const computingType of Object.values(ComputingType)) { + dispatch(setComputingStatus(computingType, RunningStatus.FAILED)); + console.error(e?.message); + } + } + } + + return () => { + canceledRequest = true; + }; + }, [ + dispatch, + nodeUuid, + currentRootNetworkUuid, + allComputingStatusFetcher, + studyUuid, + handleComputingStatusParameters, + isComputationCompleted, + ]); + + const evaluateUpdate = useCallback( + (event?: MessageEvent) => { + if (!studyUuid || !nodeUuid || !currentRootNetworkUuid) { + return; + } + const eventData = parseEventData(event ?? null); + const isUpdateForUs = isWorthUpdate(nodeUuidRef, rootNetworkUuidRef, nodeUuid, currentRootNetworkUuid); + lastUpdateRef.current = { eventData, allComputingStatusFetcher }; + if (isUpdateForUs) { + update(); + } + }, + [allComputingStatusFetcher, currentRootNetworkUuid, nodeUuid, studyUuid, update] + ); + + useNotificationsListener(NotificationsUrlKeys.STUDY, { + listenerCallbackMessage: evaluateUpdate, + }); + + /* initial fetch and update */ + useEffect(() => { + evaluateUpdate(); + }, [evaluateUpdate]); +}; diff --git a/src/components/computing-status/use-computing-status.ts b/src/components/computing-status/use-computing-status.ts index 7438a30239..3c9de10ecc 100644 --- a/src/components/computing-status/use-computing-status.ts +++ b/src/components/computing-status/use-computing-status.ts @@ -59,12 +59,6 @@ function isWorthUpdate( const node = headers?.node; const nodes = headers?.nodes; const rootNetworkUuidFromNotification = headers?.rootNetworkUuid; - if (nodeUuidRef.current !== nodeUuid) { - return true; - } - if (rootNetworkUuidRef.current !== currentRootNetworkUuid) { - return true; - } if (rootNetworkUuidFromNotification && rootNetworkUuidFromNotification !== currentRootNetworkUuid) { return false; } diff --git a/src/services/study/study.ts b/src/services/study/study.ts index 1f39b06c75..c7ed48b7a2 100644 --- a/src/services/study/study.ts +++ b/src/services/study/study.ts @@ -6,8 +6,21 @@ */ import type { UUID } from 'node:crypto'; -import { PREFIX_STUDY_QUERIES, getStudyUrl } from '.'; -import { backendFetch, backendFetchJson } from '@gridsuite/commons-ui'; +import { getStudyUrl, getStudyUrlWithNodeUuidAndRootNetworkUuid, PREFIX_STUDY_QUERIES } from '.'; +import { backendFetch, backendFetchJson, ComputingType } from '@gridsuite/commons-ui'; +import RunningStatus, { + getDynamicMarginCalculationRunningStatus, + getDynamicSecurityAnalysisRunningStatus, + getDynamicSimulationRunningStatus, + getLoadFlowRunningStatus, + getPccMinRunningStatus, + getSecurityAnalysisRunningStatus, + getSensitivityAnalysisRunningStatus, + getShortCircuitAnalysisRunningStatus, + getStateEstimationRunningStatus, + getVoltageInitRunningStatus, +} from '../../components/utils/running-status'; +import { fetchLoadFlowComputationInfos } from './loadflow'; interface BasicStudyInfos { uniqueId: string; @@ -97,3 +110,71 @@ export function unbuildAllStudyNodes(studyUuid: UUID) { console.debug(url); return backendFetch(url, { method: 'post' }); } + +export interface AllComputationStatusInfos { + pccMinStatus: string; + dynamicMarginCalculationStatus: string; + dynamicSecurityAnalysisStatus: string; + dynamicSimulationStatus: string; + stateEstimationStatus: string; + sensitivityAnalysisStatus: string; + loadFlowStatus: string; + securityAnalysisStatus: string; + oneBusShortCircuitStatus: string; + allBusShortCircuitStatus: string; + voltageInitStatus: string; +} + +export function getComputingStatusParametersFetcher( + computingType: ComputingType +): ((studyUuid: UUID, nodeUuid: UUID, currentRootNetworkUuid: UUID) => Promise) | undefined { + if (computingType === ComputingType.LOAD_FLOW) { + return fetchLoadFlowComputationInfos; + } else { + return undefined; + } +} + +export function getRunningStatusByComputingType(allStatuses: AllComputationStatusInfos, computingType: ComputingType): RunningStatus { + switch (computingType) { + case ComputingType.PCC_MIN: + return getPccMinRunningStatus(allStatuses.pccMinStatus); + case ComputingType.LOAD_FLOW: + return getLoadFlowRunningStatus(allStatuses.loadFlowStatus); + case ComputingType.SECURITY_ANALYSIS: + return getSecurityAnalysisRunningStatus(allStatuses.securityAnalysisStatus); + case ComputingType.SENSITIVITY_ANALYSIS: + return getSensitivityAnalysisRunningStatus(allStatuses.sensitivityAnalysisStatus); + case ComputingType.SHORT_CIRCUIT: + return getShortCircuitAnalysisRunningStatus(allStatuses.allBusShortCircuitStatus); + case ComputingType.SHORT_CIRCUIT_ONE_BUS: + return getShortCircuitAnalysisRunningStatus(allStatuses.oneBusShortCircuitStatus); + case ComputingType.DYNAMIC_SIMULATION: + return getDynamicSimulationRunningStatus(allStatuses.dynamicSimulationStatus); + case ComputingType.DYNAMIC_SECURITY_ANALYSIS: + return getDynamicSecurityAnalysisRunningStatus(allStatuses.dynamicSecurityAnalysisStatus); + case ComputingType.DYNAMIC_MARGIN_CALCULATION: + return getDynamicMarginCalculationRunningStatus(allStatuses.dynamicMarginCalculationStatus); + case ComputingType.VOLTAGE_INITIALIZATION: + return getVoltageInitRunningStatus(allStatuses.voltageInitStatus); + case ComputingType.STATE_ESTIMATION: + return getStateEstimationRunningStatus(allStatuses.stateEstimationStatus); + default: + return RunningStatus.IDLE; + } +} + +export function fetchAllComputationStatus( + studyUuid: UUID, + currentNodeUuid: UUID, + currentRootNetworkUuid: UUID +): Promise { + console.info( + `Fetching all computation status on study '${studyUuid}', on root network '${currentRootNetworkUuid}' and node '${currentNodeUuid}' ...` + ); + const url = + getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, currentNodeUuid, currentRootNetworkUuid) + + '/computations/status'; + console.debug(url); + return backendFetchJson(url); +} From 11cf96c8fdc1db42a5f538f532b5b3153853a367 Mon Sep 17 00:00:00 2001 From: Etienne LESOT Date: Wed, 11 Mar 2026 10:49:50 +0100 Subject: [PATCH 2/3] fix Signed-off-by: Etienne LESOT --- .../computing-status/use-all-computing-status.ts | 8 +++++++- .../computing-status/use-computing-status-at-once.ts | 11 ++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/computing-status/use-all-computing-status.ts b/src/components/computing-status/use-all-computing-status.ts index 9706f72d1d..cfe072e531 100644 --- a/src/components/computing-status/use-all-computing-status.ts +++ b/src/components/computing-status/use-all-computing-status.ts @@ -308,5 +308,11 @@ export const useAllComputingStatus = (studyUuid: UUID, currentNodeUuid: UUID, cu pccMinAvailability ); - useAllComputingStatusAtOnce(studyUuid, currentNodeUuid, currentRootNetworkUuid, fetchAllComputationStatus); + useAllComputingStatusAtOnce( + studyUuid, + currentNodeUuid, + currentRootNetworkUuid, + fetchAllComputationStatus, + getCompletions + ); }; diff --git a/src/components/computing-status/use-computing-status-at-once.ts b/src/components/computing-status/use-computing-status-at-once.ts index d237afe916..50d81994da 100644 --- a/src/components/computing-status/use-computing-status-at-once.ts +++ b/src/components/computing-status/use-computing-status-at-once.ts @@ -10,17 +10,15 @@ import type { UUID } from 'node:crypto'; import { RefObject, useCallback, useEffect, useRef } from 'react'; import { useDispatch } from 'react-redux'; import { ComputingType, NotificationsUrlKeys, useNotificationsListener } from '@gridsuite/commons-ui'; -import { OptionalServicesStatus } from '../utils/optional-services'; import { setComputingStatus, setComputingStatusParameters, setLastCompletedComputation } from '../../redux/actions'; import { AppDispatch } from '../../redux/store'; import { isParameterizedComputingType, toComputingStatusParameters } from './computing-status-utils'; -import { parseEventData, StudyUpdatedEventData } from '../../types/notification-types'; +import { NotificationType, parseEventData, StudyUpdatedEventData } from '../../types/notification-types'; import { AllComputationStatusInfos, getComputingStatusParametersFetcher, getRunningStatusByComputingType, } from '../../services/study/study'; -import { getCompletions } from './use-all-computing-status'; interface UseComputingStatusProps { ( @@ -32,7 +30,7 @@ interface UseComputingStatusProps { nodeUuid: UUID, currentRootNetworkUuid: UUID ) => Promise, - optionalServiceAvailabilityStatus?: OptionalServicesStatus + getCompletions: (computingType: ComputingType) => NotificationType[] ): void; } @@ -72,12 +70,14 @@ const shouldRequestBeCanceled = ( * @param nodeUuid current node uuid * @param allComputingStatusFetcher method fetching all state * @param currentRootNetworkUuid + * @param getCompletions */ export const useAllComputingStatusAtOnce: UseComputingStatusProps = ( studyUuid, nodeUuid, currentRootNetworkUuid, - allComputingStatusFetcher + allComputingStatusFetcher, + getCompletions, ) => { const nodeUuidRef = useRef(null); const rootNetworkUuidRef = useRef(null); @@ -197,6 +197,7 @@ export const useAllComputingStatusAtOnce: UseComputingStatusProps = ( canceledRequest = true; }; }, [ + getCompletions, dispatch, nodeUuid, currentRootNetworkUuid, From 60f5666fce261bfd0f0987f381a888c7c06f07c5 Mon Sep 17 00:00:00 2001 From: Etienne LESOT Date: Wed, 11 Mar 2026 11:06:32 +0100 Subject: [PATCH 3/3] fix Signed-off-by: Etienne LESOT --- .../computing-status/use-computing-status-at-once.ts | 9 ++++++--- src/services/study/study.ts | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/computing-status/use-computing-status-at-once.ts b/src/components/computing-status/use-computing-status-at-once.ts index 50d81994da..9bd1d99867 100644 --- a/src/components/computing-status/use-computing-status-at-once.ts +++ b/src/components/computing-status/use-computing-status-at-once.ts @@ -36,7 +36,11 @@ interface UseComputingStatusProps { interface LastUpdateProps { eventData: StudyUpdatedEventData | null; - allComputingStatusFetcher: (studyUuid: UUID, nodeUuid: UUID, currentRootNetworkUuid: UUID) => Promise; + allComputingStatusFetcher: ( + studyUuid: UUID, + nodeUuid: UUID, + currentRootNetworkUuid: UUID + ) => Promise; } function isWorthUpdate( @@ -49,7 +53,6 @@ function isWorthUpdate( return true; } return rootNetworkUuidRef.current !== currentRootNetworkUuid; - } const shouldRequestBeCanceled = ( @@ -77,7 +80,7 @@ export const useAllComputingStatusAtOnce: UseComputingStatusProps = ( nodeUuid, currentRootNetworkUuid, allComputingStatusFetcher, - getCompletions, + getCompletions ) => { const nodeUuidRef = useRef(null); const rootNetworkUuidRef = useRef(null); diff --git a/src/services/study/study.ts b/src/services/study/study.ts index c7ed48b7a2..93b1ed9daf 100644 --- a/src/services/study/study.ts +++ b/src/services/study/study.ts @@ -135,7 +135,10 @@ export function getComputingStatusParametersFetcher( } } -export function getRunningStatusByComputingType(allStatuses: AllComputationStatusInfos, computingType: ComputingType): RunningStatus { +export function getRunningStatusByComputingType( + allStatuses: AllComputationStatusInfos, + computingType: ComputingType +): RunningStatus { switch (computingType) { case ComputingType.PCC_MIN: return getPccMinRunningStatus(allStatuses.pccMinStatus);