Skip to content

Commit 65852d0

Browse files
fix: spreadsheet updates on notifications (#3284)
* fix: spreadsheet updates on notifications Signed-off-by: Joris Mancini <[email protected]> * refactor: some renaming and fixes Signed-off-by: Joris Mancini <[email protected]> * lint: remove unused imports Signed-off-by: Joris Mancini <[email protected]> * fix: delete data in branch type if it's line or twt Signed-off-by: Joris Mancini <[email protected]> * chore: fix sonar issues Signed-off-by: Joris Mancini <[email protected]> * fix: delete equipment reducer for branch Co-authored-by: Achour berrahma <[email protected]> Signed-off-by: Joris Mancini <[email protected]> --------- Signed-off-by: Joris Mancini <[email protected]> Co-authored-by: Achour berrahma <[email protected]>
1 parent 0a0307d commit 65852d0

File tree

11 files changed

+257
-436
lines changed

11 files changed

+257
-436
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { useSelector } from 'react-redux';
9+
import type { AppState } from '../../../redux/reducer';
10+
import { useStableComputedSet } from '../../../hooks/use-stable-computed-set';
11+
import type { UUID } from 'node:crypto';
12+
import { validAlias } from './use-node-aliases';
13+
import { NodeType } from '../../graph/tree-node.type';
14+
import { isStatusBuilt } from '../../graph/util/model-functions';
15+
import type { NodeAlias } from '../types/node-alias.type';
16+
17+
export function useBuiltNodesIds(nodeAliases: NodeAlias[] | undefined) {
18+
const currentNode = useSelector((state: AppState) => state.currentTreeNode);
19+
const treeNodes = useSelector((state: AppState) => state.networkModificationTreeModel?.treeNodes);
20+
21+
return useStableComputedSet(() => {
22+
const aliasedNodesIds = nodeAliases
23+
?.filter((nodeAlias) => validAlias(nodeAlias))
24+
.map((nodeAlias) => nodeAlias.id);
25+
if (currentNode?.id) {
26+
aliasedNodesIds?.push(currentNode.id);
27+
}
28+
29+
const ids = new Set<UUID>();
30+
if (aliasedNodesIds && aliasedNodesIds.length > 0 && treeNodes) {
31+
for (const treeNode of treeNodes) {
32+
if (
33+
aliasedNodesIds.includes(treeNode.id) &&
34+
(treeNode.type === NodeType.ROOT || isStatusBuilt(treeNode.data.globalBuildStatus))
35+
) {
36+
ids.add(treeNode.id);
37+
}
38+
}
39+
}
40+
return ids;
41+
}, [nodeAliases, treeNodes]);
42+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { useCallback } from 'react';
9+
import type { UUID } from 'node:crypto';
10+
import { DeletedEquipment, isStudyNotification, type NetworkImpactsInfos } from '../../../types/notification-types';
11+
import { isSpreadsheetEquipmentType, SpreadsheetEquipmentType } from '../types/spreadsheet.type';
12+
import {
13+
deleteEquipments,
14+
type EquipmentToDelete,
15+
resetEquipments,
16+
resetEquipmentsByTypes,
17+
updateEquipments,
18+
} from '../../../redux/actions';
19+
import { fetchAllEquipments } from '../../../services/study/network-map';
20+
import { useDispatch, useSelector } from 'react-redux';
21+
import { AppState } from 'redux/reducer';
22+
import { NotificationsUrlKeys, useNotificationsListener } from '@gridsuite/commons-ui';
23+
import { NodeAlias } from '../types/node-alias.type';
24+
import { useBuiltNodesIds } from './use-built-nodes-ids';
25+
26+
const SPREADSHEET_EQUIPMENTS_LISTENER_ID = 'spreadsheet-equipments-listener';
27+
28+
export function useUpdateEquipmentsOnNotification(nodeAliases: NodeAlias[] | undefined) {
29+
const dispatch = useDispatch();
30+
const allEquipments = useSelector((state: AppState) => state.spreadsheetNetwork);
31+
const studyUuid = useSelector((state: AppState) => state.studyUuid);
32+
const currentRootNetworkUuid = useSelector((state: AppState) => state.currentRootNetworkUuid);
33+
34+
const builtNodesIds = useBuiltNodesIds(nodeAliases);
35+
36+
const updateEquipmentsLocal = useCallback(
37+
(
38+
nodeUuid: UUID,
39+
impactedSubstationsIds: UUID[],
40+
deletedEquipments: DeletedEquipment[],
41+
impactedElementTypes: string[]
42+
) => {
43+
// Handle updates and resets based on impacted element types
44+
if (impactedElementTypes.length > 0) {
45+
if (impactedElementTypes.includes(SpreadsheetEquipmentType.SUBSTATION)) {
46+
dispatch(resetEquipments());
47+
return;
48+
}
49+
const impactedSpreadsheetEquipmentsTypes = impactedElementTypes.filter((type) =>
50+
Object.keys(allEquipments).includes(type)
51+
);
52+
if (impactedSpreadsheetEquipmentsTypes.length > 0) {
53+
dispatch(
54+
resetEquipmentsByTypes(impactedSpreadsheetEquipmentsTypes.filter(isSpreadsheetEquipmentType))
55+
);
56+
}
57+
return;
58+
}
59+
60+
if (impactedSubstationsIds.length > 0 && studyUuid && currentRootNetworkUuid) {
61+
fetchAllEquipments(studyUuid, nodeUuid, currentRootNetworkUuid, impactedSubstationsIds).then(
62+
(values) => {
63+
dispatch(updateEquipments(values, nodeUuid));
64+
}
65+
);
66+
}
67+
68+
if (deletedEquipments.length > 0) {
69+
const equipmentsToDelete = deletedEquipments
70+
.filter(({ equipmentType, equipmentId }) => equipmentType && equipmentId)
71+
.map(({ equipmentType, equipmentId }) => {
72+
console.info(
73+
'removing equipment with id=',
74+
equipmentId,
75+
' and type=',
76+
equipmentType,
77+
' from the network'
78+
);
79+
return { equipmentType, equipmentId };
80+
});
81+
82+
if (equipmentsToDelete.length > 0) {
83+
const equipmentsToDeleteArray = equipmentsToDelete
84+
.filter((e) => isSpreadsheetEquipmentType(e.equipmentType))
85+
.map<EquipmentToDelete>((equipment) => ({
86+
equipmentType: equipment.equipmentType as unknown as SpreadsheetEquipmentType,
87+
equipmentId: equipment.equipmentId,
88+
}));
89+
dispatch(deleteEquipments(equipmentsToDeleteArray, nodeUuid));
90+
}
91+
}
92+
},
93+
[studyUuid, currentRootNetworkUuid, dispatch, allEquipments]
94+
);
95+
96+
const listenerUpdateEquipmentsLocal = useCallback(
97+
(event: MessageEvent) => {
98+
const eventData = JSON.parse(event.data);
99+
if (isStudyNotification(eventData)) {
100+
const eventStudyUuid = eventData.headers.studyUuid;
101+
const eventNodeUuid = eventData.headers.node;
102+
const eventRootNetworkUuid = eventData.headers.rootNetworkUuid;
103+
if (
104+
studyUuid === eventStudyUuid &&
105+
currentRootNetworkUuid === eventRootNetworkUuid &&
106+
builtNodesIds.has(eventNodeUuid)
107+
) {
108+
const networkImpacts = JSON.parse(eventData.payload) as NetworkImpactsInfos;
109+
updateEquipmentsLocal(
110+
eventNodeUuid,
111+
networkImpacts.impactedSubstationsIds,
112+
networkImpacts.deletedEquipments,
113+
networkImpacts.impactedElementTypes ?? []
114+
);
115+
}
116+
}
117+
},
118+
[builtNodesIds, currentRootNetworkUuid, studyUuid, updateEquipmentsLocal]
119+
);
120+
121+
useNotificationsListener(NotificationsUrlKeys.STUDY, {
122+
listenerCallbackMessage: listenerUpdateEquipmentsLocal,
123+
propsId: SPREADSHEET_EQUIPMENTS_LISTENER_ID,
124+
});
125+
}

src/components/spreadsheet-view/spreadsheet-view.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { initTableDefinitions, setActiveSpreadsheetTab } from 'redux/actions';
2323
import { type MuiStyles, PopupConfirmationDialog, useSnackMessage } from '@gridsuite/commons-ui';
2424
import { processSpreadsheetsCollectionData } from './add-spreadsheet/dialogs/add-spreadsheet-utils';
2525
import { DiagramType } from 'components/grid-layout/cards/diagrams/diagram.type';
26+
import { useUpdateEquipmentsOnNotification } from './hooks/use-update-equipments-on-notification';
2627

2728
const styles = {
2829
invalidNode: {
@@ -60,6 +61,8 @@ export const SpreadsheetView: FunctionComponent<SpreadsheetViewProps> = ({
6061
const studyUuid = useSelector((state: AppState) => state.studyUuid);
6162
const [resetConfirmationDialogOpen, setResetConfirmationDialogOpen] = useState(false);
6263

64+
useUpdateEquipmentsOnNotification(nodeAliases);
65+
6366
const handleSwitchTab = useCallback(
6467
(tabUuid: UUID) => {
6568
dispatch(setActiveSpreadsheetTab(tabUuid));
@@ -137,6 +140,7 @@ export const SpreadsheetView: FunctionComponent<SpreadsheetViewProps> = ({
137140
<FormattedMessage id={'NoSpreadsheets'} />
138141
</Alert>
139142
) : (
143+
nodeAliases &&
140144
tablesDefinitions.map((tabDef) => {
141145
const isActive = activeSpreadsheetTabUuid === tabDef.uuid;
142146
const equipmentIdToScrollTo = tabDef.type === equipmentType && isActive ? equipmentId : null;

0 commit comments

Comments
 (0)