Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
<sonar.projectKey>org.gridsuite:loadflow-server</sonar.projectKey>
<!-- FIXME: to remove at next version of powsybl-ws-dependencies -->
<powsybl-network-store.version>1.44.0</powsybl-network-store.version>
<!-- FIXME: to be removed at next powsybl-ws-dependencies upgrade -->
<gridsuite-computation.version>2.3.0-SNAPSHOT</gridsuite-computation.version>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to delete it

</properties>

<build>
Expand Down Expand Up @@ -146,6 +148,7 @@
<dependency>
<groupId>org.gridsuite</groupId>
<artifactId>gridsuite-computation</artifactId>
<version>${gridsuite-computation.version}</version>
</dependency>
<dependency>
<groupId>com.powsybl</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ public ResponseEntity<LoadFlowStatus> getStatus(@Parameter(description = "Result
return ResponseEntity.ok().body(loadFlowService.getStatus(resultUuid));
}

@PostMapping(value = "/results/statuses", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Get loadflow statuses from the database")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The loadflow statuses")})
public ResponseEntity<Map<UUID, LoadFlowStatus>> getStatuses(@Parameter(description = "Result uuids") @RequestBody List<UUID> resultUuids) {
return ResponseEntity.ok().body(loadFlowService.getStatuses(resultUuids));
}

