Skip to content

Commit

Permalink
[backend/frontend] Migrate audiences to teams (#475)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamuelHassine authored Dec 31, 2023
1 parent 617ad17 commit d040f0c
Show file tree
Hide file tree
Showing 123 changed files with 4,248 additions and 3,122 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ OpenEx is an open source platform allowing organizations to plan, schedule and c

The goal is to create a powerful, reliable and open source tool to effectively plan and play all types of training, exercises and simulation from the technical level to the strategic one. The need for rationalization and capitalization from one year to the next, as well as the publication of ISO 22398: 2013 standard necessarily lead to the need to acquire specific software.

OpenEx aims to respond to these issues, which not only concern state services but also many private organizations. With different modules (scenarios, audiences, simulations, verification of means of communication, encryption, etc.), the platform offers advantages such as collaborative work, real-time monitoring, statistics or the management of feedback.
OpenEx aims to respond to these issues, which not only concern state services but also many private organizations. With different modules (scenarios, teams, simulations, verification of means of communication, encryption, etc.), the platform offers advantages such as collaborative work, real-time monitoring, statistics or the management of feedback.

Finally, OpenEx supports different types of inject, allowing the tool to be integrated with emails, SMS platforms, social medias, alarm systems, etc. All currently supported integration can be found in the [OpenEx ecosystem](https://filigran.notion.site/OpenEx-Ecosystem-30d8eb73d7d04611843e758ddef8941b).

Expand Down
173 changes: 88 additions & 85 deletions openex-api/src/main/java/io/openex/helper/InjectHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import io.openex.contract.Contract;
import io.openex.database.model.*;
import io.openex.database.repository.AudienceRepository;
import io.openex.database.repository.TeamRepository;
import io.openex.database.repository.DryInjectRepository;
import io.openex.database.repository.InjectRepository;
import io.openex.database.specification.DryInjectSpecification;
Expand All @@ -24,106 +24,109 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.openex.database.specification.AudienceSpecification.fromExercise;
import static io.openex.helper.StreamHelper.fromIterable;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Stream.concat;

@Component
public class InjectHelper {

private InjectRepository injectRepository;
private DryInjectRepository dryInjectRepository;
private AudienceRepository audienceRepository;
private ContractService contractService;
private ExecutionContextService executionContextService;
private InjectRepository injectRepository;
private DryInjectRepository dryInjectRepository;
private TeamRepository teamRepository;
private ContractService contractService;
private ExecutionContextService executionContextService;

@Autowired
public void setContractService(ContractService contractService) {
this.contractService = contractService;
}
@Autowired
public void setContractService(ContractService contractService) {
this.contractService = contractService;
}

@Autowired
public void setAudienceRepository(AudienceRepository audienceRepository) {
this.audienceRepository = audienceRepository;
}
@Autowired
public void setTeamRepository(TeamRepository teamRepository) {
this.teamRepository = teamRepository;
}

@Autowired
public void setInjectRepository(InjectRepository injectRepository) {
this.injectRepository = injectRepository;
}
@Autowired
public void setInjectRepository(InjectRepository injectRepository) {
this.injectRepository = injectRepository;
}

@Autowired
public void setDryInjectRepository(DryInjectRepository dryInjectRepository) {
this.dryInjectRepository = dryInjectRepository;
}
@Autowired
public void setDryInjectRepository(DryInjectRepository dryInjectRepository) {
this.dryInjectRepository = dryInjectRepository;
}

@Autowired
public void setExecutionContextService(@NotNull final ExecutionContextService executionContextService) {
this.executionContextService = executionContextService;
}
@Autowired
public void setExecutionContextService(@NotNull final ExecutionContextService executionContextService) {
this.executionContextService = executionContextService;
}

private List<Audience> getInjectAudiences(Inject inject) {
Exercise exercise = inject.getExercise();
return inject.isAllAudiences() ?
fromIterable(this.audienceRepository.findAll(fromExercise(exercise.getId()))) : inject.getAudiences();
}
private List<Team> getInjectTeams(Inject inject) {
Exercise exercise = inject.getExercise();
return inject.isAllTeams() ? exercise.getTeams() : inject.getTeams();
}

private Stream<Tuple2<User, String>> getUsersFromInjection(Injection injection) {
if (injection instanceof DryInject dryInject) {
return dryInject.getRun().getUsers().stream()
.map(user -> Tuples.of(user, "Dryrun"));
} else if (injection instanceof Inject inject) {
List<Audience> audiences = getInjectAudiences(inject);
return audiences.stream().filter(Audience::isEnabled)
.flatMap(audience -> audience.getUsers().stream()
.map(user -> Tuples.of(user, audience.getName())));
private Stream<Tuple2<User, String>> getUsersFromInjection(Injection injection) {
if (injection instanceof DryInject dryInject) {
return dryInject.getRun().getUsers().stream()
.map(user -> Tuples.of(user, "Dryrun"));
} else if (injection instanceof Inject inject) {
List<Team> teams = getInjectTeams(inject);
// We get all the teams for this inject
// But those team can be used in other exercises with different players enabled
// So we need to focus on team players only enabled in the context of the current exercise
return teams.stream().flatMap(team ->
team.getExerciseTeamUsers().stream()
.filter(exerciseTeamUser -> exerciseTeamUser.getExercise().getId().equals(injection.getExercise().getId()))
.map(exerciseTeamUser -> Tuples.of(exerciseTeamUser.getUser(), team.getName()))
);
}
throw new UnsupportedOperationException("Unsupported type of Injection");
}
throw new UnsupportedOperationException("Unsupported type of Injection");
}

private List<ExecutionContext> usersFromInjection(Injection injection) {
return getUsersFromInjection(injection)
.collect(groupingBy(Tuple2::getT1)).entrySet().stream()
.map(entry -> this.executionContextService.executionContext(entry.getKey(), injection,
entry.getValue().stream().flatMap(ua -> Stream.of(ua.getT2())).toList()))
.toList();
}
private List<ExecutionContext> usersFromInjection(Injection injection) {
return getUsersFromInjection(injection)
.collect(groupingBy(Tuple2::getT1)).entrySet().stream()
.map(entry -> this.executionContextService.executionContext(entry.getKey(), injection,
entry.getValue().stream().flatMap(ua -> Stream.of(ua.getT2())).toList()))
.toList();
}

private boolean isBeforeOrEqualsNow(Injection injection) {
Instant now = Instant.now();
Instant injectWhen = injection.getDate().orElseThrow();
return injectWhen.equals(now) || injectWhen.isBefore(now);
}
private boolean isBeforeOrEqualsNow(Injection injection) {
Instant now = Instant.now();
Instant injectWhen = injection.getDate().orElseThrow();
return injectWhen.equals(now) || injectWhen.isBefore(now);
}

@Transactional
public List<ExecutableInject> getInjectsToRun() {
// Get injects
List<Inject> injects = this.injectRepository.findAll(InjectSpecification.executable());
Stream<ExecutableInject> executableInjects = injects.stream()
.filter(this::isBeforeOrEqualsNow)
.sorted(Inject.executionComparator)
.map(inject -> {
Contract contract = this.contractService.resolveContract(inject);
List<Audience> audiences = getInjectAudiences(inject);
return new ExecutableInject(true, false, inject, contract, audiences, usersFromInjection(inject));
});
// Get dry injects
List<DryInject> dryInjects = this.dryInjectRepository.findAll(DryInjectSpecification.executable());
Stream<ExecutableInject> executableDryInjects = dryInjects.stream()
.filter(this::isBeforeOrEqualsNow)
.sorted(DryInject.executionComparator)
.map(dry -> {
Inject inject = dry.getInject();
Contract contract = this.contractService.resolveContract(inject);
List<Audience> audiences = new ArrayList<>(); // No audiences in dry run, only direct users
return new ExecutableInject(false, false, dry, inject, contract, audiences, usersFromInjection(dry));
});
// Combine injects and dry
return concat(executableInjects, executableDryInjects)
.filter(
executableInject -> executableInject.getContract() == null || !executableInject.getContract().isManual()
)
.collect(Collectors.toList());
}
@Transactional
public List<ExecutableInject> getInjectsToRun() {
// Get injects
List<Inject> injects = this.injectRepository.findAll(InjectSpecification.executable());
Stream<ExecutableInject> executableInjects = injects.stream()
.filter(this::isBeforeOrEqualsNow)
.sorted(Inject.executionComparator)
.map(inject -> {
Contract contract = this.contractService.resolveContract(inject);
List<Team> teams = getInjectTeams(inject);
return new ExecutableInject(true, false, inject, contract, teams, usersFromInjection(inject));
});
// Get dry injects
List<DryInject> dryInjects = this.dryInjectRepository.findAll(DryInjectSpecification.executable());
Stream<ExecutableInject> executableDryInjects = dryInjects.stream()
.filter(this::isBeforeOrEqualsNow)
.sorted(DryInject.executionComparator)
.map(dry -> {
Inject inject = dry.getInject();
Contract contract = this.contractService.resolveContract(inject);
List<Team> teams = new ArrayList<>(); // No teams in dry run, only direct users
return new ExecutableInject(false, false, dry, inject, contract, teams, usersFromInjection(dry));
});
// Combine injects and dry
return concat(executableInjects, executableDryInjects)
.filter(
executableInject -> executableInject.getContract() == null || !executableInject.getContract().isManual()
)
.collect(Collectors.toList());
}
}
72 changes: 40 additions & 32 deletions openex-api/src/main/java/io/openex/importer/V1_DataImporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class V1_DataImporter implements Importer {
private DocumentRepository documentRepository;
private TagRepository tagRepository;
private ExerciseRepository exerciseRepository;
private AudienceRepository audienceRepository;
private TeamRepository teamRepository;
private ObjectiveRepository objectiveRepository;
private InjectRepository injectRepository;
private OrganizationRepository organizationRepository;
Expand Down Expand Up @@ -120,8 +120,8 @@ public void setExerciseRepository(ExerciseRepository exerciseRepository) {
}

@Autowired
public void setAudienceRepository(AudienceRepository audienceRepository) {
this.audienceRepository = audienceRepository;
public void setTeamRepository(TeamRepository teamRepository) {
this.teamRepository = teamRepository;
}

@Autowired
Expand Down Expand Up @@ -207,8 +207,8 @@ private void importInjects(Map<String, Base> baseIds, Exercise exercise, List<Js
JsonNode dependsOnNode = injectNode.get("inject_depends_on");
String dependsOn = !dependsOnNode.isNull() ? baseIds.get(dependsOnNode.asText()).getId() : null;
Long dependsDuration = injectNode.get("inject_depends_duration").asLong();
boolean allAudiences = injectNode.get("inject_all_audiences").booleanValue();
injectRepository.importSave(injectId, title, description, country, city, type, contract, allAudiences,
boolean allTeams = injectNode.get("inject_all_teams").booleanValue();
injectRepository.importSave(injectId, title, description, country, city, type, contract, allTeams,
true, exercise.getId(), dependsOn, dependsDuration, content);
baseIds.put(id, new BaseHolder(injectId));
// Tags
Expand All @@ -217,11 +217,11 @@ private void importInjects(Map<String, Base> baseIds, Exercise exercise, List<Js
String remappedId = baseIds.get(tagId).getId();
injectRepository.addTag(injectId, remappedId);
});
// Audiences
List<String> injectAudienceIds = resolveJsonIds(injectNode, "inject_audiences");
injectAudienceIds.forEach(audienceId -> {
String remappedId = baseIds.get(audienceId).getId();
injectRepository.addAudience(injectId, remappedId);
// Teams
List<String> injectTeamIds = resolveJsonIds(injectNode, "inject_teams");
injectTeamIds.forEach(teamId -> {
String remappedId = baseIds.get(teamId).getId();
injectRepository.addTeam(injectId, remappedId);
});
// Documents
List<JsonNode> injectDocuments = resolveJsonElements(injectNode, "inject_documents").toList();
Expand Down Expand Up @@ -408,25 +408,33 @@ public void importData(JsonNode importNode, Map<String, ImportEntry> docReferenc
});
}

// ------------ Handling audiences
Iterator<JsonNode> exerciseAudiences = importNode.get("exercise_audiences").elements();
exerciseAudiences.forEachRemaining(nodeAudience -> {
String id = nodeAudience.get("audience_id").textValue();
Audience audience = new Audience();
audience.setName(nodeAudience.get("audience_name").textValue());
audience.setDescription(nodeAudience.get("audience_description").textValue());
// Tags
List<String> audienceTagIds = resolveJsonIds(nodeAudience, "audience_tags");
List<Tag> tagsForAudience = audienceTagIds.stream().map(baseIds::get).map(base -> (Tag) base).toList();
audience.setTags(tagsForAudience);
// Users
List<String> audienceUserIds = resolveJsonIds(nodeAudience, "audience_users");
List<User> usersForAudience = audienceUserIds.stream().map(baseIds::get).map(base -> (User) base).toList();
audience.setUsers(usersForAudience);
// Finalize
audience.setExercise(savedExercise);
Audience savedAudience = audienceRepository.save(audience);
baseIds.put(id, savedAudience);
// ------------ Handling teams
Iterator<JsonNode> exerciseTeams = importNode.get("exercise_teams").elements();
exerciseTeams.forEachRemaining(nodeTeam -> {
String id = nodeTeam.get("team_id").textValue();
String teamName = nodeTeam.get("team_name").textValue();
// Prevent duplication of team, based on the team name
List<Team> existingTeams = teamRepository.findByNameIgnoreCase(teamName);
if (existingTeams.size() == 1) {
baseIds.put(id, existingTeams.get(0));
} else {
Team team = new Team();
team.setName(nodeTeam.get("team_name").textValue());
team.setDescription(nodeTeam.get("team_description").textValue());
// Tags
List<String> teamTagIds = resolveJsonIds(nodeTeam, "team_tags");
List<Tag> tagsForTeam = teamTagIds.stream().map(baseIds::get).map(base -> (Tag) base).toList();
team.setTags(tagsForTeam);
// Users
List<String> teamUserIds = resolveJsonIds(nodeTeam, "team_users");
List<User> usersForTeam = teamUserIds.stream().map(baseIds::get).map(base -> (User) base).toList();
team.setUsers(usersForTeam);
List<Exercise> savedExercises = new ArrayList<>();
savedExercises.add(savedExercise);
team.setExercises(savedExercises);
Team savedTeam = teamRepository.save(team);
baseIds.put(id, savedTeam);
}
});

// ------------ Handling challenges
Expand Down Expand Up @@ -550,11 +558,11 @@ public void importData(JsonNode importNode, Map<String, ImportEntry> docReferenc
lessonsCategory.setDescription(nodeLessonCategory.get("lessons_category_description").textValue());
lessonsCategory.setOrder(nodeLessonCategory.get("lessons_category_order").intValue());
lessonsCategory.setExercise(exercise);
List<Audience> lessonsCategoryAudiences = resolveJsonIds(nodeLessonCategory, "lessons_category_audiences")
.stream().map(audienceId -> (Audience) baseIds.get(audienceId))
List<Team> lessonsCategoryTeams = resolveJsonIds(nodeLessonCategory, "lessons_category_teams")
.stream().map(teamId -> (Team) baseIds.get(teamId))
.filter(Objects::nonNull)
.toList();
lessonsCategory.setAudiences(lessonsCategoryAudiences);
lessonsCategory.setTeams(lessonsCategoryTeams);
LessonsCategory savedLessonsCategory = lessonsCategoryRepository.save(lessonsCategory);
baseIds.put(id, savedLessonsCategory);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import static io.openex.contract.ContractDef.contractBuilder;
import static io.openex.contract.fields.ContractChallenge.challengeField;
import static io.openex.contract.fields.ContractAttachment.attachmentField;
import static io.openex.contract.fields.ContractAudience.audienceField;
import static io.openex.contract.fields.ContractTeam.teamField;
import static io.openex.contract.fields.ContractCheckbox.checkboxField;
import static io.openex.contract.fields.ContractText.textField;
import static io.openex.contract.fields.ContractTextArea.richTextareaField;
Expand Down Expand Up @@ -63,7 +63,7 @@ public List<Contract> contracts() {
.mandatory(textField("subject", "Subject", "New challenges published for ${user.email}"))
.mandatory(richTextareaField("body", "Body", messageBody))
.optional(checkboxField("encrypted", "Encrypted", false))
.mandatory(audienceField("audiences", "Audiences", Multiple))
.mandatory(teamField("audiences", "Audiences", Multiple))
.optional(attachmentField("attachments", "Attachments", Multiple))
.build();
Contract publishChallenge = executableContract(contractConfig,
Expand Down
Loading

0 comments on commit d040f0c

Please sign in to comment.