diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentScoreServiceImpl.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentScoreServiceImpl.java --- a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentScoreServiceImpl.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentScoreServiceImpl.java @@ -19,6 +19,12 @@ @Override @Transactional public void registerScore(Assignment assignment, ClassroomUser student, long score, long maxScore) { var studentAssignment = this.studentAssignmentRepository.findByAssignmentAndStudent(assignment, student); + + // increment retake count if needed + if(studentAssignment instanceof StudentQuizAssignment quizAssignment && quizAssignment.hasBeenSubmitted()){ + quizAssignment.setRetakes(quizAssignment.getRetakes() + 1); + } + studentAssignment.setScore(score); studentAssignment.setMaxScore(maxScore); studentAssignment.setSubmissionDate(ZonedDateTime.now()); diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/QuizAssignment.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/QuizAssignment.java --- a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/QuizAssignment.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/QuizAssignment.java @@ -10,6 +10,8 @@ @OneToOne private QuizEntity quiz; + private int maxRetakes = 0; + public QuizAssignment() { this.setType(AssignmentType.QUIZ); } @@ -20,5 +22,17 @@ } public void setQuiz(QuizEntity quiz) { this.quiz = quiz; + } + + public int getMaxRetakes() { + return maxRetakes; + } + + public void setMaxRetakes(int maxRetakes) { + this.maxRetakes = maxRetakes; + } + + public boolean allowRetakes(){ + return this.maxRetakes > 0; } } diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/StudentQuizAssignment.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/StudentQuizAssignment.java --- a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/StudentQuizAssignment.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/StudentQuizAssignment.java @@ -5,10 +5,25 @@ @Entity public class StudentQuizAssignment extends StudentAssignment { + private int retakes = 0; + + public int getRetakes() { + return retakes; + } + + public void setRetakes(int retakes) { + this.retakes = retakes; + } + @Override public void resetGrades() { this.setSubmissionDate(null); this.setScore(null); this.setMaxScore(null); } + + public boolean canRetake(){ + return this.retakes < ((QuizAssignment)this.getAssignment()).getMaxRetakes(); + } + } diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/QuizAnswerController.java b/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/QuizAnswerController.java --- a/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/QuizAnswerController.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/QuizAnswerController.java @@ -3,6 +3,7 @@ import fr.univ_lille.gitlab.classrooms.assignments.AssignmentScoreService; import fr.univ_lille.gitlab.classrooms.assignments.AssignmentService; import fr.univ_lille.gitlab.classrooms.assignments.QuizAssignment; +import fr.univ_lille.gitlab.classrooms.assignments.StudentQuizAssignment; import fr.univ_lille.gitlab.classrooms.users.ClassroomUser; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -41,11 +42,20 @@ model.addAttribute("quiz", quiz); model.addAttribute("assignment", assignment); - var studentAssignment = this.assignmentService.getAssignmentResultsForStudent(assignment, student); + var studentAssignment = (StudentQuizAssignment)this.assignmentService.getAssignmentResultsForStudent(assignment, student); + if(studentAssignment.hasBeenSubmitted()){ - model.addAttribute("previousSubmission", studentAssignment); + // check if quiz can be retaken + if(studentAssignment.canRetake()) { + model.addAttribute("previousSubmission", studentAssignment); + // quiz can be retaken, showing answer form, with previous information + return "quiz/answer"; + } + // quiz cannot be retaken, showing results + return "quiz/results"; } + // quiz has not been submitted yet, showing answer form return "quiz/answer"; } diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/QuizEntity.java b/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/QuizEntity.java --- a/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/QuizEntity.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/quiz/QuizEntity.java @@ -30,4 +30,5 @@ public void setMarkdownContent(String markdownContent) { this.markdownContent = markdownContent; } + } diff --git a/src/main/resources/db/migration/V21__QUIZ_ASSIGNMENT_RETAKES.sql b/src/main/resources/db/migration/V21__QUIZ_ASSIGNMENT_RETAKES.sql new file mode 100644 --- /dev/null +++ b/src/main/resources/db/migration/V21__QUIZ_ASSIGNMENT_RETAKES.sql @@ -0,0 +1,17 @@ +ALTER TABLE quiz_assignment + ADD max_retakes INTEGER; + +UPDATE quiz_assignment +SET max_retakes = '0' +WHERE max_retakes IS NULL; +ALTER TABLE quiz_assignment + ALTER COLUMN max_retakes SET NOT NULL; + +ALTER TABLE student_quiz_assignment + ADD retakes INTEGER; + +UPDATE student_quiz_assignment +SET retakes = '0' +WHERE retakes IS NULL; +ALTER TABLE student_quiz_assignment + ALTER COLUMN retakes SET NOT NULL; \ No newline at end of file diff --git a/src/main/resources/templates/dashboard/student-dashboard.html b/src/main/resources/templates/dashboard/student-dashboard.html --- a/src/main/resources/templates/dashboard/student-dashboard.html +++ b/src/main/resources/templates/dashboard/student-dashboard.html @@ -78,7 +78,9 @@ Open in GitLab -
+
diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentScoreServiceImplTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentScoreServiceImplTest.java --- a/src/test/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentScoreServiceImplTest.java +++ b/src/test/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentScoreServiceImplTest.java @@ -38,5 +38,43 @@ verify(this.studentAssignmentRepository).findByAssignmentAndStudent(assignment, student); verify(this.studentAssignmentRepository).save(studentAssignment); } + + @Test + void registerScore_shouldIncrementRetakes() { + var student = new ClassroomUser(); + + // an assignment that can be retaken once + var assignment = new QuizAssignment(); + assignment.setMaxRetakes(1); + + // student has already a first submission + var studentAssignment = new StudentQuizAssignment(); + studentAssignment.setSubmissionDate(ZonedDateTime.now()); + studentAssignment.setRetakes(0); + + when(this.studentAssignmentRepository.findByAssignmentAndStudent(assignment, student)).thenReturn(studentAssignment); + + this.assignmentScoreService.registerScore(assignment, student, 12, 20); + + assertThat(studentAssignment.getRetakes()).isEqualTo(1); + } + + @Test + void registerScore_shouldNotIncrementRetakes_forFistSubmission() { + var student = new ClassroomUser(); + + // an assignment that can be retaken once + var assignment = new QuizAssignment(); + assignment.setMaxRetakes(1); + + // student has not a first submission + var studentAssignment = new StudentQuizAssignment(); + + when(this.studentAssignmentRepository.findByAssignmentAndStudent(assignment, student)).thenReturn(studentAssignment); + + this.assignmentScoreService.registerScore(assignment, student, 12, 20); + + assertThat(studentAssignment.getRetakes()).isEqualTo(0); + } } diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/assignments/StudentQuizAssignmentTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/assignments/StudentQuizAssignmentTest.java --- a/src/test/java/fr/univ_lille/gitlab/classrooms/assignments/StudentQuizAssignmentTest.java +++ b/src/test/java/fr/univ_lille/gitlab/classrooms/assignments/StudentQuizAssignmentTest.java @@ -31,4 +31,49 @@ studentQuizAssignment.resetGrades(); assertThat(studentQuizAssignment.hasBeenSubmitted()).isFalse(); } + + @Test + void quizCanBeRetaken_ifAssignmentAllows(){ + var assignment = new QuizAssignment(); + assignment.setMaxRetakes(1); + + // assignment already taken, with no retakes + var studentQuizAssignment = new StudentQuizAssignment(); + studentQuizAssignment.setAssignment(assignment); + studentQuizAssignment.setSubmissionDate(ZonedDateTime.now()); + studentQuizAssignment.setRetakes(0); + + assertThat(studentQuizAssignment.canRetake()).isTrue(); + } + + @Test + void quizCannotBeRetaken_isAssignmentDisallows(){ + var assignment = new QuizAssignment(); + assignment.setMaxRetakes(0); + + // assignment already taken, with no retakes + var studentQuizAssignment = new StudentQuizAssignment(); + studentQuizAssignment.setAssignment(assignment); + studentQuizAssignment.setSubmissionDate(ZonedDateTime.now()); + studentQuizAssignment.setRetakes(0); + + assertThat(studentQuizAssignment.canRetake()).isFalse(); + } + + @Test + void quizCanBeRetaken_atMostXTimes(){ + var assignment = new QuizAssignment(); + assignment.setMaxRetakes(3); + + // assignment already taken, with no retakes + var studentQuizAssignment = new StudentQuizAssignment(); + studentQuizAssignment.setAssignment(assignment); + studentQuizAssignment.setSubmissionDate(ZonedDateTime.now()); + + studentQuizAssignment.setRetakes(2); + assertThat(studentQuizAssignment.canRetake()).isTrue(); + + studentQuizAssignment.setRetakes(3); + assertThat(studentQuizAssignment.canRetake()).isFalse(); + } } diff --git a/src/test/java/fr/univ_lille/gitlab/classrooms/quiz/QuizAnswerControllerMVCTest.java b/src/test/java/fr/univ_lille/gitlab/classrooms/quiz/QuizAnswerControllerMVCTest.java --- a/src/test/java/fr/univ_lille/gitlab/classrooms/quiz/QuizAnswerControllerMVCTest.java +++ b/src/test/java/fr/univ_lille/gitlab/classrooms/quiz/QuizAnswerControllerMVCTest.java @@ -78,7 +78,7 @@ } @Test @WithMockStudent - void shouldReturnQuizPage_withPreviouslySubmittedAnswer_whenQuizExists() throws Exception { + void shouldReturnQuizPage_withPreviouslySubmittedAnswer_whenQuizIsRetaken() throws Exception { var quiz = new QuizEntity(); quiz.setName("death-star-quiz"); quiz.setMarkdownContent(""" @@ -89,12 +89,14 @@ """); var assignment = new QuizAssignment(); assignment.setQuiz(quiz); + assignment.setMaxRetakes(1); when(assignmentService.getAssignment(assignment.getId())).thenReturn(Optional.of(assignment)); var student = classroomUserService.getClassroomUser("luke.skywalker"); var studentAssignment = new StudentQuizAssignment(); studentAssignment.setSubmissionDate(ZonedDateTime.now()); + studentAssignment.setAssignment(assignment); when(assignmentService.getAssignmentResultsForStudent(assignment, student)).thenReturn(studentAssignment);