From 0485c0de9eeabf46ed4b0a71b42b0e9cb42f1797 Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Thu, 4 Apr 2024 15:10:39 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20:=20add=20quiz=20retake=20managemen?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AssignmentScoreServiceImpl.java | 6 +++ .../assignments/QuizAssignment.java | 14 ++++++ .../assignments/StudentQuizAssignment.java | 15 +++++++ .../classrooms/quiz/QuizAnswerController.java | 14 +++++- .../gitlab/classrooms/quiz/QuizEntity.java | 1 + .../V21__QUIZ_ASSIGNMENT_RETAKES.sql | 17 +++++++ .../dashboard/student-dashboard.html | 4 +- .../AssignmentScoreServiceImplTest.java | 38 ++++++++++++++++ .../StudentQuizAssignmentTest.java | 45 +++++++++++++++++++ .../quiz/QuizAnswerControllerMVCTest.java | 4 +- 10 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/db/migration/V21__QUIZ_ASSIGNMENT_RETAKES.sql 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 index ce1cf8e..eb20461 100644 --- 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 @@ class AssignmentScoreServiceImpl implements AssignmentScoreService{ @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 index cbfd766..81bd49e 100644 --- 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 @@ public class QuizAssignment extends Assignment { @OneToOne private QuizEntity quiz; + private int maxRetakes = 0; + public QuizAssignment() { this.setType(AssignmentType.QUIZ); } @@ -21,4 +23,16 @@ public class QuizAssignment extends Assignment { 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 index d921f68..6f75c4c 100644 --- 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 @@ import jakarta.persistence.Entity; @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 index 5e33373..a562561 100644 --- 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 @@ package fr.univ_lille.gitlab.classrooms.quiz; 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 @@ class QuizAnswerController { 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 index aef7f09..8ebb881 100644 --- 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 class QuizEntity { 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 index 0000000..d5521a0 --- /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 index 75895d0..b721163 100644 --- a/src/main/resources/templates/dashboard/student-dashboard.html +++ b/src/main/resources/templates/dashboard/student-dashboard.html @@ -78,7 +78,9 @@ -
+
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 index 483105d..58dc3c1 100644 --- 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 @@ class AssignmentScoreServiceImplTest { 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 index 0ef9aaf..2c3949e 100644 --- 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 @@ class StudentQuizAssignmentTest { 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 index 3479e72..7c9f02f 100644 --- 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 @@ class QuizAnswerControllerMVCTest { @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 @@ class QuizAnswerControllerMVCTest { 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); -- 2.51.2