@PutMapping(value = "/results/invalidate-status", produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Invalidate the loadflow status from the database")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The loadflow status has been invalidated")})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,4 @@
*/
@Repository
public interface GlobalStatusRepository extends JpaRepository<GlobalStatusEntity, UUID> {
GlobalStatusEntity findByResultUuid(UUID resultUuid);

void deleteByResultUuid(UUID resultUuid);

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;
import java.util.UUID;

/**
* @author Anis Touri <anis.touri at rte-france.com>
*/
@Repository
public interface ResultRepository extends JpaRepository<LoadFlowResultEntity, UUID> {
Optional<LoadFlowResultEntity> findByResultUuid(UUID resultUuid);

void deleteByResultUuid(UUID resultUuid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,13 @@ private static ExchangeMapEntryEntity toExchangeMapEntryEntity(UUID resultUuid,
@Transactional
public void delete(UUID resultUuid) {
Objects.requireNonNull(resultUuid);
globalStatusRepository.deleteByResultUuid(resultUuid);
resultRepository.deleteByResultUuid(resultUuid);
globalStatusRepository.deleteById(resultUuid);
resultRepository.deleteById(resultUuid);
}

public Optional<LoadFlowResultEntity> findResults(UUID resultUuid) {
Objects.requireNonNull(resultUuid);
return resultRepository.findByResultUuid(resultUuid);
return resultRepository.findById(resultUuid);
}

@Override
Expand All @@ -227,8 +227,16 @@ public void deleteAll() {
@Transactional(readOnly = true)
public LoadFlowStatus findStatus(UUID resultUuid) {
Objects.requireNonNull(resultUuid);
GlobalStatusEntity globalEntity = globalStatusRepository.findByResultUuid(resultUuid);
return globalEntity != null ? globalEntity.getStatus() : null;
Optional<GlobalStatusEntity> globalEntity = globalStatusRepository.findById(resultUuid);
return globalEntity.map(GlobalStatusEntity::getStatus).orElse(null);
}

@Override
@Transactional(readOnly = true)
public Map<UUID, LoadFlowStatus> findStatuses(List<UUID> resultUuids) {
Objects.requireNonNull(resultUuids);
List<GlobalStatusEntity> globalEntities = globalStatusRepository.findAllById(resultUuids);
return globalEntities.stream().collect(Collectors.toMap(GlobalStatusEntity::getResultUuid, GlobalStatusEntity::getStatus));
}
Comment on lines +236 to 240

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check GlobalStatusEntity field annotations for nullability constraints
rg -n -A2 'status' --type=java src/main/java/org/gridsuite/loadflow/server/entities/GlobalStatusEntity.java

Repository: gridsuite/loadflow-server

Length of output: 210


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== LoadFlowResultService excerpt =="
sed -n '220,245p' src/main/java/org/gridsuite/loadflow/server/service/LoadFlowResultService.java | cat -n

echo
echo "== GlobalStatusEntity file =="
sed -n '1,120p' src/main/java/org/gridsuite/loadflow/server/entities/GlobalStatusEntity.java | cat -n

echo
echo "== Search for status nullability / schema constraints =="
rg -n --hidden --glob '!target/**' --glob '!build/**' --glob '!dist/**' \
  '`@Column`\\(|nullable\\s*=\\s*false|status.*not null|not null.*status|GlobalStatusEntity' \
  src main resources .

Repository: gridsuite/loadflow-server

Length of output: 2975


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for globalStatus schema/migrations =="
rg -n --hidden --glob '!target/**' --glob '!build/**' --glob '!dist/**' \
  'globalStatus|global_status|status' \
  src/main/resources src/main/java

echo
echo "== Search for migration files =="
fd -a -t f '.*(sql|xml|yml|yaml)$' src/main/resources db . | sed -n '1,200p'

Repository: gridsuite/loadflow-server

Length of output: 11245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,40p' src/main/resources/db/changelog/changesets/changelog_20230606T161304Z.xml | cat -n

Repository: gridsuite/loadflow-server

Length of output: 3073


Handle nullable status in findStatuses

global_status.status is nullable in both the entity and the Liquibase DDL, so Collectors.toMap(...) can throw if any row has a null status. findStatus() already returns null in that case; make findStatuses() use a null-tolerant collector or filter nulls first.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/org/gridsuite/loadflow/server/service/LoadFlowResultService.java`
around lines 236 - 240, `LoadFlowResultService.findStatuses` currently uses
`Collectors.toMap` on `GlobalStatusEntity::getStatus`, which can fail when a row
has a null status. Update this method to match `findStatus()` behavior by making
the collection null-tolerant, either by filtering out null statuses before
collecting or by mapping them safely so `null` values do not break the result
map.


public List<ComponentResultEntity> findComponentResults(UUID resultUuid, List<ResourceFilterDTO> resourceFilters, Sort sort) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,39 @@ public void testStatus() {
assertEquals(LoadFlowStatus.NOT_DONE, mapper.readValue(result.getResponse().getContentAsString(), LoadFlowStatus.class));
}

@SneakyThrows
@Test
public void testStatuses() {
MvcResult result = mockMvc.perform(post(
"/" + VERSION + "/results/statuses")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(List.of(RESULT_UUID, OTHER_RESULT_UUID))))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
Map<UUID, LoadFlowStatus> statuses = mapper.readValue(result.getResponse().getContentAsString(),
new TypeReference<Map<UUID, LoadFlowStatus>>() {
});
assertTrue(statuses.isEmpty());

mockMvc.perform(put("/" + VERSION + "/results/invalidate-status?resultUuid=" + RESULT_UUID))
.andExpect(status().isOk());

result = mockMvc.perform(post(
"/" + VERSION + "/results/statuses")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(List.of(RESULT_UUID, OTHER_RESULT_UUID))))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
statuses = mapper.readValue(result.getResponse().getContentAsString(),
new TypeReference<Map<UUID, LoadFlowStatus>>() {
});
assertEquals(1, statuses.size());
assertEquals(LoadFlowStatus.NOT_DONE, statuses.get(RESULT_UUID));
assertFalse(statuses.containsKey(OTHER_RESULT_UUID));
}

@SneakyThrows
@Test
public void runWithReportTest() {
Expand Down Expand Up @@ -1041,7 +1074,7 @@ public void testCreateRunningStatus() throws Exception {
.andExpect(status().isOk())
.andReturn();
assertEquals(LoadFlowStatus.RUNNING, mapper.readValue(result.getResponse().getContentAsString(), LoadFlowStatus.class));
assertEquals(LoadFlowStatus.RUNNING, globalStatusRepository.findByResultUuid(resultUuid).getStatus());
assertEquals(LoadFlowStatus.RUNNING, globalStatusRepository.findById(resultUuid).get().getStatus());
}

@Test
Expand Down