From 7024d9a870f7492c0a90c8d01e8009baee924b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20G=C3=B6=C3=9Fmann?= Date: Tue, 5 Nov 2024 10:56:53 +0100 Subject: [PATCH 01/12] Adds rough learner structure --- .../domain/profile/CourseLearnerProfile.java | 39 ++++++++++++++ .../atlas/domain/profile/LearnerProfile.java | 54 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java new file mode 100644 index 000000000000..de9b28e82153 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java @@ -0,0 +1,39 @@ +package de.tum.cit.aet.artemis.atlas.domain.profile; + +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; + +import de.tum.cit.aet.artemis.core.domain.Course; +import de.tum.cit.aet.artemis.core.domain.DomainObject; + +@Entity +@Table(name = "course_learner_profile") +public class CourseLearnerProfile extends DomainObject { + + @ManyToOne + @JoinColumn(name = "learner_profile_id") + private LearnerProfile learnerProfile; + + @OneToOne + @JoinColumn(name = "course_id") + private Course course; + + public void setLearnerProfile(LearnerProfile learnerProfile) { + this.learnerProfile = learnerProfile; + } + + public LearnerProfile getLearnerProfile() { + return this.learnerProfile; + } + + public void setCourse(Course course) { + this.course = course; + } + + public Course getCourse() { + return this.course; + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java new file mode 100644 index 000000000000..110a578f6d85 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java @@ -0,0 +1,54 @@ +package de.tum.cit.aet.artemis.atlas.domain.profile; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; + +import de.tum.cit.aet.artemis.core.domain.DomainObject; +import de.tum.cit.aet.artemis.core.domain.User; + +@Entity +@Table(name = "learner_profile") +class LearnerProfile extends DomainObject { + + @OneToOne + @JoinColumn(name = "user_id") + private User user; + + @OneToMany + private Set courseLearnerProfiles = new HashSet<>(); + + public void setUser(User user) { + this.user = user; + } + + public User getUser() { + return this.user; + } + + public void setCourseLearnerProfiles(Set courseLearnerProfiles) { + this.courseLearnerProfiles = courseLearnerProfiles; + } + + public Set getCourseLearnerProfiles() { + return this.courseLearnerProfiles; + } + + public boolean addCourseLearnerProfile(CourseLearnerProfile courseLearnerProfile) { + return this.courseLearnerProfiles.add(courseLearnerProfile); + } + + public boolean addAllCourseLearnerProfiles(Collection courseLearnerProfiles) { + return this.courseLearnerProfiles.addAll(courseLearnerProfiles); + } + + public boolean removeCourseLearnerProfle(CourseLearnerProfile courseLearnerProfile) { + return this.courseLearnerProfiles.remove(courseLearnerProfile); + } +} From 1eb67867e4ba42786d6db240f69ede9ce177dad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Thu, 7 Nov 2024 14:54:52 +0100 Subject: [PATCH 02/12] Add creation and deletion stuff --- .../domain/profile/CourseLearnerProfile.java | 37 +++++++++++- .../atlas/domain/profile/LearnerProfile.java | 10 ++-- .../CourseLearnerProfileRepository.java | 31 ++++++++++ .../repository/LearnerProfileRepository.java | 29 +++++++++ .../learningpath/LearningPathService.java | 22 ++++++- .../profile/CourseLearnerProfileService.java | 60 +++++++++++++++++++ .../profile/LearnerProfileService.java | 31 ++++++++++ .../tum/cit/aet/artemis/core/domain/User.java | 14 +++++ .../core/repository/UserRepository.java | 13 ++++ .../artemis/core/service/CourseService.java | 37 +++++++++--- .../core/service/UserScheduleService.java | 8 ++- .../service/user/UserCreationService.java | 19 ++++-- .../core/service/user/UserService.java | 7 ++- .../aet/artemis/core/web/CourseResource.java | 30 +++++----- .../changelog/20241107130000_changelog.xml | 47 +++++++++++++++ .../resources/config/liquibase/master.xml | 1 + 16 files changed, 357 insertions(+), 39 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/atlas/repository/LearnerProfileRepository.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java create mode 100644 src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java create mode 100644 src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java index de9b28e82153..222737e21ab8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java @@ -1,6 +1,8 @@ package de.tum.cit.aet.artemis.atlas.domain.profile; +import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToOne; @@ -17,10 +19,19 @@ public class CourseLearnerProfile extends DomainObject { @JoinColumn(name = "learner_profile_id") private LearnerProfile learnerProfile; - @OneToOne + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "course_id") private Course course; + @Column(name = "aim_for_grade_or_bonus") + private int aimForGradeOrBonus; + + @Column(name = "time_investment") + private int timeInvestment; + + @Column(name = "repetition_intensity") + private int repetitionIntensity; + public void setLearnerProfile(LearnerProfile learnerProfile) { this.learnerProfile = learnerProfile; } @@ -36,4 +47,28 @@ public void setCourse(Course course) { public Course getCourse() { return this.course; } + + public int getAimForGradeOrBonus() { + return aimForGradeOrBonus; + } + + public void setAimForGradeOrBonus(int aimForGradeOrBonus) { + this.aimForGradeOrBonus = aimForGradeOrBonus; + } + + public int getTimeInvestment() { + return timeInvestment; + } + + public void setTimeInvestment(int timeInvestment) { + this.timeInvestment = timeInvestment; + } + + public int getRepetitionIntensity() { + return repetitionIntensity; + } + + public void setRepetitionIntensity(int repetitionIntensity) { + this.repetitionIntensity = repetitionIntensity; + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java index 110a578f6d85..c3c7c4e95de4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java @@ -4,7 +4,9 @@ import java.util.HashSet; import java.util.Set; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; @@ -15,13 +17,13 @@ @Entity @Table(name = "learner_profile") -class LearnerProfile extends DomainObject { +public class LearnerProfile extends DomainObject { - @OneToOne + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; - @OneToMany + @OneToMany(mappedBy = "learnerProfile", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) private Set courseLearnerProfiles = new HashSet<>(); public void setUser(User user) { @@ -48,7 +50,7 @@ public boolean addAllCourseLearnerProfiles(Collection { + + @Transactional // ok because of delete + @Modifying + @Query(""" + DELETE FROM CourseLearnerProfile clp + WHERE clp.course = :course AND clp.learnerProfile.user = :user + """) + void deleteByCourseAndUser(Course course, User user); + + @Transactional // ok because of delete + @Modifying + void deleteAllByCourse(Course couese); +} diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/LearnerProfileRepository.java b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/LearnerProfileRepository.java new file mode 100644 index 000000000000..e08712461584 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/LearnerProfileRepository.java @@ -0,0 +1,29 @@ +package de.tum.cit.aet.artemis.atlas.repository; + +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.data.jpa.repository.Modifying; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import de.tum.cit.aet.artemis.atlas.domain.profile.LearnerProfile; +import de.tum.cit.aet.artemis.core.domain.User; +import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; + +@Profile(PROFILE_CORE) +@Repository +public interface LearnerProfileRepository extends ArtemisJpaRepository { + + Optional findByUser(User user); + + default LearnerProfile findByUserElseThrow(User user) { + return getValueElseThrow(findByUser(user)); + } + + @Transactional // ok because of delete + @Modifying + void deleteByUser(User user); +} diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java index 083c06123a45..c540b90ad3e3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java @@ -37,6 +37,7 @@ import de.tum.cit.aet.artemis.atlas.repository.CourseCompetencyRepository; import de.tum.cit.aet.artemis.atlas.repository.LearningPathRepository; import de.tum.cit.aet.artemis.atlas.service.competency.CompetencyProgressService; +import de.tum.cit.aet.artemis.atlas.service.profile.CourseLearnerProfileService; import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.core.dto.SearchResultPageDTO; @@ -90,10 +91,13 @@ public class LearningPathService { private final CourseCompetencyRepository courseCompetencyRepository; + private final CourseLearnerProfileService courseLearnerProfileService; + public LearningPathService(UserRepository userRepository, LearningPathRepository learningPathRepository, CompetencyProgressRepository competencyProgressRepository, LearningPathNavigationService learningPathNavigationService, CourseRepository courseRepository, CompetencyRepository competencyRepository, CompetencyRelationRepository competencyRelationRepository, LectureUnitCompletionRepository lectureUnitCompletionRepository, - StudentParticipationRepository studentParticipationRepository, CourseCompetencyRepository courseCompetencyRepository) { + StudentParticipationRepository studentParticipationRepository, CourseCompetencyRepository courseCompetencyRepository, + CourseLearnerProfileService courseLearnerProfileService) { this.userRepository = userRepository; this.learningPathRepository = learningPathRepository; this.competencyProgressRepository = competencyProgressRepository; @@ -104,6 +108,7 @@ public LearningPathService(UserRepository userRepository, LearningPathRepository this.lectureUnitCompletionRepository = lectureUnitCompletionRepository; this.studentParticipationRepository = studentParticipationRepository; this.courseCompetencyRepository = courseCompetencyRepository; + this.courseLearnerProfileService = courseLearnerProfileService; } /** @@ -113,7 +118,9 @@ public LearningPathService(UserRepository userRepository, LearningPathRepository */ public void enableLearningPathsForCourse(@NotNull Course course) { course.setLearningPathsEnabled(true); - generateLearningPaths(course); + var students = userRepository.getStudentsWithLearnerProfile(course); + courseLearnerProfileService.createCourseLearnerProfiles(course, students); + generateLearningPaths(course, students); courseRepository.save(course); log.debug("Enabled learning paths for course (id={})", course.getId()); } @@ -124,7 +131,16 @@ public void enableLearningPathsForCourse(@NotNull Course course) { * @param course course the learning paths are created for */ public void generateLearningPaths(@NotNull Course course) { - var students = userRepository.getStudents(course); + var students = userRepository.getStudentsWithLearnerProfile(course); + generateLearningPaths(course, students); + } + + /** + * Generate learning paths for all students enrolled in the course + * + * @param course course the learning paths are created for + */ + public void generateLearningPaths(@NotNull Course course, Set students) { students.forEach(student -> generateLearningPathForUser(course, student)); log.debug("Successfully created learning paths for all {} students in course (id={})", students.size(), course.getId()); } diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java new file mode 100644 index 000000000000..76202b164bfe --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java @@ -0,0 +1,60 @@ +package de.tum.cit.aet.artemis.atlas.service.profile; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import de.tum.cit.aet.artemis.atlas.domain.profile.CourseLearnerProfile; +import de.tum.cit.aet.artemis.atlas.repository.CourseLearnerProfileRepository; +import de.tum.cit.aet.artemis.atlas.repository.LearnerProfileRepository; +import de.tum.cit.aet.artemis.core.domain.Course; +import de.tum.cit.aet.artemis.core.domain.User; + +@Profile(PROFILE_CORE) +@Service +public class CourseLearnerProfileService { + + private final CourseLearnerProfileRepository courseLearnerProfileRepository; + + private final LearnerProfileRepository learnerProfileRepository; + + public CourseLearnerProfileService(CourseLearnerProfileRepository courseLearnerProfileRepository, LearnerProfileService learnerProfileService, + LearnerProfileRepository learnerProfileRepository) { + this.courseLearnerProfileRepository = courseLearnerProfileRepository; + this.learnerProfileRepository = learnerProfileRepository; + } + + public void createCourseLearnerProfile(Course course, User user) { + var courseProfile = new CourseLearnerProfile(); + courseProfile.setCourse(course); + + var learnerProfile = learnerProfileRepository.findByUserElseThrow(user); + courseProfile.setLearnerProfile(learnerProfile); + + courseLearnerProfileRepository.save(courseProfile); + } + + public void createCourseLearnerProfiles(Course course, Set users) { + Set courseProfiles = users.stream().map(user -> { + var courseProfile = new CourseLearnerProfile(); + courseProfile.setCourse(course); + courseProfile.setLearnerProfile(user.getLearnerProfile()); + + return courseProfile; + }).collect(Collectors.toSet()); + + courseLearnerProfileRepository.saveAll(courseProfiles); + } + + public void deleteCourseLearnerProfile(Course course, User user) { + courseLearnerProfileRepository.deleteByCourseAndUser(course, user); + } + + public void deleteAllForCourse(Course course) { + courseLearnerProfileRepository.deleteAllByCourse(course); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java new file mode 100644 index 000000000000..55348af3d00e --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java @@ -0,0 +1,31 @@ +package de.tum.cit.aet.artemis.atlas.service.profile; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import de.tum.cit.aet.artemis.atlas.domain.profile.LearnerProfile; +import de.tum.cit.aet.artemis.atlas.repository.LearnerProfileRepository; +import de.tum.cit.aet.artemis.core.domain.User; + +@Profile(PROFILE_CORE) +@Service +public class LearnerProfileService { + + private final LearnerProfileRepository learnerProfileRepository; + + public LearnerProfileService(LearnerProfileRepository learnerProfileRepository) { + this.learnerProfileRepository = learnerProfileRepository; + } + + public LearnerProfile createProfile(User user) { + var profile = new LearnerProfile(); + profile.setUser(user); + return learnerProfileRepository.save(profile); + } + + public void deleteProfile(User user) { + learnerProfileRepository.deleteByUser(user); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java index 6498340f3bc2..1a6cceece034 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java @@ -21,6 +21,7 @@ import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import jakarta.persistence.Transient; import jakarta.validation.constraints.Email; @@ -40,6 +41,7 @@ import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyProgress; import de.tum.cit.aet.artemis.atlas.domain.competency.LearningPath; +import de.tum.cit.aet.artemis.atlas.domain.profile.LearnerProfile; import de.tum.cit.aet.artemis.communication.domain.push_notification.PushNotificationDeviceConfiguration; import de.tum.cit.aet.artemis.core.config.Constants; import de.tum.cit.aet.artemis.core.exception.AccessForbiddenException; @@ -224,6 +226,10 @@ public class User extends AbstractAuditingEntity implements Participant { @Column(name = "iris_accepted") private ZonedDateTime irisAccepted = null; + @OneToOne(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true) + @JsonIgnoreProperties("user") + private LearnerProfile learnerProfile; + public User() { } @@ -566,4 +572,12 @@ public String getSshPublicKey() { public @Size(max = 100) String getSshPublicKeyHash() { return sshPublicKeyHash; } + + public LearnerProfile getLearnerProfile() { + return learnerProfile; + } + + public void setLearnerProfile(LearnerProfile learnerProfile) { + this.learnerProfile = learnerProfile; + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java index 5b66b31aee98..286462286f68 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java @@ -178,6 +178,9 @@ OR LOWER(user.login) = LOWER(:searchInput) @EntityGraph(type = LOAD, attributePaths = { "groups", "authorities" }) Set findAllWithGroupsAndAuthoritiesByIsDeletedIsFalseAndGroupsContains(String groupName); + @EntityGraph(type = LOAD, attributePaths = { "groups", "authorities", "learnerProfile" }) + Set findAllWithGroupsAndAuthoritiesAndLearnerProfileByIsDeletedIsFalseAndGroupsContains(String groupName); + @Query(""" SELECT DISTINCT user FROM User user @@ -1005,6 +1008,16 @@ default Set getStudents(Course course) { return findAllWithGroupsAndAuthoritiesByIsDeletedIsFalseAndGroupsContains(course.getStudentGroupName()); } + /** + * Get students by given course with their learner Profile + * + * @param course object + * @return students for given course + */ + default Set getStudentsWithLearnerProfile(Course course) { + return findAllWithGroupsAndAuthoritiesAndLearnerProfileByIsDeletedIsFalseAndGroupsContains(course.getStudentGroupName()); + } + /** * Get tutors by given course * diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java index a286744dbe8a..81e41bd58c63 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java @@ -57,6 +57,7 @@ import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository; import de.tum.cit.aet.artemis.atlas.repository.PrerequisiteRepository; import de.tum.cit.aet.artemis.atlas.service.learningpath.LearningPathService; +import de.tum.cit.aet.artemis.atlas.service.profile.CourseLearnerProfileService; import de.tum.cit.aet.artemis.communication.domain.NotificationType; import de.tum.cit.aet.artemis.communication.domain.Post; import de.tum.cit.aet.artemis.communication.domain.notification.GroupNotification; @@ -124,8 +125,6 @@ public class CourseService { private static final Logger log = LoggerFactory.getLogger(CourseService.class); - private final FaqRepository faqRepository; - @Value("${artemis.course-archives-path}") private Path courseArchivesDirPath; @@ -213,6 +212,10 @@ public class CourseService { private final BuildJobRepository buildJobRepository; + private final FaqRepository faqRepository; + + private final CourseLearnerProfileService courseLearnerProfileService; + public CourseService(CourseRepository courseRepository, ExerciseService exerciseService, ExerciseDeletionService exerciseDeletionService, AuthorizationCheckService authCheckService, UserRepository userRepository, LectureService lectureService, GroupNotificationRepository groupNotificationRepository, ExerciseGroupRepository exerciseGroupRepository, AuditEventRepository auditEventRepository, UserService userService, ExamDeletionService examDeletionService, @@ -226,7 +229,8 @@ public CourseService(CourseRepository courseRepository, ExerciseService exercise LearningPathService learningPathService, Optional irisSettingsService, LectureRepository lectureRepository, TutorialGroupNotificationRepository tutorialGroupNotificationRepository, TutorialGroupChannelManagementService tutorialGroupChannelManagementService, PrerequisiteRepository prerequisiteRepository, CompetencyRelationRepository competencyRelationRepository, PostRepository postRepository, - AnswerPostRepository answerPostRepository, BuildJobRepository buildJobRepository, FaqRepository faqRepository) { + AnswerPostRepository answerPostRepository, BuildJobRepository buildJobRepository, FaqRepository faqRepository, + CourseLearnerProfileService courseLearnerProfileService) { this.courseRepository = courseRepository; this.exerciseService = exerciseService; this.exerciseDeletionService = exerciseDeletionService; @@ -270,6 +274,7 @@ public CourseService(CourseRepository courseRepository, ExerciseService exercise this.postRepository = postRepository; this.answerPostRepository = answerPostRepository; this.faqRepository = faqRepository; + this.courseLearnerProfileService = courseLearnerProfileService; } /** @@ -504,6 +509,7 @@ public void delete(Course course) { deleteExamsOfCourse(course); deleteGradingScaleOfCourse(course); deleteFaqsOfCourse(course); + courseLearnerProfileService.deleteAllForCourse(course); irisSettingsService.ifPresent(iss -> iss.deleteSettingsFor(course)); courseRepository.deleteById(course.getId()); log.debug("Successfully deleted course {}.", course.getTitle()); @@ -614,6 +620,7 @@ public void enrollUserForCourseOrThrow(User user, Course course) { authCheckService.checkUserAllowedToEnrollInCourseElseThrow(user, course); userService.addUserToGroup(user, course.getStudentGroupName()); if (course.getLearningPathsEnabled()) { + courseLearnerProfileService.createCourseLearnerProfile(course, user); learningPathService.generateLearningPathForUser(course, user); } final var auditEvent = new AuditEvent(user.getLogin(), Constants.ENROLL_IN_COURSE, "course=" + course.getTitle()); @@ -647,6 +654,7 @@ public List registerUsersForCourseGroup(Long courseId, List searchOtherUsersNameInCourse(Course course, String nameOfUser) /** * adds a given user to a user group * - * @param user user to be added to a group - * @param group user-group where the user should be added + * @param user user to be added to a group + * @param group user-group where the user should be added + * @param course the course in which the user should be added */ - public void addUserToGroup(User user, String group) { + public void addUserToGroup(User user, String group, Course course) { userService.addUserToGroup(user, group); + if (group.equals(course.getStudentGroupName()) && course.getLearningPathsEnabled()) { + Course courseWithCompetencies = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(course.getId()); + courseLearnerProfileService.createCourseLearnerProfile(course, user); + learningPathService.generateLearningPathForUser(courseWithCompetencies, user); + } } /** * removes a given user to a user group * - * @param user user to be removed from a group - * @param group user-group where the user should be removed + * @param user user to be removed from a group + * @param group user-group where the user should be removed + * @param course the course in which the user should be removed */ - public void removeUserFromGroup(User user, String group) { + public void removeUserFromGroup(User user, String group, Course course) { userService.removeUserFromGroup(user, group); + if (group.equals(course.getStudentGroupName())) { + courseLearnerProfileService.deleteCourseLearnerProfile(course, user); + } } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/UserScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/UserScheduleService.java index ffbe1ec288fa..c79f05eef586 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/UserScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/UserScheduleService.java @@ -17,6 +17,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import de.tum.cit.aet.artemis.atlas.service.profile.LearnerProfileService; import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.core.exception.VersionControlException; import de.tum.cit.aet.artemis.core.repository.UserRepository; @@ -39,15 +40,19 @@ public class UserScheduleService { private final ScheduledExecutorService scheduler; + private final LearnerProfileService learnerProfileService; + // Used for tracking and canceling the non-activated accounts that will be cleaned up. // The key of the map is the user id. private final Map> nonActivatedAccountsFutures = new ConcurrentHashMap<>(); - public UserScheduleService(UserRepository userRepository, Optional optionalVcsUserManagementService, CacheManager cacheManager) { + public UserScheduleService(UserRepository userRepository, Optional optionalVcsUserManagementService, CacheManager cacheManager, + LearnerProfileService learnerProfileService) { this.userRepository = userRepository; this.optionalVcsUserManagementService = optionalVcsUserManagementService; this.cacheManager = cacheManager; this.scheduler = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()); + this.learnerProfileService = learnerProfileService; } /** @@ -114,6 +119,7 @@ private void deleteUser(User user) { userRepository.delete(user); clearUserCaches(user); userRepository.flush(); + learnerProfileService.deleteProfile(user); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserCreationService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserCreationService.java index 12133bfb8800..8d87f75f02d4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserCreationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserCreationService.java @@ -23,6 +23,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.stereotype.Service; +import de.tum.cit.aet.artemis.atlas.service.profile.LearnerProfileService; import de.tum.cit.aet.artemis.core.config.Constants; import de.tum.cit.aet.artemis.core.domain.Authority; import de.tum.cit.aet.artemis.core.domain.Organization; @@ -74,9 +75,11 @@ public class UserCreationService { private final CacheManager cacheManager; + private final LearnerProfileService learnerProfileService; + public UserCreationService(UserRepository userRepository, PasswordService passwordService, AuthorityRepository authorityRepository, CourseRepository courseRepository, Optional optionalVcsUserManagementService, Optional optionalCIUserManagementService, CacheManager cacheManager, - OrganizationRepository organizationRepository) { + OrganizationRepository organizationRepository, LearnerProfileService learnerProfileService) { this.userRepository = userRepository; this.passwordService = passwordService; this.authorityRepository = authorityRepository; @@ -85,6 +88,7 @@ public UserCreationService(UserRepository userRepository, PasswordService passwo this.optionalCIUserManagementService = optionalCIUserManagementService; this.cacheManager = cacheManager; this.organizationRepository = organizationRepository; + this.learnerProfileService = learnerProfileService; } /** @@ -142,7 +146,8 @@ public User createUser(String login, @Nullable String password, @Nullable Set vcsUserManagementService.createVcsUser(savedUser, password)); + optionalCIUserManagementService.ifPresent(ciUserManagementService -> ciUserManagementService.createUser(savedUser, password)); - optionalVcsUserManagementService.ifPresent(vcsUserManagementService -> vcsUserManagementService.createVcsUser(user, password)); - optionalCIUserManagementService.ifPresent(ciUserManagementService -> ciUserManagementService.createUser(user, password)); + addUserToGroupsInternal(savedUser, userDTO.getGroups()); - addUserToGroupsInternal(user, userDTO.getGroups()); + learnerProfileService.createProfile(savedUser); log.debug("Created Information for User: {}", user); return user; diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserService.java index c883f774dcad..31206253ebf3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserService.java @@ -39,6 +39,7 @@ import org.springframework.util.StringUtils; import de.tum.cit.aet.artemis.atlas.repository.ScienceEventRepository; +import de.tum.cit.aet.artemis.atlas.service.profile.LearnerProfileService; import de.tum.cit.aet.artemis.core.domain.Authority; import de.tum.cit.aet.artemis.core.domain.GuidedTourSetting; import de.tum.cit.aet.artemis.core.domain.User; @@ -113,11 +114,13 @@ public class UserService { private final ParticipationVcsAccessTokenService participationVCSAccessTokenService; + private final LearnerProfileService learnerProfileService; + public UserService(UserCreationService userCreationService, UserRepository userRepository, AuthorityService authorityService, AuthorityRepository authorityRepository, CacheManager cacheManager, Optional ldapUserService, GuidedTourSettingsRepository guidedTourSettingsRepository, PasswordService passwordService, Optional optionalVcsUserManagementService, Optional optionalCIUserManagementService, InstanceMessageSendService instanceMessageSendService, FileService fileService, ScienceEventRepository scienceEventRepository, - ParticipationVcsAccessTokenService participationVCSAccessTokenService) { + ParticipationVcsAccessTokenService participationVCSAccessTokenService, LearnerProfileService learnerProfileService) { this.userCreationService = userCreationService; this.userRepository = userRepository; this.authorityService = authorityService; @@ -132,6 +135,7 @@ public UserService(UserCreationService userCreationService, UserRepository userR this.fileService = fileService; this.scienceEventRepository = scienceEventRepository; this.participationVCSAccessTokenService = participationVCSAccessTokenService; + this.learnerProfileService = learnerProfileService; } /** @@ -464,6 +468,7 @@ public void updateUserInConnectorsAndAuthProvider(User user, String oldUserLogin public void softDeleteUser(String login) { userRepository.findOneWithGroupsByLogin(login).ifPresent(user -> { participationVCSAccessTokenService.deleteAllByUserId(user.getId()); + learnerProfileService.deleteProfile(user); user.setDeleted(true); anonymizeUser(user); log.warn("Soft Deleted User: {}", user); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java index 16b7dd554842..79e53eb74f4b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java @@ -68,6 +68,7 @@ import de.tum.cit.aet.artemis.assessment.service.GradingScaleService; import de.tum.cit.aet.artemis.athena.service.AthenaModuleService; import de.tum.cit.aet.artemis.atlas.service.learningpath.LearningPathService; +import de.tum.cit.aet.artemis.atlas.service.profile.CourseLearnerProfileService; import de.tum.cit.aet.artemis.communication.service.ConductAgreementService; import de.tum.cit.aet.artemis.core.config.Constants; import de.tum.cit.aet.artemis.core.domain.Course; @@ -178,6 +179,8 @@ public class CourseResource { private final Optional athenaModuleService; + private final CourseLearnerProfileService courseLearnerProfileService; + @Value("${artemis.course-archives-path}") private String courseArchivesDirPath; @@ -196,7 +199,7 @@ public CourseResource(UserRepository userRepository, CourseService courseService FileService fileService, TutorialGroupsConfigurationService tutorialGroupsConfigurationService, GradingScaleService gradingScaleService, CourseScoreCalculationService courseScoreCalculationService, GradingScaleRepository gradingScaleRepository, LearningPathService learningPathService, ConductAgreementService conductAgreementService, Optional athenaModuleService, ExamRepository examRepository, ComplaintService complaintService, - TeamRepository teamRepository) { + TeamRepository teamRepository, CourseLearnerProfileService courseLearnerProfileService) { this.courseService = courseService; this.courseRepository = courseRepository; this.exerciseService = exerciseService; @@ -220,6 +223,7 @@ public CourseResource(UserRepository userRepository, CourseService courseService this.examRepository = examRepository; this.complaintService = complaintService; this.teamRepository = teamRepository; + this.courseLearnerProfileService = courseLearnerProfileService; } /** @@ -342,7 +346,9 @@ else if (courseUpdate.getCourseIcon() == null && existingCourse.getCourseIcon() // if learning paths got enabled, generate learning paths for students if (existingCourse.getLearningPathsEnabled() != courseUpdate.getLearningPathsEnabled() && courseUpdate.getLearningPathsEnabled()) { Course courseWithCompetencies = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(result.getId()); - learningPathService.generateLearningPaths(courseWithCompetencies); + Set students = userRepository.getStudentsWithLearnerProfile(courseWithCompetencies); + courseLearnerProfileService.createCourseLearnerProfiles(courseWithCompetencies, students); + learningPathService.generateLearningPaths(courseWithCompetencies, students); } // if access to restricted athena modules got disabled for the course, we need to set all exercises that use restricted modules to null @@ -1208,7 +1214,7 @@ public ResponseEntity getCourseTitle(@PathVariable Long courseId) { public ResponseEntity addStudentToCourse(@PathVariable Long courseId, @PathVariable String studentLogin) { log.debug("REST request to add {} as student to course : {}", studentLogin, courseId); var course = courseRepository.findByIdElseThrow(courseId); - return addUserToCourseGroup(studentLogin, userRepository.getUserWithGroupsAndAuthorities(), course, course.getStudentGroupName(), Role.STUDENT); + return addUserToCourseGroup(studentLogin, userRepository.getUserWithGroupsAndAuthorities(), course, course.getStudentGroupName()); } /** @@ -1223,7 +1229,7 @@ public ResponseEntity addStudentToCourse(@PathVariable Long courseId, @Pat public ResponseEntity addTutorToCourse(@PathVariable Long courseId, @PathVariable String tutorLogin) { log.debug("REST request to add {} as tutors to course : {}", tutorLogin, courseId); var course = courseRepository.findByIdElseThrow(courseId); - return addUserToCourseGroup(tutorLogin, userRepository.getUserWithGroupsAndAuthorities(), course, course.getTeachingAssistantGroupName(), Role.TEACHING_ASSISTANT); + return addUserToCourseGroup(tutorLogin, userRepository.getUserWithGroupsAndAuthorities(), course, course.getTeachingAssistantGroupName()); } /** @@ -1239,7 +1245,7 @@ public ResponseEntity addEditorToCourse(@PathVariable Long courseId, @Path log.debug("REST request to add {} as editors to course : {}", editorLogin, courseId); Course course = courseRepository.findByIdElseThrow(courseId); courseService.checkIfEditorGroupsNeedsToBeCreated(course); - return addUserToCourseGroup(editorLogin, userRepository.getUserWithGroupsAndAuthorities(), course, course.getEditorGroupName(), Role.EDITOR); + return addUserToCourseGroup(editorLogin, userRepository.getUserWithGroupsAndAuthorities(), course, course.getEditorGroupName()); } /** @@ -1254,7 +1260,7 @@ public ResponseEntity addEditorToCourse(@PathVariable Long courseId, @Path public ResponseEntity addInstructorToCourse(@PathVariable Long courseId, @PathVariable String instructorLogin) { log.debug("REST request to add {} as instructors to course : {}", instructorLogin, courseId); var course = courseRepository.findByIdElseThrow(courseId); - return addUserToCourseGroup(instructorLogin, userRepository.getUserWithGroupsAndAuthorities(), course, course.getInstructorGroupName(), Role.INSTRUCTOR); + return addUserToCourseGroup(instructorLogin, userRepository.getUserWithGroupsAndAuthorities(), course, course.getInstructorGroupName()); } /** @@ -1264,21 +1270,17 @@ public ResponseEntity addInstructorToCourse(@PathVariable Long courseId, @ * @param instructorOrAdmin the user who initiates this request who must be an instructor of the given course or an admin * @param course the course which is only passes to check if the instructorOrAdmin is an instructor of the course * @param group the group to which the userLogin should be added - * @param role the role which should be added * @return empty ResponseEntity with status 200 (OK) or with status 404 (Not Found) or with status 403 (Forbidden) */ @NotNull - public ResponseEntity addUserToCourseGroup(String userLogin, User instructorOrAdmin, Course course, String group, Role role) { + public ResponseEntity addUserToCourseGroup(String userLogin, User instructorOrAdmin, Course course, String group) { if (authCheckService.isAtLeastInstructorInCourse(course, instructorOrAdmin)) { Optional userToAddToGroup = userRepository.findOneWithGroupsAndAuthoritiesByLogin(userLogin); if (userToAddToGroup.isEmpty()) { throw new EntityNotFoundException("User", userLogin); } - courseService.addUserToGroup(userToAddToGroup.get(), group); - if (role == Role.STUDENT && course.getLearningPathsEnabled()) { - Course courseWithCompetencies = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(course.getId()); - learningPathService.generateLearningPathForUser(courseWithCompetencies, userToAddToGroup.get()); - } + User user = userToAddToGroup.get(); + courseService.addUserToGroup(user, group, course); return ResponseEntity.ok().body(null); } else { @@ -1365,7 +1367,7 @@ public ResponseEntity removeUserFromCourseGroup(String userLogin, User ins if (userToRemoveFromGroup.isEmpty()) { throw new EntityNotFoundException("User", userLogin); } - courseService.removeUserFromGroup(userToRemoveFromGroup.get(), group); + courseService.removeUserFromGroup(userToRemoveFromGroup.get(), group, course); return ResponseEntity.ok().body(null); } diff --git a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml new file mode 100644 index 000000000000..58f8a4eb27f9 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + INSERT INTO learner_profile (user_id) + SELECT id FROM jhi_user; + + + + + + + + + + + + + + + + + + + + + + INSERT INTO course_learner_profile (learner_profile_id, course_id) + SELECT lp.id, c.id + FROM learner_profile lp, course c + WHERE c.semester = 'WS24/25' AND c.test_course = FALSE AND c.learning_paths_enabled = TRUE; + + + \ No newline at end of file diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 5ae5903ea896..28b5ea12c892 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -32,6 +32,7 @@ + From 65acf2cb5a28831c523986da34d854ce9a0fc034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Thu, 7 Nov 2024 15:05:10 +0100 Subject: [PATCH 03/12] Fix user deletion --- .../atlas/repository/CourseLearnerProfileRepository.java | 8 ++++++++ .../atlas/service/profile/LearnerProfileService.java | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java index a98414d0533d..68a1316bf575 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java @@ -28,4 +28,12 @@ public interface CourseLearnerProfileRepository extends ArtemisJpaRepository Date: Thu, 7 Nov 2024 16:31:15 +0100 Subject: [PATCH 04/12] Fix some tests --- .../CourseLearnerProfileRepository.java | 3 ++- .../de/tum/cit/aet/artemis/core/domain/User.java | 2 +- .../learningpath/LearningPathIntegrationTest.java | 2 ++ .../core/test_repository/UserTestRepository.java | 7 +++++++ .../artemis/core/user/util/UserUtilService.java | 15 +++++++++++++++ .../CourseGitlabJenkinsIntegrationTest.java | 1 + 6 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java index 68a1316bf575..9b97bdd798fc 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -23,7 +24,7 @@ public interface CourseLearnerProfileRepository extends ArtemisJpaRepository findAllWithGroupsByIsDeletedIsFalse(Pageable pageable) { default User findWithLearningPathsByIdElseThrow(long userId) { return getValueElseThrow(findWithLearningPathsById(userId), userId); } + + @Query(""" + SELECT user + FROM User user + WHERE user.login LIKE CONCAT(:userPrefix, '%') + """) + Set findAllByUserPrefix(String userPrefix); } diff --git a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java index 2aa5ea3cd5f0..ef36f3924cd2 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,6 +20,8 @@ import org.springframework.security.test.context.TestSecurityContextHolder; import org.springframework.stereotype.Service; +import de.tum.cit.aet.artemis.atlas.domain.profile.LearnerProfile; +import de.tum.cit.aet.artemis.atlas.repository.LearnerProfileRepository; import de.tum.cit.aet.artemis.core.domain.Authority; import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.core.repository.AuthorityRepository; @@ -63,6 +66,9 @@ public class UserUtilService { @Autowired private UserTestRepository userTestRepository; + @Autowired + private LearnerProfileRepository learnerProfileRepository; + /** * Changes the currently authorized User to the User with the given username. * @@ -539,4 +545,13 @@ public void adjustUserGroupsToCustomGroups(String userPrefix, String userSuffix, userTestRepository.save(user); } } + + public void createLearnerProfilesForUsers(String userPrefix) { + Set learnerProfiles = userTestRepository.findAllByUserPrefix(userPrefix).stream().map(user -> { + LearnerProfile learnerProfile = new LearnerProfile(); + learnerProfile.setUser(user); + return learnerProfile; + }).collect(Collectors.toSet()); + learnerProfileRepository.saveAll(learnerProfiles); + } } diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java index 433d1a3e2deb..fe04a426ff02 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java @@ -702,6 +702,7 @@ void testSearchStudentsAndTutorsAndInstructorsInOtherCourse_forbidden() throws E @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testAddStudentOrTutorOrInstructorToCourse() throws Exception { + userUtilService.createLearnerProfilesForUsers(TEST_PREFIX); courseTestService.testAddStudentOrTutorOrEditorOrInstructorToCourse(); } From f8d8a2e4178c6f5529057643f0ac5dc9df75ca24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Fri, 8 Nov 2024 11:52:07 +0100 Subject: [PATCH 05/12] Maybe fix query counts --- .../atlas/domain/profile/LearnerProfile.java | 4 +--- .../de/tum/cit/aet/artemis/core/domain/User.java | 3 ++- .../changelog/20241107130000_changelog.xml | 14 ++++++++------ .../core/util/HibernateQueryInterceptor.java | 9 +++++++++ .../aet/artemis/core/util/QueryCountAssert.java | 4 ++++ 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java index c3c7c4e95de4..9f48fc11eeeb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java @@ -7,7 +7,6 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; @@ -19,8 +18,7 @@ @Table(name = "learner_profile") public class LearnerProfile extends DomainObject { - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") + @OneToOne private User user; @OneToMany(mappedBy = "learnerProfile", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) diff --git a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java index f30884d08d53..337b30dafa2d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java @@ -226,8 +226,9 @@ public class User extends AbstractAuditingEntity implements Participant { @Column(name = "iris_accepted") private ZonedDateTime irisAccepted = null; - @OneToOne(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnoreProperties("user") + @JoinColumn(name = "learner_profile_id") private LearnerProfile learnerProfile; public User() { diff --git a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml index 58f8a4eb27f9..e153768f1dc2 100644 --- a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml @@ -7,16 +7,18 @@ - - - + + + - + + - INSERT INTO learner_profile (user_id) - SELECT id FROM jhi_user; + INSERT INTO learner_profile (id) + SELECT u.id + FROM jhi_user u; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/HibernateQueryInterceptor.java b/src/test/java/de/tum/cit/aet/artemis/core/util/HibernateQueryInterceptor.java index f817c4efd4fc..870a925000ad 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/HibernateQueryInterceptor.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/HibernateQueryInterceptor.java @@ -1,5 +1,8 @@ package de.tum.cit.aet.artemis.core.util; +import java.util.ArrayList; +import java.util.List; + import org.hibernate.resource.jdbc.spi.StatementInspector; import org.springframework.stereotype.Component; @@ -8,6 +11,8 @@ public class HibernateQueryInterceptor implements StatementInspector { private final transient ThreadLocal threadQueryCount = new ThreadLocal<>(); + public final transient List calls = new ArrayList<>(); + /** * Start or reset the query count to 0 for the considered thread */ @@ -34,7 +39,11 @@ public Long getQueryCount() { public String inspect(String sql) { Long count = threadQueryCount.get(); if (count != null) { + if (sql.contains("learner_profile")) { + System.out.println("Penguin"); + } threadQueryCount.set(count + 1); + calls.add(sql); } return sql; } diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/QueryCountAssert.java b/src/test/java/de/tum/cit/aet/artemis/core/util/QueryCountAssert.java index 916b73fb57d5..1e8e52b0d3b9 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/QueryCountAssert.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/QueryCountAssert.java @@ -28,6 +28,8 @@ public static QueryCountAssert assertThatDb(Hiber */ public T hasBeenCalledTimes(long times) throws E { var result = performCall(); + log.error("Penguin"); + log.error(String.join("\n", interceptor.calls)); if (result.callCount != times) { throw failureWithActualExpected(interceptor.getQueryCount(), times, "Expected <%d> queries, but <%d> were performed.", times, result.callCount); } @@ -44,6 +46,8 @@ public T hasBeenCalledTimes(long times) throws E { */ public T hasBeenCalledAtMostTimes(long times) throws E { var result = performCall(); + log.error("Penguin"); + log.error(String.join("\n", interceptor.calls)); if (result.callCount > times) { throw failureWithActualExpected(interceptor.getQueryCount(), times, "Expected at most <%d> queries, but <%d> were performed.", times, result.callCount); } From ccd9a993e32025d50e2eb25096bdee0ed7d524e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Fri, 8 Nov 2024 11:59:01 +0100 Subject: [PATCH 06/12] Maybe fix query counts --- .../artemis/atlas/domain/profile/LearnerProfile.java | 2 +- .../atlas/service/profile/LearnerProfileService.java | 12 ++++++++---- .../de/tum/cit/aet/artemis/core/domain/User.java | 2 +- .../core/service/user/UserCreationService.java | 9 ++++++--- .../aet/artemis/core/service/user/UserService.java | 1 + .../liquibase/changelog/20241107130000_changelog.xml | 9 +++++++-- .../artemis/core/util/HibernateQueryInterceptor.java | 9 --------- .../cit/aet/artemis/core/util/QueryCountAssert.java | 4 ---- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java index 9f48fc11eeeb..84bed662672d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/LearnerProfile.java @@ -18,7 +18,7 @@ @Table(name = "learner_profile") public class LearnerProfile extends DomainObject { - @OneToOne + @OneToOne(mappedBy = "learnerProfile", cascade = CascadeType.PERSIST) private User user; @OneToMany(mappedBy = "learnerProfile", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java index f2a6706ea33b..3dc2be23410b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java @@ -9,6 +9,7 @@ import de.tum.cit.aet.artemis.atlas.repository.CourseLearnerProfileRepository; import de.tum.cit.aet.artemis.atlas.repository.LearnerProfileRepository; import de.tum.cit.aet.artemis.core.domain.User; +import de.tum.cit.aet.artemis.core.repository.UserRepository; @Profile(PROFILE_CORE) @Service @@ -18,19 +19,22 @@ public class LearnerProfileService { private final CourseLearnerProfileRepository courseLearnerProfileRepository; - public LearnerProfileService(LearnerProfileRepository learnerProfileRepository, CourseLearnerProfileRepository courseLearnerProfileRepository) { + private final UserRepository userRepository; + + public LearnerProfileService(LearnerProfileRepository learnerProfileRepository, CourseLearnerProfileRepository courseLearnerProfileRepository, UserRepository userRepository) { this.learnerProfileRepository = learnerProfileRepository; this.courseLearnerProfileRepository = courseLearnerProfileRepository; + this.userRepository = userRepository; } - public LearnerProfile createProfile(User user) { + public void createProfile(User user) { var profile = new LearnerProfile(); profile.setUser(user); - return learnerProfileRepository.save(profile); + user.setLearnerProfile(profile); + userRepository.save(user); } public void deleteProfile(User user) { learnerProfileRepository.deleteByUser(user); - courseLearnerProfileRepository.deleteAllByUser(user); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java index 337b30dafa2d..185459d8ce91 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java @@ -226,7 +226,7 @@ public class User extends AbstractAuditingEntity implements Participant { @Column(name = "iris_accepted") private ZonedDateTime irisAccepted = null; - @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnoreProperties("user") @JoinColumn(name = "learner_profile_id") private LearnerProfile learnerProfile; diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserCreationService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserCreationService.java index 8d87f75f02d4..bcc259a4331c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserCreationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserCreationService.java @@ -336,9 +336,9 @@ private void clearUserCaches(User user) { * @param user the user who should be added to the given groups * @param groups the groups in which the user should be added */ - private void addUserToGroupsInternal(User user, @Nullable Set groups) { + private User addUserToGroupsInternal(User user, @Nullable Set groups) { if (groups == null) { - return; + return user; } boolean userChanged = false; for (String group : groups) { @@ -350,7 +350,10 @@ private void addUserToGroupsInternal(User user, @Nullable Set groups) { if (userChanged) { // we only save if this is needed - saveUser(user); + return saveUser(user); + } + else { + return user; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserService.java index 31206253ebf3..cc135808aee4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/user/UserService.java @@ -470,6 +470,7 @@ public void softDeleteUser(String login) { participationVCSAccessTokenService.deleteAllByUserId(user.getId()); learnerProfileService.deleteProfile(user); user.setDeleted(true); + user.setLearnerProfile(null); anonymizeUser(user); log.warn("Soft Deleted User: {}", user); }); diff --git a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml index e153768f1dc2..3d764f747b13 100644 --- a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml @@ -8,19 +8,24 @@ + - + - INSERT INTO learner_profile (id) SELECT u.id FROM jhi_user u; + + UPDATE jhi_user u + SET learner_profile_id = u.id; + + diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/HibernateQueryInterceptor.java b/src/test/java/de/tum/cit/aet/artemis/core/util/HibernateQueryInterceptor.java index 870a925000ad..f817c4efd4fc 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/HibernateQueryInterceptor.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/HibernateQueryInterceptor.java @@ -1,8 +1,5 @@ package de.tum.cit.aet.artemis.core.util; -import java.util.ArrayList; -import java.util.List; - import org.hibernate.resource.jdbc.spi.StatementInspector; import org.springframework.stereotype.Component; @@ -11,8 +8,6 @@ public class HibernateQueryInterceptor implements StatementInspector { private final transient ThreadLocal threadQueryCount = new ThreadLocal<>(); - public final transient List calls = new ArrayList<>(); - /** * Start or reset the query count to 0 for the considered thread */ @@ -39,11 +34,7 @@ public Long getQueryCount() { public String inspect(String sql) { Long count = threadQueryCount.get(); if (count != null) { - if (sql.contains("learner_profile")) { - System.out.println("Penguin"); - } threadQueryCount.set(count + 1); - calls.add(sql); } return sql; } diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/QueryCountAssert.java b/src/test/java/de/tum/cit/aet/artemis/core/util/QueryCountAssert.java index 1e8e52b0d3b9..916b73fb57d5 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/QueryCountAssert.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/QueryCountAssert.java @@ -28,8 +28,6 @@ public static QueryCountAssert assertThatDb(Hiber */ public T hasBeenCalledTimes(long times) throws E { var result = performCall(); - log.error("Penguin"); - log.error(String.join("\n", interceptor.calls)); if (result.callCount != times) { throw failureWithActualExpected(interceptor.getQueryCount(), times, "Expected <%d> queries, but <%d> were performed.", times, result.callCount); } @@ -46,8 +44,6 @@ public T hasBeenCalledTimes(long times) throws E { */ public T hasBeenCalledAtMostTimes(long times) throws E { var result = performCall(); - log.error("Penguin"); - log.error(String.join("\n", interceptor.calls)); if (result.callCount > times) { throw failureWithActualExpected(interceptor.getQueryCount(), times, "Expected at most <%d> queries, but <%d> were performed.", times, result.callCount); } From 21c66240cf81fd93f48a92e3958806b37d2b306c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Fri, 8 Nov 2024 13:38:32 +0100 Subject: [PATCH 07/12] Fix tests --- .../CourseLearnerProfileRepository.java | 8 -------- .../service/user/UserCreationService.java | 19 ++++++++----------- .../core/user/util/UserUtilService.java | 6 +++--- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java index 9b97bdd798fc..c58cb82f6b96 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java @@ -29,12 +29,4 @@ public interface CourseLearnerProfileRepository extends ArtemisJpaRepository vcsUserManagementService.createVcsUser(savedUser, password)); - optionalCIUserManagementService.ifPresent(ciUserManagementService -> ciUserManagementService.createUser(savedUser, password)); + optionalVcsUserManagementService.ifPresent(vcsUserManagementService -> vcsUserManagementService.createVcsUser(user, password)); + optionalCIUserManagementService.ifPresent(ciUserManagementService -> ciUserManagementService.createUser(user, password)); - addUserToGroupsInternal(savedUser, userDTO.getGroups()); + addUserToGroupsInternal(user, userDTO.getGroups()); - learnerProfileService.createProfile(savedUser); + learnerProfileService.createProfile(user); log.debug("Created Information for User: {}", user); return user; @@ -336,9 +336,9 @@ private void clearUserCaches(User user) { * @param user the user who should be added to the given groups * @param groups the groups in which the user should be added */ - private User addUserToGroupsInternal(User user, @Nullable Set groups) { + private void addUserToGroupsInternal(User user, @Nullable Set groups) { if (groups == null) { - return user; + return; } boolean userChanged = false; for (String group : groups) { @@ -350,10 +350,7 @@ private User addUserToGroupsInternal(User user, @Nullable Set groups) { if (userChanged) { // we only save if this is needed - return saveUser(user); - } - else { - return user; + saveUser(user); } } diff --git a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java index ef36f3924cd2..0f3e3b3e12ad 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java @@ -547,11 +547,11 @@ public void adjustUserGroupsToCustomGroups(String userPrefix, String userSuffix, } public void createLearnerProfilesForUsers(String userPrefix) { - Set learnerProfiles = userTestRepository.findAllByUserPrefix(userPrefix).stream().map(user -> { + Set users = userTestRepository.findAllByUserPrefix(userPrefix).stream().peek(user -> { LearnerProfile learnerProfile = new LearnerProfile(); learnerProfile.setUser(user); - return learnerProfile; + user.setLearnerProfile(learnerProfile); }).collect(Collectors.toSet()); - learnerProfileRepository.saveAll(learnerProfiles); + userTestRepository.saveAll(users); } } From 38f43bea9be8cb2bdf226136653bc9b1d395c244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Mon, 11 Nov 2024 18:15:29 +0100 Subject: [PATCH 08/12] Add comments --- .../profile/CourseLearnerProfileService.java | 23 +++++++++++++++++++ .../profile/LearnerProfileService.java | 16 +++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java index 76202b164bfe..d4140dc486b2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java @@ -28,6 +28,12 @@ public CourseLearnerProfileService(CourseLearnerProfileRepository courseLearnerP this.learnerProfileRepository = learnerProfileRepository; } + /** + * Create a course learner profile for a user and saves it in the database + * + * @param course the course for which the profile is created + * @param user the user for which the profile is created + */ public void createCourseLearnerProfile(Course course, User user) { var courseProfile = new CourseLearnerProfile(); courseProfile.setCourse(course); @@ -38,6 +44,12 @@ public void createCourseLearnerProfile(Course course, User user) { courseLearnerProfileRepository.save(courseProfile); } + /** + * Create course learner profiles for a set of users and saves them in the database. + * + * @param course the course for which the profiles are created + * @param users the users for which the profiles are created with eagerly loaded learner profiles + */ public void createCourseLearnerProfiles(Course course, Set users) { Set courseProfiles = users.stream().map(user -> { var courseProfile = new CourseLearnerProfile(); @@ -50,10 +62,21 @@ public void createCourseLearnerProfiles(Course course, Set users) { courseLearnerProfileRepository.saveAll(courseProfiles); } + /** + * Delete a course learner profile for a user + * + * @param course the course for which the profile is deleted + * @param user the user for which the profile is deleted + */ public void deleteCourseLearnerProfile(Course course, User user) { courseLearnerProfileRepository.deleteByCourseAndUser(course, user); } + /** + * Delete all course learner profiles for a course + * + * @param course the course for which the profiles are deleted + */ public void deleteAllForCourse(Course course) { courseLearnerProfileRepository.deleteAllByCourse(course); } diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java index 3dc2be23410b..5b92074b1c0d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/LearnerProfileService.java @@ -6,7 +6,6 @@ import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.atlas.domain.profile.LearnerProfile; -import de.tum.cit.aet.artemis.atlas.repository.CourseLearnerProfileRepository; import de.tum.cit.aet.artemis.atlas.repository.LearnerProfileRepository; import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.core.repository.UserRepository; @@ -17,16 +16,18 @@ public class LearnerProfileService { private final LearnerProfileRepository learnerProfileRepository; - private final CourseLearnerProfileRepository courseLearnerProfileRepository; - private final UserRepository userRepository; - public LearnerProfileService(LearnerProfileRepository learnerProfileRepository, CourseLearnerProfileRepository courseLearnerProfileRepository, UserRepository userRepository) { + public LearnerProfileService(LearnerProfileRepository learnerProfileRepository, UserRepository userRepository) { this.learnerProfileRepository = learnerProfileRepository; - this.courseLearnerProfileRepository = courseLearnerProfileRepository; this.userRepository = userRepository; } + /** + * Create a learner profile for a user and saves it in the database + * + * @param user the user for which the profile is created + */ public void createProfile(User user) { var profile = new LearnerProfile(); profile.setUser(user); @@ -34,6 +35,11 @@ public void createProfile(User user) { userRepository.save(user); } + /** + * Delete the learner profile of a user + * + * @param user the user for which the profile is deleted + */ public void deleteProfile(User user) { learnerProfileRepository.deleteByUser(user); } From 195a3d21c677e1293a2093997e22e32264738e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Mon, 11 Nov 2024 18:23:09 +0100 Subject: [PATCH 09/12] Fix server style --- .../atlas/service/learningpath/LearningPathService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java index c540b90ad3e3..b553779e29ad 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java @@ -138,7 +138,8 @@ public void generateLearningPaths(@NotNull Course course) { /** * Generate learning paths for all students enrolled in the course * - * @param course course the learning paths are created for + * @param course course the learning paths are created for + * @param students students for which the learning paths are generated with eager loaded learner profiles */ public void generateLearningPaths(@NotNull Course course, Set students) { students.forEach(student -> generateLearningPathForUser(course, student)); From 59a65e3fb085e2971c8d084485c1a71c58e6902c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Tue, 19 Nov 2024 12:13:19 +0100 Subject: [PATCH 10/12] Add some tests --- .../domain/profile/CourseLearnerProfile.java | 8 +++++++ .../profile/CourseLearnerProfileService.java | 3 +-- .../changelog/20241107130000_changelog.xml | 14 ++++++++++- .../resources/config/liquibase/master.xml | 2 +- .../LearningPathIntegrationTest.java | 5 ++-- .../test_repository/UserTestRepository.java | 4 ++-- .../core/user/util/UserTestService.java | 24 ++++++++++++------- 7 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java index 222737e21ab8..91e77a7c44e1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java @@ -7,6 +7,8 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.core.domain.DomainObject; @@ -24,12 +26,18 @@ public class CourseLearnerProfile extends DomainObject { private Course course; @Column(name = "aim_for_grade_or_bonus") + @Min(0) + @Max(5) private int aimForGradeOrBonus; @Column(name = "time_investment") + @Min(0) + @Max(5) private int timeInvestment; @Column(name = "repetition_intensity") + @Min(0) + @Max(5) private int repetitionIntensity; public void setLearnerProfile(LearnerProfile learnerProfile) { diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java index d4140dc486b2..8ca20bec626e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/profile/CourseLearnerProfileService.java @@ -22,8 +22,7 @@ public class CourseLearnerProfileService { private final LearnerProfileRepository learnerProfileRepository; - public CourseLearnerProfileService(CourseLearnerProfileRepository courseLearnerProfileRepository, LearnerProfileService learnerProfileService, - LearnerProfileRepository learnerProfileRepository) { + public CourseLearnerProfileService(CourseLearnerProfileRepository courseLearnerProfileRepository, LearnerProfileRepository learnerProfileRepository) { this.courseLearnerProfileRepository = courseLearnerProfileRepository; this.learnerProfileRepository = learnerProfileRepository; } diff --git a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml index 3d764f747b13..c4c9877ee55d 100644 --- a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml @@ -15,6 +15,10 @@ + + + + INSERT INTO learner_profile (id) SELECT u.id @@ -44,6 +48,14 @@ + + + + + + + + INSERT INTO course_learner_profile (learner_profile_id, course_id) SELECT lp.id, c.id @@ -51,4 +63,4 @@ WHERE c.semester = 'WS24/25' AND c.test_course = FALSE AND c.learning_paths_enabled = TRUE; - \ No newline at end of file + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index b85c513ebd96..2dbe11af4739 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -33,8 +33,8 @@ - + diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java index e202856f4e94..61ed8d420097 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java @@ -248,10 +248,11 @@ void testGenerateLearningPathOnEnrollment() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); request.postWithResponseBody("/api/courses/" + course.getId() + "/enroll", null, Set.class, HttpStatus.OK); - final var user = userTestRepository.findOneWithLearningPathsByLogin(TEST_PREFIX + "student1337").orElseThrow(); + final var user = userTestRepository.findOneWithLearningPathsAndLearnerProfileByLogin(TEST_PREFIX + "student1337").orElseThrow(); assertThat(user.getLearningPaths()).isNotNull(); - assertThat(user.getLearningPaths().size()).as("should create LearningPath for student").isEqualTo(1); + assertThat(user.getLearningPaths()).as("should create LearningPath for student").hasSize(1); + assertThat(user.getLearnerProfile().getCourseLearnerProfiles()).hasSize(1); } @Test diff --git a/src/test/java/de/tum/cit/aet/artemis/core/test_repository/UserTestRepository.java b/src/test/java/de/tum/cit/aet/artemis/core/test_repository/UserTestRepository.java index 2f0b6fd9b2d5..2b1734d80fb2 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/test_repository/UserTestRepository.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/test_repository/UserTestRepository.java @@ -59,8 +59,8 @@ default Page findAllWithGroupsByIsDeletedIsFalse(Pageable pageable) { return new PageImpl<>(users, pageable, total); } - @EntityGraph(type = LOAD, attributePaths = { "learningPaths" }) - Optional findOneWithLearningPathsByLogin(String login); + @EntityGraph(type = LOAD, attributePaths = { "learningPaths", "learnerProfile", "learnerProfile.courseLearnerProfiles" }) + Optional findOneWithLearningPathsAndLearnerProfileByLogin(String login); @EntityGraph(type = LOAD, attributePaths = { "learningPaths" }) Optional findWithLearningPathsById(long userId); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserTestService.java b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserTestService.java index 77ea61f417ee..9f49999577c7 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserTestService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserTestService.java @@ -29,6 +29,7 @@ import de.tum.cit.aet.artemis.atlas.domain.science.ScienceEvent; import de.tum.cit.aet.artemis.atlas.domain.science.ScienceEventType; +import de.tum.cit.aet.artemis.atlas.repository.LearnerProfileRepository; import de.tum.cit.aet.artemis.atlas.test_repository.ScienceEventTestRepository; import de.tum.cit.aet.artemis.core.config.Constants; import de.tum.cit.aet.artemis.core.domain.Authority; @@ -103,6 +104,18 @@ public class UserTestService { @Autowired private MockMvc mockMvc; + @Autowired + private ParticipationVCSAccessTokenRepository participationVCSAccessTokenRepository; + + @Autowired + private ParticipationTestRepository participationRepository; + + @Autowired + private SubmissionTestRepository submissionRepository; + + @Autowired + private LearnerProfileRepository learnerProfileRepository; + private String TEST_PREFIX; private MockDelegate mockDelegate; @@ -119,15 +132,6 @@ public class UserTestService { private static final int NUMBER_OF_INSTRUCTORS = 1; - @Autowired - private ParticipationVCSAccessTokenRepository participationVCSAccessTokenRepository; - - @Autowired - private ParticipationTestRepository participationRepository; - - @Autowired - private SubmissionTestRepository submissionRepository; - public void setup(String testPrefix, MockDelegate mockDelegate) throws Exception { this.TEST_PREFIX = testPrefix; this.mockDelegate = mockDelegate; @@ -399,6 +403,8 @@ public User createExternalUser_asAdmin_isSuccessful() throws Exception { assertThat(student).as("New user is equal to request response").isEqualTo(response); assertThat(student).as("New user is equal to new user in DB").isEqualTo(userInDB); + assertThat(learnerProfileRepository.findByUser(student)).isNotEmpty(); + return userInDB; } From f824403ee51357bbc42c23e91122e2a8a5a1731a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Wed, 20 Nov 2024 13:32:11 +0100 Subject: [PATCH 11/12] Max --- .../artemis/core/service/CourseService.java | 3 --- .../changelog/20241107130000_changelog.xml | 18 +++++++++---- .../atlas/AbstractAtlasIntegrationTest.java | 4 +++ .../LearningPathIntegrationTest.java | 2 +- .../util/LearnerProfileUtilService.java | 27 +++++++++++++++++++ .../core/user/util/UserUtilService.java | 15 ----------- ...ogrammingIntegrationJenkinsGitlabTest.java | 4 +++ .../CourseGitlabJenkinsIntegrationTest.java | 2 +- 8 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 src/test/java/de/tum/cit/aet/artemis/atlas/profile/util/LearnerProfileUtilService.java diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java index 1b8958d78cef..e3be2a818a2e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java @@ -1070,9 +1070,6 @@ public void addUserToGroup(User user, String group, Course course) { */ public void removeUserFromGroup(User user, String group, Course course) { userService.removeUserFromGroup(user, group); - if (group.equals(course.getStudentGroupName())) { - courseLearnerProfileService.deleteCourseLearnerProfile(course, user); - } } /** diff --git a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml index c4c9877ee55d..0341d36332ca 100644 --- a/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20241107130000_changelog.xml @@ -34,9 +34,15 @@ - - - + + + + + + + + + @@ -55,12 +61,14 @@ - INSERT INTO course_learner_profile (learner_profile_id, course_id) SELECT lp.id, c.id FROM learner_profile lp, course c - WHERE c.semester = 'WS24/25' AND c.test_course = FALSE AND c.learning_paths_enabled = TRUE; + WHERE c.start_date <= NOW() + AND c.end_date >= NOW() + AND c.test_course = FALSE + AND c.learning_paths_enabled = TRUE; diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/AbstractAtlasIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/AbstractAtlasIntegrationTest.java index 71fa3201a4fe..8685950fc701 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/AbstractAtlasIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/AbstractAtlasIntegrationTest.java @@ -9,6 +9,7 @@ import de.tum.cit.aet.artemis.atlas.competency.util.PrerequisiteUtilService; import de.tum.cit.aet.artemis.atlas.competency.util.StandardizedCompetencyUtilService; import de.tum.cit.aet.artemis.atlas.learningpath.util.LearningPathUtilService; +import de.tum.cit.aet.artemis.atlas.profile.util.LearnerProfileUtilService; import de.tum.cit.aet.artemis.atlas.repository.CompetencyJolRepository; import de.tum.cit.aet.artemis.atlas.repository.CompetencyRelationRepository; import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository; @@ -145,6 +146,9 @@ public abstract class AbstractAtlasIntegrationTest extends AbstractSpringIntegra @Autowired protected LearningPathUtilService learningPathUtilService; + @Autowired + protected LearnerProfileUtilService learnerProfileUtilService; + // External Util Services @Autowired protected PageableSearchUtilService pageableSearchUtilService; diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java index 61ed8d420097..9eca796775f4 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java @@ -83,7 +83,7 @@ void setupTestScenario() throws Exception { userUtilService.createAndSaveUser(TEST_PREFIX + "student1337"); userUtilService.createAndSaveUser(TEST_PREFIX + "instructor1337"); - userUtilService.createLearnerProfilesForUsers(TEST_PREFIX); + learnerProfileUtilService.createLearnerProfilesForUsers(TEST_PREFIX); course = courseUtilService.createCoursesWithExercisesAndLectures(TEST_PREFIX, true, true, 1).getFirst(); competencies = competencyUtilService.createCompetencies(course, 5); diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/profile/util/LearnerProfileUtilService.java b/src/test/java/de/tum/cit/aet/artemis/atlas/profile/util/LearnerProfileUtilService.java new file mode 100644 index 000000000000..bf1089a5ead6 --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/profile/util/LearnerProfileUtilService.java @@ -0,0 +1,27 @@ +package de.tum.cit.aet.artemis.atlas.profile.util; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import de.tum.cit.aet.artemis.atlas.domain.profile.LearnerProfile; +import de.tum.cit.aet.artemis.core.domain.User; +import de.tum.cit.aet.artemis.core.test_repository.UserTestRepository; + +@Service +public class LearnerProfileUtilService { + + @Autowired + private UserTestRepository userTestRepository; + + public void createLearnerProfilesForUsers(String userPrefix) { + Set users = userTestRepository.findAllByUserPrefix(userPrefix).stream().peek(user -> { + LearnerProfile learnerProfile = new LearnerProfile(); + learnerProfile.setUser(user); + user.setLearnerProfile(learnerProfile); + }).collect(Collectors.toSet()); + userTestRepository.saveAll(users); + } +} diff --git a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java index 0f3e3b3e12ad..2aa5ea3cd5f0 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserUtilService.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,8 +19,6 @@ import org.springframework.security.test.context.TestSecurityContextHolder; import org.springframework.stereotype.Service; -import de.tum.cit.aet.artemis.atlas.domain.profile.LearnerProfile; -import de.tum.cit.aet.artemis.atlas.repository.LearnerProfileRepository; import de.tum.cit.aet.artemis.core.domain.Authority; import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.core.repository.AuthorityRepository; @@ -66,9 +63,6 @@ public class UserUtilService { @Autowired private UserTestRepository userTestRepository; - @Autowired - private LearnerProfileRepository learnerProfileRepository; - /** * Changes the currently authorized User to the User with the given username. * @@ -545,13 +539,4 @@ public void adjustUserGroupsToCustomGroups(String userPrefix, String userSuffix, userTestRepository.save(user); } } - - public void createLearnerProfilesForUsers(String userPrefix) { - Set users = userTestRepository.findAllByUserPrefix(userPrefix).stream().peek(user -> { - LearnerProfile learnerProfile = new LearnerProfile(); - learnerProfile.setUser(user); - user.setLearnerProfile(learnerProfile); - }).collect(Collectors.toSet()); - userTestRepository.saveAll(users); - } } diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/AbstractProgrammingIntegrationJenkinsGitlabTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/AbstractProgrammingIntegrationJenkinsGitlabTest.java index 780343412cf9..4d3c460746cf 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/AbstractProgrammingIntegrationJenkinsGitlabTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/AbstractProgrammingIntegrationJenkinsGitlabTest.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import de.tum.cit.aet.artemis.atlas.profile.util.LearnerProfileUtilService; import de.tum.cit.aet.artemis.communication.repository.conversation.ChannelRepository; import de.tum.cit.aet.artemis.communication.test_repository.PostTestRepository; import de.tum.cit.aet.artemis.core.test_repository.UserTestRepository; @@ -206,4 +207,7 @@ public abstract class AbstractProgrammingIntegrationJenkinsGitlabTest extends Ab @Autowired protected UserUtilService userUtilService; + + @Autowired + protected LearnerProfileUtilService learnerProfileUtilService; } diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java index fe04a426ff02..6be6ad09774b 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java @@ -702,7 +702,7 @@ void testSearchStudentsAndTutorsAndInstructorsInOtherCourse_forbidden() throws E @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testAddStudentOrTutorOrInstructorToCourse() throws Exception { - userUtilService.createLearnerProfilesForUsers(TEST_PREFIX); + learnerProfileUtilService.createLearnerProfilesForUsers(TEST_PREFIX); courseTestService.testAddStudentOrTutorOrEditorOrInstructorToCourse(); } From 389f18b0d8ffed972922aea54e4889a33b4a2637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20St=C3=B6hr?= Date: Thu, 28 Nov 2024 12:49:06 +0100 Subject: [PATCH 12/12] Merge --- .../atlas/service/learningpath/LearningPathService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java index b553779e29ad..8048d2c04ab8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java @@ -118,7 +118,7 @@ public LearningPathService(UserRepository userRepository, LearningPathRepository */ public void enableLearningPathsForCourse(@NotNull Course course) { course.setLearningPathsEnabled(true); - var students = userRepository.getStudentsWithLearnerProfile(course); + Set students = userRepository.getStudentsWithLearnerProfile(course); courseLearnerProfileService.createCourseLearnerProfiles(course, students); generateLearningPaths(course, students); courseRepository.save(course); @@ -131,7 +131,7 @@ public void enableLearningPathsForCourse(@NotNull Course course) { * @param course course the learning paths are created for */ public void generateLearningPaths(@NotNull Course course) { - var students = userRepository.getStudentsWithLearnerProfile(course); + Set students = userRepository.getStudentsWithLearnerProfile(course); generateLearningPaths(course, students); }