Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Introduce module-API for Atlas #9486

Open
wants to merge 22 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d3bb5e5
Add common AbstractApi
ole-ve Oct 14, 2024
152af19
Add PROFILE_ATLAS constant
ole-ve Oct 14, 2024
dc1dbab
Move atlas db config to atlas module
ole-ve Oct 14, 2024
76c4b38
Add atlas API classes and use it in every other module
ole-ve Oct 14, 2024
1db7e34
Add exemplary arch tests for api access, inheritance, and annotation
ole-ve Oct 14, 2024
ce7853d
Typo
ole-ve Oct 14, 2024
8350776
Thanks to the ArchTests
ole-ve Oct 14, 2024
786324d
Add missing classes
ole-ve Oct 14, 2024
1a10cdc
Revert "Thanks to the ArchTests"
ole-ve Oct 14, 2024
3b93050
checkstyleMain
ole-ve Oct 15, 2024
6de84ef
Annotate module api controllers with Spring profile
ole-ve Oct 15, 2024
7b54e04
Annotate atlas module config with Spring profile
ole-ve Oct 15, 2024
ead9d20
Change visibility of AtlasApiArchitectureTest to package-private
ole-ve Oct 15, 2024
7fda208
Change constructor visibility of some atlas apis to public
ole-ve Oct 15, 2024
d41f968
Avoid re-assigning variables in lambda expression
ole-ve Oct 15, 2024
8561759
Improve module arch test method names
ole-ve Oct 15, 2024
a8f0cc0
Fix logic of determining disabled profiles
ole-ve Oct 15, 2024
bf2dd86
Temporarily use PROFILE_CORE to verify getOrThrow
ole-ve Oct 15, 2024
755f3be
Use competencyProgressApi in spy-verification (in non-atlas tests)
ole-ve Oct 15, 2024
23f7a0d
Merge branch 'develop' into chore/atlas-java-api
ole-ve Oct 22, 2024
763afc1
Make updateProgressForCourses ignore module not present
ole-ve Oct 22, 2024
2f3c744
Merge branch 'develop' into chore/atlas-java-api
ole-ve Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import de.tum.cit.aet.artemis.assessment.repository.ResultRepository;
import de.tum.cit.aet.artemis.assessment.repository.StudentScoreRepository;
import de.tum.cit.aet.artemis.assessment.repository.TeamScoreRepository;
import de.tum.cit.aet.artemis.atlas.service.competency.CompetencyProgressService;
import de.tum.cit.aet.artemis.atlas.api.CompetencyProgressApi;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.core.security.SecurityUtils;
Expand Down Expand Up @@ -74,7 +74,7 @@ public class ParticipantScoreScheduleService {

private Optional<Instant> lastScheduledRun = Optional.empty();

private final CompetencyProgressService competencyProgressService;
private final CompetencyProgressApi competencyProgressApi;
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

private final ParticipantScoreRepository participantScoreRepository;

Expand All @@ -96,11 +96,11 @@ public class ParticipantScoreScheduleService {
*/
private final AtomicBoolean isRunning = new AtomicBoolean(false);

public ParticipantScoreScheduleService(@Qualifier("taskScheduler") TaskScheduler scheduler, CompetencyProgressService competencyProgressService,
public ParticipantScoreScheduleService(@Qualifier("taskScheduler") TaskScheduler scheduler, CompetencyProgressApi competencyProgressApi,
ParticipantScoreRepository participantScoreRepository, StudentScoreRepository studentScoreRepository, TeamScoreRepository teamScoreRepository,
ExerciseRepository exerciseRepository, ResultRepository resultRepository, UserRepository userRepository, TeamRepository teamRepository) {
this.scheduler = scheduler;
this.competencyProgressService = competencyProgressService;
this.competencyProgressApi = competencyProgressApi;
this.participantScoreRepository = participantScoreRepository;
this.studentScoreRepository = studentScoreRepository;
this.teamScoreRepository = teamScoreRepository;
Expand Down Expand Up @@ -336,7 +336,7 @@ private void executeTask(Long exerciseId, Long participantId, Instant resultLast
if (scoreParticipant instanceof Team team && !Hibernate.isInitialized(team.getStudents())) {
scoreParticipant = teamRepository.findWithStudentsByIdElseThrow(team.getId());
}
competencyProgressService.updateProgressByLearningObjectSync(score.getExercise(), scoreParticipant.getParticipants());
competencyProgressApi.updateProgressByLearningObjectSync(score.getExercise(), scoreParticipant.getParticipants());
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
catch (Exception e) {
log.error("Exception while processing participant score for exercise {} and participant {} for participant scores:", exerciseId, participantId, e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.tum.cit.aet.artemis.atlas.api;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_ATLAS;

import org.springframework.core.env.Environment;

import de.tum.cit.aet.artemis.core.api.AbstractApi;

public abstract class AbstractAtlasApi extends AbstractApi {

protected AbstractAtlasApi(Environment environment) {
super(environment, PROFILE_ATLAS);
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package de.tum.cit.aet.artemis.atlas.api;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import jakarta.validation.constraints.NotNull;

import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;

import de.tum.cit.aet.artemis.atlas.domain.LearningObject;
import de.tum.cit.aet.artemis.atlas.domain.competency.Competency;
import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency;
import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository;
import de.tum.cit.aet.artemis.atlas.service.competency.CompetencyProgressService;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.exercise.domain.participation.Participant;

@Profile(PROFILE_CORE)
@Controller
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
public class CompetencyProgressApi extends AbstractAtlasApi {
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

private final Optional<CompetencyProgressService> competencyProgressService;

private final Optional<CompetencyRepository> competencyRepository;

ole-ve marked this conversation as resolved.
Show resolved Hide resolved
public CompetencyProgressApi(Environment environment, Optional<CompetencyProgressService> competencyProgressService, Optional<CompetencyRepository> competencyRepository) {
super(environment);
this.competencyProgressService = competencyProgressService;
this.competencyRepository = competencyRepository;
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

public void updateProgressByLearningObjectForParticipantAsync(LearningObject learningObject, @NotNull Participant participant) {
competencyProgressService.ifPresent(service -> service.updateProgressByLearningObjectForParticipantAsync(learningObject, participant));
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
}

public void updateProgressByLearningObjectAsync(LearningObject learningObject) {
competencyProgressService.ifPresent(service -> service.updateProgressByLearningObjectAsync(learningObject));
}

public void updateProgressByCompetencyAsync(CourseCompetency competency) {
competencyProgressService.ifPresent(service -> service.updateProgressByCompetencyAsync(competency));
}

public void updateProgressForUpdatedLearningObjectAsync(LearningObject originalLearningObject, Optional<LearningObject> updatedLearningObject) {
competencyProgressService.ifPresent(service -> service.updateProgressForUpdatedLearningObjectAsync(originalLearningObject, updatedLearningObject));
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

public void updateProgressByLearningObjectSync(LearningObject learningObject, Set<User> users) {
competencyProgressService.ifPresent(service -> service.updateProgressByLearningObjectSync(learningObject, users));
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

/**
* Updates the progress for all competencies of the given courses.
*
* @param activeCourses the active courses
*/
public void updateProgressForCourses(List<Course> activeCourses) {
CompetencyProgressService competencyProgressService = getOrThrow(this.competencyProgressService);
CompetencyRepository competencyRepository = getOrThrow(this.competencyRepository);
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

activeCourses.forEach(course -> {
List<Competency> competencies = competencyRepository.findByCourseIdOrderById(course.getId());
// Asynchronously update the progress for each competency
competencies.forEach(competencyProgressService::updateProgressByCompetencyAsync);
});
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package de.tum.cit.aet.artemis.atlas.api;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Optional;
import java.util.Set;

import jakarta.validation.constraints.NotNull;

import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;

import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency;
import de.tum.cit.aet.artemis.atlas.repository.CompetencyRelationRepository;
import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository;
import de.tum.cit.aet.artemis.atlas.repository.CourseCompetencyRepository;
import de.tum.cit.aet.artemis.atlas.repository.PrerequisiteRepository;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.lecture.domain.ExerciseUnit;
import de.tum.cit.aet.artemis.lecture.domain.LectureUnit;

@Profile(PROFILE_CORE)
@Controller
public class CourseCompetencyApi extends AbstractAtlasApi {
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

private final Optional<CourseCompetencyRepository> optionalCourseCompetencyRepository;

private final Optional<CompetencyRelationRepository> optionalCompetencyRelationRepository;

private final Optional<PrerequisiteRepository> optionalPrerequisitesRepository;

private final Optional<CompetencyRepository> optionalCompetencyRepository;

public CourseCompetencyApi(Environment environment, Optional<CourseCompetencyRepository> optionalCourseCompetencyRepository,
Optional<CompetencyRelationRepository> optionalCompetencyRelationRepository, Optional<PrerequisiteRepository> optionalPrerequisitesRepository,
Optional<CompetencyRepository> optionalCompetencyRepository) {
super(environment);
this.optionalCourseCompetencyRepository = optionalCourseCompetencyRepository;
this.optionalCompetencyRelationRepository = optionalCompetencyRelationRepository;
this.optionalPrerequisitesRepository = optionalPrerequisitesRepository;
this.optionalCompetencyRepository = optionalCompetencyRepository;
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

public void findAndSetCompetenciesForCourse(Course course) {
optionalCompetencyRepository.ifPresent(service -> course.setNumberOfCompetencies(service.countByCourse(course)));
optionalPrerequisitesRepository.ifPresent(service -> course.setNumberOfPrerequisites(service.countByCourse(course)));
}

public void deleteCompetenciesOfCourse(Course course) {
optionalCompetencyRelationRepository.ifPresent(service -> service.deleteAllByCourseId(course.getId()));
optionalPrerequisitesRepository.ifPresent(service -> service.deleteAll(course.getPrerequisites()));
optionalCompetencyRepository.ifPresent(service -> service.deleteAll(course.getCompetencies()));
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

/**
* ToDo: Consider moving to service
* Deletes the competencies associated with the given lecture unit.
*
* @param lectureUnit the lecture unit
*/
public void deleteCompetencyOfLectureUnit(@NotNull LectureUnit lectureUnit) {
if (optionalCourseCompetencyRepository.isEmpty()) {
return;
}

CourseCompetencyRepository repository = optionalCourseCompetencyRepository.get();
if (!(lectureUnit instanceof ExerciseUnit)) {
// update associated competencies
Set<CourseCompetency> competencies = lectureUnit.getCompetencies();
repository.saveAll(competencies.stream().map(competency -> {
CourseCompetency updatedCompetency = repository.findByIdWithLectureUnitsElseThrow(competency.getId());
updatedCompetency.getLectureUnits().remove(lectureUnit);
return updatedCompetency;
}).toList());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package de.tum.cit.aet.artemis.atlas.api;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Optional;

import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;

import de.tum.cit.aet.artemis.atlas.dto.metrics.StudentMetricsDTO;
import de.tum.cit.aet.artemis.atlas.service.LearningMetricsService;

@Profile(PROFILE_CORE)
@Controller
public class LearningMetricsApi extends AbstractAtlasApi {
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

private final Optional<LearningMetricsService> learningMetricsService;
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

public LearningMetricsApi(Environment environment, Optional<LearningMetricsService> learningMetricsService) {
super(environment);
this.learningMetricsService = learningMetricsService;
}

public StudentMetricsDTO getStudentCourseMetrics(long userId, long courseId) {
return getOrThrow(learningMetricsService).getStudentCourseMetrics(userId, courseId);
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.tum.cit.aet.artemis.atlas.api;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Optional;

import jakarta.validation.constraints.NotNull;

import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;

import de.tum.cit.aet.artemis.atlas.service.learningpath.LearningPathService;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;

@Profile(PROFILE_CORE)
@Controller
public class LearningPathApi extends AbstractAtlasApi {
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

private final Optional<LearningPathService> optionalLearningPathService;
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

public LearningPathApi(Environment environment, Optional<LearningPathService> optionalLearningPathService) {
super(environment);
this.optionalLearningPathService = optionalLearningPathService;
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

public void generateLearningPathForUser(@NotNull Course course, @NotNull User user) {
optionalLearningPathService.ifPresent(service -> service.generateLearningPathForUser(course, user));
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

public void generateLearningPaths(@NotNull Course course) {
optionalLearningPathService.ifPresent(service -> service.generateLearningPaths(course));
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package de.tum.cit.aet.artemis.atlas.api;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Optional;
import java.util.Set;

import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;

import de.tum.cit.aet.artemis.atlas.domain.science.ScienceEvent;
import de.tum.cit.aet.artemis.atlas.repository.ScienceEventRepository;

@Profile(PROFILE_CORE)
@Controller
public class ScienceEventApi extends AbstractAtlasApi {
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

private final Optional<ScienceEventRepository> scienceEventRepository;

public ScienceEventApi(Environment environment, Optional<ScienceEventRepository> scienceEventRepository) {
super(environment);
this.scienceEventRepository = scienceEventRepository;
}

public Set<ScienceEvent> findAllByIdentity(String login) {
return getOrThrow(scienceEventRepository).findAllByIdentity(login);
}

public void renameIdentity(String oldIdentity, String newIdentity) {
scienceEventRepository.ifPresent(repository -> repository.renameIdentity(oldIdentity, newIdentity));
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.tum.cit.aet.artemis.atlas.config;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Set;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import de.tum.cit.aet.artemis.core.config.AbstractModuleConfig;
import de.tum.cit.aet.artemis.core.config.DatabaseConfiguration;
import de.tum.cit.aet.artemis.core.repository.base.RepositoryImpl;
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

@Profile(PROFILE_CORE) // in the future, we will switch to PROFILE_ATLAS
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
@Configuration
public class AtlasConfig extends AbstractModuleConfig {

/**
* Crucially required profiles for which - if not enabled - renders Atlas useless.
*/
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
private static final Set<Profiles> requiredProfiles = Set.of(Profiles.of(PROFILE_CORE));
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

public AtlasConfig(Environment environment) {
super(environment, requiredProfiles);
}

@Profile(PROFILE_CORE)
@Configuration
@EnableJpaRepositories(basePackages = { "de.tum.cit.aet.artemis.atlas.repository" }, repositoryBaseClass = RepositoryImpl.class)
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
static class AtlasDatabaseConfig extends DatabaseConfiguration {
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.tum.cit.aet.artemis.exercise.service;
package de.tum.cit.aet.artemis.atlas.service;

ole-ve marked this conversation as resolved.
Show resolved Hide resolved
import static de.tum.cit.aet.artemis.core.config.Constants.MIN_SCORE_GREEN;
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import org.springframework.web.bind.annotation.RestController;

import de.tum.cit.aet.artemis.atlas.dto.metrics.StudentMetricsDTO;
import de.tum.cit.aet.artemis.atlas.service.LearningMetricsService;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastStudentInCourse;
import de.tum.cit.aet.artemis.exercise.service.LearningMetricsService;

@Profile(PROFILE_CORE)
@RestController
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/de/tum/cit/aet/artemis/core/api/AbstractApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package de.tum.cit.aet.artemis.core.api;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Optional;

import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;

import de.tum.cit.aet.artemis.core.exception.ModuleNotPresentException;

public abstract class AbstractApi {

private final Environment environment;

private final String profileName;

public AbstractApi(Environment environment, String profileName) {
this.environment = environment;
this.profileName = profileName;
}

public boolean isActive(String profileName) {
return environment.acceptsProfiles(Profiles.of(profileName));
}
ole-ve marked this conversation as resolved.
Show resolved Hide resolved

/** @noinspection OptionalUsedAsFieldOrParameterType */
protected <T> T getOrThrow(Optional<T> instance) {
// noinspection UnnecessaryLocalVariable
String moduleProfileName = PROFILE_CORE; // in the future, we will switch to profileName
if (!isActive(moduleProfileName) || instance.isEmpty()) {
throw new ModuleNotPresentException(profileName);
ole-ve marked this conversation as resolved.
Show resolved Hide resolved
}
return instance.get();
}
}
Loading
Loading