Repository: OpenAEV-Platform/openaev (java)

Issue:
Add a breakdown of expectations by security platform
## Context

I want to benchmark the performance of my security platform 



Discussion:

Patch:
diff --git a/openbas-api/src/main/java/io/openbas/migration/V4_20__Force_es_reindex_injects.java b/openbas-api/src/main/java/io/openbas/migration/V4_20__Force_es_reindex_injects.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/main/java/io/openbas/migration/V4_20__Force_es_reindex_injects.java
@@ -0,0 +1,19 @@
+package io.openbas.migration;
+
+import java.sql.Statement;
+import org.flywaydb.core.api.migration.BaseJavaMigration;
+import org.flywaydb.core.api.migration.Context;
+import org.springframework.stereotype.Component;
+
+@Component
+public class V4_20__Force_es_reindex_injects extends BaseJavaMigration {
+
+  @Override
+  public void migrate(Context context) throws Exception {
+    try (Statement statement = context.getConnection().createStatement()) {
+      // re-index injects, simulations and scenarios in ES
+      statement.executeUpdate(
+          "DELETE FROM indexing_status WHERE indexing_status_type in ('inject', 'expectation-inject', 'simulation', 'scenario', 'attack-pattern');");
+    }
+  }
+}
diff --git a/openbas-api/src/main/java/io/openbas/rest/asset/security_platforms/SecurityPlatformApi.java b/openbas-api/src/main/java/io/openbas/rest/asset/security_platforms/SecurityPlatformApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/asset/security_platforms/SecurityPlatformApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/asset/security_platforms/SecurityPlatformApi.java
@@ -1,5 +1,6 @@
 package io.openbas.rest.asset.security_platforms;
 
+import static io.openbas.helper.StreamHelper.fromIterable;
 import static io.openbas.helper.StreamHelper.iterableToSet;
 import static io.openbas.utils.pagination.PaginationUtils.buildPaginationJPA;
 
@@ -11,6 +12,7 @@
 import io.openbas.rest.asset.security_platforms.form.SecurityPlatformUpsertInput;
 import io.openbas.rest.document.DocumentService;
 import io.openbas.rest.exception.ElementNotFoundException;
+import io.openbas.utils.FilterUtilsJpa;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -21,6 +23,7 @@
 import java.util.List;
 import java.util.Optional;
 import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.domain.Page;
 import org.springframework.web.bind.annotation.*;
@@ -185,4 +188,21 @@ public void deleteSecurityPlatform(@PathVariable @NotBlank final String security
   public List<RawDocument> documentsFromSecurityPlatform(@PathVariable String securityPlatformId) {
     return documentService.documentsForSecurityPlatform(securityPlatformId);
   }
+
+  @GetMapping(SECURITY_PLATFORM_URI + "/options")
+  @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.SECURITY_PLATFORM)
+  public List<FilterUtilsJpa.Option> optionsByName(
+      @RequestParam(required = false) final String searchText) {
+    return securityPlatformRepository.findAllByName(StringUtils.trimToNull(searchText)).stream()
+        .map(i -> new FilterUtilsJpa.Option(i.getId(), i.getName()))
+        .toList();
+  }
+
+  @PostMapping(SECURITY_PLATFORM_URI + "/options")
+  @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.SECURITY_PLATFORM)
+  public List<FilterUtilsJpa.Option> optionsById(@RequestBody final List<String> ids) {
+    return fromIterable(this.securityPlatformRepository.findAllById(ids)).stream()
+        .map(i -> new FilterUtilsJpa.Option(i.getId(), i.getName()))
+        .toList();
+  }
 }
diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/ScenarioInjectApi.java b/openbas-api/src/main/java/io/openbas/rest/inject/ScenarioInjectApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/inject/ScenarioInjectApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/inject/ScenarioInjectApi.java
@@ -16,6 +16,7 @@
 import io.openbas.rest.inject.service.InjectAssistantService;
 import io.openbas.rest.inject.service.InjectDuplicateService;
 import io.openbas.rest.inject.service.InjectService;
+import io.openbas.rest.inject.service.ScenarioInjectService;
 import io.openbas.service.*;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import io.swagger.v3.oas.annotations.Operation;
