Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
*/
public enum MessageType {
EXECUTION_STATUS_UPDATE,
STEP_STATUS_UPDATE
STEP_STATUS_UPDATE,
STEPS_STATUSES_UPDATE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @author Antoine Bouhours <antoine.bouhours at rte-france.com>
*/
public enum StepStatus {
SCHEDULED,
RUNNING,
COMPLETED,
FAILED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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.ProcessExecutionStep;
import org.gridsuite.monitor.commons.ProcessType;
import org.gridsuite.monitor.commons.SecurityAnalysisConfig;
import org.gridsuite.monitor.server.dto.ProcessExecution;
Expand Down Expand Up @@ -71,4 +72,14 @@ public ResponseEntity<List<String>> getExecutionResults(@Parameter(description =
public ResponseEntity<List<ProcessExecution>> getLaunchedProcesses(@Parameter(description = "Process type") @RequestParam(name = "processType") ProcessType processType) {
return ResponseEntity.ok(monitorService.getLaunchedProcesses(processType));
}

@GetMapping("/executions/{executionId}/step-infos")
@Operation(summary = "Get execution steps statuses")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "The execution steps statuses"),
@ApiResponse(responseCode = "404", description = "execution id was not found")})
public ResponseEntity<List<ProcessExecutionStep>> getStepsInfos(@Parameter(description = "Execution UUID") @PathVariable UUID executionId) {
return monitorService.getStepsInfos(executionId).map(list -> ResponseEntity.ok().body(list))
.orElseGet(() -> ResponseEntity.notFound().build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@

import org.gridsuite.monitor.server.dto.ProcessExecution;
import org.gridsuite.monitor.server.entities.ProcessExecutionEntity;
import org.springframework.stereotype.Component;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Component
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
public class ProcessExecutionMapper {
public ProcessExecution toDto(ProcessExecutionEntity entity) {
public static ProcessExecution toDto(ProcessExecutionEntity entity) {
return new ProcessExecution(
entity.getId(),
entity.getType(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 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.ProcessExecutionStep;
import org.gridsuite.monitor.server.entities.ProcessExecutionStepEntity;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
public class ProcessExecutionStepMapper {
public static ProcessExecutionStep toDto(ProcessExecutionStepEntity entity) {
return new ProcessExecutionStep(
entity.getId(),
entity.getStepType(),
entity.getStepOrder(),
entity.getStatus(),
entity.getResultId(),
entity.getResultType(),
entity.getReportId(),
entity.getStartedAt(),
entity.getCompletedAt());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,27 @@

import org.gridsuite.monitor.commons.SecurityAnalysisConfig;
import org.gridsuite.monitor.server.entities.SecurityAnalysisConfigEntity;
import org.springframework.stereotype.Component;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Component
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
public class SecurityAnalysisConfigMapper {

public SecurityAnalysisConfigEntity toEntity(SecurityAnalysisConfig dto) {
public static SecurityAnalysisConfigEntity toEntity(SecurityAnalysisConfig dto) {
SecurityAnalysisConfigEntity entity = new SecurityAnalysisConfigEntity();
entity.setParametersUuid(dto.parametersUuid());
entity.setContingencies(dto.contingencies());
entity.setModificationUuids(dto.modificationUuids());
update(entity, dto);
return entity;
}

public SecurityAnalysisConfig toDto(SecurityAnalysisConfigEntity entity) {
public static SecurityAnalysisConfig toDto(SecurityAnalysisConfigEntity entity) {
return new SecurityAnalysisConfig(
entity.getParametersUuid(),
entity.getContingencies(),
entity.getModificationUuids()
);
}

public void update(SecurityAnalysisConfigEntity entity, SecurityAnalysisConfig dto) {
public static void update(SecurityAnalysisConfigEntity entity, SecurityAnalysisConfig dto) {
entity.setParametersUuid(dto.parametersUuid());
entity.setContingencies(dto.contingencies());
entity.setModificationUuids(dto.modificationUuids());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.springframework.messaging.Message;

import java.io.UncheckedIOException;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;

Expand Down Expand Up @@ -53,6 +54,7 @@ public Consumer<Message<String>> consumeMonitorUpdate() {
switch (messageType) {
case EXECUTION_STATUS_UPDATE -> handleExecutionStatusUpdate(executionId, message);
case STEP_STATUS_UPDATE -> handleStepStatusUpdate(executionId, message);
case STEPS_STATUSES_UPDATE -> handleStepsStatusesUpdate(executionId, message);
default -> LOGGER.warn("Unknown message type: {}", messageType);
}
};
Expand All @@ -68,6 +70,11 @@ private void handleStepStatusUpdate(UUID executionId, Message<String> message) {
monitorService.updateStepStatus(executionId, processExecutionStep);
}

private void handleStepsStatusesUpdate(UUID executionId, Message<String> message) {
List<ProcessExecutionStep> processExecutionSteps = parsePayload(message.getPayload(), List.class);
monitorService.updateStepsStatuses(executionId, processExecutionSteps);
}

private <T> T parsePayload(String payload, Class<T> clazz) {
try {
return objectMapper.readValue(payload, clazz);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
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.mapper.ProcessExecutionStepMapper;
import org.gridsuite.monitor.server.repositories.ProcessExecutionRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
Expand All @@ -36,7 +38,6 @@ public class MonitorService {
private final NotificationService notificationService;
private final ReportService reportService;
private final ResultService resultService;
private final ProcessExecutionMapper processExecutionMapper;

@Transactional
public UUID executeProcess(UUID caseUuid, String userId, ProcessConfig processConfig) {
Expand Down Expand Up @@ -71,25 +72,45 @@ public void updateExecutionStatus(UUID executionId, ProcessStatus status, String
});
}

private void updateStep(ProcessExecutionEntity execution, ProcessExecutionStepEntity stepEntity) {
List<ProcessExecutionStepEntity> steps = Optional.ofNullable(execution.getSteps()).orElseGet(() -> {
List<ProcessExecutionStepEntity> newSteps = new java.util.ArrayList<>();
execution.setSteps(newSteps);
return newSteps;
});
steps.stream()
.filter(s -> s.getId().equals(stepEntity.getId()))
.findFirst()
.ifPresentOrElse(
existingStep -> {
existingStep.setStatus(stepEntity.getStatus());
existingStep.setStepType(stepEntity.getStepType());
existingStep.setStepOrder(stepEntity.getStepOrder());
existingStep.setStartedAt(stepEntity.getStartedAt());
existingStep.setCompletedAt(stepEntity.getCompletedAt());
existingStep.setResultId(stepEntity.getResultId());
existingStep.setResultType(stepEntity.getResultType());
existingStep.setReportId(stepEntity.getReportId());
},
() -> steps.add(stepEntity));
}

@Transactional
public void updateStepStatus(UUID executionId, ProcessExecutionStep processExecutionStep) {
executionRepository.findById(executionId).ifPresent(execution -> {
ProcessExecutionStepEntity stepEntity = toStepEntity(processExecutionStep);
execution.getSteps().stream()
.filter(s -> s.getId().equals(stepEntity.getId()))
.findFirst()
.ifPresentOrElse(
existingStep -> {
existingStep.setStatus(stepEntity.getStatus());
existingStep.setStepType(stepEntity.getStepType());
existingStep.setStepOrder(stepEntity.getStepOrder());
existingStep.setStartedAt(stepEntity.getStartedAt());
existingStep.setCompletedAt(stepEntity.getCompletedAt());
existingStep.setResultId(stepEntity.getResultId());
existingStep.setResultType(stepEntity.getResultType());
existingStep.setReportId(stepEntity.getReportId());
},
() -> execution.getSteps().add(stepEntity));
updateStep(execution, stepEntity);
executionRepository.save(execution);
});
}

@Transactional
public void updateStepsStatuses(UUID executionId, List<ProcessExecutionStep> processExecutionSteps) {
executionRepository.findById(executionId).ifPresent(execution -> {
processExecutionSteps.forEach(processExecutionStep -> {
ProcessExecutionStepEntity stepEntity = toStepEntity(processExecutionStep);
updateStep(execution, stepEntity);
});
executionRepository.save(execution);
});
}
Expand Down Expand Up @@ -118,7 +139,7 @@ public List<ReportPage> getReports(UUID executionId) {

private List<UUID> getReportIds(UUID executionId) {
return executionRepository.findById(executionId)
.map(execution -> execution.getSteps().stream()
.map(execution -> Optional.ofNullable(execution.getSteps()).orElse(List.of()).stream()
.map(ProcessExecutionStepEntity::getReportId)
.filter(java.util.Objects::nonNull)
.toList())
Expand All @@ -135,7 +156,7 @@ public List<String> getResults(UUID executionId) {

private List<ResultInfos> getResultInfos(UUID executionId) {
return executionRepository.findById(executionId)
.map(execution -> execution.getSteps().stream()
.map(execution -> Optional.ofNullable(execution.getSteps()).orElse(List.of()).stream()
.filter(step -> step.getResultId() != null)
.map(step -> new ResultInfos(step.getResultId(), step.getResultType()))
.toList())
Expand All @@ -145,6 +166,18 @@ private List<ResultInfos> getResultInfos(UUID executionId) {
@Transactional(readOnly = true)
public List<ProcessExecution> getLaunchedProcesses(ProcessType processType) {
return executionRepository.findByTypeAndStartedAtIsNotNullOrderByStartedAtDesc(processType.name()).stream()
.map(processExecutionMapper::toDto).toList();
.map(ProcessExecutionMapper::toDto).toList();
}

@Transactional(readOnly = true)
public Optional<List<ProcessExecutionStep>> getStepsInfos(UUID executionId) {
Optional<ProcessExecutionEntity> entity = executionRepository.findById(executionId);
if (entity.isPresent()) {
return entity.map(execution -> Optional.ofNullable(execution.getSteps()).orElse(List.of()).stream()
.map(ProcessExecutionStepMapper::toDto)
.toList());
} else {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,14 @@
public class ProcessConfigService {
private final ProcessConfigRepository processConfigRepository;

private final SecurityAnalysisConfigMapper securityAnalysisConfigMapper;

public ProcessConfigService(ProcessConfigRepository processConfigRepository, SecurityAnalysisConfigMapper securityAnalysisConfigMapper) {
public ProcessConfigService(ProcessConfigRepository processConfigRepository) {
this.processConfigRepository = processConfigRepository;
this.securityAnalysisConfigMapper = securityAnalysisConfigMapper;
}

@Transactional
public UUID createProcessConfig(ProcessConfig processConfig) {
AbstractProcessConfigEntity entity = switch (processConfig) {
case SecurityAnalysisConfig sac -> securityAnalysisConfigMapper.toEntity(sac);
case SecurityAnalysisConfig sac -> SecurityAnalysisConfigMapper.toEntity(sac);
default -> throw new IllegalArgumentException("Unsupported process config type: " + processConfig.processType());
};
return processConfigRepository.save(entity).getId();
Expand All @@ -45,7 +42,7 @@ public UUID createProcessConfig(ProcessConfig processConfig) {
public Optional<ProcessConfig> getProcessConfig(UUID processConfigUuid) {
return processConfigRepository.findById(processConfigUuid).flatMap(entity -> switch (entity) {
case SecurityAnalysisConfigEntity sae ->
Optional.of((ProcessConfig) securityAnalysisConfigMapper.toDto(sae));
Optional.of((ProcessConfig) SecurityAnalysisConfigMapper.toDto(sae));
default -> throw new IllegalArgumentException("Unsupported entity type: " + entity.getType());
});
}
Expand All @@ -59,7 +56,7 @@ public boolean updateProcessConfig(UUID processConfigUuid, ProcessConfig process
}
switch (processConfig) {
case SecurityAnalysisConfig sac ->
securityAnalysisConfigMapper.update((SecurityAnalysisConfigEntity) entity, sac);
SecurityAnalysisConfigMapper.update((SecurityAnalysisConfigEntity) entity, sac);
default -> throw new IllegalArgumentException("Unsupported process config type: " + processConfig.processType());
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
package org.gridsuite.monitor.server.controllers;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.gridsuite.monitor.commons.ProcessExecutionStep;
import org.gridsuite.monitor.commons.SecurityAnalysisConfig;
import org.gridsuite.monitor.commons.StepStatus;
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.ReportLog;
import org.gridsuite.monitor.server.dto.ReportPage;
Expand All @@ -24,6 +26,7 @@

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

import static org.hamcrest.Matchers.hasSize;
Expand Down Expand Up @@ -150,4 +153,34 @@ void getLaunchedProcesses() throws Exception {

verify(monitorService).getLaunchedProcesses(ProcessType.SECURITY_ANALYSIS);
}

@Test
void getStepsInfos() throws Exception {
UUID executionId = UUID.randomUUID();
ProcessExecutionStep processExecutionStep1 = new ProcessExecutionStep(UUID.randomUUID(), "loadNetwork", 0, StepStatus.RUNNING, null, null, UUID.randomUUID(), Instant.now(), null);
ProcessExecutionStep processExecutionStep2 = new ProcessExecutionStep(UUID.randomUUID(), "applyModifs", 1, StepStatus.SCHEDULED, null, null, UUID.randomUUID(), null, null);
ProcessExecutionStep processExecutionStep3 = new ProcessExecutionStep(UUID.randomUUID(), "runSA", 2, StepStatus.SCHEDULED, null, null, UUID.randomUUID(), null, null);
List<ProcessExecutionStep> processExecutionStepList = List.of(processExecutionStep1, processExecutionStep2, processExecutionStep3);

when(monitorService.getStepsInfos(executionId)).thenReturn(Optional.of(processExecutionStepList));

mockMvc.perform(get("/v1/executions/{executionId}/step-infos", executionId))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$", hasSize(3)))
.andExpect(content().json(objectMapper.writeValueAsString(processExecutionStepList)));

verify(monitorService).getStepsInfos(executionId);
}

@Test
void getStepsInfosShouldReturn404WhenExecutionNotFound() throws Exception {
UUID executionId = UUID.randomUUID();
when(monitorService.getStepsInfos(executionId)).thenReturn(Optional.empty());

mockMvc.perform(get("/v1/executions/{executionId}/step-infos", executionId))
.andExpect(status().isNotFound());

verify(monitorService).getStepsInfos(executionId);
}
}
Loading