Skip to content
Merged
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
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@
<properties>

<gridsuite-dependencies.version>43.0.0</gridsuite-dependencies.version>
<gridsuite-filter.version>1.12.0</gridsuite-filter.version>
<liquibase-hibernate-package>org.gridsuite.study.server</liquibase-hibernate-package>
<mockwebserver3.version>5.0.0-alpha.14</mockwebserver3.version>
<db-util.version>1.0.5</db-util.version>
<db-util.version>1.0.5</db-util.version>
<sonar.organization>gridsuite</sonar.organization>
<sonar.projectKey>org.gridsuite:study-server</sonar.projectKey>
</properties>
Expand Down Expand Up @@ -144,6 +145,11 @@
<groupId>com.powsybl</groupId>
<artifactId>powsybl-ws-commons</artifactId>
</dependency>
<dependency>
<groupId>org.gridsuite</groupId>
<artifactId>gridsuite-filter</artifactId>
<version>${gridsuite-filter.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ private StudyConstants() {
public static final String QUERY_PARAM_VARIANT_ID = "variantId";
public static final String QUERY_PARAM_NETWORK_UUID = "networkUuid";
public static final String QUERY_PARAM_EQUIPMENT_TYPE = "equipmentType";
public static final String QUERY_PARAM_EQUIPMENT_TYPES = "equipmentTypes";
public static final String QUERY_PARAM_ELEMENT_TYPE = "elementType";
public static final String QUERY_PARAM_SIDE = "side";
public static final String QUERY_PARAM_NOMINAL_VOLTAGES = "nominalVoltages";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.gridsuite.filter.globalfilter.GlobalFilter;
import org.gridsuite.filter.utils.EquipmentType;
import org.gridsuite.study.server.StudyApi;
import org.gridsuite.study.server.StudyConstants.ModificationsActionType;
import org.gridsuite.study.server.StudyConstants.SldDisplayMode;
Expand Down Expand Up @@ -54,6 +56,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
Expand Down Expand Up @@ -2256,6 +2259,20 @@ public ResponseEntity<String> evaluateFilter(
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(studyService.evaluateFilter(nodeUuid, rootNetworkUuid, inUpstreamBuiltParentNode, filter));
}

@PostMapping(value = "/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/global-filter/evaluate",
produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Evaluate a global filter to get matched elements")
@ApiResponse(responseCode = "200", description = "The list of matched elements")
public ResponseEntity<List<String>> evaluateGlobalFilter(
@Parameter(description = "Study uuid") @PathVariable("studyUuid") UUID studyUuid,
@Parameter(description = "Root network uuid") @PathVariable("rootNetworkUuid") UUID rootNetworkUuid,
@Parameter(description = "Node uuid") @PathVariable("nodeUuid") UUID nodeUuid,
@Parameter(description = "The equipments types to filter and return") @RequestParam(name = "equipmentTypes") @NonNull final List<EquipmentType> equipmentTypes,
@RequestBody @NonNull GlobalFilter filter) {
this.studyService.assertIsRootNetworkAndNodeInStudy(studyUuid, rootNetworkUuid, nodeUuid);
return ResponseEntity.ok(studyService.evaluateGlobalFilter(nodeUuid, rootNetworkUuid, equipmentTypes, filter));
}

@GetMapping(value = "/studies/{studyUuid}/root-networks/{rootNetworkUuid}/filters/{filterUuid}/elements")
@Operation(summary = "Evaluate a filter on root node to get matched elements")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The list of matched elements")})
Expand Down
65 changes: 45 additions & 20 deletions src/main/java/org/gridsuite/study/server/service/FilterService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@

package org.gridsuite.study.server.service;

import lombok.Getter;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.gridsuite.filter.globalfilter.GlobalFilter;
import org.gridsuite.filter.utils.EquipmentType;
import org.gridsuite.study.server.RemoteServicesProperties;
import org.gridsuite.study.server.StudyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;
Expand All @@ -25,6 +29,10 @@

import static org.gridsuite.study.server.StudyConstants.DELIMITER;
import static org.gridsuite.study.server.StudyConstants.FILTER_API_VERSION;
import static org.gridsuite.study.server.StudyConstants.IDS;
import static org.gridsuite.study.server.StudyConstants.NETWORK_UUID;
import static org.gridsuite.study.server.StudyConstants.QUERY_PARAM_EQUIPMENT_TYPES;
import static org.gridsuite.study.server.StudyConstants.QUERY_PARAM_VARIANT_ID;
import static org.gridsuite.study.server.StudyException.Type.EVALUATE_FILTER_FAILED;
import static org.gridsuite.study.server.StudyException.Type.NETWORK_NOT_FOUND;
import static org.gridsuite.study.server.utils.StudyUtils.handleHttpError;
Expand All @@ -42,13 +50,9 @@ public class FilterService {

private final RestTemplate restTemplate;

@Getter // getter to facilitate to mock
private final String baseUri;

// getter to facilitate to mock
public String getBaseUri() {
return baseUri;
}

@Autowired
public FilterService(RemoteServicesProperties remoteServicesProperties, RestTemplate restTemplate) {
this.baseUri = remoteServicesProperties.getServiceUri("filter-server");
Expand All @@ -60,12 +64,11 @@ public String evaluateFilter(UUID networkUuid, String variantId, String filter)
String endPointUrl = getBaseUri() + DELIMITER + FILTER_API_VERSION + FILTER_END_POINT_EVALUATE;

UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(endPointUrl);
uriComponentsBuilder.queryParam("networkUuid", networkUuid);
if (variantId != null && !variantId.isBlank()) {
uriComponentsBuilder.queryParam("variantId", variantId);
uriComponentsBuilder.queryParam(NETWORK_UUID, networkUuid);
if (!StringUtils.isBlank(variantId)) {
uriComponentsBuilder.queryParam(QUERY_PARAM_VARIANT_ID, variantId);
}
var uriComponent = uriComponentsBuilder
.build();
var uriComponent = uriComponentsBuilder.build();

var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Expand All @@ -83,13 +86,35 @@ public String evaluateFilter(UUID networkUuid, String variantId, String filter)
}
}

public List<String> evaluateGlobalFilter(@NonNull final UUID networkUuid, @NonNull final String variantId,
@NonNull final List<EquipmentType> equipmentTypes, @NonNull final GlobalFilter filter) {
final UriComponents uriComponent = UriComponentsBuilder.fromHttpUrl(getBaseUri())
.pathSegment(FILTER_API_VERSION, "global-filter")
.queryParam(NETWORK_UUID, networkUuid)
.queryParam(QUERY_PARAM_VARIANT_ID, variantId)
.queryParam(QUERY_PARAM_EQUIPMENT_TYPES, equipmentTypes)
.build();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
try {
return restTemplate.exchange(uriComponent.toUri(), HttpMethod.POST, new HttpEntity<>(filter, headers), new ParameterizedTypeReference<List<String>>() { })
.getBody();
} catch (final HttpStatusCodeException ex) {
if (HttpStatus.NOT_FOUND.equals(ex.getStatusCode())) {
throw new StudyException(NETWORK_NOT_FOUND);
} else {
throw handleHttpError(ex, EVALUATE_FILTER_FAILED);
}
}
}

public String exportFilter(UUID networkUuid, UUID filterUuid) {
Objects.requireNonNull(networkUuid);
Objects.requireNonNull(filterUuid);
String endPointUrl = getBaseUri() + DELIMITER + FILTER_API_VERSION + FILTER_END_POINT_EXPORT;

UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(endPointUrl);
uriComponentsBuilder.queryParam("networkUuid", networkUuid);
uriComponentsBuilder.queryParam(NETWORK_UUID, networkUuid);
var uriComponent = uriComponentsBuilder.buildAndExpand(filterUuid);

return restTemplate.getForObject(uriComponent.toUriString(), String.class);
Expand All @@ -101,11 +126,11 @@ public String exportFilters(UUID networkUuid, List<UUID> filtersUuid, String var
String endPointUrl = getBaseUri() + DELIMITER + FILTER_API_VERSION + FILTERS_END_POINT_EXPORT;

UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(endPointUrl);
uriComponentsBuilder.queryParam("networkUuid", networkUuid);
if (variantId != null && !variantId.isBlank()) {
uriComponentsBuilder.queryParam("variantId", variantId);
uriComponentsBuilder.queryParam(NETWORK_UUID, networkUuid);
if (!StringUtils.isBlank(variantId)) {
uriComponentsBuilder.queryParam(QUERY_PARAM_VARIANT_ID, variantId);
}
uriComponentsBuilder.queryParam("ids", filtersUuid);
uriComponentsBuilder.queryParam(IDS, filtersUuid);
var uriComponent = uriComponentsBuilder.buildAndExpand();

return restTemplate.getForObject(uriComponent.toUriString(), String.class);
Expand All @@ -117,7 +142,7 @@ public String evaluateFilters(UUID networkUuid, String filters) {
String endPointUrl = getBaseUri() + DELIMITER + FILTER_API_VERSION + FILTER_END_POINT_EVALUATE_IDS;

UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(endPointUrl);
uriComponentsBuilder.queryParam("networkUuid", networkUuid);
uriComponentsBuilder.queryParam(NETWORK_UUID, networkUuid);
var uriComponent = uriComponentsBuilder.buildAndExpand();

var headers = new HttpHeaders();
Expand Down
21 changes: 21 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 @@ -14,6 +14,8 @@
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import org.apache.commons.collections4.CollectionUtils;
import org.gridsuite.filter.globalfilter.GlobalFilter;
import org.gridsuite.filter.utils.EquipmentType;
import org.gridsuite.study.server.StudyConstants;
import org.gridsuite.study.server.StudyException;
import org.gridsuite.study.server.dto.*;
Expand Down Expand Up @@ -296,6 +298,14 @@ public BasicStudyInfos createStudy(UUID caseUuid, String userId, UUID studyUuid,
return basicStudyInfos;
}

@Transactional(readOnly = true)
public void assertIsRootNetworkAndNodeInStudy(@NonNull final UUID studyUuid, @NonNull final UUID rootNetworkId, @NonNull final UUID nodeUuid) {
this.rootNetworkService.assertIsRootNetworkInStudy(studyUuid, rootNetworkId);
if (!studyUuid.equals(this.networkModificationTreeService.getStudyUuidForNodeId(nodeUuid))) {
throw new StudyException(NODE_NOT_FOUND);
}
}

@Transactional
public void deleteRootNetworks(UUID studyUuid, List<UUID> rootNetworksUuids) {
assertIsStudyExist(studyUuid);
Expand Down Expand Up @@ -3202,6 +3212,17 @@ public String evaluateFilter(UUID nodeUuid, UUID rootNetworkUuid, boolean inUpst
return filterService.evaluateFilter(rootNetworkService.getNetworkUuid(rootNetworkUuid), networkModificationTreeService.getVariantId(nodeUuidToSearchIn, rootNetworkUuid), filter);
}

@Transactional(readOnly = true)
public List<String> evaluateGlobalFilter(@NonNull final UUID nodeUuid, @NonNull final UUID rootNetworkUuid,
@NonNull final List<EquipmentType> equipmentTypes, @NonNull final GlobalFilter filter) {
return filterService.evaluateGlobalFilter(
rootNetworkService.getNetworkUuid(rootNetworkUuid),
networkModificationTreeService.getVariantId(getNodeUuidToSearchIn(nodeUuid, rootNetworkUuid, true), rootNetworkUuid),
equipmentTypes,
filter
);
}

@Transactional(readOnly = true)
public String exportFilter(UUID rootNetworkUuid, UUID filterUuid) {
return filterService.exportFilter(rootNetworkService.getNetworkUuid(rootNetworkUuid), filterUuid);
Expand Down
61 changes: 61 additions & 0 deletions src/test/java/org/gridsuite/study/server/FilterServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.powsybl.commons.exceptions.UncheckedInterruptedException;
import org.gridsuite.filter.utils.EquipmentType;
import org.gridsuite.study.server.networkmodificationtree.dto.InsertMode;
import org.gridsuite.study.server.networkmodificationtree.dto.NetworkModificationNode;
import org.gridsuite.study.server.networkmodificationtree.dto.RootNode;
import org.gridsuite.study.server.repository.StudyEntity;
import org.gridsuite.study.server.repository.StudyRepository;
Expand Down Expand Up @@ -38,6 +41,7 @@
import java.util.stream.Collectors;

import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.gridsuite.study.server.utils.TestUtils.createModificationNodeInfo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
Expand All @@ -58,6 +62,7 @@ class FilterServiceTest {
private static final String FILTER_UUID_STRING = "c6c15d08-81e9-47a1-9cdb-7be22f017ad5";
private static final List<String> FILTERS_UUID_STRING = List.of("fc3aa057-5fa4-4173-b1a8-16028f5eefd1", "f8773f32-f77c-4126-8c6f-a4af8bf6f788");
private static final UUID CASE_UUID = UUID.fromString(CASE_UUID_STRING);
private static final String NODE_1_NAME = "node1";

@Autowired
private MockMvc mockMvc;
Expand Down Expand Up @@ -126,6 +131,7 @@ void testEvaluateFilter() throws Exception {
UUID studyNameUserIdUuid = studyEntity.getId();
UUID firstRootNetworkUuid = studyTestUtils.getOneRootNetworkUuid(studyNameUserIdUuid);
UUID rootNodeUuid = getRootNode(studyNameUserIdUuid).getId();
NetworkModificationNode firstNode = networkModificationTreeService.createNode(studyEntity, rootNodeUuid, createModificationNodeInfo(NODE_1_NAME), InsertMode.AFTER, null);

// whatever string is allowed but given here a json string for more expressive
final String sendBody = """
Expand Down Expand Up @@ -165,6 +171,49 @@ void testEvaluateFilter() throws Exception {
assertEquals(responseBody, resultAsString);

wireMockUtils.verifyFilterEvaluate(stubUuid, NETWORK_UUID_STRING);

// evaluate on first node
mvcResult = mockMvc.perform(post("/v1/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/filters/evaluate",
studyNameUserIdUuid, firstRootNetworkUuid, firstNode.getId())
.content(sendBody).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
resultAsString = mvcResult.getResponse().getContentAsString();
assertEquals(responseBody, resultAsString);

wireMockUtils.verifyFilterEvaluate(stubUuid, NETWORK_UUID_STRING);
}

@Test
void testEvaluateGlobalFilter() throws Exception {
StudyEntity studyEntity = insertDummyStudy(UUID.fromString(NETWORK_UUID_STRING), CASE_UUID);
UUID studyNameUserIdUuid = studyEntity.getId();
UUID firstRootNetworkUuid = studyTestUtils.getOneRootNetworkUuid(studyNameUserIdUuid);
UUID rootNodeUuid = getRootNode(studyNameUserIdUuid).getId();

// whatever string is allowed but given here a json string for more expressive
final String sendBody = """
{
"nominalV" : ["380", "225"],
"countryCode": ["FR", "BE"],
"genericFilter": ["c6c15d08-81e9-47a1-9cdb-7be22f017ad5"],
"substationProperty": {}
}
""";

String responseBody = "[\"GEN\"]";

UUID stubUuid = wireMockUtils.stubGlobalFilterEvaluate(NETWORK_UUID_STRING, List.of(EquipmentType.GENERATOR), responseBody);

MvcResult mvcResult = mockMvc.perform(post("/v1/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/global-filter/evaluate?equipmentTypes=GENERATOR",
studyNameUserIdUuid, firstRootNetworkUuid, rootNodeUuid)
.content(sendBody).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
String resultAsString = mvcResult.getResponse().getContentAsString();
assertEquals(responseBody, resultAsString);

wireMockUtils.verifyGlobalFilterEvaluate(stubUuid, NETWORK_UUID_STRING, List.of(EquipmentType.GENERATOR));
}

@Test
Expand Down Expand Up @@ -330,6 +379,8 @@ void testExportFilters() throws Exception {
UUID firstRootNetworkUuid = studyTestUtils.getOneRootNetworkUuid(studyEntity.getId());
UUID studyUuid = studyEntity.getId();
UUID rootNodeUuid = getRootNode(studyUuid).getId();
NetworkModificationNode firstNode = networkModificationTreeService.createNode(studyEntity, rootNodeUuid, createModificationNodeInfo(NODE_1_NAME), InsertMode.AFTER, null);

String responseBody = """
[
{
Expand Down Expand Up @@ -371,5 +422,15 @@ void testExportFilters() throws Exception {
assertEquals(responseBody, resultAsString);

wireMockUtils.verifyFiltersExport(stubUuid, FILTERS_UUID_STRING, NETWORK_UUID_STRING);

// export on first node
mvcResult = mockMvc.perform(get("/v1/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/filters/elements?filtersUuid=" + FILTERS_UUID_STRING.stream().collect(Collectors.joining(",")),
studyUuid, firstRootNetworkUuid, firstNode.getId()))
.andExpect(status().isOk())
.andReturn();
resultAsString = mvcResult.getResponse().getContentAsString();
assertEquals(responseBody, resultAsString);

wireMockUtils.verifyFiltersExport(stubUuid, FILTERS_UUID_STRING, NETWORK_UUID_STRING);
}
}
Loading