use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project ArTEMiS by ls1intum.
the class FileUploadAssessmentIntegrationTest method multipleCorrectionRoundsForExam.
@Test
@WithMockUser(username = "tutor1", roles = "TA")
public void multipleCorrectionRoundsForExam() throws Exception {
// Setup exam with 2 correction rounds and a programming exercise
ExerciseGroup exerciseGroup1 = new ExerciseGroup();
Exam exam = database.addExam(course);
exam.setNumberOfCorrectionRoundsInExam(2);
exam.addExerciseGroup(exerciseGroup1);
exam.setVisibleDate(ZonedDateTime.now().minusHours(3));
exam.setStartDate(ZonedDateTime.now().minusHours(2));
exam.setEndDate(ZonedDateTime.now().minusHours(1));
exam = examRepository.save(exam);
Exam examWithExerciseGroups = examRepository.findWithExerciseGroupsAndExercisesById(exam.getId()).get();
exerciseGroup1 = examWithExerciseGroups.getExerciseGroups().get(0);
FileUploadExercise exercise = ModelFactory.generateFileUploadExerciseForExam("test.pdf", exerciseGroup1);
exercise = fileUploadExerciseRepository.save(exercise);
exerciseGroup1.addExercise(exercise);
// add student submission
var submission = ModelFactory.generateFileUploadSubmission(true);
submission = database.addFileUploadSubmission(exercise, submission, "student1");
// verify setup
assertThat(exam.getNumberOfCorrectionRoundsInExam()).isEqualTo(2);
assertThat(exam.getEndDate()).isBefore(ZonedDateTime.now());
var optionalFetchedExercise = exerciseRepository.findWithEagerStudentParticipationsStudentAndSubmissionsById(exercise.getId());
assertThat(optionalFetchedExercise).isPresent();
final var exerciseWithParticipation = optionalFetchedExercise.get();
final var studentParticipation = exerciseWithParticipation.getStudentParticipations().stream().iterator().next();
// request to manually assess latest submission (correction round: 0)
LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("lock", "true");
params.add("correction-round", "0");
FileUploadSubmission submissionWithoutFirstAssessment = request.get("/api/exercises/" + exerciseWithParticipation.getId() + "/file-upload-submission-without-assessment", HttpStatus.OK, FileUploadSubmission.class, params);
// verify that no new submission was created
assertThat(submissionWithoutFirstAssessment).isEqualTo(submission);
// verify that the lock has been set
assertThat(submissionWithoutFirstAssessment.getLatestResult()).isNotNull();
assertThat(submissionWithoutFirstAssessment.getLatestResult().getAssessor().getLogin()).isEqualTo("tutor1");
assertThat(submissionWithoutFirstAssessment.getLatestResult().getAssessmentType()).isEqualTo(AssessmentType.MANUAL);
// make sure that new result correctly appears inside the continue box
LinkedMultiValueMap<String, String> paramsGetAssessedCR1Tutor1 = new LinkedMultiValueMap<>();
paramsGetAssessedCR1Tutor1.add("assessedByTutor", "true");
paramsGetAssessedCR1Tutor1.add("correction-round", "0");
var assessedSubmissionList = request.getList("/api/exercises/" + exerciseWithParticipation.getId() + "/file-upload-submissions", HttpStatus.OK, FileUploadSubmission.class, paramsGetAssessedCR1Tutor1);
assertThat(assessedSubmissionList).hasSize(1);
assertThat(assessedSubmissionList.get(0).getId()).isEqualTo(submissionWithoutFirstAssessment.getId());
assertThat(assessedSubmissionList.get(0).getResultForCorrectionRound(0)).isEqualTo(submissionWithoutFirstAssessment.getLatestResult());
// assess submission and submit
List<Feedback> feedbacks = ModelFactory.generateFeedback().stream().peek(feedback -> feedback.setDetailText("Good work here")).collect(Collectors.toList());
params = new LinkedMultiValueMap<>();
params.add("submit", "true");
final Result firstSubmittedManualResult = request.putWithResponseBodyAndParams(API_FILE_UPLOAD_SUBMISSIONS + submissionWithoutFirstAssessment.getId() + "/feedback", feedbacks, Result.class, HttpStatus.OK, params);
// make sure that new result correctly appears after the assessment for first correction round
assessedSubmissionList = request.getList("/api/exercises/" + exerciseWithParticipation.getId() + "/file-upload-submissions", HttpStatus.OK, FileUploadSubmission.class, paramsGetAssessedCR1Tutor1);
assertThat(assessedSubmissionList).hasSize(1);
assertThat(assessedSubmissionList.get(0).getId()).isEqualTo(submissionWithoutFirstAssessment.getId());
assertThat(assessedSubmissionList.get(0).getResultForCorrectionRound(0)).isNotNull();
assertThat(firstSubmittedManualResult.getAssessor().getLogin()).isEqualTo("tutor1");
// verify that the result contains the relationship
assertThat(firstSubmittedManualResult).isNotNull();
assertThat(firstSubmittedManualResult.getParticipation()).isEqualTo(studentParticipation);
// verify that the relationship between student participation,
var databaseRelationshipStateOfResultsOverParticipation = studentParticipationRepository.findWithEagerLegalSubmissionsAndResultsAssessorsById(studentParticipation.getId());
assertThat(databaseRelationshipStateOfResultsOverParticipation).isPresent();
var fetchedParticipation = databaseRelationshipStateOfResultsOverParticipation.get();
assertThat(fetchedParticipation.getSubmissions()).hasSize(1);
assertThat(fetchedParticipation.findLatestSubmission()).contains(submissionWithoutFirstAssessment);
assertThat(fetchedParticipation.findLatestLegalResult()).isEqualTo(firstSubmittedManualResult);
var databaseRelationshipStateOfResultsOverSubmission = studentParticipationRepository.findAllWithEagerSubmissionsAndEagerResultsAndEagerAssessorByExerciseId(exercise.getId());
assertThat(databaseRelationshipStateOfResultsOverSubmission).hasSize(1);
fetchedParticipation = databaseRelationshipStateOfResultsOverSubmission.get(0);
assertThat(fetchedParticipation.getSubmissions()).hasSize(1);
assertThat(fetchedParticipation.findLatestSubmission()).isPresent();
// it should contain the lock for the manual result
assertThat(fetchedParticipation.findLatestSubmission().get().getResults()).hasSize(1);
assertThat(fetchedParticipation.findLatestSubmission().get().getLatestResult()).isEqualTo(firstSubmittedManualResult);
// SECOND ROUND OF CORRECTION
database.changeUser("tutor2");
LinkedMultiValueMap<String, String> paramsSecondCorrection = new LinkedMultiValueMap<>();
paramsSecondCorrection.add("lock", "true");
paramsSecondCorrection.add("correction-round", "1");
final var submissionWithoutSecondAssessment = request.get("/api/exercises/" + exerciseWithParticipation.getId() + "/file-upload-submission-without-assessment", HttpStatus.OK, FileUploadSubmission.class, paramsSecondCorrection);
// verify that the submission is not new
assertThat(submissionWithoutSecondAssessment).isEqualTo(submission);
// verify that the lock has been set
assertThat(submissionWithoutSecondAssessment.getLatestResult()).isNotNull();
assertThat(submissionWithoutSecondAssessment.getLatestResult().getAssessor().getLogin()).isEqualTo("tutor2");
assertThat(submissionWithoutSecondAssessment.getLatestResult().getAssessmentType()).isEqualTo(AssessmentType.MANUAL);
// verify that the relationship between student participation,
databaseRelationshipStateOfResultsOverParticipation = studentParticipationRepository.findWithEagerLegalSubmissionsAndResultsAssessorsById(studentParticipation.getId());
assertThat(databaseRelationshipStateOfResultsOverParticipation).isPresent();
fetchedParticipation = databaseRelationshipStateOfResultsOverParticipation.get();
assertThat(fetchedParticipation.getSubmissions()).hasSize(1);
assertThat(fetchedParticipation.findLatestSubmission()).contains(submissionWithoutSecondAssessment);
assertThat(fetchedParticipation.getResults().stream().filter(x -> x.getCompletionDate() == null).findFirst()).contains(submissionWithoutSecondAssessment.getLatestResult());
databaseRelationshipStateOfResultsOverSubmission = studentParticipationRepository.findAllWithEagerSubmissionsAndEagerResultsAndEagerAssessorByExerciseId(exercise.getId());
assertThat(databaseRelationshipStateOfResultsOverSubmission).hasSize(1);
fetchedParticipation = databaseRelationshipStateOfResultsOverSubmission.get(0);
assertThat(fetchedParticipation.getSubmissions()).hasSize(1);
assertThat(fetchedParticipation.findLatestSubmission()).isPresent();
assertThat(fetchedParticipation.findLatestSubmission().get().getResults()).hasSize(2);
assertThat(fetchedParticipation.findLatestSubmission().get().getLatestResult()).isEqualTo(submissionWithoutSecondAssessment.getLatestResult());
// assess submission and submit
feedbacks = ModelFactory.generateFeedback().stream().peek(feedback -> feedback.setDetailText("Good work here")).collect(Collectors.toList());
params = new LinkedMultiValueMap<>();
params.add("submit", "true");
final var secondSubmittedManualResult = request.putWithResponseBodyAndParams(API_FILE_UPLOAD_SUBMISSIONS + submissionWithoutFirstAssessment.getId() + "/feedback", feedbacks, Result.class, HttpStatus.OK, params);
assertThat(secondSubmittedManualResult).isNotNull();
// make sure that new result correctly appears after the assessment for second correction round
LinkedMultiValueMap<String, String> paramsGetAssessedCR2 = new LinkedMultiValueMap<>();
paramsGetAssessedCR2.add("assessedByTutor", "true");
paramsGetAssessedCR2.add("correction-round", "1");
assessedSubmissionList = request.getList("/api/exercises/" + exerciseWithParticipation.getId() + "/file-upload-submissions", HttpStatus.OK, FileUploadSubmission.class, paramsGetAssessedCR2);
assertThat(assessedSubmissionList).hasSize(1);
assertThat(assessedSubmissionList.get(0).getId()).isEqualTo(submissionWithoutSecondAssessment.getId());
assertThat(assessedSubmissionList.get(0).getResultForCorrectionRound(1)).isEqualTo(secondSubmittedManualResult);
// make sure that they do not appear for the first correction round as the tutor only assessed the second correction round
LinkedMultiValueMap<String, String> paramsGetAssessedCR1 = new LinkedMultiValueMap<>();
paramsGetAssessedCR1.add("assessedByTutor", "true");
paramsGetAssessedCR1.add("correction-round", "0");
assessedSubmissionList = request.getList("/api/exercises/" + exerciseWithParticipation.getId() + "/file-upload-submissions", HttpStatus.OK, FileUploadSubmission.class, paramsGetAssessedCR1);
assertThat(assessedSubmissionList).isEmpty();
// Student should not have received a result over WebSocket as manual correction is ongoing
verify(messagingTemplate, never()).convertAndSendToUser(notNull(), eq(Constants.NEW_RESULT_TOPIC), isA(Result.class));
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project ArTEMiS by ls1intum.
the class FileUploadExerciseIntegrationTest method createFileUploadExercise_setBothCourseAndExerciseGroupOrNeither_badRequest.
@Test
@WithMockUser(username = "instructor1", roles = "INSTRUCTOR")
public void createFileUploadExercise_setBothCourseAndExerciseGroupOrNeither_badRequest() throws Exception {
ExerciseGroup exerciseGroup = database.addExerciseGroupWithExamAndCourse(true);
FileUploadExercise fileUploadExercise = ModelFactory.generateFileUploadExerciseForExam(creationFilePattern, exerciseGroup);
fileUploadExercise.setCourse(fileUploadExercise.getExerciseGroup().getExam().getCourse());
request.postWithResponseBody("/api/file-upload-exercises/", fileUploadExercise, FileUploadExercise.class, HttpStatus.BAD_REQUEST);
fileUploadExercise.setCourse(null);
fileUploadExercise.setExerciseGroup(null);
request.postWithResponseBody("/api/file-upload-exercises/", fileUploadExercise, FileUploadExercise.class, HttpStatus.BAD_REQUEST);
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project ArTEMiS by ls1intum.
the class ExamService method validateForStudentExamGeneration.
/**
* Validates exercise settings.
*
* @param exam exam which is validated
* @throws BadRequestAlertException an exception if the exam is not configured correctly
*/
public void validateForStudentExamGeneration(Exam exam) throws BadRequestAlertException {
List<ExerciseGroup> exerciseGroups = exam.getExerciseGroups();
long numberOfExercises = exam.getNumberOfExercisesInExam() != null ? exam.getNumberOfExercisesInExam() : 0;
long numberOfOptionalExercises = numberOfExercises - exerciseGroups.stream().filter(ExerciseGroup::getIsMandatory).count();
// Ensure that all exercise groups have at least one exercise
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
if (exerciseGroup.getExercises().isEmpty()) {
throw new BadRequestAlertException("All exercise groups must have at least one exercise", "Exam", "artemisApp.exam.validation.atLeastOneExercisePerExerciseGroup");
}
}
// Check that numberOfExercisesInExam is set
if (exam.getNumberOfExercisesInExam() == null) {
throw new BadRequestAlertException("The number of exercises in the exam is not set.", "Exam", "artemisApp.exam.validation.numberOfExercisesInExamNotSet");
}
// Check that there are enough exercise groups
if (exam.getExerciseGroups().size() < exam.getNumberOfExercisesInExam()) {
throw new BadRequestAlertException("The number of exercise groups is too small", "Exam", "artemisApp.exam.validation.tooFewExerciseGroups");
}
// Check that there are not too much mandatory exercise groups
if (numberOfOptionalExercises < 0) {
throw new BadRequestAlertException("The number of mandatory exercise groups is too large", "Exam", "artemisApp.exam.validation.tooManyMandatoryExerciseGroups");
}
// Ensure that all exercises in an exercise group have the same meaning for the exam score calculation
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
Set<IncludedInOverallScore> meaningsForScoreCalculation = exerciseGroup.getExercises().stream().map(Exercise::getIncludedInOverallScore).collect(Collectors.toSet());
if (meaningsForScoreCalculation.size() > 1) {
throw new BadRequestAlertException("All exercises in an exercise group must have the same meaning for the exam score", "Exam", "artemisApp.exam.validation.allExercisesInExerciseGroupOfSameIncludedType");
}
}
// Check that the exam max points is set
if (exam.getMaxPoints() == 0) {
throw new BadRequestAlertException("The exam max points can not be 0.", "Exam", "artemisApp.exam.validation.maxPointsNotSet");
}
// Ensure that all exercises in an exercise group have the same amount of max points and max bonus points
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
Set<Double> allMaxPoints = exerciseGroup.getExercises().stream().map(Exercise::getMaxPoints).collect(Collectors.toSet());
Set<Double> allBonusPoints = exerciseGroup.getExercises().stream().map(Exercise::getBonusPoints).collect(Collectors.toSet());
if (allMaxPoints.size() > 1 || allBonusPoints.size() > 1) {
throw new BadRequestAlertException("All exercises in an exercise group need to give the same amount of points", "Exam", "artemisApp.exam.validation.allExercisesInExerciseGroupGiveSameNumberOfPoints");
}
}
// Ensure that the sum of all max points of mandatory exercise groups is not bigger than the max points set in the exam
// At this point we are already sure that each exercise group has at least one exercise, all exercises in the group have the same no of points
// and all are of the same calculation type, therefore we can just use any as representation for the group here
Double pointsReachableByMandatoryExercises = 0.0;
Set<ExerciseGroup> mandatoryExerciseGroups = exam.getExerciseGroups().stream().filter(ExerciseGroup::getIsMandatory).collect(Collectors.toSet());
for (ExerciseGroup exerciseGroup : mandatoryExerciseGroups) {
Exercise groupRepresentativeExercise = exerciseGroup.getExercises().stream().findAny().get();
if (groupRepresentativeExercise.getIncludedInOverallScore().equals(IncludedInOverallScore.INCLUDED_COMPLETELY)) {
pointsReachableByMandatoryExercises += groupRepresentativeExercise.getMaxPoints();
}
}
if (pointsReachableByMandatoryExercises > exam.getMaxPoints()) {
throw new BadRequestAlertException("Check that you set the exam max points correctly! The max points a student can earn in the mandatory exercise groups is too big", "Exam", "artemisApp.exam.validation.tooManyMaxPoints");
}
// Ensure that the sum of all max points of all exercise groups is at least as big as the max points set in the exam
Double pointsReachable = 0.0;
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
Exercise groupRepresentativeExercise = exerciseGroup.getExercises().stream().findAny().get();
if (groupRepresentativeExercise.getIncludedInOverallScore().equals(IncludedInOverallScore.INCLUDED_COMPLETELY)) {
pointsReachable += groupRepresentativeExercise.getMaxPoints();
}
}
if (pointsReachable < exam.getMaxPoints()) {
throw new BadRequestAlertException("Check that you set the exam max points correctly! The max points a student can earn in the exercise groups is too low", "Exam", "artemisApp.exam.validation.tooFewMaxPoints");
}
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project ArTEMiS by ls1intum.
the class ExamService method evaluateQuizExercises.
/**
* Evaluates all the quiz exercises of an exam
*
* @param examId id of the exam for which the quiz exercises should be evaluated
* @return number of evaluated exercises
*/
public Integer evaluateQuizExercises(Long examId) {
var exam = examRepository.findWithExerciseGroupsAndExercisesById(examId).orElseThrow(() -> new EntityNotFoundException("Exam", examId));
// Collect all quiz exercises for the given exam
Set<QuizExercise> quizExercises = new HashSet<>();
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
for (Exercise exercise : exerciseGroup.getExercises()) {
if (exercise instanceof QuizExercise) {
quizExercises.add((QuizExercise) exercise);
}
}
}
long start = System.nanoTime();
log.info("Evaluating {} quiz exercises in exam {}", quizExercises.size(), examId);
// Evaluate all quizzes for that exercise
quizExercises.forEach(quiz -> examQuizService.evaluateQuizAndUpdateStatistics(quiz.getId()));
log.info("Evaluated {} quiz exercises in exam {} in {}", quizExercises.size(), examId, TimeLogUtil.formatDurationFrom(start));
return quizExercises.size();
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project ArTEMiS by ls1intum.
the class ExamResource method getExamForAssessmentDashboard.
/**
* GET /courses/:courseId/exams/:examId/exam-for-assessment-dashboard
*
* @param courseId the id of the course to retrieve
* @param examId the id of the exam that contains the exercises
* @return data about a course including all exercises, plus some data for the tutor as tutor status for assessment
*/
@GetMapping("/courses/{courseId}/exams/{examId}/exam-for-assessment-dashboard")
@PreAuthorize("hasRole('TA')")
public ResponseEntity<Exam> getExamForAssessmentDashboard(@PathVariable long courseId, @PathVariable long examId) {
log.debug("REST request /courses/{courseId}/exams/{examId}/exam-for-assessment-dashboard");
Exam exam = examService.findByIdWithExerciseGroupsAndExercisesElseThrow(examId);
Course course = exam.getCourse();
checkExamCourseIdElseThrow(courseId, exam);
User user = userRepository.getUserWithGroupsAndAuthorities();
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.TEACHING_ASSISTANT, course, user);
if (ZonedDateTime.now().isBefore(exam.getEndDate()) && authCheckService.isTeachingAssistantInCourse(course, user)) {
// tutors cannot access the exercises before the exam ends
throw new AccessForbiddenException("exam", examId);
}
Set<Exercise> exercises = new HashSet<>();
// extract all exercises for all the exam
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
exerciseGroup.setExercises(courseRepository.getInterestingExercisesForAssessmentDashboards(exerciseGroup.getExercises()));
exercises.addAll(exerciseGroup.getExercises());
}
List<TutorParticipation> tutorParticipations = tutorParticipationRepository.findAllByAssessedExercise_ExerciseGroup_Exam_IdAndTutor_Id(examId, user.getId());
assessmentDashboardService.generateStatisticsForExercisesForAssessmentDashboard(exercises, tutorParticipations, true);
return ResponseEntity.ok(exam);
}
Aggregations