Skip to content

Commit 2284791

Browse files
Merge branch 'main' into move_parameters
2 parents de703fa + da1430b commit 2284791

18 files changed

+795
-137
lines changed

pom.xml

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@
4242
</developers>
4343

4444
<properties>
45-
<gridsuite-dependencies.version>28</gridsuite-dependencies.version>
45+
<powsybl-ws-dependencies.version>2.7.0</powsybl-ws-dependencies.version>
4646
<liquibase-hibernate-package>org.gridsuite.shortcircuit.server</liquibase-hibernate-package>
4747
<mockito-inline.version>3.11.1</mockito-inline.version>
48-
<assertj.version>3.24.2</assertj.version>
4948
<db-util.version>1.0.5</db-util.version>
5049
</properties>
5150

@@ -86,20 +85,12 @@
8685
<dependencyManagement>
8786
<dependencies>
8887
<!-- overrides of imports -->
89-
<dependency><!-- temporally until powsybl-core is updated to v6 in gridsuite-dependencies>powsybl-ws-dependencies>powsybl-dependencies -->
90-
<!-- requires, among other things, usingRecursiveComparison() -->
91-
<groupId>org.assertj</groupId>
92-
<artifactId>assertj-bom</artifactId>
93-
<version>${assertj.version}</version>
94-
<type>pom</type>
95-
<scope>import</scope>
96-
</dependency>
9788

9889
<!-- imports -->
9990
<dependency>
100-
<groupId>org.gridsuite</groupId>
101-
<artifactId>gridsuite-dependencies</artifactId>
102-
<version>${gridsuite-dependencies.version}</version>
91+
<groupId>com.powsybl</groupId>
92+
<artifactId>powsybl-ws-dependencies</artifactId>
93+
<version>${powsybl-ws-dependencies.version}</version>
10394
<type>pom</type>
10495
<scope>import</scope>
10596
</dependency>
@@ -160,6 +151,11 @@
160151
<artifactId>powsybl-network-store-iidm-impl</artifactId>
161152
<scope>runtime</scope>
162153
</dependency>
154+
<dependency>
155+
<groupId>io.micrometer</groupId>
156+
<artifactId>micrometer-registry-prometheus</artifactId>
157+
<scope>runtime</scope>
158+
</dependency>
163159
<dependency>
164160
<groupId>org.liquibase</groupId>
165161
<artifactId>liquibase-core</artifactId>
@@ -178,7 +174,6 @@
178174
<dependency>
179175
<groupId>org.springframework.boot</groupId>
180176
<artifactId>spring-boot-starter-actuator</artifactId>
181-
<scope>runtime</scope>
182177
</dependency>
183178

184179
<!-- Test dependencies -->
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Copyright (c) 2024, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
package org.gridsuite.shortcircuit.server;
9+
10+
import org.springframework.http.HttpStatus;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.web.bind.annotation.ControllerAdvice;
13+
import org.springframework.web.bind.annotation.ExceptionHandler;
14+
15+
/**
16+
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
17+
*/
18+
@ControllerAdvice
19+
public class RestResponseEntityExceptionHandler {
20+
@ExceptionHandler(ShortCircuitException.class)
21+
protected ResponseEntity<Object> handleShortCircuitException(ShortCircuitException exception) {
22+
return switch (exception.getType()) {
23+
case RESULT_NOT_FOUND -> ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
24+
case INVALID_EXPORT_PARAMS -> ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getMessage());
25+
case BUS_OUT_OF_VOLTAGE, FILE_EXPORT_ERROR ->
26+
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(exception.getMessage());
27+
};
28+
}
29+
}

src/main/java/org/gridsuite/shortcircuit/server/ShortCircuitController.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
import static com.powsybl.shortcircuit.Fault.FaultType;
3131
import static org.gridsuite.shortcircuit.server.service.NotificationService.HEADER_USER_ID;
32-
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
32+
import static org.springframework.http.MediaType.*;
3333

