From 36b6b0d9d7a2631a11bbc1d533789b19317662be Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 10 Aug 2025 20:50:05 +0200 Subject: [PATCH 01/30] try to improve database performance reduce number of involve data in db queries statistics-lifetime-overview and management-detail are still rather slow --- .../dto/dashboard/ResultCountDTO.java | 11 ------- .../repository/ComplaintRepository.java | 25 ++++++++------- .../repository/ResultRepository.java | 32 ++++++------------- .../service/AssessmentDashboardService.java | 10 +++--- .../service/TutorLeaderboardService.java | 8 ++--- .../core/dto/StatsForDashboardDTO.java | 6 ++-- .../service/course/CourseStatsService.java | 15 ++++----- .../core/web/course/CourseStatsResource.java | 6 ++-- .../repository/ExerciseRepository.java | 19 +++++++++-- .../exercise/service/ExerciseService.java | 11 ++++--- .../stats-for-dashboard.model.ts | 6 ++-- .../artemis/core/util/CourseTestService.java | 3 +- .../exercise/ExerciseIntegrationTest.java | 7 ++-- 13 files changed, 72 insertions(+), 87 deletions(-) delete mode 100644 src/main/java/de/tum/cit/aet/artemis/assessment/dto/dashboard/ResultCountDTO.java diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/dto/dashboard/ResultCountDTO.java b/src/main/java/de/tum/cit/aet/artemis/assessment/dto/dashboard/ResultCountDTO.java deleted file mode 100644 index ae53624de905..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/dto/dashboard/ResultCountDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package de.tum.cit.aet.artemis.assessment.dto.dashboard; - -import com.fasterxml.jackson.annotation.JsonInclude; - -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public record ResultCountDTO(boolean rated, long count) { - - public ResultCountDTO(Boolean rated, Long count) { - this(rated != null ? rated : false, count != null ? count : 0); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java index 1a68b3197e91..45ed73df48d0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java @@ -3,6 +3,7 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -262,7 +263,7 @@ SELECT COUNT(c) /** * Get the number of Complaints for all tutors of a course * - * @param courseId - id of the course + * @param exerciseIds - ids of the exercises in the course (should be filtered to only include exercises with manual assessment) * @return list of TutorLeaderboardComplaints */ @Query(""" @@ -278,12 +279,12 @@ SELECT COUNT(c) JOIN s.participation p JOIN p.exercise e WHERE c.complaintType = de.tum.cit.aet.artemis.assessment.domain.ComplaintType.COMPLAINT - AND e.course.id = :courseId + AND e.id IN :exerciseIds AND r.completionDate IS NOT NULL AND r.assessor.id IS NOT NULL GROUP BY r.assessor.id """) - List findTutorLeaderboardComplaintsByCourseId(@Param("courseId") long courseId); + List findTutorLeaderboardComplaintsByCourseId(@Param("exerciseIds") Collection exerciseIds); /** * Get the number of Complaints for all tutors of an exercise @@ -341,7 +342,7 @@ SELECT COUNT(c) /** * Get the number of complaintResponses for all tutors assessments of a course * - * @param courseId - id of the exercise + * @param exerciseIds - ids of the exercises in the course (should be filtered to only include exercises with manual assessment) * @return list of TutorLeaderboardComplaintResponses */ @Query(""" @@ -357,12 +358,12 @@ SELECT COUNT(c) JOIN s.participation p JOIN p.exercise e WHERE c.complaintType = de.tum.cit.aet.artemis.assessment.domain.ComplaintType.COMPLAINT - AND e.course.id = :courseId + AND e.id IN :exerciseIds AND r.completionDate IS NOT NULL AND c.accepted IS NOT NULL GROUP BY cr.reviewer.id """) - List findTutorLeaderboardComplaintResponsesByCourseId(@Param("courseId") long courseId); + List findTutorLeaderboardComplaintResponsesByCourseId(@Param("exerciseIds") Collection exerciseIds); /** * Get the number of complaintResponses for all tutors assessments of an exercise @@ -421,7 +422,7 @@ SELECT COUNT(c) /** * Get the number of Feedback Requests for all tutors assessments of a course * - * @param courseId - id of the exercise + * @param exerciseIds - ids of the exercises in the course (should be filtered to only include exercises with manual assessment) * @return list of TutorLeaderboardMoreFeedbackRequests */ @Query(""" @@ -437,11 +438,11 @@ SELECT COUNT(c) JOIN s.participation p JOIN p.exercise e WHERE c.complaintType = de.tum.cit.aet.artemis.assessment.domain.ComplaintType.MORE_FEEDBACK - AND e.course.id = :courseId + AND e.id IN :exerciseIds AND r.completionDate IS NOT NULL GROUP BY r.assessor.id """) - List findTutorLeaderboardMoreFeedbackRequestsByCourseId(@Param("courseId") long courseId); + List findTutorLeaderboardMoreFeedbackRequestsByCourseId(@Param("exerciseIds") Collection exerciseIds); // Valid JPQL syntax. Only SCA fails to properly detect the types. /** @@ -473,7 +474,7 @@ SELECT COUNT(c) /** * Get the number of Feedback Request Responses for all tutors assessments of a course * - * @param courseId - id of the course + * @param exerciseIds - ids of the exercises in the course (should be filtered to only include exercises with manual assessment) * @return list of TutorLeaderboardAnsweredMoreFeedbackRequests */ @Query(""" @@ -489,12 +490,12 @@ SELECT COUNT(c) JOIN s.participation p JOIN p.exercise e WHERE c.complaintType = de.tum.cit.aet.artemis.assessment.domain.ComplaintType.MORE_FEEDBACK - AND e.course.id = :courseId + AND e.id IN :exerciseIds AND r.completionDate IS NOT NULL AND c.accepted = TRUE GROUP BY cr.reviewer.id """) - List findTutorLeaderboardAnsweredMoreFeedbackRequestsByCourseId(@Param("courseId") long courseId); + List findTutorLeaderboardAnsweredMoreFeedbackRequestsByCourseId(@Param("exerciseIds") Collection exerciseIds); /** * Get the number of Feedback Request Responses for all tutors assessments of an exercise diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java index d73eb6ada837..a099be31fadb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java @@ -28,7 +28,6 @@ import de.tum.cit.aet.artemis.assessment.domain.GradingCriterion; import de.tum.cit.aet.artemis.assessment.domain.Result; import de.tum.cit.aet.artemis.assessment.dto.ResultWithPointsPerGradingCriterionDTO; -import de.tum.cit.aet.artemis.assessment.dto.dashboard.ResultCountDTO; import de.tum.cit.aet.artemis.assessment.dto.tutor.TutorLeaderboardAssessmentsDTO; import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.core.domain.DomainObject; @@ -210,20 +209,19 @@ LEFT JOIN FETCH TREAT(r.submission.participation AS StudentParticipation) p /** * counts the number of assessments of a course, which are either rated or not rated * - * @param exerciseIds - the exercises of the course - * @return a list with 3 elements: count of rated (in time) and unrated (late) assessments of a course and count of assessments without rating (null) + * @param exerciseIds - the ids of the exercises of the course (should be filtered to only include exercises with manual assessment) + * @return the number of assessments for the course */ @Query(""" - SELECT new de.tum.cit.aet.artemis.assessment.dto.dashboard.ResultCountDTO(r.rated, COUNT(r)) + SELECT COUNT(r) FROM Result r JOIN r.submission s JOIN s.participation p WHERE r.completionDate IS NOT NULL AND r.assessor IS NOT NULL AND p.exercise.id IN :exerciseIds - GROUP BY r.rated """) - List countAssessmentsByExerciseIdsAndRated(@Param("exerciseIds") Set exerciseIds); + long countAssessmentsByExerciseIdsAndRated(@Param("exerciseIds") Set exerciseIds); /** * Load a result from the database by its id together with the associated submission and the list of feedback items. @@ -551,8 +549,8 @@ default DueDateStat countNumberOfAutomaticAssistedAssessmentsForExercise(long ex * @param exerciseId - the exercise we are interested in * @return a number of assessments for the exercise */ - default DueDateStat countNumberOfFinishedAssessmentsForExercise(long exerciseId) { - return new DueDateStat(countNumberOfFinishedAssessmentsForExerciseIgnoreTestRuns(exerciseId), 0L); + default long countNumberOfFinishedAssessmentsForExercise(long exerciseId) { + return countNumberOfFinishedAssessmentsForExerciseIgnoreTestRuns(exerciseId); } /** @@ -561,24 +559,12 @@ default DueDateStat countNumberOfFinishedAssessmentsForExercise(long exerciseId) * @param exerciseIds - the exercise ids of the course we are interested in * @return a number of assessments for the course */ - default DueDateStat countNumberOfAssessments(Set exerciseIds) { + default long countNumberOfAssessments(Set exerciseIds) { // avoid invoking the query for empty sets, because this can lead to performance issues if (exerciseIds == null || exerciseIds.isEmpty()) { - return new DueDateStat(0, 0); + return 0; } - var ratedCounts = countAssessmentsByExerciseIdsAndRated(exerciseIds); - long inTime = 0; - long late = 0; - for (var ratedCount : ratedCounts) { - if (ratedCount.rated()) { - inTime = ratedCount.count(); - } - else { - late = ratedCount.count(); - } - // we are not interested in results with rated is null even if the database would return such - } - return new DueDateStat(inTime, late); + return countAssessmentsByExerciseIdsAndRated(exerciseIds); } @Query(""" diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/service/AssessmentDashboardService.java b/src/main/java/de/tum/cit/aet/artemis/assessment/service/AssessmentDashboardService.java index 94f1e780407d..1969b2e6e966 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/service/AssessmentDashboardService.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/service/AssessmentDashboardService.java @@ -89,15 +89,15 @@ public void generateStatisticsForExercisesForAssessmentDashboard(Set e // parts of this loop can possibly still be extracted for (Exercise exercise : exercises) { - DueDateStat totalNumberOfAssessments; + long numberOfAssessments; if (exercise instanceof ProgrammingExercise) { - totalNumberOfAssessments = new DueDateStat(programmingExerciseRepository.countAssessmentsByExerciseIdSubmitted(exercise.getId()), 0L); + numberOfAssessments = programmingExerciseRepository.countAssessmentsByExerciseIdSubmitted(exercise.getId()); log.debug("Finished >> programmingExerciseRepository.countAssessmentsByExerciseIdSubmitted << call for exercise {} in {}", exercise.getId(), TimeLogUtil.formatDurationFrom(start)); } else { - totalNumberOfAssessments = resultRepository.countNumberOfFinishedAssessmentsForExercise(exercise.getId()); + numberOfAssessments = resultRepository.countNumberOfFinishedAssessmentsForExercise(exercise.getId()); log.debug("Finished >> resultRepository.countNumberOfFinishedAssessmentsForExercise << call for exercise {} in {}", exercise.getId(), TimeLogUtil.formatDurationFrom(start)); } @@ -112,8 +112,8 @@ public void generateStatisticsForExercisesForAssessmentDashboard(Set e TimeLogUtil.formatDurationFrom(start)); } else { - // no examMode here, so correction rounds defaults to 1 and is the same as totalNumberOfAssessments - numberOfAssessmentsOfCorrectionRounds = new DueDateStat[] { totalNumberOfAssessments }; + // no examMode here, so correction rounds defaults to 1 and is the same as numberOfAssessments + numberOfAssessmentsOfCorrectionRounds = new DueDateStat[] { new DueDateStat(numberOfAssessments, 0L) }; } exercise.setNumberOfAssessmentsOfCorrectionRounds(numberOfAssessmentsOfCorrectionRounds); diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/service/TutorLeaderboardService.java b/src/main/java/de/tum/cit/aet/artemis/assessment/service/TutorLeaderboardService.java index 51c5d5f57a06..cc85e8936cde 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/service/TutorLeaderboardService.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/service/TutorLeaderboardService.java @@ -59,10 +59,10 @@ public List getCourseLeaderboard(Course course, Set e if (!exerciseIdsOfCourse.isEmpty()) { tutorLeaderboardAssessmentDTOS = resultRepository.findTutorLeaderboardAssessmentByCourseId(exerciseIdsOfCourse); } - var tutorLeaderboardComplaints = complaintRepository.findTutorLeaderboardComplaintsByCourseId(course.getId()); - var tutorLeaderboardComplaintResponses = complaintRepository.findTutorLeaderboardComplaintResponsesByCourseId(course.getId()); - var tutorLeaderboardMoreFeedbackRequests = complaintRepository.findTutorLeaderboardMoreFeedbackRequestsByCourseId(course.getId()); - var tutorLeaderboardAnsweredMoreFeedbackRequests = complaintRepository.findTutorLeaderboardAnsweredMoreFeedbackRequestsByCourseId(course.getId()); + var tutorLeaderboardComplaints = complaintRepository.findTutorLeaderboardComplaintsByCourseId(exerciseIdsOfCourse); + var tutorLeaderboardComplaintResponses = complaintRepository.findTutorLeaderboardComplaintResponsesByCourseId(exerciseIdsOfCourse); + var tutorLeaderboardMoreFeedbackRequests = complaintRepository.findTutorLeaderboardMoreFeedbackRequestsByCourseId(exerciseIdsOfCourse); + var tutorLeaderboardAnsweredMoreFeedbackRequests = complaintRepository.findTutorLeaderboardAnsweredMoreFeedbackRequestsByCourseId(exerciseIdsOfCourse); return aggregateTutorLeaderboardData(tutors, tutorLeaderboardAssessmentDTOS, tutorLeaderboardComplaints, tutorLeaderboardMoreFeedbackRequests, tutorLeaderboardComplaintResponses, tutorLeaderboardAnsweredMoreFeedbackRequests, false); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/StatsForDashboardDTO.java b/src/main/java/de/tum/cit/aet/artemis/core/dto/StatsForDashboardDTO.java index 5f29b177aedd..ea39ffdc7562 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/dto/StatsForDashboardDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/dto/StatsForDashboardDTO.java @@ -13,7 +13,7 @@ public class StatsForDashboardDTO { private DueDateStat numberOfSubmissions; - private DueDateStat totalNumberOfAssessments; + private Long totalNumberOfAssessments; private Long totalNumberOfAssessmentLocks; @@ -63,11 +63,11 @@ public void setNumberOfSubmissions(DueDateStat numberOfSubmissions) { this.numberOfSubmissions = numberOfSubmissions; } - public DueDateStat getTotalNumberOfAssessments() { + public Long getTotalNumberOfAssessments() { return totalNumberOfAssessments; } - public void setTotalNumberOfAssessments(DueDateStat totalNumberOfAssessments) { + public void setTotalNumberOfAssessments(Long totalNumberOfAssessments) { this.totalNumberOfAssessments = totalNumberOfAssessments; } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java index 07b38d88b175..47ad83ccc8ca 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java @@ -235,8 +235,7 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin var spanSize = this.determineTimeSpanSizeForActiveStudents(course, endDate, 17); var activeStudents = getActiveStudents(exerciseIds, 0, spanSize, endDate); - DueDateStat assessments = resultRepository.countNumberOfAssessments(exerciseIds); - long numberOfAssessments = assessments.inTime() + assessments.late(); + long numberOfAssessments = resultRepository.countNumberOfAssessments(exerciseIds); long numberOfInTimeSubmissions = submissionRepository.countAllByExerciseIdsSubmittedBeforeDueDate(exerciseIds) + programmingExerciseRepository.countAllSubmissionsByExerciseIdsSubmitted(exerciseIds); @@ -277,7 +276,8 @@ private double calculatePercentage(double positive, double total) { * @return a DTO containing the statistics */ public StatsForDashboardDTO getStatsForDashboardDTO(Course course) { - Set courseExerciseIds = exerciseRepository.findAllIdsByCourseId(course.getId()); + Set courseExerciseIds = exerciseRepository.findExerciseIdsByCourseId(course.getId()); + Set courseExerciseIdsWithManualAssessments = exerciseRepository.findExerciseIdsWithManualAssessmentByCourseId(course.getId()); StatsForDashboardDTO stats = new StatsForDashboardDTO(); @@ -285,12 +285,9 @@ public StatsForDashboardDTO getStatsForDashboardDTO(Course course) { numberOfInTimeSubmissions += programmingExerciseRepository.countAllSubmissionsByExerciseIdsSubmitted(courseExerciseIds); final long numberOfLateSubmissions = submissionRepository.countAllByExerciseIdsSubmittedAfterDueDate(courseExerciseIds); - DueDateStat totalNumberOfAssessments = resultRepository.countNumberOfAssessments(courseExerciseIds); - stats.setTotalNumberOfAssessments(totalNumberOfAssessments); + long numberOfAssessments = resultRepository.countNumberOfAssessments(courseExerciseIdsWithManualAssessments); + stats.setTotalNumberOfAssessments(numberOfAssessments); - // no examMode here, so it's the same as totalNumberOfAssessments - DueDateStat[] numberOfAssessmentsOfCorrectionRounds = { totalNumberOfAssessments }; - stats.setNumberOfAssessmentsOfCorrectionRounds(numberOfAssessmentsOfCorrectionRounds); stats.setNumberOfSubmissions(new DueDateStat(numberOfInTimeSubmissions, numberOfLateSubmissions)); final long numberOfMoreFeedbackRequests = complaintService.countMoreFeedbackRequestsByCourseId(course.getId()); @@ -307,7 +304,7 @@ public StatsForDashboardDTO getStatsForDashboardDTO(Course course) { final long totalNumberOfAssessmentLocks = submissionRepository.countLockedSubmissionsByCourseId(course.getId()); stats.setTotalNumberOfAssessmentLocks(totalNumberOfAssessmentLocks); - List leaderboardEntries = tutorLeaderboardService.getCourseLeaderboard(course, courseExerciseIds); + List leaderboardEntries = tutorLeaderboardService.getCourseLeaderboard(course, courseExerciseIdsWithManualAssessments); stats.setTutorLeaderboardEntries(leaderboardEntries); stats.setNumberOfRatings(ratingRepository.countByResult_Submission_Participation_Exercise_Course_Id(course.getId())); return stats; diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java index a815afd8e29c..b114a0f1d8c4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java @@ -139,7 +139,7 @@ public ResponseEntity> getExerciseSt var amountOfStudentsInCourse = Math.toIntExact(userRepository.countUserInGroup(studentsGroup)); var exerciseStatistics = exerciseService.getStatisticsForCourseManagementOverview(courseId, amountOfStudentsInCourse); - var exerciseIds = exerciseRepository.findAllIdsByCourseId(courseId); + var exerciseIds = exerciseRepository.findExerciseIdsByCourseId(courseId); var endDate = courseStatsService.determineEndDateForActiveStudents(course); var timeSpanSize = courseStatsService.determineTimeSpanSizeForActiveStudents(course, endDate, 4); var activeStudents = courseStatsService.getActiveStudents(exerciseIds, 0, timeSpanSize, endDate); @@ -165,7 +165,7 @@ public ResponseEntity> getActiveStudentsForCourseDetailView(@PathV @RequestParam Optional periodSize) { var course = courseRepository.findByIdElseThrow(courseId); authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.TEACHING_ASSISTANT, course, null); - var exerciseIds = exerciseRepository.findAllIdsByCourseId(courseId); + var exerciseIds = exerciseRepository.findExerciseIdsByCourseId(courseId); var chartEndDate = courseStatsService.determineEndDateForActiveStudents(course); var spanEndDate = chartEndDate.plusWeeks(periodSize.orElse(17) * periodIndex); var returnedSpanSize = courseStatsService.determineTimeSpanSizeForActiveStudents(course, spanEndDate, periodSize.orElse(17)); @@ -183,7 +183,7 @@ public ResponseEntity> getActiveStudentsForCourseDetailView(@PathV @EnforceAtLeastTutor public ResponseEntity> getActiveStudentsForCourseLiveTime(@PathVariable Long courseId) { authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.TEACHING_ASSISTANT, courseRepository.findByIdElseThrow(courseId), null); - var exerciseIds = exerciseRepository.findAllIdsByCourseId(courseId); + var exerciseIds = exerciseRepository.findExerciseIdsByCourseId(courseId); var course = courseRepository.findByIdElseThrow(courseId); if (course.getStartDate() == null) { throw new IllegalArgumentException("Course does not contain start date"); 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 e9a17b7f8ce6..0b871a5e33ea 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 @@ -371,10 +371,23 @@ AND EXISTS ( @Query(""" SELECT e.id FROM Exercise e - LEFT JOIN e.course c - WHERE c.id = :courseId + WHERE e.course.id = :courseId + """) + Set findExerciseIdsByCourseId(@Param("courseId") Long courseId); + + /** + * @param courseId - course id of the exercises we want to fetch + * @return all exercise-ids which belong to the course and have manual assessment enabled, i.e. text, modeling, file upload and programming exercises with manual or + * semi-automatic assessment + */ + @Query(""" + SELECT e.id + FROM Exercise e + WHERE e.course.id = :courseId + AND e.assessmentType <> de.tum.cit.aet.artemis.assessment.domain.AssessmentType.AUTOMATIC + AND TYPE(e) <> de.tum.cit.aet.artemis.quiz.domain.QuizExercise """) - Set findAllIdsByCourseId(@Param("courseId") Long courseId); + Set findExerciseIdsWithManualAssessmentByCourseId(@Param("courseId") Long courseId); @EntityGraph(type = LOAD, attributePaths = { "studentParticipations", "studentParticipations.student", "studentParticipations.submissions" }) Optional findWithEagerStudentParticipationsStudentAndSubmissionsById(Long exerciseId); 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 cbfb7322e8c0..4442ca109ea3 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 @@ -236,19 +236,19 @@ public StatsForDashboardDTO populateCommonStatistics(Exercise exercise, boolean Course course = exercise.getCourseViaExerciseGroupOrCourseMember(); DueDateStat numberOfSubmissions; - DueDateStat totalNumberOfAssessments; + long numberOfAssessments; if (exercise instanceof ProgrammingExercise) { numberOfSubmissions = new DueDateStat(programmingExerciseRepository.countSubmissionsByExerciseIdSubmitted(exerciseId), 0L); - totalNumberOfAssessments = new DueDateStat(programmingExerciseRepository.countAssessmentsByExerciseIdSubmitted(exerciseId), 0L); + numberOfAssessments = programmingExerciseRepository.countAssessmentsByExerciseIdSubmitted(exerciseId); } else { numberOfSubmissions = submissionRepository.countSubmissionsForExercise(exerciseId); - totalNumberOfAssessments = resultRepository.countNumberOfFinishedAssessmentsForExercise(exerciseId); + numberOfAssessments = resultRepository.countNumberOfFinishedAssessmentsForExercise(exerciseId); } stats.setNumberOfSubmissions(numberOfSubmissions); - stats.setTotalNumberOfAssessments(totalNumberOfAssessments); + stats.setTotalNumberOfAssessments(numberOfAssessments); final DueDateStat[] numberOfAssessmentsOfCorrectionRounds; int numberOfCorrectionRounds = 1; @@ -259,9 +259,10 @@ public StatsForDashboardDTO populateCommonStatistics(Exercise exercise, boolean } else { // no examMode here, so correction rounds defaults to 1 and is the same as totalNumberOfAssessments - numberOfAssessmentsOfCorrectionRounds = new DueDateStat[] { totalNumberOfAssessments }; + numberOfAssessmentsOfCorrectionRounds = new DueDateStat[] { new DueDateStat(numberOfAssessments, 0L) }; } + // TODO: why do we use DueDateStat here? late values are always 0 stats.setNumberOfAssessmentsOfCorrectionRounds(numberOfAssessmentsOfCorrectionRounds); final DueDateStat[] numberOfLockedAssessmentByOtherTutorsOfCorrectionRound; diff --git a/src/main/webapp/app/assessment/shared/assessment-dashboard/stats-for-dashboard.model.ts b/src/main/webapp/app/assessment/shared/assessment-dashboard/stats-for-dashboard.model.ts index fc161ac9f90c..7697791c761e 100644 --- a/src/main/webapp/app/assessment/shared/assessment-dashboard/stats-for-dashboard.model.ts +++ b/src/main/webapp/app/assessment/shared/assessment-dashboard/stats-for-dashboard.model.ts @@ -23,11 +23,11 @@ export class StatsForDashboard { /** * Correctly initializes a class instance from a typecasted object. * Returns a 'real' class instance that supports all class methods. - * @param object: The typecasted object + * @param statsForDashboard The type casted object * @returns The class instance */ - static from(object: StatsForDashboard): StatsForDashboard { - const stats = Object.assign(new StatsForDashboard(), object); + static from(statsForDashboard: StatsForDashboard): StatsForDashboard { + const stats = Object.assign(new StatsForDashboard(), statsForDashboard); stats.numberOfSubmissions = Object.assign(new DueDateStat(), stats.numberOfSubmissions); stats.totalNumberOfAssessments = Object.assign(new DueDateStat(), stats.totalNumberOfAssessments); stats.numberOfAutomaticAssistedAssessments = Object.assign(new DueDateStat(), stats.numberOfAutomaticAssistedAssessments); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java index 91894b060d55..2ba1fa58d1a8 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java @@ -1292,8 +1292,7 @@ public void testGetCourseForAssessmentDashboardWithStats() throws Exception { long numberOfInTimeSubmissions = course.getId().equals(testCourses.getFirst().getId()) ? 5 : 0; // course 1 has 5 submissions, course 2 has 0 submissions assertThat(stats.getNumberOfSubmissions().inTime()).as("Number of in-time submissions is correct").isEqualTo(numberOfInTimeSubmissions); assertThat(stats.getNumberOfSubmissions().late()).as("Number of latte submissions is correct").isZero(); - assertThat(stats.getTotalNumberOfAssessments().inTime()).as("Number of in-time assessments is correct").isZero(); - assertThat(stats.getTotalNumberOfAssessments().late()).as("Number of late assessments is correct").isZero(); + assertThat(stats.getTotalNumberOfAssessments()).as("Number of assessments is correct").isZero(); assertThat(stats.getNumberOfAssessmentsOfCorrectionRounds()).hasSize(1); assertThat(stats.getNumberOfAssessmentsOfCorrectionRounds()[0].inTime()).isZero(); assertThat(stats.getTutorLeaderboardEntries()).as("Number of tutor leaderboard entries is correct").hasSize(5); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/ExerciseIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/ExerciseIntegrationTest.java index 689faf7adf83..72f7201b19f6 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/ExerciseIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/ExerciseIntegrationTest.java @@ -128,7 +128,7 @@ else if (i % 4 == 0) { StatsForDashboardDTO statsForDashboardDTO = request.get("/api/exercise/exercises/" + textExercise.getId() + "/stats-for-assessment-dashboard", HttpStatus.OK, StatsForDashboardDTO.class); assertThat(statsForDashboardDTO.getNumberOfSubmissions().inTime()).isEqualTo(submissions.size() + 1); - assertThat(statsForDashboardDTO.getTotalNumberOfAssessments().inTime()).isEqualTo(3); + assertThat(statsForDashboardDTO.getTotalNumberOfAssessments()).isEqualTo(3); assertThat(statsForDashboardDTO.getNumberOfAutomaticAssistedAssessments().inTime()).isEqualTo(1); for (Exercise exercise : course.getExercises()) { @@ -150,7 +150,7 @@ void testGetStatsForExamExerciseAssessmentDashboard() throws Exception { StatsForDashboardDTO statsForDashboardDTO = request.get("/api/exercise/exercises/" + textExercise.getId() + "/stats-for-assessment-dashboard", HttpStatus.OK, StatsForDashboardDTO.class); assertThat(statsForDashboardDTO.getNumberOfSubmissions().inTime()).isZero(); - assertThat(statsForDashboardDTO.getTotalNumberOfAssessments().inTime()).isZero(); + assertThat(statsForDashboardDTO.getTotalNumberOfAssessments()).isZero(); assertThat(statsForDashboardDTO.getNumberOfAutomaticAssistedAssessments().inTime()).isZero(); for (Exercise exercise : course.getExercises()) { @@ -646,8 +646,7 @@ void testGetStatsForExerciseAssessmentDashboard() throws Exception { for (Exercise exercise : course.getExercises()) { StatsForDashboardDTO stats = request.get("/api/exercise/exercises/" + exercise.getId() + "/stats-for-assessment-dashboard", HttpStatus.OK, StatsForDashboardDTO.class); - assertThat(stats.getTotalNumberOfAssessments().inTime()).as("Number of in-time assessments is correct").isZero(); - assertThat(stats.getTotalNumberOfAssessments().late()).as("Number of late assessments is correct").isZero(); + assertThat(stats.getTotalNumberOfAssessments()).as("Number of in-time assessments is correct").isZero(); assertThat(stats.getTutorLeaderboardEntries()).as("Number of tutor leaderboard entries is correct").hasSameSizeAs(tutors); assertThat(stats.getNumberOfOpenComplaints()).as("Number of open complaints should be available to tutor").isNotNull(); From dd9e7e1e26f7218e923a61769fec9348bcc3302b Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 10 Aug 2025 23:17:37 +0200 Subject: [PATCH 02/30] additional performance improvements --- .../repository/ResultRepository.java | 6 +- .../LLMTokenUsageTraceRepository.java | 3 +- .../core/repository/StatisticsRepository.java | 8 +- .../core/service/StatisticsService.java | 23 ++--- .../service/course/CourseStatsService.java | 84 ++++++++++++------- .../core/web/course/CourseStatsResource.java | 4 +- .../aet/artemis/exam/service/ExamService.java | 2 +- .../course-detail-line-chart.component.ts | 22 ++--- .../detail/course-detail.component.html | 2 +- .../manage/detail/course-detail.component.ts | 5 +- ...course-management-detail-view-dto.model.ts | 7 -- .../core/StatisticsRepositoryTest.java | 3 +- 12 files changed, 87 insertions(+), 82 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java index a099be31fadb..e388047b7b4b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java @@ -221,7 +221,7 @@ SELECT COUNT(r) AND r.assessor IS NOT NULL AND p.exercise.id IN :exerciseIds """) - long countAssessmentsByExerciseIdsAndRated(@Param("exerciseIds") Set exerciseIds); + long countAssessmentsForExerciseIds(@Param("exerciseIds") Set exerciseIds); /** * Load a result from the database by its id together with the associated submission and the list of feedback items. @@ -556,7 +556,7 @@ default long countNumberOfFinishedAssessmentsForExercise(long exerciseId) { /** * Given a courseId, return the number of assessments for that course that have been completed (e.g. no draft!) * - * @param exerciseIds - the exercise ids of the course we are interested in + * @param exerciseIds - the exercise ids of the course we are interested in (should be filtered to only include exercises with manual assessment) * @return a number of assessments for the course */ default long countNumberOfAssessments(Set exerciseIds) { @@ -564,7 +564,7 @@ default long countNumberOfAssessments(Set exerciseIds) { if (exerciseIds == null || exerciseIds.isEmpty()) { return 0; } - return countAssessmentsByExerciseIdsAndRated(exerciseIds); + return countAssessmentsForExerciseIds(exerciseIds); } @Query(""" diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/LLMTokenUsageTraceRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/LLMTokenUsageTraceRepository.java index 4fed413a2bf2..59c023f88678 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/LLMTokenUsageTraceRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/LLMTokenUsageTraceRepository.java @@ -20,8 +20,7 @@ public interface LLMTokenUsageTraceRepository extends ArtemisJpaRepository mergeUsersPerTimeslotIntoList(Map, each StatisticsData containing a date and the amount of entries for one timeslot * @param result the list in which the converted outcome should be inserted */ - default void sortDataIntoHours(List outcome, List result) { + static void sortDataIntoHours(List outcome, List result) { for (StatisticsEntry entry : outcome) { int hourIndex = ((ZonedDateTime) entry.getDay()).getHour(); int amount = Math.toIntExact(entry.getAmount()); @@ -783,7 +783,7 @@ default void sortDataIntoHours(List outcome, List resu * @param result the list in which the converted outcome should be inserted * @param startDate the startDate of the result list */ - default void sortDataIntoDays(List outcome, List result, ZonedDateTime startDate) { + static void sortDataIntoDays(List outcome, List result, ZonedDateTime startDate) { for (StatisticsEntry entry : outcome) { ZonedDateTime date = (ZonedDateTime) entry.getDay(); int amount = Math.toIntExact(entry.getAmount()); @@ -802,7 +802,7 @@ default void sortDataIntoDays(List outcome, List resul * @param result the list in which the converted outcome should be inserted, should be initialized with enough values * @param startDate the startDate of the result list */ - default void sortDataIntoWeeks(List outcome, List result, ZonedDateTime startDate) { + static void sortDataIntoWeeks(List outcome, List result, ZonedDateTime startDate) { for (StatisticsEntry entry : outcome) { ZonedDateTime date = (ZonedDateTime) entry.getDay(); int amount = Math.toIntExact(entry.getAmount()); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/StatisticsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/StatisticsService.java index 7f2848d2d68a..d6626fd4b813 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/StatisticsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/StatisticsService.java @@ -1,6 +1,9 @@ package de.tum.cit.aet.artemis.core.service; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import static de.tum.cit.aet.artemis.core.repository.StatisticsRepository.sortDataIntoDays; +import static de.tum.cit.aet.artemis.core.repository.StatisticsRepository.sortDataIntoHours; +import static de.tum.cit.aet.artemis.core.repository.StatisticsRepository.sortDataIntoWeeks; import static de.tum.cit.aet.artemis.core.util.RoundingUtil.roundScoreSpecifiedByCourseSettings; import java.time.DayOfWeek; @@ -107,21 +110,21 @@ public List getChartData(SpanType span, Integer periodIndex, GraphType case DAY -> { startDate = now.minusDays(-periodIndex).withHour(0).withMinute(0).withSecond(0).withNano(0); endDate = now.minusDays(-periodIndex).withHour(23).withMinute(59).withSecond(59); - outcome = this.statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate, endDate, view, entityId); - this.statisticsRepository.sortDataIntoHours(outcome, result); + outcome = statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate, endDate, view, entityId); + sortDataIntoHours(outcome, result); } case WEEK -> { startDate = now.minusWeeks(-periodIndex).minusDays(6).withHour(0).withMinute(0).withSecond(0).withNano(0); endDate = now.minusWeeks(-periodIndex).withHour(23).withMinute(59).withSecond(59); - outcome = this.statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate, endDate, view, entityId); - this.statisticsRepository.sortDataIntoDays(outcome, result, startDate); + outcome = statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate, endDate, view, entityId); + sortDataIntoDays(outcome, result, startDate); } case MONTH -> { startDate = now.minusMonths(1L - periodIndex).withHour(0).withMinute(0).withSecond(0).withNano(0); endDate = now.minusMonths(-periodIndex).withHour(23).withMinute(59).withSecond(59); result = new ArrayList<>(Collections.nCopies((int) ChronoUnit.DAYS.between(startDate, endDate), 0)); - outcome = this.statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate.plusDays(1), endDate, view, entityId); - this.statisticsRepository.sortDataIntoDays(outcome, result, startDate.plusDays(1)); + outcome = statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate.plusDays(1), endDate, view, entityId); + sortDataIntoDays(outcome, result, startDate.plusDays(1)); } case QUARTER -> { LocalDateTime localStartDate = now.toLocalDateTime().with(DayOfWeek.MONDAY); @@ -130,15 +133,15 @@ public List getChartData(SpanType span, Integer periodIndex, GraphType startDate = localStartDate.atZone(zone).minusWeeks(11 + (12L * (-periodIndex))).withHour(0).withMinute(0).withSecond(0).withNano(0); endDate = periodIndex != 0 ? localEndDate.atZone(zone).minusWeeks(12L * (-periodIndex)).withHour(23).withMinute(59).withSecond(59) : localEndDate.atZone(zone).withHour(23).withMinute(59).withSecond(59); - outcome = this.statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate, endDate, view, entityId); - this.statisticsRepository.sortDataIntoWeeks(outcome, result, startDate); + outcome = statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate, endDate, view, entityId); + sortDataIntoWeeks(outcome, result, startDate); } case YEAR -> { startDate = now.minusYears(1L - periodIndex).plusMonths(1).withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0); lengthOfMonth = YearMonth.of(now.minusYears(-periodIndex).getYear(), now.minusYears(-periodIndex).getMonth()).lengthOfMonth(); endDate = now.minusYears(-periodIndex).withDayOfMonth(lengthOfMonth).withHour(23).withMinute(59).withSecond(59); - outcome = this.statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate, endDate, view, entityId); - this.statisticsRepository.sortDataIntoMonths(outcome, result, startDate); + outcome = statisticsRepository.getNumberOfEntriesPerTimeSlot(graphType, span, startDate, endDate, view, entityId); + statisticsRepository.sortDataIntoMonths(outcome, result, startDate); } } return result; diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java index 47ad83ccc8ca..d913ca5ce243 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java @@ -3,6 +3,8 @@ import static de.tum.cit.aet.artemis.assessment.domain.ComplaintType.COMPLAINT; import static de.tum.cit.aet.artemis.assessment.domain.ComplaintType.MORE_FEEDBACK; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import static de.tum.cit.aet.artemis.core.repository.StatisticsRepository.getWeekOfDate; +import static de.tum.cit.aet.artemis.core.repository.StatisticsRepository.sortDataIntoWeeks; import static de.tum.cit.aet.artemis.core.util.RoundingUtil.roundScoreSpecifiedByCourseSettings; import java.time.DayOfWeek; @@ -20,6 +22,8 @@ import java.util.Set; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -59,6 +63,8 @@ @Lazy public class CourseStatsService { + private static final Logger log = LoggerFactory.getLogger(CourseStatsService.class); + private final CourseRepository courseRepository; private final StatisticsRepository statisticsRepository; @@ -144,10 +150,13 @@ public List getActiveStudents(Set exerciseIds, long periodIndex, // avoid database call if there are no exercises to reduce performance issues return List.of(); } + long start = System.currentTimeMillis(); List outcome = courseRepository.getActiveStudents(exerciseIds, startDate, endDate); + log.info("courseRepository.getActiveStudents took {} ms for exercises with ids {} between start {} and end {}", System.currentTimeMillis() - start, exerciseIds, startDate, + endDate); List distinctOutcome = removeDuplicateActiveUserRows(outcome, startDate); List result = new ArrayList<>(Collections.nCopies(length, 0)); - statisticsRepository.sortDataIntoWeeks(distinctOutcome, result, startDate); + sortDataIntoWeeks(distinctOutcome, result, startDate); return result; } @@ -161,7 +170,7 @@ public List getActiveStudents(Set exerciseIds, long periodIndex, */ private List removeDuplicateActiveUserRows(List activeUserRows, ZonedDateTime startDate) { - int startIndex = statisticsRepository.getWeekOfDate(startDate); + int startIndex = getWeekOfDate(startDate); Map> usersByDate = new HashMap<>(); for (StatisticsEntry listElement : activeUserRows) { // listElement.date has the form "2021-05-04", to convert it to ZonedDateTime, it needs a time @@ -169,7 +178,7 @@ private List removeDuplicateActiveUserRows(List removeDuplicateActiveUserRows(List returnList = new ArrayList<>(); usersByDate.forEach((date, users) -> { - int year = date < statisticsRepository.getWeekOfDate(startDate) ? startDate.getYear() + 1 : startDate.getYear(); + int year = date < getWeekOfDate(startDate) ? startDate.getYear() + 1 : startDate.getYear(); ZonedDateTime firstDateOfYear = ZonedDateTime.of(year, 1, 1, 0, 0, 0, 0, startDate.getZone()); - ZonedDateTime start = statisticsRepository.getWeekOfDate(firstDateOfYear) == 1 ? firstDateOfYear.plusWeeks(date - 1) : firstDateOfYear.plusWeeks(date); + ZonedDateTime start = getWeekOfDate(firstDateOfYear) == 1 ? firstDateOfYear.plusWeeks(date - 1) : firstDateOfYear.plusWeeks(date); StatisticsEntry listElement = new StatisticsEntry(start, users.size()); returnList.add(listElement); }); @@ -201,18 +210,20 @@ private List removeDuplicateActiveUserRows(List exercises = exerciseRepository.findAllExercisesByCourseId(course.getId()); - if (exercises == null || exercises.isEmpty()) { - return new CourseManagementDetailViewDTO(numberOfStudentsInCourse, numberOfTeachingAssistantsInCourse, numberOfEditorsInCourse, numberOfInstructorsInCourse, 0.0, 0L, - 0L, 0.0, 0L, 0L, 0.0, 0L, 0L, 0.0, 0.0, 0.0, List.of(), 0.0); + Set courseExercises = exerciseRepository.findAllExercisesByCourseId(course.getId()); + Set courseExerciseIdsWithManualAssessments = exerciseRepository.findExerciseIdsWithManualAssessmentByCourseId(course.getId()); + if (courseExercises == null || courseExercises.isEmpty()) { + return new CourseManagementDetailViewDTO(0, 0, 0, 0, 0.0, 0L, 0L, 0.0, 0L, 0L, 0.0, 0L, 0L, 0.0, 0.0, 0.0, List.of(), 0.0); } // For the average score we need to only consider scores which are included completely or as bonus - Set includedExercises = exercises.stream().filter(Exercise::isCourseExercise) + Set includedExercises = courseExercises.stream().filter(Exercise::isCourseExercise) .filter(exercise -> !exercise.getIncludedInOverallScore().equals(IncludedInOverallScore.NOT_INCLUDED)).collect(Collectors.toSet()); Double averageScoreForCourse = participantScoreRepository.findAvgRatedScore(includedExercises); averageScoreForCourse = averageScoreForCourse != null ? averageScoreForCourse : 0.0; @@ -229,40 +240,51 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin + (100.0 - gradingScale.getPresentationsWeight()) / 100.0 * averageScoreForCourse; } - Set exerciseIds = exercises.stream().map(Exercise::getId).collect(Collectors.toSet()); + Set exerciseIds = courseExercises.stream().map(Exercise::getId).collect(Collectors.toSet()); - var endDate = this.determineEndDateForActiveStudents(course); - var spanSize = this.determineTimeSpanSizeForActiveStudents(course, endDate, 17); - var activeStudents = getActiveStudents(exerciseIds, 0, spanSize, endDate); + // var endDate = this.determineEndDateForActiveStudents(course); + // var spanSize = this.determineTimeSpanSizeForActiveStudents(course, endDate, 17); + // var activeStudents = getActiveStudents(exerciseIds, 0, spanSize, endDate); - long numberOfAssessments = resultRepository.countNumberOfAssessments(exerciseIds); + long numberOfAssessments = resultRepository.countNumberOfAssessments(courseExerciseIdsWithManualAssessments); long numberOfInTimeSubmissions = submissionRepository.countAllByExerciseIdsSubmittedBeforeDueDate(exerciseIds) + programmingExerciseRepository.countAllSubmissionsByExerciseIdsSubmitted(exerciseIds); long numberOfLateSubmissions = submissionRepository.countAllByExerciseIdsSubmittedAfterDueDate(exerciseIds); long numberOfSubmissions = numberOfInTimeSubmissions + numberOfLateSubmissions; + // TODO: this number can be wrong in the client (over 100%) var currentPercentageAssessments = calculatePercentage(numberOfAssessments, numberOfSubmissions); - long currentAbsoluteComplaints = complaintResponseRepository - .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), COMPLAINT); - long currentMaxComplaints = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), COMPLAINT); - var currentPercentageComplaints = calculatePercentage(currentAbsoluteComplaints, currentMaxComplaints); + long currentAbsoluteComplaints = 0; + long currentMaxComplaints = 0; + double currentPercentageComplaints = 0.0; - long currentAbsoluteMoreFeedbacks = complaintResponseRepository - .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), MORE_FEEDBACK); - long currentMaxMoreFeedbacks = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), MORE_FEEDBACK); - var currentPercentageMoreFeedbacks = calculatePercentage(currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks); + if (course.getComplaintsEnabled()) { + currentAbsoluteComplaints = complaintResponseRepository + .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), COMPLAINT); + currentMaxComplaints = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), COMPLAINT); + currentPercentageComplaints = calculatePercentage(currentAbsoluteComplaints, currentMaxComplaints); + } + + long currentAbsoluteMoreFeedbacks = 0; + long currentMaxMoreFeedbacks = 0; + double currentPercentageMoreFeedbacks = 0; + if (course.getRequestMoreFeedbackEnabled()) { + currentAbsoluteMoreFeedbacks = complaintResponseRepository + .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), MORE_FEEDBACK); + currentMaxMoreFeedbacks = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), MORE_FEEDBACK); + currentPercentageMoreFeedbacks = calculatePercentage(currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks); + } var currentAbsoluteAverageScore = roundScoreSpecifiedByCourseSettings((averageScoreForCourse / 100.0) * currentMaxAverageScore, course); var currentPercentageAverageScore = currentMaxAverageScore > 0.0 ? roundScoreSpecifiedByCourseSettings(averageScoreForCourse, course) : 0.0; double currentTotalLlmCostInEur = llmTokenUsageTraceRepository.calculateTotalLlmCostInEurForCourse(course.getId()); - return new CourseManagementDetailViewDTO(numberOfStudentsInCourse, numberOfTeachingAssistantsInCourse, numberOfEditorsInCourse, numberOfInstructorsInCourse, - currentPercentageAssessments, numberOfAssessments, numberOfSubmissions, currentPercentageComplaints, currentAbsoluteComplaints, currentMaxComplaints, - currentPercentageMoreFeedbacks, currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks, currentPercentageAverageScore, currentAbsoluteAverageScore, - currentMaxAverageScore, activeStudents, currentTotalLlmCostInEur); + return new CourseManagementDetailViewDTO(0, 0, 0, 0, currentPercentageAssessments, numberOfAssessments, numberOfSubmissions, currentPercentageComplaints, + currentAbsoluteComplaints, currentMaxComplaints, currentPercentageMoreFeedbacks, currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks, + currentPercentageAverageScore, currentAbsoluteAverageScore, currentMaxAverageScore, List.of(), currentTotalLlmCostInEur); } private double calculatePercentage(double positive, double total) { diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java index b114a0f1d8c4..93db750fe3d0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java @@ -182,9 +182,9 @@ public ResponseEntity> getActiveStudentsForCourseDetailView(@PathV @GetMapping("courses/{courseId}/statistics-lifetime-overview") @EnforceAtLeastTutor public ResponseEntity> getActiveStudentsForCourseLiveTime(@PathVariable Long courseId) { - authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.TEACHING_ASSISTANT, courseRepository.findByIdElseThrow(courseId), null); - var exerciseIds = exerciseRepository.findExerciseIdsByCourseId(courseId); var course = courseRepository.findByIdElseThrow(courseId); + authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.TEACHING_ASSISTANT, course, null); + var exerciseIds = exerciseRepository.findExerciseIdsByCourseId(courseId); if (course.getStartDate() == null) { throw new IllegalArgumentException("Course does not contain start date"); } diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamService.java b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamService.java index e3eb79136f3b..2dd11ccb0a9c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamService.java @@ -991,7 +991,7 @@ public void validateForStudentExamGeneration(Exam exam) throws BadRequestAlertEx * @return a examStatisticsDTO filled with all statistics regarding the exam */ public ExamChecklistDTO getStatsForChecklist(Exam exam, boolean isInstructor) { - log.info("getStatsForChecklist invoked for exam {}", exam.getId()); + log.debug("getStatsForChecklist invoked for exam {}", exam.getId()); int numberOfCorrectionRoundsInExam = exam.getNumberOfCorrectionRoundsInExam(); long start = System.nanoTime(); diff --git a/src/main/webapp/app/core/course/manage/detail/course-detail-line-chart.component.ts b/src/main/webapp/app/core/course/manage/detail/course-detail-line-chart.component.ts index 459ce5067a5e..c85e85425c77 100644 --- a/src/main/webapp/app/core/course/manage/detail/course-detail-line-chart.component.ts +++ b/src/main/webapp/app/core/course/manage/detail/course-detail-line-chart.component.ts @@ -30,15 +30,11 @@ export enum SwitchTimeSpanDirection { imports: [RouterLink, TranslateDirective, HelpIconComponent, NgbTooltip, FaIconComponent, LineChartModule, ArtemisDatePipe, ArtemisTranslatePipe], }) export class CourseDetailLineChartComponent extends ActiveStudentsChart implements OnChanges { - private service = inject(CourseManagementService); + private courseManagementService = inject(CourseManagementService); private translateService = inject(TranslateService); - @Input() - course: Course; - @Input() - numberOfStudentsInCourse: number; - @Input() - initialStats: number[] | undefined; + @Input() course: Course; + @Input() numberOfStudentsInCourse: number; loading = true; displayedNumberOfWeeks: number = 8; @@ -110,7 +106,7 @@ export class CourseDetailLineChartComponent extends ActiveStudentsChart implemen private reloadChart() { this.loading = true; this.createLabels(); - this.service.getStatisticsData(this.course.id!, this.currentPeriod, this.displayedNumberOfWeeks).subscribe((res: number[]) => { + this.courseManagementService.getStatisticsData(this.course.id!, this.currentPeriod, this.displayedNumberOfWeeks).subscribe((res: number[]) => { this.processDataAndCreateChart(res); this.data = [...this.dataCopy]; }); @@ -201,13 +197,7 @@ export class CourseDetailLineChartComponent extends ActiveStudentsChart implemen this.showLifetimeOverview = false; this.loading = true; this.createLabels(); - - if (!this.initialStats) { - this.reloadChart(); - } else { - this.processDataAndCreateChart(this.initialStats.slice(Math.max(this.initialStats.length - this.displayedNumberOfWeeks, 0))); - this.data = [...this.dataCopy]; - } + this.reloadChart(); } /** @@ -256,7 +246,7 @@ export class CourseDetailLineChartComponent extends ActiveStudentsChart implemen * Fetches and caches the data for the lifetime overview from the server and creates the chart */ private fetchLifetimeOverviewData(): void { - this.service.getStatisticsForLifetimeOverview(this.course.id!).subscribe((res: number[]) => { + this.courseManagementService.getStatisticsForLifetimeOverview(this.course.id!).subscribe((res: number[]) => { this.overviewStats = res; this.processDataAndCreateChart(this.overviewStats); this.data = [...this.dataCopy]; diff --git a/src/main/webapp/app/core/course/manage/detail/course-detail.component.html b/src/main/webapp/app/core/course/manage/detail/course-detail.component.html index f39bac4c5701..a4efe8e583bb 100644 --- a/src/main/webapp/app/core/course/manage/detail/course-detail.component.html +++ b/src/main/webapp/app/core/course/manage/detail/course-detail.component.html @@ -56,7 +56,7 @@ } - + } diff --git a/src/main/webapp/app/core/course/manage/detail/course-detail.component.ts b/src/main/webapp/app/core/course/manage/detail/course-detail.component.ts index 0c5b158c2dbe..042b0265d711 100644 --- a/src/main/webapp/app/core/course/manage/detail/course-detail.component.ts +++ b/src/main/webapp/app/core/course/manage/detail/course-detail.component.ts @@ -10,7 +10,7 @@ import { CourseManagementDetailViewDto } from 'app/core/course/shared/entities/c import { onError } from 'app/shared/util/global.utils'; import { AlertService } from 'app/shared/service/alert.service'; import { EventManager } from 'app/shared/service/event-manager.service'; -import { faChalkboardUser, faChartBar, faClipboard, faEye, faFlag, faGraduationCap, faListAlt, faQuestion, faTable, faTimes, faWrench } from '@fortawesome/free-solid-svg-icons'; +import { faChartBar, faClipboard, faEye, faFlag, faGraduationCap, faListAlt, faQuestion, faTable, faTimes, faWrench } from '@fortawesome/free-solid-svg-icons'; import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service'; import { OrganizationManagementService } from 'app/core/admin/organization-management/organization-management.service'; import { IrisSettingsService } from 'app/iris/manage/settings/shared/iris-settings.service'; @@ -55,7 +55,6 @@ export class CourseDetailComponent implements OnInit, OnDestroy { protected readonly faChartBar = faChartBar; protected readonly faClipboard = faClipboard; protected readonly faGraduationCap = faGraduationCap; - protected readonly faChalkboardUser = faChalkboardUser; protected readonly faQuestion = faQuestion; private eventManager = inject(EventManager); @@ -69,7 +68,6 @@ export class CourseDetailComponent implements OnInit, OnDestroy { private markdownService = inject(ArtemisMarkdownService); courseDTO: CourseManagementDetailViewDto; - activeStudents?: number[]; course: Course; courseDetailSections: DetailOverviewSection[]; @@ -325,7 +323,6 @@ export class CourseDetailComponent implements OnInit, OnDestroy { this.courseManagementService.getCourseStatisticsForDetailView(courseId).subscribe({ next: (courseResponse: HttpResponse) => { this.courseDTO = courseResponse.body!; - this.activeStudents = courseResponse.body!.activeStudents; }, error: (error: HttpErrorResponse) => onError(this.alertService, error), }); diff --git a/src/main/webapp/app/core/course/shared/entities/course-management-detail-view-dto.model.ts b/src/main/webapp/app/core/course/shared/entities/course-management-detail-view-dto.model.ts index 4e8983fe099f..b0a0bc475092 100644 --- a/src/main/webapp/app/core/course/shared/entities/course-management-detail-view-dto.model.ts +++ b/src/main/webapp/app/core/course/shared/entities/course-management-detail-view-dto.model.ts @@ -1,9 +1,4 @@ export class CourseManagementDetailViewDto { - numberOfStudentsInCourse: number; - numberOfTeachingAssistantsInCourse: number; - numberOfEditorsInCourse: number; - numberOfInstructorsInCourse: number; - // Total Assessment currentPercentageAssessments: number; currentAbsoluteAssessments: number; @@ -24,8 +19,6 @@ export class CourseManagementDetailViewDto { currentAbsoluteAverageScore: number; currentMaxAverageScore: number; - activeStudents?: number[]; - // LLM Stats currentTotalLlmCostInEur: number; } diff --git a/src/test/java/de/tum/cit/aet/artemis/core/StatisticsRepositoryTest.java b/src/test/java/de/tum/cit/aet/artemis/core/StatisticsRepositoryTest.java index 3fea64257510..9b5ec7c4414d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/StatisticsRepositoryTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/StatisticsRepositoryTest.java @@ -1,5 +1,6 @@ package de.tum.cit.aet.artemis.core; +import static de.tum.cit.aet.artemis.core.repository.StatisticsRepository.sortDataIntoWeeks; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -142,7 +143,7 @@ void testSortDataIntoWeeks_differentYear() { expectedResultYear.add(i != 15 ? 0 : 123); } - statisticsRepository.sortDataIntoWeeks(outcome, resultYear, startDate); + sortDataIntoWeeks(outcome, resultYear, startDate); assertThat(resultYear).as("Bucket 15 now has value for the entry date (123)").isEqualTo(expectedResultYear); } From e46a314963bdb424329c35f50a6a8f00934ebf64 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Tue, 26 Aug 2025 19:57:33 +0200 Subject: [PATCH 03/30] cleanup and add logging --- .../dto/CourseManagementDetailViewDTO.java | 3 +- .../aet/artemis/core/dto/StatisticsEntry.java | 6 ++ .../service/course/CourseStatsService.java | 59 +++++++++++++------ 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseManagementDetailViewDTO.java b/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseManagementDetailViewDTO.java index 615584ee8b9c..be2d70faee4f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseManagementDetailViewDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseManagementDetailViewDTO.java @@ -5,8 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record CourseManagementDetailViewDTO(Integer numberOfStudentsInCourse, Integer numberOfTeachingAssistantsInCourse, Integer numberOfEditorsInCourse, - Integer numberOfInstructorsInCourse, Double currentPercentageAssessments, Long currentAbsoluteAssessments, Long currentMaxAssessments, Double currentPercentageComplaints, +public record CourseManagementDetailViewDTO(Double currentPercentageAssessments, Long currentAbsoluteAssessments, Long currentMaxAssessments, Double currentPercentageComplaints, Long currentAbsoluteComplaints, Long currentMaxComplaints, Double currentPercentageMoreFeedbacks, Long currentAbsoluteMoreFeedbacks, Long currentMaxMoreFeedbacks, Double currentPercentageAverageScore, Double currentAbsoluteAverageScore, Double currentMaxAverageScore, List activeStudents, Double currentTotalLlmCostInEur) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/StatisticsEntry.java b/src/main/java/de/tum/cit/aet/artemis/core/dto/StatisticsEntry.java index e57959d0cce9..3783db534bb4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/dto/StatisticsEntry.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/dto/StatisticsEntry.java @@ -37,6 +37,12 @@ public StatisticsEntry(String date, String username) { this.username = username; } + public StatisticsEntry(int year, int month, int day, String username) { + this.date = String.format("%04d-%02d-%02d", year, month, day); + this.amount = 1L; + this.username = username; + } + // empty constructor public StatisticsEntry() { this.amount = 0L; diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java index d913ca5ce243..931616ecff07 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java @@ -210,22 +210,22 @@ private List removeDuplicateActiveUserRows(List courseExercises = exerciseRepository.findAllExercisesByCourseId(course.getId()); + log.debug("exerciseRepository.findAllExercisesByCourseId took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); + start = System.currentTimeMillis(); Set courseExerciseIdsWithManualAssessments = exerciseRepository.findExerciseIdsWithManualAssessmentByCourseId(course.getId()); + log.debug("exerciseRepository.findExerciseIdsWithManualAssessmentsByCourseId took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); if (courseExercises == null || courseExercises.isEmpty()) { - return new CourseManagementDetailViewDTO(0, 0, 0, 0, 0.0, 0L, 0L, 0.0, 0L, 0L, 0.0, 0L, 0L, 0.0, 0.0, 0.0, List.of(), 0.0); + return new CourseManagementDetailViewDTO(0.0, 0L, 0L, 0.0, 0L, 0L, 0.0, 0L, 0L, 0.0, 0.0, 0.0, List.of(), 0.0); } // For the average score we need to only consider scores which are included completely or as bonus Set includedExercises = courseExercises.stream().filter(Exercise::isCourseExercise) .filter(exercise -> !exercise.getIncludedInOverallScore().equals(IncludedInOverallScore.NOT_INCLUDED)).collect(Collectors.toSet()); + start = System.currentTimeMillis(); Double averageScoreForCourse = participantScoreRepository.findAvgRatedScore(includedExercises); + log.debug("participantScoreRepository.findAvgRatedScore took {} ms for exercises with ids {}", System.currentTimeMillis() - start, + includedExercises.stream().map(Exercise::getId).collect(Collectors.toSet())); averageScoreForCourse = averageScoreForCourse != null ? averageScoreForCourse : 0.0; double currentMaxAverageScore = includedExercises.stream().map(Exercise::getMaxPoints).mapToDouble(Double::doubleValue).sum(); @@ -233,24 +233,30 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin if (gradingScale != null && gradingScale.getCourse().equals(course) && gradingScale.getPresentationsNumber() != null && gradingScale.getPresentationsWeight() != null) { double maxBaseScore = includedExercises.stream().filter(e -> !e.getIncludedInOverallScore().equals(IncludedInOverallScore.INCLUDED_AS_BONUS)) .map(Exercise::getMaxPoints).mapToDouble(Double::doubleValue).sum(); + start = System.currentTimeMillis(); currentMaxAverageScore += presentationPointsCalculationService.calculateReachablePresentationPoints(gradingScale, maxBaseScore); - + log.debug("presentationPointsCalculationService.calculateReachablePresentationPoints took {} ms for grading scale with id {}", System.currentTimeMillis() - start, + gradingScale.getId()); + start = System.currentTimeMillis(); double avgPresentationScore = studentParticipationRepository.getAvgPresentationScoreByCourseId(course.getId()); + log.debug("studentParticipationRepository.getAvgPresentationScoreByCourseId took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); averageScoreForCourse = gradingScale.getPresentationsWeight() / 100.0 * avgPresentationScore + (100.0 - gradingScale.getPresentationsWeight()) / 100.0 * averageScoreForCourse; } Set exerciseIds = courseExercises.stream().map(Exercise::getId).collect(Collectors.toSet()); - - // var endDate = this.determineEndDateForActiveStudents(course); - // var spanSize = this.determineTimeSpanSizeForActiveStudents(course, endDate, 17); - // var activeStudents = getActiveStudents(exerciseIds, 0, spanSize, endDate); - + start = System.currentTimeMillis(); long numberOfAssessments = resultRepository.countNumberOfAssessments(courseExerciseIdsWithManualAssessments); - + log.debug("resultRepository.countNumberOfAssessments took {} ms for exercises with ids {}", System.currentTimeMillis() - start, courseExerciseIdsWithManualAssessments); + start = System.currentTimeMillis(); long numberOfInTimeSubmissions = submissionRepository.countAllByExerciseIdsSubmittedBeforeDueDate(exerciseIds) + programmingExerciseRepository.countAllSubmissionsByExerciseIdsSubmitted(exerciseIds); + log.debug( + "submissionRepository.countAllByExerciseIdsSubmittedBeforeDueDate and programmingExerciseRepository.countAllSubmissionsByExerciseIdsSubmitted took {} ms for exercises with ids {}", + System.currentTimeMillis() - start, exerciseIds); + start = System.currentTimeMillis(); long numberOfLateSubmissions = submissionRepository.countAllByExerciseIdsSubmittedAfterDueDate(exerciseIds); + log.debug("submissionRepository.countAllByExerciseIdsSubmittedAfterDueDate took {} ms for exercises with ids {}", System.currentTimeMillis() - start, exerciseIds); long numberOfSubmissions = numberOfInTimeSubmissions + numberOfLateSubmissions; // TODO: this number can be wrong in the client (over 100%) @@ -261,9 +267,16 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin double currentPercentageComplaints = 0.0; if (course.getComplaintsEnabled()) { + start = System.currentTimeMillis(); currentAbsoluteComplaints = complaintResponseRepository .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), COMPLAINT); + log.debug( + "complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull took {} ms for course with id {}", + System.currentTimeMillis() - start, course.getId()); + start = System.currentTimeMillis(); currentMaxComplaints = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), COMPLAINT); + log.debug("complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType took {} ms for course with id {}", + System.currentTimeMillis() - start, course.getId()); currentPercentageComplaints = calculatePercentage(currentAbsoluteComplaints, currentMaxComplaints); } @@ -272,19 +285,27 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin double currentPercentageMoreFeedbacks = 0; if (course.getRequestMoreFeedbackEnabled()) { + start = System.currentTimeMillis(); currentAbsoluteMoreFeedbacks = complaintResponseRepository .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), MORE_FEEDBACK); + log.debug( + "complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull took {} ms for course with id {}", + System.currentTimeMillis() - start, course.getId()); + start = System.currentTimeMillis(); currentMaxMoreFeedbacks = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), MORE_FEEDBACK); + log.debug("complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType took {} ms for course with id {}", + System.currentTimeMillis() - start, course.getId()); currentPercentageMoreFeedbacks = calculatePercentage(currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks); } var currentAbsoluteAverageScore = roundScoreSpecifiedByCourseSettings((averageScoreForCourse / 100.0) * currentMaxAverageScore, course); var currentPercentageAverageScore = currentMaxAverageScore > 0.0 ? roundScoreSpecifiedByCourseSettings(averageScoreForCourse, course) : 0.0; - + start = System.currentTimeMillis(); double currentTotalLlmCostInEur = llmTokenUsageTraceRepository.calculateTotalLlmCostInEurForCourse(course.getId()); + log.debug("llmTokenUsageTraceRepository.calculateTotalLlmCostInEurForCourse took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); - return new CourseManagementDetailViewDTO(0, 0, 0, 0, currentPercentageAssessments, numberOfAssessments, numberOfSubmissions, currentPercentageComplaints, - currentAbsoluteComplaints, currentMaxComplaints, currentPercentageMoreFeedbacks, currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks, - currentPercentageAverageScore, currentAbsoluteAverageScore, currentMaxAverageScore, List.of(), currentTotalLlmCostInEur); + return new CourseManagementDetailViewDTO(currentPercentageAssessments, numberOfAssessments, numberOfSubmissions, currentPercentageComplaints, currentAbsoluteComplaints, + currentMaxComplaints, currentPercentageMoreFeedbacks, currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks, currentPercentageAverageScore, + currentAbsoluteAverageScore, currentMaxAverageScore, List.of(), currentTotalLlmCostInEur); } private double calculatePercentage(double positive, double total) { From 037fdf6e2f2429b40d79fab93505c5b1b25bf801 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Tue, 26 Aug 2025 19:57:50 +0200 Subject: [PATCH 04/30] speedup active students query --- .../core/repository/CourseRepository.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/CourseRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/CourseRepository.java index b47fdc0a9515..68cd0ea2ffa6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/CourseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/CourseRepository.java @@ -229,16 +229,18 @@ WHERE TYPE(e) = QuizExercise * @return A list with a map for every submission containing date and the username */ @Query(""" - SELECT new de.tum.cit.aet.artemis.core.dto.StatisticsEntry( - SUBSTRING(CAST(s.submissionDate AS string), 1, 10), - p.student.login - ) - FROM StudentParticipation p + SELECT NEW de.tum.cit.aet.artemis.core.dto.StatisticsEntry( + YEAR(s.submissionDate), + MONTH(s.submissionDate), + DAY(s.submissionDate), + p.student.login + ) + FROM StudentParticipation p JOIN p.submissions s - WHERE p.exercise.id IN :exerciseIds - AND s.submissionDate >= :startDate - AND s.submissionDate <= :endDate - GROUP BY SUBSTRING(CAST(s.submissionDate AS string), 1, 10), p.student.login + WHERE p.exercise.id in :exerciseIds + AND s.submissionDate >= :startDate + AND s.submissionDate < :endDate + GROUP BY YEAR(s.submissionDate), MONTH(s.submissionDate), DAY(s.submissionDate), p.student.login """) List getActiveStudents(@Param("exerciseIds") Set exerciseIds, @Param("startDate") ZonedDateTime startDate, @Param("endDate") ZonedDateTime endDate); From 0072bd81cc5b50d84184dc857caa13c9f16ddd4a Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Mon, 1 Sep 2025 23:13:14 +0200 Subject: [PATCH 05/30] fix bug assessments over 100% --- .../repository/ResultRepository.java | 18 ++++++++++++++++++ .../service/course/CourseStatsService.java | 6 ++++-- .../repository/SubmissionRepository.java | 1 + .../ProgrammingExerciseRepository.java | 14 ++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java index e388047b7b4b..b5f6249c3bdd 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java @@ -219,6 +219,8 @@ SELECT COUNT(r) JOIN s.participation p WHERE r.completionDate IS NOT NULL AND r.assessor IS NOT NULL + AND p.testRun = false + AND r.rated = true AND p.exercise.id IN :exerciseIds """) long countAssessmentsForExerciseIds(@Param("exerciseIds") Set exerciseIds); @@ -275,6 +277,22 @@ SELECT COUNT(DISTINCT p) """) long countNumberOfFinishedAssessmentsForExerciseIgnoreTestRuns(@Param("exerciseId") long exerciseId); + @Query(""" + SELECT COUNT(DISTINCT p) + FROM StudentParticipation p + JOIN p.submissions s + JOIN s.results r + JOIN p.exercise e + WHERE e.id IN :exerciseIds + AND p.testRun = FALSE + AND r.assessor IS NOT NULL + AND r.rated = TRUE + AND r.submission.submitted = TRUE + AND r.completionDate IS NOT NULL + AND (e.dueDate IS NULL OR r.submission.submissionDate <= e.dueDate) + """) + long countNumberOfFinishedAssessmentsForExerciseIdsIgnoreTestRuns(@Param("exerciseIds") Set exerciseIds); + /** * @param exerciseId id of exercise * @return a list that contains the count of manual assessments for each studentParticipation of the exercise diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java index 931616ecff07..6347b52a6286 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java @@ -246,11 +246,13 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin Set exerciseIds = courseExercises.stream().map(Exercise::getId).collect(Collectors.toSet()); start = System.currentTimeMillis(); - long numberOfAssessments = resultRepository.countNumberOfAssessments(courseExerciseIdsWithManualAssessments); + long numberOfAssessments = resultRepository.countNumberOfFinishedAssessmentsForExerciseIdsIgnoreTestRuns(exerciseIds); + log.error("number of assessments: {}", numberOfAssessments); log.debug("resultRepository.countNumberOfAssessments took {} ms for exercises with ids {}", System.currentTimeMillis() - start, courseExerciseIdsWithManualAssessments); start = System.currentTimeMillis(); long numberOfInTimeSubmissions = submissionRepository.countAllByExerciseIdsSubmittedBeforeDueDate(exerciseIds) + programmingExerciseRepository.countAllSubmissionsByExerciseIdsSubmitted(exerciseIds); + log.error("number of in time submissions: {}", numberOfInTimeSubmissions); log.debug( "submissionRepository.countAllByExerciseIdsSubmittedBeforeDueDate and programmingExerciseRepository.countAllSubmissionsByExerciseIdsSubmitted took {} ms for exercises with ids {}", System.currentTimeMillis() - start, exerciseIds); @@ -259,7 +261,7 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin log.debug("submissionRepository.countAllByExerciseIdsSubmittedAfterDueDate took {} ms for exercises with ids {}", System.currentTimeMillis() - start, exerciseIds); long numberOfSubmissions = numberOfInTimeSubmissions + numberOfLateSubmissions; - // TODO: this number can be wrong in the client (over 100%) + log.error("number of late submissions: {}, total number of submissions: {}", numberOfLateSubmissions, numberOfSubmissions); var currentPercentageAssessments = calculatePercentage(numberOfAssessments, numberOfSubmissions); long currentAbsoluteComplaints = 0; diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/repository/SubmissionRepository.java b/src/main/java/de/tum/cit/aet/artemis/exercise/repository/SubmissionRepository.java index a90c2aba1ee7..edec03bad8a7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/repository/SubmissionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/repository/SubmissionRepository.java @@ -253,6 +253,7 @@ SELECT COUNT(DISTINCT s) WHERE TYPE(s) IN (ModelingSubmission, TextSubmission, FileUploadSubmission) AND e.id IN :exerciseIds AND s.submitted = TRUE + AND p.testRun = FALSE AND (s.submissionDate <= e.dueDate OR e.dueDate IS NULL) """) long countAllByExerciseIdsSubmittedBeforeDueDate(@Param("exerciseIds") Set exerciseIds); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java index a58c179c7071..31b7ab9ab20b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java @@ -410,6 +410,19 @@ SELECT COUNT (DISTINCT p) """) long countAssessmentsByExerciseIdSubmittedIgnoreTestRunSubmissions(@Param("exerciseId") long exerciseId); + @Query(""" + SELECT COUNT (DISTINCT p) + FROM ProgrammingExerciseStudentParticipation p + LEFT JOIN p.submissions s + LEFT JOIN s.results r + WHERE p.exercise.id IN :exerciseIds + AND p.testRun = FALSE + AND r.submission.submitted = TRUE + AND r.assessor IS NOT NULL + AND r.completionDate IS NOT NULL + """) + long countAssessmentsByExerciseIdsSubmittedIgnoreTestRunSubmissions(@Param("exerciseIds") Set exerciseIds); + /** * In distinction to other exercise types, students can have multiple submissions in a programming exercise. * We therefore have to check here if any submission of the student was submitted before the due date. @@ -441,6 +454,7 @@ SELECT COUNT (DISTINCT p) JOIN p.submissions s WHERE p.exercise.assessmentType <> de.tum.cit.aet.artemis.assessment.domain.AssessmentType.AUTOMATIC AND p.exercise.id IN :exerciseIds + AND p.testRun = FALSE AND s.submitted = TRUE """) long countAllSubmissionsByExerciseIdsSubmitted(@Param("exerciseIds") Set exerciseIds); From 53efabc3de8ac7e831be0fb1b866ffac9d2d802b Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Tue, 2 Sep 2025 11:12:08 +0200 Subject: [PATCH 06/30] add exercise id to result --- .../aet/artemis/assessment/domain/Result.java | 12 +++++ .../ComplaintResponseRepository.java | 14 +++--- .../repository/ResultRepository.java | 26 +++++++++++ .../config/migration/MigrationRegistry.java | 3 ++ .../ResultExerciseIdMigrationEntry.java | 45 +++++++++++++++++++ .../exercise/service/SubmissionService.java | 10 +++++ .../ModelingExerciseFeedbackService.java | 3 ++ .../quiz/service/QuizResultService.java | 1 + .../quiz/service/QuizSubmissionService.java | 2 + .../service/TextExerciseFeedbackService.java | 1 + .../resources/config/liquibase/master.xml | 1 + 11 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java index b66ad62904fc..12ab7dbed589 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java @@ -134,6 +134,10 @@ public class Result extends DomainObject implements Comparable { @JsonIgnore private Instant lastModifiedDate; + @Column(name = "exercise_id") + @JsonIgnore + private Long exerciseId; + public ZonedDateTime getCompletionDate() { return completionDate; } @@ -173,6 +177,14 @@ public Instant getLastModifiedDate() { return lastModifiedDate; } + public Long getExerciseId() { + return exerciseId; + } + + public void setExerciseId(Long exerciseId) { + this.exerciseId = exerciseId; + } + /** * Sets the score to the specified score rounded to 4 decimal places. * If you are handling student results that potentially need rounding, use {@link Result#setScore(Double score, Course course)} instead! diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java index 13122c77a4a0..f4be2cb2856e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java @@ -55,7 +55,7 @@ long countByComplaint_Result_Submission_Participation_Exercise_ExerciseGroup_Exa @Query(""" SELECT COUNT(DISTINCT cr) FROM ComplaintResponse cr - WHERE cr.complaint.result.submission.participation.exercise.id = :exerciseId + WHERE cr.complaint.result.exerciseId = :exerciseId AND cr.complaint.complaintType = :complaintType AND cr.submittedTime IS NOT NULL """) @@ -70,15 +70,15 @@ SELECT COUNT(DISTINCT cr) */ @Query(""" SELECT new de.tum.cit.aet.artemis.assessment.dto.dashboard.ExerciseMapEntryDTO( - cr.complaint.result.submission.participation.exercise.id, + cr.complaint.result.exerciseId, COUNT(DISTINCT cr) ) FROM ComplaintResponse cr - WHERE cr.complaint.result.submission.participation.exercise.id IN :exerciseIds + WHERE cr.complaint.result.exerciseId IN :exerciseIds AND cr.submittedTime IS NOT NULL AND cr.complaint.complaintType = :complaintType AND cr.complaint.result.submission.participation.testRun = FALSE - GROUP BY cr.complaint.result.submission.participation.exercise.id + GROUP BY cr.complaint.result.exerciseId """) List countComplaintsByExerciseIdsAndComplaintComplaintTypeIgnoreTestRuns(@Param("exerciseIds") Set exerciseIds, @Param("complaintType") ComplaintType complaintType); @@ -92,14 +92,14 @@ List countComplaintsByExerciseIdsAndComplaintComplaintTypeI */ @Query(""" SELECT new de.tum.cit.aet.artemis.assessment.dto.dashboard.ExerciseMapEntryDTO( - cr.complaint.result.submission.participation.exercise.id, + cr.complaint.result.exerciseId, COUNT(DISTINCT cr) ) FROM ComplaintResponse cr - WHERE cr.complaint.result.submission.participation.exercise.id IN :exerciseIds + WHERE cr.complaint.result.exerciseId IN :exerciseIds AND cr.submittedTime IS NOT NULL AND cr.complaint.complaintType = :complaintType - GROUP BY cr.complaint.result.submission.participation.exercise.id + GROUP BY cr.complaint.result.exerciseId """) List countComplaintsByExerciseIdsAndComplaintComplaintType(@Param("exerciseIds") Set exerciseIds, @Param("complaintType") ComplaintType complaintType); diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java index b5f6249c3bdd..c059ff0933fb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java @@ -17,10 +17,14 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.EntityGraph; +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.assessment.domain.AssessmentType; import de.tum.cit.aet.artemis.assessment.domain.ExampleSubmission; @@ -908,4 +912,26 @@ SELECT MAX(r2.id) """) Optional findLatestResultWithFeedbacksBySubmissionId(@Param("submissionId") long submissionId, @Param("dateTime") ZonedDateTime dateTime); + @Transactional + @Modifying + @Query(""" + UPDATE Result r + SET r.exerciseId = ( + SELECT p.exercise.id + FROM Submission s + JOIN s.participation p + WHERE s.id = r.submission.id + ) + WHERE r.exerciseId IS NULL + AND r.id IN :ids + """) + int backfillExerciseIdBatch(@Param("ids") List ids); + + @Query(""" + SELECT r.id + FROM Result r + WHERE r.exerciseId IS NULL + """) + Slice findResultIdsWithoutExerciseId(Pageable pageable); + } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/MigrationRegistry.java b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/MigrationRegistry.java index 2a805fd79776..059eaee64f1b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/MigrationRegistry.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/MigrationRegistry.java @@ -13,6 +13,8 @@ import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; +import de.tum.cit.aet.artemis.core.config.migration.entries.ResultExerciseIdMigrationEntry; + /** * This component allows registering certain entries containing functionality that gets executed on application startup. The entries must extend {@link MigrationEntry}. * It should definitely be executed on startup, so we make it non-lazy. @@ -30,6 +32,7 @@ public class MigrationRegistry { public MigrationRegistry(MigrationService migrationService) { this.migrationService = migrationService; // Here we define the order of the ChangeEntries + migrationEntryMap.put(1, ResultExerciseIdMigrationEntry.class); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java new file mode 100644 index 000000000000..88ac536eaf56 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java @@ -0,0 +1,45 @@ +package de.tum.cit.aet.artemis.core.config.migration.entries; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Component; + +import de.tum.cit.aet.artemis.assessment.repository.ResultRepository; +import de.tum.cit.aet.artemis.core.config.migration.MigrationEntry; + +@Component +public class ResultExerciseIdMigrationEntry extends MigrationEntry { + + private static final Logger log = LoggerFactory.getLogger(ResultExerciseIdMigrationEntry.class); + + private final ResultRepository resultRepository; + + public ResultExerciseIdMigrationEntry(ResultRepository resultRepository) { + this.resultRepository = resultRepository; + } + + @Override + public void execute() { + Slice resultIds; + do { + resultIds = resultRepository.findResultIdsWithoutExerciseId(Pageable.ofSize(500)); + log.info("Backfilling exerciseId for {} results", resultIds.getNumberOfElements()); + var updatedCount = resultRepository.backfillExerciseIdBatch(resultIds.getContent()); + log.info("Backfilled exerciseId for {} results", updatedCount); + } + while (resultIds.hasNext()); + + } + + @Override + public String author() { + return "tobias-lippert"; + } + + @Override + public String date() { + return "2025-09-01"; + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java index ddaef13817ba..e9f222e3fea5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java @@ -396,6 +396,9 @@ public void hideDetails(Submission submission, User user) { public Result saveNewEmptyResult(Submission submission) { Result result = new Result(); result.setSubmission(submission); + if (submission.getParticipation() != null && submission.getParticipation().getExercise() != null) { + result.setExerciseId(submission.getParticipation().getExercise().getId()); + } submission.addResult(result); result = resultRepository.save(result); submissionRepository.save(submission); @@ -446,6 +449,9 @@ public Result copyResultFromPreviousRoundAndSave(Submission submission, Result o return saveNewEmptyResult(submission); } Result newResult = new Result(); + if (submission.getParticipation() != null && submission.getParticipation().getExercise() != null) { + newResult.setExerciseId(submission.getParticipation().getExercise().getId()); + } copyFeedbackToNewResult(newResult, oldResult); return copyResultContentAndAddToSubmission(submission, newResult, oldResult); } @@ -462,6 +468,9 @@ public Result copyResultFromPreviousRoundAndSave(Submission submission, Result o */ public Result createResultAfterComplaintResponse(Submission submission, Result oldResult, List feedbacks, String assessmentNoteText) { Result newResult = new Result(); + if (submission.getParticipation() != null && submission.getParticipation().getExercise() != null) { + newResult.setExerciseId(submission.getParticipation().getExercise().getId()); + } updateAssessmentNoteAfterComplaintResponse(newResult, assessmentNoteText, submission.getLatestResult().getAssessor()); copyFeedbackToResult(newResult, feedbacks); newResult = copyResultContentAndAddToSubmission(submission, newResult, oldResult); @@ -532,6 +541,7 @@ public void addResultWithFeedbackByCorrectionRound(StudentParticipation studentP var latestSubmission = studentParticipation.findLatestSubmission(); if (latestSubmission.isPresent() && latestSubmission.get().getResultForCorrectionRound(correctionRound) == null) { Result result = new Result(); + result.setExerciseId(studentParticipation.getExercise().getId()); result.setAssessor(assessor); result.setCompletionDate(ZonedDateTime.now()); result.setScore(score, studentParticipation.getExercise().getCourseViaExerciseGroupOrCourseMember()); diff --git a/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseFeedbackService.java b/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseFeedbackService.java index 2f74a6dbd9c5..41c3f0f6726f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseFeedbackService.java +++ b/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseFeedbackService.java @@ -145,6 +145,9 @@ public void generateAutomaticNonGradedFeedback(ModelingSubmission modelingSubmis */ private Result createInitialResult(StudentParticipation participation, Submission submission) { Result result = new Result(); + if (participation.getExercise() != null) { + result.setExerciseId(participation.getExercise().getId()); + } result.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA); result.setRated(true); result.setScore(0.0); diff --git a/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizResultService.java b/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizResultService.java index 0c45b5abbcdf..775e7cde74f0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizResultService.java +++ b/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizResultService.java @@ -156,6 +156,7 @@ else if (submissions.size() > 1) { else { // No rated result exists; create a new one Result result = new Result().rated(true).assessmentType(AssessmentType.AUTOMATIC).completionDate(ZonedDateTime.now()); + result.setExerciseId(quizExercise.getId()); // Associate submission with result result.setSubmission(quizSubmission); diff --git a/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizSubmissionService.java b/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizSubmissionService.java index 596c30e7ba4c..f3000bcea9a2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizSubmissionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizSubmissionService.java @@ -123,6 +123,7 @@ public Result submitForPractice(QuizSubmission quizSubmission, QuizExercise quiz // create result Result result = new Result(); + result.setExerciseId(quizExercise.getId()); result.setRated(false); result.setAssessmentType(AssessmentType.AUTOMATIC); result.setCompletionDate(ZonedDateTime.now()); @@ -184,6 +185,7 @@ else if (quizExercise.isQuizEnded()) { participation.setInitializationState(InitializationState.FINISHED); Result result = new Result(); + result.setExerciseId(quizExercise.getId()); result.setRated(true); result.setAssessmentType(AssessmentType.AUTOMATIC); result.setCompletionDate(quizSubmission.getSubmissionDate()); diff --git a/src/main/java/de/tum/cit/aet/artemis/text/service/TextExerciseFeedbackService.java b/src/main/java/de/tum/cit/aet/artemis/text/service/TextExerciseFeedbackService.java index e201ab0263f5..f3198ec12e4f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/text/service/TextExerciseFeedbackService.java +++ b/src/main/java/de/tum/cit/aet/artemis/text/service/TextExerciseFeedbackService.java @@ -109,6 +109,7 @@ public void generateAutomaticNonGradedFeedback(TextSubmission textSubmission, St // athena takes over the control here Result automaticResult = new Result(); + automaticResult.setExerciseId(textExercise.getId()); automaticResult.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA); automaticResult.setRated(true); automaticResult.setScore(0.0); diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 52eccb9d875f..0b6a900e8ae7 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -41,6 +41,7 @@ + From ec9675b66f82684c043134eabf73d19ff70f3682 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Tue, 2 Sep 2025 22:19:11 +0200 Subject: [PATCH 07/30] set exercise id for programming exercise results --- .../programming/service/ProgrammingExerciseGradingService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGradingService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGradingService.java index 32973696065f..13221e2d817c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGradingService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGradingService.java @@ -190,6 +190,7 @@ public Result processNewProgrammingExerciseResult(@NotNull ProgrammingExercisePa // Note: we only set one side of the relationship because we don't know yet whether the result will actually be saved newResult.setSubmission(latestSubmission); + newResult.setExerciseId(participation.getExercise().getId()); newResult.setRatedIfNotAfterDueDate(); // NOTE: the result is not saved yet, but is connected to the submission, the submission is not completely saved yet return processNewProgrammingExerciseResult(participation, newResult); From 9ee9a88c022b95c977cede1f7b185f46f4031ec1 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Tue, 2 Sep 2025 22:21:21 +0200 Subject: [PATCH 08/30] add changelog to master.xml --- src/main/resources/config/liquibase/master.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 0b6a900e8ae7..e811f7010cb0 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -41,6 +41,7 @@ + From 81e8529f6d31664c33ab92ed704b6399197f3237 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Thu, 4 Sep 2025 21:26:57 +0200 Subject: [PATCH 09/30] adjust query to new table layout --- .../repository/ComplaintRepository.java | 8 +++++ .../ComplaintResponseRepository.java | 13 ++++++++ .../repository/ResultRepository.java | 30 +++++++++++++++++-- .../ResultExerciseIdMigrationEntry.java | 20 +++++++------ .../service/course/CourseStatsService.java | 10 +++---- .../core/web/course/CourseStatsResource.java | 3 ++ .../resources/config/liquibase/master.xml | 2 +- 7 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java index 45ed73df48d0..dc235d5c4a05 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java @@ -73,6 +73,14 @@ public interface ComplaintRepository extends ArtemisJpaRepository exerciseIds, @Param("complaintType") ComplaintType complaintType); + /** * This magic method counts the number of complaints by complaint type associated to an exam id * diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java index f4be2cb2856e..0d31dbdc7c25 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java @@ -32,9 +32,22 @@ public interface ComplaintResponseRepository extends ArtemisJpaRepository exerciseIds, + @Param("complaintType") ComplaintType complaintType); + /** * This magic method counts the number of complaints responses by complaint type associated to an exam id * diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java index c059ff0933fb..166fc533319b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java @@ -225,7 +225,7 @@ SELECT COUNT(r) AND r.assessor IS NOT NULL AND p.testRun = false AND r.rated = true - AND p.exercise.id IN :exerciseIds + AND r.exerciseId IN :exerciseIds """) long countAssessmentsForExerciseIds(@Param("exerciseIds") Set exerciseIds); @@ -287,7 +287,7 @@ SELECT COUNT(DISTINCT p) JOIN p.submissions s JOIN s.results r JOIN p.exercise e - WHERE e.id IN :exerciseIds + WHERE r.exerciseId IN :exerciseIds AND p.testRun = FALSE AND r.assessor IS NOT NULL AND r.rated = TRUE @@ -297,6 +297,22 @@ SELECT COUNT(DISTINCT p) """) long countNumberOfFinishedAssessmentsForExerciseIdsIgnoreTestRuns(@Param("exerciseIds") Set exerciseIds); + @Query(""" + SELECT COUNT(DISTINCT p) + FROM StudentParticipation p + JOIN p.submissions s + JOIN s.results r + JOIN p.exercise e + WHERE e.id = :exerciseId + AND p.testRun = FALSE + AND r.assessor IS NOT NULL + AND r.rated = TRUE + AND r.submission.submitted = TRUE + AND r.completionDate IS NOT NULL + AND (e.dueDate IS NULL OR r.submission.submissionDate <= e.dueDate) + """) + long countNumberOfFinishedAssessmentsForExerciseIdIgnoreTestRuns(@Param("exerciseId") long exerciseId); + /** * @param exerciseId id of exercise * @return a list that contains the count of manual assessments for each studentParticipation of the exercise @@ -931,7 +947,17 @@ SELECT MAX(r2.id) SELECT r.id FROM Result r WHERE r.exerciseId IS NULL + ORDER BY r.id """) Slice findResultIdsWithoutExerciseId(Pageable pageable); + @Query(""" + SELECT r.id + FROM Result r + WHERE r.exerciseId IS NULL + AND r.id > :afterId + ORDER BY r.id + """) + List findNextIds(long afterId, Pageable pageable); + } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java index 88ac536eaf56..eba9b012d4f9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java @@ -2,8 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import de.tum.cit.aet.artemis.assessment.repository.ResultRepository; @@ -22,14 +21,17 @@ public ResultExerciseIdMigrationEntry(ResultRepository resultRepository) { @Override public void execute() { - Slice resultIds; - do { - resultIds = resultRepository.findResultIdsWithoutExerciseId(Pageable.ofSize(500)); - log.info("Backfilling exerciseId for {} results", resultIds.getNumberOfElements()); - var updatedCount = resultRepository.backfillExerciseIdBatch(resultIds.getContent()); - log.info("Backfilled exerciseId for {} results", updatedCount); + long lastId = 0L; + while (true) { + var ids = resultRepository.findNextIds(lastId, PageRequest.of(0, 1000)); + if (ids.isEmpty()) { + break; + } + long updatedCount = resultRepository.backfillExerciseIdBatch(ids); + log.info("{} results updated", updatedCount); + + lastId = ids.getLast(); // Java 21 List#reversed/stream alternative if needed } - while (resultIds.hasNext()); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java index 6347b52a6286..4265ff78ca13 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java @@ -270,13 +270,12 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin if (course.getComplaintsEnabled()) { start = System.currentTimeMillis(); - currentAbsoluteComplaints = complaintResponseRepository - .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), COMPLAINT); + currentAbsoluteComplaints = complaintResponseRepository.countNumberOfComplaintsByComplaintTypeAndSubmittedTimeIsNotNullForExerciseIds(exerciseIds, COMPLAINT); log.debug( "complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); start = System.currentTimeMillis(); - currentMaxComplaints = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), COMPLAINT); + currentMaxComplaints = complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, COMPLAINT); log.debug("complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); currentPercentageComplaints = calculatePercentage(currentAbsoluteComplaints, currentMaxComplaints); @@ -288,13 +287,12 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin if (course.getRequestMoreFeedbackEnabled()) { start = System.currentTimeMillis(); - currentAbsoluteMoreFeedbacks = complaintResponseRepository - .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), MORE_FEEDBACK); + currentAbsoluteMoreFeedbacks = complaintResponseRepository.countNumberOfComplaintsByComplaintTypeAndSubmittedTimeIsNotNullForExerciseIds(exerciseIds, MORE_FEEDBACK); log.debug( "complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); start = System.currentTimeMillis(); - currentMaxMoreFeedbacks = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), MORE_FEEDBACK); + currentMaxMoreFeedbacks = complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, MORE_FEEDBACK); log.debug("complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); currentPercentageMoreFeedbacks = calculatePercentage(currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java index 93db750fe3d0..6ea5289a5e71 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/course/CourseStatsResource.java @@ -206,7 +206,10 @@ public ResponseEntity getCourseDTOForDetailView(@ Course course = courseRepository.findByIdElseThrow(courseId); authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.TEACHING_ASSISTANT, course, null); GradingScale gradingScale = gradingScaleRepository.findByCourseId(courseId).orElse(null); + var startTime = System.currentTimeMillis(); CourseManagementDetailViewDTO managementDetailViewDTO = courseStatsService.getStatsForDetailView(course, gradingScale); + var endTime = System.currentTimeMillis(); + log.debug("Getting data for course management detail view took {} ms", (endTime - startTime)); return ResponseEntity.ok(managementDetailViewDTO); } } diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index e811f7010cb0..e1cb1977f924 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -41,7 +41,7 @@ - + From 4940811070ba4f9eba0ae3d6d1d54558f6112eb1 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Fri, 5 Sep 2025 10:03:23 +0200 Subject: [PATCH 10/30] adapt query --- .../cit/aet/artemis/assessment/repository/ResultRepository.java | 2 +- src/main/resources/config/liquibase/master.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java index 166fc533319b..86b3cf5aa0a4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java @@ -287,7 +287,7 @@ SELECT COUNT(DISTINCT p) JOIN p.submissions s JOIN s.results r JOIN p.exercise e - WHERE r.exerciseId IN :exerciseIds + WHERE p.exercise.id IN :exerciseIds AND p.testRun = FALSE AND r.assessor IS NOT NULL AND r.rated = TRUE diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index e1cb1977f924..9f4051d721c1 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -42,7 +42,7 @@ - + From bac732d7af7bcb714c0d20f90e8abb67589c0ef1 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Fri, 5 Sep 2025 21:31:54 +0200 Subject: [PATCH 11/30] Revert "speedup active students query" This reverts commit 037fdf6e2f2429b40d79fab93505c5b1b25bf801. --- .../core/repository/CourseRepository.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/CourseRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/CourseRepository.java index 68cd0ea2ffa6..b47fdc0a9515 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/CourseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/CourseRepository.java @@ -229,18 +229,16 @@ WHERE TYPE(e) = QuizExercise * @return A list with a map for every submission containing date and the username */ @Query(""" - SELECT NEW de.tum.cit.aet.artemis.core.dto.StatisticsEntry( - YEAR(s.submissionDate), - MONTH(s.submissionDate), - DAY(s.submissionDate), - p.student.login - ) - FROM StudentParticipation p + SELECT new de.tum.cit.aet.artemis.core.dto.StatisticsEntry( + SUBSTRING(CAST(s.submissionDate AS string), 1, 10), + p.student.login + ) + FROM StudentParticipation p JOIN p.submissions s - WHERE p.exercise.id in :exerciseIds - AND s.submissionDate >= :startDate - AND s.submissionDate < :endDate - GROUP BY YEAR(s.submissionDate), MONTH(s.submissionDate), DAY(s.submissionDate), p.student.login + WHERE p.exercise.id IN :exerciseIds + AND s.submissionDate >= :startDate + AND s.submissionDate <= :endDate + GROUP BY SUBSTRING(CAST(s.submissionDate AS string), 1, 10), p.student.login """) List getActiveStudents(@Param("exerciseIds") Set exerciseIds, @Param("startDate") ZonedDateTime startDate, @Param("endDate") ZonedDateTime endDate); From d4fd6b6c2fe152a5fb0b02e01543f3174f3b66eb Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Sat, 4 Oct 2025 22:29:53 +0200 Subject: [PATCH 12/30] migration --- .../ResultExerciseIdMigrationEntry.java | 2 +- .../changelog/20250901221600_changelog.xml | 30 +++++++++++++++++++ .../resources/config/liquibase/master.xml | 3 +- 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java index eba9b012d4f9..70dc0ef956c4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java @@ -30,7 +30,7 @@ public void execute() { long updatedCount = resultRepository.backfillExerciseIdBatch(ids); log.info("{} results updated", updatedCount); - lastId = ids.getLast(); // Java 21 List#reversed/stream alternative if needed + lastId = ids.getLast(); } } diff --git a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml new file mode 100644 index 000000000000..a73e297f6767 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 4fa88aed5abe..63ca679e1a2b 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -42,9 +42,8 @@ - - + From a89f98270c3a4811d30c46e52b9de039ada97fc9 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Sat, 4 Oct 2025 22:58:50 +0200 Subject: [PATCH 13/30] adapt queries --- .../repository/ComplaintRepository.java | 32 ++++--------------- .../ComplaintResponseRepository.java | 23 +++++++------ .../assessment/service/ComplaintService.java | 18 +++++------ .../service/course/CourseStatsService.java | 18 +++++------ .../aet/artemis/exam/service/ExamService.java | 10 +++--- 5 files changed, 39 insertions(+), 62 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java index 9f03845fd40c..aae66bb67414 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintRepository.java @@ -64,15 +64,6 @@ public interface ComplaintRepository extends ArtemisJpaRepository findByIdWithEagerAssessor(@Param("complaintId") Long complaintId); - /** - * This magic method counts the number of complaints by complaint type associated to a course id - * - * @param courseId - the id of the course we want to filter by - * @param complaintType - type of complaint we want to filter by - * @return number of more feedback requests associated to course courseId - */ - long countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(Long courseId, ComplaintType complaintType); - @Query(""" SELECT COUNT(c) FROM Complaint c @@ -81,15 +72,6 @@ SELECT COUNT(c) """) long countByExerciseIdsAndComplaintType(@Param("exerciseIds") Set exerciseIds, @Param("complaintType") ComplaintType complaintType); - /** - * This magic method counts the number of complaints by complaint type associated to an exam id - * - * @param examId - the id of the exam we want to filter by - * @param complaintType - type of complaint we want to filter by - * @return number of complaints associated to course examId - */ - long countByResult_Submission_Participation_Exercise_ExerciseGroup_Exam_IdAndComplaintType(Long examId, ComplaintType complaintType); - @Query(""" SELECT c FROM Complaint c @@ -150,7 +132,7 @@ SELECT COUNT(c) @Query(""" SELECT COUNT(c) FROM Complaint c - WHERE c.result.submission.participation.exercise.id = :exerciseId + WHERE c.result.exerciseId = :exerciseId AND c.complaintType = :complaintType """) long countComplaintsByExerciseIdAndComplaintType(@Param("exerciseId") Long exerciseId, @Param("complaintType") ComplaintType complaintType); @@ -164,13 +146,13 @@ SELECT COUNT(c) */ @Query(""" SELECT new de.tum.cit.aet.artemis.assessment.dto.dashboard.ExerciseMapEntryDTO( - c.result.submission.participation.exercise.id, + c.result.exerciseId, COUNT(DISTINCT c) ) FROM Complaint c - WHERE c.result.submission.participation.exercise.id IN :exerciseIds + WHERE c.result.exerciseId IN :exerciseIds AND c.complaintType = :complaintType - GROUP BY c.result.submission.participation.exercise.id + GROUP BY c.result.exerciseId """) List countComplaintsByExerciseIdsAndComplaintType(@Param("exerciseIds") Set exerciseIds, @Param("complaintType") ComplaintType complaintType); @@ -183,14 +165,14 @@ SELECT COUNT(c) */ @Query(""" SELECT new de.tum.cit.aet.artemis.assessment.dto.dashboard.ExerciseMapEntryDTO( - c.result.submission.participation.exercise.id, + c.result.exerciseId, COUNT(DISTINCT c) ) FROM Complaint c - WHERE c.result.submission.participation.exercise.id IN :exerciseIds + WHERE c.result.exerciseId IN :exerciseIds AND c.complaintType = :complaintType AND c.result.submission.participation.testRun = FALSE - GROUP BY c.result.submission.participation.exercise.id + GROUP BY c.result.exerciseId """) List countComplaintsByExerciseIdsAndComplaintTypeIgnoreTestRuns(@Param("exerciseIds") Set exerciseIds, @Param("complaintType") ComplaintType complaintType); diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java index 0d31dbdc7c25..b1db36d014a2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java @@ -29,12 +29,21 @@ public interface ComplaintResponseRepository extends ArtemisJpaRepository exerciseIds, @Param("complaintType") ComplaintType complaintType); @Query(""" SELECT COUNT(DISTINCT cr) @@ -48,16 +57,6 @@ SELECT COUNT(DISTINCT cr) long countNumberOfComplaintsByComplaintTypeAndSubmittedTimeIsNotNullForExerciseIds(@Param("exerciseIds") Set exerciseIds, @Param("complaintType") ComplaintType complaintType); - /** - * This magic method counts the number of complaints responses by complaint type associated to an exam id - * - * @param examId - the id of the exam we want to filter by - * @param complaintType - complaint type we want to filter by - * @return number of complaints response associated to exam examId - */ - long countByComplaint_Result_Submission_Participation_Exercise_ExerciseGroup_Exam_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(Long examId, - ComplaintType complaintType); - /** * This magic method counts the number of complaints responses by complaint type associated to an exercise id * diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java b/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java index b8f2c03cab76..38b8e8805562 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java @@ -174,12 +174,12 @@ else if (participant instanceof Team) { } } - public long countComplaintsByCourseId(long courseId) { - return complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(courseId, ComplaintType.COMPLAINT); + public long countComplaintsByExerciseIds(Set exerciseIds) { + return complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, ComplaintType.COMPLAINT); } - public long countMoreFeedbackRequestsByCourseId(long courseId) { - return complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(courseId, ComplaintType.MORE_FEEDBACK); + public long countMoreFeedbackRequestsByCourseId(Set exerciseIds) { + return complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, ComplaintType.MORE_FEEDBACK); } /** @@ -188,9 +188,8 @@ public long countMoreFeedbackRequestsByCourseId(long courseId) { * @param courseId the id of the course * @return the number of responses */ - public long countComplaintResponsesByCourseId(long courseId) { - return complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(courseId, - ComplaintType.COMPLAINT); + public long countComplaintResponsesByExerciseIds(Set exerciseIds) { + return complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, ComplaintType.COMPLAINT); } /** @@ -199,9 +198,8 @@ public long countComplaintResponsesByCourseId(long courseId) { * @param courseId the id of the course * @return the number of responses */ - public long countMoreFeedbackRequestResponsesByCourseId(long courseId) { - return complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(courseId, - ComplaintType.MORE_FEEDBACK); + public long countMoreFeedbackRequestResponsesByExerciseIds(Set exerciseIds) { + return complaintResponseRepository.countNumberOfComplaintsByComplaintTypeAndSubmittedTimeIsNotNullForExerciseIds(exerciseIds, ComplaintType.MORE_FEEDBACK); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java index b90d611571ad..ffabfa68fe61 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java @@ -271,13 +271,12 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin if (course.getComplaintsEnabled()) { start = System.currentTimeMillis(); - currentAbsoluteComplaints = complaintResponseRepository - .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), COMPLAINT); + currentAbsoluteComplaints = complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, COMPLAINT); log.debug( "complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); start = System.currentTimeMillis(); - currentMaxComplaints = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), COMPLAINT); + currentMaxComplaints = complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, COMPLAINT); log.debug("complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); currentPercentageComplaints = calculatePercentage(currentAbsoluteComplaints, currentMaxComplaints); @@ -289,13 +288,12 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin if (course.getRequestMoreFeedbackEnabled()) { start = System.currentTimeMillis(); - currentAbsoluteMoreFeedbacks = complaintResponseRepository - .countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(course.getId(), MORE_FEEDBACK); + currentAbsoluteMoreFeedbacks = complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, MORE_FEEDBACK); log.debug( "complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); start = System.currentTimeMillis(); - currentMaxMoreFeedbacks = complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType(course.getId(), MORE_FEEDBACK); + currentMaxMoreFeedbacks = complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, MORE_FEEDBACK); log.debug("complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); currentPercentageMoreFeedbacks = calculatePercentage(currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks); @@ -336,13 +334,13 @@ public StatsForDashboardDTO getStatsForDashboardDTO(Course course) { stats.setNumberOfSubmissions(new DueDateStat(numberOfInTimeSubmissions, numberOfLateSubmissions)); - final long numberOfMoreFeedbackRequests = complaintService.countMoreFeedbackRequestsByCourseId(course.getId()); + final long numberOfMoreFeedbackRequests = complaintService.countMoreFeedbackRequestsByCourseId(courseExerciseIds); stats.setNumberOfMoreFeedbackRequests(numberOfMoreFeedbackRequests); - final long numberOfMoreFeedbackComplaintResponses = complaintService.countMoreFeedbackRequestResponsesByCourseId(course.getId()); + final long numberOfMoreFeedbackComplaintResponses = complaintService.countMoreFeedbackRequestResponsesByExerciseIds(courseExerciseIds); stats.setNumberOfOpenMoreFeedbackRequests(numberOfMoreFeedbackRequests - numberOfMoreFeedbackComplaintResponses); - final long numberOfComplaints = complaintService.countComplaintsByCourseId(course.getId()); + final long numberOfComplaints = complaintService.countComplaintsByExerciseIds(courseExerciseIds); stats.setNumberOfComplaints(numberOfComplaints); - final long numberOfComplaintResponses = complaintService.countComplaintResponsesByCourseId(course.getId()); + final long numberOfComplaintResponses = complaintService.countComplaintResponsesByExerciseIds(courseExerciseIds); stats.setNumberOfOpenComplaints(numberOfComplaints - numberOfComplaintResponses); final long numberOfAssessmentLocks = submissionRepository.countLockedSubmissionsByUserIdAndCourseId(userRepository.getUserWithGroupsAndAuthorities().getId(), course.getId()); diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamService.java b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamService.java index d823f9a46764..9a8da559c72d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamService.java @@ -1371,7 +1371,9 @@ public void setExamProperties(Exam exam) { * @return data about an exam including all exercises, plus some data for the tutor as tutor status for assessment */ public StatsForDashboardDTO getStatsForExamAssessmentDashboard(Course course, Long examId) { - Exam exam = examRepository.findById(examId).orElseThrow(); + Exam exam = examRepository.findWithExerciseGroupsAndExercisesByIdOrElseThrow(examId); + Set exerciseIds = exam.getExerciseGroups().stream().flatMap(exerciseGroup -> exerciseGroup.getExercises().stream()).map(DomainObject::getId) + .collect(Collectors.toSet()); StatsForDashboardDTO stats = new StatsForDashboardDTO(); final long numberOfSubmissions = submissionRepository.countByExamIdSubmittedSubmissionsIgnoreTestRuns(examId) @@ -1382,12 +1384,10 @@ public StatsForDashboardDTO getStatsForExamAssessmentDashboard(Course course, Lo exam.getNumberOfCorrectionRoundsInExam()); stats.setNumberOfAssessmentsOfCorrectionRounds(numberOfAssessmentsOfCorrectionRounds); - final long numberOfComplaints = complaintRepository.countByResult_Submission_Participation_Exercise_ExerciseGroup_Exam_IdAndComplaintType(examId, ComplaintType.COMPLAINT); + final long numberOfComplaints = complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, ComplaintType.COMPLAINT); stats.setNumberOfComplaints(numberOfComplaints); - final long numberOfComplaintResponses = complaintResponseRepository - .countByComplaint_Result_Submission_Participation_Exercise_ExerciseGroup_Exam_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull(examId, - ComplaintType.COMPLAINT); + final long numberOfComplaintResponses = complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, ComplaintType.COMPLAINT); stats.setNumberOfOpenComplaints(numberOfComplaints - numberOfComplaintResponses); final long numberOfAssessmentLocks = submissionRepository.countLockedSubmissionsByUserIdAndExamId(userRepository.getUserWithGroupsAndAuthorities().getId(), examId); From b697052da720447856664a2203a42d84cc547476 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Sun, 5 Oct 2025 18:09:45 +0200 Subject: [PATCH 14/30] fix architecture tests --- .../aet/artemis/assessment/repository/ResultRepository.java | 2 +- .../migration/entries/ResultExerciseIdMigrationEntry.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java index b712d639fd7c..665c82d11607 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java @@ -967,6 +967,6 @@ SELECT MAX(r2.id) AND r.id > :afterId ORDER BY r.id """) - List findNextIds(long afterId, Pageable pageable); + List findNextIds(@Param("afterId") long afterId, Pageable pageable); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java index 70dc0ef956c4..d93070110163 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java @@ -2,13 +2,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Profile; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import de.tum.cit.aet.artemis.assessment.repository.ResultRepository; +import de.tum.cit.aet.artemis.core.config.Constants; import de.tum.cit.aet.artemis.core.config.migration.MigrationEntry; @Component +@Lazy +@Profile(Constants.PROFILE_CORE_AND_SCHEDULING) public class ResultExerciseIdMigrationEntry extends MigrationEntry { private static final Logger log = LoggerFactory.getLogger(ResultExerciseIdMigrationEntry.class); From 8c7abfaeb057da0d692cfa0c275ffa5ecd18643b Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Sun, 5 Oct 2025 19:15:07 +0200 Subject: [PATCH 15/30] fix course tests --- .../repository/ComplaintResponseRepository.java | 12 ------------ .../assessment/repository/ResultRepository.java | 2 +- .../artemis/assessment/service/ComplaintService.java | 2 +- .../assessment/util/ComplaintUtilService.java | 3 +++ .../cit/aet/artemis/core/util/CourseUtilService.java | 3 +++ .../participation/util/ParticipationUtilService.java | 2 ++ 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java index b1db36d014a2..eb2acf1a9bbe 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java @@ -45,18 +45,6 @@ SELECT COUNT(DISTINCT cr) """) long countComplaintResponsesForExerciseIdsAndComplaintType(@Param("exerciseIds") Set exerciseIds, @Param("complaintType") ComplaintType complaintType); - @Query(""" - SELECT COUNT(DISTINCT cr) - FROM ComplaintResponse cr - JOIN cr.complaint c - JOIN c.result r - WHERE r.exerciseId IN :exerciseIds - AND c.complaintType = :complaintType - AND cr.submittedTime IS NOT NULL - """) - long countNumberOfComplaintsByComplaintTypeAndSubmittedTimeIsNotNullForExerciseIds(@Param("exerciseIds") Set exerciseIds, - @Param("complaintType") ComplaintType complaintType); - /** * This magic method counts the number of complaints responses by complaint type associated to an exercise id * diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java index 665c82d11607..634561d31a00 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ResultRepository.java @@ -225,7 +225,7 @@ SELECT COUNT(r) WHERE r.completionDate IS NOT NULL AND r.assessor IS NOT NULL AND p.testRun = FALSE - AND p.exercise.id IN :exerciseIds + AND r.exerciseId IN :exerciseIds """) long countAssessmentsForExerciseIdsIgnoreTestRuns(@Param("exerciseIds") Set exerciseIdsWithManualAssessment); diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java b/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java index 38b8e8805562..9a8eac964504 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java @@ -199,7 +199,7 @@ public long countComplaintResponsesByExerciseIds(Set exerciseIds) { * @return the number of responses */ public long countMoreFeedbackRequestResponsesByExerciseIds(Set exerciseIds) { - return complaintResponseRepository.countNumberOfComplaintsByComplaintTypeAndSubmittedTimeIsNotNullForExerciseIds(exerciseIds, ComplaintType.MORE_FEEDBACK); + return complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, ComplaintType.MORE_FEEDBACK); } /** diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/util/ComplaintUtilService.java b/src/test/java/de/tum/cit/aet/artemis/assessment/util/ComplaintUtilService.java index b97f4d5d3e54..e9d15ea7f45d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/util/ComplaintUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/util/ComplaintUtilService.java @@ -2,6 +2,8 @@ import static tech.jhipster.config.JHipsterConstants.SPRING_PROFILE_TEST; +import java.time.ZonedDateTime; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; @@ -67,6 +69,7 @@ public void generateComplaintAndResponses(String userPrefix, int currentIndex, i ComplaintResponse complaintResponse = createInitialEmptyResponse(typeComplaint ? userPrefix + "tutor5" : currentUser.getLogin(), complaint); complaintResponse.getComplaint().setAccepted(true); complaintResponse.setResponseText(typeComplaint ? "Accepted" : "SomeMoreFeedback"); + complaintResponse.setSubmittedTime(ZonedDateTime.now().minusSeconds(1)); complaintResponseRepo.save(complaintResponse); complaint.setComplaintResponse(complaintResponse); complaintRepo.save(complaint); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java index 90ad2f2adff7..dcabd9384f65 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java @@ -1098,6 +1098,7 @@ public Course addCourseWithExercisesAndSubmissions(String courseShortName, Strin if ((i % 3) == 0) { ModelingExercise modelingExercise = ModelingExerciseFactory.generateModelingExercise(releaseDate, dueDate, assessmentDueDate, DiagramType.ClassDiagram, course); + modelingExercise.setAssessmentType(AssessmentType.MANUAL); modelingExercise.setTitle("Modeling" + i); modelingExercise.setCourse(course); modelingExercise = exerciseRepository.save(modelingExercise); @@ -1121,6 +1122,7 @@ public Course addCourseWithExercisesAndSubmissions(String courseShortName, Strin else if ((i % 3) == 1) { TextExercise textExercise = TextExerciseFactory.generateTextExercise(releaseDate, dueDate, assessmentDueDate, course); textExercise.setTitle("Text" + i); + textExercise.setAssessmentType(AssessmentType.MANUAL); textExercise.setCourse(course); textExercise = exerciseRepository.save(textExercise); course.addExercises(textExercise); @@ -1139,6 +1141,7 @@ else if ((i % 3) == 1) { else { // i.e. (i % 3) == 2 FileUploadExercise fileUploadExercise = FileUploadExerciseFactory.generateFileUploadExercise(releaseDate, dueDate, assessmentDueDate, "png,pdf", course); fileUploadExercise.setTitle("FileUpload" + i); + fileUploadExercise.setAssessmentType(AssessmentType.MANUAL); fileUploadExercise.setCourse(course); fileUploadExercise = exerciseRepository.save(fileUploadExercise); course.addExercises(fileUploadExercise); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java index 5450ee7dc250..33692fe305f1 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java @@ -235,6 +235,7 @@ public Result createSubmissionAndResult(StudentParticipation studentParticipatio Result result = ParticipationFactory.generateResult(rated, scoreAwarded); result.setSubmission(submission); + result.setExerciseId(exercise.getId()); result.completionDate(ZonedDateTime.now()); submission.addResult(result); resultRepository.save(result); @@ -650,6 +651,7 @@ public void saveResultInParticipation(Submission submission, Result result) { public Result generateResult(Submission submission, User assessor) { Result result = new Result(); result.setSubmission(submission); + result.setExerciseId(submission.getParticipation().getExercise().getId()); result.completionDate(pastTimestamp); result.setAssessmentType(AssessmentType.SEMI_AUTOMATIC); result.setAssessor(assessor); From 4fb8942824cd1dc1c2624225db3a5a53bfddf1e2 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Mon, 6 Oct 2025 22:28:07 +0200 Subject: [PATCH 16/30] switch to liquibase migration --- .../aet/artemis/assessment/domain/Result.java | 8 +-- .../config/migration/MigrationRegistry.java | 3 -- .../ResultExerciseIdMigrationEntry.java | 52 ------------------- .../changelog/20250901221600_changelog.xml | 42 ++++++++++----- 4 files changed, 33 insertions(+), 72 deletions(-) delete mode 100644 src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java index 12ab7dbed589..a5ebbdaf139c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java @@ -134,9 +134,9 @@ public class Result extends DomainObject implements Comparable { @JsonIgnore private Instant lastModifiedDate; - @Column(name = "exercise_id") + @Column(name = "exercise_id", nullable = false) @JsonIgnore - private Long exerciseId; + private long exerciseId; public ZonedDateTime getCompletionDate() { return completionDate; @@ -177,11 +177,11 @@ public Instant getLastModifiedDate() { return lastModifiedDate; } - public Long getExerciseId() { + public long getExerciseId() { return exerciseId; } - public void setExerciseId(Long exerciseId) { + public void setExerciseId(long exerciseId) { this.exerciseId = exerciseId; } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/MigrationRegistry.java b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/MigrationRegistry.java index 059eaee64f1b..2a805fd79776 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/MigrationRegistry.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/MigrationRegistry.java @@ -13,8 +13,6 @@ import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; -import de.tum.cit.aet.artemis.core.config.migration.entries.ResultExerciseIdMigrationEntry; - /** * This component allows registering certain entries containing functionality that gets executed on application startup. The entries must extend {@link MigrationEntry}. * It should definitely be executed on startup, so we make it non-lazy. @@ -32,7 +30,6 @@ public class MigrationRegistry { public MigrationRegistry(MigrationService migrationService) { this.migrationService = migrationService; // Here we define the order of the ChangeEntries - migrationEntryMap.put(1, ResultExerciseIdMigrationEntry.class); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java b/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java deleted file mode 100644 index d93070110163..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/migration/entries/ResultExerciseIdMigrationEntry.java +++ /dev/null @@ -1,52 +0,0 @@ -package de.tum.cit.aet.artemis.core.config.migration.entries; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Lazy; -import org.springframework.context.annotation.Profile; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Component; - -import de.tum.cit.aet.artemis.assessment.repository.ResultRepository; -import de.tum.cit.aet.artemis.core.config.Constants; -import de.tum.cit.aet.artemis.core.config.migration.MigrationEntry; - -@Component -@Lazy -@Profile(Constants.PROFILE_CORE_AND_SCHEDULING) -public class ResultExerciseIdMigrationEntry extends MigrationEntry { - - private static final Logger log = LoggerFactory.getLogger(ResultExerciseIdMigrationEntry.class); - - private final ResultRepository resultRepository; - - public ResultExerciseIdMigrationEntry(ResultRepository resultRepository) { - this.resultRepository = resultRepository; - } - - @Override - public void execute() { - long lastId = 0L; - while (true) { - var ids = resultRepository.findNextIds(lastId, PageRequest.of(0, 1000)); - if (ids.isEmpty()) { - break; - } - long updatedCount = resultRepository.backfillExerciseIdBatch(ids); - log.info("{} results updated", updatedCount); - - lastId = ids.getLast(); - } - - } - - @Override - public String author() { - return "tobias-lippert"; - } - - @Override - public String date() { - return "2025-09-01"; - } -} diff --git a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml index a73e297f6767..24c73ee182ab 100644 --- a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml @@ -4,27 +4,43 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"> - - - - - - - - + + + + UPDATE result + SET exercise_id = ( + SELECT p.exercise_id + FROM Submission s, Participation p + WHERE result.submission_id = s.id + AND s.participation_id = p.id + ) + WHERE exercise_id IS NULL; + + + + + DELETE FROM result + WHERE exercise_id IS NULL; + + + + - - - - - + From 0bdaf625ec7df3746ec1182f64e027c7c7b6f830 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Mon, 6 Oct 2025 23:53:46 +0200 Subject: [PATCH 17/30] try to fix migration --- .../changelog/20250901221600_changelog.xml | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml index 24c73ee182ab..84c8a3a1750d 100644 --- a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml @@ -12,16 +12,46 @@ - UPDATE result + UPDATE result r SET exercise_id = ( SELECT p.exercise_id - FROM Submission s, Participation p - WHERE result.submission_id = s.id - AND s.participation_id = p.id + FROM submission s + JOIN participation p ON s.participation_id = p.id + WHERE s.id = r.submission_id ) - WHERE exercise_id IS NULL; + WHERE r.exercise_id IS NULL; + + + UPDATE participant_score ps + SET last_result_id = NULL + FROM result r + WHERE r.id = ps.last_result_id + AND r.exercise_id IS NULL; + + DELETE FROM feedback f + USING result r + WHERE r.id = f.result_id + AND r.exercise_id IS NULL; + + DELETE FROM assessment_note a + USING result r + WHERE r.id = a.result_id + AND r.exercise_id IS NULL; + + DELETE FROM result_rating rr + USING result r + WHERE r.id = rr.result_id + AND r.exercise_id IS NULL; + + DELETE FROM complaint c + USING result r + WHERE r.id = c.result_id + AND r.exercise_id IS NULL; + + + DELETE FROM result From 43eddf0fe949f7c7d24b234dd02819e841774219 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Tue, 7 Oct 2025 23:38:07 +0200 Subject: [PATCH 18/30] fix db migration --- .../changelog/20250901221600_changelog.xml | 100 +++++++++++++----- 1 file changed, 74 insertions(+), 26 deletions(-) diff --git a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml index 84c8a3a1750d..0c76a09ed2bc 100644 --- a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml @@ -23,32 +23,80 @@ - - UPDATE participant_score ps - SET last_result_id = NULL - FROM result r - WHERE r.id = ps.last_result_id - AND r.exercise_id IS NULL; - - DELETE FROM feedback f - USING result r - WHERE r.id = f.result_id - AND r.exercise_id IS NULL; - - DELETE FROM assessment_note a - USING result r - WHERE r.id = a.result_id - AND r.exercise_id IS NULL; - - DELETE FROM result_rating rr - USING result r - WHERE r.id = rr.result_id - AND r.exercise_id IS NULL; - - DELETE FROM complaint c - USING result r - WHERE r.id = c.result_id - AND r.exercise_id IS NULL; + + UPDATE participant_score ps + SET last_result_id = NULL + FROM result r + WHERE r.id = ps.last_result_id + AND r.exercise_id IS NULL; + + UPDATE participant_score ps + SET last_rated_result_id = NULL + FROM result r + WHERE r.id = ps.last_rated_result_id + AND r.exercise_id IS NULL; + + DELETE FROM long_feedback_text lft + USING feedback f + JOIN result r ON r.id = f.result_id + WHERE lft.feedback_id = f.id + AND r.exercise_id IS NULL; + + DELETE FROM feedback f + USING result r + WHERE r.id = f.result_id + AND r.exercise_id IS NULL; + + DELETE FROM assessment_note a + USING result r + WHERE r.id = a.result_id + AND r.exercise_id IS NULL; + + DELETE FROM result_rating rr + USING result r + WHERE r.id = rr.result_id + AND r.exercise_id IS NULL; + + DELETE FROM complaint c + USING result r + WHERE r.id = c.result_id + AND r.exercise_id IS NULL; + + + + UPDATE participant_score ps + JOIN result r ON r.id = ps.last_result_id + SET ps.last_result_id = NULL + WHERE r.exercise_id IS NULL; + UPDATE participant_score ps + JOIN result r ON r.id = ps.last_rated_result_id + SET ps.last_rated_result_id = NULL + WHERE r.exercise_id IS NULL; + DELETE lft + FROM long_feedback_text lft + JOIN feedback f ON f.id = lft.feedback_id + JOIN result r ON r.id = f.result_id + WHERE r.exercise_id IS NULL; + + DELETE f + FROM feedback f + JOIN result r ON r.id = f.result_id + WHERE r.exercise_id IS NULL; + + DELETE a + FROM assessment_note a + JOIN result r ON r.id = a.result_id + WHERE r.exercise_id IS NULL; + + DELETE rr + FROM result_rating rr + JOIN result r ON r.id = rr.result_id + WHERE r.exercise_id IS NULL; + + DELETE c + FROM complaint c + JOIN result r ON r.id = c.result_id + WHERE r.exercise_id IS NULL; From 2670a00016983b629c080de3a277458cda0956fe Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Tue, 7 Oct 2025 23:39:44 +0200 Subject: [PATCH 19/30] add rollback --- .../changelog/20250901221600_changelog.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml index 0c76a09ed2bc..13c201c66a04 100644 --- a/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml +++ b/src/main/resources/config/liquibase/changelog/20250901221600_changelog.xml @@ -120,5 +120,20 @@ referencedTableName="exercise" referencedColumnNames="id" constraintName="fk_result_exercise_id"/> + + + + + + + + + + From 3f9ce53415ad81a693833b796c76c836f677ae73 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Wed, 8 Oct 2025 00:02:11 +0200 Subject: [PATCH 20/30] fix server style, guard against some empty clauses and introduce helper to prevent duplication --- .../assessment/service/ComplaintService.java | 22 ++++++++++++++----- .../service/course/CourseStatsService.java | 10 ++++----- .../exercise/service/SubmissionService.java | 20 ++++++++--------- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java b/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java index 9a8eac964504..d65b4486a2cb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/service/ComplaintService.java @@ -175,30 +175,42 @@ else if (participant instanceof Team) { } public long countComplaintsByExerciseIds(Set exerciseIds) { + if (exerciseIds == null || exerciseIds.isEmpty()) { + return 0L; + } return complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, ComplaintType.COMPLAINT); } - public long countMoreFeedbackRequestsByCourseId(Set exerciseIds) { + public long countMoreFeedbackRequestsByExerciseIds(Set exerciseIds) { + if (exerciseIds == null || exerciseIds.isEmpty()) { + return 0L; + } return complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, ComplaintType.MORE_FEEDBACK); } /** - * Counts the number of responses to complaints for the given course id + * Counts the number of responses to complaints for the given exercise ids * - * @param courseId the id of the course + * @param exerciseIds the ids of the exercises * @return the number of responses */ public long countComplaintResponsesByExerciseIds(Set exerciseIds) { + if (exerciseIds == null || exerciseIds.isEmpty()) { + return 0L; + } return complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, ComplaintType.COMPLAINT); } /** - * Counts the number of responses to feedback requests for the given course id + * Counts the number of responses to feedback requests for the given exercise ids * - * @param courseId the id of the course + * @param exerciseIds the ids of the exercises * @return the number of responses */ public long countMoreFeedbackRequestResponsesByExerciseIds(Set exerciseIds) { + if (exerciseIds == null || exerciseIds.isEmpty()) { + return 0L; + } return complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, ComplaintType.MORE_FEEDBACK); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java index ffabfa68fe61..126a1d16f016 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java @@ -272,13 +272,11 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin if (course.getComplaintsEnabled()) { start = System.currentTimeMillis(); currentAbsoluteComplaints = complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, COMPLAINT); - log.debug( - "complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull took {} ms for course with id {}", - System.currentTimeMillis() - start, course.getId()); + log.debug("complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType took {} ms for course with id {}", System.currentTimeMillis() - start, + course.getId()); start = System.currentTimeMillis(); currentMaxComplaints = complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, COMPLAINT); - log.debug("complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType took {} ms for course with id {}", - System.currentTimeMillis() - start, course.getId()); + log.debug("complaintRepository.countByExerciseIdsAndComplaintType took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); currentPercentageComplaints = calculatePercentage(currentAbsoluteComplaints, currentMaxComplaints); } @@ -334,7 +332,7 @@ public StatsForDashboardDTO getStatsForDashboardDTO(Course course) { stats.setNumberOfSubmissions(new DueDateStat(numberOfInTimeSubmissions, numberOfLateSubmissions)); - final long numberOfMoreFeedbackRequests = complaintService.countMoreFeedbackRequestsByCourseId(courseExerciseIds); + final long numberOfMoreFeedbackRequests = complaintService.countMoreFeedbackRequestsByExerciseIds(courseExerciseIds); stats.setNumberOfMoreFeedbackRequests(numberOfMoreFeedbackRequests); final long numberOfMoreFeedbackComplaintResponses = complaintService.countMoreFeedbackRequestResponsesByExerciseIds(courseExerciseIds); stats.setNumberOfOpenMoreFeedbackRequests(numberOfMoreFeedbackRequests - numberOfMoreFeedbackComplaintResponses); diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java index e9f222e3fea5..9c830ceb9875 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java @@ -396,15 +396,19 @@ public void hideDetails(Submission submission, User user) { public Result saveNewEmptyResult(Submission submission) { Result result = new Result(); result.setSubmission(submission); - if (submission.getParticipation() != null && submission.getParticipation().getExercise() != null) { - result.setExerciseId(submission.getParticipation().getExercise().getId()); - } + setExerciseIdFromSubmission(submission, result); submission.addResult(result); result = resultRepository.save(result); submissionRepository.save(submission); return result; } + private static void setExerciseIdFromSubmission(Submission submission, Result result) { + if (submission.getParticipation() != null && submission.getParticipation().getExercise() != null) { + result.setExerciseId(submission.getParticipation().getExercise().getId()); + } + } + /** * Copy Feedbacks from one Result to another Result * @@ -449,9 +453,7 @@ public Result copyResultFromPreviousRoundAndSave(Submission submission, Result o return saveNewEmptyResult(submission); } Result newResult = new Result(); - if (submission.getParticipation() != null && submission.getParticipation().getExercise() != null) { - newResult.setExerciseId(submission.getParticipation().getExercise().getId()); - } + setExerciseIdFromSubmission(submission, newResult); copyFeedbackToNewResult(newResult, oldResult); return copyResultContentAndAddToSubmission(submission, newResult, oldResult); } @@ -468,9 +470,7 @@ public Result copyResultFromPreviousRoundAndSave(Submission submission, Result o */ public Result createResultAfterComplaintResponse(Submission submission, Result oldResult, List feedbacks, String assessmentNoteText) { Result newResult = new Result(); - if (submission.getParticipation() != null && submission.getParticipation().getExercise() != null) { - newResult.setExerciseId(submission.getParticipation().getExercise().getId()); - } + setExerciseIdFromSubmission(submission, newResult); updateAssessmentNoteAfterComplaintResponse(newResult, assessmentNoteText, submission.getLatestResult().getAssessor()); copyFeedbackToResult(newResult, feedbacks); newResult = copyResultContentAndAddToSubmission(submission, newResult, oldResult); @@ -541,7 +541,7 @@ public void addResultWithFeedbackByCorrectionRound(StudentParticipation studentP var latestSubmission = studentParticipation.findLatestSubmission(); if (latestSubmission.isPresent() && latestSubmission.get().getResultForCorrectionRound(correctionRound) == null) { Result result = new Result(); - result.setExerciseId(studentParticipation.getExercise().getId()); + setExerciseIdFromSubmission(latestSubmission.get(), result); result.setAssessor(assessor); result.setCompletionDate(ZonedDateTime.now()); result.setScore(score, studentParticipation.getExercise().getCourseViaExerciseGroupOrCourseMember()); From b56d1d72c46ec743db815a79fab7699d879672a5 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Wed, 8 Oct 2025 00:13:50 +0200 Subject: [PATCH 21/30] fix server style again --- .../repository/ComplaintResponseRepository.java | 1 + .../artemis/assessment/service/ComplaintService.java | 12 ++++++++++++ .../core/service/course/CourseStatsService.java | 8 +++----- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java index eb2acf1a9bbe..b48bb2ff1412 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java @@ -30,6 +30,7 @@ public interface ComplaintResponseRepository extends ArtemisJpaRepository exerciseIds) { if (exerciseIds == null || exerciseIds.isEmpty()) { return 0L; @@ -181,6 +187,12 @@ public long countComplaintsByExerciseIds(Set exerciseIds) { return complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, ComplaintType.COMPLAINT); } + /** + * Counts the number of more feedback requests for the given exercise ids + * + * @param exerciseIds the ids of the exercises + * @return the number of more feedback requests + */ public long countMoreFeedbackRequestsByExerciseIds(Set exerciseIds) { if (exerciseIds == null || exerciseIds.isEmpty()) { return 0L; diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java index 126a1d16f016..4b78fd0fe0bd 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/course/CourseStatsService.java @@ -287,13 +287,11 @@ public CourseManagementDetailViewDTO getStatsForDetailView(Course course, Gradin if (course.getRequestMoreFeedbackEnabled()) { start = System.currentTimeMillis(); currentAbsoluteMoreFeedbacks = complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType(exerciseIds, MORE_FEEDBACK); - log.debug( - "complaintResponseRepository.countByComplaint_Result_Submission_Participation_Exercise_Course_Id_AndComplaint_ComplaintType_AndSubmittedTimeIsNotNull took {} ms for course with id {}", - System.currentTimeMillis() - start, course.getId()); + log.debug("complaintResponseRepository.countComplaintResponsesForExerciseIdsAndComplaintType took {} ms for course with id {}", System.currentTimeMillis() - start, + course.getId()); start = System.currentTimeMillis(); currentMaxMoreFeedbacks = complaintRepository.countByExerciseIdsAndComplaintType(exerciseIds, MORE_FEEDBACK); - log.debug("complaintRepository.countByResult_Submission_Participation_Exercise_Course_IdAndComplaintType took {} ms for course with id {}", - System.currentTimeMillis() - start, course.getId()); + log.debug("complaintRepository.countByExerciseIdsAndComplaintType took {} ms for course with id {}", System.currentTimeMillis() - start, course.getId()); currentPercentageMoreFeedbacks = calculatePercentage(currentAbsoluteMoreFeedbacks, currentMaxMoreFeedbacks); } double currentAbsoluteAverageScore = roundScoreSpecifiedByCourseSettings((averageScoreForCourse / 100.0) * currentMaxAverageScore, course); From b36c0a81228f4987af789a0cc6ba6b9fc48046e4 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Wed, 8 Oct 2025 13:00:39 +0200 Subject: [PATCH 22/30] down to 511 test failures --- .../aet/artemis/assessment/domain/Result.java | 5 +++++ .../ComplaintResponseIntegrationTest.java | 1 + .../ResultServiceIntegrationTest.java | 14 +++++++------- .../TutorParticipationIntegrationTest.java | 2 ++ .../assessment/util/ComplaintUtilService.java | 2 ++ .../athena/AthenaResourceIntegrationTest.java | 1 + .../CourseCompetencyIntegrationTest.java | 1 + .../artemis/core/CleanupIntegrationTest.java | 2 ++ .../core/service/AssessmentServiceTest.java | 6 ++++++ .../exam/ExamParticipationIntegrationTest.java | 4 ++++ .../exam/StudentExamIntegrationTest.java | 4 +++- .../ParticipationIntegrationTest.java | 4 ++++ .../util/ParticipationFactory.java | 17 +++++++++++++++++ .../util/ParticipationUtilService.java | 6 ++++++ .../exercise/service/SubmissionServiceTest.java | 4 ++++ .../util/FileUploadExerciseUtilService.java | 1 + .../iris/PyrisEventSystemIntegrationTest.java | 1 + .../ModelingAssessmentIntegrationTest.java | 1 + .../ModelingSubmissionIntegrationTest.java | 2 ++ .../util/ModelingExerciseUtilService.java | 2 ++ .../ProgrammingAssessmentIntegrationTest.java | 4 ++++ .../ProgrammingExerciseGradingServiceTest.java | 2 ++ ...ingExerciseParticipationIntegrationTest.java | 12 +++++++++--- .../util/ProgrammingExerciseUtilService.java | 5 +++++ .../text/util/TextExerciseUtilService.java | 2 ++ 25 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java index a5ebbdaf139c..27c4c4f4a8ee 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java @@ -173,6 +173,11 @@ public Result score(Double score) { return this; } + public Result exerciseId(long exerciseId) { + this.exerciseId = exerciseId; + return this; + } + public Instant getLastModifiedDate() { return lastModifiedDate; } diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/ComplaintResponseIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/ComplaintResponseIntegrationTest.java index 430a7455a642..a3f79c2d4515 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/ComplaintResponseIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/ComplaintResponseIntegrationTest.java @@ -87,6 +87,7 @@ void initTestCase() throws Exception { result.setHasComplaint(true); submission.addResult(result); result.setSubmission(submission); + result.setExerciseId(textExercise.getId()); result = resultRepository.saveAndFlush(result); submissionRepository.save(submission); diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/ResultServiceIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/ResultServiceIntegrationTest.java index 538feb457166..b4225ff26ad4 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/ResultServiceIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/ResultServiceIntegrationTest.java @@ -570,7 +570,7 @@ void deleteResultStudent() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createResultForExternalSubmission() throws Exception { - Result result = new Result().rated(false); + Result result = new Result().rated(false).exerciseId(modelingExercise.getId()); var createdResult = request.postWithResponseBody( "/api/assessment/exercises/" + modelingExercise.getId() + "/external-submission-results?studentLogin=" + TEST_PREFIX + "student1", result, Result.class, HttpStatus.CREATED); @@ -582,7 +582,7 @@ void createResultForExternalSubmission() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createResultForExternalSubmission_wrongExerciseId() throws Exception { - Result result = new Result().rated(false); + Result result = new Result().rated(false).exerciseId(modelingExercise.getId()); long randomId = 2145; var createdResult = request.postWithResponseBody("/api/assessment/exercises/" + randomId + "/external-submission-results", result, Result.class, HttpStatus.BAD_REQUEST); assertThat(createdResult).isNull(); @@ -594,7 +594,7 @@ void createResultForExternalSubmission_programmingExercise() throws Exception { var studentLogin = TEST_PREFIX + "student1"; User user = userTestRepository.findOneByLogin(studentLogin).orElseThrow(); mockConnectorRequestsForStartParticipation(programmingExercise, user.getParticipantIdentifier(), Set.of(user), true); - Result result = new Result().rated(false); + Result result = new Result().rated(false).exerciseId(programmingExercise.getId()); programmingExercise.setDueDate(ZonedDateTime.now().minusMinutes(5)); programmingExerciseRepository.save(programmingExercise); var createdResult = request.postWithResponseBody(externalResultPath(programmingExercise.getId(), studentLogin), result, Result.class, HttpStatus.CREATED); @@ -611,21 +611,21 @@ void createResultForExternalSubmission_quizExercise(QuizMode quizMode) throws Ex var quizExercise = QuizExerciseFactory.generateQuizExercise(now.minusDays(1), now.minusHours(2), quizMode, course); course.addExercises(quizExercise); quizExerciseRepository.save(quizExercise); - Result result = new Result().rated(false); + Result result = new Result().rated(false).exerciseId(quizExercise.getId()); request.postWithResponseBody(externalResultPath(quizExercise.getId(), TEST_PREFIX + "student1"), result, Result.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createResultForExternalSubmission_studentNotInTheCourse() throws Exception { - Result result = new Result().rated(false); + Result result = new Result().rated(false).exerciseId(modelingExercise.getId()); request.postWithResponseBody(externalResultPath(modelingExercise.getId(), TEST_PREFIX + "student11"), result, Result.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createResultForExternalSubmissionExam() throws Exception { - Result result = new Result().rated(false); + Result result = new Result().rated(false).exerciseId(this.examModelingExercise.getId()); request.postWithResponseBody("/api/assessment/exercises/" + this.examModelingExercise.getId() + "/external-submission-results?studentLogin=student1", result, Result.class, HttpStatus.BAD_REQUEST); } @@ -639,7 +639,7 @@ private String externalResultPath(long exerciseId, String studentLogin) { void createResultForExternalSubmission_dueDateNotPassed() throws Exception { modelingExercise.setDueDate(ZonedDateTime.now().plusHours(1)); modelingExerciseRepository.save(modelingExercise); - Result result = new Result().rated(false); + Result result = new Result().rated(false).exerciseId(modelingExercise.getId()); request.postWithResponseBody(externalResultPath(modelingExercise.getId(), TEST_PREFIX + "student1"), result, Result.class, HttpStatus.BAD_REQUEST); } diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/TutorParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/TutorParticipationIntegrationTest.java index e57f5a0dde3f..d93ba0e6f386 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/TutorParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/TutorParticipationIntegrationTest.java @@ -217,6 +217,7 @@ private ExampleSubmission prepareTextExampleSubmission(boolean usedForTutorial) if (usedForTutorial) { var result = submissionService.saveNewEmptyResult(exampleSubmission.getSubmission()); result.setExampleResult(true); + result.setExerciseId(textExercise.getId()); var feedback = ParticipationFactory.createManualTextFeedback(1D, textBlockIds.getFirst()); var gradingCriterion = ExerciseFactory.generateGradingCriterion("criterion"); @@ -240,6 +241,7 @@ private ExampleSubmission prepareModelingExampleSubmission(boolean usedForTutori if (usedForTutorial) { var result = submissionService.saveNewEmptyResult(exampleSubmission.getSubmission()); result.setExampleResult(true); + result.setExerciseId(modelingExercise.getId()); resultRepository.save(result); } request.postWithResponseBody("/api/assessment/exercises/" + modelingExercise.getId() + "/tutor-participations", null, TutorParticipation.class, HttpStatus.CREATED); diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/util/ComplaintUtilService.java b/src/test/java/de/tum/cit/aet/artemis/assessment/util/ComplaintUtilService.java index e9d15ea7f45d..8f63b5397d27 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/util/ComplaintUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/util/ComplaintUtilService.java @@ -104,6 +104,7 @@ public ComplaintResponse createInitialEmptyResponse(String loginOfTutor, Complai public void addComplaints(String studentLogin, Submission submission, int numberOfComplaints, ComplaintType complaintType) { for (int i = 0; i < numberOfComplaints; i++) { Result dummyResult = new Result().submission(submission); + dummyResult.setExerciseId(submission.getParticipation().getExercise().getId()); dummyResult = resultTestRepository.save(dummyResult); Complaint complaint = new Complaint().participant(userUtilService.getUserByLogin(studentLogin)).result(dummyResult).complaintType(complaintType); complaintRepo.save(complaint); @@ -138,6 +139,7 @@ public void addComplaintToSubmission(Submission submission, String userLogin, Co public void addTeamComplaints(Team team, Submission submission, int numberOfComplaints, ComplaintType complaintType) { for (int i = 0; i < numberOfComplaints; i++) { Result dummyResult = new Result().submission(submission); + dummyResult.setExerciseId(submission.getParticipation().getExercise().getId()); dummyResult = resultTestRepository.save(dummyResult); Complaint complaint = new Complaint().participant(team).result(dummyResult).complaintType(complaintType); complaintRepo.save(complaint); diff --git a/src/test/java/de/tum/cit/aet/artemis/athena/AthenaResourceIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/athena/AthenaResourceIntegrationTest.java index d1c64a909d21..c958d1f3556c 100644 --- a/src/test/java/de/tum/cit/aet/artemis/athena/AthenaResourceIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/athena/AthenaResourceIntegrationTest.java @@ -353,6 +353,7 @@ void testGetFeedbackSuggestionsAthenaEnabled() throws Exception { result.setSubmission(programmingSubmission); result.setAssessmentType(AssessmentType.MANUAL); result.setRated(true); + result.setExerciseId(programmingExercise.getId()); // Create example feedback so that Athena can process it var feedback = new Feedback(); feedback.setCredits(1.0); diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java index b2306592749e..498f6e14c27f 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java @@ -78,6 +78,7 @@ private Result createExerciseParticipationSubmissionAndResult(Exercise exercise, Result result = ParticipationFactory.generateResult(rated, scoreAwarded); result.setCompletionDate(ZonedDateTime.now()); result.setSubmission(submission); + result.setExerciseId(exercise.getId()); result = resultRepository.save(result); submission.addResult(result); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/CleanupIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/core/CleanupIntegrationTest.java index 1db3ffec0a91..b7ff5a53cca8 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/CleanupIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/CleanupIntegrationTest.java @@ -187,6 +187,7 @@ void testDeleteOrphans() throws Exception { orphanTeamScore = teamScoreRepository.save(orphanTeamScore); var orphanResult = new Result(); + orphanResult.setExerciseId(oldExercise.getId()); orphanResult = resultRepository.save(orphanResult); orphanFeedback.setResult(orphanResult); @@ -200,6 +201,7 @@ void testDeleteOrphans() throws Exception { Result nonOrphanResult = new Result(); nonOrphanResult.setSubmission(submission); + nonOrphanResult.setExerciseId(submission.getParticipation().getExercise().getId()); nonOrphanFeedback.setResult(nonOrphanResult); nonOrphanResult = resultRepository.save(nonOrphanResult); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/service/AssessmentServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/core/service/AssessmentServiceTest.java index 095e31181a87..e4d84efd55a9 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/service/AssessmentServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/service/AssessmentServiceTest.java @@ -174,6 +174,7 @@ void createFileUploadSubmissionAndCalculateScore() { var result = new Result(); result.setSubmission(submissionWithoutResult); result.setFeedbacks(feedbacks); + result.setExerciseId(exercise.getId()); submissionWithoutResult.addResult(result); resultRepository.submitResult(result, exercise); @@ -195,6 +196,7 @@ void createTextExerciseSubmissionAndCalculateScore() { var result = new Result(); result.setSubmission(submissionWithoutResult); result.setFeedbacks(feedbacks); + result.setExerciseId(exercise.getId()); submissionWithoutResult.addResult(result); resultRepository.submitResult(result, exercise); @@ -216,6 +218,7 @@ void createModelingExerciseSubmissionAndCalculateScore() { var result = new Result(); result.setSubmission(submissionWithoutResult); result.setFeedbacks(feedbacks); + result.setExerciseId(exercise.getId()); submissionWithoutResult.addResult(result); resultRepository.submitResult(result, exercise); @@ -239,6 +242,7 @@ void testRatedAfterSubmitResultWithDueDateEqualsSubmissionDateOfResult(boolean i var result = new Result(); result.setSubmission(submissionWithoutResult); result.setFeedbacks(feedbacks); + result.setExerciseId(exercise.getId()); submissionWithoutResult.addResult(result); if (isDueDateIndividual) { @@ -275,6 +279,7 @@ void testNotRatedAfterSubmitResultWithDueDateBeforeSubmissionDateOfResult() { var result = new Result(); result.setSubmission(submissionWithoutResult); result.setFeedbacks(feedbacks); + result.setExerciseId(exercise.getId()); submissionWithoutResult.addResult(result); resultRepository.submitResult(result, exercise); @@ -296,6 +301,7 @@ void testRatedAfterSubmitResultWithDueDateBeforeSubmissionDateOfResult() { var result = new Result(); result.setSubmission(submissionWithoutResult); result.setFeedbacks(feedbacks); + result.setExerciseId(exercise.getId()); submissionWithoutResult.addResult(result); resultRepository.submitResult(result, exercise); diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java index a9a6d7b4489e..9c25efddcc47 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java @@ -570,6 +570,7 @@ void testGetStatsForExamAssessmentDashboard(int numberOfCorrectionRounds) throws result.setAssessmentType(AssessmentType.SEMI_AUTOMATIC); result.setAssessor(examTutor1); result.setSubmission(submission); + result.setExerciseId(exercise.getId()); result = resultRepository.save(result); submission.addResult(result); submissionRepository.save(submission); @@ -664,6 +665,7 @@ private void lockAndAssessForSecondCorrection(Exam exam, Course course, List submissions = new HashSet<>(); submissions.add(submission); participation.setSubmissions(submissions); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java index 683f32b6c3c5..b8b94f0d1bee 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java @@ -1411,6 +1411,7 @@ void getParticipationWithLatestResult() throws Exception { participationUtilService.addResultToSubmission(null, null, participation.findLatestSubmission().orElseThrow()); var result = ParticipationFactory.generateResult(true, 70D); result.submission(submission).setCompletionDate(ZonedDateTime.now().minusHours(2)); + result.setExerciseId(textExercise.getId()); resultRepository.save(result); var actualParticipation = request.get("/api/exercise/participations/" + participation.getId() + "/with-latest-result", HttpStatus.OK, StudentParticipation.class); @@ -1717,6 +1718,7 @@ void whenRequestFeedbackForExam_thenFail() throws Exception { var result = ParticipationFactory.generateResult(true, 100).submission(submission); result.setCompletionDate(ZonedDateTime.now()); + result.setExerciseId(programmingExercise.getId()); resultRepository.save(result); request.putAndExpectError("/api/exercise/exercises/" + programmingExercise.getId() + "/request-feedback", null, HttpStatus.BAD_REQUEST, "preconditions not met"); @@ -1746,6 +1748,7 @@ void whenFeedbackRequestedAndDeadlinePassed_thenFail() throws Exception { var result = ParticipationFactory.generateResult(true, 100).submission(submission); result.setCompletionDate(ZonedDateTime.now()); + result.setExerciseId(programmingExercise.getId()); resultRepository.save(result); request.putAndExpectError("/api/exercise/exercises/" + programmingExercise.getId() + "/request-feedback", null, HttpStatus.BAD_REQUEST, "feedbackRequestAfterDueDate"); @@ -1775,6 +1778,7 @@ void whenFeedbackRequestedAndRateLimitExceeded_thenFail() throws Exception { var result = ParticipationFactory.generateResult(true, 100).submission(submission); result.setCompletionDate(ZonedDateTime.now()); + result.setExerciseId(programmingExercise.getId()); resultRepository.save(result); // generate 5 athena results diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationFactory.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationFactory.java index 931b3bb54aae..c0cb0ffd20da 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationFactory.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationFactory.java @@ -437,6 +437,7 @@ public static ProgrammingExerciseStudentParticipation generateTeamProgrammingExe /** * Generates a Result with the given arguments. + * NOTE: The caller MUST set exerciseId on the returned Result before saving it to the database. * * @param rated True, if the Result is rated * @param score The score of the Result @@ -449,6 +450,22 @@ public static Result generateResult(boolean rated, double score) { return result; } + /** + * Generates a Result with the given arguments including exercise ID. + * + * @param rated True, if the Result is rated + * @param score The score of the Result + * @param exerciseId The ID of the exercise this result belongs to + * @return The generated Result + */ + public static Result generateResult(boolean rated, double score, Long exerciseId) { + Result result = new Result(); + result.setRated(rated); + result.setScore(score); + result.setExerciseId(exerciseId); + return result; + } + /** * Generates a SubmissionVersion for the given Submission, User, and content. * diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java index 33692fe305f1..1fe58ebaf3f5 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java @@ -407,6 +407,7 @@ public ProgrammingExerciseStudentParticipation addStudentParticipationForProgram */ public Result addResultToSubmission(AssessmentType type, ZonedDateTime completionDate, Submission submission, boolean successful, boolean rated, double score) { Result result = new Result().submission(submission).successful(successful).rated(rated).score(score).assessmentType(type).completionDate(completionDate); + result.setExerciseId(submission.getParticipation().getExercise().getId()); return resultRepo.save(result); } @@ -420,6 +421,7 @@ public Result addResultToSubmission(AssessmentType type, ZonedDateTime completio */ public Result addResultToSubmission(AssessmentType assessmentType, ZonedDateTime completionDate, Submission submission) { Result result = new Result().submission(submission).successful(true).rated(true).score(100D).assessmentType(assessmentType).completionDate(completionDate); + result.setExerciseId(submission.getParticipation().getExercise().getId()); submission.addResult(result); result = resultRepo.save(result); submissionRepository.save(submission); @@ -439,6 +441,7 @@ public Result addResultToSubmission(AssessmentType assessmentType, ZonedDateTime public Result addResultToSubmission(AssessmentType assessmentType, ZonedDateTime completionDate, Submission submission, String assessorLogin, List feedbacks) { Result result = new Result().submission(submission).assessmentType(assessmentType).completionDate(completionDate).feedbacks(feedbacks); result.setAssessor(userUtilService.getUserByLogin(assessorLogin)); + result.setExerciseId(submission.getParticipation().getExercise().getId()); return resultRepo.save(result); } @@ -451,6 +454,7 @@ public Result addResultToSubmission(AssessmentType assessmentType, ZonedDateTime public Result addResultToSubmission(Participation participation, Submission submission) { Result result = new Result().submission(submission).successful(true).score(100D).rated(true).completionDate(ZonedDateTime.now()); result.setSubmission(submission); + result.setExerciseId(participation.getExercise().getId()); result = resultRepo.save(result); submission.addResult(result); submission.setParticipation(participation); @@ -557,6 +561,7 @@ public Submission addResultToSubmission(final Submission submission, AssessmentT Result result = new Result().submission(submission).assessmentType(assessmentType).score(score).rated(rated).completionDate(completionDate); result.setAssessor(user); result.setSubmission(submission); + result.setExerciseId(submission.getParticipation().getExercise().getId()); result = resultRepo.save(result); submission.addResult(result); var savedSubmission = submissionRepository.save(submission); @@ -756,6 +761,7 @@ public Submission addSubmissionWithFinishedResultsWithAssessor(StudentParticipat result.setAssessor(userUtilService.getUserByLogin(assessorLogin)); result.setCompletionDate(ZonedDateTime.now()); result.setSubmission(submission); + result.setExerciseId(participation.getExercise().getId()); submission.setParticipation(participation); submission.addResult(result); submission = saveSubmissionToRepo(submission); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/service/SubmissionServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/service/SubmissionServiceTest.java index 86c59a19b2b7..0bfc5ce10345 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/service/SubmissionServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/service/SubmissionServiceTest.java @@ -344,6 +344,7 @@ void testTextExerciseGetRandomSubmissionEligibleForNewAssessmentOneAssessmentsWi resultWithLock = submissionService.saveNewEmptyResult(submission1); resultWithLock.setAssessor(tutor1); resultWithLock.setAssessmentType(AssessmentType.MANUAL); + resultWithLock.setExerciseId(examTextExercise.getId()); resultRepository.save(resultWithLock); // checks @@ -407,6 +408,7 @@ void testTextExerciseGetRandomSubmissionEligibleForNewAssessmentOneAssessmentsIn resultForSecondCorrectionWithLock = submissionService.saveNewEmptyResult(submission1); resultForSecondCorrectionWithLock.setAssessor(tutor2); resultForSecondCorrectionWithLock.setAssessmentType(AssessmentType.MANUAL); + resultForSecondCorrectionWithLock.setExerciseId(examTextExercise.getId()); resultRepository.save(resultForSecondCorrectionWithLock); // checks @@ -498,6 +500,7 @@ void testModelingExerciseGetRandomSubmissionEligibleForNewAssessmentOneAssessmen resultWithLock = submissionService.saveNewEmptyResult(submission1); resultWithLock.setAssessor(tutor1); resultWithLock.setAssessmentType(AssessmentType.MANUAL); + resultWithLock.setExerciseId(examModelingExercise.getId()); resultRepository.save(resultWithLock); // checks @@ -559,6 +562,7 @@ void testModelingExerciseGetRandomSubmissionEligibleForNewAssessmentOneAssessmen Result resultForSecondCorrectionWithLock = submissionService.saveNewEmptyResult(submission1); resultForSecondCorrectionWithLock.setAssessor(tutor2); resultForSecondCorrectionWithLock.setAssessmentType(AssessmentType.MANUAL); + resultForSecondCorrectionWithLock.setExerciseId(examModelingExercise.getId()); resultRepository.save(resultForSecondCorrectionWithLock); // checks diff --git a/src/test/java/de/tum/cit/aet/artemis/fileupload/util/FileUploadExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/fileupload/util/FileUploadExerciseUtilService.java index 169cb5544f4b..2bb47eca0a52 100644 --- a/src/test/java/de/tum/cit/aet/artemis/fileupload/util/FileUploadExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/fileupload/util/FileUploadExerciseUtilService.java @@ -240,6 +240,7 @@ public FileUploadSubmission saveFileUploadSubmissionWithResultAndAssessorFeedbac } result.setFeedbacks(feedbacks); result.setSubmission(fileUploadSubmission); + result.setExerciseId(exercise.getId()); result = resultRepo.save(result); for (Feedback feedback : feedbacks) { feedback.setResult(result); diff --git a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemIntegrationTest.java index eaabe6e2f9f8..3c167259abe6 100644 --- a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemIntegrationTest.java @@ -155,6 +155,7 @@ private Result createSubmission(ProgrammingExerciseStudentParticipation studentP Result result = ParticipationFactory.generateResult(true, score); result.setSubmission(submission); + result.setExerciseId(studentParticipation.getExercise().getId()); result.completionDate(ZonedDateTime.now()); result.setAssessmentType(AssessmentType.AUTOMATIC); submission.addResult(result); diff --git a/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingAssessmentIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingAssessmentIntegrationTest.java index 0102ee561928..f0ae0cf4f1d6 100644 --- a/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingAssessmentIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingAssessmentIntegrationTest.java @@ -856,6 +856,7 @@ void overrideAssessmentAfterComplaint() throws Exception { firstResult.setAssessor(tutor1); firstResult.setHasComplaint(true); firstResult.setSubmission(submission); + firstResult.setExerciseId(modelingExercise.getId()); firstResult = resultRepository.saveAndFlush(firstResult); submission.addResult(firstResult); diff --git a/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingSubmissionIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingSubmissionIntegrationTest.java index aa434594fa34..86ca528da2cf 100644 --- a/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingSubmissionIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingSubmissionIntegrationTest.java @@ -328,6 +328,7 @@ void injectResultOnSubmissionUpdate() throws Exception { result.setScore(100D); result.setRated(true); result.setAssessor(user); + result.setExerciseId(classExercise.getId()); submission.addResult(result); ModelingSubmission storedSubmission = request.postWithResponseBody("/api/modeling/exercises/" + classExercise.getId() + "/modeling-submissions", submission, ModelingSubmission.class); @@ -1029,6 +1030,7 @@ private Result createResult(AssessmentType assessmentType, ModelingSubmission su if (assessor != null) { result.setAssessor(assessor); } + result.setExerciseId(participation.getExercise().getId()); resultRepository.save(result); submission.addResult(result); modelingSubmissionRepo.save(submission); diff --git a/src/test/java/de/tum/cit/aet/artemis/modeling/util/ModelingExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/modeling/util/ModelingExerciseUtilService.java index 6d95be1de698..1ae9d6f4a0fb 100644 --- a/src/test/java/de/tum/cit/aet/artemis/modeling/util/ModelingExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/modeling/util/ModelingExerciseUtilService.java @@ -252,6 +252,7 @@ public ModelingSubmission addModelingSubmissionWithEmptyResult(ModelingExercise submission = modelSubmissionService.handleModelingSubmission(submission, exercise, user); Result result = new Result(); result.setSubmission(submission); + result.setExerciseId(exercise.getId()); result = resultRepo.save(result); submission.addResult(result); studentParticipationRepo.save(participation); @@ -316,6 +317,7 @@ public ModelingSubmission addModelingSubmissionWithResultAndAssessor(ModelingExe result.setAssessmentType(AssessmentType.MANUAL); submission = modelingSubmissionRepo.save(submission); result.setSubmission(submission); + result.setExerciseId(exercise.getId()); result = resultRepo.save(result); studentParticipationRepo.save(participation); result = resultRepo.save(result); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingAssessmentIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingAssessmentIntegrationTest.java index 77fc6f708369..0093892c3137 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingAssessmentIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingAssessmentIntegrationTest.java @@ -105,6 +105,7 @@ void initTestCase() { manualResult.setAssessmentType(AssessmentType.SEMI_AUTOMATIC); manualResult.rated(true); manualResult.setSubmission(programmingSubmission); + manualResult.setExerciseId(programmingExercise.getId()); doReturn(ObjectId.fromString(dummyHash)).when(gitService).getLastCommitHash(ArgumentMatchers.any()); } @@ -565,6 +566,7 @@ void shouldKeepExistingLongFeedbackWhenSavingAnAssessment(boolean submit) throws manualLongFeedback.setDetailText(longText); var result = new Result().feedbacks(List.of(manualLongFeedback)).score(0.0); result.setRated(true); + result.setExerciseId(programmingExercise.getId()); result = resultRepository.save(result); LinkedMultiValueMap params = new LinkedMultiValueMap<>(); @@ -585,6 +587,7 @@ void shouldUpdateUnreferencedLongFeedbackWhenSavingAnAssessment(boolean submit) var longText = "abc".repeat(5000); manualLongFeedback.setDetailText(longText); var result = new Result().feedbacks(List.of(manualLongFeedback)).score(0.0).rated(true); + result.setExerciseId(programmingExercise.getId()); result = resultRepository.save(result); var newLongText = "def".repeat(5000); @@ -948,6 +951,7 @@ void overrideProgrammingAssessmentAfterComplaint() throws Exception { initialResult.setAssessor(tutor1); initialResult.setHasComplaint(true); initialResult.setAssessmentType(AssessmentType.SEMI_AUTOMATIC); + initialResult.setExerciseId(programmingExercise.getId()); initialResult = resultRepository.save(initialResult); programmingSubmission.addResult(initialResult); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseGradingServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseGradingServiceTest.java index abea0971ae69..23ca5be27ec8 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseGradingServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseGradingServiceTest.java @@ -876,6 +876,7 @@ private Result updateAndSaveAutomaticResult(Result result, boolean test1Passes, var feedback3 = new Feedback().result(result).testCase(tests.get("test3")).positive(test3Passes).type(FeedbackType.AUTOMATIC); result.addFeedback(feedback3); result.rated(true).successful(test1Passes && test2Passes && test3Passes).completionDate(ZonedDateTime.now()).assessmentType(AssessmentType.AUTOMATIC); + result.setExerciseId(programmingExercise.getId()); gradingService.calculateScoreForResult(result, programmingExercise, true); return resultRepository.save(result); } @@ -1455,6 +1456,7 @@ private void updateAndSaveAutomaticResult(Result result, boolean test1Passes, bo .type(FeedbackType.AUTOMATIC).positive(false)); result.rated(true).successful(test1Passes && test2Passes && test3Passes).completionDate(completionDate).assessmentType(AssessmentType.AUTOMATIC); + result.setExerciseId(programmingExerciseSCAEnabled.getId()); gradingService.calculateScoreForResult(result, programmingExerciseSCAEnabled, true); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java index 12f2ffc38941..1d4de2e45a59 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java @@ -576,7 +576,9 @@ void testGetLatestPendingSubmission_notProgrammingParticipation() throws Excepti void testGetLatestPendingSubmissionIfNotExists_student() throws Exception { // Submission has a result, therefore not considered pending. - Result result = resultRepository.save(new Result()); + Result result = new Result(); + result.setExerciseId(programmingExercise.getId()); + result = resultRepository.save(result); ProgrammingSubmission submission = (ProgrammingSubmission) new ProgrammingSubmission().submissionDate(ZonedDateTime.now().minusSeconds(61L)); submission = programmingExerciseUtilService.addProgrammingSubmission(programmingExercise, submission, TEST_PREFIX + "student1"); submission.addResult(result); @@ -589,7 +591,9 @@ void testGetLatestPendingSubmissionIfNotExists_student() throws Exception { @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void testGetLatestPendingSubmissionIfNotExists_ta() throws Exception { // Submission has a result, therefore not considered pending. - Result result = resultRepository.save(new Result()); + Result result = new Result(); + result.setExerciseId(programmingExercise.getId()); + result = resultRepository.save(result); ProgrammingSubmission submission = (ProgrammingSubmission) new ProgrammingSubmission().submissionDate(ZonedDateTime.now().minusSeconds(61L)); submission = programmingExerciseUtilService.addProgrammingSubmission(programmingExercise, submission, TEST_PREFIX + "student1"); submission.addResult(result); @@ -602,7 +606,9 @@ void testGetLatestPendingSubmissionIfNotExists_ta() throws Exception { @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testGetLatestPendingSubmissionIfNotExists_instructor() throws Exception { // Submission has a result, therefore not considered pending. - Result result = resultRepository.save(new Result()); + Result result = new Result(); + result.setExerciseId(programmingExercise.getId()); + result = resultRepository.save(result); ProgrammingSubmission submission = (ProgrammingSubmission) new ProgrammingSubmission().submissionDate(ZonedDateTime.now().minusSeconds(61L)); submission = programmingExerciseUtilService.addProgrammingSubmission(programmingExercise, submission, TEST_PREFIX + "student1"); submission.addResult(result); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseUtilService.java index 017e6ef88fc7..f79b5d9baef5 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseUtilService.java @@ -713,6 +713,7 @@ public Result addProgrammingSubmissionWithResult(ProgrammingExercise exercise, P submission.setParticipation(participation); submission.addResult(result); result.setSubmission(submission); + result.setExerciseId(exercise.getId()); programmingSubmissionRepo.save(submission); resultRepo.save(result); studentParticipationRepo.save(participation); @@ -737,6 +738,7 @@ public Result addTemplateSubmissionWithResult(ProgrammingExercise programmingExe submission.addResult(result); submission = submissionRepository.save(submission); result.setSubmission(submission); + result.setExerciseId(programmingExercise.getId()); result = resultRepo.save(result); templateProgrammingExerciseParticipationTestRepo.save(templateParticipation); return result; @@ -759,6 +761,7 @@ public Result addSolutionSubmissionWithResult(ProgrammingExercise programmingExe submission.addResult(result); submission = submissionRepository.save(submission); result.setSubmission(submission); + result.setExerciseId(programmingExercise.getId()); result = resultRepo.save(result); solutionProgrammingExerciseParticipationRepository.save(templateParticipation); return result; @@ -792,6 +795,7 @@ public ProgrammingSubmission addProgrammingSubmissionWithResultAndAssessor(Progr submission.setParticipation(participation); result.setSubmission(submission); + result.setExerciseId(exercise.getId()); result = resultRepo.save(result); submission.addResult(result); // Manual results are always rated @@ -815,6 +819,7 @@ public ProgrammingSubmission addProgrammingSubmissionToResultAndParticipation(Re submission.addResult(result); submission.setCommitHash(commitHash); result.setSubmission(submission); + result.setExerciseId(participation.getExercise().getId()); resultRepo.save(result); participation.addSubmission(submission); studentParticipationRepo.save(participation); diff --git a/src/test/java/de/tum/cit/aet/artemis/text/util/TextExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/text/util/TextExerciseUtilService.java index c969bffe6979..e5e1923c69bf 100644 --- a/src/test/java/de/tum/cit/aet/artemis/text/util/TextExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/text/util/TextExerciseUtilService.java @@ -300,6 +300,7 @@ private TextSubmission saveTextSubmissionWithResultAndAssessor(TextExercise exer result.setCompletionDate(ZonedDateTime.now()); } result.setSubmission(submission); + result.setExerciseId(exercise.getId()); result = resultRepo.save(result); submission.setParticipation(participation); submission.addResult(result); @@ -339,6 +340,7 @@ private TextSubmission saveTextSubmissionWithAthenaResult(TextExercise exercise, } result.setSubmission(submission); + result.setExerciseId(exercise.getId()); result = resultRepo.save(result); submission.setParticipation(participation); submission.addResult(result); From ae3bf01c8643d1e7a1cfc587b1cdebca4165bedb Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Wed, 8 Oct 2025 17:23:28 +0200 Subject: [PATCH 23/30] down to 57 test failures --- .../de/tum/cit/aet/artemis/assessment/domain/Result.java | 1 - .../programming/service/ProgrammingAssessmentService.java | 1 + .../de/tum/cit/aet/artemis/core/util/CourseUtilService.java | 5 +++++ .../participation/util/ParticipationUtilService.java | 3 +-- .../ProgrammingExerciseParticipationIntegrationTest.java | 5 +++-- .../programming/util/ProgrammingExerciseUtilService.java | 5 +++-- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java index 27c4c4f4a8ee..1e567383d8ab 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java @@ -135,7 +135,6 @@ public class Result extends DomainObject implements Comparable { private Instant lastModifiedDate; @Column(name = "exercise_id", nullable = false) - @JsonIgnore private long exerciseId; public ZonedDateTime getCompletionDate() { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingAssessmentService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingAssessmentService.java index ca46d4a76d8a..a67249f0b1cd 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingAssessmentService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingAssessmentService.java @@ -90,6 +90,7 @@ public Result saveAndSubmitManualAssessment(StudentParticipation participation, } newManualResult.setSubmission(submission); + newManualResult.setExerciseId(exercise.getId()); newManualResult.setHasComplaint(existingManualResult.getHasComplaint().orElse(false)); newManualResult = saveManualAssessment(newManualResult, assessor); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java index dcabd9384f65..498eb9941dcb 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java @@ -494,6 +494,11 @@ public List createCoursesWithExercisesAndLectures(String userPrefix, boo Result result3 = generateResult(false, 0D); Result result4 = generateResult(true, 12D); Result result5 = generateResult(false, 42D); + result1.setExerciseId(modelingExercise.getId()); + result2.setExerciseId(modelingExercise.getId()); + result3.setExerciseId(textExercise.getId()); + result4.setExerciseId(programmingExercise.getId()); + result5.setExerciseId(programmingExercise.getId()); participation1 = studentParticipationRepo.save(participation1); participation2 = studentParticipationRepo.save(participation2); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java index 1fe58ebaf3f5..e4802521a3b6 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java @@ -514,8 +514,7 @@ public Result addVariousVisibilityFeedbackToResult(Result result) { )); result.addFeedbacks(feedbacks); - resultRepo.save(result); - return result; + return resultRepo.save(result); } // @formatter:on diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java index 1d4de2e45a59..cfa9aec54ebb 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java @@ -160,7 +160,8 @@ void testGetParticipationWithLatestResult_multipleResultsAvailable(ZonedDateTime programmingExercise.setAssessmentDueDate(assessmentDueDate); programmingExerciseRepository.save(programmingExercise); // Add a parameterized second result - StudentParticipation participation = (StudentParticipation) firstResult.getSubmission().getParticipation(); + StudentParticipation participation = studentParticipationRepository.findWithEagerResultsAndFeedbackById(firstResult.getSubmission().getParticipation().getId()) + .orElseThrow(); Result secondResult = participationUtilService.addResultToSubmission(participation, participation.getSubmissions().iterator().next()); secondResult.successful(true).rated(true).score(100D).assessmentType(assessmentType).completionDate(completionDate); secondResult = participationUtilService.addVariousVisibilityFeedbackToResult(secondResult); @@ -533,7 +534,7 @@ void testGetLatestResultWithSubmission(boolean withSubmission) throws Exception assertThat(resultResponse.getFeedbacks()).noneMatch(Feedback::isAfterDueDate); assertThat(resultResponse.getFeedbacks()).containsExactlyInAnyOrderElementsOf(result.getFeedbacks()); - assertThat(result).usingRecursiveComparison().ignoringFields("submission", "feedbacks", "participation", "lastModifiedDate").isEqualTo(resultResponse); + assertThat(result).usingRecursiveComparison().ignoringFields("submission", "feedbacks", "participation", "lastModifiedDate", "exerciseId").isEqualTo(resultResponse); if (withSubmission) { assertThat(submission).isEqualTo(resultResponse.getSubmission()); } diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseUtilService.java index f79b5d9baef5..59b77b8d504c 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseUtilService.java @@ -733,12 +733,12 @@ public Result addTemplateSubmissionWithResult(ProgrammingExercise programmingExe submission = submissionRepository.save(submission); // TODO check if it needs to be persisted like before Result result = new Result(); + result.setExerciseId(programmingExercise.getId()); templateParticipation.addSubmission(submission); submission.setParticipation(templateParticipation); submission.addResult(result); submission = submissionRepository.save(submission); result.setSubmission(submission); - result.setExerciseId(programmingExercise.getId()); result = resultRepo.save(result); templateProgrammingExerciseParticipationTestRepo.save(templateParticipation); return result; @@ -756,12 +756,13 @@ public Result addSolutionSubmissionWithResult(ProgrammingExercise programmingExe ProgrammingSubmission submission = new ProgrammingSubmission(); submission = submissionRepository.save(submission); Result result = new Result(); + result.setExerciseId(programmingExercise.getId()); templateParticipation.addSubmission(submission); submission.setParticipation(templateParticipation); submission.addResult(result); submission = submissionRepository.save(submission); result.setSubmission(submission); - result.setExerciseId(programmingExercise.getId()); + result = resultRepo.save(result); solutionProgrammingExerciseParticipationRepository.save(templateParticipation); return result; From cc4f177150a0a8c5b625c1463de7e00d875ba70f Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Wed, 8 Oct 2025 17:31:10 +0200 Subject: [PATCH 24/30] down to 45 test failures --- .../cit/aet/artemis/core/util/CourseUtilService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java index 498eb9941dcb..47b62e65bc79 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseUtilService.java @@ -604,21 +604,26 @@ public Course createCourseWithAllExerciseTypesAndParticipationsAndSubmissionsAnd // Setup results Result resultModeling = generateResult(true, 100D); resultModeling.setAssessmentType(AssessmentType.MANUAL); + resultModeling.setExerciseId(modelingExercise.getId()); resultModeling.setCompletionDate(ZonedDateTime.now()); Result resultText = generateResult(true, 12D); + resultText.setExerciseId(textExercise.getId()); resultText.setAssessmentType(AssessmentType.MANUAL); resultText.setCompletionDate(ZonedDateTime.now()); Result resultFileUpload = generateResult(true, 0D); + resultFileUpload.setExerciseId(fileUploadExercise.getId()); resultFileUpload.setAssessmentType(AssessmentType.MANUAL); resultFileUpload.setCompletionDate(ZonedDateTime.now()); Result resultQuiz = generateResult(true, 0D); + resultQuiz.setExerciseId(quizExercise.getId()); resultQuiz.setAssessmentType(AssessmentType.AUTOMATIC); resultQuiz.setCompletionDate(ZonedDateTime.now()); Result resultProgramming = generateResult(true, 20D); + resultProgramming.setExerciseId(programmingExercise.getId()); resultProgramming.setAssessmentType(AssessmentType.AUTOMATIC); resultProgramming.setCompletionDate(ZonedDateTime.now()); @@ -772,22 +777,27 @@ public Course createCourseWithAllExerciseTypesAndParticipationsAndSubmissionsAnd // Setup results Result resultModeling = generateResult(true, 10D); + resultModeling.setExerciseId(modelingExercise.getId()); resultModeling.setAssessmentType(AssessmentType.MANUAL); resultModeling.setCompletionDate(ZonedDateTime.now()); Result resultText = generateResult(true, 12D); + resultText.setExerciseId(textExercise.getId()); resultText.setAssessmentType(AssessmentType.MANUAL); resultText.setCompletionDate(ZonedDateTime.now()); Result resultFileUpload = generateResult(true, 0D); + resultFileUpload.setExerciseId(fileUploadExercise.getId()); resultFileUpload.setAssessmentType(AssessmentType.MANUAL); resultFileUpload.setCompletionDate(ZonedDateTime.now()); Result resultQuiz = generateResult(true, 0D); + resultQuiz.setExerciseId(quizExercise.getId()); resultQuiz.setAssessmentType(AssessmentType.AUTOMATIC); resultQuiz.setCompletionDate(ZonedDateTime.now()); Result resultProgramming = generateResult(true, 20D); + resultProgramming.setExerciseId(programmingExercise.getId()); resultProgramming.setAssessmentType(AssessmentType.AUTOMATIC); resultProgramming.setCompletionDate(ZonedDateTime.now()); From 9a809efa891a08b75fb1f80b88a74c681f32a06e Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Wed, 8 Oct 2025 22:31:24 +0200 Subject: [PATCH 25/30] fix remaining tests --- .../assessment/service/AssessmentService.java | 7 ++++--- .../assessment/web/AssessmentResource.java | 4 ++-- .../artemis/exam/service/ExamQuizService.java | 1 + .../exercise/service/ExerciseImportService.java | 1 + .../exercise/service/SubmissionService.java | 13 ++++++++++++- .../text/api/TextSubmissionImportApi.java | 1 - .../text/service/TextAssessmentService.java | 1 + .../ExampleSubmissionIntegrationTest.java | 10 +++++----- .../TutorParticipationIntegrationTest.java | 5 ++--- .../ParticipationIntegrationTest.java | 1 + .../participation/SubmissionIntegrationTest.java | 16 ++++++++++------ .../util/ParticipationUtilService.java | 12 +++++++++++- .../exercise/service/SubmissionServiceTest.java | 2 ++ .../ModelingExerciseIntegrationTest.java | 4 ++-- .../text/TextExerciseIntegrationTest.java | 7 ++++--- 15 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/service/AssessmentService.java b/src/main/java/de/tum/cit/aet/artemis/assessment/service/AssessmentService.java index bf56794dcf07..d87d6905552d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/service/AssessmentService.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/service/AssessmentService.java @@ -264,16 +264,17 @@ private Result submitManualAssessment(long resultId, Exercise exercise) { * @param feedbackList the assessment as a feedback list that should be added to the result of the corresponding submission * @param resultId id of the result we want to save the feedbackList to, null if no result exists * @param assessmentNoteText the text of the assessment note of the result + * @param exerciseId the id of the exercise the assessment belongs to * @return the saved result */ - public Result saveManualAssessment(final Submission submission, final List feedbackList, Long resultId, String assessmentNoteText) { + public Result saveManualAssessment(final Submission submission, final List feedbackList, Long resultId, String assessmentNoteText, long exerciseId) { Result result = null; if (resultId != null) { result = resultRepository.findWithEagerSubmissionAndFeedbackAndTestCasesAndAssessmentNoteById(resultId).orElse(null); } if (result == null) { - result = submissionService.saveNewEmptyResult(submission); + result = submissionService.saveNewEmptyResult(submission, exerciseId); } // important to not lose complaint information when overriding an assessment @@ -325,7 +326,7 @@ public Result saveManualAssessment(final Submission submission, final List feedbackList, Long resultId, String assessmentNoteText, boolean submit) { - Result result = saveManualAssessment(submission, feedbackList, resultId, assessmentNoteText); + Result result = saveManualAssessment(submission, feedbackList, resultId, assessmentNoteText, exercise.getId()); if (!submit) { return result; } diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/web/AssessmentResource.java b/src/main/java/de/tum/cit/aet/artemis/assessment/web/AssessmentResource.java index 6ae8dfc7b597..93c611ccda97 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/web/AssessmentResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/web/AssessmentResource.java @@ -138,10 +138,10 @@ protected Result saveExampleAssessment(long exampleSubmissionId, List // as parameter resultId is not set, we use the latest Result, if no latest Result exists, we use null Result result; if (submission.getLatestResult() == null) { - result = assessmentService.saveManualAssessment(submission, feedbacks, null, null); + result = assessmentService.saveManualAssessment(submission, feedbacks, null, null, exercise.getId()); } else { - result = assessmentService.saveManualAssessment(submission, feedbacks, submission.getLatestResult().getId(), null); + result = assessmentService.saveManualAssessment(submission, feedbacks, submission.getLatestResult().getId(), null, exercise.getId()); } return resultRepository.submitResult(result, exercise); } diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamQuizService.java b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamQuizService.java index 20c5fd7bd292..7c2e06f8a9a5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamQuizService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamQuizService.java @@ -86,6 +86,7 @@ public void evaluateQuizParticipationsForTestRunAndTestExam(StudentExam studentE // calculate scores and update result and submission accordingly quizSubmission.calculateAndUpdateScores(quizExercise.getQuizQuestions()); result.evaluateQuizSubmission(quizExercise); + result.setExerciseId(quizExercise.getId()); // remove submission to follow save order for ordered collections result.setSubmission(null); if (studentExam.isTestExam()) { diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseImportService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseImportService.java index 5ad1f4b87268..5fd956c31cce 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseImportService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseImportService.java @@ -96,6 +96,7 @@ protected Result copyExampleResult(Result originalResult, Submission newSubmissi newResult.setAssessor(originalResult.getAssessor()); newResult.setCompletionDate(originalResult.getCompletionDate()); newResult.setExampleResult(true); + newResult.setExerciseId(originalResult.getExerciseId()); newResult.setRated(true); newResult.setScore(originalResult.getScore()); newResult.copyProgrammingExerciseCounters(originalResult); diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java index 9c830ceb9875..bf49c9cd9e1b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java @@ -394,9 +394,20 @@ public void hideDetails(Submission submission, User user) { * @return the newly created result */ public Result saveNewEmptyResult(Submission submission) { + return saveNewEmptyResult(submission, submission.getParticipation().getExercise().getId()); + } + + /** + * Creates a new Result object, assigns it to the given submission and stores the changes to the database. + * + * @param submission the submission for which a new result should be created + * @param exerciseId the id of the exercise to which the submission belongs + * @return the newly created result + */ + public Result saveNewEmptyResult(Submission submission, long exerciseId) { Result result = new Result(); result.setSubmission(submission); - setExerciseIdFromSubmission(submission, result); + result.setExerciseId(exerciseId); submission.addResult(result); result = resultRepository.save(result); submissionRepository.save(submission); diff --git a/src/main/java/de/tum/cit/aet/artemis/text/api/TextSubmissionImportApi.java b/src/main/java/de/tum/cit/aet/artemis/text/api/TextSubmissionImportApi.java index 47d731cdb570..6f40bae28411 100644 --- a/src/main/java/de/tum/cit/aet/artemis/text/api/TextSubmissionImportApi.java +++ b/src/main/java/de/tum/cit/aet/artemis/text/api/TextSubmissionImportApi.java @@ -39,7 +39,6 @@ public TextSubmissionImportApi(TextSubmissionRepository textSubmissionRepository public TextSubmission importStudentSubmission(long submissionId, long exerciseId, Map gradingInstructionCopyTracker) { TextSubmission textSubmission = textSubmissionRepository.findByIdWithEagerResultsAndFeedbackAndTextBlocksElseThrow(submissionId); checkGivenExerciseIdSameForSubmissionParticipation(exerciseId, textSubmission.getParticipation().getExercise().getId()); - // example submission does not need participation textSubmission.setParticipation(null); return textExerciseImportService.copySubmission(textSubmission, gradingInstructionCopyTracker); diff --git a/src/main/java/de/tum/cit/aet/artemis/text/service/TextAssessmentService.java b/src/main/java/de/tum/cit/aet/artemis/text/service/TextAssessmentService.java index 2b6a0534732c..b71a184bcca5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/text/service/TextAssessmentService.java +++ b/src/main/java/de/tum/cit/aet/artemis/text/service/TextAssessmentService.java @@ -72,6 +72,7 @@ public void prepareSubmissionForAssessment(TextSubmission textSubmission, @Nulla // We are the first ones to open assess this submission, we want to lock it. result = new Result(); result.setSubmission(textSubmission); + result.setExerciseId(participation.getExercise().getId()); resultService.createNewRatedManualResult(result); result.setCompletionDate(null); result = resultRepository.save(result); diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/ExampleSubmissionIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/ExampleSubmissionIntegrationTest.java index 840452ba00a1..7a5550a86d7e 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/ExampleSubmissionIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/ExampleSubmissionIntegrationTest.java @@ -262,7 +262,7 @@ void prepareExampleTextSubmissionForAssessmentShouldCreateBlocks() throws Except void createExampleTextAssessment() throws Exception { ExampleSubmission storedExampleSubmission = participationUtilService .addExampleSubmission(participationUtilService.generateExampleSubmission("Text. Submission.", textExercise, true)); - participationUtilService.addResultToSubmission(storedExampleSubmission.getSubmission(), AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(storedExampleSubmission.getSubmission(), AssessmentType.MANUAL, textExercise.getId()); final Result exampleResult = request.get( "/api/text/exercises/" + textExercise.getId() + "/submissions/" + storedExampleSubmission.getSubmission().getId() + "/example-result", HttpStatus.OK, Result.class); final Set blocks = ((TextSubmission) exampleResult.getSubmission()).getBlocks(); @@ -285,7 +285,7 @@ void createExampleTextAssessment() throws Exception { void createExampleTextAssessmentNotExistentId() throws Exception { ExampleSubmission storedExampleSubmission = participationUtilService .addExampleSubmission(participationUtilService.generateExampleSubmission("Text. Submission.", textExercise, true)); - participationUtilService.addResultToSubmission(storedExampleSubmission.getSubmission(), AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(storedExampleSubmission.getSubmission(), AssessmentType.MANUAL, textExercise.getId()); final Result exampleResult = request.get( "/api/text/exercises/" + textExercise.getId() + "/submissions/" + storedExampleSubmission.getSubmission().getId() + "/example-result", HttpStatus.OK, Result.class); final Set blocks = ((TextSubmission) exampleResult.getSubmission()).getBlocks(); @@ -304,7 +304,7 @@ void createExampleTextAssessmentNotExistentId() throws Exception { void createExampleTextAssessment_wrongExerciseId() throws Exception { ExampleSubmission storedExampleSubmission = participationUtilService .addExampleSubmission(participationUtilService.generateExampleSubmission("Text. Submission.", textExercise, true)); - participationUtilService.addResultToSubmission(storedExampleSubmission.getSubmission(), AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(storedExampleSubmission.getSubmission(), AssessmentType.MANUAL, textExercise.getId()); final Result exampleResult = request.get( "/api/text/exercises/" + textExercise.getId() + "/submissions/" + storedExampleSubmission.getSubmission().getId() + "/example-result", HttpStatus.OK, Result.class); final Set blocks = ((TextSubmission) exampleResult.getSubmission()).getBlocks(); @@ -334,7 +334,7 @@ void importExampleSubmissionWithTextSubmission() throws Exception { textBlock.setEndIndex(14); textExerciseUtilService.addAndSaveTextBlocksToTextSubmission(Set.of(textBlock), submission); - participationUtilService.addResultToSubmission(submission, AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(submission, AssessmentType.MANUAL, textExercise.getId()); // add one feedback for the created text block List textBlocks = new ArrayList<>(submission.getBlocks()); @@ -362,7 +362,7 @@ void importExampleSubmissionWithTextSubmission() throws Exception { void importExampleSubmissionWithModelingSubmission() throws Exception { ModelingSubmission submission = ParticipationFactory.generateModelingSubmission(validModel, true); submission = modelingExerciseUtilService.addModelingSubmission(modelingExercise, submission, TEST_PREFIX + "student1"); - participationUtilService.addResultToSubmission(submission, AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(submission, AssessmentType.MANUAL, modelingExercise.getId()); ExampleSubmission exampleSubmission = importExampleSubmission(modelingExercise.getId(), submission.getId(), HttpStatus.OK); assertThat(exampleSubmission.getId()).isNotNull(); diff --git a/src/test/java/de/tum/cit/aet/artemis/assessment/TutorParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/assessment/TutorParticipationIntegrationTest.java index d93ba0e6f386..c2dfd432fc1f 100644 --- a/src/test/java/de/tum/cit/aet/artemis/assessment/TutorParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/assessment/TutorParticipationIntegrationTest.java @@ -215,7 +215,7 @@ private ExampleSubmission prepareTextExampleSubmission(boolean usedForTutorial) exampleSubmission = exampleSubmissionService.save(exampleSubmission); if (usedForTutorial) { - var result = submissionService.saveNewEmptyResult(exampleSubmission.getSubmission()); + var result = submissionService.saveNewEmptyResult(exampleSubmission.getSubmission(), textExercise.getId()); result.setExampleResult(true); result.setExerciseId(textExercise.getId()); @@ -239,9 +239,8 @@ private ExampleSubmission prepareModelingExampleSubmission(boolean usedForTutori ExampleSubmission exampleSubmission = participationUtilService.generateExampleSubmission(validModel, modelingExercise, false, usedForTutorial); exampleSubmissionService.save(exampleSubmission); if (usedForTutorial) { - var result = submissionService.saveNewEmptyResult(exampleSubmission.getSubmission()); + var result = submissionService.saveNewEmptyResult(exampleSubmission.getSubmission(), modelingExercise.getId()); result.setExampleResult(true); - result.setExerciseId(modelingExercise.getId()); resultRepository.save(result); } request.postWithResponseBody("/api/assessment/exercises/" + modelingExercise.getId() + "/tutor-participations", null, TutorParticipation.class, HttpStatus.CREATED); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java index b8b94f0d1bee..34c424f7659b 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java @@ -1786,6 +1786,7 @@ void whenFeedbackRequestedAndRateLimitExceeded_thenFail() throws Exception { var athenaResult = ParticipationFactory.generateResult(false, 100).submission(submission); athenaResult.setCompletionDate(ZonedDateTime.now()); athenaResult.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA); + athenaResult.setExerciseId(programmingExercise.getId()); submission.addResult(athenaResult); resultRepository.save(athenaResult); } diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/SubmissionIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/SubmissionIntegrationTest.java index 3caabbb8eb00..6092738aef7c 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/SubmissionIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/SubmissionIntegrationTest.java @@ -50,9 +50,13 @@ class SubmissionIntegrationTest extends AbstractSpringIntegrationIndependentTest @Autowired private SubmissionVersionRepository submissionVersionRepository; + private TextExercise textExercise; + @BeforeEach void initTestCase() throws Exception { userUtilService.addUsers(TEST_PREFIX, 1, 1, 0, 1); + Course course = textExerciseUtilService.addCourseWithOneReleasedTextExercise(); + textExercise = ExerciseUtilService.getFirstExerciseWithType(course, TextExercise.class); } @Test @@ -62,11 +66,11 @@ void addMultipleResultsToOneSubmission() { Submission submission = new TextSubmission(); submission = submissionRepository.save(submission); - Result result1 = new Result().assessmentType(assessmentType).score(100D).rated(true); + Result result1 = new Result().assessmentType(assessmentType).score(100D).rated(true).exerciseId(textExercise.getId()); result1 = resultRepository.save(result1); result1.setSubmission(submission); - Result result2 = new Result().assessmentType(assessmentType).score(200D).rated(true); + Result result2 = new Result().assessmentType(assessmentType).score(200D).rated(true).exerciseId(textExercise.getId()); result2 = resultRepository.save(result2); result2.setSubmission(submission); @@ -90,14 +94,14 @@ void addMultipleResultsToOneSubmissionSavedSequentially() { Submission submission = new TextSubmission(); submission = submissionRepository.save(submission); - Result result1 = new Result().assessmentType(assessmentType).score(100D).rated(true); + Result result1 = new Result().assessmentType(assessmentType).score(100D).rated(true).exerciseId(textExercise.getId()); result1 = resultRepository.save(result1); result1.setSubmission(submission); submission.addResult(result1); submission = submissionRepository.save(submission); - Result result2 = new Result().assessmentType(assessmentType).score(200D).rated(true); + Result result2 = new Result().assessmentType(assessmentType).score(200D).rated(true).exerciseId(textExercise.getId()); result2 = resultRepository.save(result2); result2.setSubmission(submission); @@ -120,14 +124,14 @@ void updateMultipleResultsFromOneSubmission() { Submission submission = new TextSubmission(); submission = submissionRepository.save(submission); - Result result1 = new Result().assessmentType(assessmentType).score(100D).rated(true); + Result result1 = new Result().assessmentType(assessmentType).score(100D).rated(true).exerciseId(textExercise.getId()); result1 = resultRepository.save(result1); result1.setSubmission(submission); submission.addResult(result1); submission = submissionRepository.save(submission); - Result result2 = new Result().assessmentType(assessmentType).score(200D).rated(true); + Result result2 = new Result().assessmentType(assessmentType).score(200D).rated(true).exerciseId(textExercise.getId()); result2 = resultRepository.save(result2); result2.setSubmission(submission); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java index e4802521a3b6..bd872763e125 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/util/ParticipationUtilService.java @@ -557,10 +557,15 @@ public Result addFeedbackToResults(Result result) { * @return The updated Submission with eagerly loaded results and assessor */ public Submission addResultToSubmission(final Submission submission, AssessmentType assessmentType, User user, Double score, boolean rated, ZonedDateTime completionDate) { + return addResultToSubmission(submission, assessmentType, user, score, rated, completionDate, submission.getParticipation().getExercise().getId()); + } + + public Submission addResultToSubmission(final Submission submission, AssessmentType assessmentType, User user, Double score, boolean rated, ZonedDateTime completionDate, + long exerciseId) { Result result = new Result().submission(submission).assessmentType(assessmentType).score(score).rated(rated).completionDate(completionDate); result.setAssessor(user); result.setSubmission(submission); - result.setExerciseId(submission.getParticipation().getExercise().getId()); + result.setExerciseId(exerciseId); result = resultRepo.save(result); submission.addResult(result); var savedSubmission = submissionRepository.save(submission); @@ -578,6 +583,10 @@ public Submission addResultToSubmission(Submission submission, AssessmentType as return addResultToSubmission(submission, assessmentType, null, 100D, true, null); } + public Submission addResultToSubmission(Submission submission, AssessmentType assessmentType, long exerciseId) { + return addResultToSubmission(submission, assessmentType, null, 100D, true, null, exerciseId); + } + /** * Creates and saves a Result for the given Submission. The Result is rated, the score is 100, the completionDate is set to now. * @@ -914,6 +923,7 @@ public StudentParticipation addAssessmentWithFeedbackWithGradingInstructionsForE } Submission submissionWithParticipation = addSubmission(studentParticipation, submission); Result result = addResultToSubmission(studentParticipation, submissionWithParticipation); + result.setExerciseId(exercise.getId()); resultRepo.save(result); assertThat(exercise.getGradingCriteria()).isNotNull(); diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/service/SubmissionServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/service/SubmissionServiceTest.java index 0bfc5ce10345..0da0af4cf904 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/service/SubmissionServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/service/SubmissionServiceTest.java @@ -689,9 +689,11 @@ void testCopyFeedbackSetValues() { new Feedback().credits(-2.5) // should get positive = false ); Result oldResult = new Result(); + oldResult.setExerciseId(examTextExercise.getId()); oldResult.setFeedbacks(oldFeedbacks); Result newResult = new Result(); + newResult.setExerciseId(examTextExercise.getId()); List newFeedbacks = submissionService.copyFeedbackToNewResult(newResult, oldResult); diff --git a/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingExerciseIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingExerciseIntegrationTest.java index 2516e99abea0..066e6fbe9dea 100644 --- a/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingExerciseIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/modeling/ModelingExerciseIntegrationTest.java @@ -502,7 +502,7 @@ void importModelingExerciseWithExampleSubmissionFromCourseToCourse() throws Exce // Create example submission var exampleSubmission = participationUtilService.generateExampleSubmission("model", modelingExercise, true); exampleSubmission = participationUtilService.addExampleSubmission(exampleSubmission); - participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL, modelingExercise.getId()); var submission = submissionRepository.findWithEagerResultAndFeedbackAndAssessmentNoteById(exampleSubmission.getSubmission().getId()).orElseThrow(); participationUtilService.addFeedbackToResult(ParticipationFactory.generateFeedback().stream().findFirst().orElseThrow(), Objects.requireNonNull(submission.getLatestResult())); @@ -1076,7 +1076,7 @@ void testImportModelingExercise_setGradingInstructionForCopiedFeedback() throws // Create example submission var exampleSubmission = participationUtilService.generateExampleSubmission("model", modelingExercise, true); exampleSubmission = participationUtilService.addExampleSubmission(exampleSubmission); - participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL, modelingExercise.getId()); var submission = submissionRepository.findWithEagerResultAndFeedbackAndAssessmentNoteById(exampleSubmission.getSubmission().getId()).orElseThrow(); Feedback feedback = ParticipationFactory.generateFeedback().getFirst(); diff --git a/src/test/java/de/tum/cit/aet/artemis/text/TextExerciseIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/text/TextExerciseIntegrationTest.java index 86b320148fd7..7e6bfebdb6b0 100644 --- a/src/test/java/de/tum/cit/aet/artemis/text/TextExerciseIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/text/TextExerciseIntegrationTest.java @@ -620,7 +620,7 @@ void importTextExerciseWithExampleSubmissionFromCourseToCourse() throws Exceptio textExerciseUtilService.addAndSaveTextBlocksToTextSubmission(Set.of(manualTextBlock, automaticTextBlock), (TextSubmission) exampleSubmission.getSubmission()); - participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL, textExercise.getId()); TextExercise newTextExercise = request.postWithResponseBody("/api/text/text-exercises/import/" + textExercise.getId(), textExercise, TextExercise.class, HttpStatus.CREATED); assertThat(newTextExercise.getExampleSubmissions()).hasSize(1); @@ -1199,7 +1199,8 @@ void testReEvaluateAndUpdateTextExerciseWithExampleSubmission() throws Exception Set exampleSubmissionSet = new HashSet<>(); var exampleSubmission = participationUtilService.generateExampleSubmission("text", textExercise, true); exampleSubmission = participationUtilService.addExampleSubmission(exampleSubmission); - TextSubmission textSubmission = (TextSubmission) participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL); + TextSubmission textSubmission = (TextSubmission) participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL, + textExercise.getId()); textSubmission.setExampleSubmission(true); Result result = textSubmission.getLatestResult(); result.setExampleResult(true); @@ -1331,7 +1332,7 @@ void testImportTextExercise_setGradingInstructionForCopiedFeedback() throws Exce // Create example submission var exampleSubmission = participationUtilService.generateExampleSubmission("text", textExercise, true); exampleSubmission = participationUtilService.addExampleSubmission(exampleSubmission); - participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL); + participationUtilService.addResultToSubmission(exampleSubmission.getSubmission(), AssessmentType.MANUAL, textExercise.getId()); var submission = textSubmissionRepository.findByIdWithEagerResultsAndFeedbackAndTextBlocksElseThrow(exampleSubmission.getSubmission().getId()); Feedback feedback = ParticipationFactory.generateFeedback().getFirst(); From a7a00def7c0506fbcf857f3af41cf6173ec43dc3 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Wed, 8 Oct 2025 22:38:58 +0200 Subject: [PATCH 26/30] rabbit --- .../exercise/participation/ParticipationIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java index 34c424f7659b..256838ef192f 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java @@ -1781,7 +1781,7 @@ void whenFeedbackRequestedAndRateLimitExceeded_thenFail() throws Exception { result.setExerciseId(programmingExercise.getId()); resultRepository.save(result); - // generate 5 athena results + // generate 20 athena results for (int i = 0; i < 20; i++) { var athenaResult = ParticipationFactory.generateResult(false, 100).submission(submission); athenaResult.setCompletionDate(ZonedDateTime.now()); From cf1f2e5397a091b5f860e434ab85fc23f1b85dd1 Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Fri, 10 Oct 2025 14:05:35 +0200 Subject: [PATCH 27/30] code review --- .../ComplaintResponseRepository.java | 2 +- .../repository/ResultRepository.java | 40 +------------------ 2 files changed, 2 insertions(+), 40 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java index b48bb2ff1412..2cefa50cfec6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java @@ -30,7 +30,7 @@ public interface ComplaintResponseRepository extends ArtemisJpaRepository { @Query(""" - SELECT r FROM Result r LEFT JOIN FETCH r.assessor WHERE r.id = :resultId - """) + """) Optional findByIdWithEagerAssessor(@Param("resultId") long resultId); @EntityGraph(type = LOAD, attributePaths = "submission") @@ -936,37 +931,4 @@ SELECT MAX(r2.id) ) """) Set findLatestResultsByParticipationIds(@Param("participationIds") Set participationIds); - - @Transactional - @Modifying - @Query(""" - UPDATE Result r - SET r.exerciseId = ( - SELECT p.exercise.id - FROM Submission s - JOIN s.participation p - WHERE s.id = r.submission.id - ) - WHERE r.exerciseId IS NULL - AND r.id IN :ids - """) - int backfillExerciseIdBatch(@Param("ids") List ids); - - @Query(""" - SELECT r.id - FROM Result r - WHERE r.exerciseId IS NULL - ORDER BY r.id - """) - Slice findResultIdsWithoutExerciseId(Pageable pageable); - - @Query(""" - SELECT r.id - FROM Result r - WHERE r.exerciseId IS NULL - AND r.id > :afterId - ORDER BY r.id - """) - List findNextIds(@Param("afterId") long afterId, Pageable pageable); - } From 612b1156322b8acfd25a046fc4257b3f4b73c7ad Mon Sep 17 00:00:00 2001 From: Tobias Lippert Date: Fri, 10 Oct 2025 18:19:26 +0200 Subject: [PATCH 28/30] fix javadoc --- .../assessment/repository/ComplaintResponseRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java index 2cefa50cfec6..992fcae88107 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/ComplaintResponseRepository.java @@ -31,7 +31,7 @@ public interface ComplaintResponseRepository extends ArtemisJpaRepository Date: Sun, 12 Oct 2025 10:57:37 +0200 Subject: [PATCH 29/30] remove newline at the end of runconfig --- .idea/runConfigurations/Artemis_BuildAgent__Prod_.xml | 2 +- .../Artemis_Server__Dev__BuildAgent_LocalCI_.xml | 2 +- .idea/runConfigurations/Artemis_Server__Dev__Core__Jenkins_.xml | 2 +- .../Artemis_Server__Dev__LocalCI_BuildAgent__Athena_.xml | 2 +- .../Artemis_Server__Dev__LocalCI_BuildAgent__Theia_.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.idea/runConfigurations/Artemis_BuildAgent__Prod_.xml b/.idea/runConfigurations/Artemis_BuildAgent__Prod_.xml index 451ef02a82ad..fcea3536742d 100644 --- a/.idea/runConfigurations/Artemis_BuildAgent__Prod_.xml +++ b/.idea/runConfigurations/Artemis_BuildAgent__Prod_.xml @@ -11,4 +11,4 @@