diff --git a/src/main/java/org/gridsuite/shortcircuit/server/service/ShortCircuitService.java b/src/main/java/org/gridsuite/shortcircuit/server/service/ShortCircuitService.java index f06bd6b3..083bc832 100644 --- a/src/main/java/org/gridsuite/shortcircuit/server/service/ShortCircuitService.java +++ b/src/main/java/org/gridsuite/shortcircuit/server/service/ShortCircuitService.java @@ -15,7 +15,10 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Order; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -150,12 +153,13 @@ public Page getFaultResultsPage(UUID resultUuid, FaultResultsMode m result = resultRepository.find(resultUuid); if (result.isPresent()) { Page faultResultEntitiesPage = Page.empty(); + Pageable deterministicPageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), appendUniqueOrderIfNecessary(pageable.getSort(), new Order(Sort.Direction.ASC, "fault.id"))); switch (mode) { case BASIC, FULL: - faultResultEntitiesPage = resultRepository.findFaultResultsPage(result.get(), resourceFilters, pageable, mode); + faultResultEntitiesPage = resultRepository.findFaultResultsPage(result.get(), resourceFilters, deterministicPageable, mode); break; case WITH_LIMIT_VIOLATIONS: - faultResultEntitiesPage = resultRepository.findFaultResultsWithLimitViolationsPage(result.get(), resourceFilters, pageable); + faultResultEntitiesPage = resultRepository.findFaultResultsWithLimitViolationsPage(result.get(), resourceFilters, deterministicPageable); break; case NONE: default: @@ -164,7 +168,7 @@ public Page getFaultResultsPage(UUID resultUuid, FaultResultsMode m Page faultResultsPage = faultResultEntitiesPage.map(fr -> fromEntity(fr, mode)); if (LOGGER.isInfoEnabled()) { LOGGER.info("Get ShortCircuit Results {} in {}ms", resultUuid, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime.get())); - LOGGER.info("pageable = {}", LogUtils.sanitizeParam(pageable.toString())); + LOGGER.info("pageable = {}", LogUtils.sanitizeParam(deterministicPageable.toString())); } return faultResultsPage; } @@ -177,11 +181,12 @@ public Page getFeederResultsPage(UUID resultUuid, List result = resultRepository.find(resultUuid); if (result.isPresent()) { - Page feederResultEntitiesPage = resultRepository.findFeederResultsPage(result.get(), resourceFilters, pageable); + Pageable deterministicPageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), appendUniqueOrderIfNecessary(pageable.getSort(), new Order(Sort.Direction.ASC, "feederResultUuid"))); + Page feederResultEntitiesPage = resultRepository.findFeederResultsPage(result.get(), resourceFilters, deterministicPageable); Page feederResultsPage = feederResultEntitiesPage.map(ShortCircuitService::fromEntity); if (LOGGER.isInfoEnabled()) { LOGGER.info("Get ShortCircuit Results {} in {}ms", resultUuid, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime.get())); - LOGGER.info("pageable = {}", LogUtils.sanitizeParam(pageable.toString())); + LOGGER.info("pageable = {}", LogUtils.sanitizeParam(deterministicPageable.toString())); } return feederResultsPage; } @@ -207,4 +212,15 @@ public void setStatus(List resultUuids, String status) { public void stop(UUID resultUuid, String receiver) { notificationService.sendCancelMessage(new ShortCircuitCancelContext(resultUuid, receiver).toMessage()); } + + // If the Sort Orders in Pageable contains already a sort Order on the property column then do not append it + // otherwise append it at THE END to allow sorting rules to happen with rows containing same value on the sorted column + // and then finally define a deterministic order. + private Sort appendUniqueOrderIfNecessary(Sort sourceSort, Order uniqueOrder) { + Order order = sourceSort.getOrderFor(uniqueOrder.getProperty()); + if (order == null) { + return sourceSort.and(Sort.by(uniqueOrder)); + } + return sourceSort; + } } diff --git a/src/test/java/org/gridsuite/shortcircuit/server/ShortCircuitAnalysisControllerTest.java b/src/test/java/org/gridsuite/shortcircuit/server/ShortCircuitAnalysisControllerTest.java index b4384420..e898be44 100644 --- a/src/test/java/org/gridsuite/shortcircuit/server/ShortCircuitAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/shortcircuit/server/ShortCircuitAnalysisControllerTest.java @@ -117,6 +117,9 @@ private static final class ShortCircuitAnalysisResultMock { static final FaultResult FAULT_RESULT_4 = new FortescueFaultResult(new BusFault("VLHV2_0", "ELEMENT_ID_2"), 18.0, List.of(FEEDER_RESULT_4, FEEDER_RESULT_5, FEEDER_RESULT_6), List.of(LIMIT_VIOLATION_1, LIMIT_VIOLATION_2, LIMIT_VIOLATION_3), new FortescueValue(21.328664779663086, -80.73799896240234, Double.NaN, Double.NaN, Double.NaN, Double.NaN), new FortescueValue(21.328664779663086, -80.73799896240234, Double.NaN, Double.NaN, Double.NaN, Double.NaN), Collections.emptyList(), null, FaultResult.Status.SUCCESS); + static final FaultResult FAULT_RESULT_4_PAGE_0 = new FortescueFaultResult(new BusFault("VLHV2_0", "ELEMENT_ID_2"), 18.0, + List.of(FEEDER_RESULT_4, FEEDER_RESULT_5), List.of(LIMIT_VIOLATION_1, LIMIT_VIOLATION_2, LIMIT_VIOLATION_3), + new FortescueValue(21.328664779663086, -80.73799896240234, Double.NaN, Double.NaN, Double.NaN, Double.NaN), new FortescueValue(21.328664779663086, -80.73799896240234, Double.NaN, Double.NaN, Double.NaN, Double.NaN), Collections.emptyList(), null, FaultResult.Status.SUCCESS); static final FaultResult FAULT_RESULT_BASIC_1 = new MagnitudeFaultResult(new BusFault("VLHV1_0", "ELEMENT_ID_1"), 17.0, List.of(), List.of(), 45.3, FaultResult.Status.SUCCESS); @@ -129,9 +132,12 @@ private static final class ShortCircuitAnalysisResultMock { static final ShortCircuitAnalysisResult RESULT_MAGNITUDE_FULL = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_1, FAULT_RESULT_2, FAULT_RESULT_3)); static final ShortCircuitAnalysisResult RESULT_FORTESCUE_FULL = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_4)); - static final ShortCircuitAnalysisResult RESULT_SORTED_PAGE_0 = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_1, FAULT_RESULT_3)); - static final ShortCircuitAnalysisResult RESULT_SORTED_PAGE_1 = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_3)); + static final ShortCircuitAnalysisResult RESULT_FORTESCUE_PAGE_0 = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_4_PAGE_0)); + static final ShortCircuitAnalysisResult RESULT_DEFAULT_SORTED_PAGE_0 = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_3, FAULT_RESULT_1)); + static final ShortCircuitAnalysisResult RESULT_SORTED_PAGE_0 = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_3, FAULT_RESULT_1)); + static final ShortCircuitAnalysisResult RESULT_SORTED_DESC_PAGE_1 = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_3)); static final ShortCircuitAnalysisResult RESULT_WITH_LIMIT_VIOLATIONS = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_1, FAULT_RESULT_3)); + static final ShortCircuitAnalysisResult RESULT_WITH_LIMIT_VIOLATIONS_PAGE_0 = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_3, FAULT_RESULT_1)); static final ShortCircuitAnalysisResult RESULT_BASIC = new ShortCircuitAnalysisResult(List.of(FAULT_RESULT_BASIC_1, FAULT_RESULT_BASIC_2, FAULT_RESULT_BASIC_3)); } @@ -189,9 +195,9 @@ private static void assertResultsEquals(ShortCircuitAnalysisResult result, org.g private static void assertPagedFaultResultsEquals(ShortCircuitAnalysisResult result, List faultResults) { assertEquals(result.getFaultResults().size(), faultResults.size()); - List orderedFaultResults = result.getFaultResults().stream().sorted(Comparator.comparing(fr -> fr.getFault().getId())).collect(Collectors.toList()); + // List orderedFaultResults = result.getFaultResults().stream().sorted(Comparator.comparing(fr -> fr.getFault().getId())).collect(Collectors.toList()); // don't need to sort here it's done in the paged request - assertFaultResultsEquals(orderedFaultResults, faultResults); + assertFaultResultsEquals(result.getFaultResults(), faultResults); } private static void assertFaultResultsEquals(List faultResults, List faultResultsDto) { @@ -353,7 +359,19 @@ public void runTest() throws Exception { JsonNode faultResultsPageNode = mapper.readTree(result.getResponse().getContentAsString()); ObjectReader faultResultsReader = mapper.readerFor(new TypeReference>() { }); List faultResultsPageDto0 = faultResultsReader.readValue(faultResultsPageNode.get("content")); - assertPagedFaultResultsEquals(ShortCircuitAnalysisResultMock.RESULT_WITH_LIMIT_VIOLATIONS, faultResultsPageDto0); + assertPagedFaultResultsEquals(ShortCircuitAnalysisResultMock.RESULT_WITH_LIMIT_VIOLATIONS_PAGE_0, faultResultsPageDto0); + + result = mockMvc.perform(get( + "/" + VERSION + "/results/{resultUuid}/fault_results/paged", RESULT_UUID) + .param("mode", "FULL") + .param("page", "0") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andReturn(); + JsonNode faultResultsDefaultPageNode0 = mapper.readTree(result.getResponse().getContentAsString()); + List faultResultsDefaultPageDto0Full = faultResultsReader.readValue(faultResultsDefaultPageNode0.get("content")); + assertPagedFaultResultsEquals(ShortCircuitAnalysisResultMock.RESULT_DEFAULT_SORTED_PAGE_0, faultResultsDefaultPageDto0Full); result = mockMvc.perform(get( "/" + VERSION + "/results/{resultUuid}/fault_results/paged", RESULT_UUID) @@ -379,7 +397,7 @@ public void runTest() throws Exception { .andReturn(); JsonNode faultResultsPageNode1 = mapper.readTree(result.getResponse().getContentAsString()); List faultResultsPageDto1Full = faultResultsReader.readValue(faultResultsPageNode1.get("content")); - assertPagedFaultResultsEquals(ShortCircuitAnalysisResultMock.RESULT_SORTED_PAGE_1, faultResultsPageDto1Full); + assertPagedFaultResultsEquals(ShortCircuitAnalysisResultMock.RESULT_SORTED_DESC_PAGE_1, faultResultsPageDto1Full); // should throw not found if result does not exist mockMvc.perform(get("/" + VERSION + "/results/{resultUuid}", OTHER_RESULT_UUID)) @@ -440,7 +458,8 @@ public void runWithBusIdTest() throws Exception { result = mockMvc.perform(get( "/" + VERSION + "/results/{resultUuid}/feeder_results/paged", RESULT_UUID) .param("page", "0") - .param("size", "3")) + .param("size", "2") + .param("sort", "connectableId")) // to be predictive .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andReturn(); @@ -450,7 +469,7 @@ public void runWithBusIdTest() throws Exception { List feederResults = reader.readValue(feederResultsPage.get("content")); // we update the fault result with the feeders we just get to be able to use the assertion org.gridsuite.shortcircuit.server.dto.FaultResult formattedFaultResult = new org.gridsuite.shortcircuit.server.dto.FaultResult(faultResult.getFault(), faultResult.getCurrent(), faultResult.getPositiveMagnitude(), faultResult.getShortCircuitPower(), faultResult.getLimitViolations(), feederResults, faultResult.getShortCircuitLimits()); - assertPagedFaultResultsEquals(ShortCircuitAnalysisResultMock.RESULT_FORTESCUE_FULL, List.of(formattedFaultResult)); + assertPagedFaultResultsEquals(ShortCircuitAnalysisResultMock.RESULT_FORTESCUE_PAGE_0, List.of(formattedFaultResult)); } }