Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exam mode: Allow instructors to extend exam time #7119

Merged
merged 48 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c6f8c66
add a new REST call to enable time extension of the whole exam
Aug 30, 2023
e309d48
use correct request test method
Aug 30, 2023
3edca95
Merge branch 'develop' into feature/exam-mode/time-extension
Aug 30, 2023
ca8a348
Merge branch 'develop' into feature/exam-mode/time-extension
Aug 31, 2023
bb0aea1
Client implementation
aplr Sep 8, 2023
4d413b3
Update exam state, switch to relative value input, fix editor warning…
aplr Sep 9, 2023
ace83f6
Disable changing working time 5 minutes before exam end
aplr Sep 9, 2023
9652eb7
Add rescheduling of repository lock operations
aplr Sep 9, 2023
e41e989
Update src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java
aplr Sep 9, 2023
934ebc0
Merge commit 'f6e9a37cfc01ebccb4f439ba2cd90e77ddbedd84' into feature/…
aplr Sep 9, 2023
9871da5
Fix warning
aplr Sep 9, 2023
16648df
Self-review changes
aplr Sep 9, 2023
52f078e
Send working time extension instance notification once
aplr Sep 9, 2023
4504450
Teamscale fixes
aplr Sep 9, 2023
f7d357b
Rescheduling for whole exam, grouped by participation due date
aplr Sep 9, 2023
885c6b7
Merge commit '12057ed62683db3897691d395f5f93baf583a05a' into feature/…
aplr Sep 10, 2023
cc6df6b
Revert "Fix warning"
aplr Sep 10, 2023
b566e62
Add note to shadowed methods
aplr Sep 10, 2023
ed6a721
Update src/main/webapp/app/exam/shared/working-time-update/working-ti…
aplr Sep 10, 2023
09bda7a
Update debug log
aplr Sep 10, 2023
e2adaa8
Fix test declaration import
aplr Sep 10, 2023
56d42fd
Fix client tests
aplr Sep 12, 2023
f298f2d
PR feedback
aplr Sep 13, 2023
0c8469d
PR feedback
aplr Sep 13, 2023
6393263
Fix button naming
aplr Sep 13, 2023
f1437e5
PR feedback
aplr Sep 13, 2023
442352b
PR feedback
aplr Sep 13, 2023
c92220a
Remove comment
aplr Sep 13, 2023
9b8b92e
Use event emitter instead of callback
aplr Sep 13, 2023
c0fa9c6
Fix client tests
aplr Sep 13, 2023
4ae05d6
Unsubscribe on destroy
aplr Sep 13, 2023
438cd9a
Merge commit '4332d9e11655ed417350356ef0a3acf2c509df4a' into feature/…
aplr Sep 13, 2023
6b15787
Fix prettier
aplr Sep 14, 2023
26bed1f
improve logic for scheduling modeling exercises when updating (indivi…
Sep 14, 2023
3afaca9
Merge branch 'develop' into feature/exam-mode/time-extension
Strohgelaender Sep 14, 2023
2655403
Fix PR feedback
aplr Sep 14, 2023
5728c11
Fix PR feedback
aplr Sep 14, 2023
44e2402
Fix PR feedback
aplr Sep 14, 2023
67bbdab
Fetch exercise groups
aplr Sep 14, 2023
8f47b7e
Next checkout timeout
aplr Sep 14, 2023
9459fe1
Merge branch 'develop' of https://github.com/ls1intum/Artemis into fe…
Strohgelaender Sep 15, 2023
c3a4f28
fix an issue with individual end dates
Strohgelaender Sep 15, 2023
bb9f3a0
fix an issue with time extensions
Strohgelaender Sep 15, 2023
b4d537c
Code cleanup
aplr Sep 16, 2023
73adf8e
Update note
aplr Sep 16, 2023
df245cf
update note
Strohgelaender Sep 16, 2023
46f142c
Merge branch 'develop' into feature/exam-mode/time-extension
Strohgelaender Sep 16, 2023
a69f318
Display absolute working time
aplr Sep 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,11 @@ public final class Constants {
*/
public static final int GROUP_CONVERSATION_HUMAN_READABLE_NAME_LIMIT = 100;

/**
* The name of the topic for notifying the client about changes in the exam working time.
*/
public static final String STUDENT_WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC = "/topic/studentExams/%s/working-time-change-during-conduction";

private Constants() {
}
}
9 changes: 9 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/domain/exam/Exam.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.tum.in.www1.artemis.domain.exam;

import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
Expand Down Expand Up @@ -192,6 +193,14 @@ public void setEndDate(@NotNull ZonedDateTime endDate) {
this.endDate = endDate;
}

/**
* @return the duration of the exam in seconds
*/
@JsonIgnore
public int getDuration() {
return Math.toIntExact(Duration.between(getStartDate(), getEndDate()).toSeconds());
}

public ZonedDateTime getPublishResultsDate() {
return publishResultsDate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
import de.tum.in.www1.artemis.service.*;
import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseParticipationService;
import de.tum.in.www1.artemis.service.programming.ProgrammingTriggerService;
import de.tum.in.www1.artemis.service.scheduled.ProgrammingExerciseScheduleService;
import de.tum.in.www1.artemis.service.util.ExamExerciseStartPreparationStatus;
import de.tum.in.www1.artemis.service.util.Tuple;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException;

Expand All @@ -51,8 +53,6 @@ public class StudentExamService {

private static final String EXAM_EXERCISE_START_STATUS_TOPIC = "/topic/exams/%s/exercise-start-status";

private static final String WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC = "/topic/studentExams/%s/working-time-change-during-conduction";

private final Logger log = LoggerFactory.getLogger(StudentExamService.class);

private final ParticipationService participationService;
Expand Down Expand Up @@ -89,6 +89,8 @@ public class StudentExamService {

private final WebsocketMessagingService websocketMessagingService;

private final ProgrammingExerciseScheduleService programmingExerciseScheduleService;

private final TaskScheduler scheduler;

public StudentExamService(StudentExamRepository studentExamRepository, UserRepository userRepository, ParticipationService participationService,
Expand All @@ -97,7 +99,7 @@ public StudentExamService(StudentExamRepository studentExamRepository, UserRepos
ProgrammingExerciseParticipationService programmingExerciseParticipationService, SubmissionService submissionService,
StudentParticipationRepository studentParticipationRepository, ExamQuizService examQuizService, ProgrammingExerciseRepository programmingExerciseRepository,
ProgrammingTriggerService programmingTriggerService, ExamRepository examRepository, CacheManager cacheManager, WebsocketMessagingService websocketMessagingService,
@Qualifier("taskScheduler") TaskScheduler scheduler) {
ProgrammingExerciseScheduleService programmingExerciseScheduleService, @Qualifier("taskScheduler") TaskScheduler scheduler) {
this.participationService = participationService;
this.studentExamRepository = studentExamRepository;
this.userRepository = userRepository;
Expand All @@ -115,6 +117,7 @@ public StudentExamService(StudentExamRepository studentExamRepository, UserRepos
this.examRepository = examRepository;
this.cacheManager = cacheManager;
this.websocketMessagingService = websocketMessagingService;
this.programmingExerciseScheduleService = programmingExerciseScheduleService;
this.scheduler = scheduler;
}

Expand Down Expand Up @@ -646,6 +649,13 @@ private void setUpExerciseParticipationsAndSubmissionsWithInitializationDate(Stu
|| ExamDateService.getExamProgrammingExerciseUnlockDate(programmingExercise).isBefore(ZonedDateTime.now())) {
// Note: only unlock the programming exercise student repository for the affected user (Important: Do NOT invoke unlockAll)
programmingExerciseParticipationService.unlockStudentRepositoryAndParticipation(programmingParticipation);

// This is a special case if "prepare exercise start" was pressed shortly before the exam start
// Normally, the locking operation at the end of the exam gets scheduled during the initial unlocking process
// (see ProgrammingExerciseScheduleService#scheduleIndividualRepositoryAndParticipationLockTasks)
// Since this gets never executed here, we need to manually schedule the locking.
var tupel = new Tuple<>(studentExam.getIndividualEndDate(), programmingParticipation);
programmingExerciseScheduleService.scheduleIndividualRepositoryAndParticipationLockTasks(programmingExercise, Set.of(tupel));
aplr marked this conversation as resolved.
Show resolved Hide resolved
}
else {
programmingExerciseParticipationService.lockStudentParticipation(programmingParticipation);
Expand Down Expand Up @@ -786,8 +796,4 @@ private StudentExam generateIndividualStudentExam(Exam exam, User student) {
userHashSet.add(student);
return studentExamRepository.createRandomStudentExams(exam, userHashSet).get(0);
}

public void notifyStudentAboutWorkingTimeChangeDuringConduction(StudentExam studentExam) {
websocketMessagingService.sendMessage(WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC.formatted(studentExam.getId()), studentExam.getWorkingTime());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,13 @@ public void sendAssessedExerciseSubmissionNotificationSchedule(Long exerciseId)
}

@Override
public void sendExamWorkingTimeChangeDuringConduction(Long studentExamId) {
public void sendExamWorkingTimeChangeDuringConduction(Long examId) {
log.info("Sending schedule to reschedule exam {} to broker.", examId);
sendMessageDelayed(MessageTopic.EXAM_RESCHEDULE_DURING_CONDUCTION, examId);
}

@Override
public void sendStudentExamIndividualWorkingTimeChangeDuringConduction(Long studentExamId) {
log.info("Sending schedule to reschedule student exam {} to broker.", studentExamId);
sendMessageDelayed(MessageTopic.STUDENT_EXAM_RESCHEDULE_DURING_CONDUCTION, studentExamId);
}
Expand All @@ -162,6 +168,7 @@ public void sendParticipantScoreSchedule(Long exerciseId, Long participantId, Lo
sendMessageDelayed(MessageTopic.PARTICIPANT_SCORE_SCHEDULE, exerciseId, participantId, resultId);
}

// NOTE: Don't remove any of the following methods despite the warning.
private void sendMessageDelayed(MessageTopic topic, Long payload) {
exec.schedule(() -> hazelcastInstance.getTopic(topic.toString()).publish(payload), 1, TimeUnit.SECONDS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,14 @@
SecurityUtils.setAuthorizationObject();
processScheduleAssessedExerciseSubmittedNotification((message.getMessageObject()));
});
hazelcastInstance.<Long>getTopic(MessageTopic.STUDENT_EXAM_RESCHEDULE_DURING_CONDUCTION.toString()).addMessageListener(message -> {
hazelcastInstance.<Long>getTopic(MessageTopic.EXAM_RESCHEDULE_DURING_CONDUCTION.toString()).addMessageListener(message -> {
SecurityUtils.setAuthorizationObject();
processExamWorkingTimeChangeDuringConduction(message.getMessageObject());
});
hazelcastInstance.<Long>getTopic(MessageTopic.STUDENT_EXAM_RESCHEDULE_DURING_CONDUCTION.toString()).addMessageListener(message -> {
SecurityUtils.setAuthorizationObject();
processStudentExamIndividualWorkingTimeChangeDuringConduction(message.getMessageObject());
});
hazelcastInstance.<Long[]>getTopic(MessageTopic.PARTICIPANT_SCORE_SCHEDULE.toString()).addMessageListener(message -> {
SecurityUtils.setAuthorizationObject();
processScheduleParticipantScore(message.getMessageObject()[0], message.getMessageObject()[1], message.getMessageObject()[2]);
Expand Down Expand Up @@ -286,7 +290,12 @@
notificationScheduleService.updateSchedulingForAssessedExercisesSubmissions(exercise);
}

public void processExamWorkingTimeChangeDuringConduction(Long studentExamId) {
public void processExamWorkingTimeChangeDuringConduction(Long examId) {
log.info("Received reschedule of exam during conduction {}", examId);
programmingExerciseScheduleService.rescheduleExamDuringConduction(examId);
}

public void processStudentExamIndividualWorkingTimeChangeDuringConduction(Long studentExamId) {

Check warning on line 298 in src/main/java/de/tum/in/www1/artemis/service/messaging/InstanceMessageReceiveService.java

View check run for this annotation

Teamscale / teamscale-findings

src/main/java/de/tum/in/www1/artemis/service/messaging/InstanceMessageReceiveService.java#L298

In this file 24 interface comments are missing. Consider adding explanatory comments or restricting the visibility. View in Teamscale: https://teamscale.io/activity.html#details/GitHub-ls1intum-Artemis?t=feature%2Fexam-mode%2Ftime-extension%3A1694871322000
log.info("Received reschedule of student exam during conduction {}", studentExamId);
programmingExerciseScheduleService.rescheduleStudentExamDuringConduction(studentExamId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,19 @@ public interface InstanceMessageSendService {
*/
void sendAssessedExerciseSubmissionNotificationSchedule(Long exerciseId);

/**
* Send a message to the main server that the working time of an exam was changed during the conduction and rescheduling might be necessary
*
* @param examId the id of the exam that should be scheduled
*/
void sendExamWorkingTimeChangeDuringConduction(Long examId);

/**
* Send a message to the main server that the working time of a student exam was changed during the conduction and rescheduling might be necessary
*
* @param studentExamId the id of the student exam that should be scheduled
*/
void sendExamWorkingTimeChangeDuringConduction(Long studentExamId);
void sendStudentExamIndividualWorkingTimeChangeDuringConduction(Long studentExamId);

/**
* Send a message to the main server that schedules to update the participant score for this exercise/participant
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,13 @@ public void sendAssessedExerciseSubmissionNotificationSchedule(Long exerciseId)
}

@Override
public void sendExamWorkingTimeChangeDuringConduction(Long studentExamId) {
instanceMessageReceiveService.processExamWorkingTimeChangeDuringConduction(studentExamId);
public void sendExamWorkingTimeChangeDuringConduction(Long examId) {
instanceMessageReceiveService.processExamWorkingTimeChangeDuringConduction(examId);
}

@Override
public void sendStudentExamIndividualWorkingTimeChangeDuringConduction(Long studentExamId) {
instanceMessageReceiveService.processStudentExamIndividualWorkingTimeChangeDuringConduction(studentExamId);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum MessageTopic {
USER_MANAGEMENT_CANCEL_REMOVE_NON_ACTIVATED_USERS("user-management-cancel-remove-non-activated-users"),
EXERCISE_RELEASED_SCHEDULE("exercise-released-schedule"),
ASSESSED_EXERCISE_SUBMISSION_SCHEDULE("assessed-exercise-submission-schedule"),
EXAM_RESCHEDULE_DURING_CONDUCTION("exam-reschedule-during-conduction"),
STUDENT_EXAM_RESCHEDULE_DURING_CONDUCTION("student-exam-reschedule-during-conduction"),
PARTICIPANT_SCORE_SCHEDULE("participant-score-schedule");
// @formatter:on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import de.tum.in.www1.artemis.domain.enumeration.AssessmentType;
import de.tum.in.www1.artemis.domain.enumeration.ExerciseLifecycle;
import de.tum.in.www1.artemis.domain.modeling.ModelingExercise;
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.repository.ModelingExerciseRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
import de.tum.in.www1.artemis.service.compass.CompassService;
import de.tum.in.www1.artemis.service.exam.ExamDateService;
Expand Down Expand Up @@ -128,9 +128,7 @@ private void scheduleCourseExercise(ModelingExercise exercise) {

// For any course exercise that needsToBeScheduled (buildAndTestAfterDueDate and/or manual assessment)
if (exercise.getDueDate() != null && ZonedDateTime.now().isBefore(exercise.getDueDate())) {
scheduleService.scheduleTask(exercise, ExerciseLifecycle.DUE, () -> {
buildModelingClusters(exercise).run();
});
scheduleService.scheduleTask(exercise, ExerciseLifecycle.DUE, () -> buildModelingClusters(exercise).run());
log.debug("Scheduled build modeling clusters after due date for Modeling Exercise '{}' (#{}) for {}.", exercise.getTitle(), exercise.getId(), exercise.getDueDate());
}
else {
Expand All @@ -148,9 +146,7 @@ private void scheduleExamExercise(ModelingExercise exercise) {
if (ZonedDateTime.now().isBefore(examDateService.getLatestIndividualExamEndDateWithGracePeriod(exam))) {
var buildDate = endDate.plusMinutes(EXAM_END_WAIT_TIME_FOR_COMPASS_MINUTES);
exercise.setClusterBuildDate(buildDate);
scheduleService.scheduleTask(exercise, ExerciseLifecycle.BUILD_COMPASS_CLUSTERS_AFTER_EXAM, () -> {
buildModelingClusters(exercise).run();
});
scheduleService.scheduleTask(exercise, ExerciseLifecycle.BUILD_COMPASS_CLUSTERS_AFTER_EXAM, () -> buildModelingClusters(exercise).run());
}
log.debug("Scheduled Exam Modeling Exercise '{}' (#{}).", exercise.getTitle(), exercise.getId());
}
Expand All @@ -166,7 +162,7 @@ public void scheduleExerciseForInstant(ModelingExercise exercise) {

/**
* Returns a runnable that, once executed, will build modeling clusters
*
* <p>
* NOTE: this will not build modeling clusters as only a Runnable is returned!
*
* @param exercise The exercise for which the clusters will be created
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package de.tum.in.www1.artemis.service.scheduled;

import java.time.ZonedDateTime;
import java.util.*;
import java.util.Set;

import javax.annotation.PostConstruct;

Expand Down
Loading
Loading