Skip to content

Commit 49d61c0

Browse files
fix: spreadsheet updates on notifications
Signed-off-by: Joris Mancini <[email protected]>
1 parent a0fbbcb commit 49d61c0

File tree

10 files changed

+237
-384
lines changed

10 files changed

+237
-384
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useSelector } from 'react-redux';
2+
import type { AppState } from '../../../redux/reducer';
3+
import { useStableComputedSet } from '../../../hooks/use-stable-computed-set';
4+
import type { UUID } from 'crypto';
5+
import { validAlias } from './use-node-aliases';
6+
import { NodeType } from '../../graph/tree-node.type';
7+
import { isStatusBuilt } from '../../graph/util/model-functions';
8+
import type { NodeAlias } from '../types/node-alias.type';
9+
10+
export function useSpreadsheetNodes(nodeAliases: NodeAlias[] | undefined) {
11+
const currentNode = useSelector((state: AppState) => state.currentTreeNode);
12+
const treeNodes = useSelector((state: AppState) => state.networkModificationTreeModel?.treeNodes);
13+
14+
const builtNodesIds = useStableComputedSet(() => {
15+
const ids: Set<UUID> = new Set<UUID>();
16+
if (currentNode?.id) {
17+
ids.add(currentNode.id);
18+
}
19+
const aliasedNodesIds = nodeAliases
20+
?.filter((nodeAlias) => validAlias(nodeAlias))
21+
.map((nodeAlias) => nodeAlias.id);
22+
if (aliasedNodesIds && aliasedNodesIds.length > 0) {
23+
treeNodes?.forEach((treeNode) => {
24+
if (
25+
aliasedNodesIds.includes(treeNode.id) &&
26+
(treeNode.type === NodeType.ROOT || isStatusBuilt(treeNode.data.globalBuildStatus))
27+
) {
28+
ids.add(treeNode.id);
29+
}
30+
});
31+
}
32+
return ids;
33+
}, [nodeAliases, treeNodes]);
34+
35+
return { builtNodesIds };
36+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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 '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 { useSpreadsheetNodes } from './use-spreadsheet-nodes';
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 } = useSpreadsheetNodes(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+
}
58+
59+
if (impactedSubstationsIds.length > 0 && studyUuid && currentRootNetworkUuid) {
60+
fetchAllEquipments(studyUuid, nodeUuid, currentRootNetworkUuid, impactedSubstationsIds).then(
61+
(values) => {
62+
dispatch(updateEquipments(values, nodeUuid));
63+
}
64+
);
65+
}
66+
67+
if (deletedEquipments.length > 0) {
68+
const equipmentsToDelete = deletedEquipments
69+
.filter(({ equipmentType, equipmentId }) => equipmentType && equipmentId)
70+
.map(({ equipmentType, equipmentId }) => {
71+
console.info(
72+
'removing equipment with id=',
73+
equipmentId,
74+
' and type=',
75+
equipmentType,
76+
' from the network'
77+
);
78+
return { equipmentType, equipmentId };
79+
});
80+
81+
if (equipmentsToDelete.length > 0) {
82+
const equipmentsToDeleteArray = equipmentsToDelete
83+
.filter((e) => isSpreadsheetEquipmentType(e.equipmentType))
84+
.map<EquipmentToDelete>((equipment) => ({
85+
equipmentType: equipment.equipmentType as unknown as SpreadsheetEquipmentType,
86+
equipmentId: equipment.equipmentId,
87+
}));
88+
dispatch(deleteEquipments(equipmentsToDeleteArray, nodeUuid));
89+
}
90+
}
91+
},
92+
[studyUuid, currentRootNetworkUuid, dispatch, allEquipments]
93+
);
94+
95+
const listenerUpdateEquipmentsLocal = useCallback(
96+
(event: MessageEvent) => {
97+
const eventData = JSON.parse(event.data);
98+
if (isStudyNotification(eventData)) {
99+
const eventStudyUuid = eventData.headers.studyUuid;
100+
const eventNodeUuid = eventData.headers.node;
101+
const eventRootNetworkUuid = eventData.headers.rootNetworkUuid;
102+
if (
103+
studyUuid === eventStudyUuid &&
104+
currentRootNetworkUuid === eventRootNetworkUuid &&
105+
builtNodesIds.has(eventNodeUuid)
106+
) {
107+
const payload = JSON.parse(eventData.payload) as NetworkImpactsInfos;
108+
console.log(`notif impacts: ${JSON.stringify(payload)}`);
109+
const impactedSubstationsIds = payload.impactedSubstationsIds;
110+
const deletedEquipments = payload.deletedEquipments;
111+
const impactedElementTypes = payload.impactedElementTypes ?? [];
112+
updateEquipmentsLocal(
113+
eventNodeUuid,
114+
impactedSubstationsIds,
115+
deletedEquipments,
116+
impactedElementTypes
117+
);
118+
}
119+
}
120+
},
121+
[builtNodesIds, currentRootNetworkUuid, studyUuid, updateEquipmentsLocal]
122+
);
123+
124+
useNotificationsListener(NotificationsUrlKeys.STUDY, {
125+
listenerCallbackMessage: listenerUpdateEquipmentsLocal,
126+
propsId: SPREADSHEET_EQUIPMENTS_LISTENER_ID,
127+
});
128+
}

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 { 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)