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 LearnerProfile #9673

Open
wants to merge 18 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
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,74 @@
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 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")
private int aimForGradeOrBonus;

@Column(name = "time_investment")
private int timeInvestment;

@Column(name = "repetition_intensity")
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);
var students = userRepository.getStudentsWithLearnerProfile(course);
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
courseLearnerProfileService.createCourseLearnerProfiles(course, students);
generateLearningPaths(course, students);
courseRepository.save(course);
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
log.debug("Enabled learning paths for course (id={})", course.getId());
}
Expand All @@ -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);
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
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<User> students) {
students.forEach(student -> generateLearningPathForUser(course, student));
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,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;
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

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

public void deleteCourseLearnerProfile(Course course, User user) {
courseLearnerProfileRepository.deleteByCourseAndUser(course, user);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

public void deleteAllForCourse(Course course) {
courseLearnerProfileRepository.deleteAllByCourse(course);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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.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
public class LearnerProfileService {

private final LearnerProfileRepository learnerProfileRepository;

private final CourseLearnerProfileRepository courseLearnerProfileRepository;

private final UserRepository userRepository;

public LearnerProfileService(LearnerProfileRepository learnerProfileRepository, CourseLearnerProfileRepository courseLearnerProfileRepository, UserRepository userRepository) {
this.learnerProfileRepository = learnerProfileRepository;
this.courseLearnerProfileRepository = courseLearnerProfileRepository;
this.userRepository = userRepository;
}

public void createProfile(User user) {
var profile = new LearnerProfile();
profile.setUser(user);
user.setLearnerProfile(profile);
userRepository.save(user);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

public void deleteProfile(User user) {
learnerProfileRepository.deleteByUser(user);
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
}
15 changes: 15 additions & 0 deletions src/main/java/de/tum/cit/aet/artemis/core/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -224,6 +226,11 @@ public class User extends AbstractAuditingEntity implements Participant {
@Column(name = "iris_accepted")
private ZonedDateTime irisAccepted = null;

@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnoreProperties("user")
@JoinColumn(name = "learner_profile_id")
private LearnerProfile learnerProfile;

public User() {
}

Expand Down Expand Up @@ -566,4 +573,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;
}
}
Loading
Loading