Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,26 @@ public ResponseEntity<Void> moveModification(@PathVariable("studyUuid") UUID stu
return ResponseEntity.ok().build();
}

@PutMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}/composite-sub-modification/{modificationUuid}")
@Operation(summary = "Move a composite sub-modification within/between composites or to/from root level")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The sub-modification order has been updated")})
public ResponseEntity<Void> moveSubModification(
@PathVariable("studyUuid") UUID studyUuid,
@PathVariable("nodeUuid") UUID nodeUuid,
@PathVariable("modificationUuid") UUID modificationUuid,
@Nullable @Parameter(description = "Source composite UUID; absent when moving from root level") @RequestParam(value = "sourceCompositeUuid", required = false) UUID sourceCompositeUuid,
@Nullable @Parameter(description = "Target composite UUID; absent when moving to root level") @RequestParam(value = "targetCompositeUuid", required = false) UUID targetCompositeUuid,
@Nullable @Parameter(description = "Insert before this UUID; absent means append at end") @RequestParam(value = "beforeUuid", required = false) UUID beforeUuid,
@RequestHeader(HEADER_USER_ID) String userId) {
studyService.assertCanUpdateNodeInStudy(studyUuid, nodeUuid);
studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid);
rebuildNodeService.moveSubModification(
studyUuid, nodeUuid,
sourceCompositeUuid, targetCompositeUuid,
modificationUuid, beforeUuid, userId);
return ResponseEntity.ok().build();
}

@PutMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "For a list of network modifications passed in body, copy or cut, then append them to target node")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The modification list has been updated.")})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,4 +406,46 @@ public Map<UUID, Object> searchModifications(UUID networkUuid, String userInput)
}).getBody();

}

public void moveSubModification(
UUID groupUuid,
UUID sourceCompositeUuid,
UUID targetCompositeUuid,
UUID modificationUuid,
UUID beforeUuid) {

var path = UriComponentsBuilder.fromPath(
COMPOSITE_PATH + "groups" + DELIMITER
+ "{groupUuid}" + DELIMITER
+ "sub-modifications" + DELIMITER
+ "{modificationUuid}");

if (sourceCompositeUuid != null) {
path.queryParam("sourceCompositeUuid", sourceCompositeUuid);
}
if (targetCompositeUuid != null) {
path.queryParam("targetCompositeUuid", targetCompositeUuid);
}
if (beforeUuid != null) {
path.queryParam("beforeUuid", beforeUuid);
}

restTemplate.put(
getNetworkModificationServerURI(false)
+ path.buildAndExpand(groupUuid, modificationUuid).toUriString(),
null);
}

public Set<UUID> expandToLeafUuids(List<UUID> modificationUuids) {
var path = UriComponentsBuilder.fromPath(COMPOSITE_PATH + "leaf-uuids")
.queryParam("uuids", modificationUuids)
.toUriString();

return restTemplate.exchange(
getNetworkModificationServerURI(false) + path,
HttpMethod.GET,
null,
new ParameterizedTypeReference<Set<UUID>>() { }
).getBody();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
package org.gridsuite.study.server.service;

import lombok.NonNull;
import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -84,6 +85,38 @@ public void moveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, UUID o
() -> handleMoveNetworkModifications(studyUuid, targetNodeUuid, originNodeUuid, modificationsToCopyUuidList, userId));
}

public void moveSubModification(
UUID studyUuid,
UUID nodeUuid,
UUID sourceCompositeUuid,
UUID targetCompositeUuid,
UUID modificationUuid,
UUID beforeUuid,
String userId) {
handleRebuildNode(studyUuid, nodeUuid, userId,
() -> handleMoveNetworkSubmodification(
studyUuid, nodeUuid,
sourceCompositeUuid, targetCompositeUuid,
modificationUuid, beforeUuid, userId));
}

private void handleMoveNetworkSubmodification(@NonNull UUID studyUuid,
@NonNull UUID nodeUuid,
UUID sourceCompositeUuid,
UUID targetCompositeUuid,
@NonNull UUID modificationUuid,
UUID beforeUuid,
String userId) {
studyService.invalidateNodeTreeWhenMoveModification(studyUuid, nodeUuid);
try {
studyService.moveSubModification(studyUuid, nodeUuid,
sourceCompositeUuid, targetCompositeUuid,
modificationUuid, beforeUuid, userId);
} finally {
studyService.unblockNodeTree(studyUuid, nodeUuid);
}
}

