Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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 composite modifications
}

private final NetworkModificationService networkModificationService;
Expand Down Expand Up @@ -89,17 +92,26 @@ public ResponseEntity<Map<UUID, UUID>> 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<ResponseEntity<NetworkModificationsResult>> 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<UUID>, List<ModificationApplicationContext>> modificationContextInfos) {
public CompletableFuture<ResponseEntity<NetworkModificationsResult>> 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<String> compositeNames,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be added to modificationContextInfos ? Like do you think it would be possible to replace List from the Pair to a list of records/dto containing UUID and their coresponding compositeName ?
compositeName would be optional to keep the system working for copying non composite modifications

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. I don't think any of those ways is satisfying because it will be useful only for INSERT_COMPOSITE. At least here the other action types can completely ignore that parameter, no structure has to be changed. But I don't like it either.
What do you think @SlimaneAmar ?

@RequestBody Pair<List<UUID>, List<ModificationApplicationContext>> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import lombok.Setter;
import org.gridsuite.modification.dto.CompositeModificationInfos;
import org.gridsuite.modification.dto.ModificationInfos;
import org.hibernate.annotations.ColumnDefault;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -27,6 +28,10 @@
@Table(name = "composite_modification")
public class CompositeModificationEntity extends ModificationEntity {

@Column(name = "composite_name")
@ColumnDefault("'My Composite'")
private String compositeName;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinTable(
name = "compositeModificationSubModifications",
Expand All @@ -44,6 +49,7 @@ public CompositeModificationEntity(@NonNull CompositeModificationInfos composite
public CompositeModificationInfos toModificationInfos() {
List<ModificationInfos> modificationsInfos = modifications.stream().map(ModificationEntity::toModificationInfos).toList();
return CompositeModificationInfos.builder()
.compositeName(getCompositeName())
.activated(getActivated())
.description(getDescription())
.date(getDate())
Expand All @@ -56,6 +62,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ public UUID createNetworkCompositeModification(@NonNull List<UUID> modificationU
return modificationRepository.save(compositeEntity).getId();
}

public CompositeModificationInfos cloneCompositeModification(@NonNull UUID compositeModificationUuid, String name) {
CompositeModificationInfos newCompositeInfos = CompositeModificationInfos.builder().modifications(List.of()).build();
List<ModificationInfos> copiedModifications = getCompositeModificationsInfosNonTransactional(List.of(compositeModificationUuid)).stream()
.toList();
newCompositeInfos.setModifications(copiedModifications);
newCompositeInfos.setCompositeName(name);
return newCompositeInfos;
}

public void updateCompositeModification(@NonNull UUID compositeUuid, @NonNull List<UUID> modificationUuids) {
ModificationEntity modificationEntity = modificationRepository.findById(compositeUuid)
.orElseThrow(() -> new NetworkModificationException(MODIFICATION_NOT_FOUND, String.format(MODIFICATION_NOT_FOUND_MESSAGE, compositeUuid)));
Expand Down Expand Up @@ -765,4 +774,18 @@ public List<ModificationInfos> 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<ModificationInfos> insertCompositeModificationsIntoGroup(
@NonNull UUID targetGroupUuid,
@NonNull List<UUID> compositeModificationsUuids,
@NonNull List<String> compositeNames) {
List<ModificationInfos> newCompositeModifications = new ArrayList<>();
for (int i = 0; i < compositeModificationsUuids.size(); i++) {
CompositeModificationInfos newCompositeModification = cloneCompositeModification(compositeModificationsUuids.get(i), compositeNames.get(i));
newCompositeModifications.add(newCompositeModification);
}
List<ModificationEntity> newEntities = saveModificationInfosNonTransactional(targetGroupUuid, newCompositeModifications);
return newEntities.stream().map(ModificationEntity::toModificationInfos).toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,23 @@ public CompletableFuture<NetworkModificationsResult> duplicateModifications(@Non
new NetworkModificationsResult(ids, result));
}

public CompletableFuture<NetworkModificationsResult> insertCompositeModifications(@NonNull UUID targetGroupUuid, @NonNull List<UUID> modificationsUuids, @NonNull List<ModificationApplicationContext> applicationContexts) {
List<ModificationInfos> modifications = networkModificationRepository.saveCompositeModifications(targetGroupUuid, modificationsUuids);
/**
* all their network modifications are extracted from the composite modifications and inserted into the group
*/
public CompletableFuture<NetworkModificationsResult> splitCompositeModifications(@NonNull UUID targetGroupUuid, @NonNull List<UUID> compositeModificationsUuids, @NonNull List<ModificationApplicationContext> applicationContexts) {
List<ModificationInfos> modifications = networkModificationRepository.saveCompositeModifications(targetGroupUuid, compositeModificationsUuids);
List<UUID> ids = modifications.stream().map(ModificationInfos::getUuid).toList();
return applyModifications(targetGroupUuid, modifications, applicationContexts).thenApply(result ->
new NetworkModificationsResult(ids, result));
}

public CompletableFuture<NetworkModificationsResult> insertCompositeModificationsIntoGroup(
@NonNull UUID targetGroupUuid,
@NonNull List<UUID> compositeModificationUuids,
@NonNull List<ModificationApplicationContext> applicationContexts,
@NonNull List<String> compositeNames) {
List<ModificationInfos> modifications = networkModificationRepository.insertCompositeModificationsIntoGroup(
targetGroupUuid, compositeModificationUuids, compositeNames);
List<UUID> ids = modifications.stream().map(ModificationInfos::getUuid).toList();
return applyModifications(targetGroupUuid, modifications, applicationContexts).thenApply(result ->
new NetworkModificationsResult(ids, result));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="deharbemat (generated)" id="1770384121049-15">
<addColumn tableName="composite_modification">
<column defaultValue="My Composite" name="composite_name" type="varchar(255)"/>
</addColumn>
Comment on lines 3 to 6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no issue if it hasn't a default name for preexisting composite modifications ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how it could happen but I can add one just in case. Like "My composite" ?

The thing is that this name is useless for now and can be useful ony if the composite is inserted into gridstudy, and then it gets a name. How I see it : the front interface will use the name of the composite in grid-explore (its element name) as a default value and then call the endpoint with it.

But you are right : better to add one just in case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this : ? 9786107

</changeSet>
</databaseChangeLog>
3 changes: 3 additions & 0 deletions src/main/resources/db/changelog/db.changelog-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,6 @@ databaseChangeLog:
- include:
file: changesets/changelog_20251215T152152Z.xml
relativeToChangelogFile: true
- include:
file: changesets/changelog_20260206T132143Z.xml
relativeToChangelogFile: true
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -861,6 +858,27 @@ void testNetworkCompositeModification() throws Exception {
List<UUID> 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<ModificationInfos> 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());
}

/**
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
Loading