Skip to content

Commit

Permalink
[backend/frontend] display latest simulations results on scenario ove…
Browse files Browse the repository at this point in the history
…rview
  • Loading branch information
isselparra committed Dec 4, 2024
1 parent 756dd1e commit 7259a5d
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 261 deletions.
Original file line number Diff line number Diff line change
@@ -1,46 +1,33 @@
package io.openbas.rest.scenario;

import static io.openbas.database.model.User.ROLE_USER;
import static io.openbas.rest.scenario.ScenarioApi.SCENARIO_URI;
import static org.springframework.util.StringUtils.hasText;

import io.openbas.database.repository.ScenarioRepository;
import io.openbas.rest.helper.RestBehavior;
import io.openbas.rest.scenario.response.ScenarioStatistic;
import io.openbas.rest.scenario.service.ScenarioStatisticService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.transaction.Transactional;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Secured(ROLE_USER)
@RequiredArgsConstructor
public class ScenarioStatisticApi extends RestBehavior {

private final ScenarioRepository scenarioRepository;
private final ScenarioStatisticService scenarioStatisticService;

@GetMapping(SCENARIO_URI + "/statistics")
@GetMapping(SCENARIO_URI + "/{scenarioId}/statistics")
@PreAuthorize("isScenarioObserver(#scenarioId)")
@Transactional(rollbackOn = Exception.class)
@Operation(summary = "Retrieve scenario statistics")
public ScenarioStatistic scenarioStatistic() {
ScenarioStatistic statistic = new ScenarioStatistic();
statistic.setScenariosGlobalCount(this.scenarioRepository.count());
statistic.setScenariosCategoriesCount(findTopCategories());
return statistic;
}

private Map<String, Long> findTopCategories() {
List<Object[]> results = this.scenarioRepository.findTopCategories(3);
Map<String, Long> categoryCount = new LinkedHashMap<>();
for (Object[] result : results) {
String category = (String) result[0];
if (hasText(category)) {
Long count = (Long) result[1];
categoryCount.put(category, count);
}
}
return categoryCount;
public ScenarioStatistic getScenarioStatistics(@PathVariable @NotBlank final String scenarioId) {
return scenarioStatisticService.getStatistics(scenarioId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.openbas.rest.scenario.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import java.time.Instant;

public record GlobalScoreBySimulationEndDate(
@JsonProperty("simulation_end_date") @NotNull Instant simulationEndDate,
@JsonProperty("global_score_success_percentage") @NotNull
double globalScoreSuccessPercentage) {}
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
package io.openbas.rest.scenario.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
import lombok.Data;
import jakarta.validation.constraints.NotNull;

@Data
public class ScenarioStatistic {

@JsonProperty("scenarios_global_count")
private long scenariosGlobalCount;

@JsonProperty("scenarios_attack_scenario_count")
private Map<String, Long> scenariosCategoriesCount;
}
public record ScenarioStatistic(
@JsonProperty("simulations_results_latest") @NotNull
SimulationsResultsLatest simulationsResultsLatest) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.openbas.rest.scenario.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openbas.expectation.ExpectationType;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;

public record SimulationsResultsLatest(
@JsonProperty("global_scores_by_expectation_type") @NotNull
Map<ExpectationType, List<GlobalScoreBySimulationEndDate>> globalScoresByExpectationType) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package io.openbas.rest.scenario.service;

import io.openbas.database.raw.RawFinishedExerciseWithInjects;
import io.openbas.database.repository.ExerciseRepository;
import io.openbas.expectation.ExpectationType;
import io.openbas.rest.scenario.response.GlobalScoreBySimulationEndDate;
import io.openbas.rest.scenario.response.ScenarioStatistic;
import io.openbas.rest.scenario.response.SimulationsResultsLatest;
import io.openbas.utils.AtomicTestingUtils.ExpectationResultsByType;
import io.openbas.utils.AtomicTestingUtils.ResultDistribution;
import io.openbas.utils.ResultUtils;
import java.util.*;
import java.util.function.BinaryOperator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ScenarioStatisticService {

private final ExerciseRepository exerciseRepository;

private final ResultUtils resultUtils;

public ScenarioStatistic getStatistics(String scenarioId) {
return getLatestSimulationsStatistics(scenarioId);
}

private ScenarioStatistic getLatestSimulationsStatistics(String scenarioId) {
List<RawFinishedExerciseWithInjects> rawFinishedExercises =
exerciseRepository.rawLatestFinishedExercisesWithInjectsByScenarioId(scenarioId);

Map<ExpectationType, List<GlobalScoreBySimulationEndDate>>
initialGlobalScoresByExpectationType = new HashMap<>();
initialGlobalScoresByExpectationType.put(ExpectationType.PREVENTION, new ArrayList<>());
initialGlobalScoresByExpectationType.put(ExpectationType.DETECTION, new ArrayList<>());
initialGlobalScoresByExpectationType.put(ExpectationType.HUMAN_RESPONSE, new ArrayList<>());

Map<ExpectationType, List<GlobalScoreBySimulationEndDate>> globalScoresByExpectationType =
rawFinishedExercises.stream()
.reduce(
initialGlobalScoresByExpectationType,
(scoresByType, rawFinishedExercise) ->
addGlobalScores(
scoresByType,
rawFinishedExercise,
resultUtils.getResultsByTypes(rawFinishedExercise.getInject_ids())),
getMapBinaryOperator());

return new ScenarioStatistic(new SimulationsResultsLatest(globalScoresByExpectationType));
}

private static Map<ExpectationType, List<GlobalScoreBySimulationEndDate>> addGlobalScores(
Map<ExpectationType, List<GlobalScoreBySimulationEndDate>> globalScoresByExpectationType,
RawFinishedExerciseWithInjects rawFinishedExercise,
List<ExpectationResultsByType> expectationResultsByType) {

updateGlobalScores(
globalScoresByExpectationType,
rawFinishedExercise,
expectationResultsByType,
ExpectationType.PREVENTION);

updateGlobalScores(
globalScoresByExpectationType,
rawFinishedExercise,
expectationResultsByType,
ExpectationType.DETECTION);

updateGlobalScores(
globalScoresByExpectationType,
rawFinishedExercise,
expectationResultsByType,
ExpectationType.HUMAN_RESPONSE);

return globalScoresByExpectationType;
}

private static void updateGlobalScores(
Map<ExpectationType, List<GlobalScoreBySimulationEndDate>> globalScoresByExpectationType,
RawFinishedExerciseWithInjects rawFinishedExercise,
List<ExpectationResultsByType> expectationResultsByType,
ExpectationType expectationType) {
List<GlobalScoreBySimulationEndDate> globalScores =
getGlobalScoresBySimulationEndDates(
rawFinishedExercise, expectationResultsByType, expectationType);
updateGlobalScoresByExpectationType(
globalScoresByExpectationType, globalScores, expectationType);
}

private static void updateGlobalScoresByExpectationType(
Map<ExpectationType, List<GlobalScoreBySimulationEndDate>> globalScoresByType,
List<GlobalScoreBySimulationEndDate> globalScores,
ExpectationType expectationType) {
List<GlobalScoreBySimulationEndDate> previousGlobalScores =
globalScoresByType.getOrDefault(expectationType, new ArrayList<>());
previousGlobalScores.addAll(globalScores);
globalScoresByType.put(expectationType, previousGlobalScores);
}

private static List<GlobalScoreBySimulationEndDate> getGlobalScoresBySimulationEndDates(
RawFinishedExerciseWithInjects rawFinishedExercise,
List<ExpectationResultsByType> expectationResultsByType,
ExpectationType expectationType) {

return expectationResultsByType.stream()
.filter(expectationResultByType -> expectationResultByType.type() == expectationType)
.map(
expectationResultByType ->
new GlobalScoreBySimulationEndDate(
rawFinishedExercise.getExercise_end_date(),
getPercentageOfInjectsOnSuccess(expectationResultByType)))
.toList();
}

private static float getPercentageOfInjectsOnSuccess(
ExpectationResultsByType expectationResultByType) {
if (expectationResultByType.distribution().isEmpty()) {
return 0;
}
var totalNumberOfInjects =
expectationResultByType.distribution().stream()
.map(ResultDistribution::value)
.reduce(0, Integer::sum);
var numberOfInjectsOnSuccess = expectationResultByType.distribution().getFirst().value();
return (float) numberOfInjectsOnSuccess / totalNumberOfInjects;
}

private static BinaryOperator<Map<ExpectationType, List<GlobalScoreBySimulationEndDate>>>
getMapBinaryOperator() {
return (m1, m2) -> {
m1.putAll(m2);
return m1;
};
}
}
4 changes: 2 additions & 2 deletions openbas-front/src/actions/scenarios/scenario-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ export const updateScenarioRecurrence = (

// -- STATISTIC --

export const fetchScenarioStatistic = () => {
const uri = `${SCENARIO_URI}/statistics`;
export const fetchScenarioStatistic = (scenarioId: Scenario['scenario_id']) => {
const uri = `${SCENARIO_URI}/${scenarioId}/statistics`;
return simpleCall(uri);
};

Expand Down
Loading

0 comments on commit 7259a5d

Please sign in to comment.