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 @@ -78,6 +78,18 @@ public ResponseEntity<Void> moveSubModification(
return ResponseEntity.ok().build();
}

@PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Merge some network modifications into a new composite modification")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The composite modification has been created")})
public ResponseEntity<UUID> mergeNetworkModificationsIntoNewComposite(
@RequestBody List<UUID> mergedModificationsUuids) {

return ResponseEntity.ok().body(
networkModificationService.mergeNetworkModificationsIntoNewComposite(
mergedModificationsUuids)
);
}

@PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Create a network composite modification")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The composite modification has been created")})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ SELECT CAST(sm.modification_id AS VARCHAR)
""", nativeQuery = true)
List<UUID> findModificationIdsByCompositeModificationId(UUID uuid);

// return the uuid of the composite containing the modification sent as parameter
@Query(value = """
SELECT CAST(sm.id AS VARCHAR)
FROM composite_modification_sub_modifications sm
INNER JOIN modification m ON sm.modification_id = m.id
WHERE sm.modification_id = :uuid
ORDER BY m.modifications_order
""", nativeQuery = true)
UUID findCompositeIdByContainedModificationId(UUID uuid);

@Query(value = """
SELECT CAST(sm.modification_id AS VARCHAR)
FROM composite_modification_sub_modifications sm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,75 @@ public List<ModificationInfos> insertCompositeModifications(
return newEntities.stream().map(ModificationEntity::toModificationInfos).toList();
}

@Transactional
public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite(
List<UUID> mergedModificationsUuids) {
// get the target (groupUuid or composite Uuid of the first merged modification + its index in this target)
UUID firstModifUuid = mergedModificationsUuids.getFirst();
ModificationEntity firstModificationEntity = getModificationEntity(firstModifUuid);
int targetIndex = firstModificationEntity.getModificationsOrder();
ModificationGroupEntity targetGroup = firstModificationEntity.getGroup();
CompositeModificationEntity targetComposite = null;
if (targetGroup == null) {
// the first modification is inside a composite
UUID targetCompositeUuid = modificationRepository.findCompositeIdByContainedModificationId(firstModifUuid);
targetComposite = compositeModificationRepository.findById(targetCompositeUuid).orElse(null);
}

// get all the modifications to be merged, remove previous assignment
List<ModificationEntity> mergedModifications = mergedModificationsUuids.stream()
.map(modificationRepository::findById).filter(Optional::isPresent).map(Optional::get).toList();
// remove previous assignments of the merged modifications
// 1. cleans and reorders the origin group if there is one :
ModificationGroupEntity originGroup = mergedModifications.stream()
.map(ModificationEntity::getGroup)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
if (originGroup != null) {
List<ModificationEntity> originGroupModifications = originGroup.getModifications();
originGroupModifications.removeIf(mod -> mergedModificationsUuids.contains(mod.getId()));
originGroup.setModifications(originGroupModifications);
mergedModifications.forEach(modificationEntity -> modificationEntity.setGroup(null));
}
// 2. cleans the composites whose submodifications are merged into a new one
for (ModificationEntity mergedModification : mergedModifications.stream().filter(mod -> mod.getGroup() == null).toList()) {
UUID compositeUuid = modificationRepository.findCompositeIdByContainedModificationId(mergedModification.getId());
if (compositeUuid != null) {
CompositeModificationEntity previousOwner = compositeModificationRepository.findById(compositeUuid).orElse(null);
if (previousOwner != null) {
List<ModificationEntity> modificationsLeft = previousOwner.getModifications()
.stream()
.filter(mod -> !mergedModificationsUuids.contains(mod.getId()))
.toList();
previousOwner.setModifications(modificationsLeft);
}
}
}

// create the new composite
CompositeModificationInfos newCompositeInfos = CompositeModificationInfos.builder()
.modificationsInfos(List.of())
.name("New composite modification")
.build();
CompositeModificationEntity newCompositeEntity = (CompositeModificationEntity) ModificationEntity.fromDTO(newCompositeInfos);
newCompositeEntity.setModificationsOrder(targetIndex);

// assign modifications
newCompositeEntity.setModifications(mergedModifications);
// put the new composite in the target group or composite
if (targetGroup != null) {
List<ModificationEntity> modifications = targetGroup.getModifications();
modifications.add(targetIndex, newCompositeEntity);
targetGroup.setModifications(modifications);
} else if (targetComposite != null) {
List<ModificationEntity> modifications = targetComposite.getModifications();
modifications.add(targetIndex, newCompositeEntity);
}

return modificationRepository.save(newCompositeEntity);
}