private void handleMoveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, UUID originNodeUuid, List<UUID> modificationsToCopyUuidList, String userId) {
boolean isTargetInDifferentNodeTree = studyService.invalidateNodeTreeWhenMoveModifications(studyUuid, targetNodeUuid, originNodeUuid);
try {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/gridsuite/study/server/service/StudyService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2171,6 +2171,7 @@ public void updateNetworkModificationsActivationInRootNetwork(UUID studyUuid, UU
if (!networkModificationTreeService.getStudyUuidForNodeId(nodeUuid).equals(studyUuid)) {
throw new StudyException(NOT_ALLOWED);
}
modificationsUuids.addAll(networkModificationService.expandToLeafUuids(new ArrayList<>(modificationsUuids)));
rootNetworkNodeInfoService.updateModificationsToExclude(nodeUuid, rootNetworkUuid, modificationsUuids, activated);
invalidateNodeTree(studyUuid, nodeUuid, rootNetworkUuid);
} finally {
Expand Down Expand Up @@ -2391,6 +2392,24 @@ public void moveNetworkModifications(
notificationService.emitElementUpdated(studyUuid, userId);
}

public void moveSubModification(
@NonNull UUID studyUuid,
@NonNull UUID nodeUuid,
UUID sourceCompositeUuid,
UUID targetCompositeUuid,
@NonNull UUID modificationUuid,
UUID beforeUuid,
String userId) {
List<UUID> childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid);
notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_UPDATING_IN_PROGRESS);
checkStudyContainsNode(studyUuid, nodeUuid);
UUID groupUuid = networkModificationTreeService.getModificationGroupUuid(nodeUuid);
networkModificationService.moveSubModification(
groupUuid, sourceCompositeUuid, targetCompositeUuid, modificationUuid, beforeUuid);
notificationService.emitEndModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids);
notificationService.emitElementUpdated(studyUuid, userId);
}

private void emitNetworkModificationImpactsForAllRootNetworks(List<Optional<NetworkModificationResult>> modificationResults, StudyEntity studyEntity, UUID impactedNode) {
int index = 0;
List<RootNetworkEntity> rootNetworkEntities = studyEntity.getRootNetworks();
Expand Down
132 changes: 132 additions & 0 deletions src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3084,6 +3084,138 @@ private void checkElementUpdatedMessageSent(UUID elementUuid, String userId) {
assertEquals(userId, message.getHeaders().get(NotificationService.HEADER_MODIFIED_BY));
}

@Test
void testMoveSubModification() throws Exception {
String userId = "userId";
StudyEntity studyEntity = insertDummyStudy(UUID.fromString(NETWORK_UUID_STRING), CASE_UUID, "UCTE");
UUID studyUuid = studyEntity.getId();
UUID rootNodeUuid = getRootNode(studyUuid).getId();
NetworkModificationNode node = createNetworkModificationNode(studyUuid, rootNodeUuid,
UUID.randomUUID(), VARIANT_ID, "node 1", userId);
UUID nodeUuid = node.getId();

UUID modificationUuid = UUID.randomUUID();
UUID sourceCompositeUuid = UUID.randomUUID();
UUID targetCompositeUuid = UUID.randomUUID();
UUID beforeUuid = UUID.randomUUID();

String moveSubModifUrlPattern = "/v1/network-composite-modifications/groups/" + node.getModificationGroupUuid()
+ "/sub-modifications/" + modificationUuid;

// --- Case 1: move between two composites with a beforeUuid ---
wireMockServer.stubFor(WireMock.put(WireMock.urlPathEqualTo(moveSubModifUrlPattern))
.willReturn(WireMock.ok()));

mockMvc.perform(put("/v1/studies/{studyUuid}/nodes/{nodeUuid}/composite-sub-modification/{modificationUuid}",
studyUuid, nodeUuid, modificationUuid)
.queryParam("sourceCompositeUuid", sourceCompositeUuid.toString())
.queryParam("targetCompositeUuid", targetCompositeUuid.toString())
.queryParam("beforeUuid", beforeUuid.toString())
.header(USER_ID_HEADER, userId))
.andExpect(status().isOk());
checkUpdateStatusMessagesReceived(studyUuid, nodeUuid, output);
checkEquipmentUpdatingMessagesReceived(studyUuid, nodeUuid);
checkEquipmentUpdatingFinishedMessagesReceived(studyUuid, nodeUuid);
checkElementUpdatedMessageSent(studyUuid, userId);
WireMockUtilsCriteria.verifyPutRequest(wireMockServer, moveSubModifUrlPattern, false, Map.of(
"sourceCompositeUuid", WireMock.equalTo(sourceCompositeUuid.toString()),
"targetCompositeUuid", WireMock.equalTo(targetCompositeUuid.toString()),
"beforeUuid", WireMock.equalTo(beforeUuid.toString())), null);

// --- Case 2: move from root level into a composite (no sourceCompositeUuid) ---
wireMockServer.stubFor(WireMock.put(WireMock.urlPathEqualTo(moveSubModifUrlPattern))
.willReturn(WireMock.ok()));

mockMvc.perform(put("/v1/studies/{studyUuid}/nodes/{nodeUuid}/composite-sub-modification/{modificationUuid}",
studyUuid, nodeUuid, modificationUuid)
.queryParam("targetCompositeUuid", targetCompositeUuid.toString())
.header(USER_ID_HEADER, userId))
.andExpect(status().isOk());
checkUpdateStatusMessagesReceived(studyUuid, nodeUuid, output);
checkEquipmentUpdatingMessagesReceived(studyUuid, nodeUuid);
checkEquipmentUpdatingFinishedMessagesReceived(studyUuid, nodeUuid);
checkElementUpdatedMessageSent(studyUuid, userId);
WireMockUtilsCriteria.verifyPutRequest(wireMockServer, moveSubModifUrlPattern, false, Map.of(
"targetCompositeUuid", WireMock.equalTo(targetCompositeUuid.toString())), null);

// --- Case 3: move from inside a composite to root level (no targetCompositeUuid) ---
wireMockServer.stubFor(WireMock.put(WireMock.urlPathEqualTo(moveSubModifUrlPattern))
.willReturn(WireMock.ok()));

mockMvc.perform(put("/v1/studies/{studyUuid}/nodes/{nodeUuid}/composite-sub-modification/{modificationUuid}",
studyUuid, nodeUuid, modificationUuid)
.queryParam("sourceCompositeUuid", sourceCompositeUuid.toString())
.header(USER_ID_HEADER, userId))
.andExpect(status().isOk());
checkUpdateStatusMessagesReceived(studyUuid, nodeUuid, output);
checkEquipmentUpdatingMessagesReceived(studyUuid, nodeUuid);
checkEquipmentUpdatingFinishedMessagesReceived(studyUuid, nodeUuid);
checkElementUpdatedMessageSent(studyUuid, userId);
WireMockUtilsCriteria.verifyPutRequest(wireMockServer, moveSubModifUrlPattern, false, Map.of(
"sourceCompositeUuid", WireMock.equalTo(sourceCompositeUuid.toString())), null);
}

@Test
void testUpdateNetworkModificationsActivationExpandsCompositeToLeafUuids() throws Exception {
String userId = "userId";
StudyEntity studyEntity = insertDummyStudy(UUID.fromString(NETWORK_UUID_STRING), CASE_UUID, "UCTE");
UUID studyUuid = studyEntity.getId();
UUID rootNetworkUuid = studyTestUtils.getOneRootNetworkUuid(studyUuid);
UUID rootNodeUuid = getRootNode(studyUuid).getId();
NetworkModificationNode node = createNetworkModificationNode(studyUuid, rootNodeUuid,
UUID.randomUUID(), VARIANT_ID, "node 1", userId);
UUID nodeUuid = node.getId();

UUID compositeUuid = UUID.randomUUID();
UUID leafUuid1 = UUID.randomUUID();
UUID leafUuid2 = UUID.randomUUID();
Set<UUID> expandedLeafUuids = Set.of(leafUuid1, leafUuid2);

// Stub verifyModifications
wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(
"/v1/groups/" + node.getModificationGroupUuid() + "/network-modifications/verify"))
.willReturn(WireMock.ok()));

