From 0367d2f04a9e994109af1c4b9f96a96fb04301b6 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Mon, 18 May 2026 23:42:50 +0200 Subject: [PATCH 1/2] export network --- .../server/controller/StudyController.java | 12 ++-- .../server/dto/RootNetworkExportInfos.java | 34 +++++++++++ .../study/server/dto/StudyExportInfos.java | 14 +++++ .../study/server/dto/StudyImportInfos.java | 20 +++++++ .../server/dto/StudyTreeNodeExportInfos.java | 28 +++++++++ .../service/NetworkModificationService.java | 46 ++++++++++---- .../server/service/RootNetworkService.java | 5 +- .../study/server/service/StudyService.java | 60 +++++++++++++++---- 8 files changed, 188 insertions(+), 31 deletions(-) create mode 100644 src/main/java/org/gridsuite/study/server/dto/RootNetworkExportInfos.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/StudyExportInfos.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/StudyImportInfos.java create mode 100644 src/main/java/org/gridsuite/study/server/dto/StudyTreeNodeExportInfos.java diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index b165475a7a..050d015fc8 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -1347,13 +1347,11 @@ public ResponseEntity getNetworkModifications(@Parameter(description = " return ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN).body(networkModificationTreeService.getNetworkModifications(nodeUuid, onlyStashed, onlyMetadata)); } - @GetMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}/network-modifications/export", produces = MediaType.APPLICATION_JSON_VALUE) - @Operation(summary = "Get network modifications to export for a given node") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The network modifications to export was returned"), @ApiResponse(responseCode = "404", description = "The study/node is not found")}) - public ResponseEntity getExportedNetworkModifications(@Parameter(description = "Study UUID") @PathVariable("studyUuid") UUID studyUuid, - @Parameter(description = "Node UUID") @PathVariable("nodeUuid") UUID nodeUuid) { - studyService.assertIsStudyAndNodeExist(studyUuid, nodeUuid); - return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(studyService.getExportedNetworkModifications(studyUuid, nodeUuid)); + @GetMapping(value = "/studies/{studyUuid}/network-modifications/export", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Export a full study: root networks + node tree with embedded modifications") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The study export"), @ApiResponse(responseCode = "404", description = "The study is not found")}) + public ResponseEntity exportStudy(@Parameter(description = "Study UUID") @PathVariable("studyUuid") UUID studyUuid) { + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(studyService.getStudyExport(studyUuid)); } @GetMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}/excluded-network-modifications", produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/org/gridsuite/study/server/dto/RootNetworkExportInfos.java b/src/main/java/org/gridsuite/study/server/dto/RootNetworkExportInfos.java new file mode 100644 index 0000000000..18aa1e700e --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/RootNetworkExportInfos.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026, 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/. + */ +package org.gridsuite.study.server.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +import java.util.Map; +import java.util.UUID; + +/** + * @author Rehili Ghazwa + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RootNetworkExportInfos { + private UUID id; + private String name; + private String description; + private String tag; + private String caseName; + private String caseFormat; + private Map importParameters; +} diff --git a/src/main/java/org/gridsuite/study/server/dto/StudyExportInfos.java b/src/main/java/org/gridsuite/study/server/dto/StudyExportInfos.java new file mode 100644 index 0000000000..88fc4e4898 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/StudyExportInfos.java @@ -0,0 +1,14 @@ +package org.gridsuite.study.server.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; +import java.util.UUID; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record StudyExportInfos(UUID studyUuid, + List rootNetworks, + StudyTreeNodeExportInfos rootNode) { +} diff --git a/src/main/java/org/gridsuite/study/server/dto/StudyImportInfos.java b/src/main/java/org/gridsuite/study/server/dto/StudyImportInfos.java new file mode 100644 index 0000000000..f901e4126a --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/StudyImportInfos.java @@ -0,0 +1,20 @@ +package org.gridsuite.study.server.dto; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.*; + +import java.util.List; +import java.util.UUID; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class StudyImportInfos { + private String exportVersion; + private UUID studyUuid; + private String studyName; + private List rootNetworks; + private JsonNode rootNode; +} diff --git a/src/main/java/org/gridsuite/study/server/dto/StudyTreeNodeExportInfos.java b/src/main/java/org/gridsuite/study/server/dto/StudyTreeNodeExportInfos.java new file mode 100644 index 0000000000..7e433e47b8 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/StudyTreeNodeExportInfos.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2026, 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/. + */ +package org.gridsuite.study.server.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.gridsuite.study.server.networkmodificationtree.entities.NetworkModificationNodeType; +import org.gridsuite.study.server.networkmodificationtree.entities.NodeType; + +import java.util.List; +import java.util.UUID; + +/** + * @author Rehili Ghazwa + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record StudyTreeNodeExportInfos(UUID uuid, + String name, + NodeType type, + NetworkModificationNodeType nodeType, + @JsonInclude(JsonInclude.Include.NON_EMPTY) Object modifications, + List nodes) { +} diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index 852a378dc5..15fb54cf95 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -7,15 +7,14 @@ package org.gridsuite.study.server.service; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.NonNull; import org.gridsuite.study.server.RemoteServicesProperties; import org.gridsuite.study.server.StudyConstants; import org.gridsuite.study.server.dto.BuildInfos; import org.gridsuite.study.server.dto.NodeReceiver; -import org.gridsuite.study.server.dto.modification.ModificationApplicationContext; -import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; -import org.gridsuite.study.server.dto.modification.NetworkModificationsResult; +import org.gridsuite.study.server.dto.modification.*; import org.gridsuite.study.server.dto.workflow.AbstractWorkflowInfos; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; @@ -93,14 +92,18 @@ public String getModifications(UUID groupUUid, boolean stashedModifications, boo return restTemplate.exchange(getNetworkModificationServerURI(false) + path, HttpMethod.GET, null, String.class).getBody(); } - public String getModificationsToExport(UUID groupUUid) { - Objects.requireNonNull(groupUUid); - var path = UriComponentsBuilder.fromPath(GROUP_PATH + DELIMITER + NETWORK_MODIFICATIONS_PATH + DELIMITER + "export") - .queryParam(QUERY_PARAM_ERROR_ON_GROUP_NOT_FOUND, false) - .buildAndExpand(groupUUid) - .toUriString(); + public Map getModificationsInfosToExport(List groupUuids) { + Objects.requireNonNull(groupUuids); + if (groupUuids.isEmpty()) { + return Map.of(); + } + var path = UriComponentsBuilder.fromPath("groups" + DELIMITER + "modifications" + DELIMITER + "export") + .queryParam(QUERY_PARAM_ERROR_ON_GROUP_NOT_FOUND, false) + .toUriString(); - return restTemplate.exchange(getNetworkModificationServerURI(false) + path, HttpMethod.GET, null, String.class).getBody(); + HttpEntity> httpEntity = new HttpEntity<>(groupUuids); + return restTemplate.exchange(getNetworkModificationServerURI(false) + path, HttpMethod.POST, httpEntity, + new ParameterizedTypeReference>() { }).getBody(); } public Integer getModificationsCount(UUID groupUUid, boolean stashedModifications) { @@ -450,4 +453,27 @@ public Set expandToLeafUuids(List modificationUuids) { new ParameterizedTypeReference>() { } ).getBody(); } + + public Map getModificationsToExportByGroups(List groupUuids) { + Objects.requireNonNull(groupUuids); + if (groupUuids.isEmpty()) { + return Map.of(); + } + + var path = UriComponentsBuilder.fromPath("groups/modifications/export") + .queryParam(QUERY_PARAM_ERROR_ON_GROUP_NOT_FOUND, false) + .build() + .toUriString(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(groupUuids, headers); + + return restTemplate.exchange( + getNetworkModificationServerURI(false) + path, + HttpMethod.POST, + entity, + new ParameterizedTypeReference>() { } + ).getBody(); + } } diff --git a/src/main/java/org/gridsuite/study/server/service/RootNetworkService.java b/src/main/java/org/gridsuite/study/server/service/RootNetworkService.java index 3c1e7b84a9..30beb9a36b 100644 --- a/src/main/java/org/gridsuite/study/server/service/RootNetworkService.java +++ b/src/main/java/org/gridsuite/study/server/service/RootNetworkService.java @@ -10,11 +10,8 @@ import com.powsybl.network.store.client.NetworkStoreService; import com.powsybl.network.store.model.VariantInfos; import lombok.NonNull; +import org.gridsuite.study.server.dto.*; import org.gridsuite.study.server.error.StudyException; -import org.gridsuite.study.server.dto.CaseInfos; -import org.gridsuite.study.server.dto.NetworkInfos; -import org.gridsuite.study.server.dto.RootNetworkAction; -import org.gridsuite.study.server.dto.RootNetworkInfos; import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.repository.rootnetwork.RootNetworkRequestEntity; diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index a4eef7f405..1084d4149d 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -38,10 +38,7 @@ import org.gridsuite.study.server.elasticsearch.StudyInfosService; import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.networkmodificationtree.dto.*; -import org.gridsuite.study.server.networkmodificationtree.entities.NetworkModificationNodeInfoEntity; -import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; -import org.gridsuite.study.server.networkmodificationtree.entities.NodeType; -import org.gridsuite.study.server.networkmodificationtree.entities.RootNetworkNodeInfoEntity; +import org.gridsuite.study.server.networkmodificationtree.entities.*; import org.gridsuite.study.server.notification.NotificationService; import org.gridsuite.study.server.notification.dto.NetworkImpactsInfos; import org.gridsuite.study.server.repository.*; @@ -524,14 +521,57 @@ public List searchModifications(@NonNull UUID r return networkModificationTreeService.getNetworkModificationsByNodeInfos(modificationsByGroup); } - @Transactional - public String getExportedNetworkModifications(UUID studyUuid, UUID nodeUuid) { - if (!networkModificationTreeService.getStudyUuidForNodeId(nodeUuid).equals(studyUuid)) { - throw new StudyException(NOT_ALLOWED); + @Transactional(readOnly = true) + public StudyExportInfos getStudyExport(UUID studyUuid) { + assertIsStudyExist(studyUuid); + + List rootNetworks = rootNetworkService.getRootNetworkInfosWithLinksInfos(studyUuid).stream() + .map(this::toRootNetworkExportInfos) + .toList(); + + RootNode rootNode = networkModificationTreeService.getStudyTree(studyUuid, null); + + List groupUuids = new ArrayList<>(); + collectGroupUuids(rootNode, groupUuids); + + Map modificationsByGroup = networkModificationService.getModificationsInfosToExport(groupUuids); + + return new StudyExportInfos(studyUuid, rootNetworks, toNodeExportInfos(rootNode, modificationsByGroup)); + } + + private RootNetworkExportInfos toRootNetworkExportInfos(RootNetworkInfos infos) { + CaseInfos caseInfos = infos.getCaseInfos(); + Map importParameters = infos.getImportParameters() != null + ? new LinkedHashMap<>(infos.getImportParameters()) + : null; + return new RootNetworkExportInfos( + infos.getId(), + infos.getName(), + infos.getDescription(), + infos.getTag(), + caseInfos != null ? caseInfos.getCaseName() : null, + caseInfos != null ? caseInfos.getCaseFormat() : null, + importParameters); + } + + private void collectGroupUuids(AbstractNode node, List acc) { + if (node instanceof NetworkModificationNode nmNode && nmNode.getModificationGroupUuid() != null) { + acc.add(nmNode.getModificationGroupUuid()); } - UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeUuid); + node.getChildren().forEach(child -> collectGroupUuids(child, acc)); + } - return networkModificationService.getModificationsToExport(groupId); + private StudyTreeNodeExportInfos toNodeExportInfos(AbstractNode node, Map modificationsByGroup) { + NetworkModificationNodeType nodeType = null; + Object modifications = null; + if (node instanceof NetworkModificationNode nmNode) { + nodeType = nmNode.getNodeType(); + if (modificationsByGroup.get(nmNode.getModificationGroupUuid()) instanceof Map wrapper) { + modifications = wrapper.get("modifications"); + } + } + List children = node.getChildren().stream().map(child -> toNodeExportInfos(child, modificationsByGroup)).toList(); + return new StudyTreeNodeExportInfos(node.getId(), node.getName(), node.getType(), nodeType, modifications, children.isEmpty() ? null : children); } private Optional doDeleteStudyIfNotCreationInProgress(UUID studyUuid) { From 20f3553ae9f78f5a2f2ead973bc881e23f897ed2 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Thu, 28 May 2026 15:20:03 +0200 Subject: [PATCH 2/2] update test --- .../service/NetworkModificationService.java | 5 ++-- .../server/NetworkModificationTreeTest.java | 30 +++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java index 5a3d59a8df..f40d2127bc 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java @@ -46,7 +46,8 @@ public class NetworkModificationService { private static final String DELIMITER = "/"; private static final String COMPOSITE_PATH = "network-composite-modifications" + DELIMITER; - private static final String GROUP_PATH = "groups" + DELIMITER + "{groupUuid}"; + private static final String GROUPS = "groups"; + private static final String GROUP_PATH = GROUPS + DELIMITER + "{groupUuid}"; private static final String NETWORK_MODIFICATIONS_PATH = "network-modifications"; private static final String NETWORK_MODIFICATIONS_COUNT_PATH = "network-modifications-count"; private static final String QUERY_PARAM_ACTION = "action"; @@ -98,7 +99,7 @@ public Map getModificationsInfosToExport(List groupUuids) { if (groupUuids.isEmpty()) { return Map.of(); } - var path = UriComponentsBuilder.fromPath("groups" + DELIMITER + "modifications" + DELIMITER + "export") + var path = UriComponentsBuilder.fromPath(GROUPS + DELIMITER + NETWORK_MODIFICATIONS_PATH + DELIMITER + "export") .queryParam(QUERY_PARAM_ERROR_ON_GROUP_NOT_FOUND, false) .toUriString(); diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java index 8f48228fbe..9f3e8b2b4e 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java @@ -30,6 +30,7 @@ import okhttp3.HttpUrl; import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.RootNetworkNodeInfo; +import org.gridsuite.study.server.dto.StudyExportInfos; import org.gridsuite.study.server.dto.modification.NetworkModificationResult; import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.networkmodificationtree.dto.*; @@ -289,6 +290,9 @@ public MockResponse dispatch(RecordedRequest request) { return new MockResponse(HttpStatus.OK.value()); } else if (path.matches("/v1/network-modifications.*")) { return new MockResponse(HttpStatus.OK.value()); + } else if (path.matches("/v1/groups/network-modifications/export.*") && request.getMethod().equals("POST")) { + return new MockResponse(HttpStatus.OK.value(), Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), + objectMapper.writeValueAsString(Map.of(MODIFICATION_GROUP_UUID, List.of()))); } else if (path.matches("/v1/groups/" + MODIFICATION_GROUP_UUID + "/network-modifications-count.*") && request.getMethod().equals("GET")) { return new MockResponse(HttpStatus.OK.value(), Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), objectMapper.writeValueAsString(0)); } else if (path.matches("/v1/groups/" + MODIFICATION_GROUP_UUID_2 + "/network-modifications-count.*") && request.getMethod().equals("GET")) { @@ -1517,23 +1521,25 @@ void testGetNetworkModificationsNode() throws Exception { void testGetNetworkModificationsNodeToExport() throws Exception { String userId = "userId"; RootNode root = createRoot(); - NetworkModificationNode node = NetworkModificationTreeTest.buildNetworkModificationConstructionNode("modification node 1", "", UUID.randomUUID(), VARIANT_ID, + NetworkModificationNode node = NetworkModificationTreeTest.buildNetworkModificationConstructionNode( + "modification node 2", "", MODIFICATION_GROUP_UUID, VARIANT_ID, randomUuidsResultStack(), BuildStatus.BUILT); createNode(root.getStudyId(), root, node, userId); - mockMvc.perform(get("/v1/studies/{studyUuid}/nodes/{nodeUuid}/network-modifications/export", root.getStudyId(), node.getId())) - .andExpect(status().isNotFound()); - - // No network modification for a root node - mockMvc.perform(get("/v1/studies/{studyUuid}/nodes/{nodeUuid}/network-modifications/export", root.getStudyId(), root.getId())) - .andExpect(status().isNotFound()); - - node = NetworkModificationTreeTest.buildNetworkModificationConstructionNode("modification node 2", "", MODIFICATION_GROUP_UUID, VARIANT_ID, - randomUuidsResultStack(), BuildStatus.BUILT); - createNode(root.getStudyId(), root, node, userId); - mockMvc.perform(get("/v1/studies/{studyUuid}/nodes/{nodeUuid}/network-modifications/export", root.getStudyId(), node.getId())) + String result = mockMvc.perform(get("/v1/studies/{studyUuid}/network-modifications/export", root.getStudyId())) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); + + StudyExportInfos export = objectMapper.readValue(result, StudyExportInfos.class); + assertEquals(root.getStudyId(), export.studyUuid()); + assertEquals("Root", export.rootNode().name()); + assertEquals(1, export.rootNode().nodes().size()); + } + + @Test + void testGetNetworkModificationsToExportStudyNotFound() throws Exception { + mockMvc.perform(get("/v1/studies/{studyUuid}/network-modifications/export", UUID.randomUUID())) + .andExpect(status().isNotFound()); } private void checkMessagesReceivedWhenRestoreOrStash(RootNode root, NetworkModificationNode node, String restoreOrStashUpdateType) {