@Transactional
public void moveSubModification(@NonNull UUID groupUuid, UUID sourceCompositeUuid, UUID targetCompositeUuid,
@NonNull UUID modificationUuid, UUID beforeUuid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.gridsuite.filter.AbstractFilter;
import org.gridsuite.modification.ModificationType;
import org.gridsuite.modification.NetworkModificationException;
import org.gridsuite.modification.dto.CompositeModificationInfos;
import org.gridsuite.modification.dto.EquipmentModificationInfos;
import org.gridsuite.modification.dto.GenerationDispatchInfos;
import org.gridsuite.modification.dto.ModificationInfos;
Expand Down Expand Up @@ -474,6 +475,15 @@ public CompletableFuture<NetworkModificationsResult> insertCompositeModification
new NetworkModificationsResult(ids, result));
}

@Transactional
public UUID mergeNetworkModificationsIntoNewComposite(
@NonNull List<UUID> mergedModificationsUuids) {
CompositeModificationInfos newComposite =
networkModificationRepository.mergeNetworkModificationsIntoNewComposite(mergedModificationsUuids).toModificationInfos();

return newComposite.getUuid();
}

@Transactional
public UUID createNetworkCompositeModification(@NonNull List<UUID> modificationUuids) {
return networkModificationRepository.createNetworkCompositeModification(modificationUuids);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import org.gridsuite.modification.dto.ModificationInfos;
import org.gridsuite.modification.server.dto.NetworkModificationResult;
import org.gridsuite.modification.server.dto.NetworkModificationsResult;
import org.gridsuite.modification.server.entities.CompositeModificationEntity;
import org.gridsuite.modification.server.entities.ModificationEntity;
import org.gridsuite.modification.server.repositories.CompositeModificationRepository;
import org.gridsuite.modification.server.repositories.NetworkModificationRepository;
import org.gridsuite.modification.server.service.ReportService;
import org.gridsuite.modification.server.utils.NetworkCreation;
Expand All @@ -38,11 +40,7 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;

import static org.gridsuite.modification.ModificationType.COMPOSITE_MODIFICATION;
import static org.gridsuite.modification.server.utils.NetworkCreation.VARIANT_ID;
Expand Down Expand Up @@ -76,6 +74,9 @@ class CompositeControllerTest {
@Autowired
private NetworkModificationRepository modificationRepository;

@Autowired
private CompositeModificationRepository compositeRepository;

@MockitoBean
private ReportService reportService;

Expand Down Expand Up @@ -276,7 +277,9 @@ void testDuplicateCompositeModification() throws Exception {

Map<UUID, UUID> returnedMap = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { });
assertEquals(1, returnedMap.size());
Map.Entry<UUID, UUID> returnedIds = returnedMap.entrySet().stream().findFirst().get();
Optional<Map.Entry<UUID, UUID>> first = returnedMap.entrySet().stream().findFirst();
assertTrue(first.isPresent());
Map.Entry<UUID, UUID> returnedIds = first.get();
UUID returnedSourceId = returnedIds.getKey();
UUID returnedNewId = returnedIds.getValue();
assertNotEquals(returnedSourceId, returnedNewId);
Expand Down Expand Up @@ -484,6 +487,96 @@ void testMoveSubModificationFromCompositeToRoot() throws Exception {
assertNull(remainingEntity.getGroup());
}

@Test
void testMergeNetworkModificationsIntoNewComposite() throws Exception {
// Create 3 root-level modifications in the group
List<ModificationInfos> rootMods = createSomeSwitchModifications(TEST_GROUP_ID, 3);
List<UUID> rootModUuids = rootMods.stream().map(ModificationInfos::getUuid).toList();

assertEquals(3, modificationRepository.getModifications(TEST_GROUP_ID, true, true).size());

// ---- 1. Merge the first 2 root-level modifications into a new composite
List<UUID> mergedModificationUuids = rootModUuids.subList(0, 2);
MvcResult mvcResult = mockMvc.perform(post(URI_COMPOSITE_NETWORK_MODIF_BASE + "/composite-modification")
.content(mapper.writeValueAsString(mergedModificationUuids))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andReturn();

UUID firstCompositeUuid = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { });
assertNotNull(firstCompositeUuid);

// The root group should now contain the new composite and the remaining non-merged modification
List<ModificationInfos> rootModificationsAfterMerge = modificationRepository.getModifications(TEST_GROUP_ID, true, true);
assertEquals(2, rootModificationsAfterMerge.size());
assertEquals(firstCompositeUuid, rootModificationsAfterMerge.getFirst().getUuid());
assertEquals(COMPOSITE_MODIFICATION, rootModificationsAfterMerge.getFirst().getType());
assertEquals(rootModUuids.get(2), rootModificationsAfterMerge.get(1).getUuid());

// The new composite should contain the merged modifications in the same order
Map<UUID, List<ModificationInfos>> compositeContentMap = mapper.readValue(
mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}", firstCompositeUuid))
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString(),
new TypeReference<>() { });
List<ModificationInfos> compositeContent = compositeContentMap.get(firstCompositeUuid);

assertEquals(2, compositeContent.size());
assertEquals(rootModUuids.get(0), compositeContent.get(0).getUuid());
assertEquals(rootModUuids.get(1), compositeContent.get(1).getUuid());

// The new composite must belong to TEST_GROUP_ID at root level
CompositeModificationEntity firstComposite = compositeRepository.findById(firstCompositeUuid).orElseThrow();
assertNotNull(firstComposite.getGroup());
assertEquals(TEST_GROUP_ID, firstComposite.getGroup().getId());

// The merged modifications must no longer belong directly to the group
ModificationEntity firstMergedEntity = modificationRepository.getModificationEntity(rootModUuids.get(0));
ModificationEntity secondMergedEntity = modificationRepository.getModificationEntity(rootModUuids.get(1));
assertNull(firstMergedEntity.getGroup());
assertNull(secondMergedEntity.getGroup());

// The non-merged modification must still belong to TEST_GROUP_ID
ModificationEntity remainingEntity = modificationRepository.getModificationEntity(rootModUuids.get(2));
assertNotNull(remainingEntity.getGroup());
assertEquals(TEST_GROUP_ID, remainingEntity.getGroup().getId());

// ---- 2. now merges a modification which is inside a composite with something that is outside :
mergedModificationUuids = List.of(compositeContent.get(0).getUuid(), rootModUuids.get(2));
mvcResult = mockMvc.perform(post(URI_COMPOSITE_NETWORK_MODIF_BASE + "/composite-modification")
.content(mapper.writeValueAsString(mergedModificationUuids))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andReturn();

// this new composite will be generated inside the other composite because its first element was inside it
UUID twodepthCompositeUuid = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { });
assertNotNull(twodepthCompositeUuid);

// The root group should now contain the new composite and nothing else
rootModificationsAfterMerge = modificationRepository.getModifications(TEST_GROUP_ID, true, true);
assertEquals(1, rootModificationsAfterMerge.size());

// The first composite should contain the new composite, then the other untouched modification
compositeContentMap = mapper.readValue(
mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}", firstCompositeUuid))
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString(),
new TypeReference<>() { });
compositeContent = compositeContentMap.get(firstCompositeUuid);

assertEquals(2, compositeContent.size());
assertEquals(rootModUuids.get(1), compositeContent.get(0).getUuid());
assertEquals(twodepthCompositeUuid, compositeContent.get(1).getUuid());

// The new 2 depth composite must now belong to the first composite, not to a group
CompositeModificationEntity twoDepthComposite = compositeRepository.findById(twodepthCompositeUuid).orElseThrow();
assertNull(twoDepthComposite.getGroup());
compositeContentMap = mapper.readValue(
mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}", firstCompositeUuid))
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString(),
new TypeReference<>() { });
assertTrue(compositeContentMap.get(firstCompositeUuid).stream()
.map(ModificationInfos::getUuid)
.anyMatch(twodepthCompositeUuid::equals));
}

@Test
void testExpandToLeafUuidsNestedComposites() throws Exception {
// Build nested structure: outerComposite → [innerComposite → [leaf1, leaf2], leaf3]
Expand Down
Loading