3434
/**
3535
* @author Etienne Homer <etienne.homer at rte-france.com>
@@ -74,6 +74,18 @@ public ResponseEntity<ShortCircuitAnalysisResult> getResult(@Parameter(descripti
7474
: ResponseEntity.notFound().build();
7575
}
7676

77+
@PostMapping(value = "/results/{resultUuid}/csv", produces = APPLICATION_OCTET_STREAM_VALUE)
78+
@Operation(summary = "Get a short circuit analysis csv result from the database")
79+
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The short circuit analysis csv export"),
80+
@ApiResponse(responseCode = "404", description = "Short circuit analysis result has not been found")})
81+
public ResponseEntity<byte[]> getZippedCsvExportFaultResult(
82+
@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid,
83+
@Parameter(description = "Csv headers and translations payload") @RequestBody CsvTranslation csvTranslation) {
84+
return ResponseEntity.ok()
85+
.contentType(MediaType.parseMediaType(APPLICATION_OCTET_STREAM_VALUE))
86+
.body(shortCircuitService.getZippedCsvExportResult(resultUuid, csvTranslation));
87+
}
88+
7789
@GetMapping(value = "/results/{resultUuid}/fault_results/paged", produces = APPLICATION_JSON_VALUE)
7890
@Operation(summary = "Get a fault results page for a given short circuit analysis result")
7991
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The page of fault results"),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright (c) 2024, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
package org.gridsuite.shortcircuit.server;
8+
9+
import lombok.Getter;
10+
11+
import java.util.Objects;
12+
13+
/**
14+
* @author David SARTORI <david.sartori_externe at rte-france.com>
15+
*/
16+
@Getter
17+
public class ShortCircuitException extends RuntimeException {
18+
19+
public enum Type {
20+
BUS_OUT_OF_VOLTAGE,
21+
RESULT_NOT_FOUND,
22+
INVALID_EXPORT_PARAMS,
23+
FILE_EXPORT_ERROR,
24+
}
25+
26+
private final Type type;
27+
28+
public ShortCircuitException(Type type) {
29+
super(Objects.requireNonNull(type.name()));
30+
this.type = type;
31+
}
32+
33+
public ShortCircuitException(Type type, String message) {
34+
super(message);
35+
this.type = type;
36+
}
37+
38+
public Type getType() {
39+
return type;
40+
}
41+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.gridsuite.shortcircuit.server.dto;
2+
3+
import lombok.Builder;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
@Builder
9+
public record CsvTranslation(
10+
List<String> headersCsv,
11+
Map<String, String> enumValueTranslations
12+
) { }

src/main/java/org/gridsuite/shortcircuit/server/repositories/ShortCircuitAnalysisResultRepository.java

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
import org.gridsuite.shortcircuit.server.dto.ResourceFilter;
1313
import org.gridsuite.shortcircuit.server.dto.ShortCircuitLimits;
1414
import org.gridsuite.shortcircuit.server.entities.*;
15+
import org.gridsuite.shortcircuit.server.service.ShortCircuitRunContext;
1516
import org.gridsuite.shortcircuit.server.utils.FaultResultSpecificationBuilder;
1617
import org.gridsuite.shortcircuit.server.utils.FeederResultSpecificationBuilder;
1718
import org.slf4j.Logger;
1819
import org.slf4j.LoggerFactory;
1920
import org.springframework.beans.factory.annotation.Autowired;
20-
import org.springframework.data.domain.Page;
21-
import org.springframework.data.domain.Pageable;
21+
import org.springframework.data.domain.*;
2222
import org.springframework.data.jpa.domain.Specification;
2323
import org.springframework.stereotype.Repository;
2424
import org.springframework.transaction.annotation.Transactional;
@@ -43,6 +43,12 @@ public class ShortCircuitAnalysisResultRepository {
4343
private final FaultResultRepository faultResultRepository;
4444
private final FeederResultRepository feederResultRepository;
4545

46+
private static final String DEFAULT_FAULT_RESULT_SORT_COLUMN = "faultResultUuid";
47+
48+
private static final String DEFAULT_FEEDER_RESULT_SORT_COLUMN = "feederResultUuid";
49+
50+
private static final Sort.Direction DEFAULT_SORT_DIRECTION = Sort.Direction.ASC;
51+
4652
@Autowired
4753
public ShortCircuitAnalysisResultRepository(GlobalStatusRepository globalStatusRepository,
4854
ResultRepository resultRepository,
@@ -161,10 +167,12 @@ public void insertStatus(List<UUID> resultUuids, String status) {
161167
}
162168

163169
@Transactional
164-
public void insert(UUID resultUuid, ShortCircuitAnalysisResult result, Map<String, ShortCircuitLimits> allCurrentLimits, String status) {
170+
public void insert(UUID resultUuid, ShortCircuitAnalysisResult result, ShortCircuitRunContext runContext, String status) {
165171
Objects.requireNonNull(resultUuid);
166-
if (result != null && !result.getFaultResults().stream().map(FaultResult::getStatus).allMatch(FaultResult.Status.NO_SHORT_CIRCUIT_DATA::equals)) {
167-
resultRepository.save(toResultEntity(resultUuid, result, allCurrentLimits));
172+
if (result != null && (runContext.getBusId() != null ||
173+
!result.getFaultResults().stream().map(FaultResult::getStatus).allMatch(FaultResult.Status.NO_SHORT_CIRCUIT_DATA::equals))
174+
) {
175+
resultRepository.save(toResultEntity(resultUuid, result, runContext.getShortCircuitLimits()));
168176
}
169177
globalStatusRepository.save(toStatusEntity(resultUuid, status));
170178
}
@@ -209,10 +217,10 @@ public Optional<ShortCircuitAnalysisResultEntity> findWithFaultResults(UUID resu
209217
public Optional<ShortCircuitAnalysisResultEntity> findFullResults(UUID resultUuid) {
210218
Objects.requireNonNull(resultUuid);
211219
Optional<ShortCircuitAnalysisResultEntity> result = resultRepository.findWithFaultResultsAndLimitViolationsByResultUuid(resultUuid);
212-
if (!result.isPresent()) {
220+
if (result.isEmpty()) {
213221
return result;
214222
}
215-
// using the the Hibernate First-Level Cache or Persistence Context
223+
// using the Hibernate First-Level Cache or Persistence Context
216224
// cf.https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/
217225
if (!result.get().getFaultResults().isEmpty()) {
218226
resultRepository.findWithFaultResultsAndFeederResultsByResultUuid(resultUuid);
@@ -224,14 +232,14 @@ public Optional<ShortCircuitAnalysisResultEntity> findFullResults(UUID resultUui
224232
public Optional<ShortCircuitAnalysisResultEntity> findResultsWithLimitViolations(UUID resultUuid) {
225233
Objects.requireNonNull(resultUuid);
226234
Optional<ShortCircuitAnalysisResultEntity> result = resultRepository.findWithFaultResultsAndLimitViolationsByResultUuid(resultUuid);
227-
if (!result.isPresent()) {
235+
if (result.isEmpty()) {
228236
return result;
229237
}
230238
List<UUID> faultResultsUuidWithLimitViolations = result.get().getFaultResults().stream()
231239
.filter(fr -> !fr.getLimitViolations().isEmpty())
232240
.map(FaultResultEntity::getFaultResultUuid)
233241
.collect(Collectors.toList());
234-
// using the the Hibernate First-Level Cache or Persistence Context
242+
// using the Hibernate First-Level Cache or Persistence Context
235243
// cf.https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/
236244
if (!result.get().getFaultResults().isEmpty()) {
237245
faultResultRepository.findAllWithFeederResultsByFaultResultUuidIn(faultResultsUuidWithLimitViolations);
@@ -247,7 +255,7 @@ public Page<FaultResultEntity> findFaultResultsPage(ShortCircuitAnalysisResultEn
247255
// HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
248256
// cf. https://vladmihalcea.com/fix-hibernate-hhh000104-entity-fetch-pagination-warning-message/
249257
// We must separate in two requests, one with pagination the other one with Join Fetch
250-
Page<FaultResultEntity> faultResultsPage = faultResultRepository.findAll(specification, pageable);
258+
Page<FaultResultEntity> faultResultsPage = faultResultRepository.findAll(specification, addDefaultSort(pageable, DEFAULT_FAULT_RESULT_SORT_COLUMN));
251259
if (faultResultsPage.hasContent() && mode != FaultResultsMode.BASIC) {
252260
appendLimitViolationsAndFeederResults(faultResultsPage);
253261
}
@@ -258,7 +266,7 @@ public Page<FaultResultEntity> findFaultResultsPage(ShortCircuitAnalysisResultEn
258266
public Page<FeederResultEntity> findFeederResultsPage(ShortCircuitAnalysisResultEntity result, List<ResourceFilter> resourceFilters, Pageable pageable) {
259267
Objects.requireNonNull(result);
260268
Specification<FeederResultEntity> specification = FeederResultSpecificationBuilder.buildSpecification(result.getResultUuid(), resourceFilters);
261-
return feederResultRepository.findAll(specification, pageable);
269+
return feederResultRepository.findAll(specification, addDefaultSort(pageable, DEFAULT_FEEDER_RESULT_SORT_COLUMN));
262270
}
263271

264272
@Transactional(readOnly = true)
@@ -270,15 +278,15 @@ public Page<FaultResultEntity> findFaultResultsWithLimitViolationsPage(ShortCirc
270278
// HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
271279
// cf. https://vladmihalcea.com/fix-hibernate-hhh000104-entity-fetch-pagination-warning-message/
272280
// We must separate in two requests, one with pagination the other one with Join Fetch
273-
Page<FaultResultEntity> faultResultsPage = faultResultRepository.findAll(specification, pageable);
281+
Page<FaultResultEntity> faultResultsPage = faultResultRepository.findAll(specification, addDefaultSort(pageable, DEFAULT_FAULT_RESULT_SORT_COLUMN));
274282
if (faultResultsPage.hasContent()) {
275283
appendLimitViolationsAndFeederResults(faultResultsPage);
276284
}
277285
return faultResultsPage;
278286
}
279287

280288
private void appendLimitViolationsAndFeederResults(Page<FaultResultEntity> pagedFaultResults) {
281-
// using the the Hibernate First-Level Cache or Persistence Context
289+
// using the Hibernate First-Level Cache or Persistence Context
282290
// cf.https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/
283291
if (!pagedFaultResults.isEmpty()) {
284292
List<UUID> faultResultsUuids = pagedFaultResults.stream()
@@ -299,4 +307,14 @@ public String findStatus(UUID resultUuid) {
299307
return null;
300308
}
301309
}
310+
311+
private Pageable addDefaultSort(Pageable pageable, String defaultSortColumn) {
312+
if (pageable.isPaged() && pageable.getSort().getOrderFor(defaultSortColumn) == null) {
313+
//if it's already sorted by our defaultColumn we don't add another sort by the same column
314+
Sort finalSort = pageable.getSort().and(Sort.by(DEFAULT_SORT_DIRECTION, defaultSortColumn));
315+
return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), finalSort);
316+
}
317+
//nothing to do if the request is not paged
318+
return pageable;
319+
}
302320
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright (c) 2024, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
package org.gridsuite.shortcircuit.server.service;
8+
9+
import com.powsybl.shortcircuit.ShortCircuitAnalysis;
10+
import com.powsybl.shortcircuit.ShortCircuitAnalysisResult;
11+
import io.micrometer.core.instrument.Counter;
12+
import io.micrometer.core.instrument.MeterRegistry;
13+
import io.micrometer.observation.Observation;
14+
import io.micrometer.observation.ObservationRegistry;
15+
import lombok.NonNull;
16+
import org.springframework.stereotype.Service;
17+
18+
/**
19+
* @author Abdelsalem Hedhili <abdelsalem.hedhili at rte-france.com>
20+
*/
21+
@Service
22+
public class ShortCircuitObserver {
23+
private static final String OBSERVATION_PREFIX = "app.computation.";
24+
private static final String TYPE_TAG_NAME = "type";
25+
private static final String STATUS_TAG_NAME = "status";
26+
private static final String COMPUTATION_TYPE = "shortcircuit";
27+
private static final String COMPUTATION_COUNTER_NAME = OBSERVATION_PREFIX + "count";
28+
private static final String PROVIDER_TAG_NAME = "provider";
29+
private final ObservationRegistry observationRegistry;
30+
private final MeterRegistry meterRegistry;
31+
32+
public ShortCircuitObserver(@NonNull ObservationRegistry observationRegistry, @NonNull MeterRegistry meterRegistry) {
33+
this.observationRegistry = observationRegistry;
34+
this.meterRegistry = meterRegistry;
35+
}
36+
37+
public <E extends Throwable> void observe(String name, Observation.CheckedRunnable<E> runnable) throws E {
38+
createObservation(name).observeChecked(runnable);
39+
}
40+
41+
public <T extends ShortCircuitAnalysisResult, E extends Throwable> T observeRun(String name, Observation.CheckedCallable<T, E> callable) throws E {
42+
T result = createObservation(name).observeChecked(callable);
43+
incrementCount(result);
44+
return result;
45+
}
46+
47+
public <T, E extends Throwable> T observe(String name, Observation.CheckedCallable<T, E> callable) throws E {
48+
return createObservation(name).observeChecked(callable);
49+
}
50+
51+
private Observation createObservation(String name) {
52+
return Observation.createNotStarted(OBSERVATION_PREFIX + name, observationRegistry)
53+
.lowCardinalityKeyValue(PROVIDER_TAG_NAME, ShortCircuitAnalysis.find().getName())
54+
.lowCardinalityKeyValue(TYPE_TAG_NAME, COMPUTATION_TYPE);
55+
}
56+
57+
private void incrementCount(ShortCircuitAnalysisResult result) {
58+
Counter.builder(COMPUTATION_COUNTER_NAME)
59+
.tag(PROVIDER_TAG_NAME, ShortCircuitAnalysis.find().getName())
60+
.tag(TYPE_TAG_NAME, COMPUTATION_TYPE)
61+
.tag(STATUS_TAG_NAME, result != null ? "OK" : "NOK")
62+
.register(meterRegistry)
63+
.increment();
64+
}
65+
}

0 commit comments

Comments
 (0)