From 807207c10e62ee9de2ea6ea9e41127edb798290d Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 17 Nov 2023 10:25:19 +0100 Subject: [PATCH 01/17] Dynamic Simulation - Curve Configuration - Add some expert filter's criteria for Load and Generator --- .../utils/expertfilter/ExpertFilterUtils.java | 25 ++++++++++++------- .../server/utils/expertfilter/FieldType.java | 3 ++- .../filter/server/ExpertFilterUtilsTest.java | 14 ++++++++--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/ExpertFilterUtils.java index bed023e4..e5c39915 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/ExpertFilterUtils.java @@ -25,22 +25,28 @@ public static > String getFieldValue(FieldType field, }; } + private static String getInjectionFieldValue(FieldType field, Injection injection) { + return switch (field) { + case ID -> injection.getId(); + case NAME -> injection.getNameOrId(); + case COUNTRY -> { + Optional country = injection.getTerminal().getVoltageLevel().getSubstation().flatMap(Substation::getCountry); + yield country.isPresent() ? String.valueOf(country.get()) : ""; + } + case NOMINAL_VOLTAGE -> String.valueOf(injection.getTerminal().getVoltageLevel().getNominalV()); + case VOLTAGE_LEVEL_ID -> injection.getTerminal().getVoltageLevel().getId(); + default -> throw new PowsyblException("Field " + field + " with " + injection.getType() + " injection type is not implemented with expert filter"); + }; + } + private static String getLoadFieldValue(FieldType field, Load load) { return switch (field) { - case ID -> load.getId(); - default -> throw new PowsyblException("Field " + field + " with " + load.getType() + " injection type is not implemented with expert filter"); + default -> getInjectionFieldValue(field, load); }; } private static String getGeneratorFieldValue(FieldType field, Generator generator) { return switch (field) { - case ID -> generator.getId(); - case NAME -> generator.getNameOrId(); - case NOMINAL_VOLTAGE -> String.valueOf(generator.getTerminal().getVoltageLevel().getNominalV()); - case COUNTRY -> { - Optional country = generator.getTerminal().getVoltageLevel().getSubstation().flatMap(Substation::getCountry); - yield country.isPresent() ? String.valueOf(country.get()) : ""; - } case ENERGY_SOURCE -> String.valueOf(generator.getEnergySource()); case MIN_P -> String.valueOf(generator.getMinP()); case MAX_P -> String.valueOf(generator.getMaxP()); @@ -48,6 +54,7 @@ private static String getGeneratorFieldValue(FieldType field, Generator generato case TARGET_P -> String.valueOf(generator.getTargetP()); case TARGET_Q -> String.valueOf(generator.getTargetQ()); case VOLTAGE_REGULATOR_ON -> String.valueOf(generator.isVoltageRegulatorOn()); + default -> getInjectionFieldValue(field, generator); }; } } diff --git a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/FieldType.java b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/FieldType.java index b61f466f..88206081 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/FieldType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/FieldType.java @@ -20,5 +20,6 @@ public enum FieldType { TARGET_Q, ENERGY_SOURCE, COUNTRY, - VOLTAGE_REGULATOR_ON + VOLTAGE_REGULATOR_ON, + VOLTAGE_LEVEL_ID } diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java index 22f5915d..889426cc 100644 --- a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -6,9 +6,7 @@ */ package org.gridsuite.filter.server; -import com.powsybl.iidm.network.EnergySource; -import com.powsybl.iidm.network.Generator; -import com.powsybl.iidm.network.IdentifiableType; +import com.powsybl.iidm.network.*; import org.gridsuite.filter.server.dto.expertfilter.expertrule.*; import org.gridsuite.filter.server.utils.expertfilter.CombinatorType; import org.gridsuite.filter.server.utils.expertfilter.FieldType; @@ -29,6 +27,12 @@ public class ExpertFilterUtilsTest { @Before public void setUp() { + VoltageLevel voltageLevel = Mockito.mock(VoltageLevel.class); + Mockito.when(voltageLevel.getId()).thenReturn("GEN_01"); + + Terminal terminal = Mockito.mock(Terminal.class); + Mockito.when(terminal.getVoltageLevel()).thenReturn(voltageLevel); + gen = Mockito.mock(Generator.class); Mockito.when(gen.getType()).thenReturn(IdentifiableType.GENERATOR); Mockito.when(gen.getMinP()).thenReturn(-500.0); @@ -38,6 +42,7 @@ public void setUp() { Mockito.when(gen.getNameOrId()).thenReturn("NAME"); Mockito.when(gen.getEnergySource()).thenReturn(EnergySource.HYDRO); Mockito.when(gen.isVoltageRegulatorOn()).thenReturn(true); + Mockito.when(gen.getTerminal()).thenReturn(terminal); } @Test @@ -74,6 +79,9 @@ public void testEvaluateExpertFilterWithANDCombination() { BooleanExpertRule booleanRule7 = BooleanExpertRule.builder().value(false) .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.NOT_EQUALS).build(); andRules2.add(booleanRule7); + StringExpertRule stringRule8 = StringExpertRule.builder().value("GEN_01") + .field(FieldType.VOLTAGE_LEVEL_ID).operator(OperatorType.IS).build(); + andRules2.add(stringRule8); CombinatorExpertRule andFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules2).build(); From eeb112e7049258e40a3545cc30f6b0a5acbd4178 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 17 Nov 2023 11:25:27 +0100 Subject: [PATCH 02/17] Introduce end point to evaluate a non persisted filter --- .../filter/server/FilterController.java | 18 ++++++++++++++++++ .../gridsuite/filter/server/FilterService.java | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/org/gridsuite/filter/server/FilterController.java b/src/main/java/org/gridsuite/filter/server/FilterController.java index cf9eb1ec..c6f41fd3 100644 --- a/src/main/java/org/gridsuite/filter/server/FilterController.java +++ b/src/main/java/org/gridsuite/filter/server/FilterController.java @@ -17,9 +17,11 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import jakarta.persistence.EntityNotFoundException; + import java.util.List; import java.util.Optional; import java.util.UUID; @@ -158,4 +160,20 @@ public ResponseEntity> exportFilters(@RequestParam("ids") .contentType(MediaType.APPLICATION_JSON) .body(ret); } + + @PostMapping(value = "/filters/evaluate", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Export matched elements to JSON format") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "The list of matched elements"), + @ApiResponse(responseCode = "204", description = "No matched element found") + }) + public ResponseEntity> evaluateFilter(@RequestParam(value = "networkUuid") UUID networkUuid, + @RequestParam(value = "variantId", required = false) String variantId, + @RequestBody AbstractFilter filter) { + List identifiableAttributes = service.evaluateFilter(filter, networkUuid, variantId); + return CollectionUtils.isEmpty(identifiableAttributes) ? ResponseEntity.noContent().build() : + ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(identifiableAttributes); + } } diff --git a/src/main/java/org/gridsuite/filter/server/FilterService.java b/src/main/java/org/gridsuite/filter/server/FilterService.java index 47da3c76..8b3bbb4e 100644 --- a/src/main/java/org/gridsuite/filter/server/FilterService.java +++ b/src/main/java/org/gridsuite/filter/server/FilterService.java @@ -685,6 +685,11 @@ private List getIdentifiableAttributes(AbstractFilter fi } } + public List evaluateFilter(AbstractFilter filter, UUID networkUuid, String variantId) { + Objects.requireNonNull(filter); + return getIdentifiableAttributes(filter, networkUuid, variantId); + } + public Optional> exportFilter(UUID id, UUID networkUuid, String variantId) { Objects.requireNonNull(id); return getFilter(id).map(filter -> getIdentifiableAttributes(filter, networkUuid, variantId)); From 6de045fc805b2e12d25cb1db33346d7b1eba2257 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 17 Nov 2023 11:35:49 +0100 Subject: [PATCH 03/17] Style check --- .../java/org/gridsuite/filter/server/FilterController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/FilterController.java b/src/main/java/org/gridsuite/filter/server/FilterController.java index c6f41fd3..22c00d3c 100644 --- a/src/main/java/org/gridsuite/filter/server/FilterController.java +++ b/src/main/java/org/gridsuite/filter/server/FilterController.java @@ -164,8 +164,8 @@ public ResponseEntity> exportFilters(@RequestParam("ids") @PostMapping(value = "/filters/evaluate", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Export matched elements to JSON format") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "The list of matched elements"), - @ApiResponse(responseCode = "204", description = "No matched element found") + @ApiResponse(responseCode = "200", description = "The list of matched elements"), + @ApiResponse(responseCode = "204", description = "No matched element found") }) public ResponseEntity> evaluateFilter(@RequestParam(value = "networkUuid") UUID networkUuid, @RequestParam(value = "variantId", required = false) String variantId, From dc38bfba2e7ca6f89a5ef64c499d9b6993f8c2b1 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 17 Nov 2023 14:39:15 +0100 Subject: [PATCH 04/17] Enrich unit test for end point --- .../server/FilterEntityControllerTest.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java index ee223509..59379be0 100644 --- a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java +++ b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java @@ -1049,6 +1049,15 @@ private void checkExpertFilterExportAndMetadata(UUID filterId, String expectedJs mvc.perform(delete(URL_TEMPLATE + "/" + filterId)).andExpect(status().isOk()); } + private void checkFilterEvaluating(AbstractFilter filter, String expectedJson) throws Exception { + mvc.perform(post(URL_TEMPLATE + "/evaluate?networkUuid=" + NETWORK_UUID) + .content(objectMapper.writeValueAsString(filter)) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + private AbstractFilter insertFilter(UUID filterId, AbstractFilter filter) throws Exception { String response = mvc.perform(post(URL_TEMPLATE).param("id", filterId.toString()) .content(objectMapper.writeValueAsString(filter)) @@ -1527,9 +1536,13 @@ public void testExpertGeneratorFilter() throws Exception { ExpertFilter expertFilter = new ExpertFilter(filterId, modificationDate, EquipmentType.GENERATOR, andCombination); insertFilter(filterId, expertFilter); checkExpertFilter(filterId, expertFilter); - checkExpertFilterExportAndMetadata(filterId, """ + + // check result when evaluating a filter on a network + String expectedResultJson = """ [{"id":"GEN","type":"GENERATOR"}] - """, EquipmentType.GENERATOR); + """; + checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + checkFilterEvaluating(expertFilter, expectedResultJson); } @Test @@ -1548,9 +1561,13 @@ public void testExpertLoadFilter() throws Exception { ExpertFilter expertFilter = new ExpertFilter(filterId, modificationDate, EquipmentType.LOAD, gen1); insertFilter(filterId, expertFilter); checkExpertFilter(filterId, expertFilter); - checkExpertFilterExportAndMetadata(filterId, """ + + // check result when evaluating a filter on a network + String expectedResultJson = """ [{"id":"LOAD","type":"LOAD"}] - """, EquipmentType.LOAD); + """; + checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.LOAD); + checkFilterEvaluating(expertFilter, expectedResultJson); } @Test From 5445b6d88f87e875e7b20746d8cc5652d551b1e4 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 17 Nov 2023 15:30:38 +0100 Subject: [PATCH 05/17] Remove No Content 204 Http code when evaluate filter --- .../org/gridsuite/filter/server/FilterController.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/FilterController.java b/src/main/java/org/gridsuite/filter/server/FilterController.java index 22c00d3c..258eca7f 100644 --- a/src/main/java/org/gridsuite/filter/server/FilterController.java +++ b/src/main/java/org/gridsuite/filter/server/FilterController.java @@ -164,16 +164,12 @@ public ResponseEntity> exportFilters(@RequestParam("ids") @PostMapping(value = "/filters/evaluate", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Export matched elements to JSON format") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "The list of matched elements"), - @ApiResponse(responseCode = "204", description = "No matched element found") + @ApiResponse(responseCode = "200", description = "The list of matched elements") }) public ResponseEntity> evaluateFilter(@RequestParam(value = "networkUuid") UUID networkUuid, @RequestParam(value = "variantId", required = false) String variantId, @RequestBody AbstractFilter filter) { List identifiableAttributes = service.evaluateFilter(filter, networkUuid, variantId); - return CollectionUtils.isEmpty(identifiableAttributes) ? ResponseEntity.noContent().build() : - ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(identifiableAttributes); + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(identifiableAttributes); } } From f122589637458b0ac87a3988d50f5e7d1af33281 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 17 Nov 2023 15:38:07 +0100 Subject: [PATCH 06/17] checkstyle --- src/main/java/org/gridsuite/filter/server/FilterController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/gridsuite/filter/server/FilterController.java b/src/main/java/org/gridsuite/filter/server/FilterController.java index 258eca7f..df989c97 100644 --- a/src/main/java/org/gridsuite/filter/server/FilterController.java +++ b/src/main/java/org/gridsuite/filter/server/FilterController.java @@ -17,7 +17,6 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import jakarta.persistence.EntityNotFoundException; From 50028a1cba2d7a64857f9af626d02628677e888e Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 24 Nov 2023 13:53:11 +0100 Subject: [PATCH 07/17] Support IN and NOT_IN operator for type String and ENUM --- .../expertrule/EnumExpertRule.java | 18 ++++++++++++++++++ .../expertrule/StringExpertRule.java | 18 ++++++++++++++++++ .../utils/expertfilter/OperatorType.java | 2 ++ 3 files changed, 38 insertions(+) diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java index 88088abb..4708ee9e 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java @@ -16,6 +16,10 @@ import lombok.experimental.SuperBuilder; import org.gridsuite.filter.server.utils.expertfilter.DataType; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; /** @@ -30,6 +34,18 @@ public class EnumExpertRule extends AbstractExpertRule { @Schema(description = "Value") private String value; + // a derived set of values in case multiple + private Set values; + + private Set getValues() { + // lazy initialization + if (values == null) { + values = Stream.of(this.value.trim().split(",")) + .collect(Collectors.toSet()); + } + return values; + } + @Override public String getStringValue() { return getValue(); @@ -47,6 +63,8 @@ public boolean evaluateRule(Identifiable identifiable) { return switch (this.getOperator()) { case EQUALS -> identifiableValue.equals(this.getValue()); case NOT_EQUALS -> !identifiableValue.equals(this.getValue()); + case IN -> this.getValues().contains(identifiableValue); + case NOT_IN -> !this.getValues().contains(identifiableValue); default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java index a965fe83..3ad1cf23 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java @@ -17,6 +17,10 @@ import org.apache.commons.lang3.StringUtils; import org.gridsuite.filter.server.utils.expertfilter.DataType; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; /** @@ -31,6 +35,18 @@ public class StringExpertRule extends AbstractExpertRule { @Schema(description = "Value") private String value; + // a derived set of values in case multiple + private Set values; + + private Set getValues() { + // lazy initialization + if (values == null) { + values = Stream.of(this.value.trim().split(",")) + .collect(Collectors.toSet()); + } + return values; + } + @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public DataType getDataType() { @@ -45,6 +61,8 @@ public boolean evaluateRule(Identifiable identifiable) { case CONTAINS -> StringUtils.containsIgnoreCase(identifiableValue, this.getValue()); case BEGINS_WITH -> StringUtils.startsWithIgnoreCase(identifiableValue, this.getValue()); case ENDS_WITH -> StringUtils.endsWithIgnoreCase(identifiableValue, this.getValue()); + case IN -> this.getValues().contains(identifiableValue); + case NOT_IN -> !this.getValues().contains(identifiableValue); default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } diff --git a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java index 492c79f0..e3678661 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java @@ -23,4 +23,6 @@ public enum OperatorType { CONTAINS, BEGINS_WITH, ENDS_WITH, + IN, + NOT_IN } From 012bd35040a7b971b26583cad10466eb452fc7ff Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 24 Nov 2023 14:34:45 +0100 Subject: [PATCH 08/17] Use values multiple --- .../expertfilter/expertrule/EnumExpertRule.java | 16 ++++------------ .../expertrule/NumberExpertRule.java | 10 ++++++++++ .../expertrule/StringExpertRule.java | 16 ++++------------ 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java index 4708ee9e..7b5f7225 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java @@ -7,6 +7,7 @@ package org.gridsuite.filter.server.dto.expertfilter.expertrule; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; import io.swagger.v3.oas.annotations.media.Schema; @@ -16,9 +17,8 @@ import lombok.experimental.SuperBuilder; import org.gridsuite.filter.server.utils.expertfilter.DataType; +import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; @@ -34,18 +34,10 @@ public class EnumExpertRule extends AbstractExpertRule { @Schema(description = "Value") private String value; - // a derived set of values in case multiple + @Schema(description = "Values") + @JsonDeserialize(as = HashSet.class) private Set values; - private Set getValues() { - // lazy initialization - if (values == null) { - values = Stream.of(this.value.trim().split(",")) - .collect(Collectors.toSet()); - } - return values; - } - @Override public String getStringValue() { return getValue(); diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java index 26c83406..1437ad19 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java @@ -7,6 +7,7 @@ package org.gridsuite.filter.server.dto.expertfilter.expertrule; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; import io.swagger.v3.oas.annotations.media.Schema; @@ -16,6 +17,9 @@ import lombok.experimental.SuperBuilder; import org.gridsuite.filter.server.utils.expertfilter.DataType; +import java.util.HashSet; +import java.util.Set; + import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; /** @@ -30,6 +34,10 @@ public class NumberExpertRule extends AbstractExpertRule { @Schema(description = "Value") private Double value; + @Schema(description = "Values") + @JsonDeserialize(as = HashSet.class) + private Set values; + public static Double getNumberValue(String value) { return Double.parseDouble(value); } @@ -50,6 +58,8 @@ public boolean evaluateRule(Identifiable identifiable) { case GREATER -> identifiableValue.compareTo(filterValue) > 0; case LOWER_OR_EQUALS -> identifiableValue.compareTo(filterValue) <= 0; case LOWER -> identifiableValue.compareTo(filterValue) < 0; + case IN -> this.getValues().contains(identifiableValue); + case NOT_IN -> !this.getValues().contains(identifiableValue); default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java index 3ad1cf23..07639928 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java @@ -7,6 +7,7 @@ package org.gridsuite.filter.server.dto.expertfilter.expertrule; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; import io.swagger.v3.oas.annotations.media.Schema; @@ -17,9 +18,8 @@ import org.apache.commons.lang3.StringUtils; import org.gridsuite.filter.server.utils.expertfilter.DataType; +import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; @@ -35,18 +35,10 @@ public class StringExpertRule extends AbstractExpertRule { @Schema(description = "Value") private String value; - // a derived set of values in case multiple + @Schema(description = "Values") + @JsonDeserialize(as = HashSet.class) private Set values; - private Set getValues() { - // lazy initialization - if (values == null) { - values = Stream.of(this.value.trim().split(",")) - .collect(Collectors.toSet()); - } - return values; - } - @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public DataType getDataType() { From d1590c3a737e53e2bd9097002a9cff656fa5e9fb Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 24 Nov 2023 18:13:10 +0100 Subject: [PATCH 09/17] Correct persist for multiple values --- .../expertrule/EnumExpertRule.java | 2 +- .../expertrule/NumberExpertRule.java | 5 +- .../expertrule/StringExpertRule.java | 2 +- .../ExpertFilterRepositoryProxy.java | 47 ++++++++++++++----- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java index 7b5f7225..e893dbcc 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java @@ -40,7 +40,7 @@ public class EnumExpertRule extends AbstractExpertRule { @Override public String getStringValue() { - return getValue(); + return getValue() != null ? getValue() : String.join(",", getValues()); } @Override diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java index 1437ad19..a6b497b4 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; @@ -66,6 +67,8 @@ public boolean evaluateRule(Identifiable identifiable) { @Override public String getStringValue() { - return String.valueOf(this.getValue()); + return this.getValue() != null ? + String.valueOf(this.getValue()) : + values.stream().map(String::valueOf).collect(Collectors.joining(",")); } } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java index 07639928..5afa9465 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java @@ -61,6 +61,6 @@ public boolean evaluateRule(Identifiable identifiable) { @Override public String getStringValue() { - return getValue(); + return getValue() != null ? getValue() : String.join(",", getValues()); } } diff --git a/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java b/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java index 896dac12..19cd88ea 100644 --- a/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java +++ b/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java @@ -19,11 +19,13 @@ import org.gridsuite.filter.server.repositories.proxies.AbstractFilterRepositoryProxy; import org.gridsuite.filter.server.utils.EquipmentType; import org.gridsuite.filter.server.utils.FilterType; +import org.gridsuite.filter.server.utils.expertfilter.OperatorType; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Antoine Bouhours @@ -66,25 +68,46 @@ public static AbstractExpertRule entityToDto(ExpertRuleEntity filterEntity) { .build(); } case NUMBER -> { - return NumberExpertRule.builder() + NumberExpertRule.NumberExpertRuleBuilder ruleBuilder = NumberExpertRule.builder() .field(filterEntity.getField()) - .operator(filterEntity.getOperator()) - .value(Double.valueOf(filterEntity.getValue())) - .build(); + .operator(filterEntity.getOperator()); + if(filterEntity.getOperator() == OperatorType.IN || + filterEntity.getOperator() == OperatorType.NOT_IN) + { + ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).map(Double::valueOf).collect(Collectors.toSet())); + } + else { + ruleBuilder.value(Double.valueOf(filterEntity.getValue())); + } + return ruleBuilder.build(); } case STRING -> { - return StringExpertRule.builder() + StringExpertRule.StringExpertRuleBuilder ruleBuilder = StringExpertRule.builder() .field(filterEntity.getField()) - .operator(filterEntity.getOperator()) - .value(filterEntity.getValue()) - .build(); + .operator(filterEntity.getOperator()); + if(filterEntity.getOperator() == OperatorType.IN || + filterEntity.getOperator() == OperatorType.NOT_IN) + { + ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).collect(Collectors.toSet())); + } + else { + ruleBuilder.value(filterEntity.getValue()); + } + return ruleBuilder.build(); } case ENUM -> { - return EnumExpertRule.builder() + EnumExpertRule.EnumExpertRuleBuilder ruleBuilder = EnumExpertRule.builder() .field(filterEntity.getField()) - .operator(filterEntity.getOperator()) - .value(filterEntity.getValue()) - .build(); + .operator(filterEntity.getOperator()); + if(filterEntity.getOperator() == OperatorType.IN || + filterEntity.getOperator() == OperatorType.NOT_IN) + { + ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).collect(Collectors.toSet())); + } + else { + ruleBuilder.value(filterEntity.getValue()); + } + return ruleBuilder.build(); } default -> throw new PowsyblException("Unknown rule data type: " + filterEntity.getDataType() + ", supported data types are: COMBINATOR, BOOLEAN, NUMBER, STRING, ENUM"); } From c6f824f84d44398e6ad0f80012b80daee7c5af43 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Fri, 24 Nov 2023 18:17:30 +0100 Subject: [PATCH 10/17] Change order in OperatorType --- .../filter/server/utils/expertfilter/OperatorType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java index e3678661..3a071531 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java @@ -13,6 +13,8 @@ public enum OperatorType { // Common EQUALS, NOT_EQUALS, + IN, + NOT_IN, // Number LOWER, LOWER_OR_EQUALS, @@ -23,6 +25,4 @@ public enum OperatorType { CONTAINS, BEGINS_WITH, ENDS_WITH, - IN, - NOT_IN } From 7c644d85b2cd718623628679612924de1af7d87f Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 28 Nov 2023 13:19:03 +0100 Subject: [PATCH 11/17] refacto code --- .../expertrule/EnumExpertRule.java | 7 ++- .../expertrule/NumberExpertRule.java | 9 ++-- .../expertrule/StringExpertRule.java | 15 ++++--- .../ExpertFilterRepositoryProxy.java | 43 +++++++++---------- .../utils/expertfilter/OperatorType.java | 7 ++- 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java index e893dbcc..258085a2 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java @@ -21,6 +21,7 @@ import java.util.Set; import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; +import static org.gridsuite.filter.server.utils.expertfilter.OperatorType.isMultipleCriteriaOperator; /** * @author Antoine Bouhours @@ -40,7 +41,11 @@ public class EnumExpertRule extends AbstractExpertRule { @Override public String getStringValue() { - return getValue() != null ? getValue() : String.join(",", getValues()); + if (isMultipleCriteriaOperator(this.getOperator())) { // multiple values + return String.join(",", this.getValues()); + } else { // single value or absence + return this.getValue(); + } } @Override diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java index a6b497b4..6ca2a528 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/NumberExpertRule.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; +import static org.gridsuite.filter.server.utils.expertfilter.OperatorType.isMultipleCriteriaOperator; /** * @author Antoine Bouhours @@ -67,8 +68,10 @@ public boolean evaluateRule(Identifiable identifiable) { @Override public String getStringValue() { - return this.getValue() != null ? - String.valueOf(this.getValue()) : - values.stream().map(String::valueOf).collect(Collectors.joining(",")); + if (isMultipleCriteriaOperator(this.getOperator())) { // multiple values + return this.getValues().stream().map(String::valueOf).collect(Collectors.joining(",")); + } else { // single value or absence + return this.getValue() != null ? String.valueOf(this.getValue()) : null; + } } } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java index 5afa9465..7f303ecc 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/StringExpertRule.java @@ -22,6 +22,7 @@ import java.util.Set; import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; +import static org.gridsuite.filter.server.utils.expertfilter.OperatorType.isMultipleCriteriaOperator; /** * @author Antoine Bouhours @@ -39,6 +40,15 @@ public class StringExpertRule extends AbstractExpertRule { @JsonDeserialize(as = HashSet.class) private Set values; + @Override + public String getStringValue() { + if (isMultipleCriteriaOperator(this.getOperator())) { // multiple values + return String.join(",", this.getValues()); + } else { // single value or absence + return this.getValue(); + } + } + @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public DataType getDataType() { @@ -58,9 +68,4 @@ public boolean evaluateRule(Identifiable identifiable) { default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } - - @Override - public String getStringValue() { - return getValue() != null ? getValue() : String.join(",", getValues()); - } } diff --git a/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java b/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java index 19cd88ea..a1d76296 100644 --- a/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java +++ b/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java @@ -19,7 +19,6 @@ import org.gridsuite.filter.server.repositories.proxies.AbstractFilterRepositoryProxy; import org.gridsuite.filter.server.utils.EquipmentType; import org.gridsuite.filter.server.utils.FilterType; -import org.gridsuite.filter.server.utils.expertfilter.OperatorType; import java.util.Collections; import java.util.List; @@ -27,6 +26,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.gridsuite.filter.server.utils.expertfilter.OperatorType.isMultipleCriteriaOperator; + /** * @author Antoine Bouhours */ @@ -71,13 +72,13 @@ public static AbstractExpertRule entityToDto(ExpertRuleEntity filterEntity) { NumberExpertRule.NumberExpertRuleBuilder ruleBuilder = NumberExpertRule.builder() .field(filterEntity.getField()) .operator(filterEntity.getOperator()); - if(filterEntity.getOperator() == OperatorType.IN || - filterEntity.getOperator() == OperatorType.NOT_IN) - { - ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).map(Double::valueOf).collect(Collectors.toSet())); - } - else { - ruleBuilder.value(Double.valueOf(filterEntity.getValue())); + if (filterEntity.getValue() != null) { + if (isMultipleCriteriaOperator(filterEntity.getOperator())) { // for multiple values + ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).map(Double::valueOf).collect(Collectors.toSet())); + } + else { // for single value + ruleBuilder.value(Double.valueOf(filterEntity.getValue())); + } } return ruleBuilder.build(); } @@ -85,13 +86,12 @@ public static AbstractExpertRule entityToDto(ExpertRuleEntity filterEntity) { StringExpertRule.StringExpertRuleBuilder ruleBuilder = StringExpertRule.builder() .field(filterEntity.getField()) .operator(filterEntity.getOperator()); - if(filterEntity.getOperator() == OperatorType.IN || - filterEntity.getOperator() == OperatorType.NOT_IN) - { - ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).collect(Collectors.toSet())); - } - else { - ruleBuilder.value(filterEntity.getValue()); + if (filterEntity.getValue() != null) { + if (isMultipleCriteriaOperator(filterEntity.getOperator())) { // for multiple values + ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).collect(Collectors.toSet())); + } else { // for single value + ruleBuilder.value(filterEntity.getValue()); + } } return ruleBuilder.build(); } @@ -99,13 +99,12 @@ public static AbstractExpertRule entityToDto(ExpertRuleEntity filterEntity) { EnumExpertRule.EnumExpertRuleBuilder ruleBuilder = EnumExpertRule.builder() .field(filterEntity.getField()) .operator(filterEntity.getOperator()); - if(filterEntity.getOperator() == OperatorType.IN || - filterEntity.getOperator() == OperatorType.NOT_IN) - { - ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).collect(Collectors.toSet())); - } - else { - ruleBuilder.value(filterEntity.getValue()); + if (filterEntity.getValue() != null) { + if (isMultipleCriteriaOperator(filterEntity.getOperator())) { // for multiple values + ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).collect(Collectors.toSet())); + } else { // for single value + ruleBuilder.value(filterEntity.getValue()); + } } return ruleBuilder.build(); } diff --git a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java index 3a071531..461187a9 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/expertfilter/OperatorType.java @@ -24,5 +24,10 @@ public enum OperatorType { IS, CONTAINS, BEGINS_WITH, - ENDS_WITH, + ENDS_WITH; + + public static boolean isMultipleCriteriaOperator(OperatorType operator) { + return operator == IN || operator == NOT_IN; + } + } From dbdb92fbddc29aa5ceb650c09988bfd175b99c6b Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 28 Nov 2023 16:36:09 +0100 Subject: [PATCH 12/17] Enrich unit test --- .../filter/server/ExpertFilterUtilsTest.java | 101 ++++++++++++++++-- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java index 889426cc..52e9293a 100644 --- a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -17,6 +17,8 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.Set; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -27,12 +29,6 @@ public class ExpertFilterUtilsTest { @Before public void setUp() { - VoltageLevel voltageLevel = Mockito.mock(VoltageLevel.class); - Mockito.when(voltageLevel.getId()).thenReturn("GEN_01"); - - Terminal terminal = Mockito.mock(Terminal.class); - Mockito.when(terminal.getVoltageLevel()).thenReturn(voltageLevel); - gen = Mockito.mock(Generator.class); Mockito.when(gen.getType()).thenReturn(IdentifiableType.GENERATOR); Mockito.when(gen.getMinP()).thenReturn(-500.0); @@ -42,7 +38,6 @@ public void setUp() { Mockito.when(gen.getNameOrId()).thenReturn("NAME"); Mockito.when(gen.getEnergySource()).thenReturn(EnergySource.HYDRO); Mockito.when(gen.isVoltageRegulatorOn()).thenReturn(true); - Mockito.when(gen.getTerminal()).thenReturn(terminal); } @Test @@ -79,9 +74,6 @@ public void testEvaluateExpertFilterWithANDCombination() { BooleanExpertRule booleanRule7 = BooleanExpertRule.builder().value(false) .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.NOT_EQUALS).build(); andRules2.add(booleanRule7); - StringExpertRule stringRule8 = StringExpertRule.builder().value("GEN_01") - .field(FieldType.VOLTAGE_LEVEL_ID).operator(OperatorType.IS).build(); - andRules2.add(stringRule8); CombinatorExpertRule andFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules2).build(); @@ -222,4 +214,93 @@ public void testEvaluateExpertFilterIgnoreCase() { assertTrue(result); } + + @Test + public void testEvaluateExpertFilterInAndNotInOperators() { + + // --- Test IN Operator --- // + + // Mock for 1st generator + Generator gen1 = Mockito.mock(Generator.class); + Mockito.when(gen1.getType()).thenReturn(IdentifiableType.GENERATOR); + + Substation substation1 = Mockito.mock(Substation.class); + Mockito.when(substation1.getCountry()).thenReturn(Optional.of(Country.FR)); + + VoltageLevel voltageLevel1 = Mockito.mock(VoltageLevel.class); + Mockito.when(voltageLevel1.getId()).thenReturn("VL1"); + Mockito.when(voltageLevel1.getNominalV()).thenReturn(13.0); + Mockito.when(voltageLevel1.getSubstation()).thenReturn(Optional.of(substation1)); + + + Terminal terminal1 = Mockito.mock(Terminal.class); + Mockito.when(terminal1.getVoltageLevel()).thenReturn(voltageLevel1); + Mockito.when(gen1.getTerminal()).thenReturn(terminal1); + + // Build a filter AND with only an IN operator for VOLTAGE_LEVEL_ID + StringExpertRule stringInRule = StringExpertRule.builder().values(Set.of("VL1", "VL3")) + .field(FieldType.VOLTAGE_LEVEL_ID).operator(OperatorType.IN).build(); + CombinatorExpertRule inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(List.of(stringInRule)).build(); + + // Check gen1 must be in the list + assertTrue(inFilter.evaluateRule(gen1)); + + // Build a filter AND with only an IN operator for NOMINAL_VOLTAGE + NumberExpertRule numberInRule = NumberExpertRule.builder().values(Set.of(13.0, 17.0)) + .field(FieldType.NOMINAL_VOLTAGE).operator(OperatorType.IN).build(); + inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(List.of(numberInRule)).build(); + + // Check gen1 must be in the list + assertTrue(inFilter.evaluateRule(gen1)); + + // Build a filter AND with only an IN operator for COUNTRY + EnumExpertRule enumInRule = EnumExpertRule.builder().values(Set.of(Country.FR.name(), Country.GB.name())) + .field(FieldType.COUNTRY).operator(OperatorType.IN).build(); + inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(List.of(enumInRule)).build(); + + // Check gen1 must be in the list + assertTrue(inFilter.evaluateRule(gen1)); + + // --- Test NOT_IN Operator --- // + + // Mock for 2nd generator + Generator gen2 = Mockito.mock(Generator.class); + Mockito.when(gen2.getType()).thenReturn(IdentifiableType.GENERATOR); + + Substation substation2 = Mockito.mock(Substation.class); + Mockito.when(substation2.getCountry()).thenReturn(Optional.of(Country.GE)); + + VoltageLevel voltageLevel2 = Mockito.mock(VoltageLevel.class); + Mockito.when(voltageLevel2.getId()).thenReturn("VL2"); + Mockito.when(voltageLevel2.getNominalV()).thenReturn(15.0); + Mockito.when(voltageLevel2.getSubstation()).thenReturn(Optional.of(substation2)); + + Terminal terminal2 = Mockito.mock(Terminal.class); + Mockito.when(terminal2.getVoltageLevel()).thenReturn(voltageLevel2); + Mockito.when(gen2.getTerminal()).thenReturn(terminal2); + + // Build a filter AND with only a NOT_IN operator for VOLTAGE_LEVEL_ID + StringExpertRule stringNotInRule = StringExpertRule.builder().values(Set.of("VL1", "VL3")) + .field(FieldType.VOLTAGE_LEVEL_ID).operator(OperatorType.NOT_IN).build(); + CombinatorExpertRule notInFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(List.of(stringNotInRule)).build(); + + // Check gen2 must be not in the list + assertTrue(notInFilter.evaluateRule(gen2)); + + // Build a filter AND with only a NOT_IN operator for NOMINAL_VOLTAGE + NumberExpertRule numberNotInRule = NumberExpertRule.builder().values(Set.of(13.0, 17.0)) + .field(FieldType.NOMINAL_VOLTAGE).operator(OperatorType.NOT_IN).build(); + notInFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(List.of(numberNotInRule)).build(); + + // Check gen2 must be not in the list + assertTrue(notInFilter.evaluateRule(gen2)); + + // Build a filter AND with only a NOT_IN operator for COUNTRY + EnumExpertRule enumNotInRule = EnumExpertRule.builder().values(Set.of(Country.FR.name(), Country.GB.name())) + .field(FieldType.COUNTRY).operator(OperatorType.NOT_IN).build(); + notInFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(List.of(enumNotInRule)).build(); + + // Check gen2 must be not in the list + assertTrue(notInFilter.evaluateRule(gen2)); + } } From 384ab5b09862f6f596410cf7fdfffbcd1f98f5b4 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 28 Nov 2023 16:41:30 +0100 Subject: [PATCH 13/17] Style check --- .../proxies/expertfiler/ExpertFilterRepositoryProxy.java | 3 +-- .../org/gridsuite/filter/server/ExpertFilterUtilsTest.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java b/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java index a1d76296..73408fba 100644 --- a/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java +++ b/src/main/java/org/gridsuite/filter/server/repositories/proxies/expertfiler/ExpertFilterRepositoryProxy.java @@ -75,8 +75,7 @@ public static AbstractExpertRule entityToDto(ExpertRuleEntity filterEntity) { if (filterEntity.getValue() != null) { if (isMultipleCriteriaOperator(filterEntity.getOperator())) { // for multiple values ruleBuilder.values(Stream.of(filterEntity.getValue().split(",")).map(Double::valueOf).collect(Collectors.toSet())); - } - else { // for single value + } else { // for single value ruleBuilder.value(Double.valueOf(filterEntity.getValue())); } } diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java index 52e9293a..721027b3 100644 --- a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -230,8 +230,8 @@ public void testEvaluateExpertFilterInAndNotInOperators() { VoltageLevel voltageLevel1 = Mockito.mock(VoltageLevel.class); Mockito.when(voltageLevel1.getId()).thenReturn("VL1"); Mockito.when(voltageLevel1.getNominalV()).thenReturn(13.0); - Mockito.when(voltageLevel1.getSubstation()).thenReturn(Optional.of(substation1)); + Mockito.when(voltageLevel1.getSubstation()).thenReturn(Optional.of(substation1)); Terminal terminal1 = Mockito.mock(Terminal.class); Mockito.when(terminal1.getVoltageLevel()).thenReturn(voltageLevel1); From 11459da8ed0e38a334f616cb542ca4ac764a7c1d Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 28 Nov 2023 23:04:25 +0100 Subject: [PATCH 14/17] Enrich test for persist and export endpoint --- .../server/FilterEntityControllerTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java index 59379be0..f79b39f4 100644 --- a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java +++ b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java @@ -1570,6 +1570,54 @@ public void testExpertLoadFilter() throws Exception { checkFilterEvaluating(expertFilter, expectedResultJson); } + @Test + public void testExpertFilterGeneratorWithInAndNotInOperator() throws Exception { + UUID filterId = UUID.fromString("77614d91-c168-4f89-8fb9-77a23729e88e"); + + // Build a filter AND with only an IN operator for VOLTAGE_LEVEL_ID + StringExpertRule stringInRule = StringExpertRule.builder().values(new HashSet<>(Arrays.asList("VLGEN", "VLGEN2"))) + .field(FieldType.VOLTAGE_LEVEL_ID).operator(OperatorType.IN).build(); + CombinatorExpertRule inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(Arrays.asList(stringInRule)).build(); + + ExpertFilter expertFilter = new ExpertFilter(filterId, new Date(), EquipmentType.GENERATOR, inFilter); + insertFilter(filterId, expertFilter); + checkExpertFilter(filterId, expertFilter); + + // check result when evaluating a filter on a network + String expectedResultJson = """ + [ + {"id":"GEN","type":"GENERATOR"}, + {"id":"GEN2","type":"GENERATOR"} + ] + """; + checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + + // Build a filter AND with only an IN operator for NOMINAL_VOLTAGE + NumberExpertRule numberInRule = NumberExpertRule.builder().values(new HashSet<>(Arrays.asList(24.0))) + .field(FieldType.NOMINAL_VOLTAGE).operator(OperatorType.IN).build(); + inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(Arrays.asList(numberInRule)).build(); + + expertFilter = new ExpertFilter(filterId, new Date(), EquipmentType.GENERATOR, inFilter); + insertFilter(filterId, expertFilter); + checkExpertFilter(filterId, expertFilter); + + // check result when evaluating a filter on a network + checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + + + // Build a filter AND with only an IN operator for COUNTRY + EnumExpertRule enumInRule = EnumExpertRule.builder().values(new HashSet<>(Arrays.asList(Country.FR.name(), Country.GB.name()))) + .field(FieldType.COUNTRY).operator(OperatorType.IN).build(); + inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(Arrays.asList(enumInRule)).build(); + + expertFilter = new ExpertFilter(filterId, new Date(), EquipmentType.GENERATOR, inFilter); + insertFilter(filterId, expertFilter); + checkExpertFilter(filterId, expertFilter); + + // check result when evaluating a filter on a network + checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + } + @Test public void lineFilterIsEmpty() { HvdcLineFilter hvdcFilter = new HvdcLineFilter( From e7881378663aa9d6380ab1cb7b42a615889af736 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 28 Nov 2023 23:22:11 +0100 Subject: [PATCH 15/17] Style check --- .../org/gridsuite/filter/server/FilterEntityControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java index f79b39f4..32ab086a 100644 --- a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java +++ b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java @@ -1604,7 +1604,6 @@ public void testExpertFilterGeneratorWithInAndNotInOperator() throws Exception { // check result when evaluating a filter on a network checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); - // Build a filter AND with only an IN operator for COUNTRY EnumExpertRule enumInRule = EnumExpertRule.builder().values(new HashSet<>(Arrays.asList(Country.FR.name(), Country.GB.name()))) .field(FieldType.COUNTRY).operator(OperatorType.IN).build(); From f861b71901da4cd13a0b19109f2b6da7c6375778 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 28 Nov 2023 23:38:43 +0100 Subject: [PATCH 16/17] Avoid duplicated code by inheritance --- .../expertrule/EnumExpertRule.java | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java index 258085a2..e5b7392c 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertfilter/expertrule/EnumExpertRule.java @@ -7,46 +7,20 @@ package org.gridsuite.filter.server.dto.expertfilter.expertrule; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.server.utils.expertfilter.DataType; -import java.util.HashSet; -import java.util.Set; - import static org.gridsuite.filter.server.utils.expertfilter.ExpertFilterUtils.getFieldValue; -import static org.gridsuite.filter.server.utils.expertfilter.OperatorType.isMultipleCriteriaOperator; /** * @author Antoine Bouhours */ @AllArgsConstructor -@NoArgsConstructor -@Getter @SuperBuilder -public class EnumExpertRule extends AbstractExpertRule { - - @Schema(description = "Value") - private String value; - - @Schema(description = "Values") - @JsonDeserialize(as = HashSet.class) - private Set values; - - @Override - public String getStringValue() { - if (isMultipleCriteriaOperator(this.getOperator())) { // multiple values - return String.join(",", this.getValues()); - } else { // single value or absence - return this.getValue(); - } - } +public class EnumExpertRule extends StringExpertRule { @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) From 9c777fa5d5a960a6ae44a50283ee753671c7de72 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Tue, 28 Nov 2023 23:54:41 +0100 Subject: [PATCH 17/17] Add test for NOT_IN operator --- .../server/FilterEntityControllerTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java index 32ab086a..bed6a9c1 100644 --- a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java +++ b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java @@ -1592,6 +1592,18 @@ public void testExpertFilterGeneratorWithInAndNotInOperator() throws Exception { """; checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + // Build a filter AND with only a NOT_IN operator for VOLTAGE_LEVEL_ID + stringInRule = StringExpertRule.builder().values(new HashSet<>(Arrays.asList("VLGEN2"))) + .field(FieldType.VOLTAGE_LEVEL_ID).operator(OperatorType.NOT_IN).build(); + inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(Arrays.asList(stringInRule)).build(); + + expertFilter = new ExpertFilter(filterId, new Date(), EquipmentType.GENERATOR, inFilter); + insertFilter(filterId, expertFilter); + checkExpertFilter(filterId, expertFilter); + + // check result when evaluating a filter on a network + checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + // Build a filter AND with only an IN operator for NOMINAL_VOLTAGE NumberExpertRule numberInRule = NumberExpertRule.builder().values(new HashSet<>(Arrays.asList(24.0))) .field(FieldType.NOMINAL_VOLTAGE).operator(OperatorType.IN).build(); @@ -1604,6 +1616,18 @@ public void testExpertFilterGeneratorWithInAndNotInOperator() throws Exception { // check result when evaluating a filter on a network checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + // Build a filter AND with only a NOT_IN operator for NOMINAL_VOLTAGE + numberInRule = NumberExpertRule.builder().values(new HashSet<>(Arrays.asList(12.0))) + .field(FieldType.NOMINAL_VOLTAGE).operator(OperatorType.NOT_IN).build(); + inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(Arrays.asList(numberInRule)).build(); + + expertFilter = new ExpertFilter(filterId, new Date(), EquipmentType.GENERATOR, inFilter); + insertFilter(filterId, expertFilter); + checkExpertFilter(filterId, expertFilter); + + // check result when evaluating a filter on a network + checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + // Build a filter AND with only an IN operator for COUNTRY EnumExpertRule enumInRule = EnumExpertRule.builder().values(new HashSet<>(Arrays.asList(Country.FR.name(), Country.GB.name()))) .field(FieldType.COUNTRY).operator(OperatorType.IN).build(); @@ -1615,6 +1639,18 @@ public void testExpertFilterGeneratorWithInAndNotInOperator() throws Exception { // check result when evaluating a filter on a network checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); + + // Build a filter AND with only a NOT_IN operator for COUNTRY + enumInRule = EnumExpertRule.builder().values(new HashSet<>(Arrays.asList(Country.GB.name()))) + .field(FieldType.COUNTRY).operator(OperatorType.NOT_IN).build(); + inFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(Arrays.asList(enumInRule)).build(); + + expertFilter = new ExpertFilter(filterId, new Date(), EquipmentType.GENERATOR, inFilter); + insertFilter(filterId, expertFilter); + checkExpertFilter(filterId, expertFilter); + + // check result when evaluating a filter on a network + checkExpertFilterExportAndMetadata(filterId, expectedResultJson, EquipmentType.GENERATOR); } @Test