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

Learning paths: Add setting to include all exercises relevant to the course score #9083

14 changes: 14 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/domain/Course.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

import de.tum.in.www1.artemis.domain.competency.Competency;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.domain.competency.LearningPathsConfiguration;
import de.tum.in.www1.artemis.domain.competency.Prerequisite;
import de.tum.in.www1.artemis.domain.enumeration.CourseInformationSharingConfiguration;
import de.tum.in.www1.artemis.domain.enumeration.Language;
Expand Down Expand Up @@ -225,6 +226,11 @@ public class Course extends DomainObject {
@Column(name = "learning_paths_enabled", nullable = false)
private boolean learningPathsEnabled = false;

@OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name = "learning_paths_configuration_id")
@JsonIgnoreProperties("course")
private LearningPathsConfiguration learningPathsConfiguration;

@Column(name = "student_course_analytics_dashboard_enabled", nullable = false)
private boolean studentCourseAnalyticsDashboardEnabled = false;

Expand Down Expand Up @@ -767,6 +773,14 @@ public void setLearningPathsEnabled(boolean learningPathsEnabled) {
this.learningPathsEnabled = learningPathsEnabled;
}

public LearningPathsConfiguration getLearningPathsConfiguration() {
return learningPathsConfiguration;
}

public void setLearningPathsConfiguration(LearningPathsConfiguration learningPathsConfiguration) {
this.learningPathsConfiguration = learningPathsConfiguration;
}

