diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java index 1ea95f1d6657..7427a9fc1ce6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java @@ -48,7 +48,13 @@ default Post findPostOrMessagePostByIdElseThrow(Long postId) throws EntityNotFou List findAllByConversationId(Long conversationId); - List findAllByCourseId(Long courseId); + @Query(""" + SELECT p + FROM Post p + LEFT JOIN p.conversation conversation + WHERE conversation.course.id = :courseId + """) + List findAllByCourseId(@Param("courseId") Long courseId); List findByIdIn(List idList); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java b/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java index 2f3b8d51596c..8ee1f5982268 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java @@ -3,5 +3,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record CourseDeletionSummaryDTO(long numberOfBuilds, long numberOfCommunicationPosts, long numberOfAnswerPosts) { +public record CourseDeletionSummaryDTO(long numberOfBuilds, long numberOfCommunicationPosts, long numberOfAnswerPosts, long numberProgrammingExercises, long numberTextExercises, + long numberFileUploadExercises, long numberModelingExercises, long numberQuizExercises, long numberExams, long numberLectures) { } 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 232e86070473..6122b92810c2 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 @@ -98,6 +98,7 @@ import de.tum.cit.aet.artemis.exam.repository.ExerciseGroupRepository; import de.tum.cit.aet.artemis.exam.service.ExamDeletionService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; +import de.tum.cit.aet.artemis.exercise.domain.ExerciseType; import de.tum.cit.aet.artemis.exercise.domain.IncludedInOverallScore; import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; @@ -475,13 +476,26 @@ public Set findAllOnlineCoursesForPlatformForUser(String registrationId, * @return the course deletion summary */ public CourseDeletionSummaryDTO getDeletionSummary(Course course) { + Long courseId = course.getId(); + List programmingExerciseIds = course.getExercises().stream().map(Exercise::getId).toList(); long numberOfBuilds = buildJobRepository.countBuildJobsByExerciseIds(programmingExerciseIds); - List posts = postRepository.findAllByCourseId(course.getId()); + List posts = postRepository.findAllByCourseId(courseId); long numberOfCommunicationPosts = posts.size(); long numberOfAnswerPosts = answerPostRepository.countAnswerPostsByPostIdIn(posts.stream().map(Post::getId).toList()); - return new CourseDeletionSummaryDTO(numberOfBuilds, numberOfCommunicationPosts, numberOfAnswerPosts); + long numberLectures = lectureRepository.countByCourse_Id(courseId); + long numberExams = examRepository.countByCourse_Id(courseId); + + Map countByExerciseType = exerciseService.countByCourseIdGroupByType(courseId); + long numberProgrammingExercises = countByExerciseType.get(ExerciseType.PROGRAMMING); + long numberTextExercises = countByExerciseType.get(ExerciseType.TEXT); + long numberQuizExercises = countByExerciseType.get(ExerciseType.QUIZ); + long numberFileUploadExercises = countByExerciseType.get(ExerciseType.FILE_UPLOAD); + long numberModelingExercises = countByExerciseType.get(ExerciseType.MODELING); + + return new CourseDeletionSummaryDTO(numberOfBuilds, numberOfCommunicationPosts, numberOfAnswerPosts, numberProgrammingExercises, numberTextExercises, + numberFileUploadExercises, numberModelingExercises, numberQuizExercises, numberExams, numberLectures); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRepository.java b/src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRepository.java index a884ed754e4a..48f17f7ae26d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRepository.java @@ -385,6 +385,8 @@ SELECT COUNT(studentExam) """) long countGeneratedStudentExamsByExamWithoutTestRuns(@Param("examId") long examId); + long countByCourse_Id(Long courseId); + /** * Returns the title of the exam with the given id. * diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseTypeCountDTO.java b/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseTypeCountDTO.java new file mode 100644 index 000000000000..094ec79cd85f --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseTypeCountDTO.java @@ -0,0 +1,13 @@ +package de.tum.cit.aet.artemis.exercise.dto; + +import java.util.Objects; + +import de.tum.cit.aet.artemis.exercise.domain.Exercise; +import de.tum.cit.aet.artemis.exercise.domain.ExerciseType; + +public record ExerciseTypeCountDTO(ExerciseType exerciseType, long count) { + + public ExerciseTypeCountDTO(Class exerciseType, Long count) { + this(ExerciseType.getExerciseTypeFromClass(exerciseType.asSubclass(Exercise.class)), Objects.requireNonNullElse(count, 0L)); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseRepository.java b/src/main/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseRepository.java index 60b85bc14f79..dbe00ff1083c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseRepository.java @@ -25,6 +25,7 @@ import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; import de.tum.cit.aet.artemis.exam.web.ExamResource; import de.tum.cit.aet.artemis.exercise.domain.Exercise; +import de.tum.cit.aet.artemis.exercise.dto.ExerciseTypeCountDTO; import de.tum.cit.aet.artemis.exercise.dto.ExerciseTypeMetricsEntry; /** @@ -627,4 +628,21 @@ SELECT count(e) > 0 AND e.exerciseGroup IS NOT NULL """) boolean isExamExercise(@Param("exerciseId") long exerciseId); + + /** + * Returns a mapping from exercise type to count for a given course id. Note that there are way fewer courses + * than exercise, so loading the course and joining the course is way faster than vice versa. + * + * @param courseId the courseId to get the exerciseType->count mapping for + * @return a list of mappings from exercise type to count + */ + @Query(""" + SELECT new de.tum.cit.aet.artemis.exercise.dto.ExerciseTypeCountDTO(TYPE(e), COUNT(e)) + FROM Course c + JOIN c.exercises e + WHERE c.id = :courseId + AND TYPE(e) IN (ModelingExercise, TextExercise, ProgrammingExercise, QuizExercise, FileUploadExercise) + GROUP BY TYPE(e) + """) + List countByCourseIdGroupedByType(@Param("courseId") long courseId); } diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseService.java index 73fce3b0f9f1..bcaa1fcbac35 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseService.java @@ -6,6 +6,7 @@ import static java.time.ZonedDateTime.now; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -63,9 +64,11 @@ import de.tum.cit.aet.artemis.exam.service.ExamLiveEventsService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.domain.ExerciseMode; +import de.tum.cit.aet.artemis.exercise.domain.ExerciseType; import de.tum.cit.aet.artemis.exercise.domain.Submission; import de.tum.cit.aet.artemis.exercise.domain.Team; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; +import de.tum.cit.aet.artemis.exercise.dto.ExerciseTypeCountDTO; import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; import de.tum.cit.aet.artemis.exercise.repository.SubmissionRepository; @@ -829,4 +832,17 @@ public T saveWithCompetencyLinks(T exercise, Function public void reconnectCompetencyExerciseLinks(Exercise exercise) { exercise.getCompetencyLinks().forEach(link -> link.setExercise(exercise)); } + + /** + * Returns a map from exercise type to count of exercise given a course id. + * + * @param courseId the course id + * @return the mapping from exercise type to course type. If a course has no exercises for a specific type, the map contains an entry for that type with value 0. + */ + public Map countByCourseIdGroupByType(Long courseId) { + Map exerciseTypeCountMap = exerciseRepository.countByCourseIdGroupedByType(courseId).stream() + .collect(Collectors.toMap(ExerciseTypeCountDTO::exerciseType, ExerciseTypeCountDTO::count)); + + return Arrays.stream(ExerciseType.values()).collect(Collectors.toMap(type -> type, type -> exerciseTypeCountMap.getOrDefault(type, 0L))); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java index 33ce07e7b6fb..e441c1633b23 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java @@ -216,4 +216,6 @@ default Lecture findByIdWithLectureUnitsAndSlidesAndAttachmentsElseThrow(long le GROUP BY l.course.id """) Set countVisibleLectures(@Param("courseIds") Set courseIds, @Param("now") ZonedDateTime now); + + long countByCourse_Id(Long courseId); } diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts index e4b3865d3dbc..8e15c55fbd28 100644 --- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts +++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts @@ -204,12 +204,6 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After } private getExistingSummaryEntries(): EntitySummary { - const numberRepositories = - this.course?.exercises - ?.filter((exercise) => exercise.type === 'programming') - .map((exercise) => exercise?.numberOfParticipations ?? 0) - .reduce((repositorySum, numberOfParticipationsForRepository) => repositorySum + numberOfParticipationsForRepository, 0) ?? 0; - const numberOfExercisesPerType = new Map(); this.course?.exercises?.forEach((exercise) => { if (exercise.type === undefined) { @@ -219,8 +213,6 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After numberOfExercisesPerType.set(exercise.type, oldValue + 1); }); - const numberExams = this.course?.numberOfExams ?? 0; - const numberLectures = this.course?.lectures?.length ?? 0; const numberStudents = this.course?.numberOfStudents ?? 0; const numberTutors = this.course?.numberOfTeachingAssistants ?? 0; const numberEditors = this.course?.numberOfEditors ?? 0; @@ -228,14 +220,6 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After const isTestCourse = this.course?.testCourse; return { - 'artemisApp.course.delete.summary.numberRepositories': numberRepositories, - 'artemisApp.course.delete.summary.numberProgrammingExercises': numberOfExercisesPerType.get(ExerciseType.PROGRAMMING) ?? 0, - 'artemisApp.course.delete.summary.numberModelingExercises': numberOfExercisesPerType.get(ExerciseType.MODELING) ?? 0, - 'artemisApp.course.delete.summary.numberTextExercises': numberOfExercisesPerType.get(ExerciseType.TEXT) ?? 0, - 'artemisApp.course.delete.summary.numberFileUploadExercises': numberOfExercisesPerType.get(ExerciseType.FILE_UPLOAD) ?? 0, - 'artemisApp.course.delete.summary.numberQuizExercises': numberOfExercisesPerType.get(ExerciseType.QUIZ) ?? 0, - 'artemisApp.course.delete.summary.numberExams': numberExams, - 'artemisApp.course.delete.summary.numberLectures': numberLectures, 'artemisApp.course.delete.summary.numberStudents': numberStudents, 'artemisApp.course.delete.summary.numberTutors': numberTutors, 'artemisApp.course.delete.summary.numberEditors': numberEditors, @@ -259,6 +243,13 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After return { ...this.getExistingSummaryEntries(), + 'artemisApp.course.delete.summary.numberExams': summary.numberExams, + 'artemisApp.course.delete.summary.numberLectures': summary.numberLectures, + 'artemisApp.course.delete.summary.numberProgrammingExercises': summary.numberProgrammingExercises, + 'artemisApp.course.delete.summary.numberTextExercises': summary.numberTextExercises, + 'artemisApp.course.delete.summary.numberFileUploadExercises': summary.numberFileUploadExercises, + 'artemisApp.course.delete.summary.numberQuizExercises': summary.numberQuizExercises, + 'artemisApp.course.delete.summary.numberModelingExercises': summary.numberModelingExercises, 'artemisApp.course.delete.summary.numberBuilds': summary.numberOfBuilds, 'artemisApp.course.delete.summary.numberCommunicationPosts': summary.numberOfCommunicationPosts, 'artemisApp.course.delete.summary.numberAnswerPosts': summary.numberOfAnswerPosts, diff --git a/src/main/webapp/app/entities/course-deletion-summary.model.ts b/src/main/webapp/app/entities/course-deletion-summary.model.ts index 606a9d440d49..f36eadc241d4 100644 --- a/src/main/webapp/app/entities/course-deletion-summary.model.ts +++ b/src/main/webapp/app/entities/course-deletion-summary.model.ts @@ -2,4 +2,11 @@ export interface CourseDeletionSummaryDTO { numberOfBuilds: number; numberOfCommunicationPosts: number; numberOfAnswerPosts: number; + numberProgrammingExercises: number; + numberTextExercises: number; + numberFileUploadExercises: number; + numberQuizExercises: number; + numberModelingExercises: number; + numberExams: number; + numberLectures: number; } diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/util/ConversationUtilService.java b/src/test/java/de/tum/cit/aet/artemis/communication/util/ConversationUtilService.java index 1a1855651224..1d581ffc7c66 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/util/ConversationUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/util/ConversationUtilService.java @@ -660,7 +660,6 @@ private Post createMessageWithReactionForUser(String login, String messageText, message.setAuthor(userUtilService.getUserByLogin(login)); message.setContent(messageText); message.setCreationDate(ZonedDateTime.now()); - message.setCourse(conversation.getCourse()); conversation.setCreator(message.getAuthor()); addReactionForUserToPost(login, message); conversationRepository.save(conversation);