@@ -37,10 +38,10 @@ public class ScenarioInjectApi extends RestBehavior {
   private final InjectAssistantService injectAssistantService;
   private final InjectSearchService injectSearchService;
   private final InjectRepository injectRepository;
-  private final InjectDocumentRepository injectDocumentRepository;
   private final ScenarioService scenarioService;
   private final InjectService injectService;
   private final InjectDuplicateService injectDuplicateService;
+  private final ScenarioInjectService scenarioInjectService;
 
   @GetMapping(SCENARIO_URI + "/{scenarioId}/injects/simple")
   @RBAC(
@@ -194,9 +195,6 @@ public Inject updateInjectActivationForScenario(
   public void deleteInjectForScenario(
       @PathVariable @NotBlank final String scenarioId,
       @PathVariable @NotBlank final String injectId) {
-    Scenario scenario = this.scenarioService.scenario(scenarioId);
-    assert scenarioId.equals(scenario.getId());
-    this.injectDocumentRepository.deleteDocumentsFromInject(injectId);
-    this.injectRepository.deleteById(injectId);
+    this.scenarioInjectService.deleteInject(scenarioId, injectId);
   }
 }
diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/ExerciseInjectApi.java b/openbas-api/src/main/java/io/openbas/rest/inject/SimulationInjectApi.java
similarity index 97%
rename from openbas-api/src/main/java/io/openbas/rest/inject/ExerciseInjectApi.java
rename to openbas-api/src/main/java/io/openbas/rest/inject/SimulationInjectApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/inject/ExerciseInjectApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/inject/SimulationInjectApi.java
@@ -24,8 +24,8 @@
 import io.openbas.rest.inject.service.InjectDuplicateService;
 import io.openbas.rest.inject.service.InjectService;
 import io.openbas.rest.inject.service.InjectStatusService;
+import io.openbas.rest.inject.service.SimulationInjectService;
 import io.openbas.service.InjectSearchService;
-import io.openbas.service.InjectTestStatusService;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Content;
@@ -53,22 +53,21 @@
 @Slf4j
 @RestController
 @RequiredArgsConstructor
-public class ExerciseInjectApi extends RestBehavior {
+public class SimulationInjectApi extends RestBehavior {
 
   private final InjectSearchService injectSearchService;
-  private final InjectTestStatusService injectTestStatusService;
   private final Executor executor;
   private final InjectorContractRepository injectorContractRepository;
   private final CommunicationRepository communicationRepository;
   private final ExerciseRepository exerciseRepository;
   private final UserRepository userRepository;
   private final InjectRepository injectRepository;
-  private final InjectDocumentRepository injectDocumentRepository;
   private final TeamRepository teamRepository;
   private final ExecutionContextService executionContextService;
   private final InjectService injectService;
   private final InjectDuplicateService injectDuplicateService;
   private final InjectStatusService injectStatusService;
+  private final SimulationInjectService simulationInjectService;
 
   @Operation(summary = "Retrieved injects for an exercise")
   @ApiResponses(
@@ -272,8 +271,7 @@ public InjectStatus executeInject(
       actionPerformed = Action.WRITE,
       resourceType = ResourceType.SIMULATION)
   public void deleteInject(@PathVariable String exerciseId, @PathVariable String injectId) {
-    injectDocumentRepository.deleteDocumentsFromInject(injectId);
-    injectRepository.deleteById(injectId);
+    this.simulationInjectService.deleteInject(exerciseId, injectId);
   }
 
   @PutMapping(EXERCISE_URI + "/{exerciseId}/injects/{injectId}/activation")
diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/service/ScenarioInjectService.java b/openbas-api/src/main/java/io/openbas/rest/inject/service/ScenarioInjectService.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/main/java/io/openbas/rest/inject/service/ScenarioInjectService.java
@@ -0,0 +1,24 @@
+package io.openbas.rest.inject.service;
+
+import io.openbas.database.model.Scenario;
+import io.openbas.service.ScenarioService;
+import jakarta.validation.constraints.NotBlank;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class ScenarioInjectService {
+
+  private final ScenarioService scenarioService;
+  private final InjectService injectService;
+
+  public void deleteInject(@NotBlank final String scenarioId, @NotBlank final String injectId) {
+    Scenario scenario = this.scenarioService.scenario(scenarioId);
+    assert scenarioId.equals(scenario.getId());
+    this.injectService.delete(injectId);
+    this.scenarioService.updateScenario(scenario);
+  }
+}
diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/service/SimulationInjectService.java b/openbas-api/src/main/java/io/openbas/rest/inject/service/SimulationInjectService.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/main/java/io/openbas/rest/inject/service/SimulationInjectService.java
@@ -0,0 +1,23 @@
+package io.openbas.rest.inject.service;
+
+import io.openbas.database.model.Exercise;
+import io.openbas.rest.exercise.service.ExerciseService;
+import jakarta.validation.constraints.NotBlank;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class SimulationInjectService {
+
+  private final ExerciseService exerciseService;
+  private final InjectService injectService;
+
+  public void deleteInject(@NotBlank final String exerciseId, @NotBlank final String injectId) {
+    Exercise exercise = this.exerciseService.exercise(exerciseId);
+    injectService.delete(injectId);
+    this.exerciseService.updateExercise(exercise);
+  }
+}
diff --git a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java
--- a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java
@@ -1,7 +1,6 @@
 package io.openbas.database.model;
 
 import static io.openbas.helper.InjectExpectationHelper.computeStatus;
-import static java.time.Instant.now;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -19,7 +18,9 @@
 import java.util.Objects;
 import lombok.Getter;
 import lombok.Setter;
+import org.hibernate.annotations.CreationTimestamp;
 import org.hibernate.annotations.Type;
+import org.hibernate.annotations.UpdateTimestamp;
 import org.hibernate.annotations.UuidGenerator;
 
 @Getter
@@ -114,13 +115,15 @@ public EXPECTATION_STATUS getResponse() {
   @Setter
   @Column(name = "inject_expectation_created_at")
   @JsonProperty("inject_expectation_created_at")
-  private Instant createdAt = now();
+  @CreationTimestamp
+  private Instant createdAt;
 
   @Queryable(filterable = true, label = "updated at")
   @Setter
   @Column(name = "inject_expectation_updated_at")
   @JsonProperty("inject_expectation_updated_at")
-  private Instant updatedAt = now();
+  @UpdateTimestamp
+  private Instant updatedAt;
 
   @Setter
   @Column(name = "inject_expectation_group")
diff --git a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectationResult.java b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectationResult.java
--- a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectationResult.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectationResult.java
@@ -2,11 +2,15 @@
 
 import jakarta.validation.constraints.NotBlank;
 import java.util.Map;
+import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
 @Builder
+@NoArgsConstructor
+@AllArgsConstructor
 public class InjectExpectationResult {
 
   private String sourceId;
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Scenario.java b/openbas-model/src/main/java/io/openbas/database/model/Scenario.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Scenario.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Scenario.java
@@ -40,6 +40,11 @@
 @Grantable(grantFieldName = "scenario")
 public class Scenario implements Base {
 
+  public enum RECURRENCE_STATUS {
+    SCHEDULED,
+    NOT_PLANNED,
+  }
+
   public enum SEVERITY {
     @JsonProperty("low")
     low,
diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawInjectIndexing.java b/openbas-model/src/main/java/io/openbas/database/raw/RawInjectIndexing.java
--- a/openbas-model/src/main/java/io/openbas/database/raw/RawInjectIndexing.java
+++ b/openbas-model/src/main/java/io/openbas/database/raw/RawInjectIndexing.java
@@ -17,6 +17,10 @@ public interface RawInjectIndexing {
 
   Instant getInjector_contract_updated_at();
 
+  Instant getTracking_sent_date();
+
+  Set<String> getInject_platforms();
+
   Set<String> getInject_attack_patterns();
 
   Set<String> getInject_children();
diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawScenario.java b/openbas-model/src/main/java/io/openbas/database/raw/RawScenario.java
--- a/openbas-model/src/main/java/io/openbas/database/raw/RawScenario.java
+++ b/openbas-model/src/main/java/io/openbas/database/raw/RawScenario.java
@@ -9,6 +9,8 @@ public interface RawScenario {
 
   public String getScenario_name();
 
+  public String getScenario_recurrence();
+
   Instant getScenario_created_at();
 
   Instant getScenario_injects_updated_at();
@@ -24,4 +26,6 @@ public interface RawScenario {
   public Set<String> getScenario_teams();
 
   public Set<String> getScenario_injects();
+
+  public Set<String> getScenario_platforms();
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawSimulation.java b/openbas-model/src/main/java/io/openbas/database/raw/RawSimulation.java
--- a/openbas-model/src/main/java/io/openbas/database/raw/RawSimulation.java
+++ b/openbas-model/src/main/java/io/openbas/database/raw/RawSimulation.java
@@ -56,6 +56,8 @@ public interface RawSimulation {
 
   Set<String> getExercise_users();
 
+  Set<String> getExercise_platforms();
+
   Set<String> getLessons_answers();
 
   Set<String> getLogs();
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java
@@ -354,12 +354,13 @@ List<Object[]> findAllOptionByNameLinkedToFindingsWithContext(
   @Query(
       value =
           "WITH exercise_data AS ("
-              + "SELECT ex.exercise_id, ex.exercise_name, ex.exercise_created_at, MAX(se.scenario_id) AS scenario_id, "
-              + "GREATEST(ex.exercise_updated_at, max(inj.inject_updated_at)) as exercise_injects_updated_at, "
+              + "SELECT ex.exercise_id, ex.exercise_name, ex.exercise_status, ex.exercise_created_at, MAX(se.scenario_id) AS scenario_id, "
+              + "GREATEST(ex.exercise_updated_at, max(inj.inject_updated_at), max(ic.injector_contract_updated_at)) as exercise_injects_updated_at, "
               + "array_agg(DISTINCT et.tag_id) FILTER ( WHERE et.tag_id IS NOT NULL ) as exercise_tags, "
               + "array_agg(DISTINCT ete.team_id) FILTER ( WHERE ete.team_id IS NOT NULL ) as exercise_teams, "
               + "array_agg(DISTINCT ia.asset_id) FILTER ( WHERE ia.asset_id IS NOT NULL ) as exercise_assets, "
-              + "array_agg(DISTINCT iag.asset_group_id) FILTER ( WHERE iag.asset_group_id IS NOT NULL ) as exercise_asset_groups "
+              + "array_agg(DISTINCT iag.asset_group_id) FILTER ( WHERE iag.asset_group_id IS NOT NULL ) as exercise_asset_groups, "
+              + "array_union_agg(ic.injector_contract_platforms) FILTER ( WHERE ic.injector_contract_platforms IS NOT NULL ) as exercise_platforms "
               + "FROM exercises ex "
               + "LEFT JOIN exercises_tags et ON et.exercise_id = ex.exercise_id "
               + "LEFT JOIN exercises_teams ete ON ete.exercise_id = ex.exercise_id "
@@ -367,6 +368,7 @@ List<Object[]> findAllOptionByNameLinkedToFindingsWithContext(
               + "LEFT JOIN injects_assets ia ON ia.inject_id = inj.inject_id "
               + "LEFT JOIN injects_asset_groups iag ON iag.inject_id = inj.inject_id "
               + "LEFT JOIN scenarios_exercises se ON ex.exercise_id = se.exercise_id "
+              + "LEFT JOIN injectors_contracts ic ON ic.injector_contract_id = inj.inject_injector_contract "
               + "GROUP BY ex.exercise_id, ex.exercise_name, ex.exercise_created_at, ex.exercise_updated_at"
               + ") "
               + "SELECT * FROM exercise_data ed "
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/InjectRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/InjectRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/InjectRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/InjectRepository.java
@@ -36,7 +36,8 @@ public interface InjectRepository
 
   @Query(
       value =
-          "SELECT f.inject_id, f.inject_title, f.inject_scenario, f.inject_exercise, f.inject_created_at, f.inject_updated_at, f.inject_injector_contract, ic.injector_contract_updated_at, "
+          "SELECT f.inject_id, f.inject_title, f.inject_scenario, f.inject_exercise, f.inject_created_at, f.inject_updated_at, f.inject_injector_contract, ic.injector_contract_updated_at, ins.tracking_sent_date, "
+              + "array_union_agg(ic.injector_contract_platforms) FILTER ( WHERE ic.injector_contract_platforms IS NOT NULL ) as inject_platforms, "
               + "array_agg(icap.attack_pattern_id) FILTER ( WHERE icap.attack_pattern_id IS NOT NULL ) as inject_attack_patterns, "
               + "array_agg(ap.phase_id) FILTER ( WHERE ap.phase_id IS NOT NULL ) as inject_kill_chain_phases, "
               + "array_agg(idp.inject_children_id) FILTER ( WHERE idp.inject_children_id IS NOT NULL ) as inject_children, "
@@ -78,7 +79,7 @@ public interface InjectRepository
               + "    WHERE sub_ic.injector_contract_id = inject_children.inject_injector_contract "
               + "      AND sub_ic.injector_contract_updated_at > :from "
               + ")"
-              + "GROUP BY f.inject_id, f.inject_updated_at, ic.injector_contract_updated_at ORDER BY GREATEST(f.inject_updated_at, ic.injector_contract_updated_at) ASC LIMIT "
+              + "GROUP BY f.inject_id, f.inject_updated_at, ic.injector_contract_updated_at, ins.tracking_sent_date ORDER BY GREATEST(f.inject_updated_at, ic.injector_contract_updated_at) ASC LIMIT "
               + Constants.INDEXING_RECORD_SET_SIZE
               + ";",
       nativeQuery = true)
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java
@@ -25,19 +25,21 @@ public interface ScenarioRepository
   @Query(
       value =
           "WITH scenario_data AS ("
-              + "    SELECT s.scenario_id, s.scenario_name, s.scenario_created_at,"
-              + "           GREATEST(s.scenario_updated_at, max(inj.inject_updated_at)) as scenario_injects_updated_at,"
-              + "           array_agg(DISTINCT st.tag_id) FILTER (WHERE st.tag_id IS NOT NULL) as scenario_tags,"
-              + "           array_agg(DISTINCT ste.team_id) FILTER (WHERE ste.team_id IS NOT NULL) as scenario_teams,"
-              + "           array_agg(DISTINCT ia.asset_id) FILTER (WHERE ia.asset_id IS NOT NULL) as scenario_assets,"
-              + "           array_agg(DISTINCT iag.asset_group_id) FILTER (WHERE iag.asset_group_id IS NOT NULL) as scenario_asset_groups"
-              + "    FROM scenarios s"
-              + "             LEFT JOIN scenarios_tags st ON st.scenario_id = s.scenario_id"
-              + "             LEFT JOIN scenarios_teams ste ON ste.scenario_id = s.scenario_id"
-              + "             LEFT JOIN injects inj ON s.scenario_id = inj.inject_scenario"
-              + "             LEFT JOIN injects_assets ia ON ia.inject_id = inj.inject_id"
-              + "             LEFT JOIN injects_asset_groups iag ON iag.inject_id = inj.inject_id"
-              + "    GROUP BY s.scenario_id, s.scenario_name, s.scenario_created_at, s.scenario_updated_at"
+              + "SELECT s.scenario_id, s.scenario_name, s.scenario_recurrence, s.scenario_created_at, "
+              + "GREATEST(s.scenario_updated_at, max(inj.inject_updated_at), max(ic.injector_contract_updated_at)) as scenario_injects_updated_at, "
+              + "array_agg(DISTINCT st.tag_id) FILTER (WHERE st.tag_id IS NOT NULL) as scenario_tags, "
+              + "array_agg(DISTINCT ste.team_id) FILTER (WHERE ste.team_id IS NOT NULL) as scenario_teams, "
+              + "array_agg(DISTINCT ia.asset_id) FILTER (WHERE ia.asset_id IS NOT NULL) as scenario_assets, "
+              + "array_agg(DISTINCT iag.asset_group_id) FILTER (WHERE iag.asset_group_id IS NOT NULL) as scenario_asset_groups, "
+              + "array_union_agg(ic.injector_contract_platforms) FILTER ( WHERE ic.injector_contract_platforms IS NOT NULL ) as scenario_platforms "
+              + "FROM scenarios s "
+              + "LEFT JOIN scenarios_tags st ON st.scenario_id = s.scenario_id "
+              + "LEFT JOIN scenarios_teams ste ON ste.scenario_id = s.scenario_id "
+              + "LEFT JOIN injects inj ON s.scenario_id = inj.inject_scenario "
+              + "LEFT JOIN injects_assets ia ON ia.inject_id = inj.inject_id "
+              + "LEFT JOIN injects_asset_groups iag ON iag.inject_id = inj.inject_id "
+              + "LEFT JOIN injectors_contracts ic ON ic.injector_contract_id = inj.inject_injector_contract "
+              + "GROUP BY s.scenario_id, s.scenario_name, s.scenario_created_at, s.scenario_updated_at"
               + ") "
               + "SELECT * FROM scenario_data sd "
               + "WHERE sd.scenario_injects_updated_at > :from "
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/SecurityPlatformRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/SecurityPlatformRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/SecurityPlatformRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/SecurityPlatformRepository.java
@@ -1,7 +1,10 @@
 package io.openbas.database.repository;
 
+import io.openbas.database.model.AssetType;
 import io.openbas.database.model.Document;
 import io.openbas.database.model.SecurityPlatform;
+import io.openbas.database.raw.RawAsset;
+import io.openbas.utils.Constants;
 import java.time.Instant;
 import java.util.List;
 import java.util.Optional;
@@ -39,4 +42,26 @@ Optional<SecurityPlatform> findByExternalReference(
           + "union "
           + "select distinct s.logoLight from SecurityPlatform s ")
   List<Document> securityPlatformLogo();
+
+  @Query(
+      value =
+          "SELECT a.asset_id, a.asset_name, a.asset_created_at, a.asset_updated_at "
+              + "FROM assets a "
+              + "WHERE a.asset_updated_at > :from AND a.asset_type = '"
+              + AssetType.Values.SECURITY_PLATFORM_TYPE
+              + "' "
+              + "GROUP BY a.asset_id, a.asset_updated_at "
+              + "ORDER BY a.asset_updated_at LIMIT "
+              + Constants.INDEXING_RECORD_SET_SIZE
+              + ";",
+      nativeQuery = true)
+  List<RawAsset> findForIndexing(@Param("from") Instant from);
+
+  @Query(
+      "SELECT DISTINCT a FROM Asset a "
+          + "WHERE a.type = '"
+          + AssetType.Values.SECURITY_PLATFORM_TYPE
+          + "' AND "
+          + "(:name IS NULL OR lower(a.name) LIKE lower(concat('%', cast(coalesce(:name, '') as string), '%')))")
+  List<SecurityPlatform> findAllByName(String name);
 }
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/inject/EsInject.java b/openbas-model/src/main/java/io/openbas/engine/model/inject/EsInject.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/inject/EsInject.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/inject/EsInject.java
@@ -3,7 +3,10 @@
 import io.openbas.annotation.EsQueryable;
 import io.openbas.annotation.Indexable;
 import io.openbas.annotation.Queryable;
+import io.openbas.database.model.Endpoint;
+import io.openbas.database.model.ExecutionStatus;
 import io.openbas.engine.model.EsBase;
+import java.time.Instant;
 import java.util.Set;
 import lombok.Getter;
 import lombok.Setter;
@@ -18,9 +21,13 @@ public class EsInject extends EsBase {
   @Queryable(label = "inject title")
   private String inject_title;
 
-  @Queryable(label = "inject status")
+  @Queryable(label = "inject status", filterable = true, refEnumClazz = ExecutionStatus.class)
+  @EsQueryable(keyword = true)
   private String inject_status;
 
+  @Queryable(label = "execution date", filterable = true, sortable = true)
+  private Instant inject_execution_date;
+
   // -- SIDE --
 
   @Queryable(label = "scenario")
@@ -31,7 +38,7 @@ public class EsInject extends EsBase {
   @EsQueryable(keyword = true)
   private String base_simulation_side; // Must finish by _side
 
-  @Queryable(label = "attack patterns")
+  @Queryable(label = "attack patterns", filterable = true)
   @EsQueryable(keyword = true)
   private Set<String> base_attack_patterns_side; // Must finish by _side
 
@@ -66,4 +73,13 @@ public class EsInject extends EsBase {
   @Queryable(label = "teams", filterable = true, dynamicValues = true)
   @EsQueryable(keyword = true)
   private Set<String> base_teams_side; // Must finish by _side
+
+  // -- SIDE DENORMALIZED --
+  // like side but directly names instead of ids in the Set
+  // Don't forget to keep track of updated_at values in the SQL query indexing for those attributes
+  // denormalized
+
+  @Queryable(label = "platforms", filterable = true, refEnumClazz = Endpoint.PLATFORM_TYPE.class)
+  @EsQueryable(keyword = true)
+  private Set<String> base_platforms_side_denormalized;
 }
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/inject/InjectHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/inject/InjectHandler.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/inject/InjectHandler.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/inject/InjectHandler.java
@@ -4,6 +4,7 @@
 import static org.springframework.util.CollectionUtils.isEmpty;
 import static org.springframework.util.StringUtils.hasText;
 
+import io.openbas.database.model.ExecutionStatus;
 import io.openbas.database.raw.RawInjectIndexing;
 import io.openbas.database.repository.InjectRepository;
 import io.openbas.engine.Handler;
@@ -50,7 +51,13 @@ public List<EsInject> fetch(Instant from) {
                   buildRestrictions(inject.getInject_scenario(), inject.getInject_Exercise()));
               // Specific
               esInject.setInject_title(inject.getInject_title());
-              esInject.setInject_status(inject.getInject_status_name());
+              esInject.setInject_status(
+                  inject.getInject_status_name() != null
+                          && !inject.getInject_status_name().isBlank()
+                      ? inject.getInject_status_name()
+                      : ExecutionStatus.DRAFT.name());
+              esInject.setBase_platforms_side_denormalized(inject.getInject_platforms());
+              esInject.setInject_execution_date(inject.getTracking_sent_date());
               // Dependencies
               List<String> dependencies = new ArrayList<>();
               if (hasText(inject.getInject_scenario())) {
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/EsInjectExpectation.java b/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/EsInjectExpectation.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/EsInjectExpectation.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/EsInjectExpectation.java
@@ -99,4 +99,8 @@ public class EsInjectExpectation extends EsBase {
       clazz = String.class)
   @EsQueryable(keyword = true)
   private Set<String> base_attack_patterns_side; // Must finish by _side
+
+  @Queryable(label = "security platform", filterable = true, dynamicValues = true)
+  @EsQueryable(keyword = true)
+  private Set<String> base_security_platforms_side; // Must finish by _side
 }
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/InjectExpectationHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/InjectExpectationHandler.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/InjectExpectationHandler.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/InjectExpectationHandler.java
@@ -1,18 +1,23 @@
 package io.openbas.engine.model.injectexpectation;
 
 import static io.openbas.engine.EsUtils.buildRestrictions;
-import static io.openbas.helper.InjectExpectationHelper.computeStatus;
+import static io.openbas.helper.InjectExpectationHelper.computeStatusForIndexing;
 import static java.lang.String.valueOf;
 import static org.springframework.util.CollectionUtils.isEmpty;
 import static org.springframework.util.StringUtils.hasText;
 
-import io.openbas.database.model.InjectExpectation;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.openbas.database.model.InjectExpectationResult;
 import io.openbas.database.raw.RawInjectExpectation;
 import io.openbas.database.repository.InjectExpectationRepository;
 import io.openbas.engine.Handler;
 import java.time.Instant;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 
@@ -20,6 +25,8 @@
 @RequiredArgsConstructor
 public class InjectExpectationHandler implements Handler<EsInjectExpectation> {
 
+  private static final String SECURITY_PLATFORM = "security-platform";
+
   private final InjectExpectationRepository injectExpectationRepository;
 
   @Override
@@ -52,12 +59,6 @@ public List<EsInjectExpectation> fetch(Instant from) {
               esInjectExpectation.setInject_expectation_results(
                   injectExpectation.getInject_expectation_results());
 
-              InjectExpectation injectExpectationTmp = new InjectExpectation();
-              injectExpectationTmp.setScore(injectExpectation.getInject_expectation_score());
-              injectExpectationTmp.setExpectedScore(
-                  injectExpectation.getInject_expectation_expected_score());
-              esInjectExpectation.setInject_expectation_status(
-                  valueOf(computeStatus(injectExpectationTmp)));
               esInjectExpectation.setInject_expectation_score(
                   injectExpectation.getInject_expectation_score());
               esInjectExpectation.setInject_expectation_expected_score(
@@ -101,6 +102,29 @@ public List<EsInjectExpectation> fetch(Instant from) {
                 esInjectExpectation.setBase_attack_patterns_side(
                     injectExpectation.getAttack_pattern_ids());
               }
+              if (injectExpectation.getInject_expectation_results() != null
+                  && !injectExpectation.getInject_expectation_results().isBlank()) {
+                ObjectMapper mapper = new ObjectMapper();
+                try {
+                  List<InjectExpectationResult> results =
+                      mapper.readValue(
+                          injectExpectation.getInject_expectation_results(),
+                          new TypeReference<>() {});
+                  Set<String> securityPlatformIds =
+                      results.stream()
+                          .filter(
+                              injectExpectationResult ->
+                                  SECURITY_PLATFORM.equals(injectExpectationResult.getSourceType()))
+                          .map(InjectExpectationResult::getSourceId)
+                          .collect(Collectors.toSet());
+                  dependencies.addAll(securityPlatformIds);
+                  esInjectExpectation.setBase_security_platforms_side(securityPlatformIds);
+                } catch (Exception e) {
+                  esInjectExpectation.setBase_security_platforms_side(new HashSet<>());
+                }
+              }
+              esInjectExpectation.setInject_expectation_status(
+                  valueOf(computeStatusForIndexing(injectExpectation)));
               esInjectExpectation.setBase_dependencies(dependencies);
               return esInjectExpectation;
             })
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/scenario/EsScenario.java b/openbas-model/src/main/java/io/openbas/engine/model/scenario/EsScenario.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/scenario/EsScenario.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/scenario/EsScenario.java
@@ -3,6 +3,8 @@
 import io.openbas.annotation.EsQueryable;
 import io.openbas.annotation.Indexable;
 import io.openbas.annotation.Queryable;
+import io.openbas.database.model.Endpoint;
+import io.openbas.database.model.Scenario;
 import io.openbas.engine.model.EsBase;
 import java.util.Set;
 import lombok.Getter;
@@ -18,6 +20,13 @@ public class EsScenario extends EsBase {
   @Queryable(label = "scenario name")
   private String name;
 
+  @Queryable(
+      label = "scenario status",
+      filterable = true,
+      refEnumClazz = Scenario.RECURRENCE_STATUS.class)
+  @EsQueryable(keyword = true)
+  private String status;
+
   // -- SIDE --
 
   @Queryable(label = "tags", filterable = true, dynamicValues = true)
@@ -35,4 +44,13 @@ public class EsScenario extends EsBase {
   @Queryable(label = "teams", filterable = true, dynamicValues = true)
   @EsQueryable(keyword = true)
   private Set<String> base_teams_side; // Must finish by _side
+
+  // -- SIDE DENORMALIZED --
+  // like side but directly names instead of ids in the Set
+  // Don't forget to keep track of updated_at values in the SQL query indexing for those attributes
+  // denormalized
+
+  @Queryable(label = "platforms", filterable = true, refEnumClazz = Endpoint.PLATFORM_TYPE.class)
+  @EsQueryable(keyword = true)
+  private Set<String> base_platforms_side_denormalized;
 }
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/scenario/ScenarioHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/scenario/ScenarioHandler.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/scenario/ScenarioHandler.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/scenario/ScenarioHandler.java
@@ -3,6 +3,7 @@
 import static io.openbas.engine.EsUtils.buildRestrictions;
 import static org.springframework.util.CollectionUtils.isEmpty;
 
+import io.openbas.database.model.Scenario;
 import io.openbas.database.raw.RawScenario;
 import io.openbas.database.repository.ScenarioRepository;
 import io.openbas.engine.Handler;
@@ -32,12 +33,17 @@ public List<EsScenario> fetch(Instant from) {
               EsScenario esScenario = new EsScenario();
               // Base
               esScenario.setBase_id(scenario.getScenario_id());
+              esScenario.setStatus(
+                  scenario.getScenario_recurrence() != null
+                      ? Scenario.RECURRENCE_STATUS.SCHEDULED.name()
+                      : Scenario.RECURRENCE_STATUS.NOT_PLANNED.name());
               esScenario.setBase_created_at(scenario.getScenario_created_at());
               esScenario.setBase_updated_at(scenario.getScenario_injects_updated_at());
 
               esScenario.setBase_representative(scenario.getScenario_name());
               esScenario.setBase_restrictions(buildRestrictions(scenario.getScenario_id()));
               // Specific
+              esScenario.setBase_platforms_side_denormalized(scenario.getScenario_platforms());
               // Dependencies
               List<String> dependencies = new ArrayList<>();
               if (!isEmpty(scenario.getScenario_tags())) {
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/securityplatform/EsSecurityPlatform.java b/openbas-model/src/main/java/io/openbas/engine/model/securityplatform/EsSecurityPlatform.java
new file mode 100644
--- /dev/null
+++ b/openbas-model/src/main/java/io/openbas/engine/model/securityplatform/EsSecurityPlatform.java
@@ -0,0 +1,17 @@
+package io.openbas.engine.model.securityplatform;
+
+import io.openbas.annotation.Indexable;
+import io.openbas.annotation.Queryable;
+import io.openbas.engine.model.EsBase;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Indexable(index = "security-platform", label = "Security Platform")
+public class EsSecurityPlatform extends EsBase {
+  /* Every attribute must be uniq, so prefixed with the entity type! */
+  /* Except relationships, they should have same name on every model! */
+  @Queryable(label = "security platform name")
+  private String name;
+}
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/securityplatform/SecurityPlatformHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/securityplatform/SecurityPlatformHandler.java
new file mode 100644
--- /dev/null
+++ b/openbas-model/src/main/java/io/openbas/engine/model/securityplatform/SecurityPlatformHandler.java
@@ -0,0 +1,40 @@
+package io.openbas.engine.model.securityplatform;
+
+import static io.openbas.engine.EsUtils.buildRestrictions;
+
+import io.openbas.database.raw.RawAsset;
+import io.openbas.database.repository.SecurityPlatformRepository;
+import io.openbas.engine.Handler;
+import java.time.Instant;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+public class SecurityPlatformHandler implements Handler<EsSecurityPlatform> {
+
+  private final SecurityPlatformRepository securityPlatformRepository;
+
+  @Override
+  public List<EsSecurityPlatform> fetch(Instant from) {
+    Instant queryFrom = from != null ? from : Instant.ofEpochMilli(0);
+    List<RawAsset> forIndexing = securityPlatformRepository.findForIndexing(queryFrom);
+    return forIndexing.stream()
+        .map(
+            securityPlatform -> {
+              EsSecurityPlatform esSecurityPlatform = new EsSecurityPlatform();
+              // Base
+              esSecurityPlatform.setBase_id(securityPlatform.getAsset_id());
+              esSecurityPlatform.setName(securityPlatform.getAsset_name());
+              esSecurityPlatform.setBase_created_at(securityPlatform.getAsset_created_at());
+              esSecurityPlatform.setBase_updated_at(securityPlatform.getAsset_updated_at());
+              esSecurityPlatform.setBase_representative(securityPlatform.getAsset_name());
+              esSecurityPlatform.setBase_restrictions(
+                  buildRestrictions(securityPlatform.getAsset_id()));
+              // Specific
+              return esSecurityPlatform;
+            })
+        .toList();
+  }
+}
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/simulation/EsSimulation.java b/openbas-model/src/main/java/io/openbas/engine/model/simulation/EsSimulation.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/simulation/EsSimulation.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/simulation/EsSimulation.java
@@ -3,6 +3,8 @@
 import io.openbas.annotation.EsQueryable;
 import io.openbas.annotation.Indexable;
 import io.openbas.annotation.Queryable;
+import io.openbas.database.model.Endpoint;
+import io.openbas.database.model.ExerciseStatus;
 import io.openbas.engine.model.EsBase;
 import java.util.Set;
 import lombok.Getter;
@@ -18,6 +20,10 @@ public class EsSimulation extends EsBase {
   @Queryable(label = "simulation name", filterable = true)
   private String name;
 
+  @Queryable(label = "simulation status", filterable = true, refEnumClazz = ExerciseStatus.class)
+  @EsQueryable(keyword = true)
+  private String status;
+
   // -- SIDE --
 
   @Queryable(label = "tags", filterable = true, dynamicValues = true)
@@ -39,4 +45,13 @@ public class EsSimulation extends EsBase {
   @Queryable(label = "scenario", filterable = true, dynamicValues = true)
   @EsQueryable(keyword = true)
   private String base_scenario_side; // Must finish by _side
+
+  // -- SIDE DENORMALIZED --
+  // like side but directly names instead of ids in the Set
+  // Don't forget to keep track of updated_at values in the SQL query indexing for those attributes
+  // denormalized
+
+  @Queryable(label = "platforms", filterable = true, refEnumClazz = Endpoint.PLATFORM_TYPE.class)
+  @EsQueryable(keyword = true)
+  private Set<String> base_platforms_side_denormalized;
 }
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/simulation/SimulationHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/simulation/SimulationHandler.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/simulation/SimulationHandler.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/simulation/SimulationHandler.java
@@ -29,6 +29,7 @@ public List<EsSimulation> fetch(Instant from) {
               EsSimulation esSimulation = new EsSimulation();
               // Base
               esSimulation.setBase_id(simulation.getExercise_id());
+              esSimulation.setStatus(simulation.getExercise_status());
               esSimulation.setBase_created_at(simulation.getExercise_created_at());
               esSimulation.setBase_updated_at(simulation.getExercise_injects_updated_at());
               esSimulation.setName(simulation.getExercise_name());
@@ -37,6 +38,7 @@ public List<EsSimulation> fetch(Instant from) {
               esSimulation.setBase_restrictions(
                   buildRestrictions(simulation.getExercise_id(), simulation.getScenario_id()));
               // Specific
+              esSimulation.setBase_platforms_side_denormalized(simulation.getExercise_platforms());
               // Dependencies
               List<String> dependencies = new ArrayList<>();
               if (!isEmpty(simulation.getExercise_tags())) {
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/team/TeamGroupHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/team/TeamHandler.java
similarity index 95%
rename from openbas-model/src/main/java/io/openbas/engine/model/team/TeamGroupHandler.java
rename to openbas-model/src/main/java/io/openbas/engine/model/team/TeamHandler.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/team/TeamGroupHandler.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/team/TeamHandler.java
@@ -12,7 +12,7 @@
 
 @RequiredArgsConstructor
 @Service
-public class TeamGroupHandler implements Handler<EsTeam> {
+public class TeamHandler implements Handler<EsTeam> {
 
   private final TeamRepository teamRepository;
 
diff --git a/openbas-model/src/main/java/io/openbas/helper/InjectExpectationHelper.java b/openbas-model/src/main/java/io/openbas/helper/InjectExpectationHelper.java
--- a/openbas-model/src/main/java/io/openbas/helper/InjectExpectationHelper.java
+++ b/openbas-model/src/main/java/io/openbas/helper/InjectExpectationHelper.java
@@ -1,9 +1,11 @@
 package io.openbas.helper;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import io.openbas.database.model.InjectExpectation;
 import io.openbas.database.model.InjectExpectation.EXPECTATION_STATUS;
-import io.openbas.database.model.InjectExpectationResult;
-import java.util.List;
+import io.openbas.database.raw.RawInjectExpectation;
 import org.jetbrains.annotations.NotNull;
 
 public class InjectExpectationHelper {
@@ -16,7 +18,8 @@ public static EXPECTATION_STATUS computeStatus(
       return EXPECTATION_STATUS.PENDING;
     }
     if (injectExpectation.getTeam() != null) {
-      return computeStatusForTeam(injectExpectation.getResults());
+      // Only 1 result possible when expectation team
+      return computeStatusForTeam(injectExpectation.getResults().getFirst().getResult());
     }
 
     if (injectExpectation.getScore() >= injectExpectation.getExpectedScore()) {
@@ -28,10 +31,37 @@ public static EXPECTATION_STATUS computeStatus(
     return EXPECTATION_STATUS.PARTIAL;
   }
 
-  public static EXPECTATION_STATUS computeStatusForTeam(
-      @NotNull final List<InjectExpectationResult> results) {
-    String result = results.getFirst().getResult().toUpperCase();
-    return switch (result) {
+  public static EXPECTATION_STATUS computeStatusForIndexing(
+      @NotNull final RawInjectExpectation injectExpectation) {
+    if (injectExpectation.getInject_expectation_score() == null) {
+      return EXPECTATION_STATUS.PENDING;
+    }
+    if (injectExpectation.getTeam_id() != null) {
+      ObjectMapper mapper = new ObjectMapper();
+      try {
+        JsonNode node = mapper.readTree(injectExpectation.getInject_expectation_results());
+        // Only 1 result possible when expectation team
+        return computeStatusForTeam(node.get(0).get("result").asText());
+      } catch (JsonProcessingException e) {
+        return EXPECTATION_STATUS.PENDING;
+      }
+    }
+
+    if (injectExpectation.getInject_expectation_score()
+        >= injectExpectation.getInject_expectation_expected_score()) {
+      return EXPECTATION_STATUS.SUCCESS;
+    }
+    if (0.0 == injectExpectation.getInject_expectation_score()) {
+      return EXPECTATION_STATUS.FAILED;
+    }
+    return EXPECTATION_STATUS.PARTIAL;
+  }
+
+  public static EXPECTATION_STATUS computeStatusForTeam(final String result) {
+    if (result == null) {
+      return EXPECTATION_STATUS.PENDING;
+    }
+    return switch (result.toUpperCase()) {
       case "FAILED" -> EXPECTATION_STATUS.FAILED;
       case "SUCCESS" -> EXPECTATION_STATUS.SUCCESS;
       case "PARTIAL" -> EXPECTATION_STATUS.PARTIAL;


Test patch:
diff --git a/openbas-api/src/test/java/io/openbas/rest/SecurityPlatformApiTest.java b/openbas-api/src/test/java/io/openbas/rest/SecurityPlatformApiTest.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/test/java/io/openbas/rest/SecurityPlatformApiTest.java
@@ -0,0 +1,136 @@
+package io.openbas.rest;
+
+import static io.openbas.rest.asset.security_platforms.SecurityPlatformApi.SECURITY_PLATFORM_URI;
+import static io.openbas.utils.JsonUtils.asJsonString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+
+import io.openbas.IntegrationTest;
+import io.openbas.database.model.SecurityPlatform;
+import io.openbas.database.repository.SecurityPlatformRepository;
+import io.openbas.utils.fixtures.composers.SecurityPlatformComposer;
+import io.openbas.utils.mockUser.WithMockAdminUser;
+import jakarta.persistence.EntityManager;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+import org.json.JSONArray;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.transaction.annotation.Transactional;
+
+@TestInstance(PER_CLASS)
+@Transactional
+class SecurityPlatformApiTest extends IntegrationTest {
+
+  private static final String SECURITY_PLATFORM_NAME = "My Security Platform ";
+
+  @Autowired private MockMvc mvc;
+  @Autowired private SecurityPlatformRepository securityPlatformRepository;
+  @Autowired private SecurityPlatformComposer securityPlatformComposer;
+  @Autowired private EntityManager entityManager;
+
+  @BeforeEach
+  public void beforeEach() {
+    securityPlatformComposer.reset();
+  }
+
+  // Options tests
+
+  private List<SecurityPlatformComposer.Composer> prepareOptionsSecurityPlatformTestData() {
+    SecurityPlatform securityPlatformTest1 = new SecurityPlatform();
+    securityPlatformTest1.setName(SECURITY_PLATFORM_NAME + "1");
+    securityPlatformTest1.setSecurityPlatformType(SecurityPlatform.SECURITY_PLATFORM_TYPE.SIEM);
+    SecurityPlatformComposer.Composer securityPlatform1 =
+        securityPlatformComposer.forSecurityPlatform(securityPlatformTest1);
+    securityPlatform1.persist();
+    SecurityPlatform securityPlatformTest2 = new SecurityPlatform();
+    securityPlatformTest2.setName(SECURITY_PLATFORM_NAME + "2");
+    securityPlatformTest2.setSecurityPlatformType(SecurityPlatform.SECURITY_PLATFORM_TYPE.SIEM);
+    SecurityPlatformComposer.Composer securityPlatform2 =
+        securityPlatformComposer.forSecurityPlatform(securityPlatformTest2);
+    securityPlatform2.persist();
+    entityManager.flush();
+    entityManager.clear();
+    return List.of(securityPlatform1, securityPlatform2);
+  }
+
+  Stream<Arguments> optionsByNameTestParameters() {
+    return Stream.of(
+        Arguments.of("toto", 0),
+        Arguments.of(SECURITY_PLATFORM_NAME, 2),
+        Arguments.of(SECURITY_PLATFORM_NAME + "1", 1));
+  }
+
+  @DisplayName("Test optionsByName")
+  @ParameterizedTest
+  @MethodSource("optionsByNameTestParameters")
+  @WithMockAdminUser
+  void optionsByNameTest(String searchText, Integer expectedNumberOfResults) throws Exception {
+    // --PREPARE--
+    prepareOptionsSecurityPlatformTestData();
+
+    // --EXECUTE--;
+    String response =
+        mvc.perform(
+                get(SECURITY_PLATFORM_URI + "/options")
+                    .queryParam("searchText", searchText)
+                    .accept(MediaType.APPLICATION_JSON))
+            .andReturn()
+            .getResponse()
+            .getContentAsString();
+
+    JSONArray jsonArray = new JSONArray(response);
+
+    // --ASSERT--
+    assertEquals(expectedNumberOfResults, jsonArray.length());
+  }
+
+  Stream<Arguments> optionsByIdTestParameters() {
+    return Stream.of(
+        Arguments.of(0, 0), // Case 1: 0 ID given
+        Arguments.of(1, 1), // Case 1: 1 ID given
+        Arguments.of(2, 2) // Case 2: 2 IDs given
+        );
+  }
+
+  @DisplayName("Test optionsById")
+  @ParameterizedTest
+  @MethodSource("optionsByIdTestParameters")
+  @WithMockAdminUser
+  void optionsByIdTest(Integer numberOfSecurityPlatformToProvide, Integer expectedNumberOfResults)
+      throws Exception {
+    List<SecurityPlatformComposer.Composer> securityPlatforms =
+        prepareOptionsSecurityPlatformTestData();
+
+    List<String> securityPlatformIdsToSearch = new ArrayList<>();
+    for (int i = 0; i < numberOfSecurityPlatformToProvide; i++) {
+      securityPlatformIdsToSearch.add(securityPlatforms.get(i).get().getId());
+    }
+
+    // --EXECUTE--;
+    String response =
+        mvc.perform(
+                post(SECURITY_PLATFORM_URI + "/options")
+                    .content(asJsonString(securityPlatformIdsToSearch))
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .accept(MediaType.APPLICATION_JSON))
+            .andReturn()
+            .getResponse()
+            .getContentAsString();
+
+    JSONArray jsonArray = new JSONArray(response);
+
+    // --ASSERT--
+    assertEquals(expectedNumberOfResults, jsonArray.length());
+  }
+}
diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/composers/SecurityPlatformComposer.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/composers/SecurityPlatformComposer.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/composers/SecurityPlatformComposer.java
@@ -0,0 +1,41 @@
+package io.openbas.utils.fixtures.composers;
+
+import io.openbas.database.model.SecurityPlatform;
+import io.openbas.database.repository.SecurityPlatformRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SecurityPlatformComposer extends ComposerBase<SecurityPlatform> {
+  @Autowired private SecurityPlatformRepository securityPlatformRepository;
+
+  public class Composer extends InnerComposerBase<SecurityPlatform> {
+    private final SecurityPlatform securityPlatform;
+
+    public Composer(SecurityPlatform securityPlatform) {
+      this.securityPlatform = securityPlatform;
+    }
+
+    @Override
+    public Composer persist() {
+      securityPlatformRepository.save(securityPlatform);
+      return this;
+    }
+
+    @Override
+    public Composer delete() {
+      securityPlatformRepository.delete(securityPlatform);
+      return this;
+    }
+
+    @Override
+    public SecurityPlatform get() {
+      return this.securityPlatform;
+    }
+  }
+
+  public SecurityPlatformComposer.Composer forSecurityPlatform(SecurityPlatform securityPlatform) {
+    generatedItems.add(securityPlatform);
+    return new SecurityPlatformComposer.Composer(securityPlatform);
+  }
+}
diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/payload_info/PayloadInfoPaper.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/payload_info/PayloadInfoPaper.tsx
--- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/payload_info/PayloadInfoPaper.tsx
+++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/payload_info/PayloadInfoPaper.tsx
@@ -1,7 +1,8 @@
-import { Box, Chip, Paper, Tooltip, Typography } from '@mui/material';
+import { Box, Paper, Typography } from '@mui/material';
 import { useTheme } from '@mui/material/styles';
 import { makeStyles } from 'tss-react/mui';
 
+import AttackPatternChip from '../../../../../components/AttackPatternChip';
 import { useFormatter } from '../../../../../components/i18n';
 import ItemTags from '../../../../../components/ItemTags';
 import PlatformIcon from '../../../../../components/PlatformIcon';
@@ -17,14 +18,6 @@ const useStyles = makeStyles()(theme => ({
     gap: theme.spacing(3),
   },
   allWidth: { gridColumn: 'span 2' },
-  chip: {
-    fontSize: 12,
-    height: 25,
-    marginRight: theme.spacing(1),
-    textTransform: 'uppercase',
-    borderRadius: 4,
-    width: 180,
-  },
   platformIcon: { marginRight: theme.spacing(1) },
 }));
 
@@ -65,14 +58,7 @@ const PayloadInfoPaper = ({ payloadOutput }: Props) => {
           {t('Attack patterns')}
         </Typography>
         {payloadOutput.payload_attack_patterns && payloadOutput.payload_attack_patterns.length === 0 ? '-' : payloadOutput.payload_attack_patterns?.map((attackPattern: AttackPatternSimple) => (
-          <Tooltip key={attackPattern.attack_pattern_id} title={`[${attackPattern.attack_pattern_external_id}] ${attackPattern.attack_pattern_name}`}>
-            <Chip
-              variant="outlined"
-              classes={{ root: classes.chip }}
-              color="primary"
-              label={`[${attackPattern.attack_pattern_external_id}] ${attackPattern.attack_pattern_name}`}
-            />
-          </Tooltip>
+          <AttackPatternChip key={attackPattern.attack_pattern_id} attackPattern={attackPattern}></AttackPatternChip>
         ))}
       </Box>
       <Box>