public boolean getStudentCourseAnalyticsDashboardEnabled() {
return studentCourseAnalyticsDashboardEnabled;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package de.tum.in.www1.artemis.domain.competency;
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.in.www1.artemis.domain.Course;
import de.tum.in.www1.artemis.domain.DomainObject;

/**
* A LearningPathsConfiguration. Stores all settings that the instructor can set for a course.
*/
@Entity
@Table(name = "learning_paths_configuration")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class LearningPathsConfiguration extends DomainObject {

@OneToOne(mappedBy = "learningPathsConfiguration")
@JsonIgnoreProperties(value = "learningPathsConfiguration", allowSetters = true)
private Course course;

@Column(name = "include_all_graded_exercises")
private boolean includeAllGradedExercises;

public Course getCourse() {
return course;
}

public void setCourse(Course course) {
this.course = course;
}

public boolean getIncludeAllGradedExercises() {
return includeAllGradedExercises;
}

public void setIncludeAllGradedExercises(boolean includeAllGradedExercises) {
this.includeAllGradedExercises = includeAllGradedExercises;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ SELECT COUNT(c) > 0
@EntityGraph(type = LOAD, attributePaths = { "competencies", "prerequisites" })
Optional<Course> findWithEagerCompetenciesAndPrerequisitesById(long courseId);

@EntityGraph(type = LOAD, attributePaths = { "learningPathsConfiguration" })
Optional<Course> findWithEagerLearningPathsConfigurationById(long courseId);

@EntityGraph(type = LOAD, attributePaths = { "competencies", "prerequisites", "learningPathsConfiguration" })
Optional<Course> findWithEagerCompetenciesAndPrerequisitesAndLearningPathsConfigurationById(long courseId);

@Query("""
SELECT c
FROM Course c
Expand Down Expand Up @@ -157,7 +163,8 @@ SELECT COUNT(c) > 0
@EntityGraph(type = LOAD, attributePaths = { "lectures", "lectures.lectureUnits", "lectures.attachments" })
Optional<Course> findWithEagerLecturesAndLectureUnitsById(long courseId);

@EntityGraph(type = LOAD, attributePaths = { "organizations", "competencies", "prerequisites", "tutorialGroupsConfiguration", "onlineCourseConfiguration" })
@EntityGraph(type = LOAD, attributePaths = { "organizations", "competencies", "prerequisites", "tutorialGroupsConfiguration", "onlineCourseConfiguration",
"learningPathsConfiguration" })
Optional<Course> findForUpdateById(long courseId);

@EntityGraph(type = LOAD, attributePaths = { "exercises", "lectures", "lectures.lectureUnits", "lectures.attachments", "competencies", "prerequisites" })
Expand Down Expand Up @@ -486,6 +493,16 @@ default Course findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(long cours
return getValueElseThrow(findWithEagerCompetenciesAndPrerequisitesById(courseId), courseId);
}

@NotNull
default Course findWithEagerLearningPathsConfigurationByIdElseThrow(long courseId) {
return getValueElseThrow(findWithEagerLearningPathsConfigurationById(courseId), courseId);
}

@NotNull
default Course findWithEagerCompetenciesAndPrerequisitesAndLearningPathsConfigurationByIdElseThrow(long courseId) {
return getValueElseThrow(findWithEagerCompetenciesAndPrerequisitesAndLearningPathsConfigurationById(courseId), courseId);
}

@NotNull
default Course findWithEagerLearningPathsAndLearningPathCompetenciesByIdElseThrow(long courseId) {
return getValueElseThrow(findWithEagerLearningPathsAndLearningPathCompetencies(courseId), courseId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package de.tum.in.www1.artemis.repository.competency;
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

import java.util.Optional;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import de.tum.in.www1.artemis.domain.competency.LearningPathsConfiguration;
import de.tum.in.www1.artemis.repository.base.ArtemisJpaRepository;

/**
* Spring Data JPA repository for the LearningPathsConfiguration entity.
*/
public interface LearningPathsConfigurationRepository extends ArtemisJpaRepository<LearningPathsConfiguration, Long> {

@Query("""
SELECT lpc
FROM LearningPathsConfiguration lpc
LEFT JOIN lpc.course c
LEFT JOIN c.learningPaths l
WHERE l.id = :learningPathId
""")
Optional<LearningPathsConfiguration> findByLearningPathId(@Param("learningPathId") long learningPathId);

default LearningPathsConfiguration findByLearningPathIdElseThrow(long learningPathId) {
return getValueElseThrow(findByLearningPathId(learningPathId), learningPathId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.competency.CourseCompetency;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.domain.competency.LearningPathsConfiguration;
import de.tum.in.www1.artemis.repository.competency.LearningPathsConfigurationRepository;
import de.tum.in.www1.artemis.service.LearningObjectService;
import de.tum.in.www1.artemis.service.learningpath.LearningPathRecommendationService.RecommendationState;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathNavigationDTO;
Expand All @@ -31,9 +33,13 @@ public class LearningPathNavigationService {

private final LearningObjectService learningObjectService;

public LearningPathNavigationService(LearningPathRecommendationService learningPathRecommendationService, LearningObjectService learningObjectService) {
private final LearningPathsConfigurationRepository learningPathsConfigurationRepository;

public LearningPathNavigationService(LearningPathRecommendationService learningPathRecommendationService, LearningObjectService learningObjectService,
LearningPathsConfigurationRepository learningPathsConfigurationRepository) {
this.learningPathRecommendationService = learningPathRecommendationService;
this.learningObjectService = learningObjectService;
this.learningPathsConfigurationRepository = learningPathsConfigurationRepository;
}

/**
Expand All @@ -44,14 +50,16 @@ public LearningPathNavigationService(LearningPathRecommendationService learningP
* @return the navigation
*/
public LearningPathNavigationDTO getNavigation(LearningPath learningPath) {
var learningPathsConfiguration = learningPathsConfigurationRepository.findByLearningPathIdElseThrow(learningPath.getId());
var recommendationState = learningPathRecommendationService.getRecommendedOrderOfNotMasteredCompetencies(learningPath);
var currentLearningObject = learningPathRecommendationService.getFirstLearningObject(learningPath.getUser(), recommendationState);
var currentLearningObject = learningPathRecommendationService.getFirstLearningObject(learningPath.getUser(), recommendationState, learningPathsConfiguration);
CourseCompetency competencyOfCurrentLearningObject;
var recommendationStateWithAllCompetencies = learningPathRecommendationService.getRecommendedOrderOfAllCompetencies(learningPath);

// If all competencies are mastered, get the last completed learning object
if (currentLearningObject == null) {
currentLearningObject = learningPathRecommendationService.getLastLearningObject(learningPath.getUser(), recommendationStateWithAllCompetencies);
currentLearningObject = learningPathRecommendationService.getLastLearningObject(learningPath.getUser(), recommendationStateWithAllCompetencies,
learningPathsConfiguration);

// If we still didn't find any learning object, there exists no learning object in the learning path and we can return an empty navigation
if (currentLearningObject == null) {
Expand All @@ -65,7 +73,8 @@ public LearningPathNavigationDTO getNavigation(LearningPath learningPath) {
competencyOfCurrentLearningObject = findCorrespondingCompetencyForLearningObject(recommendationState, currentLearningObject, true);
}

return getNavigationRelativeToLearningObject(recommendationStateWithAllCompetencies, currentLearningObject, competencyOfCurrentLearningObject.getId(), learningPath);
return getNavigationRelativeToLearningObject(recommendationStateWithAllCompetencies, currentLearningObject, competencyOfCurrentLearningObject.getId(), learningPath,
learningPathsConfiguration);
}

/**
Expand Down Expand Up @@ -103,38 +112,42 @@ private CourseCompetency findCorrespondingCompetencyForLearningObject(Recommenda
*/
public LearningPathNavigationDTO getNavigationRelativeToLearningObject(LearningPath learningPath, long learningObjectId, LearningObjectType learningObjectType,
long competencyId) {
var learningPathsConfiguration = learningPathsConfigurationRepository.findByLearningPathIdElseThrow(learningPath.getId());

var recommendationState = learningPathRecommendationService.getRecommendedOrderOfAllCompetencies(learningPath);
var currentLearningObject = learningObjectService.getLearningObjectByIdAndType(learningObjectId, learningObjectType);

return getNavigationRelativeToLearningObject(recommendationState, currentLearningObject, competencyId, learningPath);
return getNavigationRelativeToLearningObject(recommendationState, currentLearningObject, competencyId, learningPath, learningPathsConfiguration);
}

private LearningPathNavigationDTO getNavigationRelativeToLearningObject(RecommendationState recommendationState, LearningObject currentLearningObject, long competencyId,
LearningPath learningPath) {
LearningPath learningPath, LearningPathsConfiguration learningPathsConfiguration) {
var currentCompetency = recommendationState.competencyIdMap().get(competencyId);

var learningObjectsInCurrentCompetency = learningPathRecommendationService.getOrderOfLearningObjectsForCompetency(currentCompetency, learningPath.getUser());
var learningObjectsInCurrentCompetency = learningPathRecommendationService.getOrderOfLearningObjectsForCompetency(currentCompetency, learningPath.getUser(),
learningPathsConfiguration);
int indexOfCurrentLearningObject = learningObjectsInCurrentCompetency.indexOf(currentLearningObject);

var predecessorLearningObjectDTO = getPredecessorOfLearningObject(recommendationState, currentCompetency, learningObjectsInCurrentCompetency, indexOfCurrentLearningObject,
learningPath.getUser());
learningPath.getUser(), learningPathsConfiguration);
var currentLearningObjectDTO = createLearningPathNavigationObjectDTO(currentLearningObject, learningPath.getUser(), currentCompetency);
var successorLearningObjectDTO = getSuccessorOfLearningObject(recommendationState, currentCompetency, learningObjectsInCurrentCompetency, indexOfCurrentLearningObject,
learningPath.getUser());
learningPath.getUser(), learningPathsConfiguration);

return new LearningPathNavigationDTO(predecessorLearningObjectDTO, currentLearningObjectDTO, successorLearningObjectDTO, learningPath.getProgress());
}

private LearningPathNavigationObjectDTO getPredecessorOfLearningObject(RecommendationState recommendationState, CourseCompetency currentCompetency,
List<LearningObject> learningObjectsInCurrentCompetency, int indexOfCurrentLearningObject, User user) {
List<LearningObject> learningObjectsInCurrentCompetency, int indexOfCurrentLearningObject, User user, LearningPathsConfiguration learningPathsConfiguration) {
LearningObject predecessorLearningObject = null;
CourseCompetency competencyOfPredecessor = null;
if (indexOfCurrentLearningObject <= 0) {
int indexOfCompetencyToSearch = recommendationState.recommendedOrderOfCompetencies().indexOf(currentCompetency.getId()) - 1;
while (indexOfCompetencyToSearch >= 0 && predecessorLearningObject == null) {
long competencyIdToSearchNext = recommendationState.recommendedOrderOfCompetencies().get(indexOfCompetencyToSearch);
var competencyToSearch = recommendationState.competencyIdMap().get(competencyIdToSearchNext);
var learningObjectsInPreviousCompetency = learningPathRecommendationService.getOrderOfLearningObjectsForCompetency(competencyToSearch, user);
var learningObjectsInPreviousCompetency = learningPathRecommendationService.getOrderOfLearningObjectsForCompetency(competencyToSearch, user,
learningPathsConfiguration);
if (!learningObjectsInPreviousCompetency.isEmpty()) {
predecessorLearningObject = learningObjectsInPreviousCompetency.getLast();
competencyOfPredecessor = competencyToSearch;
Expand All @@ -150,15 +163,16 @@ private LearningPathNavigationObjectDTO getPredecessorOfLearningObject(Recommend
}

private LearningPathNavigationObjectDTO getSuccessorOfLearningObject(RecommendationState recommendationState, CourseCompetency currentCompetency,
List<LearningObject> learningObjectsInCurrentCompetency, int indexOfCurrentLearningObject, User user) {
List<LearningObject> learningObjectsInCurrentCompetency, int indexOfCurrentLearningObject, User user, LearningPathsConfiguration learningPathsConfiguration) {
LearningObject successorLearningObject = null;
CourseCompetency competencyOfSuccessor = null;
if (indexOfCurrentLearningObject >= learningObjectsInCurrentCompetency.size() - 1) {
int indexOfCompetencyToSearch = recommendationState.recommendedOrderOfCompetencies().indexOf(currentCompetency.getId()) + 1;
while (indexOfCompetencyToSearch < recommendationState.recommendedOrderOfCompetencies().size() && successorLearningObject == null) {
long competencyIdToSearchNext = recommendationState.recommendedOrderOfCompetencies().get(indexOfCompetencyToSearch);
var nextCompetencyToSearch = recommendationState.competencyIdMap().get(competencyIdToSearchNext);
var learningObjectsInNextCompetency = learningPathRecommendationService.getOrderOfLearningObjectsForCompetency(nextCompetencyToSearch, user);
var learningObjectsInNextCompetency = learningPathRecommendationService.getOrderOfLearningObjectsForCompetency(nextCompetencyToSearch, user,
learningPathsConfiguration);
if (!learningObjectsInNextCompetency.isEmpty()) {
successorLearningObject = learningObjectsInNextCompetency.getFirst();
competencyOfSuccessor = nextCompetencyToSearch;
Expand All @@ -176,14 +190,15 @@ private LearningPathNavigationObjectDTO getSuccessorOfLearningObject(Recommendat
/**
* Get the navigation overview for the given learning path.
*
* @param learningPath the learning path
* @param learningPath the learning path
* @param learningPathsConfiguration the settings for the learning paths
* @return the navigation overview
*/
public LearningPathNavigationOverviewDTO getNavigationOverview(LearningPath learningPath) {
public LearningPathNavigationOverviewDTO getNavigationOverview(LearningPath learningPath, LearningPathsConfiguration learningPathsConfiguration) {
var learningPathUser = learningPath.getUser();
RecommendationState recommendationState = learningPathRecommendationService.getRecommendedOrderOfAllCompetencies(learningPath);
var learningObjects = recommendationState.recommendedOrderOfCompetencies().stream().map(competencyId -> recommendationState.competencyIdMap().get(competencyId))
.flatMap(competency -> learningPathRecommendationService.getOrderOfLearningObjectsForCompetency(competency, learningPathUser).stream()
.flatMap(competency -> learningPathRecommendationService.getOrderOfLearningObjectsForCompetency(competency, learningPathUser, learningPathsConfiguration).stream()
.map(learningObject -> createLearningPathNavigationObjectDTO(learningObject, learningPathUser, competency)))
.toList();
return new LearningPathNavigationOverviewDTO(learningObjects);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import de.tum.in.www1.artemis.domain.competency.CompetencyRelation;
import de.tum.in.www1.artemis.domain.competency.CourseCompetency;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.domain.competency.LearningPathsConfiguration;
import de.tum.in.www1.artemis.domain.competency.RelationType;
import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
import de.tum.in.www1.artemis.repository.CompetencyRelationRepository;
Expand Down Expand Up @@ -262,7 +263,7 @@ private void generateNgxPathRepresentationForCompetency(User user, CourseCompete
currentCluster.add(NgxLearningPathDTO.Node.of(startNodeId, NgxLearningPathDTO.NodeType.COMPETENCY_START, competency.getId()));
currentCluster.add(NgxLearningPathDTO.Node.of(endNodeId, NgxLearningPathDTO.NodeType.COMPETENCY_END, competency.getId()));

final var recommendedLearningObjects = learningPathRecommendationService.getRecommendedOrderOfLearningObjects(user, competency, state);
final var recommendedLearningObjects = learningPathRecommendationService.getRecommendedOrderOfLearningObjects(user, competency, state, new LearningPathsConfiguration());
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < recommendedLearningObjects.size(); i++) {

// add node for learning object
Expand Down
Loading
Loading