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

Adaptive learning: Add learner profile #9673

Open
wants to merge 28 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7024d9a
Adds rough learner structure
N0W0RK Nov 5, 2024
1eb6786
Add creation and deletion stuff
JohannesStoehr Nov 7, 2024
65acf2c
Fix user deletion
JohannesStoehr Nov 7, 2024
52b6161
Fix some tests
JohannesStoehr Nov 7, 2024
f8d8a2e
Maybe fix query counts
JohannesStoehr Nov 8, 2024
d30fc8f
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Nov 8, 2024
ccd9a99
Maybe fix query counts
JohannesStoehr Nov 8, 2024
21c6624
Fix tests
JohannesStoehr Nov 8, 2024
38f43be
Add comments
JohannesStoehr Nov 11, 2024
195a3d2
Fix server style
JohannesStoehr Nov 11, 2024
34c28bc
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Nov 11, 2024
6dde045
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Nov 18, 2024
59a65e3
Add some tests
JohannesStoehr Nov 19, 2024
f824403
Max
JohannesStoehr Nov 20, 2024
290ae82
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Nov 21, 2024
3077ca6
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Nov 28, 2024
389f18b
Merge
JohannesStoehr Nov 28, 2024
0bd6721
Merge branch 'develop' into feature/adaptive-learning/learner-profile
N0W0RK Nov 28, 2024
d83180d
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Dec 2, 2024
fee5f6c
Merge remote-tracking branch 'origin/feature/adaptive-learning/learne…
JohannesStoehr Dec 2, 2024
90e061b
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Dec 3, 2024
04e2a26
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Dec 5, 2024
2a9cf48
Merge branch 'develop' into feature/adaptive-learning/learner-profile
MaximilianAnzinger Dec 6, 2024
c25e76e
Fix compilation
JohannesStoehr Dec 6, 2024
c4e9df8
Fix api
JohannesStoehr Dec 6, 2024
268dc39
Merge branch 'develop' into feature/adaptive-learning/learner-profile
MaximilianAnzinger Dec 10, 2024
2d4bff9
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Dec 10, 2024
5ec7732
Merge branch 'develop' into feature/adaptive-learning/learner-profile
JohannesStoehr Dec 11, 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
@@ -0,0 +1,51 @@
package de.tum.cit.aet.artemis.atlas.api;

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

import java.util.Set;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;

import de.tum.cit.aet.artemis.atlas.service.profile.CourseLearnerProfileService;
import de.tum.cit.aet.artemis.atlas.service.profile.LearnerProfileService;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;

