diff --git a/src/main/java/de/tum/in/www1/artemis/config/PublicResourcesConfiguration.java b/src/main/java/de/tum/in/www1/artemis/config/PublicResourcesConfiguration.java index 8a9a88798481..338dd09582dd 100644 --- a/src/main/java/de/tum/in/www1/artemis/config/PublicResourcesConfiguration.java +++ b/src/main/java/de/tum/in/www1/artemis/config/PublicResourcesConfiguration.java @@ -82,7 +82,7 @@ private static String getClasspathPublicSubPathLocation(String... subPaths) { */ private static String getFileSystemPublicSubPathResourceLocation(String... subPaths) { var userDir = System.getProperty("user.dir"); - var morePaths = Stream.concat(Stream.of("public"), Arrays.stream(subPaths)).toList().toArray(new String[1 + subPaths.length]); + var morePaths = Stream.concat(Stream.of("public"), Arrays.stream(subPaths)).toArray(String[]::new); return "file:" + Path.of(userDir, morePaths) + "/"; } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/CourseExamExportService.java b/src/main/java/de/tum/in/www1/artemis/service/CourseExamExportService.java index f2db41af11d6..8edeb8dcff62 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/CourseExamExportService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/CourseExamExportService.java @@ -115,7 +115,7 @@ public Optional exportCourse(Course course, String outputDir, List Optional exportedCourse = zipExportedExercises(outputDir, exportErrors, notificationTopic, tmpCourseDir, exportedFiles); - log.info("Successfully exported course {}. The zip file is located at: {}", course.getId(), exportedCourse); + log.info("Successfully exported course {}. The zip file is located at: {}", course.getId(), exportedCourse.orElse(null)); return exportedCourse; } @@ -178,7 +178,7 @@ public Optional exportExam(Exam exam, String outputDir, List expor Optional exportedExamPath = zipExportedExercises(outputDir, exportErrors, notificationTopic, tempExamsDir, exportedExercises); - log.info("Successfully exported exam {}. The zip file is located at: {}", exam.getId(), exportedExamPath); + log.info("Successfully exported exam {}. The zip file is located at: {}", exam.getId(), exportedExamPath.orElse(null)); return exportedExamPath; } @@ -356,7 +356,7 @@ private List exportExercises(String notificationTopic, Set exerc // Export exercises for (var exercise : sortedExercises) { - log.info("Exporting exercise {} with id {} ", exercise.getTitle(), exercise.getId()); + log.info("Exporting {} exercise {} with id {} ", exercise.getType(), exercise.getTitle(), exercise.getId()); // Notify the user after the progress currentProgress++; diff --git a/src/main/java/de/tum/in/www1/artemis/service/CourseService.java b/src/main/java/de/tum/in/www1/artemis/service/CourseService.java index 3f24326ca201..aabbfac38301 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/CourseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/CourseService.java @@ -738,6 +738,7 @@ public StatsForDashboardDTO getStatsForDashboardDTO(Course course) { */ @Async public void archiveCourse(Course course) { + long start = System.nanoTime(); SecurityUtils.setAuthorizationObject(); // Archiving a course is only possible after the course is over @@ -746,7 +747,7 @@ public void archiveCourse(Course course) { } // This contains possible errors encountered during the archive process - ArrayList exportErrors = new ArrayList<>(); + List exportErrors = Collections.synchronizedList(new ArrayList<>()); groupNotificationService.notifyInstructorGroupAboutCourseArchiveState(course, NotificationType.COURSE_ARCHIVE_STARTED, exportErrors); @@ -775,6 +776,7 @@ public void archiveCourse(Course course) { } groupNotificationService.notifyInstructorGroupAboutCourseArchiveState(course, NotificationType.COURSE_ARCHIVE_FINISHED, exportErrors); + log.info("archive course took {}", TimeLogUtil.formatDurationFrom(start)); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/service/ExerciseDeletionService.java b/src/main/java/de/tum/in/www1/artemis/service/ExerciseDeletionService.java index 79bc21dbb954..8a5fcdcc5c96 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/ExerciseDeletionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/ExerciseDeletionService.java @@ -23,6 +23,7 @@ import de.tum.in.www1.artemis.repository.plagiarism.PlagiarismResultRepository; import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseService; +import de.tum.in.www1.artemis.service.util.TimeLogUtil; /** * Service Implementation for managing Exercise. @@ -123,8 +124,10 @@ public void delete(long exerciseId, boolean deleteStudentReposBuildPlans, boolea var exercise = exerciseRepository.findByIdWithCompetenciesElseThrow(exerciseId); log.info("Request to delete {} with id {}", exercise.getClass().getSimpleName(), exerciseId); + long start = System.nanoTime(); Channel exreciseChannel = channelRepository.findChannelByExerciseId(exerciseId); channelService.deleteChannel(exreciseChannel); + log.info("Deleting the channel took {}", TimeLogUtil.formatDurationFrom(start)); if (exercise instanceof ModelingExercise modelingExercise) { log.info("Deleting clusters, elements and cancel scheduled operations of exercise {}", exercise.getId()); diff --git a/src/main/java/de/tum/in/www1/artemis/service/dataexport/DataExportExerciseCreationService.java b/src/main/java/de/tum/in/www1/artemis/service/dataexport/DataExportExerciseCreationService.java index 0e33e0470a2f..f29034fbf690 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/dataexport/DataExportExerciseCreationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/dataexport/DataExportExerciseCreationService.java @@ -7,9 +7,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -132,16 +130,16 @@ public void createProgrammingExerciseExport(ProgrammingExercise programmingExerc repositoryExportOptions.setCombineStudentCommits(false); repositoryExportOptions.setFilterLateSubmissionsIndividualDueDate(false); repositoryExportOptions.setExcludePracticeSubmissions(false); - repositoryExportOptions.setNormalizeCodeStyle(true); + repositoryExportOptions.setNormalizeCodeStyle(false); var listOfProgrammingExerciseParticipations = programmingExercise.getStudentParticipations().stream() .filter(studentParticipation -> studentParticipation instanceof ProgrammingExerciseStudentParticipation) .map(studentParticipation -> (ProgrammingExerciseStudentParticipation) studentParticipation).toList(); - List exportRepoErrors = new ArrayList<>(); + // we use this directory only to clone the repository and don't do this in our current directory because the current directory is part of the final data export // --> we can delete it after use var tempRepoWorkingDir = fileService.getTemporaryUniquePath(repoClonePath, 10); programmingExerciseExportService.exportStudentRepositories(programmingExercise, listOfProgrammingExerciseParticipations, repositoryExportOptions, tempRepoWorkingDir, - exerciseDir, exportRepoErrors); + exerciseDir, Collections.synchronizedList(new ArrayList<>())); createPlagiarismCaseInfoExport(programmingExercise, exerciseDir, userId); diff --git a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamImportService.java b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamImportService.java index 8a9db8e1db6f..12121000919e 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamImportService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamImportService.java @@ -8,7 +8,6 @@ import de.tum.in.www1.artemis.domain.enumeration.ExerciseType; import de.tum.in.www1.artemis.domain.exam.Exam; import de.tum.in.www1.artemis.domain.exam.ExerciseGroup; -import de.tum.in.www1.artemis.domain.metis.conversation.Channel; import de.tum.in.www1.artemis.domain.modeling.ModelingExercise; import de.tum.in.www1.artemis.domain.quiz.QuizExercise; import de.tum.in.www1.artemis.repository.*; @@ -102,9 +101,7 @@ public Exam importExamWithExercises(Exam examToCopy, long targetCourseId) { // 2nd: Copy the exercise groups to the exam copyExerciseGroupsWithExercisesToExam(exerciseGroupsToCopy, examCopied); - Channel createdChannel = channelService.createExamChannel(examCopied, Optional.ofNullable(examToCopy.getChannelName())); - channelService.registerTutorsAndInstructorsToChannel(examCopied.getCourse(), createdChannel); - + channelService.createExamChannel(examCopied, Optional.ofNullable(examToCopy.getChannelName())); return examRepository.findWithExerciseGroupsAndExercisesByIdOrElseThrow(examCopied.getId()); } diff --git a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamRegistrationService.java b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamRegistrationService.java index edcd3cd02c22..8b886c052bb9 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamRegistrationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamRegistrationService.java @@ -20,7 +20,6 @@ import de.tum.in.www1.artemis.security.Role; import de.tum.in.www1.artemis.service.AuthorizationCheckService; import de.tum.in.www1.artemis.service.ParticipationService; -import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; import de.tum.in.www1.artemis.service.user.UserService; import de.tum.in.www1.artemis.web.rest.dto.ExamUserDTO; import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException; @@ -55,11 +54,9 @@ public class ExamRegistrationService { private final AuthorizationCheckService authorizationCheckService; - private final ChannelService channelService; - public ExamRegistrationService(ExamUserRepository examUserRepository, ExamRepository examRepository, UserService userService, ParticipationService participationService, UserRepository userRepository, AuditEventRepository auditEventRepository, CourseRepository courseRepository, StudentExamRepository studentExamRepository, - StudentParticipationRepository studentParticipationRepository, AuthorizationCheckService authorizationCheckService, ChannelService channelService) { + StudentParticipationRepository studentParticipationRepository, AuthorizationCheckService authorizationCheckService) { this.examRepository = examRepository; this.userService = userService; this.userRepository = userRepository; @@ -70,7 +67,6 @@ public ExamRegistrationService(ExamUserRepository examUserRepository, ExamReposi this.studentParticipationRepository = studentParticipationRepository; this.authorizationCheckService = authorizationCheckService; this.examUserRepository = examUserRepository; - this.channelService = channelService; } /** @@ -133,7 +129,6 @@ public List registerStudentsForExam(Long courseId, Long examId, Lis } } examRepository.save(exam); - channelService.registerUsersToExamChannel(usersAddedToExam, exam); try { User currentUser = userRepository.getUserWithGroupsAndAuthorities(); @@ -202,8 +197,6 @@ public void registerStudentToExam(Course course, Exam exam, User student) { registeredExamUser = examUserRepository.save(registeredExamUser); exam.addExamUser(registeredExamUser); examRepository.save(exam); - - channelService.registerUsersToExamChannel(List.of(student.getLogin()), exam); } else { log.warn("Student {} is already registered for the exam {}", student.getLogin(), exam.getId()); @@ -264,9 +257,6 @@ public void unregisterStudentFromExam(Exam exam, boolean deleteParticipationsAnd examRepository.save(exam); examUserRepository.delete(registeredExamUser); - // Remove the student from exam channel - channelService.deregisterUsersFromExamChannel(Set.of(student), exam.getId()); - // The student exam might already be generated, then we need to delete it Optional optionalStudentExam = studentExamRepository.findWithExercisesByUserIdAndExamId(student.getId(), exam.getId()); optionalStudentExam.ifPresent(studentExam -> removeStudentExam(studentExam, deleteParticipationsAndSubmission)); @@ -306,10 +296,6 @@ public void unregisterAllStudentFromExam(Exam exam, boolean deleteParticipations examRepository.save(exam); examUserRepository.deleteAllById(registeredExamUsers.stream().map(ExamUser::getId).toList()); - var students = userRepository.getStudents(exam.getCourse()); - - channelService.deregisterUsersFromExamChannel(students, exam.getId()); - // remove all students exams Set studentExams = studentExamRepository.findAllWithoutTestRunsWithExercisesByExamId(exam.getId()); studentExams.forEach(studentExam -> removeStudentExam(studentExam, deleteParticipationsAndSubmission)); diff --git a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java index cae59edc33ee..a35a35ea1c1c 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java @@ -1187,15 +1187,16 @@ public StatsForDashboardDTO getStatsForExamAssessmentDashboard(Course course, Lo */ @Async public void archiveExam(Exam exam) { + long start = System.nanoTime(); SecurityUtils.setAuthorizationObject(); - // Archiving a course is only possible after the exam is over + // Archiving an exam is only possible after the exam is over if (ZonedDateTime.now().isBefore(exam.getEndDate())) { return; } // This contains possible errors encountered during the archive process - ArrayList exportErrors = new ArrayList<>(); + List exportErrors = Collections.synchronizedList(new ArrayList<>()); groupNotificationService.notifyInstructorGroupAboutExamArchiveState(exam, NotificationType.EXAM_ARCHIVE_STARTED, exportErrors); @@ -1224,6 +1225,7 @@ public void archiveExam(Exam exam) { } groupNotificationService.notifyInstructorGroupAboutExamArchiveState(exam, NotificationType.EXAM_ARCHIVE_FINISHED, exportErrors); + log.info("archive exam took {}", TimeLogUtil.formatDurationFrom(start)); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/service/exam/StudentExamService.java b/src/main/java/de/tum/in/www1/artemis/service/exam/StudentExamService.java index bb6d6b097cf1..dd46b5396650 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/exam/StudentExamService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/exam/StudentExamService.java @@ -695,7 +695,7 @@ public CompletableFuture startExercises(Long examId) { generatedParticipations.size(), startedAt, lock); return null; })) - .toList().toArray(new CompletableFuture[studentExams.size()]); + .toArray(CompletableFuture[]::new); return CompletableFuture.allOf(futures).thenApply((emtpy) -> { threadPool.shutdown(); sendAndCacheExercisePreparationStatus(examId, finishedExamsCounter.get(), failedExamsCounter.get(), studentExams.size(), generatedParticipations.size(), startedAt, diff --git a/src/main/java/de/tum/in/www1/artemis/service/metis/conversation/ChannelService.java b/src/main/java/de/tum/in/www1/artemis/service/metis/conversation/ChannelService.java index 71a6a251db67..5bf92bc64838 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/metis/conversation/ChannelService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/metis/conversation/ChannelService.java @@ -11,7 +11,6 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -25,7 +24,6 @@ import de.tum.in.www1.artemis.repository.UserRepository; import de.tum.in.www1.artemis.repository.metis.ConversationParticipantRepository; import de.tum.in.www1.artemis.repository.metis.conversation.ChannelRepository; -import de.tum.in.www1.artemis.security.SecurityUtils; import de.tum.in.www1.artemis.service.metis.conversation.errors.ChannelNameDuplicateException; import de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException; import de.tum.in.www1.artemis.web.rest.metis.conversation.dtos.ChannelDTO; @@ -158,52 +156,6 @@ public Channel createChannel(Course course, Channel channel, Optional crea return savedChannel; } - /** - * Adds all course students to the given channel asynchronously - * - * @param course the course to add the students from - * @param channel the channel to add the students to - */ - @Async - public void registerCourseStudentsToChannelAsynchronously(Course course, Channel channel) { - if (channel == null) { - return; - } - SecurityUtils.setAuthorizationObject(); - registerUsersToChannel(true, false, false, List.of(), course, channel); - } - - /** - * Adds tutors and instructors to the given channel asynchronously - * - * @param course the course to add the tutors and instructors from - * @param channel the exam channel to add the users to - */ - @Async - public void registerTutorsAndInstructorsToChannel(Course course, Channel channel) { - if (channel == null || !course.getCourseInformationSharingConfiguration().isMessagingEnabled()) { - return; - } - SecurityUtils.setAuthorizationObject(); - registerUsersToChannel(false, true, true, List.of(), course, channel); - } - - /** - * Adds users to the channel of the given exam asynchronously - * - * @param users list of user logins to register for the exam channel - * @param exam exam to which channel the users should be added - */ - @Async - public void registerUsersToExamChannel(List users, Exam exam) { - Channel channel = channelRepository.findChannelByExamId(exam.getId()); - if (channel == null) { - return; - } - SecurityUtils.setAuthorizationObject(); - registerUsersToChannel(false, false, false, users, exam.getCourse(), channel); - } - /** * Register users to the newly created channel * @@ -293,7 +245,7 @@ public void unarchiveChannel(Long channelId) { } /** - * Creates a channel for a lecture and sets the channel name of the lecture accordingly. Also adds all course members asynchronously. + * Creates a channel for a lecture and sets the channel name of the lecture accordingly. * * @param lecture the lecture to create the channel for * @param channelName the name of the channel @@ -308,7 +260,7 @@ public Channel createLectureChannel(Lecture lecture, Optional channelNam } /** - * Creates a channel for a course exercise and sets the channel name of the exercise accordingly. Also adds all course members asynchronously. + * Creates a channel for a course exercise and sets the channel name of the exercise accordingly. * * @param exercise the exercise to create the channel for * @param channelName the name of the channel @@ -324,7 +276,7 @@ public Channel createExerciseChannel(Exercise exercise, Optional channel } /** - * Creates a channel for a real exam and sets the channel name of the exam accordingly. Also adds all course members asynchronously. + * Creates a channel for a real exam and sets the channel name of the exam accordingly. * * @param exam the exam to create the channel for * @param channelName the name of the channel @@ -336,7 +288,6 @@ public Channel createExamChannel(Exam exam, Optional channelName) { } Channel channelToCreate = createDefaultChannel(channelName, "exam-", exam.getTitle()); channelToCreate.setIsPublic(false); - channelToCreate.setIsCourseWide(false); channelToCreate.setExam(exam); Channel createdChannel = createChannel(exam.getCourse(), channelToCreate, Optional.of(userRepository.getUserWithGroupsAndAuthorities())); exam.setChannelName(createdChannel.getName()); @@ -391,21 +342,6 @@ public Channel updateExamChannel(Exam originalExam, Exam updatedExam) { return updateChannelName(channel, updatedExam.getChannelName()); } - /** - * Removes users from an exam channel - * - * @param users users to remove from the channel - * @param examId id of the exam the channel belongs to - */ - public void deregisterUsersFromExamChannel(Set users, Long examId) { - Channel channel = channelRepository.findChannelByExamId(examId); - if (channel == null) { - return; - } - - conversationService.deregisterUsersFromAConversation(channel.getCourse(), users, channel); - } - private Channel updateChannelName(Channel channel, String newChannelName) { // Update channel name if necessary diff --git a/src/main/java/de/tum/in/www1/artemis/service/metis/conversation/ConversationService.java b/src/main/java/de/tum/in/www1/artemis/service/metis/conversation/ConversationService.java index a8336697033d..5db5b8df1c9a 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/metis/conversation/ConversationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/metis/conversation/ConversationService.java @@ -10,7 +10,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestBody; import de.tum.in.www1.artemis.domain.*; @@ -249,11 +248,7 @@ public void deregisterUsersFromAConversation(Course course, Set users, Con * * @param conversation the conversation to be deleted */ - @Transactional // ok because of delete public void deleteConversation(Conversation conversation) { - var usersToMessage = conversationParticipantRepository.findConversationParticipantByConversationId(conversation.getId()).stream().map(ConversationParticipant::getUser) - .collect(Collectors.toSet()); - broadcastOnConversationMembershipChannel(conversation.getCourse(), MetisCrudAction.DELETE, conversation, usersToMessage); this.postRepository.deleteAllByConversationId(conversation.getId()); this.conversationParticipantRepository.deleteAllByConversationId(conversation.getId()); this.conversationRepository.deleteById(conversation.getId()); @@ -272,36 +267,6 @@ public void broadcastOnConversationMembershipChannel(Course course, MetisCrudAct recipients.forEach(user -> sendToConversationMembershipChannel(metisCrudAction, conversation, user, conversationParticipantTopicName)); } - /** - * Deregister all clients from the exercise channel of the given exercise - * - * @param exercise the exercise that is being deleted - */ - public void deregisterAllClientsFromChannel(Exercise exercise) { - // deregister all clients from the channel - Channel originalChannel = channelRepository.findChannelByExerciseId(exercise.getId()); - if (exercise.isCourseExercise() && originalChannel != null) { - Set channelParticipants = conversationParticipantRepository.findConversationParticipantByConversationId(originalChannel.getId()); - Set usersToBeDeregistered = channelParticipants.stream().map(ConversationParticipant::getUser).collect(Collectors.toSet()); - broadcastOnConversationMembershipChannel(originalChannel.getCourse(), MetisCrudAction.DELETE, originalChannel, usersToBeDeregistered); - } - } - - /** - * Deregister all clients from the lecture channel of the given exercise - * - * @param lecture the lecture that is being deleted - */ - public void deregisterAllClientsFromChannel(Lecture lecture) { - // deregister all clients from the channel - Channel originalChannel = channelRepository.findChannelByLectureId(lecture.getId()); - if (originalChannel != null) { - Set channelParticipants = conversationParticipantRepository.findConversationParticipantByConversationId(originalChannel.getId()); - Set usersToBeDeregistered = channelParticipants.stream().map(ConversationParticipant::getUser).collect(Collectors.toSet()); - broadcastOnConversationMembershipChannel(lecture.getCourse(), MetisCrudAction.DELETE, originalChannel, usersToBeDeregistered); - } - } - @NotNull public static String getConversationParticipantTopicName(Long courseId) { return METIS_WEBSOCKET_CHANNEL_PREFIX + "courses/" + courseId + "/conversations/user/"; diff --git a/src/main/java/de/tum/in/www1/artemis/service/notifications/GroupNotificationScheduleService.java b/src/main/java/de/tum/in/www1/artemis/service/notifications/GroupNotificationScheduleService.java index ffb48c5782ae..288f32aa5fb0 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/notifications/GroupNotificationScheduleService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/notifications/GroupNotificationScheduleService.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service; import de.tum.in.www1.artemis.domain.Exercise; +import de.tum.in.www1.artemis.security.SecurityUtils; import de.tum.in.www1.artemis.service.ExerciseDateService; import de.tum.in.www1.artemis.service.messaging.InstanceMessageSendService; @@ -140,7 +141,8 @@ private void checkAndCreateAssessedExerciseSubmissionNotificationsWhenUpdatingEx * @param exercise that is created */ @Async - public void checkNotificationsForNewExercise(Exercise exercise) { + public void checkNotificationsForNewExerciseAsync(Exercise exercise) { + SecurityUtils.setAuthorizationObject(); // required for async // TODO: in a course with 2000 participants, this can take really long, we should optimize this checkNotificationForExerciseRelease(exercise); checkNotificationForAssessmentDueDate(exercise); diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseExportService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseExportService.java index 2dbd072ccf6c..74326f490bc0 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseExportService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseExportService.java @@ -11,6 +11,8 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -236,6 +238,7 @@ public Path exportProgrammingExerciseRepositories(ProgrammingExercise exercise, .map(studentParticipation -> (ProgrammingExerciseStudentParticipation) studentParticipation).sorted(Comparator.comparing(DomainObject::getId)).toList(); var exportOptions = new RepositoryExportOptionsDTO(); exportOptions.setAnonymizeRepository(false); + exportOptions.setExportAllParticipants(true); // Export student repositories and add them to list var exportedStudentRepositoryFiles = exportStudentRepositories(exercise, studentParticipations, exportOptions, outputDir, outputDir, exportErrors).stream() @@ -528,27 +531,36 @@ public List exportStudentRepositories(ProgrammingExercise programmingExerc RepositoryExportOptionsDTO repositoryExportOptions, Path workingDir, Path outputDir, List exportErrors) { var programmingExerciseId = programmingExercise.getId(); if (repositoryExportOptions.isExportAllParticipants()) { - log.info("Request to export all student or team repositories of programming exercise {} with title '{}'", programmingExerciseId, programmingExercise.getTitle()); + log.info("Request to export all {} student or team repositories of programming exercise {} with title '{}'", participations.size(), programmingExerciseId, + programmingExercise.getTitle()); } else { - log.info("Request to export the repositories of programming exercise {} with title '{}' of the following students or teams: {}", programmingExerciseId, - programmingExercise.getTitle(), participations.stream().map(StudentParticipation::getParticipantIdentifier).collect(Collectors.joining(", "))); + log.info("Request to export the repositories of programming exercise {} with title '{}' of {} students or teams", programmingExerciseId, programmingExercise.getTitle(), + participations.size()); + log.debug("Export repositories for students or teams: {}", + participations.stream().map(StudentParticipation::getParticipantIdentifier).collect(Collectors.joining(", "))); } - List exportedStudentRepositories = new ArrayList<>(); - participations.forEach(participation -> { + List exportedStudentRepositories = Collections.synchronizedList(new ArrayList<>()); + + log.info("export student repositories for programming exercise {} in parallel", programmingExercise.getId()); + var threadPool = Executors.newFixedThreadPool(10); + var futures = participations.stream().map(participation -> CompletableFuture.runAsync(() -> { try { + log.debug("invoke createZipForRepositoryWithParticipation for participation {}", participation.getId()); Path zipFile = createZipForRepositoryWithParticipation(programmingExercise, participation, repositoryExportOptions, workingDir, outputDir); if (zipFile != null) { exportedStudentRepositories.add(zipFile); } } - catch (Exception e) { + catch (Exception exception) { var error = "Failed to export the student repository with participation: " + participation.getId() + " for programming exercise '" + programmingExercise.getTitle() + "' (id: " + programmingExercise.getId() + ") because the repository couldn't be downloaded. "; exportErrors.add(error); } - }); + }, threadPool).toCompletableFuture()).toArray(CompletableFuture[]::new); + // wait until all operations finish + CompletableFuture.allOf(futures).thenRun(threadPool::shutdown).join(); return exportedStudentRepositories; } @@ -633,6 +645,7 @@ private Path createZipForRepositoryWithParticipation(final ProgrammingExercise p return null; } + // TODO: this operation is only necessary if the repo was not newly cloned gitService.resetToOriginHead(repository); if (repositoryExportOptions.isFilterLateSubmissions()) { diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java index 9c964f93fa27..5256f48f1a8d 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingExerciseService.java @@ -222,7 +222,7 @@ public ProgrammingExercise createProgrammingExercise(ProgrammingExercise program // not yet saved in the database, so we cannot save the submission accordingly (see ProgrammingSubmissionService.processNewProgrammingSubmission) versionControl.addWebHooksForExercise(savedProgrammingExercise); scheduleOperations(savedProgrammingExercise.getId()); - groupNotificationScheduleService.checkNotificationsForNewExercise(savedProgrammingExercise); + groupNotificationScheduleService.checkNotificationsForNewExerciseAsync(savedProgrammingExercise); return savedProgrammingExercise; } diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java index 2d011c2aca04..388821a2274f 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/programming/ProgrammingSubmissionService.java @@ -236,8 +236,8 @@ private void checkForIllegalSubmission(ProgrammingExerciseParticipation programm User student = optionalStudentWithGroups.get(); if (!isAllowedToSubmit(studentParticipation, student, programmingSubmission)) { - final String message = "The student %s illegally submitted code after the allowed individual due date (including the grace period) in the participation %d for the programming exercise %d" - .formatted(student.getLogin(), programmingExerciseParticipation.getId(), programmingExercise.getId()); + final String message = ("The student %s illegally submitted code after the allowed individual due date (including the grace period) in the participation %d for the " + + "programming exercise \"%s\"").formatted(student.getLogin(), programmingExerciseParticipation.getId(), programmingExercise.getTitle()); programmingSubmission.setType(SubmissionType.ILLEGAL); programmingMessagingService.notifyInstructorGroupAboutIllegalSubmissionsForExercise(programmingExercise, message); log.warn(message); @@ -246,8 +246,8 @@ private void checkForIllegalSubmission(ProgrammingExerciseParticipation programm // we include submission policies here: if the student (for whatever reason) has more submission than allowed attempts, the submission would be illegal if (exceedsSubmissionPolicy(studentParticipation, submissionPolicy)) { - final String message = "The student %s illegally submitted code after the submission policy lock limit %d in the participation %d for the programming exercise %d" - .formatted(student.getLogin(), submissionPolicy.getSubmissionLimit(), programmingExerciseParticipation.getId(), programmingExercise.getId()); + final String message = "The student %s illegally submitted code after the submission policy lock limit %d in the participation %d for the programming exercise \"%s\"" + .formatted(student.getLogin(), submissionPolicy.getSubmissionLimit(), programmingExerciseParticipation.getId(), programmingExercise.getTitle()); programmingSubmission.setType(SubmissionType.ILLEGAL); programmingMessagingService.notifyInstructorGroupAboutIllegalSubmissionsForExercise(programmingExercise, message); log.warn(message); @@ -272,7 +272,7 @@ private boolean isAllowedToSubmit(ProgrammingExerciseStudentParticipation partic private boolean isAllowedToSubmitForCourseExercise(ProgrammingExerciseStudentParticipation participation, ProgrammingSubmission programmingSubmission) { var dueDate = ExerciseDateService.getDueDate(participation); - if (dueDate.isEmpty()) { + if (dueDate.isEmpty() || participation.isTestRun()) { return true; } return dueDate.get().plusSeconds(PROGRAMMING_GRACE_PERIOD_SECONDS).isAfter(programmingSubmission.getSubmissionDate()); diff --git a/src/main/java/de/tum/in/www1/artemis/service/util/TimeLogUtil.java b/src/main/java/de/tum/in/www1/artemis/service/util/TimeLogUtil.java index ee3428af1373..2d7a4bf7a8c7 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/util/TimeLogUtil.java +++ b/src/main/java/de/tum/in/www1/artemis/service/util/TimeLogUtil.java @@ -10,15 +10,23 @@ public class TimeLogUtil { */ public static String formatDurationFrom(long timeNanoStart) { long durationInMicroSeconds = (System.nanoTime() - timeNanoStart) / 1000; - if (durationInMicroSeconds > 1000) { - double durationInMilliSeconds = durationInMicroSeconds / 1000.0; - if (durationInMilliSeconds > 1000) { - double durationInSeconds = durationInMilliSeconds / 1000.0; - return roundOffTo2DecPlaces(durationInSeconds) + "s"; - } + if (durationInMicroSeconds < 1000) { + return durationInMicroSeconds + "µs"; + } + double durationInMilliSeconds = durationInMicroSeconds / 1000.0; + if (durationInMilliSeconds < 1000) { return roundOffTo2DecPlaces(durationInMilliSeconds) + "ms"; } - return durationInMicroSeconds + "µs"; + double durationInSeconds = durationInMilliSeconds / 1000.0; + if (durationInSeconds < 60) { + return roundOffTo2DecPlaces(durationInSeconds) + "sec"; + } + double durationInMinutes = durationInSeconds / 60.0; + if (durationInMinutes < 60) { + return roundOffTo2DecPlaces(durationInMinutes) + "min"; + } + double durationInHours = durationInMinutes / 60.0; + return roundOffTo2DecPlaces(durationInHours) + "hours"; } private static String roundOffTo2DecPlaces(double val) { diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java index 00b3bfe6a122..9f42c7385907 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java @@ -33,7 +33,9 @@ import de.tum.in.www1.artemis.config.Constants; import de.tum.in.www1.artemis.domain.*; -import de.tum.in.www1.artemis.domain.exam.*; +import de.tum.in.www1.artemis.domain.exam.Exam; +import de.tum.in.www1.artemis.domain.exam.ExerciseGroup; +import de.tum.in.www1.artemis.domain.exam.StudentExam; import de.tum.in.www1.artemis.domain.metis.conversation.Channel; import de.tum.in.www1.artemis.domain.participation.TutorParticipation; import de.tum.in.www1.artemis.repository.*; @@ -171,8 +173,7 @@ public ResponseEntity createExam(@PathVariable Long courseId, @RequestBody Exam savedExam = examRepository.save(exam); - Channel createdChannel = channelService.createExamChannel(savedExam, Optional.ofNullable(exam.getChannelName())); - channelService.registerTutorsAndInstructorsToChannel(savedExam.getCourse(), createdChannel); + channelService.createExamChannel(savedExam, Optional.ofNullable(exam.getChannelName())); return ResponseEntity.created(new URI("/api/courses/" + courseId + "/exams/" + savedExam.getId())).body(savedExam); } @@ -928,8 +929,6 @@ public ResponseEntity registerCourseStudents(@PathVariable Long courseId, } examRegistrationService.addAllStudentsOfCourseToExam(courseId, exam); - Channel channel = channelRepository.findChannelByExamId(exam.getId()); - channelService.registerCourseStudentsToChannelAsynchronously(exam.getCourse(), channel); return ResponseEntity.ok().body(null); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/FileUploadExerciseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/FileUploadExerciseResource.java index c6acb02443fe..88f19fc188a2 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/FileUploadExerciseResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/FileUploadExerciseResource.java @@ -27,7 +27,6 @@ import de.tum.in.www1.artemis.service.feature.Feature; import de.tum.in.www1.artemis.service.feature.FeatureToggle; import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; -import de.tum.in.www1.artemis.service.metis.conversation.ConversationService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationScheduleService; import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO; import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO; @@ -81,14 +80,12 @@ public class FileUploadExerciseResource { private final ChannelRepository channelRepository; - private final ConversationService conversationService; - public FileUploadExerciseResource(FileUploadExerciseRepository fileUploadExerciseRepository, UserRepository userRepository, AuthorizationCheckService authCheckService, CourseService courseService, ExerciseService exerciseService, ExerciseDeletionService exerciseDeletionService, FileUploadSubmissionExportService fileUploadSubmissionExportService, GradingCriterionRepository gradingCriterionRepository, CourseRepository courseRepository, ParticipationRepository participationRepository, GroupNotificationScheduleService groupNotificationScheduleService, FileUploadExerciseImportService fileUploadExerciseImportService, FileUploadExerciseService fileUploadExerciseService, ChannelService channelService, - ChannelRepository channelRepository, ConversationService conversationService) { + ChannelRepository channelRepository) { this.fileUploadExerciseRepository = fileUploadExerciseRepository; this.userRepository = userRepository; this.courseService = courseService; @@ -104,7 +101,6 @@ public FileUploadExerciseResource(FileUploadExerciseRepository fileUploadExercis this.fileUploadExerciseService = fileUploadExerciseService; this.channelService = channelService; this.channelRepository = channelRepository; - this.conversationService = conversationService; } /** @@ -133,7 +129,7 @@ public ResponseEntity createFileUploadExercise(@RequestBody FileUploadExercise result = fileUploadExerciseRepository.save(fileUploadExercise); channelService.createExerciseChannel(result, Optional.ofNullable(fileUploadExercise.getChannelName())); - groupNotificationScheduleService.checkNotificationsForNewExercise(fileUploadExercise); + groupNotificationScheduleService.checkNotificationsForNewExerciseAsync(fileUploadExercise); return ResponseEntity.created(new URI("/api/file-upload-exercises/" + result.getId())).body(result); } @@ -333,7 +329,6 @@ public ResponseEntity deleteFileUploadExercise(@PathVariable Long exercise authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, exercise, user); // note: we use the exercise service here, because this one makes sure to clean up all lazy references correctly. exerciseService.logDeletion(exercise, exercise.getCourseViaExerciseGroupOrCourseMember(), user); - conversationService.deregisterAllClientsFromChannel(exercise); exerciseDeletionService.delete(exerciseId, false, false); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, exercise.getTitle())).build(); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LectureResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LectureResource.java index 59408ae7f4f8..2a43ac61cba5 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/LectureResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LectureResource.java @@ -32,7 +32,6 @@ import de.tum.in.www1.artemis.service.LectureImportService; import de.tum.in.www1.artemis.service.LectureService; import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; -import de.tum.in.www1.artemis.service.metis.conversation.ConversationService; import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO; import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO; import de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException; @@ -66,15 +65,13 @@ public class LectureResource { private final ExerciseService exerciseService; - private final ConversationService conversationService; - private final ChannelService channelService; private final ChannelRepository channelRepository; public LectureResource(LectureRepository lectureRepository, LectureService lectureService, LectureImportService lectureImportService, CourseRepository courseRepository, UserRepository userRepository, AuthorizationCheckService authCheckService, ExerciseService exerciseService, ChannelService channelService, - ConversationService conversationService, ChannelRepository channelRepository) { + ChannelRepository channelRepository) { this.lectureRepository = lectureRepository; this.lectureService = lectureService; this.lectureImportService = lectureImportService; @@ -83,7 +80,6 @@ public LectureResource(LectureRepository lectureRepository, LectureService lectu this.authCheckService = authCheckService; this.exerciseService = exerciseService; this.channelService = channelService; - this.conversationService = conversationService; this.channelRepository = channelRepository; } @@ -367,7 +363,6 @@ public ResponseEntity deleteLecture(@PathVariable Long lectureId) { } authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null); - conversationService.deregisterAllClientsFromChannel(lecture); log.debug("REST request to delete Lecture : {}", lectureId); lectureService.delete(lecture); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, lectureId.toString())).build(); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingExerciseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingExerciseResource.java index b18c2ef6b990..5f262e3fa6e8 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingExerciseResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingExerciseResource.java @@ -32,7 +32,6 @@ import de.tum.in.www1.artemis.service.feature.Feature; import de.tum.in.www1.artemis.service.feature.FeatureToggle; import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; -import de.tum.in.www1.artemis.service.metis.conversation.ConversationService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationScheduleService; import de.tum.in.www1.artemis.service.plagiarism.ModelingPlagiarismDetectionService; import de.tum.in.www1.artemis.service.util.TimeLogUtil; @@ -90,8 +89,6 @@ public class ModelingExerciseResource { private final ChannelService channelService; - private final ConversationService conversationService; - private final ChannelRepository channelRepository; public ModelingExerciseResource(ModelingExerciseRepository modelingExerciseRepository, UserRepository userRepository, CourseService courseService, @@ -99,8 +96,7 @@ public ModelingExerciseResource(ModelingExerciseRepository modelingExerciseRepos ModelingExerciseService modelingExerciseService, ExerciseDeletionService exerciseDeletionService, PlagiarismResultRepository plagiarismResultRepository, ModelingExerciseImportService modelingExerciseImportService, SubmissionExportService modelingSubmissionExportService, ExerciseService exerciseService, GroupNotificationScheduleService groupNotificationScheduleService, GradingCriterionRepository gradingCriterionRepository, - ModelingPlagiarismDetectionService modelingPlagiarismDetectionService, ChannelService channelService, ConversationService conversationService, - ChannelRepository channelRepository) { + ModelingPlagiarismDetectionService modelingPlagiarismDetectionService, ChannelService channelService, ChannelRepository channelRepository) { this.modelingExerciseRepository = modelingExerciseRepository; this.courseService = courseService; this.modelingExerciseService = modelingExerciseService; @@ -117,7 +113,6 @@ public ModelingExerciseResource(ModelingExerciseRepository modelingExerciseRepos this.gradingCriterionRepository = gradingCriterionRepository; this.modelingPlagiarismDetectionService = modelingPlagiarismDetectionService; this.channelService = channelService; - this.conversationService = conversationService; this.channelRepository = channelRepository; } @@ -155,7 +150,7 @@ public ResponseEntity createModelingExercise(@RequestBody Mode channelService.createExerciseChannel(result, Optional.ofNullable(modelingExercise.getChannelName())); modelingExerciseService.scheduleOperations(result.getId()); - groupNotificationScheduleService.checkNotificationsForNewExercise(modelingExercise); + groupNotificationScheduleService.checkNotificationsForNewExerciseAsync(modelingExercise); return ResponseEntity.created(new URI("/api/modeling-exercises/" + result.getId())).body(result); } @@ -293,7 +288,6 @@ public ResponseEntity deleteModelingExercise(@PathVariable Long exerciseId authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, modelingExercise, user); // note: we use the exercise service here, because this one makes sure to clean up all lazy references correctly. exerciseService.logDeletion(modelingExercise, modelingExercise.getCourseViaExerciseGroupOrCourseMember(), user); - conversationService.deregisterAllClientsFromChannel(modelingExercise); exerciseDeletionService.delete(exerciseId, false, false); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, modelingExercise.getTitle())).build(); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseExportImportResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseExportImportResource.java index aa74a6c01503..689bb01ba706 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseExportImportResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseExportImportResource.java @@ -8,10 +8,7 @@ import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import javax.validation.constraints.NotNull; @@ -270,7 +267,7 @@ public ResponseEntity exportInstructorExercise(@PathVariable long exer long start = System.nanoTime(); Path path; try { - path = programmingExerciseExportService.exportProgrammingExerciseInstructorMaterial(programmingExercise, new ArrayList<>()); + path = programmingExerciseExportService.exportProgrammingExerciseInstructorMaterial(programmingExercise, Collections.synchronizedList(new ArrayList<>())); } catch (Exception e) { log.error("Error while exporting programming exercise with id " + exerciseId + " for instructor", e); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseResource.java index 994caff41ee3..fd265be5796f 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ProgrammingExerciseResource.java @@ -38,7 +38,6 @@ import de.tum.in.www1.artemis.service.feature.Feature; import de.tum.in.www1.artemis.service.feature.FeatureToggle; import de.tum.in.www1.artemis.service.messaging.InstanceMessageSendService; -import de.tum.in.www1.artemis.service.metis.conversation.ConversationService; import de.tum.in.www1.artemis.service.programming.*; import de.tum.in.www1.artemis.web.rest.dto.BuildLogStatisticsDTO; import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO; @@ -108,8 +107,6 @@ public class ProgrammingExerciseResource { private final BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository; - private final ConversationService conversationService; - private final InstanceMessageSendService instanceMessageSendService; public ProgrammingExerciseResource(ProgrammingExerciseRepository programmingExerciseRepository, ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository, @@ -120,8 +117,7 @@ public ProgrammingExerciseResource(ProgrammingExerciseRepository programmingExer StaticCodeAnalysisService staticCodeAnalysisService, GradingCriterionRepository gradingCriterionRepository, CourseRepository courseRepository, GitService gitService, AuxiliaryRepositoryService auxiliaryRepositoryService, SolutionProgrammingExerciseParticipationRepository solutionProgrammingExerciseParticipationRepository, TemplateProgrammingExerciseParticipationRepository templateProgrammingExerciseParticipationRepository, ProfileService profileService, - BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, ConversationService conversationService, ChannelRepository channelRepository, - InstanceMessageSendService instanceMessageSendService) { + BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, ChannelRepository channelRepository, InstanceMessageSendService instanceMessageSendService) { this.profileService = profileService; this.programmingExerciseRepository = programmingExerciseRepository; this.programmingExerciseTestCaseRepository = programmingExerciseTestCaseRepository; @@ -143,7 +139,6 @@ public ProgrammingExerciseResource(ProgrammingExerciseRepository programmingExer this.solutionProgrammingExerciseParticipationRepository = solutionProgrammingExerciseParticipationRepository; this.templateProgrammingExerciseParticipationRepository = templateProgrammingExerciseParticipationRepository; this.buildLogStatisticsEntryRepository = buildLogStatisticsEntryRepository; - this.conversationService = conversationService; this.channelRepository = channelRepository; this.instanceMessageSendService = instanceMessageSendService; } @@ -456,7 +451,6 @@ public ResponseEntity deleteProgrammingExercise(@PathVariable long exercis User user = userRepository.getUserWithGroupsAndAuthorities(); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, programmingExercise, user); exerciseService.logDeletion(programmingExercise, programmingExercise.getCourseViaExerciseGroupOrCourseMember(), user); - conversationService.deregisterAllClientsFromChannel(programmingExercise); exerciseDeletionService.delete(exerciseId, deleteStudentReposBuildPlans, deleteBaseReposBuildPlans); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, programmingExercise.getTitle())).build(); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/QuizExerciseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/QuizExerciseResource.java index d785222ddb75..5e11afd43ae4 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/QuizExerciseResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/QuizExerciseResource.java @@ -31,7 +31,6 @@ import de.tum.in.www1.artemis.service.*; import de.tum.in.www1.artemis.service.exam.ExamDateService; import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; -import de.tum.in.www1.artemis.service.metis.conversation.ConversationService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationScheduleService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationService; import de.tum.in.www1.artemis.service.scheduled.cache.quiz.QuizScheduleService; @@ -98,15 +97,12 @@ public class QuizExerciseResource { private final ChannelRepository channelRepository; - private final ConversationService conversationService; - public QuizExerciseResource(QuizExerciseService quizExerciseService, QuizExerciseRepository quizExerciseRepository, CourseService courseService, UserRepository userRepository, ExerciseDeletionService exerciseDeletionServiceService, QuizScheduleService quizScheduleService, QuizStatisticService quizStatisticService, QuizExerciseImportService quizExerciseImportService, AuthorizationCheckService authCheckService, CourseRepository courseRepository, GroupNotificationService groupNotificationService, ExerciseService exerciseService, ExamDateService examDateService, QuizMessagingService quizMessagingService, GroupNotificationScheduleService groupNotificationScheduleService, StudentParticipationRepository studentParticipationRepository, QuizBatchService quizBatchService, - QuizBatchRepository quizBatchRepository, SubmissionRepository submissionRepository, ChannelService channelService, ChannelRepository channelRepository, - ConversationService conversationService) { + QuizBatchRepository quizBatchRepository, SubmissionRepository submissionRepository, ChannelService channelService, ChannelRepository channelRepository) { this.quizExerciseService = quizExerciseService; this.quizExerciseRepository = quizExerciseRepository; this.exerciseDeletionService = exerciseDeletionServiceService; @@ -128,7 +124,6 @@ public QuizExerciseResource(QuizExerciseService quizExerciseService, QuizExercis this.submissionRepository = submissionRepository; this.channelService = channelService; this.channelRepository = channelRepository; - this.conversationService = conversationService; } /** @@ -553,7 +548,6 @@ public ResponseEntity deleteQuizExercise(@PathVariable Long quizExerciseId // note: we use the exercise service here, because this one makes sure to clean up all lazy references correctly. exerciseService.logDeletion(quizExercise, quizExercise.getCourseViaExerciseGroupOrCourseMember(), user); - conversationService.deregisterAllClientsFromChannel(quizExercise); exerciseDeletionService.delete(quizExerciseId, false, false); quizExerciseService.cancelScheduledQuiz(quizExerciseId); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, quizExercise.getTitle())).build(); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/TextExerciseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/TextExerciseResource.java index 85cae438d727..6361d050658d 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/TextExerciseResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/TextExerciseResource.java @@ -27,7 +27,6 @@ import de.tum.in.www1.artemis.service.feature.FeatureToggle; import de.tum.in.www1.artemis.service.messaging.InstanceMessageSendService; import de.tum.in.www1.artemis.service.metis.conversation.ChannelService; -import de.tum.in.www1.artemis.service.metis.conversation.ConversationService; import de.tum.in.www1.artemis.service.notifications.GroupNotificationScheduleService; import de.tum.in.www1.artemis.service.plagiarism.TextPlagiarismDetectionService; import de.tum.in.www1.artemis.service.util.TimeLogUtil; @@ -99,8 +98,6 @@ public class TextExerciseResource { private final ChannelService channelService; - private final ConversationService conversationService; - private final ChannelRepository channelRepository; public TextExerciseResource(TextExerciseRepository textExerciseRepository, TextExerciseService textExerciseService, FeedbackRepository feedbackRepository, @@ -110,7 +107,7 @@ public TextExerciseResource(TextExerciseRepository textExerciseRepository, TextE TextSubmissionExportService textSubmissionExportService, ExampleSubmissionRepository exampleSubmissionRepository, ExerciseService exerciseService, GradingCriterionRepository gradingCriterionRepository, TextBlockRepository textBlockRepository, GroupNotificationScheduleService groupNotificationScheduleService, InstanceMessageSendService instanceMessageSendService, TextPlagiarismDetectionService textPlagiarismDetectionService, CourseRepository courseRepository, - ChannelService channelService, ChannelRepository channelRepository, ConversationService conversationService) { + ChannelService channelService, ChannelRepository channelRepository) { this.feedbackRepository = feedbackRepository; this.exerciseDeletionService = exerciseDeletionService; this.plagiarismResultRepository = plagiarismResultRepository; @@ -133,7 +130,6 @@ public TextExerciseResource(TextExerciseRepository textExerciseRepository, TextE this.textPlagiarismDetectionService = textPlagiarismDetectionService; this.courseRepository = courseRepository; this.channelService = channelService; - this.conversationService = conversationService; this.channelRepository = channelRepository; } @@ -170,7 +166,7 @@ public ResponseEntity createTextExercise(@RequestBody TextExercise channelService.createExerciseChannel(result, Optional.ofNullable(textExercise.getChannelName())); instanceMessageSendService.sendTextExerciseSchedule(result.getId()); - groupNotificationScheduleService.checkNotificationsForNewExercise(textExercise); + groupNotificationScheduleService.checkNotificationsForNewExerciseAsync(textExercise); return ResponseEntity.created(new URI("/api/text-exercises/" + result.getId())).body(result); } @@ -300,7 +296,6 @@ public ResponseEntity deleteTextExercise(@PathVariable Long exerciseId) { authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, textExercise, user); // NOTE: we use the exerciseDeletionService here, because this one makes sure to clean up all lazy references correctly. exerciseService.logDeletion(textExercise, textExercise.getCourseViaExerciseGroupOrCourseMember(), user); - conversationService.deregisterAllClientsFromChannel(textExercise); exerciseDeletionService.delete(exerciseId, false, false); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, textExercise.getTitle())).build(); } @@ -366,6 +361,10 @@ public ResponseEntity getDataForTextEditor(@PathVariable L if (!authCheckService.isAtLeastTeachingAssistantForExercise(textExercise, user)) { result.filterSensitiveInformation(); } + + // only send the one latest result to the client + textSubmission.setResults(List.of(result)); + participation.setResults(Set.of(result)); } participation.addSubmission(textSubmission); diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/RepositoryExportOptionsDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/RepositoryExportOptionsDTO.java index 17a737abcf3b..e95197b598e0 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/RepositoryExportOptionsDTO.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/RepositoryExportOptionsDTO.java @@ -8,6 +8,7 @@ * This is a dto for the repository export options. */ @JsonInclude(JsonInclude.Include.NON_EMPTY) +// TODO: we should convert this into a Record public class RepositoryExportOptionsDTO { private boolean exportAllParticipants; diff --git a/src/main/resources/config/liquibase/changelog/20230907114600_changelog.xml b/src/main/resources/config/liquibase/changelog/20230907114600_changelog.xml new file mode 100644 index 000000000000..e979182051f3 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20230907114600_changelog.xml @@ -0,0 +1,28 @@ + + + + + Set the is_course_wide value to true for course-wide channels that have been created before this flag existed. + On the first hand, these are lecture and exercise channels, identified by an existing lecture_id or exercise_id. + On the other hand, these are the 4 default channels (announcement, organization, random, tech-support) created on course creation, + identified by the absence of a creator and no association with a tutorial group. + + + UPDATE conversation c + SET is_course_wide = true + WHERE c.lecture_id IS NOT null + OR c.exercise_id IS NOT null + OR c.exam_id IS NOT null + OR ( + c.creator_id IS null + AND NOT EXISTS ( + SELECT 1 + FROM tutorial_group AS tg + WHERE tg.tutorial_group_channel_id = c.id + ) + ) + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 26c149abbd2f..eb868ed1b3bf 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -52,6 +52,7 @@ + diff --git a/src/main/webapp/app/exam/exam-scores/exam-scores.component.ts b/src/main/webapp/app/exam/exam-scores/exam-scores.component.ts index a6ba3b558ff7..730e6f1d61ce 100644 --- a/src/main/webapp/app/exam/exam-scores/exam-scores.component.ts +++ b/src/main/webapp/app/exam/exam-scores/exam-scores.component.ts @@ -20,7 +20,6 @@ import { AlertService } from 'app/core/util/alert.service'; import { roundValueSpecifiedByCourseSettings } from 'app/shared/util/utils'; import { LocaleConversionService } from 'app/shared/service/locale-conversion.service'; import { JhiLanguageHelper } from 'app/core/language/language.helper'; -import { TranslateService } from '@ngx-translate/core'; import { ParticipantScoresService, ScoresDTO } from 'app/shared/participant-scores/participant-scores.service'; import { captureException } from '@sentry/angular-ivy'; import { GradingSystemService } from 'app/grading-system/grading-system.service'; @@ -138,7 +137,6 @@ export class ExamScoresComponent implements OnInit, OnDestroy { private changeDetector: ChangeDetectorRef, private languageHelper: JhiLanguageHelper, private localeConversionService: LocaleConversionService, - private translateService: TranslateService, private participantScoresService: ParticipantScoresService, private gradingSystemService: GradingSystemService, private courseManagementService: CourseManagementService, diff --git a/src/main/webapp/app/shared/metis/metis-conversation.service.ts b/src/main/webapp/app/shared/metis/metis-conversation.service.ts index 08f1f69dc2d9..c0ecdce30b5d 100644 --- a/src/main/webapp/app/shared/metis/metis-conversation.service.ts +++ b/src/main/webapp/app/shared/metis/metis-conversation.service.ts @@ -13,7 +13,6 @@ import { OneToOneChatService } from 'app/shared/metis/conversations/one-to-one-c import { ChannelService } from 'app/shared/metis/conversations/channel.service'; import { onError } from 'app/shared/util/global.utils'; import { Course } from 'app/entities/course.model'; -import { CourseManagementService } from 'app/course/manage/course-management.service'; import { ChannelDTO } from 'app/entities/metis/conversation/channel.model'; import { OneToOneChatDTO } from 'app/entities/metis/conversation/one-to-one-chat.model'; import { GroupChatService } from 'app/shared/metis/conversations/group-chat.service'; @@ -46,7 +45,6 @@ export class MetisConversationService implements OnDestroy { private _isServiceSetup$: ReplaySubject = new ReplaySubject(1); constructor( - private courseManagementService: CourseManagementService, private groupChatService: GroupChatService, private oneToOneChatService: OneToOneChatService, private channelService: ChannelService, diff --git a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java index 00511d38be22..bd77cb71ecaa 100644 --- a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java +++ b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java @@ -2102,7 +2102,7 @@ else if (exercise instanceof TextExercise) { } private List archiveCourseAndExtractFiles(Course course) throws IOException { - List exportErrors = new ArrayList<>(); + List exportErrors = Collections.synchronizedList(new ArrayList<>()); Optional exportedCourse = courseExamExportService.exportCourse(course, courseArchivesDirPath, exportErrors); assertThat(exportedCourse).isNotEmpty(); @@ -2118,7 +2118,7 @@ private List archiveCourseAndExtractFiles(Course course) throws IOExceptio public void testExportCourse_cannotCreateTmpDir() throws Exception { Course course = courseUtilService.createCourseWithTestModelingAndFileUploadExercisesAndSubmissions(userPrefix); - List exportErrors = new ArrayList<>(); + List exportErrors = Collections.synchronizedList(new ArrayList<>()); MockedStatic mockedFiles = mockStatic(Files.class); mockedFiles.when(() -> Files.createDirectories(argThat(path -> path.toString().contains("exports")))).thenThrow(IOException.class); @@ -2130,7 +2130,7 @@ public void testExportCourse_cannotCreateTmpDir() throws Exception { public void testExportCourse_cannotCreateCourseExercisesDir() throws Exception { Course course = courseUtilService.createCourseWithTestModelingAndFileUploadExercisesAndSubmissions(userPrefix); - List exportErrors = new ArrayList<>(); + List exportErrors = Collections.synchronizedList(new ArrayList<>()); MockedStatic mockedFiles = mockStatic(Files.class); mockedFiles.when(() -> Files.createDirectory(argThat(path -> path.toString().contains("course-exercises")))).thenThrow(IOException.class); @@ -2142,7 +2142,7 @@ public void testExportCourse_cannotCreateCourseExercisesDir() throws Exception { public void testExportCourseExam_cannotCreateTmpDir() throws Exception { Course course = courseUtilService.createCourseWithExamAndExercises(userPrefix); - List exportErrors = new ArrayList<>(); + List exportErrors = Collections.synchronizedList(new ArrayList<>()); Optional exam = examRepo.findByCourseId(course.getId()).stream().findFirst(); assertThat(exam).isPresent(); @@ -2157,7 +2157,7 @@ public void testExportCourseExam_cannotCreateTmpDir() throws Exception { public void testExportCourseExam_cannotCreateExamsDir() throws Exception { Course course = courseUtilService.createCourseWithExamAndExercises(userPrefix); - List exportErrors = new ArrayList<>(); + List exportErrors = Collections.synchronizedList(new ArrayList<>()); course = courseRepo.findWithEagerExercisesById(course.getId()); diff --git a/src/test/java/de/tum/in/www1/artemis/exam/ExamIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exam/ExamIntegrationTest.java index bd1b3d4ee8f3..3d3a23e2b37e 100644 --- a/src/test/java/de/tum/in/www1/artemis/exam/ExamIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exam/ExamIntegrationTest.java @@ -41,7 +41,6 @@ import de.tum.in.www1.artemis.domain.*; import de.tum.in.www1.artemis.domain.enumeration.*; import de.tum.in.www1.artemis.domain.exam.*; -import de.tum.in.www1.artemis.domain.metis.ConversationParticipant; import de.tum.in.www1.artemis.domain.metis.conversation.Channel; import de.tum.in.www1.artemis.domain.modeling.ModelingExercise; import de.tum.in.www1.artemis.domain.modeling.ModelingSubmission; @@ -60,7 +59,6 @@ import de.tum.in.www1.artemis.exercise.textexercise.TextExerciseUtilService; import de.tum.in.www1.artemis.participation.ParticipationUtilService; import de.tum.in.www1.artemis.repository.*; -import de.tum.in.www1.artemis.repository.metis.ConversationParticipantRepository; import de.tum.in.www1.artemis.repository.metis.conversation.ChannelRepository; import de.tum.in.www1.artemis.repository.plagiarism.PlagiarismCaseRepository; import de.tum.in.www1.artemis.security.SecurityUtils; @@ -177,9 +175,6 @@ class ExamIntegrationTest extends AbstractSpringIntegrationBambooBitbucketJiraTe @Autowired private ChannelRepository channelRepository; - @Autowired - private ConversationParticipantRepository conversationParticipantRepository; - @Autowired private UserUtilService userUtilService; @@ -1001,13 +996,6 @@ void testCreateExam_asInstructor() throws Exception { Channel channelFromDB = channelRepository.findChannelByExamId(savedExam.getId()); assertThat(channelFromDB).isNotNull(); - - // Check that the conversation participants are added correctly to the exercise channel - await().until(() -> { - SecurityUtils.setAuthorizationObject(); - Set conversationParticipants = conversationParticipantRepository.findConversationParticipantByConversationId(channelFromDB.getId()); - return conversationParticipants.size() == 3; // only the instructors and tutors should be added to exam channel, not students (see @BeforeEach) - }); } private List createExamsWithInvalidDates(Course course) { @@ -1693,13 +1681,6 @@ void testAddAllRegisteredUsersToExam() throws Exception { assertThat(channelFromDB).isNotNull(); assertThat(channelFromDB.getExam()).isEqualTo(exam); assertThat(channelFromDB.getName()).isEqualTo(examChannel.getName()); - - // Check that the conversation participants are added correctly to the exercise channel - await().until(() -> { - SecurityUtils.setAuthorizationObject(); - Set conversationParticipants = conversationParticipantRepository.findConversationParticipantByConversationId(channelFromDB.getId()); - return conversationParticipants.size() == 4; // 3 students should be added (see @BeforeEach) + 1 new student = 5 - }); } @Test diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionAndResultBitbucketBambooIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionAndResultBitbucketBambooIntegrationTest.java index ccd5d479c067..8a94261d32cd 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionAndResultBitbucketBambooIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/ProgrammingSubmissionAndResultBitbucketBambooIntegrationTest.java @@ -947,9 +947,9 @@ void shouldCreateGradleFeedback() throws Exception { } @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") - @MethodSource("testGracePeriodValues") + @MethodSource("testSubmissionAfterDueDateValues") @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void testGracePeriod(ZonedDateTime dueDate, SubmissionType expectedType, boolean expectedRated) throws Exception { + void testSubmissionAfterDueDate(ZonedDateTime dueDate, SubmissionType expectedType, boolean expectedRated, boolean testRun) throws Exception { var user = userRepository.findUserWithGroupsAndAuthoritiesByLogin(TEST_PREFIX + "student1").orElseThrow(); Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExercise(); @@ -959,6 +959,10 @@ void testGracePeriod(ZonedDateTime dueDate, SubmissionType expectedType, boolean // Add a participation for the programming exercise var participation = participationUtilService.addStudentParticipationForProgrammingExercise(programmingExercise, user.getLogin()); + if (testRun) { + participation.setTestRun(testRun); + participation = participationRepository.save(participation); + } // mock request for fetchCommitInfo() final String projectKey = "test201904bprogrammingexercise6"; @@ -993,13 +997,15 @@ void testGracePeriod(ZonedDateTime dueDate, SubmissionType expectedType, boolean assertThat(createdResult.getParticipation().getId()).isEqualTo(updatedParticipation.getId()); } - private static Stream testGracePeriodValues() { + private static Stream testSubmissionAfterDueDateValues() { ZonedDateTime now = ZonedDateTime.now(); return Stream.of( // short after due date -> grace period active, type manual + rated - Arguments.of(now.minusSeconds(10), SubmissionType.MANUAL, true), + Arguments.of(now.minusSeconds(10), SubmissionType.MANUAL, true, false), // long after due date -> grace period not active, illegal + non rated - Arguments.of(now.minusMinutes(2), SubmissionType.ILLEGAL, false)); + Arguments.of(now.minusMinutes(2), SubmissionType.ILLEGAL, false, false), + // After grace period but a practice submission and therefore ok + Arguments.of(now.minusMinutes(2), SubmissionType.MANUAL, false, true)); } private Result assertBuildError(Long participationId, String userLogin, ProgrammingLanguage programmingLanguage) throws Exception { diff --git a/src/test/java/de/tum/in/www1/artemis/metis/ChannelIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/metis/ChannelIntegrationTest.java index 94d013253145..8597c6c55704 100644 --- a/src/test/java/de/tum/in/www1/artemis/metis/ChannelIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/metis/ChannelIntegrationTest.java @@ -34,6 +34,8 @@ class ChannelIntegrationTest extends AbstractConversationTest { + private static final String TEST_PREFIX = "chtest"; + @Autowired TutorialGroupRepository tutorialGroupRepository; @@ -46,8 +48,6 @@ class ChannelIntegrationTest extends AbstractConversationTest { @Autowired private LectureRepository lectureRepository; - private static final String TEST_PREFIX = "chtest"; - @Autowired private TextExerciseUtilService textExerciseUtilService; @@ -244,8 +244,6 @@ void deleteChannel_asInstructor_shouldDeleteChannel(boolean isPublicChannel) thr request.delete("/api/courses/" + exampleCourseId + "/channels/" + channel.getId(), HttpStatus.OK); // then assertThat(channelRepository.findById(channel.getId())).isEmpty(); - verifyMultipleParticipantTopicWebsocketSent(MetisCrudAction.DELETE, channel.getId(), "instructor1"); - verifyNoParticipantTopicWebsocketSentExceptAction(MetisCrudAction.DELETE); } @ParameterizedTest @@ -283,8 +281,6 @@ void deleteChannel_asCreator_shouldDeleteChannel(boolean isPublicChannel) throws request.delete("/api/courses/" + exampleCourseId + "/channels/" + channel.getId(), HttpStatus.OK); // then assertThat(channelRepository.findById(channel.getId())).isEmpty(); - verifyMultipleParticipantTopicWebsocketSent(MetisCrudAction.DELETE, channel.getId(), "tutor1"); - verifyNoParticipantTopicWebsocketSentExceptAction(MetisCrudAction.DELETE); } @ParameterizedTest diff --git a/src/test/java/de/tum/in/www1/artemis/notification/GroupNotificationServiceTest.java b/src/test/java/de/tum/in/www1/artemis/notification/GroupNotificationServiceTest.java index 85672749103c..763e5cdf0842 100644 --- a/src/test/java/de/tum/in/www1/artemis/notification/GroupNotificationServiceTest.java +++ b/src/test/java/de/tum/in/www1/artemis/notification/GroupNotificationServiceTest.java @@ -289,7 +289,7 @@ void testNotifyAboutExerciseUpdate_correctReleaseDate_courseExercise() { */ @Test void testCheckNotificationForExerciseRelease_undefinedReleaseDate() { - groupNotificationScheduleService.checkNotificationsForNewExercise(exercise); + groupNotificationScheduleService.checkNotificationsForNewExerciseAsync(exercise); verify(groupNotificationService, timeout(1500)).notifyAllGroupsAboutReleasedExercise(any()); } @@ -299,7 +299,7 @@ void testCheckNotificationForExerciseRelease_undefinedReleaseDate() { @Test void testCheckNotificationForExerciseRelease_currentOrPastReleaseDate() { exercise.setReleaseDate(CURRENT_TIME); - groupNotificationScheduleService.checkNotificationsForNewExercise(exercise); + groupNotificationScheduleService.checkNotificationsForNewExerciseAsync(exercise); verify(groupNotificationService, timeout(1500)).notifyAllGroupsAboutReleasedExercise(any()); } @@ -309,7 +309,7 @@ void testCheckNotificationForExerciseRelease_currentOrPastReleaseDate() { @Test void testCheckNotificationForExerciseRelease_futureReleaseDate() { exercise.setReleaseDate(FUTURE_TIME); - groupNotificationScheduleService.checkNotificationsForNewExercise(exercise); + groupNotificationScheduleService.checkNotificationsForNewExerciseAsync(exercise); verify(instanceMessageSendService, timeout(1500)).sendExerciseReleaseNotificationSchedule(any()); } diff --git a/src/test/java/de/tum/in/www1/artemis/text/TextAssessmentIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/text/TextAssessmentIntegrationTest.java index efbea3b16883..196aa22e7ce1 100644 --- a/src/test/java/de/tum/in/www1/artemis/text/TextAssessmentIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/text/TextAssessmentIntegrationTest.java @@ -529,6 +529,38 @@ void getDataForTextEditor_testExam() throws Exception { assertThat(exam.getCourse()).isNull(); } + @Test + @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") + void getDataForTextEditor_secondCorrection_oneResult() throws Exception { + Exam exam = examUtilService.addExamWithExerciseGroup(course, true); + exam.setVisibleDate(now().minusHours(3)); + exam.setStartDate(now().minusHours(2)); + exam.setEndDate(now().minusHours(1)); + exam.setPublishResultsDate(now().minusMinutes(30)); + exam.setNumberOfCorrectionRoundsInExam(2); + + ExerciseGroup exerciseGroup = exam.getExerciseGroups().get(0); + TextExercise textExercise = TextExerciseFactory.generateTextExerciseForExam(exerciseGroup); + exerciseGroup.addExercise(textExercise); + exerciseGroupRepository.save(exerciseGroup); + textExercise = exerciseRepo.save(textExercise); + + examRepository.save(exam); + + TextSubmission textSubmission = ParticipationFactory.generateTextSubmission("Some text", Language.ENGLISH, true); + textSubmission = textExerciseUtilService.saveTextSubmissionWithResultAndAssessor(textExercise, textSubmission, TEST_PREFIX + "student1", TEST_PREFIX + "tutor1"); + // second correction round + textSubmission = textExerciseUtilService.saveTextSubmissionWithResultAndAssessor(textExercise, textSubmission, TEST_PREFIX + "student1", TEST_PREFIX + "tutor2"); + + var participation = request.get("/api/text-editor/" + textSubmission.getParticipation().getId(), HttpStatus.OK, Participation.class); + + assertThat(participation).isNotNull(); + assertThat(participation.getSubmissions()).containsExactly(textSubmission); + var submission = participation.getSubmissions().iterator().next(); + assertThat(submission.getResults()).hasSize(1); + assertThat(participation.getResults()).hasSize(1); + } + @Test @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void getDataForTextEditor_studentHidden() throws Exception {