diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CompetencyExerciseLinkRepository.java b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CompetencyExerciseLinkRepository.java index d19675ca6412..8622daff5490 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CompetencyExerciseLinkRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CompetencyExerciseLinkRepository.java @@ -2,7 +2,11 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import java.util.List; + import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyExerciseLink; @@ -12,4 +16,10 @@ @Repository public interface CompetencyExerciseLinkRepository extends ArtemisJpaRepository { + @Query(""" + SELECT cel FROM CompetencyExerciseLink cel + LEFT JOIN FETCH cel.competency + WHERE cel.exercise.id = :exerciseId + """) + List findByExerciseIdWithCompetency(@Param("exerciseId") long exerciseId); } diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/competency/CompetencyService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/competency/CompetencyService.java index 9c11f0f33fdc..9fc79520601b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/competency/CompetencyService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/competency/CompetencyService.java @@ -7,14 +7,17 @@ import java.util.HashMap; import java.util.List; 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.competency.Competency; +import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyLectureUnitLink; import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency; import de.tum.cit.aet.artemis.atlas.dto.CompetencyImportOptionsDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyWithTailRelationDTO; +import de.tum.cit.aet.artemis.atlas.repository.CompetencyExerciseLinkRepository; import de.tum.cit.aet.artemis.atlas.repository.CompetencyLectureUnitLinkRepository; import de.tum.cit.aet.artemis.atlas.repository.CompetencyProgressRepository; import de.tum.cit.aet.artemis.atlas.repository.CompetencyRelationRepository; @@ -27,6 +30,8 @@ import de.tum.cit.aet.artemis.core.repository.CourseRepository; import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; import de.tum.cit.aet.artemis.exercise.service.ExerciseService; +import de.tum.cit.aet.artemis.lecture.domain.ExerciseUnit; +import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.repository.LectureUnitCompletionRepository; import de.tum.cit.aet.artemis.lecture.service.LectureUnitService; @@ -39,15 +44,19 @@ public class CompetencyService extends CourseCompetencyService { private final CompetencyRepository competencyRepository; + private final CompetencyExerciseLinkRepository competencyExerciseLinkRepository; + public CompetencyService(CompetencyRepository competencyRepository, AuthorizationCheckService authCheckService, CompetencyRelationRepository competencyRelationRepository, LearningPathService learningPathService, CompetencyProgressService competencyProgressService, LectureUnitService lectureUnitService, CompetencyProgressRepository competencyProgressRepository, LectureUnitCompletionRepository lectureUnitCompletionRepository, StandardizedCompetencyRepository standardizedCompetencyRepository, CourseCompetencyRepository courseCompetencyRepository, ExerciseService exerciseService, - LearningObjectImportService learningObjectImportService, CompetencyLectureUnitLinkRepository competencyLectureUnitLinkRepository, CourseRepository courseRepository) { + LearningObjectImportService learningObjectImportService, CompetencyLectureUnitLinkRepository competencyLectureUnitLinkRepository, CourseRepository courseRepository, + CompetencyExerciseLinkRepository competencyExerciseLinkRepository) { super(competencyProgressRepository, courseCompetencyRepository, competencyRelationRepository, competencyProgressService, exerciseService, lectureUnitService, learningPathService, authCheckService, standardizedCompetencyRepository, lectureUnitCompletionRepository, learningObjectImportService, competencyLectureUnitLinkRepository, courseRepository); this.competencyRepository = competencyRepository; + this.competencyExerciseLinkRepository = competencyExerciseLinkRepository; } /** @@ -121,4 +130,26 @@ public List findCompetenciesWithProgressForUserByCourseId(Long cours List competencies = competencyRepository.findByCourseIdOrderById(courseId); return findProgressForCompetenciesAndUser(competencies, userId); } + + /** + * Creates competency links for exercise units of the lecture. + *

+ * As exercise units can not be linked to competencies but only via the exercise itself, we add temporary links to the exercise units. + * Although they can not be persisted, this makes it easier to display the linked competencies in the client consistently across all lecture unit type. + * + * @param lecture the lecture to augment the exercise unit links for + */ + public void addCompetencyLinksToExerciseUnits(Lecture lecture) { + var exerciseUnits = lecture.getLectureUnits().stream().filter(unit -> unit instanceof ExerciseUnit); + exerciseUnits.forEach(unit -> { + var exerciseUnit = (ExerciseUnit) unit; + var exercise = exerciseUnit.getExercise(); + if (exercise != null) { + var competencyExerciseLinks = competencyExerciseLinkRepository.findByExerciseIdWithCompetency(exercise.getId()); + var competencyLectureUnitLinks = competencyExerciseLinks.stream().map(link -> new CompetencyLectureUnitLink(link.getCompetency(), exerciseUnit, link.getWeight())) + .collect(Collectors.toSet()); + exerciseUnit.setCompetencyLinks(competencyLectureUnitLinks); + } + }); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/ExerciseUnit.java b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/ExerciseUnit.java index c609a9f7b049..8b7be910e689 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/domain/ExerciseUnit.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/domain/ExerciseUnit.java @@ -11,12 +11,14 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; +import jakarta.persistence.Transient; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyLectureUnitLink; import de.tum.cit.aet.artemis.exercise.domain.Exercise; @@ -32,6 +34,13 @@ public class ExerciseUnit extends LectureUnit { @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private Exercise exercise; + // Competency links are not persisted in this entity but only in the exercise itself + @Transient + @JsonSerialize + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonIgnoreProperties("lectureUnit") + private Set competencyLinks = new HashSet<>(); + public Exercise getExercise() { return exercise; } @@ -66,15 +75,16 @@ public void setReleaseDate(ZonedDateTime releaseDate) { } @Override - @JsonIgnore + @JsonSerialize + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonIgnoreProperties("lectureUnit") public Set getCompetencyLinks() { - // Set the links in the associated exercise instead - return new HashSet<>(); + return competencyLinks; } @Override public void setCompetencyLinks(Set competencyLinks) { - // Retrieve the link in the associated exercise instead" + this.competencyLinks = competencyLinks; } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java index df85a54b6675..baecd6cd7c57 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/web/LectureResource.java @@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import de.tum.cit.aet.artemis.atlas.service.competency.CompetencyService; import de.tum.cit.aet.artemis.communication.domain.conversation.Channel; import de.tum.cit.aet.artemis.communication.repository.conversation.ChannelRepository; import de.tum.cit.aet.artemis.communication.service.conversation.ChannelService; @@ -66,6 +67,8 @@ public class LectureResource { private static final String ENTITY_NAME = "lecture"; + private final CompetencyService competencyService; + @Value("${jhipster.clientApp.name}") private String applicationName; @@ -89,7 +92,7 @@ public class LectureResource { public LectureResource(LectureRepository lectureRepository, LectureService lectureService, LectureImportService lectureImportService, CourseRepository courseRepository, UserRepository userRepository, AuthorizationCheckService authCheckService, ExerciseService exerciseService, ChannelService channelService, - ChannelRepository channelRepository) { + ChannelRepository channelRepository, CompetencyService competencyService) { this.lectureRepository = lectureRepository; this.lectureService = lectureService; this.lectureImportService = lectureImportService; @@ -99,6 +102,7 @@ public LectureResource(LectureRepository lectureRepository, LectureService lectu this.exerciseService = exerciseService; this.channelService = channelService; this.channelRepository = channelRepository; + this.competencyService = competencyService; } /** @@ -300,6 +304,7 @@ public ResponseEntity ingestLectures(@PathVariable Long courseId, @Requ public ResponseEntity getLectureWithDetails(@PathVariable Long lectureId) { log.debug("REST request to get lecture {} with details", lectureId); Lecture lecture = lectureRepository.findByIdWithAttachmentsAndPostsAndLectureUnitsAndCompetenciesAndCompletionsElseThrow(lectureId); + competencyService.addCompetencyLinksToExerciseUnits(lecture); Course course = lecture.getCourse(); if (course == null) { return ResponseEntity.badRequest().build(); @@ -326,9 +331,10 @@ public ResponseEntity getLectureWithDetailsAndSlides(@PathVariable long if (course == null) { return ResponseEntity.badRequest().build(); } - authCheckService.checkIsAllowedToSeeLectureElseThrow(lecture, userRepository.getUserWithGroupsAndAuthorities()); - User user = userRepository.getUserWithGroupsAndAuthorities(); + authCheckService.checkIsAllowedToSeeLectureElseThrow(lecture, user); + + competencyService.addCompetencyLinksToExerciseUnits(lecture); lectureService.filterActiveAttachmentUnits(lecture); lectureService.filterActiveAttachments(lecture, user); return ResponseEntity.ok(lecture); diff --git a/src/test/java/de/tum/cit/aet/artemis/lecture/LectureIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/lecture/LectureIntegrationTest.java index 75587b96f77d..dea62c73d76b 100644 --- a/src/test/java/de/tum/cit/aet/artemis/lecture/LectureIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/lecture/LectureIntegrationTest.java @@ -110,6 +110,7 @@ void initTestCase() throws Exception { channel.setLecture(this.lecture1); channelRepository.save(channel); textExercise = textExerciseRepository.findByCourseIdWithCategories(course1.getId()).stream().findFirst().orElseThrow(); + // Add users that are not in the course userUtilService.createAndSaveUser(TEST_PREFIX + "student42"); userUtilService.createAndSaveUser(TEST_PREFIX + "instructor42"); @@ -126,6 +127,7 @@ void initTestCase() throws Exception { lecture1 = lectureUtilService.addLectureUnitsToLecture(this.lecture1, List.of(exerciseUnit, attachmentUnit, videoUnit, textUnit, onlineUnit)); competency = competencyUtilService.createCompetency(course1); + competencyUtilService.linkExerciseToCompetency(competency, textExercise); } private void addAttachmentToLecture() { @@ -296,6 +298,8 @@ void getLecture_ExerciseAndAttachmentReleased_shouldGetLectureWithAllLectureUnit Lecture receivedLectureWithDetails = request.get("/api/lectures/" + lecture1.getId() + "/details", HttpStatus.OK, Lecture.class); assertThat(receivedLectureWithDetails.getId()).isEqualTo(lecture1.getId()); assertThat(receivedLectureWithDetails.getLectureUnits()).hasSize(5); + assertThat(receivedLectureWithDetails.getLectureUnits().stream().filter(lectureUnit -> lectureUnit instanceof ExerciseUnit).toList().getFirst().getCompetencyLinks()) + .hasSize(1); assertThat(receivedLectureWithDetails.getAttachments()).hasSize(2); testGetLecture(lecture1.getId());