diff --git a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/AnswerService.java b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/AnswerService.java index 4dbfc1cc7..9b07abdac 100644 --- a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/AnswerService.java +++ b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/AnswerService.java @@ -1,5 +1,7 @@ package pt.ulisboa.tecnico.socialsoftware.tutor.answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.retry.annotation.Backoff; @@ -47,6 +49,8 @@ @Service public class AnswerService { + private static final Logger logger = LoggerFactory.getLogger(AnswerService.class); + @Autowired private Mailer mailer; @@ -370,7 +374,10 @@ public StatementQuizDto getQuizByQRCode(int userId, int quizId) { QuizAnswer quizAnswer = getQuizAnswer(student, quiz); if (quiz.getAvailableDate() == null || DateHandler.now().isAfter(quiz.getAvailableDate())) { - return getPreparedStatementQuizDto(student, quiz, quizAnswer); + StatementQuizDto result = getPreparedStatementQuizDto(student, quiz, quizAnswer); + if (quiz.isOneWay() && quiz.getConclusionDate() != null) + logger.info("Student {} for quiz answer {} get quiz {} with sequence {}", student.getUsername(), quizAnswer.getId(), result); + return result; } else { // Send timer StatementQuizDto quizDto = new StatementQuizDto(); quizDto.setTimeToAvailability(ChronoUnit.MILLIS.between(DateHandler.now(), quiz.getAvailableDate())); @@ -398,7 +405,10 @@ public StatementQuizDto startQuiz(int userId, int quizId) { QuizAnswer quizAnswer = getQuizAnswer(student, quiz); - return getPreparedStatementQuizDto(student, quiz, quizAnswer); + StatementQuizDto result = getPreparedStatementQuizDto(student, quiz, quizAnswer); + if (quiz.isOneWay() && quiz.getConclusionDate() != null) + logger.info("Student {} for quiz answer {} get quiz {} with sequence {}", student.getUsername(), quizAnswer.getId(), result); + return result; } private void commonChecks(Student student, Quiz quiz) { @@ -481,16 +491,20 @@ public StatementQuestionDto getQuestionForQuizAnswer(Integer questionId, Stateme QuizAnswer quizAnswer = quizAnswerRepository.findById(answer.getQuizAnswerId()) .orElseThrow(() -> new TutorException(QUIZ_ANSWER_NOT_FOUND)); - try { - quizAnswer.checkCanGetQuestion(questionId); - } catch (TutorException te) { + Integer sequenceQuestionId = quizAnswer.checkCorrectSequenceQuestion(answer.getSequence(), questionId); + + if (!sequenceQuestionId.equals(questionId)) { + logger.info("Student of quiz answer {} tried to get question id {} with sequence {} but the correct question id is {}", + quizAnswer.getId(), questionId, answer.getSequence() + 1, sequenceQuestionId); + mailer.sendSimpleMail(mailUsername, "rito.silva@tecnico.ulisboa.pt", Mailer.QUIZZES_TUTOR_SUBJECT + " Alert", "In course " + quizAnswer.getQuiz().getCourseExecution().getCourse().getName() - + ", the following behavior was detected but the student *was not able* to answer questions out of the predefined order: " - + te.getMessage()); + + ", the student tried to get a sequence-question (" + answer.getSequence() + "," + questionId + ")" + + " out of the predefined order but the correct question is " + sequenceQuestionId); } - Question question = questionRepository.findById(questionId) + + Question question = questionRepository.findById(sequenceQuestionId) .orElseThrow(() -> new TutorException(QUESTION_NOT_FOUND, questionId)); return new StatementQuestionDto(question); @@ -509,7 +523,7 @@ public void submitAnswer(String username, int quizId, StatementAnswerDto answer) } catch (TutorException te) { for (Teacher teacher : quizAnswer.getQuiz().getCourseExecution().getTeachers()) { mailer.sendSimpleMail(mailUsername, teacher.getEmail(), - Mailer.QUIZZES_TUTOR_SUBJECT + " Alert", "The following behavior was detected but the student *was not able* to answer questions out of the predefined order: " + te.getMessage()); + Mailer.QUIZZES_TUTOR_SUBJECT + " Alert", "The student tried to answer questions out of the predefined order: " + te.getMessage()); } throw te; diff --git a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/domain/QuizAnswer.java b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/domain/QuizAnswer.java index 8c00bd29d..371caebd1 100644 --- a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/domain/QuizAnswer.java +++ b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/domain/QuizAnswer.java @@ -14,6 +14,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -84,10 +85,13 @@ public QuizAnswer(Student student, Quiz quiz) { for (int i = 0; i < quizQuestions.size(); i++) { new QuestionAnswer(this, quizQuestions.get(i), i); + } - if (quiz.isOneWay() && quiz.getType().equals(Quiz.QuizType.IN_CLASS)) { - this.questionIds.add(quizQuestions.get(i).getQuestion().getId()); - } + if (quiz.isOneWay() && quiz.getType().equals(Quiz.QuizType.IN_CLASS)) { + questionIds = getQuestionAnswers().stream() + .sorted(Comparator.comparing(QuestionAnswer::getSequence)) + .map(questionAnswer -> questionAnswer.getQuestion().getId()) + .collect(Collectors.toList()); } } @@ -218,20 +222,19 @@ public void calculateQuestionStatistics() { } } - public void checkCanGetQuestion(Integer questionId) { + public Integer checkCorrectSequenceQuestion(Integer sequence, Integer questionId) { if (!questionIds.isEmpty()) { - if (currentSequenceQuestion < questionIds.size() - 1 - && questionIds.get(currentSequenceQuestion + 1).equals(questionId)) { - currentSequenceQuestion = currentSequenceQuestion + 1; - - logger.info("Student {} for quiz answer {} get question id {} with sequence {}", getStudent().getUsername(), getId(), questionId, currentSequenceQuestion); - } else if (!questionIds.get(currentSequenceQuestion).equals(questionId)) { - throw new TutorException(INVALID_QUIZ_ANSWER_SEQUENCE, - getStudent().getUsername() + " tried to get question with ID " - + questionId + " when they sequence is " + (currentSequenceQuestion + 1) - + " and the question IDs sequence is " + questionIds.stream().map(String::valueOf).collect(Collectors.joining(", "))); + if (sequence.equals(currentSequenceQuestion + 1)) { + currentSequenceQuestion = sequence; } + + logger.info("Student {} for quiz answer {} get question id {} with sequence {}", getStudent().getUsername(), getId(), questionId, (currentSequenceQuestion + 1)); + logger.info("Question ids {}", questionIds); + + return questionIds.get(currentSequenceQuestion); } + + return questionId; } public void checkIsCurrentQuestion(Integer questionId) { diff --git a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementAnswerDto.java b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementAnswerDto.java index 37ffe871c..9ce346b12 100644 --- a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementAnswerDto.java +++ b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementAnswerDto.java @@ -9,12 +9,19 @@ public class StatementAnswerDto implements Serializable { private Integer timeTaken; + private Integer timeToSubmission; + private Integer sequence; + private Integer quizAnswerId; + private Integer questionAnswerId; + private Integer questionId; + private Integer quizQuestionId; + private DiscussionDto userDiscussion; private StatementAnswerDetailsDto answerDetails; @@ -32,8 +39,8 @@ public StatementAnswerDto(QuestionAnswer questionAnswer) { this.answerDetails = questionAnswer.getStatementAnswerDetailsDto(); - if (questionAnswer.getDiscussion() != null){ - this.userDiscussion = new DiscussionDto(questionAnswer.getDiscussion(),false); + if (questionAnswer.getDiscussion() != null) { + this.userDiscussion = new DiscussionDto(questionAnswer.getDiscussion(), false); } } @@ -93,7 +100,7 @@ public void setTimeToSubmission(Integer timeToSubmission) { this.timeToSubmission = timeToSubmission; } - public AnswerDetails getAnswerDetails(QuestionAnswer questionAnswer){ + public AnswerDetails getAnswerDetails(QuestionAnswer questionAnswer) { return this.getAnswerDetails() != null ? this.answerDetails.getAnswerDetails(questionAnswer) : null; } @@ -117,10 +124,13 @@ public void setUserDiscussion(DiscussionDto userDiscussion) { public String toString() { return "StatementAnswerDto{" + "timeTaken=" + timeTaken + + ", timeToSubmission=" + timeToSubmission + ", sequence=" + sequence + + ", quizAnswerId=" + quizAnswerId + ", questionAnswerId=" + questionAnswerId + + ", questionId=" + questionId + ", quizQuestionId=" + quizQuestionId + - ", timeToSubmission=" + timeToSubmission + + ", userDiscussion=" + userDiscussion + ", answerDetails=" + answerDetails + '}'; } diff --git a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementQuestionDto.java b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementQuestionDto.java index efa83d953..c61f12def 100644 --- a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementQuestionDto.java +++ b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementQuestionDto.java @@ -9,8 +9,11 @@ public class StatementQuestionDto implements Serializable { private String content; + private ImageDto image; + private Integer sequence; + private Integer questionId; private StatementQuestionDetailsDto questionDetails; @@ -36,6 +39,8 @@ public StatementQuestionDto(Question question) { this.image = new ImageDto(question.getImage()); this.questionDetails = question.getStatementQuestionDetailsDto(); + + this.questionId = question.getId(); } public String getContent() { @@ -76,6 +81,7 @@ public String toString() { "content='" + content + '\'' + ", image=" + image + ", sequence=" + sequence + + ", questionId=" + questionId + ", questionDetails=" + questionDetails + '}'; } diff --git a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementQuizDto.java b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementQuizDto.java index 99cd7ee9e..2b9df0788 100644 --- a/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementQuizDto.java +++ b/backend/src/main/java/pt/ulisboa/tecnico/socialsoftware/tutor/answer/dto/StatementQuizDto.java @@ -19,19 +19,31 @@ public class StatementQuizDto implements Serializable { private Integer id; + private Integer quizAnswerId; + private String title; + private boolean oneWay; + private boolean timed; + private String availableDate; + private String conclusionDate; + private Integer questionOrder = 0; + private Long timeToAvailability; + private Long timeToSubmission; + private List questions = new ArrayList<>(); + private List answers = new ArrayList<>(); - public StatementQuizDto() {} + public StatementQuizDto() { + } public StatementQuizDto(QuizAnswer quizAnswer, boolean complete) { this.id = quizAnswer.getQuiz().getId(); @@ -64,7 +76,7 @@ public StatementQuizDto(QuizAnswer quizAnswer, List items) { List finalItems = new ArrayList<>(); for (int i = 0; i < items.size() - 1; i++) { - if (!items.get(i).getQuizQuestionId().equals(items.get(i+1).getQuizQuestionId())) { + if (!items.get(i).getQuizQuestionId().equals(items.get(i + 1).getQuizQuestionId())) { finalItems.add(items.get(i)); } } @@ -185,8 +197,10 @@ public String toString() { ", quizAnswerId=" + quizAnswerId + ", title='" + title + '\'' + ", oneWay=" + oneWay + + ", timed=" + timed + ", availableDate='" + availableDate + '\'' + ", conclusionDate='" + conclusionDate + '\'' + + ", questionOrder=" + questionOrder + ", timeToAvailability=" + timeToAvailability + ", timeToSubmission=" + timeToSubmission + ", questions=" + questions + diff --git a/frontend/src/views/student/quiz/QuizView.vue b/frontend/src/views/student/quiz/QuizView.vue index 9ea159410..811546f65 100644 --- a/frontend/src/views/student/quiz/QuizView.vue +++ b/frontend/src/views/student/quiz/QuizView.vue @@ -197,6 +197,10 @@ export default class QuizView extends Vue { this.statementQuiz.questions[order].image = question.image; this.statementQuiz.questions[order].questionDetails = question.questionDetails; + + // ensure that the questions ids are equal to que returned by the server + this.statementQuiz.questions[order].questionId = question.questionId; + this.statementQuiz.answers[order].questionId = question.questionId; } catch (error) { await this.$store.dispatch('error', error); await this.$router.push({ name: 'available-quizzes' });