Skip to content

Commit c67a7da

Browse files
authored
Merge branch 'main' into move_shortcircuit_parameters
2 parents b19a4ac + e4bc4e5 commit c67a7da

File tree

11 files changed

+230
-18
lines changed

11 files changed

+230
-18
lines changed

pom.xml

+6-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@
182182
<groupId>org.springdoc</groupId>
183183
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
184184
</dependency>
185-
185+
186186
<dependency>
187187
<groupId>com.jayway.jsonpath</groupId>
188188
<artifactId>json-path-assert</artifactId>
@@ -227,6 +227,11 @@
227227
<artifactId>spring-boot-starter-actuator</artifactId>
228228
<scope>runtime</scope>
229229
</dependency>
230+
<dependency>
231+
<groupId>io.micrometer</groupId>
232+
<artifactId>micrometer-registry-prometheus</artifactId>
233+
<scope>runtime</scope>
234+
</dependency>
230235
<dependency>
231236
<groupId>org.springframework.boot</groupId>
232237
<artifactId>spring-boot-actuator</artifactId>

src/main/java/org/gridsuite/study/server/StudyController.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -1106,10 +1106,9 @@ public ResponseEntity<Void> updateNetworkModification(@Parameter(description = "
11061106
public ResponseEntity<Void> deleteNetworkModifications(@Parameter(description = "Study UUID") @PathVariable("studyUuid") UUID studyUuid,
11071107
@Parameter(description = "Node UUID") @PathVariable("nodeUuid") UUID nodeUuid,
11081108
@Parameter(description = "Network modification UUIDs") @RequestParam(name = "uuids", required = false) List<UUID> networkModificationUuids,
1109-
@Parameter(description = "Delete only stashed modifications") @RequestParam(name = "onlyStashed", required = false, defaultValue = "false") Boolean onlyStashed,
11101109
@RequestHeader(HEADER_USER_ID) String userId) {
11111110
studyService.assertCanModifyNode(studyUuid, nodeUuid);
1112-
studyService.deleteNetworkModifications(studyUuid, nodeUuid, networkModificationUuids, onlyStashed, userId);
1111+
studyService.deleteNetworkModifications(studyUuid, nodeUuid, networkModificationUuids, userId);
11131112

11141113
return ResponseEntity.ok().build();
11151114
}

src/main/java/org/gridsuite/study/server/SupervisionController.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
import io.swagger.v3.oas.annotations.responses.ApiResponse;
1212
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1313
import io.swagger.v3.oas.annotations.tags.Tag;
14+
import org.gridsuite.study.server.service.StudyService;
1415
import org.gridsuite.study.server.service.SupervisionService;
1516

17+
import java.util.List;
1618
import java.util.UUID;
1719

1820
import org.gridsuite.study.server.dto.ComputationType;
@@ -42,8 +44,11 @@ public class SupervisionController {
4244

4345
private final SupervisionService supervisionService;
4446

45-
public SupervisionController(SupervisionService supervisionService) {
47+
private final StudyService studyService;
48+
49+
public SupervisionController(SupervisionService supervisionService, StudyService studyService) {
4650
this.supervisionService = supervisionService;
51+
this.studyService = studyService;
4752
}
4853

4954
@DeleteMapping(value = "/computation/results")
@@ -96,6 +101,21 @@ public ResponseEntity<Long> deleteStudyIndexedEquipmentsAndTombstoned(@PathVaria
96101
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(supervisionService.deleteStudyIndexedEquipmentsAndTombstoned(studyUuid));
97102
}
98103

104+
@GetMapping(value = "/orphan_indexed_network_uuids")
105+
@Operation(summary = "Get all orphan indexed equipments network uuids")
106+
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The list of orphan indexed equipments network uuids")})
107+
public ResponseEntity<List<UUID>> getOrphanIndexedEquipments() {
108+
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(studyService.getAllOrphanIndexedEquipmentsNetworkUuids());
109+
}
110+
111+
@DeleteMapping(value = "/studies/{networkUuid}/indexed-equipments-by-network-uuid")
112+
@Operation(summary = "delete indexed equipments and tombstoned equipments for the given networkUuid")
113+
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "all indexed equipments and tombstoned equipments for the given networkUuid have been deleted")})
114+
public ResponseEntity<Long> deleteNetworkUuidIndexedEquipmentsAndTombstoned(@PathVariable("networkUuid") UUID networkUuid) {
115+
studyService.deleteEquipmentIndexes(networkUuid);
116+
return ResponseEntity.ok().build();
117+
}
118+
99119
@DeleteMapping(value = "/studies/{studyUuid}/nodes/builds")
100120
@Operation(summary = "Invalidate node builds for the given study")
101121
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "all built nodes for the given study have been invalidated")})

