use of de.tum.in.www1.artemis.domain.participation.StudentParticipation in project Artemis by ls1intum.
the class QuizScheduleService method saveQuizSubmissionWithParticipationAndResultToDatabase.
/**
* check if the user submitted the submission or if the quiz has ended: if true: -> Create Participation and Result and save to Database (DB Write) Remove processed Submissions
* from SubmissionHashMap and write Participations with Result into ParticipationHashMap and Results into ResultHashMap
*
* @param quizExercise the quiz which should be checked
* @param userSubmissionMap a Map with all submissions for the given quizExercise mapped by the username
* @param userBatchMap a Map of the username to quiz batch id for the given quizExercise
* @param batchCache a Map of all the batches for the given quizExercise
* @return the number of processed submissions (submit or timeout)
*/
private int saveQuizSubmissionWithParticipationAndResultToDatabase(@NotNull QuizExercise quizExercise, Map<String, QuizSubmission> userSubmissionMap, Map<String, Long> userBatchMap, Map<Long, QuizBatch> batchCache) {
int count = 0;
for (String username : userSubmissionMap.keySet()) {
try {
// first case: the user submitted the quizSubmission
QuizSubmission quizSubmission = userSubmissionMap.get(username);
QuizBatch quizBatch = batchCache.get(userBatchMap.getOrDefault(username, 0L));
if (quizSubmission.isSubmitted()) {
if (quizSubmission.getType() == null) {
quizSubmission.setType(SubmissionType.MANUAL);
}
} else // second case: the quiz or batch has ended
if (quizExercise.isQuizEnded() || quizBatch != null && quizBatch.isEnded()) {
quizSubmission.setSubmitted(true);
quizSubmission.setType(SubmissionType.TIMEOUT);
quizSubmission.setSubmissionDate(ZonedDateTime.now());
} else {
// the quiz is running and the submission was not yet submitted.
continue;
}
if (quizBatch != null) {
// record which batch the submission belongs to
quizSubmission.setQuizBatch(quizBatch.getId());
}
count++;
// Create Participation and Result and save to Database (DB Write)
// Remove processed Submissions from SubmissionHashMap and write Participations with Result into ParticipationHashMap and Results into ResultHashMap
StudentParticipation participation = new StudentParticipation();
// TODO: when this is set earlier for the individual quiz start of a student, we don't need to set this here anymore
participation.setInitializationDate(quizSubmission.getSubmissionDate());
Optional<User> user = userRepository.findOneByLogin(username);
user.ifPresent(participation::setParticipant);
// add the quizExercise to the participation
participation.setExercise(quizExercise);
participation.setInitializationState(InitializationState.FINISHED);
// create new result
Result result = new Result().participation(participation);
result.setRated(true);
result.setAssessmentType(AssessmentType.AUTOMATIC);
result.setCompletionDate(quizSubmission.getSubmissionDate());
result.setSubmission(quizSubmission);
// calculate scores and update result and submission accordingly
quizSubmission.calculateAndUpdateScores(quizExercise);
result.evaluateQuizSubmission();
// add result to participation
participation.addResult(result);
// add submission to participation
participation.setSubmissions(Set.of(quizSubmission));
// NOTE: we save (1) participation and (2) submission (in this particular order) here individually so that one exception (e.g. duplicated key) cannot
// destroy multiple student answers
participation = studentParticipationRepository.save(participation);
quizSubmission.addResult(result);
quizSubmission.setParticipation(participation);
// this automatically saves the results due to CascadeType.ALL
quizSubmission = quizSubmissionRepository.save(quizSubmission);
log.info("Successfully saved submission in quiz " + quizExercise.getTitle() + " for user " + username);
// reconnect entities after save
participation.setSubmissions(Set.of(quizSubmission));
participation.setResults(Set.of(result));
result.setSubmission(quizSubmission);
result.setParticipation(participation);
// no point in keeping the participation around for non-synchronized modes where the due date may only be in a week
if (quizExercise.getQuizMode() == QuizMode.SYNCHRONIZED) {
// add the participation to the participationHashMap for the send out at the end of the quiz
addParticipation(quizExercise.getId(), participation);
}
// remove the submission only after the participation has been added to the participation hashmap to avoid duplicated key exceptions for multiple participations for
// the same user
userSubmissionMap.remove(username);
// clean up the batch association
userBatchMap.remove(username);
// add the result of the participation resultHashMap for the statistic-Update
addResultForStatisticUpdate(quizExercise.getId(), result);
} catch (ConstraintViolationException constraintViolationException) {
log.error("ConstraintViolationException in saveQuizSubmissionWithParticipationAndResultToDatabase() for user {} in quiz {}: {}", username, quizExercise.getId(), constraintViolationException.getMessage(), constraintViolationException);
// We got a ConstraintViolationException -> The "User-Quiz" pair is already saved in the database, but for some reason was not removed from the maps
// We remove it from the maps now to prevent this error from occurring again
// We do NOT add it to the participation map, as this should have been done already earlier (when the entry was added to the database)
userSubmissionMap.remove(username);
// clean up the batch association
userBatchMap.remove(username);
} catch (Exception e) {
log.error("Exception in saveQuizSubmissionWithParticipationAndResultToDatabase() for user {} in quiz {}: {}", username, quizExercise.getId(), e.getMessage(), e);
}
}
return count;
}
use of de.tum.in.www1.artemis.domain.participation.StudentParticipation in project Artemis by ls1intum.
the class ProgrammingExerciseScheduleService method invokeOperationOnAllParticipationsThatSatisfy.
/**
* Invokes the given <code>operation</code> on all student participations that satisfy the <code>condition</code>-{@link Predicate}.
* <p>
*
* @param programmingExerciseId the programming exercise whose participations should be processed
* @param operation the operation to perform
* @param condition the condition that tests whether to invoke the operation on a participation
* @param operationName the name of the operation, this is only used for logging
* @return a list containing all participations for which the operation has failed with an exception
* @throws EntityNotFoundException if the programming exercise can't be found.
*/
private List<ProgrammingExerciseStudentParticipation> invokeOperationOnAllParticipationsThatSatisfy(Long programmingExerciseId, BiConsumer<ProgrammingExercise, ProgrammingExerciseStudentParticipation> operation, Predicate<ProgrammingExerciseStudentParticipation> condition, String operationName) {
log.info("Invoking (scheduled) task '{}' for programming exercise with id {}.", operationName, programmingExerciseId);
ProgrammingExercise programmingExercise = programmingExerciseRepository.findWithEagerStudentParticipationsById(programmingExerciseId).orElseThrow(() -> new EntityNotFoundException("ProgrammingExercise", programmingExerciseId));
List<ProgrammingExerciseStudentParticipation> failedOperations = new ArrayList<>();
for (StudentParticipation studentParticipation : programmingExercise.getStudentParticipations()) {
ProgrammingExerciseStudentParticipation programmingExerciseStudentParticipation = (ProgrammingExerciseStudentParticipation) studentParticipation;
try {
if (condition.test(programmingExerciseStudentParticipation)) {
operation.accept(programmingExercise, programmingExerciseStudentParticipation);
}
} catch (Exception e) {
log.error(String.format("'%s' failed for programming exercise with id %d for student repository with participation id %d", operationName, programmingExercise.getId(), studentParticipation.getId()), e);
failedOperations.add(programmingExerciseStudentParticipation);
}
}
return failedOperations;
}
use of de.tum.in.www1.artemis.domain.participation.StudentParticipation 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.participation.StudentParticipation in project Artemis by ls1intum.
the class FileUploadSubmissionIntegrationTest method getSubmissionById_asTA_withResult_wrongResultId.
@Test
@WithMockUser(username = "instructor1", roles = "INSTRUCTOR")
public void getSubmissionById_asTA_withResult_wrongResultId() throws Exception {
FileUploadSubmission fileUploadSubmission = ModelFactory.generateFileUploadSubmission(true);
fileUploadSubmission = database.addFileUploadSubmission(releasedFileUploadExercise, fileUploadSubmission, "student1");
Participation studentParticipation = database.createAndSaveParticipationForExercise(releasedFileUploadExercise, "student1");
Result result = database.addResultToParticipation(studentParticipation, fileUploadSubmission);
long submissionID = fileUploadSubmission.getId();
var params = new LinkedMultiValueMap<String, String>();
params.add("resultId", String.valueOf(result.getId() + 1));
FileUploadSubmission receivedSubmission = request.get("/api/file-upload-submissions/" + submissionID, HttpStatus.BAD_REQUEST, FileUploadSubmission.class, params);
assertThat(receivedSubmission).isNull();
}
use of de.tum.in.www1.artemis.domain.participation.StudentParticipation in project Artemis by ls1intum.
the class FileUploadSubmissionIntegrationTest method submitExercise_afterDueDate.
@Test
@WithMockUser(username = "student3", roles = "USER")
public void submitExercise_afterDueDate() throws Exception {
StudentParticipation studentParticipation = database.createAndSaveParticipationForExerciseInTheFuture(releasedFileUploadExercise, "student3");
submittedFileUploadSubmission.setParticipation(studentParticipation);
request.postWithMultipartFile("/api/exercises/" + releasedFileUploadExercise.getId() + "/file-upload-submissions", submittedFileUploadSubmission, "submission", validFile, FileUploadSubmission.class, HttpStatus.OK);
}
Aggregations