// Stub expandToLeafUuids
wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(
"/v1/network-composite-modifications/leaf-uuids"))
.willReturn(WireMock.ok()
.withBody(mapper.writeValueAsString(expandedLeafUuids))
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)));

// PUT .../network-modifications?activated=false&uuids=<compositeUuid>
mockMvc.perform(put("/v1/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/network-modifications",
studyUuid, rootNetworkUuid, nodeUuid)
.queryParam("activated", "false")
.queryParam("uuids", compositeUuid.toString())
.header(USER_ID_HEADER, userId))
.andExpect(status().isOk());

checkEquipmentUpdatingMessagesReceived(studyUuid, nodeUuid);
checkUpdateStatusMessagesReceived(studyUuid, nodeUuid, output);
checkEquipmentUpdatingFinishedMessagesReceived(studyUuid, nodeUuid);
checkElementUpdatedMessageSent(studyUuid, userId);

// Verify that verifyModifications was called with the composite UUID
WireMockUtilsCriteria.verifyGetRequest(wireMockServer,
"/v1/groups/" + node.getModificationGroupUuid() + "/network-modifications/verify",
Map.of("uuids", WireMock.equalTo(compositeUuid.toString())));

// Verify that expandToLeafUuids was called with the composite UUID
WireMockUtilsCriteria.verifyGetRequest(wireMockServer,
"/v1/network-composite-modifications/leaf-uuids",
Map.of("uuids", WireMock.equalTo(compositeUuid.toString())));

// Verify that the set passed to updateModificationsToExclude contains both
// the original composite UUID and the expanded leaf UUIDs
verify(rootNetworkNodeInfoService, times(1)).updateModificationsToExclude(
eq(nodeUuid),
eq(rootNetworkUuid),
argThat((Set<UUID> uuids) -> uuids.contains(compositeUuid)
&& uuids.containsAll(expandedLeafUuids)),
eq(false));
}

@AfterEach
void tearDown() {
studyRepository.findAll().forEach(s -> networkModificationTreeService.doDeleteTree(s.getId()));
Expand Down