diff --git a/src/main/java/org/gridsuite/modification/server/NetworkModificationController.java b/src/main/java/org/gridsuite/modification/server/NetworkModificationController.java index bc0503889..10f61d94b 100644 --- a/src/main/java/org/gridsuite/modification/server/NetworkModificationController.java +++ b/src/main/java/org/gridsuite/modification/server/NetworkModificationController.java @@ -37,7 +37,10 @@ public class NetworkModificationController { private enum GroupModificationAction { - MOVE, COPY, INSERT + MOVE, + COPY, + SPLIT_COMPOSITE, // the network modifications contained into the composite modifications are extracted and inserted one by one + INSERT_COMPOSITE // the composite modifications are fully inserted as composote modifications } private final NetworkModificationService networkModificationService; @@ -89,17 +92,26 @@ public ResponseEntity> duplicateGroup(@RequestParam("groupUuid") @PutMapping(value = "/groups/{groupUuid}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "For a list of network modifications passed in body, Move them before another one or at the end of the list, or Duplicate them at the end of the list, or Insert them (composite) at the end of the list") @ApiResponse(responseCode = "200", description = "The modification list of the group has been updated.") - public CompletableFuture> handleNetworkModifications(@Parameter(description = "updated group UUID, where modifications are pasted") @PathVariable("groupUuid") UUID targetGroupUuid, - @Parameter(description = "kind of modification", required = true) @RequestParam(value = "action") GroupModificationAction action, - @Parameter(description = "the modification Uuid to move before (MOVE option, empty means moving at the end)") @RequestParam(value = "before", required = false) UUID beforeModificationUuid, - @Parameter(description = "origin group UUID, where modifications are copied or cut") @RequestParam(value = "originGroupUuid", required = false) UUID originGroupUuid, - @Parameter(description = "modifications can be applied (default is true)") @RequestParam(value = "build", required = false, defaultValue = "true") Boolean canApply, - @RequestBody Pair, List> modificationContextInfos) { + public CompletableFuture> handleNetworkModifications( + @Parameter(description = "updated group UUID, where modifications are pasted") @PathVariable("groupUuid") UUID targetGroupUuid, + @Parameter(description = "kind of modification", required = true) @RequestParam(value = "action") GroupModificationAction action, + @Parameter(description = "the modification Uuid to move before (MOVE option, empty means moving at the end)") @RequestParam(value = "before", required = false) UUID beforeModificationUuid, + @Parameter(description = "origin group UUID, where modifications are copied or cut") @RequestParam(value = "originGroupUuid", required = false) UUID originGroupUuid, + @Parameter(description = "modifications can be applied (default is true)") @RequestParam(value = "build", required = false, defaultValue = "true") Boolean canApply, + @Parameter(description = "composite modifications names") @RequestParam(value = "compositeNames", required = false) List compositeNames, + @RequestBody Pair, List> modificationContextInfos) { return switch (action) { case COPY -> networkModificationService.duplicateModifications(targetGroupUuid, originGroupUuid, modificationContextInfos.getFirst(), modificationContextInfos.getSecond()).thenApply(ResponseEntity.ok()::body); - case INSERT -> - networkModificationService.insertCompositeModifications(targetGroupUuid, modificationContextInfos.getFirst(), modificationContextInfos.getSecond()).thenApply(ResponseEntity.ok()::body); + case SPLIT_COMPOSITE -> + networkModificationService.splitCompositeModifications(targetGroupUuid, modificationContextInfos.getFirst(), modificationContextInfos.getSecond()).thenApply(ResponseEntity.ok()::body); + case INSERT_COMPOSITE -> + networkModificationService.insertCompositeModificationsIntoGroup( + targetGroupUuid, + modificationContextInfos.getFirst(), + modificationContextInfos.getSecond(), + compositeNames + ).thenApply(ResponseEntity.ok()::body); case MOVE -> { UUID sourceGroupUuid = originGroupUuid == null ? targetGroupUuid : originGroupUuid; boolean applyModifications = canApply; diff --git a/src/main/java/org/gridsuite/modification/server/entities/CompositeModificationEntity.java b/src/main/java/org/gridsuite/modification/server/entities/CompositeModificationEntity.java index ecab41a0d..09370f8a7 100644 --- a/src/main/java/org/gridsuite/modification/server/entities/CompositeModificationEntity.java +++ b/src/main/java/org/gridsuite/modification/server/entities/CompositeModificationEntity.java @@ -27,6 +27,9 @@ @Table(name = "composite_modification") public class CompositeModificationEntity extends ModificationEntity { + @Column(name = "composite_name") + private String compositeName; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinTable( name = "compositeModificationSubModifications", @@ -44,6 +47,7 @@ public CompositeModificationEntity(@NonNull CompositeModificationInfos composite public CompositeModificationInfos toModificationInfos() { List modificationsInfos = modifications.stream().map(ModificationEntity::toModificationInfos).toList(); return CompositeModificationInfos.builder() + .compositeName(getCompositeName()) .activated(getActivated()) .description(getDescription()) .date(getDate()) @@ -56,6 +60,7 @@ public CompositeModificationInfos toModificationInfos() { // when we go back to an empty list, dont use addAll() on the list because JPA could start // @OrderColumn to 1 instead of 0 private void assignAttributes(CompositeModificationInfos compositeModificationInfos) { + this.setCompositeName(compositeModificationInfos.getCompositeName()); modifications.clear(); modifications = compositeModificationInfos.getModifications().stream() .map(ModificationEntity::fromDTO) diff --git a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java index 57c201a97..f9abd9daf 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java @@ -139,6 +139,17 @@ public UUID createNetworkCompositeModification(@NonNull List modificationU return modificationRepository.save(compositeEntity).getId(); } + public UUID cloneCompositeModification(@NonNull UUID compositeModificationUuid, String name) { + CompositeModificationInfos newCompositeInfos = CompositeModificationInfos.builder().modifications(List.of()).build(); + CompositeModificationEntity newCompositeEntity = (CompositeModificationEntity) ModificationEntity.fromDTO(newCompositeInfos); + List copiedModifications = getCompositeModificationsInfosNonTransactional(List.of(compositeModificationUuid)).stream() + .map(ModificationEntity::fromDTO) + .toList(); + newCompositeEntity.setModifications(copiedModifications); + newCompositeEntity.setCompositeName(name); + return modificationRepository.save(newCompositeEntity).getId(); + } + public void updateCompositeModification(@NonNull UUID compositeUuid, @NonNull List modificationUuids) { ModificationEntity modificationEntity = modificationRepository.findById(compositeUuid) .orElseThrow(() -> new NetworkModificationException(MODIFICATION_NOT_FOUND, String.format(MODIFICATION_NOT_FOUND_MESSAGE, compositeUuid))); @@ -765,4 +776,21 @@ public List saveCompositeModifications(@NonNull UUID targetGr // We can't return modificationInfos directly because it wouldn't have the IDs coming from the new saved entities return newEntities.stream().map(ModificationEntity::toModificationInfos).toList(); } + + @Transactional + public List insertCompositeModificationsIntoGroup( + @NonNull UUID targetGroupUuid, + @NonNull List compositeModificationsUuids, + @NonNull List compositeNames) { + List newCompositeModifications = new ArrayList<>(); + for (int i = 0; i < Math.min(compositeNames.size(), compositeModificationsUuids.size()); i++) { + UUID newCompositeModificationUuid = cloneCompositeModification(compositeModificationsUuids.get(i), compositeNames.get(i)); // ici c'est fait orderonné par modification order plutôt que UUID + newCompositeModifications.add((CompositeModificationEntity) getModificationEntity(newCompositeModificationUuid)); + } + List modifs = newCompositeModifications.stream() + .map(modif -> (ModificationInfos) modif.toModificationInfos()) + .toList(); + List newEntities = saveModificationInfosNonTransactional(targetGroupUuid, modifs); + return newEntities.stream().map(ModificationEntity::toModificationInfos).toList(); + } } diff --git a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java index d0487ea21..415717415 100644 --- a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java @@ -397,13 +397,29 @@ public CompletableFuture duplicateModifications(@Non new NetworkModificationsResult(ids, result)); } - public CompletableFuture insertCompositeModifications(@NonNull UUID targetGroupUuid, @NonNull List modificationsUuids, @NonNull List applicationContexts) { + /** + * all their network modifications are extracted from the composite modifications and inserted into the group + * @param modificationsUuids uuids of the composite modifications + */ + public CompletableFuture splitCompositeModifications(@NonNull UUID targetGroupUuid, @NonNull List modificationsUuids, @NonNull List applicationContexts) { List modifications = networkModificationRepository.saveCompositeModifications(targetGroupUuid, modificationsUuids); List ids = modifications.stream().map(ModificationInfos::getUuid).toList(); return applyModifications(targetGroupUuid, modifications, applicationContexts).thenApply(result -> new NetworkModificationsResult(ids, result)); } + public CompletableFuture insertCompositeModificationsIntoGroup( + @NonNull UUID targetGroupUuid, + @NonNull List compositeModificationUuids, + @NonNull List applicationContexts, + @NonNull List compositeNames) { + List modifications = networkModificationRepository.insertCompositeModificationsIntoGroup( + targetGroupUuid, compositeModificationUuids, compositeNames); + List ids = modifications.stream().map(ModificationInfos::getUuid).toList(); + return applyModifications(targetGroupUuid, modifications, applicationContexts).thenApply(result -> + new NetworkModificationsResult(ids, result)); + } + @Transactional public UUID createNetworkCompositeModification(@NonNull List modificationUuids) { return networkModificationRepository.createNetworkCompositeModification(modificationUuids); diff --git a/src/main/resources/db/changelog/changesets/changelog_20260206T132143Z.xml b/src/main/resources/db/changelog/changesets/changelog_20260206T132143Z.xml new file mode 100644 index 000000000..cf909ac87 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20260206T132143Z.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 22bc992fa..e6ba17e34 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -453,3 +453,6 @@ databaseChangeLog: - include: file: changesets/changelog_20251215T152152Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20260206T132143Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/modification/server/ModificationControllerTest.java b/src/test/java/org/gridsuite/modification/server/ModificationControllerTest.java index 4ec138b38..346c06d59 100644 --- a/src/test/java/org/gridsuite/modification/server/ModificationControllerTest.java +++ b/src/test/java/org/gridsuite/modification/server/ModificationControllerTest.java @@ -845,14 +845,11 @@ void testNetworkCompositeModification() throws Exception { mvcResult = mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}&onlyMetadata=false", compositeModificationUuid)) .andExpect(status().isOk()).andReturn(); compositeModificationContent = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { }); - assertEquals("open", ((EquipmentAttributeModificationInfos) compositeModificationContent.get(0)).getEquipmentAttributeName()); - assertEquals(Boolean.TRUE, ((EquipmentAttributeModificationInfos) compositeModificationContent.get(0)).getEquipmentAttributeValue()); - assertEquals(IdentifiableType.SWITCH, ((EquipmentAttributeModificationInfos) compositeModificationContent.get(0)).getEquipmentType()); - assertEquals("v1b1", ((EquipmentAttributeModificationInfos) compositeModificationContent.get(0)).getEquipmentId()); + checkCompositeModificationContent(compositeModificationContent); // Insert the composite modification in the group - String bodyJson = getJsonBody(List.of(compositeModificationUuid), NetworkCreation.VARIANT_ID); - mvcResult = runRequestAsync(mockMvc, put("/v1/groups/" + TEST_GROUP_ID + "?action=INSERT").content(bodyJson).contentType(MediaType.APPLICATION_JSON), status().isOk()); + final String bodyJson = getJsonBody(List.of(compositeModificationUuid), NetworkCreation.VARIANT_ID); + mvcResult = runRequestAsync(mockMvc, put("/v1/groups/" + TEST_GROUP_ID + "?action=SPLIT_COMPOSITE").content(bodyJson).contentType(MediaType.APPLICATION_JSON), status().isOk()); assertApplicationStatusOK(mvcResult); @@ -861,6 +858,27 @@ void testNetworkCompositeModification() throws Exception { List newModificationUuidList = newModificationList.stream().map(ModificationInfos::getUuid).toList(); assertEquals(modificationUuids.get(0), newModificationUuidList.get(0)); assertThat(modificationList.get(0)).recursivelyEquals(newModificationList.get(modificationsNumber)); + + // insert the same composite modification inside the same group but this time as a complete composite, not split into regular network modifications + mvcResult = runRequestAsync( + mockMvc, + put("/v1/groups/" + TEST_GROUP_ID + "?action=INSERT_COMPOSITE&compositeName='random name'") + .content(bodyJson).contentType(MediaType.APPLICATION_JSON), status().isOk() + ); + assertApplicationStatusOK(mvcResult); + newModificationList = modificationRepository.getModifications(TEST_GROUP_ID, false, true); + assertEquals(modificationsNumber * 2 + 1, newModificationList.size()); + CompositeModificationInfos insertedComposite = (CompositeModificationInfos) newModificationList.stream().filter(modificationInfos -> + modificationInfos.getType().equals(COMPOSITE_MODIFICATION)).findFirst().orElseThrow(); + assertNotNull(insertedComposite); + checkCompositeModificationContent(insertedComposite.getModifications()); + } + + private static void checkCompositeModificationContent(List compositeModificationContent) { + assertEquals("open", ((EquipmentAttributeModificationInfos) compositeModificationContent.getFirst()).getEquipmentAttributeName()); + assertEquals(Boolean.TRUE, ((EquipmentAttributeModificationInfos) compositeModificationContent.getFirst()).getEquipmentAttributeValue()); + assertEquals(IdentifiableType.SWITCH, ((EquipmentAttributeModificationInfos) compositeModificationContent.getFirst()).getEquipmentType()); + assertEquals("v1b1", ((EquipmentAttributeModificationInfos) compositeModificationContent.getFirst()).getEquipmentId()); } /** @@ -903,13 +921,10 @@ void testNetworkCompositeModificationOld() throws Exception { mvcResult = mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}&onlyMetadata=false", compositeModificationUuid)) .andExpect(status().isOk()).andReturn(); compositeModificationContent = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { }); - assertEquals("open", ((EquipmentAttributeModificationInfos) compositeModificationContent.get(0)).getEquipmentAttributeName()); - assertEquals(Boolean.TRUE, ((EquipmentAttributeModificationInfos) compositeModificationContent.get(0)).getEquipmentAttributeValue()); - assertEquals(IdentifiableType.SWITCH, ((EquipmentAttributeModificationInfos) compositeModificationContent.get(0)).getEquipmentType()); - assertEquals("v1b1", ((EquipmentAttributeModificationInfos) compositeModificationContent.get(0)).getEquipmentId()); + checkCompositeModificationContent(compositeModificationContent); String bodyJson = getJsonBody(List.of(compositeModificationUuid), NetworkCreation.VARIANT_ID); // Insert the composite modification in the group - mvcResult = runRequestAsync(mockMvc, put("/v1/groups/" + TEST_GROUP_ID + "?action=INSERT").content(bodyJson).contentType(MediaType.APPLICATION_JSON), status().isOk()); + mvcResult = runRequestAsync(mockMvc, put("/v1/groups/" + TEST_GROUP_ID + "?action=SPLIT_COMPOSITE").content(bodyJson).contentType(MediaType.APPLICATION_JSON), status().isOk()); assertApplicationStatusOK(mvcResult); diff --git a/src/test/java/org/gridsuite/modification/server/service/ModificationIndexationTest.java b/src/test/java/org/gridsuite/modification/server/service/ModificationIndexationTest.java index 9b8e88385..b69352209 100644 --- a/src/test/java/org/gridsuite/modification/server/service/ModificationIndexationTest.java +++ b/src/test/java/org/gridsuite/modification/server/service/ModificationIndexationTest.java @@ -304,10 +304,10 @@ void testInsertCompositeModifications() { ((NetworkImpl) networkInfos.getNetwork()).getListeners().clear(); /* - Insert as composite this modification to group 2, variant 2 + Split this composite and insert the contained modifications to group 2, variant 2 */ UUID groupUuid2 = UUID.randomUUID(); - NetworkModificationsResult modificationsResult = networkModificationService.insertCompositeModifications( + NetworkModificationsResult modificationsResult = networkModificationService.splitCompositeModifications( groupUuid2, List.of(compositeUuid), List.of(new ModificationApplicationContext(networkInfos.getNetworkUuuid(), variant2, UUID.randomUUID(), UUID.randomUUID()))