From 1638833398871518b24542af2fd07325cf7667d9 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Wed, 31 Jul 2024 14:17:26 +0200 Subject: [PATCH] Hide results based on studentExam state --- .../artemis/service/exam/ExamService.java | 16 +++++++- ...grammingExerciseParticipationResource.java | 39 ++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) 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 8a830851abcd..9c53024cc078 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 @@ -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. @@ -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 latestSubmission = participation.findLatestSubmission(); // To prevent LazyInitializationException. diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java index 3dd15c750255..ff820ab4e484 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java @@ -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; @@ -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; @@ -31,6 +33,7 @@ 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; @@ -38,6 +41,7 @@ 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; @@ -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; @@ -87,6 +94,7 @@ public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipation this.resultService = resultService; this.participationAuthCheckService = participationAuthCheckService; this.repositoryService = repositoryService; + this.studentExamRepository = studentExamRepository; } /** @@ -103,6 +111,9 @@ public ResponseEntity 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()); @@ -123,6 +134,9 @@ public ResponseEntity 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()); @@ -143,6 +157,12 @@ public ResponseEntity 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 = resultRepository.findLatestResultWithFeedbacksForParticipation(participation.getId(), withSubmission); result.ifPresent(value -> resultService.filterSensitiveInformationIfNecessary(participation, value)); @@ -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; + } + }