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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
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.ResultInfos;
import org.gridsuite.monitor.commons.SecurityAnalysisConfig;
import org.gridsuite.monitor.server.dto.Report;
import org.gridsuite.monitor.server.services.MonitorService;
import org.gridsuite.monitor.server.services.ReportService;
import org.gridsuite.monitor.server.services.ResultService;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.util.List;
Expand All @@ -29,9 +34,13 @@
public class MonitorController {

private final MonitorService monitorService;
private final ResultService resultService;
private final ReportService reportService;

public MonitorController(MonitorService monitorService) {
public MonitorController(MonitorService monitorService, ResultService resultService, ReportService reportService) {
this.monitorService = monitorService;
this.resultService = resultService;
this.reportService = reportService;
}

@PostMapping("/execute/security-analysis")
Expand All @@ -48,15 +57,33 @@ public ResponseEntity<UUID> executeSecurityAnalysis(
@Operation(summary = "Get reports for an execution")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The execution reports")})
public ResponseEntity<List<Report>> getExecutionReports(@Parameter(description = "Execution UUID") @PathVariable UUID executionId) {
List<Report> reports = monitorService.getReports(executionId);
List<UUID> reportIds = monitorService.getReportIds(executionId);
List<Report> reports = reportIds.stream()
.map(reportService::getReport)
.toList();

return ResponseEntity.ok(reports);
}

@GetMapping("/executions/{executionId}/results")
@Operation(summary = "Get results for an execution")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The execution results")})
public ResponseEntity<List<String>> getExecutionResults(@Parameter(description = "Execution UUID") @PathVariable UUID executionId) {
List<String> results = monitorService.getResults(executionId);
List<ResultInfos> resultInfos = monitorService.getResultInfos(executionId);
List<String> results = resultInfos.stream()
.map(resultService::getResult)
.toList();

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<Void> deleteExecution(@PathVariable UUID executionId) {
return monitorService.deleteExecution(executionId) ?
ResponseEntity.ok().build() :
ResponseEntity.notFound().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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.events;

import org.gridsuite.monitor.commons.ResultInfos;

import java.util.List;
import java.util.UUID;

/**
* @author Antoine Bouhours <antoine.bouhours at rte-france.com>
*/
public record ExecutionDeletedEvent(List<ResultInfos> resultInfos, List<UUID> reportIds) {}
Original file line number Diff line number Diff line change
@@ -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.events;

import lombok.RequiredArgsConstructor;
import org.gridsuite.monitor.commons.ResultInfos;
import org.gridsuite.monitor.server.services.NotificationService;
import org.gridsuite.monitor.server.services.ReportService;
import org.gridsuite.monitor.server.services.ResultService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

import java.util.UUID;

/**
* @author Antoine Bouhours <antoine.bouhours at rte-france.com>
*/
@Component
@RequiredArgsConstructor
public class MonitorEventListener {
private static final Logger LOGGER = LoggerFactory.getLogger(MonitorEventListener.class);
private final NotificationService notificationService;
private final ResultService resultService;
private final ReportService reportService;

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onProcessScheduled(ProcessExecutionEvent event) {
notificationService.sendProcessRunMessage(event.caseUuid(), event.config(), event.executionId());
}

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onExecutionDeleted(ExecutionDeletedEvent event) {
event.resultInfos().forEach(this::safeDeleteResult);
event.reportIds().forEach(this::safeDeleteReport);
}

private void safeDeleteResult(ResultInfos resultInfos) {
try {
resultService.deleteResult(resultInfos);
} catch (Exception e) {
LOGGER.error("Failed to delete result {} of type {}: {}",
resultInfos.resultUUID(), resultInfos.resultType(), e.getMessage(), e);
}
}

private void safeDeleteReport(UUID reportId) {
try {
reportService.deleteReport(reportId);
} catch (Exception e) {
LOGGER.error("Failed to delete report {}: {}", reportId, e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* 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.events;

import org.gridsuite.monitor.commons.ProcessConfig;

import java.util.UUID;

/**
* @author Antoine Bouhours <antoine.bouhours at rte-france.com>
*/
public record ProcessExecutionEvent(UUID caseUuid, ProcessConfig config, UUID executionId) {}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
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.events.ExecutionDeletedEvent;
import org.gridsuite.monitor.server.events.ProcessExecutionEvent;
import org.gridsuite.monitor.server.repositories.ProcessExecutionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
Expand All @@ -30,9 +34,7 @@
public class MonitorService {

private final ProcessExecutionRepository executionRepository;
private final NotificationService notificationService;
private final ReportService reportService;
private final ResultService resultService;
private final ApplicationEventPublisher eventPublisher;

@Transactional
public UUID executeProcess(UUID caseUuid, ProcessConfig processConfig) {
Expand All @@ -44,7 +46,7 @@ public UUID executeProcess(UUID caseUuid, ProcessConfig processConfig) {
.build();
executionRepository.save(execution);

notificationService.sendProcessRunMessage(caseUuid, processConfig, execution.getId());
eventPublisher.publishEvent(new ProcessExecutionEvent(caseUuid, processConfig, execution.getId()));

return execution.getId();
}
Expand Down Expand Up @@ -101,36 +103,43 @@ private ProcessExecutionStepEntity toStepEntity(ProcessExecutionStep processExec
}

@Transactional(readOnly = true)
public List<Report> getReports(UUID executionId) {
List<UUID> reportIds = getReportIds(executionId);
return reportIds.stream()
.map(reportService::getReport)
.toList();
}

private List<UUID> getReportIds(UUID executionId) {
public List<UUID> getReportIds(UUID executionId) {
return executionRepository.findById(executionId)
.map(execution -> execution.getSteps().stream()
.map(ProcessExecutionStepEntity::getReportId)
.filter(java.util.Objects::nonNull)
.filter(Objects::nonNull)
.toList())
.orElse(List.of());
}

@Transactional(readOnly = true)
public List<String> getResults(UUID executionId) {
List<ResultInfos> resultInfos = getResultInfos(executionId);
return resultInfos.stream()
.map(resultService::getResult)
.toList();
}

private List<ResultInfos> getResultInfos(UUID executionId) {
public List<ResultInfos> getResultInfos(UUID executionId) {
return executionRepository.findById(executionId)
.map(execution -> execution.getSteps().stream()
.filter(step -> step.getResultId() != null)
.map(step -> new ResultInfos(step.getResultId(), step.getResultType()))
.toList())
.orElse(List.of());
}

@Transactional
public boolean deleteExecution(UUID executionId) {
return executionRepository.findById(executionId)
.map(entity -> {
List<ResultInfos> resultInfos = entity.getSteps().stream()
.filter(s -> s.getResultId() != null && s.getResultType() != null)
.map(s -> new ResultInfos(s.getResultId(), s.getResultType()))
.toList();
List<UUID> reportIds = entity.getSteps().stream()
.map(ProcessExecutionStepEntity::getReportId)
.filter(Objects::nonNull)
.toList();

executionRepository.delete(entity);
eventPublisher.publishEvent(new ExecutionDeletedEvent(resultInfos, reportIds));

return true;
})
.orElse(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,12 @@ public Report getReport(UUID reportId) {

return restTemplate.exchange(this.getReportServerURI() + path, HttpMethod.GET, new HttpEntity<>(headers), new ParameterizedTypeReference<Report>() { }).getBody();
}

public void deleteReport(UUID reportId) {
var path = UriComponentsBuilder.fromPath("{id}")
.buildAndExpand(reportId)
.toUriString();

restTemplate.delete(this.getReportServerURI() + path);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ public interface ResultProvider {
ResultType getType();

String getResult(UUID resultId);

void deleteResult(UUID resultId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;
import java.util.UUID;

/**
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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);
}
}
Loading
Loading