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 @@ -497,14 +497,15 @@ public ResponseEntity<Void> duplicateCompositeNetworkModification(@PathVariable(
return ResponseEntity.ok().build();
}

@PutMapping(value = "/explore/elements/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
@PutMapping(value = "/explore/elements/{id}/types/{type}", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Modify an element")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The element has been modified successfully")})
@PreAuthorize("@authorizationService.canWrite(#id)")
public ResponseEntity<Void> updateElement(
@PathVariable UUID id,
@PathVariable String type,
@RequestBody ElementAttributes elementAttributes) {
exploreService.updateElement(id, elementAttributes);
exploreService.updateElement(id, type, elementAttributes);
return ResponseEntity.ok().build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
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.explore.server.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import java.util.UUID;

/**
* @author Etienne Lesot <etienne.lesot at rte-france.com>
*/
@Getter
@AllArgsConstructor
@Builder
@Schema(description = "Modification metadata")
public class ModificationMetadata {
@Schema(description = "Modification id")
private UUID id;

Comment on lines +25 to +26

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is good time to have exactly the same ModificationMetadata dto between netmod-server and here, even if the data is not useful yet :

Suggested change
private UUID id;
private UUID id;
@Schema(description = "Modification type")
private ModificationType type;

@Schema(description = "Modification name")
private String name;

@Schema(description = "Modification description")
private String description;
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class ExploreService {
static final String CASE = "CASE";
static final String CONTINGENCY_LIST = "CONTINGENCY_LIST";
static final String FILTER = "FILTER";
public static final String MODIFICATION = "MODIFICATION";
public static final String MODIFICATION = "MODIFICATION"; // only for composite modification
static final String DIRECTORY = "DIRECTORY";
static final String SPREADSHEET_CONFIG = "SPREADSHEET_CONFIG";
static final String SPREADSHEET_CONFIG_COLLECTION = "SPREADSHEET_CONFIG_COLLECTION";
Expand Down Expand Up @@ -423,23 +423,21 @@ public void notifyCasesThresholdReached(int userCasesCount, int userMaxAllowedSt
}
}

public void updateElement(UUID id, ElementAttributes elementAttributes) {
// The check to know if the user have the right to update the element is done in the directory-server
directoryService.updateElement(id, elementAttributes);
ElementAttributes elementsInfos = directoryService.getElementInfos(id);
notifyElementUpdated(elementsInfos);
public void updateElement(UUID id, String type, ElementAttributes updatedElementAttributes) {
if (type.equals(MODIFICATION)) {
networkModificationService.updateCompositeModificationMetadata(id, updatedElementAttributes.getElementName(), updatedElementAttributes.getDescription());
} else {
directoryService.updateElement(id, updatedElementAttributes);
ElementAttributes elementsInfos = directoryService.getElementInfos(id);
notifyElementUpdated(elementsInfos);
}
Comment thread
Mathieu-Deharbe marked this conversation as resolved.
}

private void notifyElementUpdated(ElementAttributes element) {
// send notification if the study name was updated
if (STUDY.equals(element.getType())) {
studyService.notifyStudyUpdate(element.getElementUuid());
}

// the composite modification name has to be updated in order to match the new element name
if (MODIFICATION.equals(element.getType())) {
networkModificationService.updateCompositeModification(element.getElementUuid(), element.getElementName());
}
}

private void notifyElementMoved(ElementAttributes element) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,18 @@ default List<ElementAttributes> completeElementAttribute(List<ElementAttributes>
item = metadataItem.getOrDefault("uuid", "");
}
ElementAttributes e = mapElementAttribute.get(item.toString());
return populateMedataItem(e, metadataItem);
return populateMetadataItem(e, metadataItem);
}).collect(Collectors.toList());
} catch (ResourceAccessException e) {
String elementType = lstElementAttribute.isEmpty() ? "UNKNOWN" : lstElementAttribute.getFirst().getType();
LOGGER.warn("{} metadata service is unavailable, returning elements with empty specific metadata", elementType);
return lstElementAttribute.stream()
.map(elementAttributes -> populateMedataItem(elementAttributes, Map.of()))
.map(elementAttributes -> populateMetadataItem(elementAttributes, Map.of()))
.collect(Collectors.toList());
}
}