src/main/java/org/gridsuite/study/server/dto/BasicEquipmentInfos.java

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import io.swagger.v3.oas.annotations.media.Schema;
1010
import lombok.*;
11+
import lombok.experimental.FieldNameConstants;
1112
import lombok.experimental.SuperBuilder;
1213
import org.springframework.data.annotation.AccessType;
1314
import org.springframework.data.annotation.Id;
@@ -27,6 +28,7 @@
2728
@Setter
2829
@ToString
2930
@EqualsAndHashCode
31+
@FieldNameConstants
3032
@Schema(description = "Basic equipment infos")
3133
public class BasicEquipmentInfos {
3234
@Id

src/main/java/org/gridsuite/study/server/elasticsearch/EquipmentInfosService.java

+107-3
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,22 @@
77
package org.gridsuite.study.server.elasticsearch;
88

99
import co.elastic.clients.elasticsearch._types.FieldValue;
10+
import co.elastic.clients.elasticsearch._types.aggregations.*;
11+
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
1012
import co.elastic.clients.elasticsearch._types.query_dsl.*;
1113
import com.powsybl.iidm.network.VariantManagerConstants;
1214
import org.apache.commons.lang3.StringUtils;
15+
import org.apache.commons.lang3.tuple.Pair;
16+
import org.gridsuite.study.server.dto.BasicEquipmentInfos;
1317
import org.gridsuite.study.server.dto.EquipmentInfos;
1418
import org.gridsuite.study.server.dto.TombstonedEquipmentInfos;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
1521
import org.springframework.data.domain.PageRequest;
16-
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
17-
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
18-
import org.springframework.data.elasticsearch.client.elc.Queries;
22+
import org.springframework.data.elasticsearch.client.elc.*;
1923
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
2024
import org.springframework.data.elasticsearch.core.SearchHit;
25+
import org.springframework.data.elasticsearch.core.SearchHits;
2126
import org.springframework.lang.NonNull;
2227
import org.springframework.stereotype.Service;
2328

@@ -41,6 +46,8 @@ public enum FieldSelector {
4146

4247
private static final int PAGE_MAX_SIZE = 400;
4348

49+
private static final int COMPOSITE_AGGREGATION_BATCH_SIZE = 1000;
50+
4451
public static final Map<String, Integer> EQUIPMENT_TYPE_SCORES = Map.ofEntries(
4552
entry("SUBSTATION", 15),
4653
entry("VOLTAGE_LEVEL", 14),
@@ -72,6 +79,8 @@ public enum FieldSelector {
7279

7380
private final ElasticsearchOperations elasticsearchOperations;
7481

82+
private static final Logger LOGGER = LoggerFactory.getLogger(EquipmentInfosService.class);
83+
7584
public EquipmentInfosService(EquipmentInfosRepository equipmentInfosRepository, TombstonedEquipmentInfosRepository tombstonedEquipmentInfosRepository, ElasticsearchOperations elasticsearchOperations) {
7685
this.equipmentInfosRepository = equipmentInfosRepository;
7786
this.tombstonedEquipmentInfosRepository = tombstonedEquipmentInfosRepository;
@@ -105,6 +114,101 @@ public long getEquipmentInfosCount() {
105114
return equipmentInfosRepository.count();
106115
}
107116

117+
private CompositeAggregation buildCompositeAggregation(String field, Map<String, FieldValue> afterKey) {
118+
List<Map<String, CompositeAggregationSource>> sources = List.of(
119+
Map.of(field, CompositeAggregationSource.of(s -> s.terms(t -> t.field(field + ".keyword")))
120+
)
121+
);
122+
123+
CompositeAggregation.Builder compositeAggregationBuilder = new CompositeAggregation.Builder()
124+
.size(COMPOSITE_AGGREGATION_BATCH_SIZE)
125+
.sources(sources);
126+
127+
if (afterKey != null) {
128+
compositeAggregationBuilder.after(afterKey);
129+
}
130+
131+
return compositeAggregationBuilder.build();
132+
}
133+
134+
/**
135+
* Constructs a NativeQuery with a composite aggregation.
136+
*
137+
* @param compositeName The name of the composite aggregation.
138+
* @param compositeAggregation The composite aggregation configuration.
139+
* @return A NativeQuery object configured with the specified composite aggregation.
140+
*/
141+
private NativeQuery buildCompositeAggregationQuery(String compositeName, CompositeAggregation compositeAggregation) {
142+
Aggregation aggregation = Aggregation.of(a -> a.composite(compositeAggregation));
143+
144+
return new NativeQueryBuilder()
145+
.withAggregation(compositeName, aggregation)
146+
.build();
147+
}
148+
149+
/**
150+
* This method is used to extract the results of a composite aggregation from Elasticsearch search hits.
151+
*
152+
* @param searchHits The search hits returned from an Elasticsearch query.
153+
* @param compositeName The name of the composite aggregation.
154+
* @return A Pair consisting of two elements:
155+
* The left element of the Pair is a list of maps, where each map represents a bucket's key. Each bucket is a result of the composite aggregation.
156+
* The right element of the Pair is the afterKey map, which is used for pagination in Elasticsearch.
157+
* If there are no more pages, the afterKey will be null.
158+
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-composite-aggregation.html">Elasticsearch Composite Aggregation Documentation</a>
159+
*/
160+
private Pair<List<Map<String, FieldValue>>, Map<String, FieldValue>> extractCompositeAggregationResults(SearchHits<EquipmentInfos> searchHits, String compositeName) {
161+
ElasticsearchAggregations aggregations = (ElasticsearchAggregations) searchHits.getAggregations();
162+
163+
List<Map<String, FieldValue>> results = new ArrayList<>();
164+
if (aggregations != null) {
165+
Map<String, ElasticsearchAggregation> aggregationList = aggregations.aggregationsAsMap();
166+
if (!aggregationList.isEmpty()) {
167+
Aggregate aggregate = aggregationList.get(compositeName).aggregation().getAggregate();
168+
if (aggregate.isComposite() && aggregate.composite() != null) {
169+
for (CompositeBucket bucket : aggregate.composite().buckets().array()) {
170+
Map<String, FieldValue> key = bucket.key();
171+
results.add(key);
172+
}
173+
return Pair.of(results, aggregate.composite().afterKey());
174+
}
175+
}
176+
}
177+
return Pair.of(results, null);
178+
}
179+
180+
public List<UUID> getEquipmentInfosDistinctNetworkUuids() {
181+
List<UUID> networkUuids = new ArrayList<>();
182+
Map<String, FieldValue> afterKey = null;
183+
String compositeName = "composite_agg";
184+
String networkUuidField = BasicEquipmentInfos.Fields.networkUuid;
185+
186+
do {
187+
CompositeAggregation compositeAggregation = buildCompositeAggregation(networkUuidField, afterKey);
188+
NativeQuery query = buildCompositeAggregationQuery(compositeName, compositeAggregation);
189+
190+
SearchHits<EquipmentInfos> searchHits = elasticsearchOperations.search(query, EquipmentInfos.class);
191+
Pair<List<Map<String, FieldValue>>, Map<String, FieldValue>> searchResults = extractCompositeAggregationResults(searchHits, compositeName);
192+
193+
searchResults.getLeft().stream()
194+
.map(result -> result.get(networkUuidField))
195+
.filter(Objects::nonNull)
196+
.map(FieldValue::stringValue)
197+
.map(UUID::fromString)
198+
.forEach(networkUuids::add);
199+
200+
afterKey = searchResults.getRight();
201+
} while (afterKey != null && !afterKey.isEmpty());
202+
203+
return networkUuids;
204+
}
205+
206+
public List<UUID> getOrphanEquipmentInfosNetworkUuids(List<UUID> networkUuidsInDatabase) {
207+
List<UUID> networkUuids = getEquipmentInfosDistinctNetworkUuids();
208+
networkUuids.removeAll(networkUuidsInDatabase);
209+
return networkUuids;
210+
}
211+
108212
public long getTombstonedEquipmentInfosCount() {
109213
return tombstonedEquipmentInfosRepository.count();
110214
}

src/main/java/org/gridsuite/study/server/notification/NotificationService.java

+14
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,12 @@ public class NotificationService {
7878
public static final String UPDATE_TYPE_INDEXATION_STATUS = "indexation_status_updated";
7979

8080
public static final String MODIFICATIONS_CREATING_IN_PROGRESS = "creatingInProgress";
81+
public static final String MODIFICATIONS_STASHING_IN_PROGRESS = "stashingInProgress";
82+
public static final String MODIFICATIONS_RESTORING_IN_PROGRESS = "restoringInProgress";
8183
public static final String MODIFICATIONS_DELETING_IN_PROGRESS = "deletingInProgress";
8284
public static final String MODIFICATIONS_UPDATING_IN_PROGRESS = "updatingInProgress";
8385
public static final String MODIFICATIONS_UPDATING_FINISHED = "UPDATE_FINISHED";
86+
public static final String MODIFICATIONS_DELETING_FINISHED = "DELETE_FINISHED";
8487

8588
public static final String EVENTS_CRUD_CREATING_IN_PROGRESS = "eventCreatingInProgress";
8689
public static final String EVENTS_CRUD_DELETING_IN_PROGRESS = "eventDeletingInProgress";
@@ -344,6 +347,17 @@ public void emitEndModificationEquipmentNotification(UUID studyUuid, UUID parent
344347
);
345348
}
346349

350+
@PostCompletion
351+
public void emitEndDeletionEquipmentNotification(UUID studyUuid, UUID parentNodeUuid, Collection<UUID> childrenUuids) {
352+
sendUpdateMessage(MessageBuilder.withPayload("")
353+
.setHeader(HEADER_STUDY_UUID, studyUuid)
354+
.setHeader(HEADER_PARENT_NODE, parentNodeUuid)
355+
.setHeader(HEADER_NODES, childrenUuids)
356+
.setHeader(HEADER_UPDATE_TYPE, MODIFICATIONS_DELETING_FINISHED)
357+
.build()
358+
);
359+
}
360+
347361
public void emitStartEventCrudNotification(UUID studyUuid, UUID parentNodeUuid, Collection<UUID> childrenUuids, String crudType) {
348362
sendUpdateMessage(MessageBuilder.withPayload("")
349363
.setHeader(HEADER_STUDY_UUID, studyUuid)

src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,12 @@ public void deleteModifications(UUID groupUUid) {
126126
}
127127
}
128128

129-
public void deleteModifications(UUID groupUuid, List<UUID> modificationsUuids, boolean onlyStashed) {
129+
public void deleteModifications(UUID groupUuid, List<UUID> modificationsUuids) {
130130
Objects.requireNonNull(groupUuid);
131131
var path = UriComponentsBuilder
132132
.fromUriString(getNetworkModificationServerURI(false) + NETWORK_MODIFICATIONS_PATH)
133133
.queryParam(UUIDS, modificationsUuids)
134134
.queryParam(GROUP_UUID, groupUuid)
135-
.queryParam(QUERY_PARAM_ONLY_STASHED, onlyStashed)
136135
.buildAndExpand()
137136
.toUriString();
138137
try {

src/main/java/org/gridsuite/study/server/service/StudyService.java

+15-5
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,16 @@ public List<CreatedStudyBasicInfos> getStudies() {
237237
.collect(Collectors.toList());
238238
}
239239

240+
public List<UUID> getStudiesNetworkUuids() {
241+
return studyRepository.findAll().stream()
242+
.map(StudyEntity::getNetworkUuid)
243+
.toList();
244+
}
245+
246+
public List<UUID> getAllOrphanIndexedEquipmentsNetworkUuids() {
247+
return equipmentInfosService.getOrphanEquipmentInfosNetworkUuids(getStudiesNetworkUuids());
248+
}
249+
240250
public String getStudyCaseName(UUID studyUuid) {
241251
Objects.requireNonNull(studyUuid);
242252
StudyEntity study = studyRepository.findById(studyUuid).orElseThrow(() -> new StudyException(STUDY_NOT_FOUND));
@@ -1344,29 +1354,29 @@ public void changeModificationActiveState(@NonNull UUID studyUuid, @NonNull UUID
13441354
}
13451355

13461356
@Transactional
1347-
public void deleteNetworkModifications(UUID studyUuid, UUID nodeUuid, List<UUID> modificationsUuids, boolean onlyStashed, String userId) {
1357+
public void deleteNetworkModifications(UUID studyUuid, UUID nodeUuid, List<UUID> modificationsUuids, String userId) {
13481358
List<UUID> childrenUuids = networkModificationTreeService.getChildren(nodeUuid);
13491359
notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_DELETING_IN_PROGRESS);
13501360
try {
13511361
if (!networkModificationTreeService.getStudyUuidForNodeId(nodeUuid).equals(studyUuid)) {
13521362
throw new StudyException(NOT_ALLOWED);
13531363
}
13541364
UUID groupId = networkModificationTreeService.getModificationGroupUuid(nodeUuid);
1355-
networkModificationService.deleteModifications(groupId, modificationsUuids, onlyStashed);
1365+
networkModificationService.deleteModifications(groupId, modificationsUuids);
13561366
if (modificationsUuids != null) {
13571367
networkModificationTreeService.removeModificationsToExclude(nodeUuid, modificationsUuids);
13581368
}
13591369
updateStatuses(studyUuid, nodeUuid, false, false, false);
13601370
} finally {
1361-
notificationService.emitEndModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids);
1371+
notificationService.emitEndDeletionEquipmentNotification(studyUuid, nodeUuid, childrenUuids);
13621372
}
13631373
notificationService.emitElementUpdated(studyUuid, userId);
13641374
}
13651375

13661376
@Transactional
13671377
public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List<UUID> modificationsUuids, String userId) {
13681378
List<UUID> childrenUuids = networkModificationTreeService.getChildren(nodeUuid);
1369-
notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_DELETING_IN_PROGRESS);
1379+
notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_STASHING_IN_PROGRESS);
13701380
try {
13711381
if (!networkModificationTreeService.getStudyUuidForNodeId(nodeUuid).equals(studyUuid)) {
13721382
throw new StudyException(NOT_ALLOWED);
@@ -1384,7 +1394,7 @@ public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List<UUID>
13841394
@Transactional
13851395
public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List<UUID> modificationsUuids, String userId) {
13861396
List<UUID> childrenUuids = networkModificationTreeService.getChildren(nodeUuid);
1387-
notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_DELETING_IN_PROGRESS);
1397+
notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_RESTORING_IN_PROGRESS);
13881398
try {
13891399
if (!networkModificationTreeService.getStudyUuidForNodeId(nodeUuid).equals(studyUuid)) {
13901400
throw new StudyException(NOT_ALLOWED);

0 commit comments

Comments
 (0)