use of de.tum.in.www1.artemis.domain.quiz.QuizSubmission in project ArTEMiS by ls1intum.
the class ExamQuizService method evaluateSubmissions.
/**
* // @formatter:off
* Evaluate the given quiz exercise by performing the following actions for each participation:
* 1. Get the submission for each participation (there should be only one as in exam mode, the submission gets created upfront and will be updated)
* - If no submission is found, print a warning and continue as we cannot evaluate that submission
* - If more than one submission is found, select one of them
* 2. mark submission and participation as evaluated
* 3. Create a new result for the selected submission and calculate scores
* 4. Save the updated submission & participation and the newly created result
*
* After processing all participations, the created results will be returned for further processing
* Note: We ignore test run participations
* // @formatter:on
* @param quizExercise the id of the QuizExercise that should be evaluated
* @return the newly generated results
*/
private Set<Result> evaluateSubmissions(@NotNull QuizExercise quizExercise) {
Set<Result> createdResults = new HashSet<>();
List<StudentParticipation> studentParticipations = studentParticipationRepository.findAllWithEagerLegalSubmissionsAndEagerResultsByExerciseId(quizExercise.getId());
for (var participation : studentParticipations) {
if (!participation.isTestRun()) {
try {
// reconnect so that the quiz questions are available later on (otherwise there will be a org.hibernate.LazyInitializationException)
participation.setExercise(quizExercise);
Set<Submission> submissions = participation.getSubmissions();
QuizSubmission quizSubmission;
if (submissions.isEmpty()) {
log.warn("Found no submissions for participation {} (Participant {}) in quiz {}", participation.getId(), participation.getParticipant().getName(), quizExercise.getId());
continue;
} else if (submissions.size() > 1) {
log.warn("Found multiple ({}) submissions for participation {} (Participant {}) in quiz {}, taking the one with highest id", submissions.size(), participation.getId(), participation.getParticipant().getName(), quizExercise.getId());
List<Submission> submissionsList = new ArrayList<>(submissions);
// Load submission with highest id
submissionsList.sort(Comparator.comparing(Submission::getId).reversed());
quizSubmission = (QuizSubmission) submissionsList.get(0);
} else {
quizSubmission = (QuizSubmission) submissions.iterator().next();
}
participation.setInitializationState(InitializationState.FINISHED);
boolean resultExisting = false;
// create new result if none is existing
Result result;
if (participation.getResults().isEmpty()) {
result = new Result().participation(participation);
} else {
resultExisting = true;
result = participation.getResults().iterator().next();
}
// Only create Results once after the first evaluation
if (!resultExisting) {
// delete result from quizSubmission, to be able to set a new one
if (quizSubmission.getLatestResult() != null) {
resultService.deleteResultWithComplaint(quizSubmission.getLatestResult().getId());
}
result.setRated(true);
result.setAssessmentType(AssessmentType.AUTOMATIC);
result.setCompletionDate(ZonedDateTime.now());
// set submission to calculate scores
result.setSubmission(quizSubmission);
// calculate scores and update result and submission accordingly
quizSubmission.calculateAndUpdateScores(quizExercise);
result.evaluateQuizSubmission();
// remove submission to follow save order for ordered collections
result.setSubmission(null);
// NOTE: we save participation, submission and result here individually so that one exception (e.g. duplicated key) cannot destroy multiple student answers
submissionRepository.save(quizSubmission);
result = resultRepository.save(result);
// add result to participation
participation.addResult(result);
studentParticipationRepository.save(participation);
// add result to submission
result.setSubmission(quizSubmission);
quizSubmission.addResult(result);
submissionRepository.save(quizSubmission);
// Add result so that it can be returned (and processed later)
createdResults.add(result);
}
} catch (Exception e) {
log.error("Exception in evaluateExamQuizExercise() for user {} in quiz {}: {}", participation.getParticipantIdentifier(), quizExercise.getId(), e.getMessage(), e);
}
}
}
return createdResults;
}
use of de.tum.in.www1.artemis.domain.quiz.QuizSubmission in project ArTEMiS by ls1intum.
the class QuizSubmissionResource method submitQuizForExam.
/**
* PUT /exercises/:exerciseId/submissions/exam : Update a QuizSubmission for exam mode
*
* @param exerciseId the id of the exercise for which to update the submission
* @param quizSubmission the quizSubmission to update
* @return the ResponseEntity with status 200 and body the result or the appropriate error code.
*/
@PutMapping("exercises/{exerciseId}/submissions/exam")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<QuizSubmission> submitQuizForExam(@PathVariable Long exerciseId, @RequestBody QuizSubmission quizSubmission) {
long start = System.currentTimeMillis();
log.debug("REST request to submit QuizSubmission for exam : {}", quizSubmission);
// recreate pointers back to submission in each submitted answer
for (SubmittedAnswer submittedAnswer : quizSubmission.getSubmittedAnswers()) {
submittedAnswer.setSubmission(quizSubmission);
}
QuizExercise quizExercise = quizExerciseRepository.findByIdWithQuestionsElseThrow(exerciseId);
User user = userRepository.getUserWithGroupsAndAuthorities();
// Apply further checks if it is an exam submission
examSubmissionService.checkSubmissionAllowanceElseThrow(quizExercise, user);
// Prevent multiple submissions (currently only for exam submissions)
quizSubmission = (QuizSubmission) examSubmissionService.preventMultipleSubmissions(quizExercise, quizSubmission, user);
QuizSubmission updatedQuizSubmission = quizSubmissionService.saveSubmissionForExamMode(quizExercise, quizSubmission, user.getLogin());
long end = System.currentTimeMillis();
log.info("submitQuizForExam took {}ms for exercise {} and user {}", end - start, exerciseId, user.getLogin());
return ResponseEntity.ok(updatedQuizSubmission);
}
use of de.tum.in.www1.artemis.domain.quiz.QuizSubmission in project ArTEMiS by ls1intum.
the class QuizSubmissionResource method submitForPractice.
/**
* POST /exercises/:exerciseId/submissions/practice : Submit a new quizSubmission for practice mode.
*
* @param exerciseId the id of the exercise for which to init a participation
* @param quizSubmission the quizSubmission to submit
* @return the ResponseEntity with status 200 (OK) and the Result as its body, or with status 4xx if the request is invalid
*/
@PostMapping("/exercises/{exerciseId}/submissions/practice")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<Result> submitForPractice(@PathVariable Long exerciseId, @RequestBody QuizSubmission quizSubmission) {
log.debug("REST request to submit QuizSubmission for practice : {}", quizSubmission);
// recreate pointers back to submission in each submitted answer
for (SubmittedAnswer submittedAnswer : quizSubmission.getSubmittedAnswers()) {
submittedAnswer.setSubmission(quizSubmission);
}
if (quizSubmission.getId() != null) {
return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(applicationName, true, ENTITY_NAME, "idexists", "A new quizSubmission cannot already have an ID.")).body(null);
}
QuizExercise quizExercise = quizExerciseRepository.findByIdWithQuestionsElseThrow(exerciseId);
User user = userRepository.getUserWithGroupsAndAuthorities();
if (!authCheckService.isAllowedToSeeExercise(quizExercise, user)) {
return ResponseEntity.status(403).headers(HeaderUtil.createFailureAlert(applicationName, true, "submission", "Forbidden", "You are not allowed to participate in this exercise.")).body(null);
}
// Note that exam quiz exercises do not have an end date, so we need to check in that order
if (!Boolean.TRUE.equals(quizExercise.isIsOpenForPractice()) || !quizExercise.isQuizEnded()) {
return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(applicationName, true, "submission", "exerciseNotOpenForPractice", "The exercise is not open for practice or hasn't ended yet.")).body(null);
}
// the following method either reuses an existing participation or creates a new one
StudentParticipation participation = participationService.startExercise(quizExercise, user, false);
// we set the exercise again to prevent issues with lazy loaded quiz questions
participation.setExercise(quizExercise);
// update and save submission
Result result = quizSubmissionService.submitForPractice(quizSubmission, quizExercise, participation);
// The quizScheduler is usually responsible for updating the participation to FINISHED in the database. If quizzes where the student did not participate are used for
// practice, the QuizScheduler does not update the participation, that's why we update it manually here
participation.setInitializationState(InitializationState.FINISHED);
studentParticipationRepository.saveAndFlush(participation);
// remove some redundant or unnecessary data that is not needed on client side
for (SubmittedAnswer answer : quizSubmission.getSubmittedAnswers()) {
answer.getQuizQuestion().setQuizQuestionStatistic(null);
}
quizExercise.setQuizPointStatistic(null);
quizExercise.setCourse(null);
messagingService.broadcastNewResult(result.getParticipation(), result);
// return result with quizSubmission, participation and quiz exercise (including the solution)
return ResponseEntity.ok(result);
}
use of de.tum.in.www1.artemis.domain.quiz.QuizSubmission in project ArTEMiS by ls1intum.
the class ExamQuizServiceTest method evaluateQuizWithMultipleSubmissions.
@Test
@WithMockUser(username = "instructor1", roles = "INSTRUCTOR")
public void evaluateQuizWithMultipleSubmissions() throws Exception {
for (int i = 0; i < numberOfParticipants; i++) {
exam.addRegisteredUser(users.get(i));
}
exam = examRepository.save(exam);
exerciseGroup.setExam(exam);
exerciseGroup = exerciseGroupRepository.save(exerciseGroup);
exam.setExerciseGroups(List.of(exerciseGroup));
quizExercise.setExerciseGroup(exerciseGroup);
quizExercise = quizExerciseService.save(quizExercise);
exerciseGroup.setExercises(Set.of(quizExercise));
assertThat(studentExamRepository.generateStudentExams(exam)).hasSize(numberOfParticipants);
assertThat(studentExamRepository.findByExamId(exam.getId())).hasSize(numberOfParticipants);
assertThat(studentExamService.startExercises(exam.getId())).isEqualTo(numberOfParticipants);
for (int i = 0; i < numberOfParticipants; i++) {
final var user = database.getUserByLogin("student" + (i + 1));
database.changeUser(user.getLogin());
QuizSubmission quizSubmission = database.generateSubmissionForThreeQuestions(quizExercise, i + 1, true, ZonedDateTime.now());
request.put("/api/exercises/" + quizExercise.getId() + "/submissions/exam", quizSubmission, HttpStatus.OK);
// add another submission manually to trigger multiple submission branch of evaluateQuizSubmission
final var studentParticipation = studentParticipationRepository.findWithEagerLegalSubmissionsByExerciseIdAndStudentLogin(quizExercise.getId(), user.getLogin()).get();
QuizSubmission quizSubmission2 = database.generateSubmissionForThreeQuestions(quizExercise, i + 1, true, ZonedDateTime.now());
quizSubmission2.setParticipation(studentParticipation);
quizSubmissionRepository.save(quizSubmission2);
}
database.changeUser("instructor1");
// All exams should be over before evaluation
for (StudentExam studentExam : studentExamRepository.findByExamId(exam.getId())) {
studentExam.setWorkingTime(0);
studentExamRepository.save(studentExam);
}
Integer numberOfEvaluatedExercises = request.postWithResponseBody("/api/courses/" + course.getId() + "/exams/" + exam.getId() + "/student-exams/evaluate-quiz-exercises", Optional.empty(), Integer.class, HttpStatus.OK);
assertThat(numberOfEvaluatedExercises).isEqualTo(1);
checkStatistics(quizExercise);
studentExamRepository.deleteAll();
// Make sure delete also works if so many objects have been created before
request.delete("/api/courses/" + course.getId() + "/exams/" + exam.getId(), HttpStatus.OK);
userRepository.deleteAll();
}
use of de.tum.in.www1.artemis.domain.quiz.QuizSubmission in project Artemis by ls1intum.
the class QuizSubmissionService method submitForPractice.
/**
* Submit the given submission for practice
*
* @param quizSubmission the submission to submit
* @param quizExercise the exercise to submit in
* @param participation the participation where the result should be saved
* @return the result entity
*/
public Result submitForPractice(QuizSubmission quizSubmission, QuizExercise quizExercise, Participation participation) {
// update submission properties
quizSubmission.setSubmitted(true);
quizSubmission.setType(SubmissionType.MANUAL);
quizSubmission.setSubmissionDate(ZonedDateTime.now());
// calculate scores
quizSubmission.calculateAndUpdateScores(quizExercise);
// save parent submission object
quizSubmission = quizSubmissionRepository.save(quizSubmission);
// create result
Result result = new Result().participation(participation);
result.setRated(false);
result.setAssessmentType(AssessmentType.AUTOMATIC);
result.setCompletionDate(ZonedDateTime.now());
// save result
result = resultRepository.save(result);
// setup result - submission relation
result.setSubmission(quizSubmission);
// calculate score and update result accordingly
result.evaluateQuizSubmission();
quizSubmission.addResult(result);
quizSubmission.setParticipation(participation);
// save submission to set result index column
quizSubmissionRepository.save(quizSubmission);
// save result to store score
resultRepository.save(result);
// result.participation.exercise.quizQuestions turn into proxy objects after saving, so we need to set it again to prevent problems later on
result.setParticipation(participation);
// add result to statistics
quizScheduleService.addResultForStatisticUpdate(quizExercise.getId(), result);
log.debug("submit practice quiz finished: {}", quizSubmission);
return result;
}
Aggregations