@Controller
@Profile(PROFILE_CORE)
public class LearnerProfileApi extends AbstractAtlasApi {
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

private final LearnerProfileService learnerProfileService;

private final CourseLearnerProfileService courseLearnerProfileService;

public LearnerProfileApi(LearnerProfileService learnerProfileService, CourseLearnerProfileService courseLearnerProfileService) {
this.learnerProfileService = learnerProfileService;
this.courseLearnerProfileService = courseLearnerProfileService;
}

public void deleteAllForCourse(Course course) {
courseLearnerProfileService.deleteAllForCourse(course);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

public void createCourseLearnerProfile(Course course, User user) {
courseLearnerProfileService.createCourseLearnerProfile(course, user);
}

public void createCourseLearnerProfiles(Course course, Set<User> students) {
courseLearnerProfileService.createCourseLearnerProfiles(course, students);
}

public void deleteCourseLearnerProfile(Course course, User user) {
courseLearnerProfileService.deleteCourseLearnerProfile(course, user);
}

public void createProfile(User user) {
learnerProfileService.createProfile(user);
}

public void deleteProfile(User user) {
learnerProfileService.deleteProfile(user);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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;
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;

@Entity
@Table(name = "course_learner_profile")
public class CourseLearnerProfile extends DomainObject {
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

@ManyToOne
@JoinColumn(name = "learner_profile_id")
private LearnerProfile learnerProfile;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "course_id")
private Course course;
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

@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;
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

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;
}

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;
}
}
Original file line number Diff line number Diff line change
@@ -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.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
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")
public class LearnerProfile extends DomainObject {

@OneToOne(mappedBy = "learnerProfile", cascade = CascadeType.PERSIST)
private User user;
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

@OneToMany(mappedBy = "learnerProfile", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<CourseLearnerProfile> courseLearnerProfiles = new HashSet<>();
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

public void setUser(User user) {
this.user = user;
}

public User getUser() {
return this.user;
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

public void setCourseLearnerProfiles(Set<CourseLearnerProfile> courseLearnerProfiles) {
this.courseLearnerProfiles = courseLearnerProfiles;
}

public Set<CourseLearnerProfile> getCourseLearnerProfiles() {
return this.courseLearnerProfiles;
}

public boolean addCourseLearnerProfile(CourseLearnerProfile courseLearnerProfile) {
return this.courseLearnerProfiles.add(courseLearnerProfile);
}

public boolean addAllCourseLearnerProfiles(Collection<? extends CourseLearnerProfile> courseLearnerProfiles) {
return this.courseLearnerProfiles.addAll(courseLearnerProfiles);
}

public boolean removeCourseLearnerProfile(CourseLearnerProfile courseLearnerProfile) {
return this.courseLearnerProfiles.remove(courseLearnerProfile);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package de.tum.cit.aet.artemis.atlas.repository;

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

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;

import de.tum.cit.aet.artemis.atlas.domain.profile.CourseLearnerProfile;
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.repository.base.ArtemisJpaRepository;

@Profile(PROFILE_CORE)
@Repository
public interface CourseLearnerProfileRepository extends ArtemisJpaRepository<CourseLearnerProfile, Long> {

@Transactional // ok because of delete
@Modifying
@Query("""
DELETE FROM CourseLearnerProfile clp
WHERE clp.course = :course AND clp.learnerProfile.user = :user
""")
void deleteByCourseAndUser(@Param("course") Course course, @Param("user") User user);

@Transactional // ok because of delete
@Modifying
void deleteAllByCourse(Course couese);
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -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<LearnerProfile, Long> {

Optional<LearnerProfile> findByUser(User user);

default LearnerProfile findByUserElseThrow(User user) {
return getValueElseThrow(findByUser(user));
}

@Transactional // ok because of delete
@Modifying
void deleteByUser(User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -104,6 +108,7 @@ public LearningPathService(UserRepository userRepository, LearningPathRepository
this.lectureUnitCompletionRepository = lectureUnitCompletionRepository;
this.studentParticipationRepository = studentParticipationRepository;
this.courseCompetencyRepository = courseCompetencyRepository;
this.courseLearnerProfileService = courseLearnerProfileService;
}

/**
Expand All @@ -113,7 +118,9 @@ public LearningPathService(UserRepository userRepository, LearningPathRepository
*/
public void enableLearningPathsForCourse(@NotNull Course course) {
course.setLearningPathsEnabled(true);
generateLearningPaths(course);
Set<User> students = userRepository.getStudentsWithLearnerProfile(course);
courseLearnerProfileService.createCourseLearnerProfiles(course, students);
generateLearningPaths(course, students);
courseRepository.save(course);
log.debug("Enabled learning paths for course (id={})", course.getId());
}
Expand All @@ -124,7 +131,17 @@ 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);
Set<User> 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
* @param students students for which the learning paths are generated with eager loaded learner profiles
*/
public void generateLearningPaths(@NotNull Course course, Set<User> students) {
students.forEach(student -> generateLearningPathForUser(course, student));
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
log.debug("Successfully created learning paths for all {} students in course (id={})", students.size(), course.getId());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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, LearnerProfileRepository learnerProfileRepository) {
this.courseLearnerProfileRepository = courseLearnerProfileRepository;
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);

var learnerProfile = learnerProfileRepository.findByUserElseThrow(user);
courseProfile.setLearnerProfile(learnerProfile);

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<User> users) {
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
Set<CourseLearnerProfile> courseProfiles = users.stream().map(user -> {
var courseProfile = new CourseLearnerProfile();
courseProfile.setCourse(course);
courseProfile.setLearnerProfile(user.getLearnerProfile());
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

return courseProfile;
}).collect(Collectors.toSet());

courseLearnerProfileRepository.saveAll(courseProfiles);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading