From 0748f140963e93b8fc67c9d36014f9f7aa98480f Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Fri, 23 Jan 2026 15:05:51 +0100 Subject: [PATCH 1/5] Delete an execution Signed-off-by: Franck LECUYER --- .../server/controllers/MonitorController.java | 10 ++++ .../server/services/MonitorService.java | 54 ++++++++++++++++- .../server/services/ReportService.java | 8 +++ .../server/services/ResultProvider.java | 2 + .../server/services/ResultService.java | 9 +++ .../SecurityAnalysisResultProvider.java | 5 ++ .../services/SecurityAnalysisService.java | 12 ++++ .../controllers/MonitorControllerTest.java | 25 ++++++++ .../server/services/MonitorServiceTest.java | 58 +++++++++++++++++++ .../server/services/ReportServiceTest.java | 23 ++++++++ .../server/services/ResultServiceTest.java | 26 +++++++++ .../SecurityAnalysisResultProviderTest.java | 12 ++++ .../services/SecurityAnalysisServiceTest.java | 20 +++++++ 13 files changed, 262 insertions(+), 2 deletions(-) diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java index 3b74e8e..ce50f5a 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java @@ -59,4 +59,14 @@ public ResponseEntity> getExecutionResults(@Parameter(description = List results = monitorService.getResults(executionId); return ResponseEntity.ok(results); } + + @DeleteMapping("/executions/{executionId}") + @Operation(summary = "Delete an execution") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Execution was deleted"), + @ApiResponse(responseCode = "404", description = "Execution was not found")}) + public ResponseEntity deleteExecution(@PathVariable UUID executionId) { + return monitorService.deleteExecution(executionId) ? + ResponseEntity.ok().build() : + ResponseEntity.notFound().build(); + } } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java index fb6b053..0c71813 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java @@ -6,7 +6,6 @@ */ package org.gridsuite.monitor.server.services; -import lombok.RequiredArgsConstructor; import org.gridsuite.monitor.commons.ProcessConfig; import org.gridsuite.monitor.commons.ProcessExecutionStep; import org.gridsuite.monitor.commons.ProcessStatus; @@ -15,24 +14,39 @@ import org.gridsuite.monitor.server.entities.ProcessExecutionEntity; import org.gridsuite.monitor.server.entities.ProcessExecutionStepEntity; import org.gridsuite.monitor.server.repositories.ProcessExecutionRepository; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Instant; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; /** * @author Antoine Bouhours */ @Service -@RequiredArgsConstructor public class MonitorService { private final ProcessExecutionRepository executionRepository; private final NotificationService notificationService; private final ReportService reportService; private final ResultService resultService; + private final MonitorService self; + + public MonitorService(ProcessExecutionRepository executionRepository, + NotificationService notificationService, + ReportService reportService, + ResultService resultService, + @Lazy MonitorService monitorService) { + this.executionRepository = executionRepository; + this.notificationService = notificationService; + this.reportService = reportService; + this.resultService = resultService; + this.self = monitorService; + } @Transactional public UUID executeProcess(UUID caseUuid, ProcessConfig processConfig) { @@ -133,4 +147,40 @@ private List getResultInfos(UUID executionId) { .toList()) .orElse(List.of()); } + + @Transactional(readOnly = true) + public boolean getReportsAndResultsUuids(UUID executionId, List resultIds, List reportIds) { + Optional executionEntity = executionRepository.findById(executionId); + if (executionEntity.isPresent()) { + executionEntity.get().getSteps().forEach(step -> { + if (step.getResultId() != null && step.getResultType() != null) { + resultIds.add(new ResultInfos(step.getResultId(), step.getResultType())); + } + if (step.getReportId() != null) { + reportIds.add(step.getReportId()); + } + }); + return true; + } else { + return false; + } + } + + @Transactional + public void deleteExecutionEntity(UUID executionId) { + executionRepository.deleteById(executionId); + } + + public boolean deleteExecution(UUID executionId) { + List resultIds = new ArrayList<>(); + List reportIds = new ArrayList<>(); + boolean executionFound = self.getReportsAndResultsUuids(executionId, resultIds, reportIds); + if (executionFound) { + resultIds.forEach(resultService::deleteResult); + reportIds.forEach(reportService::deleteReport); + + self.deleteExecutionEntity(executionId); + } + return executionFound; + } } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ReportService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ReportService.java index 9a11052..d2f6dc1 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ReportService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ReportService.java @@ -52,4 +52,12 @@ public Report getReport(UUID reportId) { return restTemplate.exchange(this.getReportServerURI() + path, HttpMethod.GET, new HttpEntity<>(headers), new ParameterizedTypeReference() { }).getBody(); } + + public void deleteReport(UUID reportId) { + var path = UriComponentsBuilder.fromPath("{id}") + .buildAndExpand(reportId) + .toUriString(); + + restTemplate.delete(this.getReportServerURI() + path); + } } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultProvider.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultProvider.java index d80f407..e66f2d0 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultProvider.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultProvider.java @@ -17,4 +17,6 @@ public interface ResultProvider { ResultType getType(); String getResult(UUID resultId); + + void deleteResult(UUID resultId); } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultService.java index 04101ea..2a30a4a 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultService.java @@ -39,4 +39,13 @@ public String getResult(ResultInfos resultInfos) { throw new IllegalArgumentException("Unsupported result type: " + resultInfos.resultType()); } } + + public void deleteResult(ResultInfos resultInfos) { + ResultProvider provider = providers.get(resultInfos.resultType()); + if (provider != null) { + provider.deleteResult(resultInfos.resultUUID()); + } else { + throw new IllegalArgumentException("Unsupported result type: " + resultInfos.resultType()); + } + } } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/SecurityAnalysisResultProvider.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/SecurityAnalysisResultProvider.java index 9b99f2d..b446d1d 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/SecurityAnalysisResultProvider.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/SecurityAnalysisResultProvider.java @@ -31,4 +31,9 @@ public ResultType getType() { public String getResult(UUID resultId) { return securityAnalysisService.getResult(resultId); } + + @Override + public void deleteResult(UUID resultId) { + securityAnalysisService.deleteResult(resultId); + } } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/SecurityAnalysisService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/SecurityAnalysisService.java index 3779d74..0af66bf 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/SecurityAnalysisService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/SecurityAnalysisService.java @@ -16,6 +16,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import java.util.List; import java.util.UUID; /** @@ -52,4 +53,15 @@ public String getResult(UUID resultUuid) { return restTemplate.exchange(getSecurityAnalysisServerBaseUri() + path, HttpMethod.GET, null, String.class).getBody(); } + + public void deleteResult(UUID resultUuid) { + LOGGER.info("Deleting result {}", resultUuid); + + var path = UriComponentsBuilder.fromPath("/results") + .queryParam("resultsUuids", List.of(resultUuid)) + .build() + .toUriString(); + + restTemplate.delete(getSecurityAnalysisServerBaseUri() + path); + } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java index d26e755..ce1d41f 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -112,4 +113,28 @@ void getExecutionResultsShouldReturnListOfResults() throws Exception { verify(monitorService).getResults(executionId); } + + @Test + void deleteExecutionShouldReturnTrue() throws Exception { + UUID executionId = UUID.randomUUID(); + when(monitorService.deleteExecution(executionId)) + .thenReturn(Boolean.TRUE); + + mockMvc.perform(delete("/v1/executions/{executionId}", executionId)) + .andExpect(status().isOk()); + + verify(monitorService).deleteExecution(executionId); + } + + @Test + void deleteExecutionShouldReturnFalse() throws Exception { + UUID executionId = UUID.randomUUID(); + when(monitorService.deleteExecution(executionId)) + .thenReturn(Boolean.FALSE); + + mockMvc.perform(delete("/v1/executions/{executionId}", executionId)) + .andExpect(status().isNotFound()); + + verify(monitorService).deleteExecution(executionId); + } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java index 22985f3..f822043 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java @@ -17,6 +17,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; import java.time.Instant; import java.util.ArrayList; @@ -298,4 +299,61 @@ void getResultsShouldReturnResults() { verify(resultService, times(2)).getResult(any(ResultInfos.class)); } + @Test + void deleteExecutionShouldDeleteResultsAndReports() { + UUID reportId1 = UUID.randomUUID(); + UUID resultId1 = UUID.randomUUID(); + UUID reportId2 = UUID.randomUUID(); + UUID resultId2 = UUID.randomUUID(); + ProcessExecutionStepEntity step0 = ProcessExecutionStepEntity.builder() + .id(UUID.randomUUID()) + .stepOrder(0) + .reportId(reportId1) + .resultId(resultId1) + .build(); + ProcessExecutionStepEntity step1 = ProcessExecutionStepEntity.builder() + .id(UUID.randomUUID()) + .stepOrder(1) + .reportId(reportId2) + .resultId(resultId2) + .resultType(ResultType.SECURITY_ANALYSIS) + .build(); + ProcessExecutionEntity execution = ProcessExecutionEntity.builder() + .id(executionId) + .steps(List.of(step0, step1)) + .build(); + + when(executionRepository.findById(executionId)).thenReturn(Optional.of(execution)); + doNothing().when(executionRepository).deleteById(executionId); + + doNothing().when(reportService).deleteReport(reportId1); + doNothing().when(reportService).deleteReport(reportId2); + doNothing().when(resultService).deleteResult(any(ResultInfos.class)); + + ReflectionTestUtils.setField(monitorService, "self", monitorService); + + boolean done = monitorService.deleteExecution(executionId); + assertThat(done).isTrue(); + + verify(executionRepository).findById(executionId); + verify(executionRepository).deleteById(executionId); + verify(reportService).deleteReport(reportId1); + verify(reportService).deleteReport(reportId2); + verify(resultService, times(1)).deleteResult(any(ResultInfos.class)); + } + + @Test + void deleteExecutionShouldReturnFalseWhenExecutionNotFound() { + when(executionRepository.findById(executionId)).thenReturn(Optional.empty()); + + ReflectionTestUtils.setField(monitorService, "self", monitorService); + + boolean done = monitorService.deleteExecution(executionId); + assertThat(done).isFalse(); + + verify(executionRepository).findById(executionId); + verifyNoInteractions(reportService); + verifyNoInteractions(resultService); + verify(executionRepository, never()).deleteById(executionId); + } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ReportServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ReportServiceTest.java index 66aa7f5..6297a02 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ReportServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ReportServiceTest.java @@ -25,6 +25,7 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** @@ -72,4 +73,26 @@ void getReportFailed() { assertThatThrownBy(() -> reportService.getReport(reportId)).isInstanceOf(RestClientException.class); } + + @Test + void deleteReport() { + UUID reportId = UUID.randomUUID(); + + server.expect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andExpect(MockRestRequestMatchers.requestTo("http://report-server/v1/reports/" + reportId)) + .andRespond(MockRestResponseCreators.withSuccess()); + + assertThatNoException().isThrownBy(() -> reportService.deleteReport(reportId)); + } + + @Test + void deleteReportFailed() { + UUID reportId = UUID.randomUUID(); + + server.expect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andExpect(MockRestRequestMatchers.requestTo("http://report-server/v1/reports/" + reportId)) + .andRespond(MockRestResponseCreators.withServerError()); + + assertThatThrownBy(() -> reportService.deleteReport(reportId)).isInstanceOf(RestClientException.class); + } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ResultServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ResultServiceTest.java index acfdc4b..4c617c5 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ResultServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ResultServiceTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,4 +59,29 @@ void getResultShouldThrowExceptionWhenProviderNotFound() { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Unsupported result type: " + ResultType.SECURITY_ANALYSIS); } + + @Test + void deleteResultShouldDeleteFromCorrectProvider() { + UUID resultId = UUID.randomUUID(); + doNothing().when(resultProvider).deleteResult(resultId); + when(resultProvider.getType()).thenReturn(ResultType.SECURITY_ANALYSIS); + resultService = new ResultService(List.of(resultProvider)); + ResultInfos resultInfos = new ResultInfos(resultId, ResultType.SECURITY_ANALYSIS); + + resultService.deleteResult(resultInfos); + + verify(resultProvider).deleteResult(resultId); + } + + @Test + void deleteResultShouldThrowExceptionWhenProviderNotFound() { + UUID resultId = UUID.randomUUID(); + ResultInfos resultInfos = new ResultInfos(resultId, ResultType.SECURITY_ANALYSIS); + + ResultService emptyService = new ResultService(List.of()); + + assertThatThrownBy(() -> emptyService.deleteResult(resultInfos)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unsupported result type: " + ResultType.SECURITY_ANALYSIS); + } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/SecurityAnalysisResultProviderTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/SecurityAnalysisResultProviderTest.java index 29d5780..f02ba95 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/SecurityAnalysisResultProviderTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/SecurityAnalysisResultProviderTest.java @@ -44,4 +44,16 @@ void getResultShouldDelegateToSecurityAnalysisService() { verify(securityAnalysisService).getResult(id); verifyNoMoreInteractions(securityAnalysisService); } + + @Test + void deleteResultShouldDelegateToSecurityAnalysisService() { + UUID id = UUID.randomUUID(); + + doNothing().when(securityAnalysisService).deleteResult(id); + + provider.deleteResult(id); + + verify(securityAnalysisService).deleteResult(id); + verifyNoMoreInteractions(securityAnalysisService); + } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/SecurityAnalysisServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/SecurityAnalysisServiceTest.java index a236e6d..bf8b690 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/SecurityAnalysisServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/SecurityAnalysisServiceTest.java @@ -20,6 +20,7 @@ import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -71,4 +72,23 @@ void getResultFailed() { assertThatThrownBy(() -> securityAnalysisService.getResult(RESULT_UUID)) .isInstanceOf(RestClientException.class); } + + @Test + void deleteResult() { + server.expect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andExpect(MockRestRequestMatchers.requestTo("http://security-analysis-server/v1/results?resultsUuids=" + RESULT_UUID)) + .andRespond(MockRestResponseCreators.withSuccess()); + + assertThatNoException().isThrownBy(() -> securityAnalysisService.deleteResult(RESULT_UUID)); + } + + @Test + void deleteResultFailed() { + server.expect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andExpect(MockRestRequestMatchers.requestTo("http://security-analysis-server/v1/results?resultsUuids=" + RESULT_UUID)) + .andRespond(MockRestResponseCreators.withServerError()); + + assertThatThrownBy(() -> securityAnalysisService.deleteResult(RESULT_UUID)) + .isInstanceOf(RestClientException.class); + } } From 6180444dcf7a05f615d43f2b54a46c39f8be72b7 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Mon, 2 Feb 2026 13:25:24 +0100 Subject: [PATCH 2/5] Delete an execution given its id Signed-off-by: Franck LECUYER --- .../monitor/commons/ProcessConfig.java | 10 + .../commons/ProcessExecutionStatusUpdate.java | 1 + .../server/controllers/MonitorController.java | 19 +- .../controllers/ProcessConfigController.java | 88 +++++++++ .../monitor/server/dto/ProcessExecution.java | 31 ++++ .../entities/AbstractProcessConfigEntity.java | 65 +++++++ .../entities/ProcessExecutionEntity.java | 6 + .../SecurityAnalysisConfigEntity.java | 60 ++++++ .../server/mapper/ProcessExecutionMapper.java | 30 +++ .../mapper/SecurityAnalysisConfigMapper.java | 40 ++++ .../repositories/ProcessConfigRepository.java | 20 ++ .../ProcessExecutionRepository.java | 2 + .../server/services/ConsumerService.java | 2 +- .../server/services/MonitorService.java | 21 ++- .../server/services/ProcessConfigService.java | 78 ++++++++ .../changesets/changelog_20260130T160426Z.xml | 62 +++++++ .../db/changelog/db.changelog-master.yaml | 4 + .../server/MonitorIntegrationTest.java | 57 +++++- .../controllers/MonitorControllerTest.java | 28 ++- .../ProcessConfigControllerTest.java | 173 +++++++++++++++++ .../ProcessExecutionRepositoryTest.java | 72 +++++++ .../server/services/ConsumerServiceTest.java | 13 +- .../server/services/MonitorServiceTest.java | 76 +++++++- .../services/ProcessConfigServiceTest.java | 175 ++++++++++++++++++ .../services/ProcessExecutionService.java | 1 + 25 files changed, 1113 insertions(+), 21 deletions(-) create mode 100644 monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/ProcessConfigController.java create mode 100644 monitor-server/src/main/java/org/gridsuite/monitor/server/dto/ProcessExecution.java create mode 100644 monitor-server/src/main/java/org/gridsuite/monitor/server/entities/AbstractProcessConfigEntity.java create mode 100644 monitor-server/src/main/java/org/gridsuite/monitor/server/entities/SecurityAnalysisConfigEntity.java create mode 100644 monitor-server/src/main/java/org/gridsuite/monitor/server/mapper/ProcessExecutionMapper.java create mode 100644 monitor-server/src/main/java/org/gridsuite/monitor/server/mapper/SecurityAnalysisConfigMapper.java create mode 100644 monitor-server/src/main/java/org/gridsuite/monitor/server/repositories/ProcessConfigRepository.java create mode 100644 monitor-server/src/main/java/org/gridsuite/monitor/server/services/ProcessConfigService.java create mode 100644 monitor-server/src/main/resources/db/changelog/changesets/changelog_20260130T160426Z.xml create mode 100644 monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/ProcessConfigControllerTest.java create mode 100644 monitor-server/src/test/java/org/gridsuite/monitor/server/services/ProcessConfigServiceTest.java diff --git a/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessConfig.java b/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessConfig.java index 1613a1c..419e426 100644 --- a/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessConfig.java +++ b/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessConfig.java @@ -6,12 +6,22 @@ */ package org.gridsuite.monitor.commons; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + import java.util.List; import java.util.UUID; /** * @author Antoine Bouhours */ +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "processType" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = SecurityAnalysisConfig.class, name = "SECURITY_ANALYSIS") +}) public interface ProcessConfig { ProcessType processType(); diff --git a/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessExecutionStatusUpdate.java b/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessExecutionStatusUpdate.java index 614b24d..494e15b 100644 --- a/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessExecutionStatusUpdate.java +++ b/monitor-commons/src/main/java/org/gridsuite/monitor/commons/ProcessExecutionStatusUpdate.java @@ -23,5 +23,6 @@ public class ProcessExecutionStatusUpdate { private ProcessStatus status; private String executionEnvName; + private Instant startedAt; private Instant completedAt; } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java index ce50f5a..b8d60fe 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/MonitorController.java @@ -11,7 +11,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import org.gridsuite.monitor.commons.ProcessType; import org.gridsuite.monitor.commons.SecurityAnalysisConfig; +import org.gridsuite.monitor.server.dto.ProcessExecution; import org.gridsuite.monitor.server.dto.Report; import org.gridsuite.monitor.server.services.MonitorService; import org.springframework.http.ResponseEntity; @@ -30,6 +32,8 @@ public class MonitorController { private final MonitorService monitorService; + public static final String HEADER_USER_ID = "userId"; + public MonitorController(MonitorService monitorService) { this.monitorService = monitorService; } @@ -39,8 +43,9 @@ public MonitorController(MonitorService monitorService) { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis execution has been started")}) public ResponseEntity executeSecurityAnalysis( @RequestParam UUID caseUuid, - @RequestBody SecurityAnalysisConfig securityAnalysisConfig) { - UUID executionId = monitorService.executeProcess(caseUuid, securityAnalysisConfig); + @RequestBody SecurityAnalysisConfig securityAnalysisConfig, + @RequestHeader(HEADER_USER_ID) String userId) { + UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig); return ResponseEntity.ok(executionId); } @@ -60,13 +65,21 @@ public ResponseEntity> getExecutionResults(@Parameter(description = return ResponseEntity.ok(results); } + @GetMapping("/executions") + @Operation(summary = "Get launched processes") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The launched processes")}) + public ResponseEntity> getLaunchedProcesses(@Parameter(description = "Process type") @RequestParam(name = "processType") ProcessType processType) { + return ResponseEntity.ok(monitorService.getLaunchedProcesses(processType)); + } + @DeleteMapping("/executions/{executionId}") @Operation(summary = "Delete an execution") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Execution was deleted"), - @ApiResponse(responseCode = "404", description = "Execution was not found")}) + @ApiResponse(responseCode = "404", description = "Execution was not found")}) public ResponseEntity deleteExecution(@PathVariable UUID executionId) { return monitorService.deleteExecution(executionId) ? ResponseEntity.ok().build() : ResponseEntity.notFound().build(); } } + diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/ProcessConfigController.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/ProcessConfigController.java new file mode 100644 index 0000000..2b4e9c3 --- /dev/null +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/controllers/ProcessConfigController.java @@ -0,0 +1,88 @@ +/** + * 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.server.controllers; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.gridsuite.monitor.commons.ProcessConfig; +import org.gridsuite.monitor.server.services.ProcessConfigService; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author Franck Lecuyer + */ + +@RestController +@RequestMapping(value = "/" + MonitorApi.API_VERSION + "/process-configs") +@Tag(name = "Process config") +public class ProcessConfigController { + + private final ProcessConfigService processConfigService; + + public ProcessConfigController(ProcessConfigService processConfigService) { + this.processConfigService = processConfigService; + } + + @PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Create process config") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "process config was created")}) + public ResponseEntity createProcessConfig(@RequestBody ProcessConfig processConfig) { + return ResponseEntity.ok().body(processConfigService.createProcessConfig(processConfig)); + } + + @GetMapping(value = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Get process config") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "process config was returned"), + @ApiResponse(responseCode = "404", description = "process config was not found")}) + public ResponseEntity getProcessConfig( + @Parameter(description = "process config UUID") @PathVariable("uuid") UUID processConfigUuid) { + Optional processConfig = processConfigService.getProcessConfig(processConfigUuid); + return processConfig.map(config -> ResponseEntity.ok().body(config)).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @PutMapping(value = "/{uuid}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Update process config") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "process config was updated"), + @ApiResponse(responseCode = "404", description = "process config was not found")}) + public ResponseEntity updateProcessConfig( + @Parameter(description = "process config UUID") @PathVariable("uuid") UUID processConfigUuid, + @RequestBody ProcessConfig processConfig) { + return processConfigService.updateProcessConfig(processConfigUuid, processConfig) ? + ResponseEntity.ok().build() : + ResponseEntity.notFound().build(); + } + + @DeleteMapping(value = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Delete process config") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "process config was deleted"), + @ApiResponse(responseCode = "404", description = "process config was not found")}) + public ResponseEntity deleteProcessConfig( + @Parameter(description = "process config UUID") @PathVariable("uuid") UUID processConfigUuid) { + return processConfigService.deleteProcessConfig(processConfigUuid) ? + ResponseEntity.ok().build() : + ResponseEntity.notFound().build(); + } +} diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/dto/ProcessExecution.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/dto/ProcessExecution.java new file mode 100644 index 0000000..e307689 --- /dev/null +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/dto/ProcessExecution.java @@ -0,0 +1,31 @@ +/** + * 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.server.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import org.gridsuite.monitor.commons.ProcessStatus; + +import java.time.Instant; +import java.util.UUID; + +/** + * @author Franck Lecuyer + */ +@Schema(description = "Process execution data") +@Builder +public record ProcessExecution( + UUID id, + String type, + UUID caseUuid, + ProcessStatus status, + String executionEnvName, + Instant scheduledAt, + Instant startedAt, + Instant completedAt, + String userId +) { } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/AbstractProcessConfigEntity.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/AbstractProcessConfigEntity.java new file mode 100644 index 0000000..0a4e6f3 --- /dev/null +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/AbstractProcessConfigEntity.java @@ -0,0 +1,65 @@ +/** + * 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.server.entities; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.Table; +import jakarta.persistence.ForeignKey; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.gridsuite.monitor.commons.ProcessType; + +import java.util.List; +import java.util.UUID; + +import static jakarta.persistence.DiscriminatorType.STRING; + +/** + * @author Franck Lecuyer + */ +@Entity +@Table(name = "process_config") +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorColumn(name = "process_type", discriminatorType = STRING) +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public abstract class AbstractProcessConfigEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "process_config_modifications", + joinColumns = @JoinColumn(name = "process_config_id"), + foreignKey = @ForeignKey(name = "AbstractProcessConfigEntity_modificationUuids_fk1")) + @Column(name = "modification_uuid") + @OrderColumn(name = "pos_modifications") + private List modificationUuids; + + public abstract ProcessType getType(); +} + + + + diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java index 4e534f7..0f1a3dd 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java @@ -46,9 +46,15 @@ public class ProcessExecutionEntity { @Column private Instant scheduledAt; + @Column + private Instant startedAt; + @Column private Instant completedAt; + @Column + private String userId; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "execution_id", foreignKey = @ForeignKey(name = "processExecutionStep_processExecution_fk")) @OrderBy("stepOrder ASC") diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/SecurityAnalysisConfigEntity.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/SecurityAnalysisConfigEntity.java new file mode 100644 index 0000000..59b924d --- /dev/null +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/SecurityAnalysisConfigEntity.java @@ -0,0 +1,60 @@ +/** + * 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.server.entities; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.gridsuite.monitor.commons.ProcessType; + +import java.util.List; +import java.util.UUID; + +/** + * @author Franck Lecuyer + */ +@Entity +@Table(name = "security_analysis_config") +@DiscriminatorValue("SECURITY_ANALYSIS") +@PrimaryKeyJoinColumn(foreignKey = @ForeignKey(name = "securityAnalysisConfig_id_fk_constraint")) +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class SecurityAnalysisConfigEntity extends AbstractProcessConfigEntity { + @Column(name = "parameters_uuid") + private UUID parametersUuid; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "security_analysis_contingencies", + joinColumns = @JoinColumn(name = "security_analysis_config_id"), + foreignKey = @ForeignKey(name = "SecurityAnalysisConfigEntity_contingencies_fk1")) + @Column(name = "contingency") + @OrderColumn(name = "pos_contingencies") + private List contingencies; + + @Override + public ProcessType getType() { + return ProcessType.SECURITY_ANALYSIS; + } +} + + + + diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/mapper/ProcessExecutionMapper.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/mapper/ProcessExecutionMapper.java new file mode 100644 index 0000000..d0cae4c --- /dev/null +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/mapper/ProcessExecutionMapper.java @@ -0,0 +1,30 @@ +/** + * 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.server.mapper; + +import org.gridsuite.monitor.server.dto.ProcessExecution; +import org.gridsuite.monitor.server.entities.ProcessExecutionEntity; +import org.springframework.stereotype.Component; + +/** + * @author Franck Lecuyer + */ +@Component +public class ProcessExecutionMapper { + public ProcessExecution toDto(ProcessExecutionEntity entity) { + return new ProcessExecution( + entity.getId(), + entity.getType(), + entity.getCaseUuid(), + entity.getStatus(), + entity.getExecutionEnvName(), + entity.getScheduledAt(), + entity.getStartedAt(), + entity.getCompletedAt(), + entity.getUserId()); + } +} diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/mapper/SecurityAnalysisConfigMapper.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/mapper/SecurityAnalysisConfigMapper.java new file mode 100644 index 0000000..1b00ccd --- /dev/null +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/mapper/SecurityAnalysisConfigMapper.java @@ -0,0 +1,40 @@ +/** + * 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.server.mapper; + +import org.gridsuite.monitor.commons.SecurityAnalysisConfig; +import org.gridsuite.monitor.server.entities.SecurityAnalysisConfigEntity; +import org.springframework.stereotype.Component; + +/** + * @author Franck Lecuyer + */ +@Component +public class SecurityAnalysisConfigMapper { + + public SecurityAnalysisConfigEntity toEntity(SecurityAnalysisConfig dto) { + SecurityAnalysisConfigEntity entity = new SecurityAnalysisConfigEntity(); + entity.setParametersUuid(dto.parametersUuid()); + entity.setContingencies(dto.contingencies()); + entity.setModificationUuids(dto.modificationUuids()); + return entity; + } + + public SecurityAnalysisConfig toDto(SecurityAnalysisConfigEntity entity) { + return new SecurityAnalysisConfig( + entity.getParametersUuid(), + entity.getContingencies(), + entity.getModificationUuids() + ); + } + + public void update(SecurityAnalysisConfigEntity entity, SecurityAnalysisConfig dto) { + entity.setParametersUuid(dto.parametersUuid()); + entity.setContingencies(dto.contingencies()); + entity.setModificationUuids(dto.modificationUuids()); + } +} diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/repositories/ProcessConfigRepository.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/repositories/ProcessConfigRepository.java new file mode 100644 index 0000000..47fda5f --- /dev/null +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/repositories/ProcessConfigRepository.java @@ -0,0 +1,20 @@ +/** + * 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.server.repositories; + +import org.gridsuite.monitor.server.entities.AbstractProcessConfigEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +/** + * @author Franck Lecuyer + */ +@Repository +public interface ProcessConfigRepository extends JpaRepository { +} diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/repositories/ProcessExecutionRepository.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/repositories/ProcessExecutionRepository.java index c40af8a..5c2938a 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/repositories/ProcessExecutionRepository.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/repositories/ProcessExecutionRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.UUID; /** @@ -17,4 +18,5 @@ */ @Repository public interface ProcessExecutionRepository extends JpaRepository { + List findByTypeAndStartedAtIsNotNullOrderByStartedAtDesc(String type); } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerService.java index a6578b3..927396f 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerService.java @@ -60,7 +60,7 @@ public Consumer> consumeMonitorUpdate() { private void handleExecutionStatusUpdate(UUID executionId, Message message) { ProcessExecutionStatusUpdate payload = parsePayload(message.getPayload(), ProcessExecutionStatusUpdate.class); - monitorService.updateExecutionStatus(executionId, payload.getStatus(), payload.getExecutionEnvName(), payload.getCompletedAt()); + monitorService.updateExecutionStatus(executionId, payload.getStatus(), payload.getExecutionEnvName(), payload.getStartedAt(), payload.getCompletedAt()); } private void handleStepStatusUpdate(UUID executionId, Message message) { diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java index 0c71813..f73d47a 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java @@ -9,10 +9,13 @@ import org.gridsuite.monitor.commons.ProcessConfig; import org.gridsuite.monitor.commons.ProcessExecutionStep; import org.gridsuite.monitor.commons.ProcessStatus; +import org.gridsuite.monitor.commons.ProcessType; import org.gridsuite.monitor.commons.ResultInfos; +import org.gridsuite.monitor.server.dto.ProcessExecution; import org.gridsuite.monitor.server.dto.Report; import org.gridsuite.monitor.server.entities.ProcessExecutionEntity; import org.gridsuite.monitor.server.entities.ProcessExecutionStepEntity; +import org.gridsuite.monitor.server.mapper.ProcessExecutionMapper; import org.gridsuite.monitor.server.repositories.ProcessExecutionRepository; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -35,26 +38,30 @@ public class MonitorService { private final ReportService reportService; private final ResultService resultService; private final MonitorService self; + private final ProcessExecutionMapper processExecutionMapper; public MonitorService(ProcessExecutionRepository executionRepository, NotificationService notificationService, ReportService reportService, ResultService resultService, - @Lazy MonitorService monitorService) { + @Lazy MonitorService monitorService, + ProcessExecutionMapper processExecutionMapper) { this.executionRepository = executionRepository; this.notificationService = notificationService; this.reportService = reportService; this.resultService = resultService; this.self = monitorService; + this.processExecutionMapper = processExecutionMapper; } @Transactional - public UUID executeProcess(UUID caseUuid, ProcessConfig processConfig) { + public UUID executeProcess(UUID caseUuid, String userId, ProcessConfig processConfig) { ProcessExecutionEntity execution = ProcessExecutionEntity.builder() .type(processConfig.processType().name()) .caseUuid(caseUuid) .status(ProcessStatus.SCHEDULED) .scheduledAt(Instant.now()) + .userId(userId) .build(); executionRepository.save(execution); @@ -64,12 +71,15 @@ public UUID executeProcess(UUID caseUuid, ProcessConfig processConfig) { } @Transactional - public void updateExecutionStatus(UUID executionId, ProcessStatus status, String executionEnvName, Instant completedAt) { + public void updateExecutionStatus(UUID executionId, ProcessStatus status, String executionEnvName, Instant startedAt, Instant completedAt) { executionRepository.findById(executionId).ifPresent(execution -> { execution.setStatus(status); if (executionEnvName != null) { execution.setExecutionEnvName(executionEnvName); } + if (startedAt != null) { + execution.setStartedAt(startedAt); + } if (completedAt != null) { execution.setCompletedAt(completedAt); } @@ -148,6 +158,11 @@ private List getResultInfos(UUID executionId) { .orElse(List.of()); } + public List getLaunchedProcesses(ProcessType processType) { + return executionRepository.findByTypeAndStartedAtIsNotNullOrderByStartedAtDesc(processType.name()).stream() + .map(processExecutionMapper::toDto).toList(); + } + @Transactional(readOnly = true) public boolean getReportsAndResultsUuids(UUID executionId, List resultIds, List reportIds) { Optional executionEntity = executionRepository.findById(executionId); diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ProcessConfigService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ProcessConfigService.java new file mode 100644 index 0000000..de235b8 --- /dev/null +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ProcessConfigService.java @@ -0,0 +1,78 @@ +/** + * 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.server.services; + +import org.gridsuite.monitor.commons.ProcessConfig; +import org.gridsuite.monitor.commons.SecurityAnalysisConfig; +import org.gridsuite.monitor.server.entities.AbstractProcessConfigEntity; +import org.gridsuite.monitor.server.entities.SecurityAnalysisConfigEntity; +import org.gridsuite.monitor.server.mapper.SecurityAnalysisConfigMapper; +import org.gridsuite.monitor.server.repositories.ProcessConfigRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author Franck Lecuyer + */ +@Service +public class ProcessConfigService { + private final ProcessConfigRepository processConfigRepository; + + private final SecurityAnalysisConfigMapper securityAnalysisConfigMapper; + + public ProcessConfigService(ProcessConfigRepository processConfigRepository, SecurityAnalysisConfigMapper securityAnalysisConfigMapper) { + this.processConfigRepository = processConfigRepository; + this.securityAnalysisConfigMapper = securityAnalysisConfigMapper; + } + + @Transactional + public UUID createProcessConfig(ProcessConfig processConfig) { + AbstractProcessConfigEntity entity = switch (processConfig) { + case SecurityAnalysisConfig sac -> securityAnalysisConfigMapper.toEntity(sac); + default -> throw new IllegalArgumentException("Unsupported process config type: " + processConfig.processType()); + }; + return processConfigRepository.save(entity).getId(); + } + + @Transactional(readOnly = true) + public Optional getProcessConfig(UUID processConfigUuid) { + return processConfigRepository.findById(processConfigUuid).flatMap(entity -> switch (entity) { + case SecurityAnalysisConfigEntity sae -> + Optional.of((ProcessConfig) securityAnalysisConfigMapper.toDto(sae)); + default -> throw new IllegalArgumentException("Unsupported entity type: " + entity.getType()); + }); + } + + @Transactional + public boolean updateProcessConfig(UUID processConfigUuid, ProcessConfig processConfig) { + return processConfigRepository.findById(processConfigUuid) + .map(entity -> { + if (entity.getType() != processConfig.processType()) { + throw new IllegalArgumentException("Process config type mismatch : " + entity.getType()); + } + switch (processConfig) { + case SecurityAnalysisConfig sac -> + securityAnalysisConfigMapper.update((SecurityAnalysisConfigEntity) entity, sac); + default -> throw new IllegalArgumentException("Unsupported process config type: " + processConfig.processType()); + } + return true; + }) + .orElse(false); + } + + @Transactional + public boolean deleteProcessConfig(UUID processConfigUuid) { + if (processConfigRepository.existsById(processConfigUuid)) { + processConfigRepository.deleteById(processConfigUuid); + return true; + } + return false; + } +} diff --git a/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260130T160426Z.xml b/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260130T160426Z.xml new file mode 100644 index 0000000..e9ebc6b --- /dev/null +++ b/monitor-server/src/main/resources/db/changelog/changesets/changelog_20260130T160426Z.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/monitor-server/src/main/resources/db/changelog/db.changelog-master.yaml b/monitor-server/src/main/resources/db/changelog/db.changelog-master.yaml index e147c40..44dd608 100644 --- a/monitor-server/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/monitor-server/src/main/resources/db/changelog/db.changelog-master.yaml @@ -5,3 +5,7 @@ databaseChangeLog: - include: file: changesets/changelog_20260115T144727Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20260130T160426Z.xml + relativeToChangelogFile: true + diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/MonitorIntegrationTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/MonitorIntegrationTest.java index 9eba7c2..95df467 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/MonitorIntegrationTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/MonitorIntegrationTest.java @@ -10,8 +10,10 @@ import org.gridsuite.monitor.commons.*; import org.gridsuite.monitor.server.dto.Report; import org.gridsuite.monitor.server.entities.ProcessExecutionEntity; +import org.gridsuite.monitor.server.repositories.ProcessConfigRepository; import org.gridsuite.monitor.server.repositories.ProcessExecutionRepository; import org.gridsuite.monitor.server.services.ConsumerService; +import org.gridsuite.monitor.server.services.ProcessConfigService; import org.gridsuite.monitor.server.services.ReportService; import org.gridsuite.monitor.server.services.MonitorService; import org.gridsuite.monitor.server.services.ResultService; @@ -30,7 +32,9 @@ import org.springframework.test.web.servlet.MockMvc; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.Optional; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -50,9 +54,15 @@ class MonitorIntegrationTest { @Autowired private MonitorService monitorService; + @Autowired + private ProcessConfigService configService; + @Autowired private ProcessExecutionRepository executionRepository; + @Autowired + private ProcessConfigRepository processConfigRepository; + @Autowired private ConsumerService consumerService; @@ -73,11 +83,14 @@ class MonitorIntegrationTest { private UUID caseUuid; + private String userId; + public static final String PROCESS_SA_RUN_DESTINATION = "monitor.process.securityanalysis.run"; @BeforeEach void setUp() { caseUuid = UUID.randomUUID(); + userId = "user1"; } @Test @@ -87,7 +100,7 @@ void securityAnalysisProcessIT() throws Exception { UUID.randomUUID(), List.of("contingency1", "contingency2"), List.of(UUID.randomUUID())); - UUID executionId = monitorService.executeProcess(caseUuid, securityAnalysisConfig); + UUID executionId = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig); // Verify message was published Message sentMessage = outputDestination.receive(1000, PROCESS_SA_RUN_DESTINATION); @@ -145,10 +158,13 @@ void securityAnalysisProcessIT() throws Exception { assertThat(execution.getSteps().get(1).getResultId()).isEqualTo(resultId1); // Complete the execution via message + Instant startedAt = Instant.now(); + Instant completedAt = Instant.now(); ProcessExecutionStatusUpdate finalStatus = ProcessExecutionStatusUpdate.builder() .status(ProcessStatus.COMPLETED) .executionEnvName("test-env") - .completedAt(Instant.now()) + .startedAt(startedAt) + .completedAt(completedAt) .build(); sendMessage(executionId, finalStatus, MessageType.EXECUTION_STATUS_UPDATE); @@ -156,6 +172,8 @@ void securityAnalysisProcessIT() throws Exception { execution = executionRepository.findById(executionId).orElse(null); assertThat(execution.getStatus()).isEqualTo(ProcessStatus.COMPLETED); assertThat(execution.getExecutionEnvName()).isEqualTo("test-env"); + assertThat(execution.getStartedAt().truncatedTo(ChronoUnit.MILLIS)).isEqualTo(startedAt.truncatedTo(ChronoUnit.MILLIS)); + assertThat(execution.getCompletedAt().truncatedTo(ChronoUnit.MILLIS)).isEqualTo(completedAt.truncatedTo(ChronoUnit.MILLIS)); // Mock the report service responses Report report0 = new Report(reportId0, null, "Load Network Report", null, List.of()); @@ -196,4 +214,39 @@ private void sendMessage(UUID executionId, Object step, MessageType messageType) .build(); consumerService.consumeMonitorUpdate().accept(message); } + + @Test + void processConfigIT() { + UUID parametersUuid = UUID.randomUUID(); + UUID modificationUuid = UUID.randomUUID(); + SecurityAnalysisConfig securityAnalysisConfig = new SecurityAnalysisConfig( + parametersUuid, + List.of("contingency1", "contingency2"), + List.of(modificationUuid) + ); + UUID configId = configService.createProcessConfig(securityAnalysisConfig); + assertThat(processConfigRepository.findById(configId)).isNotEmpty(); + + Optional config = configService.getProcessConfig(configId); + assertThat(config).isNotEmpty(); + assertThat(config.get()).usingRecursiveComparison().isEqualTo(securityAnalysisConfig); + + UUID updatedParametersUuid = UUID.randomUUID(); + UUID updatedModificationUuid = UUID.randomUUID(); + SecurityAnalysisConfig updatedSecurityAnalysisConfig = new SecurityAnalysisConfig( + updatedParametersUuid, + List.of("contingency3", "contingency4"), + List.of(updatedModificationUuid) + ); + boolean updated = configService.updateProcessConfig(configId, updatedSecurityAnalysisConfig); + assertThat(updated).isTrue(); + Optional updatedConfig = configService.getProcessConfig(configId); + assertThat(updatedConfig).isNotEmpty(); + assertThat(updatedConfig.get()).usingRecursiveComparison().isEqualTo(updatedSecurityAnalysisConfig); + + boolean deleted = configService.deleteProcessConfig(configId); + assertThat(deleted).isTrue(); + Optional deletedConfig = configService.getProcessConfig(configId); + assertThat(deletedConfig).isEmpty(); + } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java index ce1d41f..981ccee 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/MonitorControllerTest.java @@ -7,7 +7,10 @@ package org.gridsuite.monitor.server.controllers; import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.monitor.commons.ProcessStatus; +import org.gridsuite.monitor.commons.ProcessType; import org.gridsuite.monitor.commons.SecurityAnalysisConfig; +import org.gridsuite.monitor.server.dto.ProcessExecution; import org.gridsuite.monitor.server.dto.Report; import org.gridsuite.monitor.server.dto.Severity; import org.gridsuite.monitor.server.services.MonitorService; @@ -18,6 +21,7 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; +import java.time.Instant; import java.util.List; import java.util.UUID; @@ -58,18 +62,19 @@ void executeSecurityAnalysisShouldReturnExecutionId() throws Exception { List.of(modificationUuid) ); - when(monitorService.executeProcess(any(UUID.class), any(SecurityAnalysisConfig.class))) + when(monitorService.executeProcess(any(UUID.class), any(String.class), any(SecurityAnalysisConfig.class))) .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())); - verify(monitorService).executeProcess(eq(caseUuid), any(SecurityAnalysisConfig.class)); + verify(monitorService).executeProcess(eq(caseUuid), any(String.class), any(SecurityAnalysisConfig.class)); } @Test @@ -114,6 +119,25 @@ void getExecutionResultsShouldReturnListOfResults() throws Exception { verify(monitorService).getResults(executionId); } + @Test + void getLaunchedProcesses() throws Exception { + ProcessExecution processExecution1 = new ProcessExecution(UUID.randomUUID(), ProcessType.SECURITY_ANALYSIS.name(), UUID.randomUUID(), ProcessStatus.COMPLETED, "env1", Instant.now().minusSeconds(80), Instant.now().minusSeconds(60), Instant.now().minusSeconds(30), "user1"); + ProcessExecution processExecution2 = new ProcessExecution(UUID.randomUUID(), ProcessType.SECURITY_ANALYSIS.name(), UUID.randomUUID(), ProcessStatus.FAILED, "env2", Instant.now().minusSeconds(70), Instant.now().minusSeconds(50), null, "user2"); + ProcessExecution processExecution3 = new ProcessExecution(UUID.randomUUID(), ProcessType.SECURITY_ANALYSIS.name(), UUID.randomUUID(), ProcessStatus.RUNNING, "env3", Instant.now().minusSeconds(50), Instant.now().minusSeconds(40), null, "user3"); + + List processExecutionList = List.of(processExecution1, processExecution2, processExecution3); + + when(monitorService.getLaunchedProcesses(ProcessType.SECURITY_ANALYSIS)).thenReturn(processExecutionList); + + mockMvc.perform(get("/v1/executions?processType=SECURITY_ANALYSIS").accept(MediaType.APPLICATION_JSON_VALUE).header("userId", "user1,user2,user3")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(3))) + .andExpect(content().json(objectMapper.writeValueAsString(processExecutionList))); + + verify(monitorService).getLaunchedProcesses(ProcessType.SECURITY_ANALYSIS); + } + @Test void deleteExecutionShouldReturnTrue() throws Exception { UUID executionId = UUID.randomUUID(); diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/ProcessConfigControllerTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/ProcessConfigControllerTest.java new file mode 100644 index 0000000..a7a80bf --- /dev/null +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/controllers/ProcessConfigControllerTest.java @@ -0,0 +1,173 @@ +/** + * 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.server.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.monitor.commons.ProcessConfig; +import org.gridsuite.monitor.commons.SecurityAnalysisConfig; +import org.gridsuite.monitor.server.services.ProcessConfigService; +import org.junit.jupiter.api.Test; +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 java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Franck Lecuyer + */ +@WebMvcTest(ProcessConfigController.class) +class ProcessConfigControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private ProcessConfigService processConfigService; + + @Test + void createSecurityAnalysisConfig() throws Exception { + UUID processConfigId = UUID.randomUUID(); + SecurityAnalysisConfig securityAnalysisConfig = new SecurityAnalysisConfig( + UUID.randomUUID(), + List.of("contingency1", "contingency2"), + List.of(UUID.randomUUID(), UUID.randomUUID()) + ); + + when(processConfigService.createProcessConfig(any(ProcessConfig.class))) + .thenReturn(processConfigId); + + mockMvc.perform(post("/v1/process-configs") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(securityAnalysisConfig))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").value(processConfigId.toString())); + + verify(processConfigService).createProcessConfig(any(SecurityAnalysisConfig.class)); + } + + @Test + void getSecurityAnalysisConfig() throws Exception { + UUID processConfigId = UUID.randomUUID(); + SecurityAnalysisConfig securityAnalysisConfig = new SecurityAnalysisConfig( + UUID.randomUUID(), + List.of("contingency1", "contingency2"), + List.of(UUID.randomUUID(), UUID.randomUUID()) + ); + String expectedJson = objectMapper.writeValueAsString(securityAnalysisConfig); + + when(processConfigService.getProcessConfig(any(UUID.class))) + .thenReturn(Optional.of(securityAnalysisConfig)); + + mockMvc.perform(get("/v1/process-configs/{uuid}", processConfigId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + + verify(processConfigService).getProcessConfig(any(UUID.class)); + } + + @Test + void getSecurityAnalysisConfigNotFound() throws Exception { + UUID processConfigId = UUID.randomUUID(); + + when(processConfigService.getProcessConfig(any(UUID.class))) + .thenReturn(Optional.empty()); + + mockMvc.perform(get("/v1/process-configs/{uuid}", processConfigId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + + verify(processConfigService).getProcessConfig(any(UUID.class)); + } + + @Test + void updateSecurityAnalysisConfig() throws Exception { + UUID processConfigId = UUID.randomUUID(); + SecurityAnalysisConfig securityAnalysisConfig = new SecurityAnalysisConfig( + UUID.randomUUID(), + List.of("contingency1", "contingency2"), + List.of(UUID.randomUUID(), UUID.randomUUID()) + ); + + when(processConfigService.updateProcessConfig(any(UUID.class), any(ProcessConfig.class))) + .thenReturn(Boolean.TRUE); + + mockMvc.perform(put("/v1/process-configs/{uuid}", processConfigId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(securityAnalysisConfig))) + .andExpect(status().isOk()); + + verify(processConfigService).updateProcessConfig(any(UUID.class), any(ProcessConfig.class)); + } + + @Test + void updateSecurityAnalysisConfigNotFound() throws Exception { + UUID processConfigId = UUID.randomUUID(); + SecurityAnalysisConfig securityAnalysisConfig = new SecurityAnalysisConfig( + UUID.randomUUID(), + List.of("contingency1", "contingency2"), + List.of(UUID.randomUUID(), UUID.randomUUID()) + ); + + when(processConfigService.updateProcessConfig(any(UUID.class), any(ProcessConfig.class))) + .thenReturn(Boolean.FALSE); + + mockMvc.perform(put("/v1/process-configs/{uuid}", processConfigId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(securityAnalysisConfig))) + .andExpect(status().isNotFound()); + + verify(processConfigService).updateProcessConfig(any(UUID.class), any(ProcessConfig.class)); + } + + @Test + void deleteSecurityAnalysisConfig() throws Exception { + UUID processConfigId = UUID.randomUUID(); + + when(processConfigService.deleteProcessConfig(any(UUID.class))) + .thenReturn(Boolean.TRUE); + + mockMvc.perform(delete("/v1/process-configs/{uuid}", processConfigId)) + .andExpect(status().isOk()); + + verify(processConfigService).deleteProcessConfig(any(UUID.class)); + } + + @Test + void deleteSecurityAnalysisConfigNotFound() throws Exception { + UUID processConfigId = UUID.randomUUID(); + + when(processConfigService.deleteProcessConfig(any(UUID.class))) + .thenReturn(Boolean.FALSE); + + mockMvc.perform(delete("/v1/process-configs/{uuid}", processConfigId)) + .andExpect(status().isNotFound()); + + verify(processConfigService).deleteProcessConfig(any(UUID.class)); + } +} diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/repositories/ProcessExecutionRepositoryTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/repositories/ProcessExecutionRepositoryTest.java index 68c923b..4a62c4e 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/repositories/ProcessExecutionRepositoryTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/repositories/ProcessExecutionRepositoryTest.java @@ -7,6 +7,7 @@ package org.gridsuite.monitor.server.repositories; import org.gridsuite.monitor.commons.ProcessStatus; +import org.gridsuite.monitor.commons.ProcessType; import org.gridsuite.monitor.commons.StepStatus; import org.gridsuite.monitor.server.entities.ProcessExecutionEntity; import org.gridsuite.monitor.server.entities.ProcessExecutionStepEntity; @@ -16,7 +17,9 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.List; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -75,4 +78,73 @@ void stepsShouldBeOrderedByStepOrder() { assertThat(retrieved.getSteps().get(1).getStepType()).isEqualTo("SECOND_STEP"); assertThat(retrieved.getSteps().get(1).getStepOrder()).isEqualTo(1); } + + @Test + void securityAnalysisLaunchedProcesses() { + UUID case1Uuid = UUID.randomUUID(); + Instant scheduledAt1 = Instant.now().minusSeconds(60); + Instant startedAt1 = Instant.now().minusSeconds(30); + Instant completedAt1 = Instant.now(); + ProcessExecutionEntity execution1 = ProcessExecutionEntity.builder() + .type(ProcessType.SECURITY_ANALYSIS.name()) + .caseUuid(case1Uuid) + .status(ProcessStatus.COMPLETED) + .executionEnvName("env1") + .scheduledAt(scheduledAt1) + .startedAt(startedAt1) + .completedAt(completedAt1) + .userId("user1") + .build(); + + UUID case2Uuid = UUID.randomUUID(); + Instant scheduledAt2 = Instant.now().minusSeconds(90); + Instant startedAt2 = Instant.now().minusSeconds(20); + ProcessExecutionEntity execution2 = ProcessExecutionEntity.builder() + .type(ProcessType.SECURITY_ANALYSIS.name()) + .caseUuid(case2Uuid) + .status(ProcessStatus.RUNNING) + .executionEnvName("env2") + .scheduledAt(scheduledAt2) + .startedAt(startedAt2) + .userId("user2") + .build(); + + UUID case3Uuid = UUID.randomUUID(); + Instant scheduledAt3 = Instant.now().minusSeconds(90); + ProcessExecutionEntity execution3 = ProcessExecutionEntity.builder() + .type(ProcessType.SECURITY_ANALYSIS.name()) + .caseUuid(case3Uuid) + .status(ProcessStatus.SCHEDULED) + .executionEnvName("env3") + .scheduledAt(scheduledAt3) + .userId("user3") + .build(); + + executionRepository.saveAll(List.of(execution1, execution2, execution3)); + + // Force DB write and reload + entityManager.flush(); + entityManager.clear(); + + List retrieved = executionRepository.findByTypeAndStartedAtIsNotNullOrderByStartedAtDesc(ProcessType.SECURITY_ANALYSIS.name()); + assertThat(retrieved).hasSize(2); + + assertThat(retrieved.get(0).getType()).isEqualTo(ProcessType.SECURITY_ANALYSIS.name()); + assertThat(retrieved.get(0).getCaseUuid()).isEqualTo(case2Uuid); + assertThat(retrieved.get(0).getStatus()).isEqualTo(ProcessStatus.RUNNING); + assertThat(retrieved.get(0).getExecutionEnvName()).isEqualTo("env2"); + assertThat(retrieved.get(0).getScheduledAt().truncatedTo(ChronoUnit.MILLIS)).isEqualTo(scheduledAt2.truncatedTo(ChronoUnit.MILLIS)); + assertThat(retrieved.get(0).getStartedAt().truncatedTo(ChronoUnit.MILLIS)).isEqualTo(startedAt2.truncatedTo(ChronoUnit.MILLIS)); + assertThat(retrieved.get(0).getCompletedAt()).isNull(); + assertThat(retrieved.get(0).getUserId()).isEqualTo("user2"); + + assertThat(retrieved.get(1).getType()).isEqualTo(ProcessType.SECURITY_ANALYSIS.name()); + assertThat(retrieved.get(1).getCaseUuid()).isEqualTo(case1Uuid); + assertThat(retrieved.get(1).getStatus()).isEqualTo(ProcessStatus.COMPLETED); + assertThat(retrieved.get(1).getExecutionEnvName()).isEqualTo("env1"); + assertThat(retrieved.get(1).getScheduledAt().truncatedTo(ChronoUnit.MILLIS)).isEqualTo(scheduledAt1.truncatedTo(ChronoUnit.MILLIS)); + assertThat(retrieved.get(1).getStartedAt().truncatedTo(ChronoUnit.MILLIS)).isEqualTo(startedAt1.truncatedTo(ChronoUnit.MILLIS)); + assertThat(retrieved.get(1).getCompletedAt().truncatedTo(ChronoUnit.MILLIS)).isEqualTo(completedAt1.truncatedTo(ChronoUnit.MILLIS)); + assertThat(retrieved.get(1).getUserId()).isEqualTo("user1"); + } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ConsumerServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ConsumerServiceTest.java index b16c34f..dae001f 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ConsumerServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ConsumerServiceTest.java @@ -52,10 +52,14 @@ void setUp() { @Test void consumeProcessExecutionStatusUpdateMessage() throws JsonProcessingException { UUID executionId = UUID.randomUUID(); + Instant startedAt = Instant.parse("2025-01-01T11:59:00Z"); + Instant completedAt = Instant.parse("2025-01-01T12:00:00Z"); + ProcessExecutionStatusUpdate statusUpdate = ProcessExecutionStatusUpdate.builder() .status(ProcessStatus.RUNNING) .executionEnvName("env-1") - .completedAt(Instant.parse("2025-01-01T12:00:00Z")) + .startedAt(startedAt) + .completedAt(completedAt) .build(); String payload = objectMapper.writeValueAsString(statusUpdate); Map headers = new HashMap<>(); @@ -70,7 +74,8 @@ void consumeProcessExecutionStatusUpdateMessage() throws JsonProcessingException executionId, ProcessStatus.RUNNING, "env-1", - Instant.parse("2025-01-01T12:00:00Z") + startedAt, + completedAt ); verify(monitorService, never()).updateStepStatus(any(), any()); } @@ -89,7 +94,7 @@ void consumeMonitorUpdateThrowsOnInvalidJson() { .isInstanceOf(UncheckedIOException.class) .hasMessageContaining("Failed to parse payload as ProcessExecutionStatusUpdate"); - verify(monitorService, never()).updateExecutionStatus(any(), any(), any(), any()); + verify(monitorService, never()).updateExecutionStatus(any(), any(), any(), any(), any()); verify(monitorService, never()).updateStepStatus(any(), any()); } @@ -113,6 +118,6 @@ void consumeProcessExecutionStepUpdateMessage() throws JsonProcessingException { consumer.accept(message); verify(monitorService).updateStepStatus(eq(executionId), any(ProcessExecutionStep.class)); - verify(monitorService, never()).updateExecutionStatus(any(), any(), any(), any()); + verify(monitorService, never()).updateExecutionStatus(any(), any(), any(), any(), any()); } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java index f822043..c17fbab 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java @@ -7,15 +7,18 @@ package org.gridsuite.monitor.server.services; import org.gridsuite.monitor.commons.*; +import org.gridsuite.monitor.server.dto.ProcessExecution; import org.gridsuite.monitor.server.dto.Report; import org.gridsuite.monitor.server.entities.ProcessExecutionEntity; import org.gridsuite.monitor.server.entities.ProcessExecutionStepEntity; +import org.gridsuite.monitor.server.mapper.ProcessExecutionMapper; import org.gridsuite.monitor.server.repositories.ProcessExecutionRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; @@ -32,7 +35,7 @@ /** * @author Antoine Bouhours */ -@ExtendWith(MockitoExtension.class) +@ExtendWith({MockitoExtension.class}) class MonitorServiceTest { @Mock @@ -50,14 +53,19 @@ class MonitorServiceTest { @InjectMocks private MonitorService monitorService; + @Spy + private ProcessExecutionMapper processExecutionMapper; + private SecurityAnalysisConfig securityAnalysisConfig; private UUID caseUuid; private UUID executionId; + private String userId; @BeforeEach void setUp() { caseUuid = UUID.randomUUID(); executionId = UUID.randomUUID(); + userId = "user1"; securityAnalysisConfig = new SecurityAnalysisConfig( UUID.randomUUID(), List.of("contingency1", "contingency2"), @@ -75,15 +83,17 @@ void executeProcessCreateExecutionAndSendNotification() { return entity; }); - UUID result = monitorService.executeProcess(caseUuid, securityAnalysisConfig); + UUID result = monitorService.executeProcess(caseUuid, userId, securityAnalysisConfig); assertThat(result).isNotNull(); verify(executionRepository).save(argThat(execution -> execution.getId() != null && ProcessType.SECURITY_ANALYSIS.name().equals(execution.getType()) && caseUuid.equals(execution.getCaseUuid()) && + userId.equals(execution.getUserId()) && ProcessStatus.SCHEDULED.equals(execution.getStatus()) && - execution.getScheduledAt() != null + execution.getScheduledAt() != null && + execution.getStartedAt() == null )); verify(notificationService).sendProcessRunMessage( caseUuid, @@ -98,16 +108,18 @@ void updateExecutionStatusShouldUpdateStatusOnly() { .id(executionId) .type(ProcessType.SECURITY_ANALYSIS.name()) .caseUuid(caseUuid) + .userId(userId) .status(ProcessStatus.SCHEDULED) .scheduledAt(Instant.now()) .build(); when(executionRepository.findById(executionId)).thenReturn(Optional.of(execution)); - monitorService.updateExecutionStatus(executionId, ProcessStatus.RUNNING, null, null); + monitorService.updateExecutionStatus(executionId, ProcessStatus.RUNNING, null, null, null); verify(executionRepository).findById(executionId); assertThat(execution.getStatus()).isEqualTo(ProcessStatus.RUNNING); assertThat(execution.getExecutionEnvName()).isNull(); + assertThat(execution.getStartedAt()).isNull(); assertThat(execution.getCompletedAt()).isNull(); verify(executionRepository).save(execution); } @@ -118,18 +130,21 @@ void updateExecutionStatusShouldUpdateAllFields() { .id(executionId) .type(ProcessType.SECURITY_ANALYSIS.name()) .caseUuid(caseUuid) + .userId(userId) .status(ProcessStatus.RUNNING) .scheduledAt(Instant.now()) .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.COMPLETED, envName, completedAt); + monitorService.updateExecutionStatus(executionId, ProcessStatus.COMPLETED, envName, startedAt, completedAt); verify(executionRepository).findById(executionId); assertThat(execution.getStatus()).isEqualTo(ProcessStatus.COMPLETED); assertThat(execution.getExecutionEnvName()).isEqualTo(envName); + assertThat(execution.getStartedAt()).isEqualTo(startedAt); assertThat(execution.getCompletedAt()).isEqualTo(completedAt); verify(executionRepository).save(execution); } @@ -138,7 +153,7 @@ void updateExecutionStatusShouldUpdateAllFields() { void updateExecutionStatusShouldHandleExecutionNotFound() { when(executionRepository.findById(executionId)).thenReturn(Optional.empty()); - monitorService.updateExecutionStatus(executionId, ProcessStatus.COMPLETED, "env", Instant.now()); + monitorService.updateExecutionStatus(executionId, ProcessStatus.COMPLETED, "env", Instant.now(), Instant.now()); verify(executionRepository).findById(executionId); verify(executionRepository, never()).save(any()); @@ -150,6 +165,7 @@ void updateStepStatusShouldAddNewStep() { .id(executionId) .type(ProcessType.SECURITY_ANALYSIS.name()) .caseUuid(caseUuid) + .userId(userId) .status(ProcessStatus.RUNNING) .steps(new ArrayList<>()) .build(); @@ -202,6 +218,7 @@ void updateStepStatusShouldUpdateExistingStep() { .id(executionId) .type(ProcessType.SECURITY_ANALYSIS.name()) .caseUuid(caseUuid) + .userId(userId) .status(ProcessStatus.RUNNING) .steps(new ArrayList<>(List.of(existingStep))) .build(); @@ -248,6 +265,7 @@ void getReportsShouldReturnReports() { .build(); ProcessExecutionEntity execution = ProcessExecutionEntity.builder() .id(executionId) + .userId(userId) .steps(List.of(step0, step1)) .build(); when(executionRepository.findById(executionId)).thenReturn(Optional.of(execution)); @@ -283,6 +301,7 @@ void getResultsShouldReturnResults() { ProcessExecutionEntity execution = ProcessExecutionEntity.builder() .id(executionId) .steps(List.of(step0, step1)) + .userId(userId) .build(); when(executionRepository.findById(executionId)).thenReturn(Optional.of(execution)); String result1 = "{\"result\": \"data1\"}"; @@ -299,6 +318,51 @@ void getResultsShouldReturnResults() { verify(resultService, times(2)).getResult(any(ResultInfos.class)); } + @Test + void getLaunchedProcesses() { + UUID execution1Uuid = UUID.randomUUID(); + UUID case1Uuid = UUID.randomUUID(); + Instant scheduledAt1 = Instant.now().minusSeconds(60); + Instant startedAt1 = Instant.now().minusSeconds(30); + Instant completedAt1 = Instant.now(); + ProcessExecutionEntity execution1 = ProcessExecutionEntity.builder() + .id(execution1Uuid) + .type(ProcessType.SECURITY_ANALYSIS.name()) + .caseUuid(case1Uuid) + .status(ProcessStatus.COMPLETED) + .executionEnvName("env1") + .scheduledAt(scheduledAt1) + .startedAt(startedAt1) + .completedAt(completedAt1) + .userId("user1") + .build(); + + UUID execution2Uuid = UUID.randomUUID(); + UUID case2Uuid = UUID.randomUUID(); + Instant scheduledAt2 = Instant.now().minusSeconds(90); + Instant startedAt2 = Instant.now().minusSeconds(80); + ProcessExecutionEntity execution2 = ProcessExecutionEntity.builder() + .id(execution2Uuid) + .type(ProcessType.SECURITY_ANALYSIS.name()) + .caseUuid(case2Uuid) + .status(ProcessStatus.RUNNING) + .executionEnvName("env2") + .scheduledAt(scheduledAt2) + .startedAt(startedAt2) + .userId("user2") + .build(); + + when(executionRepository.findByTypeAndStartedAtIsNotNullOrderByStartedAtDesc(ProcessType.SECURITY_ANALYSIS.name())).thenReturn(List.of(execution2, execution1)); + + List result = monitorService.getLaunchedProcesses(ProcessType.SECURITY_ANALYSIS); + + ProcessExecution processExecution1 = new ProcessExecution(execution1Uuid, ProcessType.SECURITY_ANALYSIS.name(), case1Uuid, ProcessStatus.COMPLETED, "env1", scheduledAt1, startedAt1, completedAt1, "user1"); + ProcessExecution processExecution2 = new ProcessExecution(execution2Uuid, ProcessType.SECURITY_ANALYSIS.name(), case2Uuid, ProcessStatus.RUNNING, "env2", scheduledAt2, startedAt2, null, "user2"); + + assertThat(result).hasSize(2).containsExactly(processExecution2, processExecution1); + verify(executionRepository).findByTypeAndStartedAtIsNotNullOrderByStartedAtDesc(ProcessType.SECURITY_ANALYSIS.name()); + } + @Test void deleteExecutionShouldDeleteResultsAndReports() { UUID reportId1 = UUID.randomUUID(); diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ProcessConfigServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ProcessConfigServiceTest.java new file mode 100644 index 0000000..b9833b5 --- /dev/null +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/ProcessConfigServiceTest.java @@ -0,0 +1,175 @@ +/** + * 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.server.services; + +import org.gridsuite.monitor.commons.ProcessConfig; +import org.gridsuite.monitor.commons.ProcessType; +import org.gridsuite.monitor.commons.SecurityAnalysisConfig; +import org.gridsuite.monitor.server.entities.SecurityAnalysisConfigEntity; +import org.gridsuite.monitor.server.mapper.SecurityAnalysisConfigMapper; +import org.gridsuite.monitor.server.repositories.ProcessConfigRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Franck Lecuyer + */ +@ExtendWith(MockitoExtension.class) +class ProcessConfigServiceTest { + + @Mock + private ProcessConfigRepository processConfigRepository; + + @InjectMocks + private ProcessConfigService processConfigService; + + private SecurityAnalysisConfig securityAnalysisConfig; + + @Spy + private SecurityAnalysisConfigMapper securityAnalysisConfigMapper; + + @BeforeEach + void setUp() { + securityAnalysisConfig = new SecurityAnalysisConfig( + UUID.randomUUID(), + List.of("contingency1", "contingency2"), + List.of(UUID.randomUUID()) + ); + } + + @Test + void createSecurityAnalysisConfig() { + UUID expectedProcessConfigId = UUID.randomUUID(); + when(processConfigRepository.save(any(SecurityAnalysisConfigEntity.class))) + .thenAnswer(invocation -> { + SecurityAnalysisConfigEntity entity = invocation.getArgument(0); + entity.setId(expectedProcessConfigId); + return entity; + }); + + UUID result = processConfigService.createProcessConfig(securityAnalysisConfig); + assertThat(result).isEqualTo(expectedProcessConfigId); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SecurityAnalysisConfigEntity.class); + verify(processConfigRepository).save(captor.capture()); + + SecurityAnalysisConfigEntity savedEntity = captor.getValue(); + assertThat(savedEntity.getId()).isEqualTo(expectedProcessConfigId); + assertThat(savedEntity.getType()).isEqualTo(ProcessType.SECURITY_ANALYSIS); + assertThat(savedEntity.getParametersUuid()).isEqualTo(securityAnalysisConfig.parametersUuid()); + assertThat(savedEntity.getContingencies()).isEqualTo(securityAnalysisConfig.contingencies()); + assertThat(savedEntity.getModificationUuids()).isEqualTo(securityAnalysisConfig.modificationUuids()); + } + + @Test + void getSecurityAnalysisConfig() { + UUID processConfigId = UUID.randomUUID(); + SecurityAnalysisConfigEntity securityAnalysisConfigEntity = securityAnalysisConfigMapper.toEntity(securityAnalysisConfig); + + when(processConfigRepository.findById(processConfigId)).thenReturn(Optional.of(securityAnalysisConfigEntity)); + + Optional processConfig = processConfigService.getProcessConfig(processConfigId); + verify(processConfigRepository).findById(processConfigId); + assertThat(processConfig).isPresent(); + assertThat(processConfig.get()).usingRecursiveComparison().isEqualTo(securityAnalysisConfig); + } + + @Test + void getSecurityAnalysisConfigNotFound() { + UUID processConfigId = UUID.randomUUID(); + + when(processConfigRepository.findById(processConfigId)).thenReturn(Optional.empty()); + + Optional processConfig = processConfigService.getProcessConfig(processConfigId); + verify(processConfigRepository).findById(processConfigId); + assertThat(processConfig).isEmpty(); + } + + @Test + void updateSecurityAnalysisConfig() { + UUID processConfigId = UUID.randomUUID(); + SecurityAnalysisConfigEntity securityAnalysisConfigEntity = securityAnalysisConfigMapper.toEntity(securityAnalysisConfig); + + SecurityAnalysisConfig newSecurityAnalysisConfig = new SecurityAnalysisConfig( + UUID.randomUUID(), + List.of("contingency3", "contingency4", "contingency5"), + List.of(UUID.randomUUID()) + ); + + when(processConfigRepository.findById(processConfigId)).thenReturn(Optional.of(securityAnalysisConfigEntity)); + + boolean done = processConfigService.updateProcessConfig(processConfigId, newSecurityAnalysisConfig); + assertThat(done).isTrue(); + + verify(processConfigRepository).findById(processConfigId); + + Optional processConfigUpdated = processConfigService.getProcessConfig(processConfigId); + assertThat(processConfigUpdated).isPresent(); + assertThat(processConfigUpdated.get()).usingRecursiveComparison().isEqualTo(newSecurityAnalysisConfig); + } + + @Test + void updateSecurityAnalysisConfigNotFound() { + UUID processConfigId = UUID.randomUUID(); + + when(processConfigRepository.findById(processConfigId)).thenReturn(Optional.empty()); + + SecurityAnalysisConfig newSecurityAnalysisConfig = new SecurityAnalysisConfig( + UUID.randomUUID(), + List.of("contingency1"), + List.of(UUID.randomUUID()) + ); + boolean done = processConfigService.updateProcessConfig(processConfigId, newSecurityAnalysisConfig); + assertThat(done).isFalse(); + + verify(processConfigRepository).findById(processConfigId); + } + + @Test + void deleteSecurityAnalysisConfig() { + UUID processConfigId = UUID.randomUUID(); + + when(processConfigRepository.existsById(processConfigId)).thenReturn(Boolean.TRUE); + doNothing().when(processConfigRepository).deleteById(processConfigId); + + boolean done = processConfigService.deleteProcessConfig(processConfigId); + assertThat(done).isTrue(); + + verify(processConfigRepository).existsById(processConfigId); + verify(processConfigRepository).deleteById(processConfigId); + } + + @Test + void deleteSecurityAnalysisConfigNotFound() { + UUID processConfigId = UUID.randomUUID(); + + when(processConfigRepository.existsById(processConfigId)).thenReturn(Boolean.FALSE); + + boolean done = processConfigService.deleteProcessConfig(processConfigId); + assertThat(done).isFalse(); + + verify(processConfigRepository).existsById(processConfigId); + verify(processConfigRepository, never()).deleteById(processConfigId); + } +} diff --git a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionService.java b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionService.java index 6a4e8f5..ddaf8ae 100644 --- a/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionService.java +++ b/monitor-worker-server/src/main/java/org/gridsuite/monitor/worker/server/services/ProcessExecutionService.java @@ -70,6 +70,7 @@ private void updateExecutionStatus(UUID executionId, String envName, ProcessStat ProcessExecutionStatusUpdate processExecutionStatusUpdate = new ProcessExecutionStatusUpdate( status, envName, + status == ProcessStatus.RUNNING ? Instant.now() : null, status == ProcessStatus.COMPLETED || status == ProcessStatus.FAILED ? Instant.now() : null ); From f6f7dba67e9b9ae52ccaf72f75f1b81a0d5f4683 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Mon, 2 Feb 2026 13:51:04 +0100 Subject: [PATCH 3/5] Delete an execution given its id Signed-off-by: Franck LECUYER --- .../entities/ProcessExecutionEntity.java | 2 +- .../server/services/MonitorService.java | 33 +++++-------------- .../server/services/ResultService.java | 22 ++++++------- .../server/services/MonitorServiceTest.java | 5 --- 4 files changed, 20 insertions(+), 42 deletions(-) diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java index 0f1a3dd..eaaeaa1 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionEntity.java @@ -23,7 +23,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@Table(name = "processExecution") +@Table(name = "process_execution") public class ProcessExecutionEntity { @Id diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java index 64e034b..bc0e9ea 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java @@ -17,7 +17,6 @@ import org.gridsuite.monitor.server.entities.ProcessExecutionStepEntity; import org.gridsuite.monitor.server.mapper.ProcessExecutionMapper; import org.gridsuite.monitor.server.repositories.ProcessExecutionRepository; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -37,20 +36,17 @@ public class MonitorService { private final NotificationService notificationService; private final ReportService reportService; private final ResultService resultService; - private final MonitorService self; private final ProcessExecutionMapper processExecutionMapper; public MonitorService(ProcessExecutionRepository executionRepository, NotificationService notificationService, ReportService reportService, ResultService resultService, - @Lazy MonitorService monitorService, ProcessExecutionMapper processExecutionMapper) { this.executionRepository = executionRepository; this.notificationService = notificationService; this.reportService = reportService; this.resultService = resultService; - this.self = monitorService; this.processExecutionMapper = processExecutionMapper; } @@ -164,8 +160,11 @@ public List getLaunchedProcesses(ProcessType processType) { .map(processExecutionMapper::toDto).toList(); } - @Transactional(readOnly = true) - public boolean getReportsAndResultsUuids(UUID executionId, List resultIds, List reportIds) { + @Transactional + public boolean deleteExecution(UUID executionId) { + List resultIds = new ArrayList<>(); + List reportIds = new ArrayList<>(); + Optional executionEntity = executionRepository.findById(executionId); if (executionEntity.isPresent()) { executionEntity.get().getSteps().forEach(step -> { @@ -176,27 +175,13 @@ public boolean getReportsAndResultsUuids(UUID executionId, List res reportIds.add(step.getReportId()); } }); - return true; - } else { - return false; - } - } - - @Transactional - public void deleteExecutionEntity(UUID executionId) { - executionRepository.deleteById(executionId); - } - - public boolean deleteExecution(UUID executionId) { - List resultIds = new ArrayList<>(); - List reportIds = new ArrayList<>(); - boolean executionFound = self.getReportsAndResultsUuids(executionId, resultIds, reportIds); - if (executionFound) { resultIds.forEach(resultService::deleteResult); reportIds.forEach(reportService::deleteReport); - self.deleteExecutionEntity(executionId); + executionRepository.deleteById(executionId); + + return true; } - return executionFound; + return false; } } diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultService.java index 2a30a4a..3e1bd22 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/ResultService.java @@ -31,21 +31,19 @@ public ResultService(List resultProviders) { )); } - public String getResult(ResultInfos resultInfos) { - ResultProvider provider = providers.get(resultInfos.resultType()); - if (provider != null) { - return provider.getResult(resultInfos.resultUUID()); - } else { - throw new IllegalArgumentException("Unsupported result type: " + resultInfos.resultType()); + public ResultProvider getProvider(ResultType resultType) { + ResultProvider provider = providers.get(resultType); + if (provider == null) { + throw new IllegalArgumentException("Unsupported result type: " + resultType); } + return provider; + } + + public String getResult(ResultInfos resultInfos) { + return getProvider(resultInfos.resultType()).getResult(resultInfos.resultUUID()); } public void deleteResult(ResultInfos resultInfos) { - ResultProvider provider = providers.get(resultInfos.resultType()); - if (provider != null) { - provider.deleteResult(resultInfos.resultUUID()); - } else { - throw new IllegalArgumentException("Unsupported result type: " + resultInfos.resultType()); - } + getProvider(resultInfos.resultType()).deleteResult(resultInfos.resultUUID()); } } diff --git a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java index c17fbab..eb304d5 100644 --- a/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java +++ b/monitor-server/src/test/java/org/gridsuite/monitor/server/services/MonitorServiceTest.java @@ -20,7 +20,6 @@ import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; import java.time.Instant; import java.util.ArrayList; @@ -394,8 +393,6 @@ void deleteExecutionShouldDeleteResultsAndReports() { doNothing().when(reportService).deleteReport(reportId2); doNothing().when(resultService).deleteResult(any(ResultInfos.class)); - ReflectionTestUtils.setField(monitorService, "self", monitorService); - boolean done = monitorService.deleteExecution(executionId); assertThat(done).isTrue(); @@ -410,8 +407,6 @@ void deleteExecutionShouldDeleteResultsAndReports() { void deleteExecutionShouldReturnFalseWhenExecutionNotFound() { when(executionRepository.findById(executionId)).thenReturn(Optional.empty()); - ReflectionTestUtils.setField(monitorService, "self", monitorService); - boolean done = monitorService.deleteExecution(executionId); assertThat(done).isFalse(); From 609ee9237f5b80e6f87757e158848356fe910259 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Mon, 2 Feb 2026 14:00:07 +0100 Subject: [PATCH 4/5] Change table name in ProcessExecutionStepEntity annotation Signed-off-by: Franck LECUYER --- .../monitor/server/entities/ProcessExecutionStepEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionStepEntity.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionStepEntity.java index 0a3e2b6..a2b0a95 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionStepEntity.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/entities/ProcessExecutionStepEntity.java @@ -23,7 +23,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@Table(name = "processExecutionStep") +@Table(name = "process_execution_step") public class ProcessExecutionStepEntity { @Id From a180a83498e2442a554a144186338ad96cc147bf Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 4 Feb 2026 11:54:53 +0100 Subject: [PATCH 5/5] Fix last coderabbitai review Signed-off-by: Franck LECUYER --- .../org/gridsuite/monitor/server/services/MonitorService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java index 8300c15..d0722e5 100644 --- a/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java +++ b/monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java @@ -197,7 +197,7 @@ public boolean deleteExecution(UUID executionId) { Optional executionEntity = executionRepository.findById(executionId); if (executionEntity.isPresent()) { - executionEntity.get().getSteps().forEach(step -> { + Optional.ofNullable(executionEntity.get().getSteps()).orElse(List.of()).forEach(step -> { if (step.getResultId() != null && step.getResultType() != null) { resultIds.add(new ResultInfos(step.getResultId(), step.getResultType())); }