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
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public record ProcessRunMessage<T extends ProcessConfig>(
@JsonSubTypes({
@JsonSubTypes.Type(value = SecurityAnalysisConfig.class, name = "SECURITY_ANALYSIS")
})
T config
T config,
boolean isDebug
) {
public ProcessType processType() {
return config.processType();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.monitor.commons.utils;

import java.util.UUID;

/**
* @author Kevin Le Saulnier <kevin.le-saulnier at rte-france.com>
*/
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
public class S3PathUtils {
public static final String S3_DELIMITER = "/";

/**
* Builds root path used to build debug file location
* @param executionEnvName
* @param processType
* @param executionId
* @return {executionEnvName}_debug/process/{processType}/{executionId}
*/
public static String toDebugLocation(String executionEnvName, String processType, UUID executionId) {
return String.join(S3_DELIMITER, executionEnvName + "_debug", "process", processType, executionId.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ public MonitorController(MonitorService monitorService) {
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis execution has been started")})
public ResponseEntity<UUID> executeSecurityAnalysis(
@RequestParam UUID caseUuid,
@RequestParam(required = false, defaultValue = "false") boolean isDebug,
@RequestBody SecurityAnalysisConfig securityAnalysisConfig,
@RequestHeader(HEADER_USER_ID) String userId) {
UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig);
UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, isDebug);
return ResponseEntity.ok(executionId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public class ProcessExecutionEntity {
@Column
private String userId;

@Column
private boolean isDebug;
Comment on lines +58 to +59
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Lombok boolean isDebug generates asymmetric accessor names — potential serialization/mapping issues.

With Lombok, a primitive boolean field named isDebug generates getter isDebug() but setter setDebug(boolean). This asymmetry can cause issues with Jackson deserialization, JPA property access, and @Builder usage. Consider renaming the field to debug so Lombok generates the conventional isDebug() / setDebug() pair consistently.

Proposed fix
     `@Column`
-    private boolean isDebug;
+    private boolean debug;

This will produce isDebug() and setDebug(boolean) — proper JavaBeans convention. Ensure the JPA column mapping still resolves to is_debug (or add @Column(name = "is_debug") explicitly).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Column
private boolean isDebug;
`@Column`(name = "is_debug")
private boolean debug;
🤖 Prompt for AI Agents
In
`@monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java`
around lines 58 - 59, Rename the boolean field isDebug in the
ProcessExecutionEntity class to debug to avoid Lombok-generated asymmetric
accessors; update the field declaration (private boolean debug) and, to preserve
DB column name, add `@Column`(name = "is_debug") on the field, then update any
references/usages (getters, setters, builder/DTO mapping, tests) that relied on
isDebug to use the new debug property or its generated isDebug()/setDebug(...)
methods as appropriate so Jackson/JPA/Lombok produce consistent JavaBean
accessors.


@Column
private String debugFileLocation;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "execution_id", foreignKey = @ForeignKey(name = "processExecutionStep_processExecution_fk"))
@OrderBy("stepOrder ASC")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.gridsuite.monitor.commons.ProcessStatus;
import org.gridsuite.monitor.commons.ProcessType;
import org.gridsuite.monitor.commons.ResultInfos;
import org.gridsuite.monitor.commons.utils.S3PathUtils;
import org.gridsuite.monitor.server.dto.ProcessExecution;
import org.gridsuite.monitor.server.dto.ReportPage;
import org.gridsuite.monitor.server.entities.ProcessExecutionEntity;
Expand Down Expand Up @@ -49,17 +50,18 @@ public MonitorService(ProcessExecutionRepository executionRepository,
}

@Transactional
public UUID executeProcess(UUID caseUuid, String userId, ProcessConfig processConfig) {
public UUID executeProcess(UUID caseUuid, String userId, ProcessConfig processConfig, boolean isDebug) {
ProcessExecutionEntity execution = ProcessExecutionEntity.builder()
.type(processConfig.processType().name())
.caseUuid(caseUuid)
.status(ProcessStatus.SCHEDULED)
.scheduledAt(Instant.now())
.userId(userId)
.isDebug(isDebug)
.build();
executionRepository.save(execution);

notificationService.sendProcessRunMessage(caseUuid, processConfig, execution.getId());
notificationService.sendProcessRunMessage(caseUuid, processConfig, isDebug, execution.getId());

return execution.getId();
}
Expand All @@ -77,6 +79,10 @@ public void updateExecutionStatus(UUID executionId, ProcessStatus status, String
if (completedAt != null) {
execution.setCompletedAt(completedAt);
}
if (execution.isDebug() && executionEnvName != null
&& (status.equals(ProcessStatus.COMPLETED) || status.equals(ProcessStatus.FAILED))) {
execution.setDebugFileLocation(S3PathUtils.toDebugLocation(executionEnvName, execution.getType(), executionId));
}
executionRepository.save(execution);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ public class NotificationService {

private final StreamBridge publisher;

public void sendProcessRunMessage(UUID caseUuid, ProcessConfig processConfig, UUID executionId) {
public void sendProcessRunMessage(UUID caseUuid, ProcessConfig processConfig, boolean isDebug, UUID executionId) {
String bindingName = switch (processConfig.processType()) {
case SECURITY_ANALYSIS -> "publishRunSecurityAnalysis-out-0";
};
ProcessRunMessage<?> message = new ProcessRunMessage<>(executionId, caseUuid, processConfig);
ProcessRunMessage<?> message = new ProcessRunMessage<>(executionId, caseUuid, processConfig, isDebug);
publisher.send(bindingName, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="lesaulnierkev (generated)" id="1770884531328-1">
<addColumn tableName="process_execution">
<column name="debug_file_location" type="varchar(255)"/>
</addColumn>
</changeSet>
<changeSet author="lesaulnierkev (generated)" id="1770884531328-2">
<addColumn tableName="process_execution">
<column name="is_debug" type="boolean" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ databaseChangeLog:
- include:
file: changesets/changelog_20260130T160426Z.xml
relativeToChangelogFile: true

- include:
file: changesets/changelog_20260212T082157Z.xml
relativeToChangelogFile: true
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ void securityAnalysisProcessIT() throws Exception {
UUID.randomUUID(),
List.of("contingency1", "contingency2"),
List.of(UUID.randomUUID()));
UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig);
UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, false);

// Verify message was published
Message<byte[]> sentMessage = outputDestination.receive(1000, PROCESS_SA_RUN_DESTINATION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@
import org.gridsuite.monitor.server.dto.Severity;
import org.gridsuite.monitor.server.services.MonitorService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;

import java.time.Instant;
import java.util.List;
Expand Down Expand Up @@ -54,8 +58,10 @@ class MonitorControllerTest {
@MockitoBean
private MonitorService monitorService;

@Test
void executeSecurityAnalysisShouldReturnExecutionId() throws Exception {
@ParameterizedTest
@ValueSource(booleans = {true, false})
@NullSource
void executeSecurityAnalysisShouldReturnExecutionId(Boolean isDebug) throws Exception {
UUID caseUuid = UUID.randomUUID();
UUID parametersUuid = UUID.randomUUID();
UUID modificationUuid = UUID.randomUUID();
Expand All @@ -65,20 +71,27 @@ void executeSecurityAnalysisShouldReturnExecutionId() throws Exception {
List.of("contingency1", "contingency2"),
List.of(modificationUuid)
);
boolean expectedDebugValue = Boolean.TRUE.equals(isDebug);

when(monitorService.executeProcess(any(UUID.class), any(String.class), any(SecurityAnalysisConfig.class)))
when(monitorService.executeProcess(any(UUID.class), any(String.class), any(SecurityAnalysisConfig.class), eq(expectedDebugValue)))
.thenReturn(executionId);

mockMvc.perform(post("/v1/execute/security-analysis")
.param("caseUuid", caseUuid.toString())
.header("userId", "user1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(config)))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$").value(executionId.toString()));
MockHttpServletRequestBuilder request = post("/v1/execute/security-analysis")
.param("caseUuid", caseUuid.toString())
.header("userId", "user1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(config));

if (isDebug != null) {
request.param("isDebug", isDebug.toString());
}

mockMvc.perform(request)
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$").value(executionId.toString()));

verify(monitorService).executeProcess(eq(caseUuid), any(String.class), any(SecurityAnalysisConfig.class));
verify(monitorService).executeProcess(eq(caseUuid), any(String.class), any(SecurityAnalysisConfig.class), eq(expectedDebugValue));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package org.gridsuite.monitor.server.services;

import org.gridsuite.monitor.commons.*;
import org.gridsuite.monitor.commons.utils.S3PathUtils;
import org.gridsuite.monitor.server.dto.ProcessExecution;
import org.gridsuite.monitor.server.dto.ReportLog;
import org.gridsuite.monitor.server.dto.ReportPage;
Expand All @@ -17,6 +18,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
Expand Down Expand Up @@ -69,8 +73,9 @@ void setUp() {
);
}

@Test
void executeProcessCreateExecutionAndSendNotification() {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void executeProcessCreateExecutionAndSendNotification(boolean isDebug) {
UUID expectedExecutionId = UUID.randomUUID();
when(executionRepository.save(any(ProcessExecutionEntity.class)))
.thenAnswer(invocation -> {
Expand All @@ -79,7 +84,7 @@ void executeProcessCreateExecutionAndSendNotification() {
return entity;
});

UUID result = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig);
UUID result = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig, isDebug);

assertThat(result).isNotNull();
verify(executionRepository).save(argThat(execution ->
Expand All @@ -94,6 +99,7 @@ void executeProcessCreateExecutionAndSendNotification() {
verify(notificationService).sendProcessRunMessage(
caseUuid,
securityAnalysisConfig,
isDebug,
result
);
}
Expand Down Expand Up @@ -145,6 +151,62 @@ void updateExecutionStatusShouldUpdateAllFields() {
verify(executionRepository).save(execution);
}

@ParameterizedTest
@EnumSource(value = ProcessStatus.class, names = {"COMPLETED", "FAILED"})
void updateExecutionStatusWithDebugOnAndProcessStatusDoneShouldUpdateDebugLocation(ProcessStatus processStatus) {
ProcessExecutionEntity execution = ProcessExecutionEntity.builder()
.id(executionId)
.type(ProcessType.SECURITY_ANALYSIS.name())
.caseUuid(caseUuid)
.userId(userId)
.status(ProcessStatus.RUNNING)
.scheduledAt(Instant.now())
.isDebug(true)
.build();
when(executionRepository.findById(executionId)).thenReturn(Optional.of(execution));
String envName = "production-env";
Instant startedAt = Instant.now().minusSeconds(60);
Instant completedAt = Instant.now();

monitorService.updateExecutionStatus(executionId, processStatus, envName, startedAt, completedAt);

verify(executionRepository).findById(executionId);
// assert standard fields
assertThat(execution.getStatus()).isEqualTo(processStatus);
assertThat(execution.getExecutionEnvName()).isEqualTo(envName);
assertThat(execution.getStartedAt()).isEqualTo(startedAt);
assertThat(execution.getCompletedAt()).isEqualTo(completedAt);

// assert debug location field
assertThat(execution.getDebugFileLocation()).isEqualTo(S3PathUtils.toDebugLocation(envName, ProcessType.SECURITY_ANALYSIS.name(), executionId));
verify(executionRepository).save(execution);
}

@ParameterizedTest
@EnumSource(value = ProcessStatus.class, names = {"RUNNING", "SCHEDULED"})
void updateExecutionStatusWithDebugOnAndProcessStatusNotDoneShouldNotUpdateDebugLocation(ProcessStatus processStatus) {
ProcessExecutionEntity execution = ProcessExecutionEntity.builder()
.id(executionId)
.type(ProcessType.SECURITY_ANALYSIS.name())
.caseUuid(caseUuid)
.userId(userId)
.status(ProcessStatus.RUNNING)
.scheduledAt(Instant.now())
.isDebug(true)
.build();
when(executionRepository.findById(executionId)).thenReturn(Optional.of(execution));
String envName = "production-env";
Instant startedAt = Instant.now().minusSeconds(60);
Instant completedAt = Instant.now();

monitorService.updateExecutionStatus(executionId, processStatus, envName, startedAt, completedAt);

verify(executionRepository).findById(executionId);
// assert debug location field
assertThat(execution.getDebugFileLocation()).isNull();
verify(executionRepository).save(execution);
}

@Test
void updateExecutionStatusShouldHandleExecutionNotFound() {
when(executionRepository.findById(executionId)).thenReturn(Optional.empty());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import org.gridsuite.monitor.commons.ProcessRunMessage;
import org.gridsuite.monitor.commons.SecurityAnalysisConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
Expand Down Expand Up @@ -53,16 +54,18 @@ void setUp() {
);
}

@Test
void sendProcessRunMessage() {
notificationService.sendProcessRunMessage(caseUuid, securityAnalysisConfig, executionId);
@ParameterizedTest
@ValueSource(booleans = {true, false})
void sendProcessRunMessage(boolean isDebug) {
notificationService.sendProcessRunMessage(caseUuid, securityAnalysisConfig, isDebug, executionId);

verify(publisher).send(
eq("publishRunSecurityAnalysis-out-0"),
argThat((ProcessRunMessage<?> message) ->
message.executionId().equals(executionId) &&
message.caseUuid().equals(caseUuid) &&
message.config().equals(securityAnalysisConfig))
message.config().equals(securityAnalysisConfig) &&
message.isDebug() == isDebug)
);
}
}
6 changes: 6 additions & 0 deletions monitor-worker-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<properties>
<network-modification.version>0.61.0</network-modification.version>
<spring-cloud-aws-starter-s3>3.3.1</spring-cloud-aws-starter-s3>
</properties>

<build>
Expand Down Expand Up @@ -108,6 +109,11 @@
<groupId>com.powsybl</groupId>
<artifactId>powsybl-case-datasource-client</artifactId>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-s3</artifactId>
<version>${spring-cloud-aws-starter-s3}</version>
</dependency>
Comment on lines +112 to +116
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat monitor-worker-server/pom.xml | head -150

Repository: gridsuite/monitor-core

Length of output: 5736


🌐 Web query:

spring-cloud-aws-starter-s3 3.3.1 Maven Central repository

💡 Result:

Artifact coordinates on Maven Central:

  • GroupId: io.awspring.cloud
  • ArtifactId: spring-cloud-aws-starter-s3
  • Version: 3.3.1 [1][2]

Maven dependency

<dependency>
  <groupId>io.awspring.cloud</groupId>
  <artifactId>spring-cloud-aws-starter-s3</artifactId>
  <version>3.3.1</version>
</dependency>

[1]

Maven Central pages

https://central.sonatype.com/artifact/io.awspring.cloud/spring-cloud-aws-starter-s3/3.3.1
https://repo1.maven.org/maven2/io/awspring/cloud/spring-cloud-aws-starter-s3/3.3.1/

[2][3]

All versions directory (repo1)

https://repo1.maven.org/maven2/io/awspring/cloud/spring-cloud-aws-starter-s3/

[3]

Sources: [1] [2] [3]


🏁 Script executed:

rg "spring-cloud-aws-starter-s3" monitor-worker-server/pom.xml -A 2 -B 2

Repository: gridsuite/monitor-core

Length of output: 473


🏁 Script executed:

rg "spring.boot" monitor-worker-server/pom.xml -i

Repository: gridsuite/monitor-core

Length of output: 388


🏁 Script executed:

find . -name "pom.xml" -type f | grep -E "(parent|root|pom\.xml$)" | head -20

Repository: gridsuite/monitor-core

Length of output: 158


🏁 Script executed:

cat pom.xml | grep -A 20 "spring.boot\|Spring Boot" | head -50

Repository: gridsuite/monitor-core

Length of output: 48


🏁 Script executed:

rg "spring-boot" pom.xml -i --max-count 5

Repository: gridsuite/monitor-core

Length of output: 48


🏁 Script executed:

cat pom.xml | head -200

Repository: gridsuite/monitor-core

Length of output: 3477


🏁 Script executed:

rg "spring-boot\|spring.boot" . -t xml --max-count 20

Repository: gridsuite/monitor-core

Length of output: 48


🌐 Web query:

spring-cloud-aws-starter-s3 3.3.1 spring boot version compatibility requirements

💡 Result:

io.awspring.cloud:spring-cloud-aws-starter-s3:3.3.1 is part of Spring Cloud AWS 3.3.x, which is compatible with:

  • Spring Boot: 3.4.x
  • (and aligns with Spring Cloud: 2024.0.x, Spring Framework: 6.2.x)

So you should use Spring Boot 3.4.* with spring-cloud-aws-starter-s3 3.3.1. If you’re on Spring Boot 3.3.*, use Spring Cloud AWS 3.2.x instead. [1]

Sources
[1] Spring Cloud AWS “Compatibility with Spring Project Versions” table (awspring.io) (awspring.io)

Citations:


🌐 Web query:

powsybl-parent-ws 25 spring boot version

💡 Result:

com.powsybl:powsybl-parent-ws:25 sets the Spring Boot version to 3.3.3 (property maven.spring-boot.version). (central.sonatype.com)

Citations:


🏁 Script executed:

rg "spring.boot.version\|<version>" monitor-worker-server/pom.xml | head -20

Repository: gridsuite/monitor-core

Length of output: 48


Version 3.3.1 is incompatible with the project's Spring Boot 3.3.3. Spring Cloud AWS 3.3.1 requires Spring Boot 3.4.x. For Spring Boot 3.3.3, use spring-cloud-aws-starter-s3 version 3.2.x instead.

🤖 Prompt for AI Agents
In `@monitor-worker-server/pom.xml` around lines 112 - 116, The pom currently
declares io.awspring.cloud:spring-cloud-aws-starter-s3 with version property
${spring-cloud-aws-starter-s3} which is set to 3.3.1 (incompatible with Spring
Boot 3.3.3); update that property (or the dependency version) to a 3.2.x release
compatible with Spring Boot 3.3.3 so the dependency block for
spring-cloud-aws-starter-s3 uses a 3.2.x version instead of 3.3.1.


<!-- Runtime dependencies -->
<dependency>
Expand Down
Loading