private ElementAttributes populateMedataItem(ElementAttributes elementAttributes, Map<String, Object> metadataItem) {
default ElementAttributes populateMetadataItem(ElementAttributes elementAttributes, Map<String, Object> metadataItem) {
elementAttributes.setSpecificMetadata(metadataItem);
return elementAttributes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
package org.gridsuite.explore.server.services;

import lombok.Setter;
import org.gridsuite.explore.server.dto.ElementAttributes;
import org.gridsuite.explore.server.dto.ModificationMetadata;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
Expand All @@ -21,6 +24,8 @@
import java.util.UUID;
import java.util.stream.Collectors;

import static org.gridsuite.explore.server.ExploreConstants.HEADER_USER_ID;

/**
* @author David Braquart <david.braquart at rte-france.com>
*/
Expand All @@ -30,6 +35,7 @@ public class NetworkModificationService implements IDirectoryElementsService {
private static final String DELIMITER = "/";
public static final String UUIDS = "uuids";
public static final String NAME = "name";
public static final String DESCRIPTION = "description";
Comment thread
Mathieu-Deharbe marked this conversation as resolved.
public static final String NETWORK_COMPOSITE_MODIFICATIONS_PATH = "network-composite-modifications";
private static final String NETWORK_MODIFICATIONS_PATH = "network-modifications";
private static final String CONTAINERS_PATH = "containers";
Expand Down Expand Up @@ -75,19 +81,22 @@ public void replaceCompositeModification(UUID compositeModificationId, String ne

/**
* @param newName null if the name shouldn't be updated
* @param newDescription null if the description shouldn't be updated
*/
public void updateCompositeModification(UUID compositeModificationId, String newName) {
public void updateCompositeModificationMetadata(UUID compositeModificationId, String newName, String newDescription) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromPath(
DELIMITER + NETWORK_MODIFICATION_API_VERSION + DELIMITER + NETWORK_COMPOSITE_MODIFICATIONS_PATH + DELIMITER + compositeModificationId
DELIMITER + NETWORK_MODIFICATION_API_VERSION +
DELIMITER + NETWORK_MODIFICATIONS_PATH +
DELIMITER + compositeModificationId +
DELIMITER + "name-and-description"
);
if (newName != null) {
uriComponentsBuilder.queryParam(NAME, newName);
}

String path = uriComponentsBuilder.buildAndExpand().toUriString();
String userId = SecurityContextHolder.getContext().getAuthentication().getName();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
restTemplate.exchange(networkModificationServerBaseUri + path, HttpMethod.PUT, new HttpEntity<>(headers), Void.class);
headers.set(HEADER_USER_ID, userId);
ModificationMetadata modificationMetadata = new ModificationMetadata(compositeModificationId, newName, newDescription);
restTemplate.exchange(networkModificationServerBaseUri + path, HttpMethod.PUT, new HttpEntity<>(modificationMetadata, headers), Void.class);
}

@Override
Expand Down Expand Up @@ -128,4 +137,18 @@ public Map<UUID, List<Object>> getCompositeModificationContent(UUID compositeMod
new ParameterizedTypeReference<Map<UUID, List<Object>>>() {
}).getBody();
}

@Override
public ElementAttributes populateMetadataItem(ElementAttributes elementAttributes, Map<String, Object> metadataItem) {
if (metadataItem != null) {
if (metadataItem.containsKey(NAME) && metadataItem.get(NAME) != null) {
elementAttributes.setElementName(metadataItem.get(NAME).toString());
}
if (metadataItem.containsKey(DESCRIPTION) && metadataItem.get(DESCRIPTION) != null) {
elementAttributes.setDescription(metadataItem.get(DESCRIPTION).toString());
}
}
elementAttributes.setSpecificMetadata(metadataItem);
return elementAttributes;
}
}
39 changes: 34 additions & 5 deletions src/test/java/org/gridsuite/explore/server/ExploreTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ class ExploreTest {
private final Map<String, Object> specificMetadata2 = Map.of("equipmentType", "LINE", "id", FILTER_UUID_2);
private final Map<String, Object> caseSpecificMetadata = Map.of("uuid", CASE_UUID, "name", TEST_FILE, "format", "XIIDM");
private final Map<String, Object> modificationSpecificMetadata = Map.of("id", MODIFICATION_UUID, "type", "LOAD_MODIFICATION");
private final Map<String, Object> compositeModificationSpecificMetadata = Map.of(
"id", COMPOSITE_MODIFICATION_UUID,
"type", "COMPOSITE",
"name", "compositeName",
"description", "compositeDescription");
private final Map<UUID, List<Map<String, Object>>> compositeModificationMetadata = Map.of(COMPOSITE_MODIFICATION_UUID, List.of(
Map.of(
"uuid", MODIFICATION_UUID,
Expand Down Expand Up @@ -242,6 +247,8 @@ void setup(final MockWebServer server, TestInfo testInfo) throws Exception {
String caseInfosAttributesAsString = mapper.writeValueAsString(List.of(caseSpecificMetadata));
String modificationElementAttributesAsString = mapper.writeValueAsString(new ElementAttributes(MODIFICATION_UUID, "one modif", "MODIFICATION", USER1, 0L, null));
String modificationInfosAttributesAsString = mapper.writeValueAsString(List.of(modificationSpecificMetadata));
String compositeModificationSpecificMetadataAsString = mapper.writeValueAsString(List.of(compositeModificationSpecificMetadata));
String compositeModificationAsString = mapper.writeValueAsString(new ElementAttributes(COMPOSITE_MODIFICATION_UUID, "a composite", MODIFICATION, USER1, 0, null));
String compositeModificationIdAsString = mapper.writeValueAsString(MODIFICATION_UUID);
String newStudyUuidAsString = mapper.writeValueAsString(STUDY_COPY_UUID);
String newCaseUuidAsString = mapper.writeValueAsString(CASE_COPY_UUID);
Expand Down Expand Up @@ -329,6 +336,8 @@ public MockResponse dispatch(RecordedRequest request) {
return new MockResponse(200, Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), "[" + caseElementAttributesAsString + "]");
} else if (path.matches("/v1/elements\\?ids=" + MODIFICATION_UUID) && "GET".equals(request.getMethod())) {
return new MockResponse(200, Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), "[" + modificationElementAttributesAsString + "]");
} else if (path.matches("/v1/elements\\?ids=" + COMPOSITE_MODIFICATION_UUID) && "GET".equals(request.getMethod())) {
return new MockResponse(200, Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), "[" + compositeModificationAsString + "]");
} else if (path.matches("/v1/elements\\?ids=" + CONTINGENCY_LIST_METADATA_ERROR_UUID) && "GET".equals(request.getMethod())) {
return new MockResponse(200, Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE), "[" + contingencyListMetadataErrorAttributesAsString + "]");
} else if (path.matches("/v1/filters/metadata\\?ids=" + FILTER_UUID + "," + FILTER_UUID_2) && "GET".equals(request.getMethod())) {
Expand Down Expand Up @@ -413,6 +422,10 @@ public MockResponse dispatch(RecordedRequest request) {
return new MockResponse(200,
Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE),
modificationInfosAttributesAsString);
} else if (path.matches("/v1/network-modifications/metadata[?]ids=" + COMPOSITE_MODIFICATION_UUID)) {
return new MockResponse(200,
Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE),
compositeModificationSpecificMetadataAsString);
} else if (path.matches("/v1/containers/network-modifications[?]uuids=" + COMPOSITE_MODIFICATION_UUID + "&onlyMetadata=true")) {
return new MockResponse(200,
Headers.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE),
Expand Down Expand Up @@ -457,6 +470,8 @@ public MockResponse dispatch(RecordedRequest request) {
} else if (path.matches("/v1/directories/" + PARENT_DIRECTORY_UUID_FORBIDDEN + "/permissions") &&
USER_NOT_ALLOWED.equals(request.getHeaders().get(HEADER_USER_ID))) {
return new MockResponse(403);
} else if (path.matches("/v1/network-modifications/" + ELEMENT_COMPOSITE_UUID + "/name-and-description")) {
return new MockResponse(200);
}
} else if ("DELETE".equals(request.getMethod())) {
if (path.matches("/v1/filters/" + FILTER_UUID)) {
Expand Down Expand Up @@ -1162,8 +1177,8 @@ void testCaseAlertThreshold() throws Exception {
void testUpdateElement() throws Exception {
ElementAttributes elementAttributes = new ElementAttributes();
elementAttributes.setElementName(STUDY1);
mockMvc.perform(put("/v1/explore/elements/{id}",
ELEMENT_UUID)
mockMvc.perform(put("/v1/explore/elements/{id}/types/{type}",
ELEMENT_UUID, "STUDY")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(elementAttributes))
).andExpect(status().isOk());
Expand All @@ -1173,8 +1188,8 @@ void testUpdateElement() throws Exception {
void testUpdateCompositeName() throws Exception {
ElementAttributes elementAttributes = new ElementAttributes();
elementAttributes.setElementName("new Name");
mockMvc.perform(put("/v1/explore/elements/{id}",
ELEMENT_COMPOSITE_UUID)
mockMvc.perform(put("/v1/explore/elements/{id}/types/{type}",
ELEMENT_COMPOSITE_UUID, "MODIFICATION")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(elementAttributes))
).andExpect(status().isOk());
Expand All @@ -1196,7 +1211,8 @@ void testMoveElementsDirectory() throws Exception {
void testUpdateElementNotOk() throws Exception {
ElementAttributes elementAttributes = new ElementAttributes();
elementAttributes.setElementName(STUDY1);
mockMvc.perform(put("/v1/explore/elements/{id}", FORBIDDEN_ELEMENT_UUID)
mockMvc.perform(put("/v1/explore/elements/{id}/types/{type}",
FORBIDDEN_ELEMENT_UUID, "STUDY")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(elementAttributes))
).andExpect(status().isForbidden());
Expand Down Expand Up @@ -1345,4 +1361,17 @@ void testDeleteElementsFromDirectoryRevertsStatusWhenElementDeletionFails() thro
wireMockServer.verify(1, WireMock.deleteRequestedFor(WireMock.urlMatching("/v1/elements\\?ids=" + PRIVATE_STUDY_UUID + "&parentDirectoryUuid=.*")));
wireMockServer.verify(1, WireMock.putRequestedFor(WireMock.urlMatching("/v1/elements\\?ids=" + FILTER_UUID + "&status=CREATED")));
}

@Test
void testGetElementsMetadataForModification() throws Exception {
MvcResult result = mockMvc.perform(get("/v1/explore/elements/metadata?ids=" + COMPOSITE_MODIFICATION_UUID))
.andExpect(status().isOk())
.andReturn();
String res = result.getResponse().getContentAsString();
List<ElementAttributes> elementsMetadata = mapper.readValue(res, new TypeReference<>() { });
String compositeModificationAttributesAsString = mapper.writeValueAsString(new ElementAttributes(COMPOSITE_MODIFICATION_UUID,
"compositeName", "MODIFICATION", USER1, 0L, "compositeDescription", compositeModificationSpecificMetadata));
assertEquals(1, elementsMetadata.size());
assertEquals(mapper.writeValueAsString(elementsMetadata.getFirst()), compositeModificationAttributesAsString);
}
}
Loading