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: Hide unreleased programming exercise results #9152

Merged
merged 3 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading