Skip to content

Commit 45d69c1

Browse files
add shortcut to build node (#3196)
Signed-off-by: Franck LECUYER <[email protected]>
1 parent db6e23b commit 45d69c1

File tree

3 files changed

+123
-1
lines changed

3 files changed

+123
-1
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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 { BUILD_STATUS } from 'components/network/constants';
9+
import React, { useCallback, useState } from 'react';
10+
import { PlayCircleFilled, StopCircleOutlined } from '@mui/icons-material';
11+
import { Button, CircularProgress, Theme } from '@mui/material';
12+
import { buildNode, unbuildNode } from '../../../services/study';
13+
import { UUID } from 'crypto';
14+
import { useSnackMessage } from '@gridsuite/commons-ui';
15+
import { HTTP_MAX_NODE_BUILDS_EXCEEDED_MESSAGE } from 'components/network-modification-tree-pane';
16+
17+
type BuildButtonProps = {
18+
buildStatus?: BUILD_STATUS;
19+
studyUuid: UUID | null;
20+
currentRootNetworkUuid: UUID | null;
21+
nodeUuid: UUID;
22+
};
23+
24+
const styles = {
25+
button: {
26+
minWidth: '40px',
27+
},
28+
playColor: (theme: Theme) => ({
29+
color: theme.palette.mode === 'light' ? 'grey' : 'white',
30+
}),
31+
};
32+
33+
export const BuildButton = ({ buildStatus, studyUuid, currentRootNetworkUuid, nodeUuid }: BuildButtonProps) => {
34+
const [isLoading, setIsLoading] = useState(false);
35+
const { snackError } = useSnackMessage();
36+
37+
const handleClick = useCallback(
38+
(event: React.MouseEvent<HTMLButtonElement>) => {
39+
event.stopPropagation();
40+
if (!studyUuid || !currentRootNetworkUuid || isLoading) {
41+
return;
42+
}
43+
44+
setIsLoading(true);
45+
46+
if (buildStatus === BUILD_STATUS.NOT_BUILT) {
47+
buildNode(studyUuid, nodeUuid, currentRootNetworkUuid)
48+
.catch((error) => {
49+
if (error.status === 403 && error.message.includes(HTTP_MAX_NODE_BUILDS_EXCEEDED_MESSAGE)) {
50+
// retrieve last word of the message (ex: "MAX_NODE_BUILDS_EXCEEDED max allowed built nodes : 2" -> 2)
51+
let limit = error.message.split(/[: ]+/).pop();
52+
snackError({
53+
messageId: 'maxBuiltNodeExceededError',
54+
messageValues: { limit: limit },
55+
});
56+
} else {
57+
snackError({
58+
messageTxt: error.message,
59+
headerId: 'NodeBuildingError',
60+
});
61+
}
62+
})
63+
.finally(() => {
64+
setIsLoading(false);
65+
});
66+
} else {
67+
unbuildNode(studyUuid, nodeUuid, currentRootNetworkUuid)
68+
.catch((error) => {
69+
snackError({
70+
messageTxt: error.message,
71+
headerId: 'NodeUnbuildingError',
72+
});
73+
})
74+
.finally(() => {
75+
setIsLoading(false);
76+
});
77+
}
78+
},
79+
[studyUuid, currentRootNetworkUuid, nodeUuid, buildStatus, isLoading, snackError]
80+
);
81+
82+
const getIcon = () => {
83+
if (isLoading) {
84+
return <CircularProgress size={24} color="primary" />;
85+
}
86+
return buildStatus === BUILD_STATUS.NOT_BUILT ? (
87+
<PlayCircleFilled sx={styles.playColor} />
88+
) : (
89+
<StopCircleOutlined color="primary" />
90+
);
91+
};
92+
93+
const isButtonDisabled = isLoading || !studyUuid || !currentRootNetworkUuid;
94+
95+
return (
96+
<Button size="small" onClick={handleClick} sx={styles.button} disabled={isButtonDisabled}>
97+
{getIcon()}
98+
</Button>
99+
);
100+
};

src/components/graph/nodes/network-modification-node.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import NodeHandle from './node-handle';
2020
import { baseNodeStyles, interactiveNodeStyles, selectedBaseNodeStyles } from './styles';
2121
import NodeOverlaySpinner from './node-overlay-spinner';
2222
import BuildStatusChip from './build-status-chip';
23+
import React from 'react';
24+
import { BuildButton } from './build-button';
2325

2426
const styles = {
2527
networkModificationSelected: (theme: Theme) => ({
@@ -54,6 +56,13 @@ const styles = {
5456
marginLeft: theme.spacing(1),
5557
height: '35%',
5658
}),
59+
buildBox: (theme: Theme) => ({
60+
display: 'flex',
61+
justifyContent: 'flex-end',
62+
marginTop: theme.spacing(-5),
63+
marginRight: theme.spacing(0),
64+
height: '35%',
65+
}),
5766
chipFloating: (theme: Theme) => ({
5867
position: 'absolute',
5968
top: theme.spacing(-4),
@@ -68,6 +77,8 @@ const styles = {
6877
const NetworkModificationNode = (props: NodeProps<ModificationNode>) => {
6978
const currentNode = useSelector((state: AppState) => state.currentTreeNode);
7079
const selectionForCopy = useSelector((state: AppState) => state.nodeSelectionForCopy);
80+
const studyUuid = useSelector((state: AppState) => state.studyUuid);
81+
const currentRootNetworkUuid = useSelector((state: AppState) => state.currentRootNetworkUuid);
7182

7283
const isSelectedNode = () => {
7384
return props.id === currentNode?.id;
@@ -121,6 +132,17 @@ const NetworkModificationNode = (props: NodeProps<ModificationNode>) => {
121132
)}
122133
</Box>
123134

135+
<Box sx={styles.buildBox}>
136+
{props.data.localBuildStatus !== BUILD_STATUS.BUILDING && (
137+
<BuildButton
138+
buildStatus={props.data.localBuildStatus}
139+
studyUuid={studyUuid}
140+
currentRootNetworkUuid={currentRootNetworkUuid}
141+
nodeUuid={props.id}
142+
/>
143+
)}
144+
</Box>
145+
124146
{props.data.localBuildStatus === BUILD_STATUS.BUILDING && <NodeOverlaySpinner />}
125147
</Box>
126148
</>

src/components/network-modification-tree-pane.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const noNodeSelectionForCopy = {
5151
allChildrenIds: null,
5252
};
5353

54-
const HTTP_MAX_NODE_BUILDS_EXCEEDED_MESSAGE = 'MAX_NODE_BUILDS_EXCEEDED';
54+
export const HTTP_MAX_NODE_BUILDS_EXCEEDED_MESSAGE = 'MAX_NODE_BUILDS_EXCEEDED';
5555

5656
export const NetworkModificationTreePane = ({
5757
studyUuid,

0 commit comments

Comments
 (0)