Skip to content

Commit

Permalink
Exam mode: Hide unreleased programming exercise results (#9152)
Browse files Browse the repository at this point in the history
  • Loading branch information
pzdr7 committed Aug 1, 2024
1 parent 5b0b084 commit 35b893a
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 4 deletions.
16 changes: 14 additions & 2 deletions src/main/java/de/tum/in/www1/artemis/service/exam/ExamService.java
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,19 @@ else if (submissionPolicy != null && Boolean.TRUE.equals(submissionPolicy.isActi
}
}

/**
* Determines whether the student should see the result of the exam.
* This is the case if the exam is started and not ended yet or if the results are already published.
*
* @param studentExam The student exam
* @param participation The participation of the student
* @return true if the student should see the result, false otherwise
*/
public static boolean shouldStudentSeeResult(StudentExam studentExam, StudentParticipation participation) {
return (studentExam.getExam().isStarted() && !studentExam.isEnded() && participation instanceof ProgrammingExerciseStudentParticipation)
|| studentExam.areResultsPublishedYet();
}

/**
* Helper method which attaches the result to its participation.
* For direct automatic feedback during the exam conduction for {@link ProgrammingExercise}, we need to attach the results.
Expand All @@ -642,8 +655,7 @@ else if (submissionPolicy != null && Boolean.TRUE.equals(submissionPolicy.isActi
*/
private static void setResultIfNecessary(StudentExam studentExam, StudentParticipation participation, boolean isAtLeastInstructor) {
// Only set the result during the exam for programming exercises (for direct automatic feedback) or after publishing the results
boolean isStudentAllowedToSeeResult = (studentExam.getExam().isStarted() && !studentExam.isEnded() && participation instanceof ProgrammingExerciseStudentParticipation)
|| studentExam.areResultsPublishedYet();
boolean isStudentAllowedToSeeResult = shouldStudentSeeResult(studentExam, participation);
Optional<Submission> latestSubmission = participation.findLatestSubmission();

// To prevent LazyInitializationException.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.jgit.api.errors.GitAPIException;
Expand All @@ -22,6 +23,7 @@
import de.tum.in.www1.artemis.domain.ProgrammingExercise;
import de.tum.in.www1.artemis.domain.ProgrammingSubmission;
import de.tum.in.www1.artemis.domain.Result;
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.VcsRepositoryUri;
import de.tum.in.www1.artemis.domain.enumeration.RepositoryType;
import de.tum.in.www1.artemis.domain.participation.Participation;
Expand All @@ -31,13 +33,15 @@
import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository;
import de.tum.in.www1.artemis.repository.ResultRepository;
import de.tum.in.www1.artemis.repository.StudentExamRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastTutor;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.ParticipationAuthorizationCheckService;
import de.tum.in.www1.artemis.service.ResultService;
import de.tum.in.www1.artemis.service.exam.ExamService;
import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseParticipationService;
import de.tum.in.www1.artemis.service.programming.ProgrammingSubmissionService;
import de.tum.in.www1.artemis.service.programming.RepositoryService;
Expand Down Expand Up @@ -73,10 +77,13 @@ public class ProgrammingExerciseParticipationResource {

private final RepositoryService repositoryService;

private final StudentExamRepository studentExamRepository;

public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipationService programmingExerciseParticipationService, ResultRepository resultRepository,
ParticipationRepository participationRepository, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository,
ProgrammingSubmissionService submissionService, ProgrammingExerciseRepository programmingExerciseRepository, AuthorizationCheckService authCheckService,
ResultService resultService, ParticipationAuthorizationCheckService participationAuthCheckService, RepositoryService repositoryService) {
ResultService resultService, ParticipationAuthorizationCheckService participationAuthCheckService, RepositoryService repositoryService,
StudentExamRepository studentExamRepository) {
this.programmingExerciseParticipationService = programmingExerciseParticipationService;
this.participationRepository = participationRepository;
this.programmingExerciseStudentParticipationRepository = programmingExerciseStudentParticipationRepository;
Expand All @@ -87,6 +94,7 @@ public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipation
this.resultService = resultService;
this.participationAuthCheckService = participationAuthCheckService;
this.repositoryService = repositoryService;
this.studentExamRepository = studentExamRepository;
}

/**
Expand All @@ -103,6 +111,9 @@ public ResponseEntity<ProgrammingExerciseStudentParticipation> getParticipationW
.orElseThrow(() -> new EntityNotFoundException("Participation", participationId));

hasAccessToParticipationElseThrow(participation);
if (shouldHideExamExerciseResults(participation)) {
participation.setResults(Set.of());
}

// hide details that should not be shown to the students
resultService.filterSensitiveInformationIfNecessary(participation, participation.getResults(), Optional.empty());
Expand All @@ -123,6 +134,9 @@ public ResponseEntity<ProgrammingExerciseStudentParticipation> getParticipationW

// TODO: improve access checks to avoid fetching the user multiple times
hasAccessToParticipationElseThrow(participation);
if (shouldHideExamExerciseResults(participation)) {
participation.setResults(Set.of());
}

// hide details that should not be shown to the students
resultService.filterSensitiveInformationIfNecessary(participation, participation.getResults(), Optional.empty());
Expand All @@ -143,6 +157,12 @@ public ResponseEntity<Result> getLatestResultWithFeedbacksForProgrammingExercise
@RequestParam(defaultValue = "false") boolean withSubmission) {
var participation = participationRepository.findByIdElseThrow(participationId);
participationAuthCheckService.checkCanAccessParticipationElseThrow(participation);

if (participation instanceof ProgrammingExerciseStudentParticipation programmingExerciseStudentParticipation
&& shouldHideExamExerciseResults(programmingExerciseStudentParticipation)) {
return ResponseEntity.ok(null);
}

Optional<Result> result = resultRepository.findLatestResultWithFeedbacksForParticipation(participation.getId(), withSubmission);
result.ifPresent(value -> resultService.filterSensitiveInformationIfNecessary(participation, value));

Expand Down Expand Up @@ -390,4 +410,21 @@ private void hasAccessToParticipationElseThrow(ProgrammingExerciseStudentPartici
}
}

/**
* Checks if the results should be hidden for the given participation.
*
* @param participation the participation to check
* @return true if the results should be hidden, false otherwise
*/
private boolean shouldHideExamExerciseResults(ProgrammingExerciseStudentParticipation participation) {
if (participation.getProgrammingExercise().isExamExercise()) {
User student = participation.getStudent()
.orElseThrow(() -> new EntityNotFoundException("Participation with id " + participation.getId() + " does not have a student!"));
var studentExam = studentExamRepository.findByExerciseIdAndUserId(participation.getExercise().getId(), student.getId())
.orElseThrow(() -> new EntityNotFoundException("Participation " + participation.getId() + " does not have a student exam!"));
return !ExamService.shouldStudentSeeResult(studentExam, participation);
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private void broadcastNewResultToParticipants(StudentParticipation studentPartic
final Exercise exercise = studentParticipation.getExercise();
boolean isWorkingPeriodOver;
if (exercise.isExamExercise()) {
isWorkingPeriodOver = examDateService.isExerciseWorkingPeriodOver(exercise, studentParticipation);
isWorkingPeriodOver = examDateService.isIndividualExerciseWorkingPeriodOver(exercise.getExam(), studentParticipation);
}
else {
isWorkingPeriodOver = exerciseDateService.isAfterLatestDueDate(exercise);
Expand Down

0 comments on commit 35b893a

Please sign in to comment.