diff --git a/.gitignore b/.gitignore index 491f10dd9d91e03c7187da85178b3b3358c4b1a9..9e9fe03aa6f08e57eb86f9d6ca1c0f985a431ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ target/ *.iml *.ipr +### Spring ### +*.log* + ### NetBeans ### /nbproject/private/ /nbbuild/ diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/PhasesController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/PhasesController.java index ab9fe85604f607d55d751559e5e7644f8a55e857..86d3547994b952b9d4d72c52f076b13972adcf9c 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/PhasesController.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/PhasesController.java @@ -21,8 +21,6 @@ import java.util.List; @RestController @RequestMapping(value = "/training-definitions/{definitionId}/phases", produces = MediaType.APPLICATION_JSON_VALUE) -@CrossOrigin(origins = "*", allowCredentials = "true", allowedHeaders = "*", - methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT}) @Api(value = "/training-definitions/{definitionId}/phase", tags = "Phases", consumes = MediaType.APPLICATION_JSON_VALUE, diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java deleted file mode 100644 index af9ae3971aade5f75d62481c6808ed1139dc2e07..0000000000000000000000000000000000000000 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java +++ /dev/null @@ -1,58 +0,0 @@ -package cz.muni.ics.kypo.training.adaptive.controller; - -import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO; -import cz.muni.ics.kypo.training.adaptive.facade.QuestionnaireEvaluationFacade; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.validation.Valid; - -@RestController -@RequestMapping(value = "/training-runs", produces = MediaType.APPLICATION_JSON_VALUE) -@CrossOrigin(origins = "*", allowCredentials = "true", allowedHeaders = "*", - methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT}) -@Api(value = "/training-runs", - tags = "Training runs", - consumes = MediaType.APPLICATION_JSON_VALUE, - authorizations = @Authorization(value = "bearerAuth")) -public class QuestionnaireEvaluationController { - - private final QuestionnaireEvaluationFacade questionnaireEvaluationFacade; - - @Autowired - public QuestionnaireEvaluationController(QuestionnaireEvaluationFacade questionnaireEvaluationFacade) { - this.questionnaireEvaluationFacade = questionnaireEvaluationFacade; - } - - @ApiOperation(httpMethod = "POST", - value = "Evaluate answers to a questionnaire phase", - nickname = "evaluateAnswersToQuestionnaire", - produces = MediaType.APPLICATION_JSON_VALUE - ) - @ApiResponses(value = { - @ApiResponse(code = 200, message = "Answers evaluated"), - @ApiResponse(code = 500, message = "Unexpected application error") - }) - @PutMapping(value = "/{runId}/questionnaire-evaluation", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Void> evaluateAnswersToQuestionnaire(@ApiParam(value = "Training run ID", required = true) - @PathVariable("runId") Long runId, - @ApiParam(value = "Responses to questionnaire", required = true) - @Valid @RequestBody QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) { - questionnaireEvaluationFacade.evaluateAnswersToQuestionnaire(runId, questionnairePhaseAnswersDTO); - return ResponseEntity.noContent().build(); - } -} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TasksController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TasksController.java index d3ad7e447b38fff389d841e1a5fc36a9006f9d17..c939708abc3a8dbc95c26bb9bf187b5352f1f2e0 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TasksController.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TasksController.java @@ -15,8 +15,6 @@ import javax.validation.Valid; @RestController @RequestMapping(value = "/training-definitions/{definitionId}/phases/{phaseId}/tasks", produces = MediaType.APPLICATION_JSON_VALUE) -@CrossOrigin(origins = "*", allowCredentials = "true", allowedHeaders = "*", - methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT}) @Api(value = "/training-definitions/{definitionId}/tasks", tags = "Tasks", consumes = MediaType.APPLICATION_JSON_VALUE, diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TrainingRunsRestController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TrainingRunsRestController.java index 83fe2059e8f757b1f1071abee2a51bdc16042f47..f3c7250a1acb12666789fdf2b49c3adb18aaed22 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TrainingRunsRestController.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TrainingRunsRestController.java @@ -11,6 +11,7 @@ import cz.muni.ics.kypo.training.adaptive.dto.AbstractPhaseDTO; import cz.muni.ics.kypo.training.adaptive.dto.IsCorrectAnswerDTO; import cz.muni.ics.kypo.training.adaptive.dto.UserRefDTO; import cz.muni.ics.kypo.training.adaptive.dto.responses.PageResultResource; +import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO; import cz.muni.ics.kypo.training.adaptive.dto.training.ValidateAnswerDTO; import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.AccessTrainingRunDTO; import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.AccessedTrainingRunDTO; @@ -358,30 +359,21 @@ public class TrainingRunsRestController { return ResponseEntity.ok().build(); } - /** - * Evaluate responses to questionnaire. - * - * @param runId id of training run. - * @param responses to questionnaire - * @return the response entity - */ @ApiOperation(httpMethod = "PUT", - value = "Evaluate responses to questionnaire", - nickname = "evaluateResponsesToQuestionnaire", + value = "Evaluate answers to a questionnaire phase", + nickname = "evaluateAnswersToQuestionnaire", produces = MediaType.APPLICATION_JSON_VALUE ) @ApiResponses(value = { - @ApiResponse(code = 204, message = "The responses to questionnaire has been evaluated and stored."), - @ApiResponse(code = 404, message = "The training run has not been found.", response = ApiError.class), - @ApiResponse(code = 409, message = "Current phase of training is not questionnaire phase or phase has been already answered.", response = ApiError.class), - @ApiResponse(code = 500, message = "Unexpected condition was encountered.", response = ApiError.class) + @ApiResponse(code = 200, message = "Answers evaluated"), + @ApiResponse(code = 500, message = "Unexpected application error") }) - @PutMapping(value = "/{runId}/questionnaire-evaluations", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Void> evaluateResponsesToQuestionnaire(@ApiParam(value = "Training run ID", required = true) - @PathVariable("runId") Long runId, - @ApiParam(value = "Responses to questionnaire", required = true) - @RequestBody String responses) { - trainingRunFacade.evaluateResponsesToQuestionnaire(runId, responses); + @PutMapping(value = "/{runId}/questionnaire-evaluation", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Void> evaluateAnswersToQuestionnaire(@ApiParam(value = "Training run ID", required = true) + @PathVariable("runId") Long runId, + @ApiParam(value = "Responses to questionnaire", required = true) + @Valid @RequestBody QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) { + trainingRunFacade.evaluateAnswersToQuestionnaire(runId, questionnairePhaseAnswersDTO); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/Question.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/Question.java index 9edf793159a89284be53f814665d69bef62de17e..cc6db3e32034cf9ff832dadc7ca58413e3e33c46 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/Question.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/Question.java @@ -95,4 +95,19 @@ public class Question implements Serializable { public void addQuestionPhaseRelation(QuestionPhaseRelation questionPhaseRelation) { this.questionPhaseRelations.add(questionPhaseRelation); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Question)) return false; + Question question = (Question) o; + return getOrder() == question.getOrder() && + getQuestionType() == question.getQuestionType() && + Objects.equals(getText(), question.getText()); + } + + @Override + public int hashCode() { + return Objects.hash(getQuestionType(), getText(), getOrder()); + } } diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswer.java similarity index 53% rename from src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java rename to src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswer.java index 211b904f7e0768cc2cce281416faaa17d605de1d..1e5a94bad9777acaa6b105dd34bf74180848c817 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswer.java @@ -1,9 +1,12 @@ -package cz.muni.ics.kypo.training.adaptive.domain; +package cz.muni.ics.kypo.training.adaptive.domain.phase.questions; + +import cz.muni.ics.kypo.training.adaptive.domain.UserRef; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.Table; import java.io.Serializable; +import java.util.Objects; @Entity @Table(name = "question_answer") @@ -11,7 +14,6 @@ public class QuestionAnswer implements Serializable { @EmbeddedId private QuestionAnswerId questionAnswerId; - private String answer; public QuestionAnswerId getQuestionAnswerId() { @@ -29,4 +31,18 @@ public class QuestionAnswer implements Serializable { public void setAnswer(String answer) { this.answer = answer; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof QuestionAnswer)) return false; + QuestionAnswer that = (QuestionAnswer) o; + return getQuestionAnswerId().equals(that.getQuestionAnswerId()) && + getAnswer().equals(that.getAnswer()); + } + + @Override + public int hashCode() { + return Objects.hash(getQuestionAnswerId(), getAnswer()); + } } diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswerId.java similarity index 65% rename from src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java rename to src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswerId.java index d59dff5ccdf7b39773de264a7dd56601a3d582d7..71b48ba66fc64aed2559f537b262dbacaad560f4 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswerId.java @@ -1,11 +1,19 @@ -package cz.muni.ics.kypo.training.adaptive.domain; +package cz.muni.ics.kypo.training.adaptive.domain.phase.questions; + +import cz.muni.ics.kypo.training.adaptive.domain.TRAcquisitionLock; import javax.persistence.Embeddable; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import java.io.Serializable; import java.util.Objects; @Embeddable public class QuestionAnswerId implements Serializable { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "question_id") private Question question; private Long trainingRunId; @@ -36,10 +44,11 @@ public class QuestionAnswerId implements Serializable { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof QuestionAnswerId)) + return false; QuestionAnswerId that = (QuestionAnswerId) o; - return Objects.equals(question, that.question) && - Objects.equals(trainingRunId, that.trainingRunId); + return getQuestion().equals(that.getQuestion()) && + getTrainingRunId().equals(that.getTrainingRunId()); } @Override diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionPhaseResult.java similarity index 78% rename from src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java rename to src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionPhaseResult.java index cfc2bf0de2ab73fc2c22897f37d35d228092bcb3..034a16594221085c65827681f03b22ccb068a53f 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionPhaseResult.java @@ -1,14 +1,7 @@ -package cz.muni.ics.kypo.training.adaptive.domain; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; +package cz.muni.ics.kypo.training.adaptive.domain.phase.questions; + + +import javax.persistence.*; import java.io.Serializable; @Entity @@ -25,6 +18,7 @@ public class QuestionPhaseResult implements Serializable { private int achievedResult; @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "question_phase_relation_id") private QuestionPhaseRelation questionPhaseRelation; public Long getId() { diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/training/TrainingRun.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/training/TrainingRun.java index 95acdfb77bc62b175e916b242aea200528e3d078..42a8bc2bffd4a97c52d484f46056ed0d3d014c83 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/training/TrainingRun.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/training/TrainingRun.java @@ -120,10 +120,6 @@ public class TrainingRun extends AbstractEntity<Long> { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_ref_id", nullable = false) private UserRef participantRef; - @Lob - @Type(type = "org.hibernate.type.TextType") - @Column(name = "questionnaire_responses", nullable = true) - private String questionnaireResponses; @Column(name = "phase_answered") private boolean phaseAnswered; @Column(name = "previous_sandbox_instance_ref_id") @@ -304,24 +300,6 @@ public class TrainingRun extends AbstractEntity<Long> { this.solutionTaken = solutionTaken; } - /** - * Gets responses of current assessment phase - * - * @return the assessment responses - */ - public String getQuestionnaireResponses() { - return questionnaireResponses; - } - - /** - * Sets responses of current assessment phase - * - * @param assessmentResponses the assessment responses - */ - public void setQuestionnaireResponses(String assessmentResponses) { - this.questionnaireResponses = assessmentResponses; - } - /** * Gets DB reference of trainee * diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java deleted file mode 100644 index fd657740cb375d2aaef4ec50eb08794d848af01a..0000000000000000000000000000000000000000 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java +++ /dev/null @@ -1,25 +0,0 @@ -package cz.muni.ics.kypo.training.adaptive.facade; - -import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer; -import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO; -import cz.muni.ics.kypo.training.adaptive.service.QuestionnaireEvaluationService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -public class QuestionnaireEvaluationFacade { - - private final QuestionnaireEvaluationService questionnaireEvaluationService; - - @Autowired - public QuestionnaireEvaluationFacade(QuestionnaireEvaluationService questionnaireEvaluationService) { - this.questionnaireEvaluationService = questionnaireEvaluationService; - } - - public void evaluateAnswersToQuestionnaire(Long runId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) { - List<QuestionAnswer> savedAnswers = questionnaireEvaluationService.saveAnswersToQuestionnaire(runId, questionnairePhaseAnswersDTO); - questionnaireEvaluationService.evaluateAnswersToQuestionnaire(runId, savedAnswers); - } -} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingRunFacade.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingRunFacade.java index 980b96fb6d86a0f617678abc2f5c4fedeaaa1a24..dbc54add237e913852b8084f984b37040463dc10 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingRunFacade.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingRunFacade.java @@ -20,6 +20,8 @@ import cz.muni.ics.kypo.training.adaptive.dto.questionnaire.QuestionChoiceDTO; import cz.muni.ics.kypo.training.adaptive.dto.questionnaire.QuestionDTO; import cz.muni.ics.kypo.training.adaptive.dto.questionnaire.QuestionnairePhaseDTO; import cz.muni.ics.kypo.training.adaptive.dto.responses.PageResultResource; +import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionAnswerDTO; +import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO; import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.AccessTrainingRunDTO; import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.AccessedTrainingRunDTO; import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.TrainingRunByIdDTO; @@ -28,8 +30,7 @@ import cz.muni.ics.kypo.training.adaptive.enums.Actions; import cz.muni.ics.kypo.training.adaptive.enums.PhaseType; import cz.muni.ics.kypo.training.adaptive.mapping.mapstruct.PhaseMapper; import cz.muni.ics.kypo.training.adaptive.mapping.mapstruct.TrainingRunMapper; -import cz.muni.ics.kypo.training.adaptive.service.SecurityService; -import cz.muni.ics.kypo.training.adaptive.service.UserService; +import cz.muni.ics.kypo.training.adaptive.service.QuestionnaireEvaluationService; import cz.muni.ics.kypo.training.adaptive.service.api.UserManagementServiceApi; import cz.muni.ics.kypo.training.adaptive.service.training.TrainingRunService; import org.slf4j.Logger; @@ -43,10 +44,8 @@ import org.springframework.transaction.annotation.Transactional; import java.time.Clock; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; /** * The type Training run facade. @@ -56,29 +55,29 @@ public class TrainingRunFacade { private static final Logger LOG = LoggerFactory.getLogger(TrainingRunFacade.class); - private TrainingRunService trainingRunService; - private UserService userService; - private UserManagementServiceApi userManagementServiceApi; - private TrainingRunMapper trainingRunMapper; - private PhaseMapper phaseMapper; + private final TrainingRunService trainingRunService; + private final UserManagementServiceApi userManagementServiceApi; + private final TrainingRunMapper trainingRunMapper; + private final PhaseMapper phaseMapper; + private final QuestionnaireEvaluationService questionnaireEvaluationService; + /** * Instantiates a new Training run facade. * * @param trainingRunService the training run service - * @param userService the user service * @param trainingRunMapper the training run mapper * @param phaseMapper the phase mapper */ @Autowired public TrainingRunFacade(TrainingRunService trainingRunService, - UserService userService, + QuestionnaireEvaluationService questionnaireEvaluationService, UserManagementServiceApi userManagementServiceApi, TrainingRunMapper trainingRunMapper, PhaseMapper phaseMapper) { this.trainingRunService = trainingRunService; - this.userService = userService; + this.questionnaireEvaluationService = questionnaireEvaluationService; this.userManagementServiceApi = userManagementServiceApi; this.trainingRunMapper = trainingRunMapper; this.phaseMapper = phaseMapper; @@ -334,15 +333,18 @@ public class TrainingRunFacade { } /** - * Evaluate and store responses to questionnaire. + * Evaluate and store answers to questionnaire. * - * @param trainingRunId id of Training Run to be finish. - * @param responsesAsString responses to questionnaire + * @param trainingRunId id of the Training Run. + * @param questionnairePhaseAnswersDTO answers to questionnaire */ - @IsTrainee + @PreAuthorize("hasAuthority(T(cz.muni.ics.kypo.training.adaptive.enums.RoleTypeSecurity).ROLE_ADAPTIVE_TRAINING_ADMINISTRATOR)" + + "or @securityService.isTraineeOfGivenTrainingRun(#trainingRunId)") @TransactionalWO - public void evaluateResponsesToQuestionnaire(Long trainingRunId, String responsesAsString) { - trainingRunService.evaluateResponsesToQuestionnaire(trainingRunId, responsesAsString); + public void evaluateAnswersToQuestionnaire(Long trainingRunId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) { + Map<Long, String> questionsAnswersMapping = questionnairePhaseAnswersDTO.getAnswers().stream() + .collect(Collectors.toMap(QuestionAnswerDTO::getQuestionId, QuestionAnswerDTO::getAnswer)); + questionnaireEvaluationService.saveAndEvaluateAnswersToQuestionnaire(trainingRunId, questionsAnswersMapping); } /** diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java index 2c5e54e8ab08423f376c53b068f9768672a78e02..8fc46323af5d8daa8f9adc1ab6a3908cae6b8229 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java @@ -1,7 +1,7 @@ package cz.muni.ics.kypo.training.adaptive.repository; -import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer; -import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswerId; +import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.QuestionAnswer; +import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.QuestionAnswerId; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java index 86c9945d90e33876ca59d3496d20309ee5264a90..6c417b061364af17e29da234883b711c6793cd6b 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java @@ -1,6 +1,6 @@ package cz.muni.ics.kypo.training.adaptive.repository; -import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseResult; +import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.QuestionPhaseResult; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionRepository.java index 2e2ceb2bb1ad1b4501b14b4966c87323a6fc30ed..c6aad8d64a6f52db57497b526016879f8aac1deb 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionRepository.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionRepository.java @@ -4,6 +4,10 @@ import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.Question; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface QuestionRepository extends JpaRepository<Question, Long> { + + Optional<Question> findByIdAndQuestionnairePhaseId(Long questionId, Long questionnaireId); } diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java index c1d70ef79527c3377621353cc11f78aded7e8ddc..ad097254a2b4cb6699ad74ed885aace649fc14e3 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java @@ -1,33 +1,29 @@ package cz.muni.ics.kypo.training.adaptive.service; -import cz.muni.ics.kypo.training.adaptive.domain.Question; -import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer; -import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswerId; -import cz.muni.ics.kypo.training.adaptive.domain.QuestionChoice; -import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseRelation; -import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseResult; -import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionAnswerDTO; -import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO; +import cz.muni.ics.kypo.training.adaptive.domain.phase.QuestionnairePhase; +import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.*; +import cz.muni.ics.kypo.training.adaptive.domain.training.TrainingRun; +import cz.muni.ics.kypo.training.adaptive.enums.QuestionnaireType; +import cz.muni.ics.kypo.training.adaptive.exceptions.BadRequestException; +import cz.muni.ics.kypo.training.adaptive.exceptions.EntityConflictException; +import cz.muni.ics.kypo.training.adaptive.exceptions.EntityErrorDetail; +import cz.muni.ics.kypo.training.adaptive.exceptions.EntityNotFoundException; import cz.muni.ics.kypo.training.adaptive.repository.QuestionAnswerRepository; -import cz.muni.ics.kypo.training.adaptive.repository.QuestionPhaseRelationRepository; import cz.muni.ics.kypo.training.adaptive.repository.QuestionPhaseResultRepository; -import cz.muni.ics.kypo.training.adaptive.repository.QuestionRepository; +import cz.muni.ics.kypo.training.adaptive.repository.phases.QuestionPhaseRelationRepository; +import cz.muni.ics.kypo.training.adaptive.repository.phases.QuestionRepository; +import cz.muni.ics.kypo.training.adaptive.repository.training.TrainingRunRepository; +import cz.muni.ics.kypo.training.adaptive.service.audit.AuditEventsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @Service -@Transactional public class QuestionnaireEvaluationService { private static final Logger LOG = LoggerFactory.getLogger(QuestionnaireEvaluationService.class); @@ -36,35 +32,70 @@ public class QuestionnaireEvaluationService { private final QuestionAnswerRepository questionAnswerRepository; private final QuestionPhaseRelationRepository questionPhaseRelationRepository; private final QuestionPhaseResultRepository questionPhaseResultRepository; + private final TrainingRunRepository trainingRunRepository; + private final AuditEventsService auditEventsService; @Autowired - public QuestionnaireEvaluationService(QuestionRepository questionRepository, QuestionAnswerRepository questionAnswerRepository, - QuestionPhaseRelationRepository questionPhaseRelationRepository, QuestionPhaseResultRepository questionPhaseResultRepository) { + public QuestionnaireEvaluationService(QuestionRepository questionRepository, + QuestionAnswerRepository questionAnswerRepository, + QuestionPhaseRelationRepository questionPhaseRelationRepository, + QuestionPhaseResultRepository questionPhaseResultRepository, + TrainingRunRepository trainingRunRepository, + AuditEventsService auditEventsService) { this.questionRepository = questionRepository; this.questionAnswerRepository = questionAnswerRepository; this.questionPhaseRelationRepository = questionPhaseRelationRepository; this.questionPhaseResultRepository = questionPhaseResultRepository; + this.trainingRunRepository = trainingRunRepository; + this.auditEventsService = auditEventsService; } - public List<QuestionAnswer> saveAnswersToQuestionnaire(Long runId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) { - List<QuestionAnswer> result = new ArrayList<>(); - for (QuestionAnswerDTO answer : questionnairePhaseAnswersDTO.getAnswers()) { - Question question = questionRepository.findById(answer.getQuestionId()) - .orElseThrow(() -> new RuntimeException("Question was not found")); - // TODO throw proper exception once kypo2-training is migrated - - QuestionAnswer questionAnswer = new QuestionAnswer(); - questionAnswer.setQuestionAnswerId(new QuestionAnswerId(question, runId)); - questionAnswer.setAnswer(answer.getAnswer()); + /** + * Evaluate and store answers to questionnaire. + * + * @param trainingRunId id of training run to answer the questionnaire. + * @param questionnairePhaseAnswers answers to questionnaire to be evaluated. Map of entries (Key = question ID, Value = answer). + * @throws EntityNotFoundException training run is not found. + */ + public List<QuestionAnswer> saveAndEvaluateAnswersToQuestionnaire(Long trainingRunId, Map<Long, String> questionnairePhaseAnswers) { + TrainingRun trainingRun = this.findByIdWithPhase(trainingRunId); + this.checkIfCanBeEvaluated(trainingRun); + + List<QuestionAnswer> storedQuestionsAnswers = new ArrayList<>(); + for (Map.Entry<Long, String> questionAnswer : questionnairePhaseAnswers.entrySet()) { + Long questionnaireId = trainingRun.getCurrentPhase().getId(); + storedQuestionsAnswers.add(saveQuestionAnswer(questionAnswer.getKey(), questionAnswer.getValue(), questionnaireId, trainingRun.getId())); + } + if (((QuestionnairePhase) trainingRun.getCurrentPhase()).getQuestionnaireType() == QuestionnaireType.ADAPTIVE) { + this.evaluateAnswersToQuestionnaire(trainingRunId, storedQuestionsAnswers); + } + auditEventsService.auditPhaseCompletedAction(trainingRun); + auditEventsService.auditQuestionnaireAnswersAction(trainingRun, storedQuestionsAnswers.stream() + .collect(Collectors.toMap(questionAnswer -> questionAnswer.getQuestionAnswerId().getQuestion().getText(), QuestionAnswer::getAnswer)) + .toString()); + trainingRun.setPhaseAnswered(true); + return storedQuestionsAnswers; + } - questionAnswerRepository.save(questionAnswer); - result.add(questionAnswer); + private void checkIfCanBeEvaluated(TrainingRun trainingRun) { + if (!(trainingRun.getCurrentPhase() instanceof QuestionnairePhase)) { + throw new BadRequestException("Current phase is not questionnaire phase and cannot be evaluated."); } + if (trainingRun.isPhaseAnswered()) + throw new EntityConflictException(new EntityErrorDetail(TrainingRun.class, "id", Long.class, trainingRun.getId(), + "Current phase of the training run has been already answered.")); + } - return result; + private QuestionAnswer saveQuestionAnswer(Long questionId, String answer, Long questionnaireId, Long trainingRunId) { + Question question = this.findQuestionByIdAndQuestionnairePhaseId(questionId, questionnaireId); + QuestionAnswer questionAnswer = new QuestionAnswer(); + questionAnswer.setQuestionAnswerId(new QuestionAnswerId(question, trainingRunId)); + questionAnswer.setAnswer(answer); + + return questionAnswerRepository.save(questionAnswer); } - public void evaluateAnswersToQuestionnaire(Long runId, List<QuestionAnswer> answers) { + private void evaluateAnswersToQuestionnaire(Long runId, List<QuestionAnswer> answers) { if (CollectionUtils.isEmpty(answers)) { return; } @@ -118,4 +149,14 @@ public class QuestionnaireEvaluationService { .filter(q -> Objects.equals(answer, q.getText())) .findFirst(); } + + private TrainingRun findByIdWithPhase(Long runId) { + return trainingRunRepository.findByIdWithPhase(runId).orElseThrow(() -> new EntityNotFoundException( + new EntityErrorDetail(TrainingRun.class, "id", runId.getClass(), runId))); + } + + private Question findQuestionByIdAndQuestionnairePhaseId(Long questionId, Long questionnaireId) { + return questionRepository.findByIdAndQuestionnairePhaseId(questionId, questionnaireId) + .orElseThrow(() -> new EntityNotFoundException(new EntityErrorDetail(Question.class, "id", Long.class, questionId))); + } } diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java index f19241709c6c7bd01ebc9a4f683e557de1c71a94..88d7e2cabda9342d79f3fd2e35e8db90cef040a6 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java @@ -60,7 +60,7 @@ public class TrainingRunService { * @param trainingInstanceRepository the training instance repository * @param participantRefRepository the participant ref repository * @param auditEventsService the audit events service - * @param securityService the security service + * @param sandboxServiceApi the sandbox service api * @param trAcquisitionLockRepository the tr acquisition lock repository */ @Autowired @@ -322,7 +322,6 @@ public class TrainingRunService { } else { newTrainingRun.setParticipantRef(participantRefRepository.save(new UserRef(userManagementServiceApi.getLoggedInUserRefId()))); } - newTrainingRun.setQuestionnaireResponses("[]"); newTrainingRun.setState(TRState.RUNNING); newTrainingRun.setTrainingInstance(trainingInstance); newTrainingRun.setStartTime(startTime); @@ -501,13 +500,7 @@ public class TrainingRunService { trainingRunRepository.save(trainingRun); } - /** - * Evaluate and store responses to questionnaire. - * - * @param trainingRunId id of training run to be finished. - * @param responsesAsString response to questionnaire to be evaluated - * @throws EntityNotFoundException training run is not found. - */ + public void evaluateResponsesToQuestionnaire(Long trainingRunId, String responsesAsString) { TrainingRun trainingRun = findByIdWithPhase(trainingRunId); if (!(trainingRun.getCurrentPhase() instanceof QuestionnairePhase)) { diff --git a/src/main/resources/db/migration/V1__db_adaptive_trainings_schema.sql b/src/main/resources/db/migration/V1__db_adaptive_trainings_schema.sql index 43bbe615f9acb9e3a7bd6aecfee6ccaaf533655f..14d59308693f6666ec71a7432dff0dc4adb012c5 100644 --- a/src/main/resources/db/migration/V1__db_adaptive_trainings_schema.sql +++ b/src/main/resources/db/migration/V1__db_adaptive_trainings_schema.sql @@ -172,6 +172,25 @@ create table question_phase_relation_question ( foreign key (question_id) references question ); +create table question_answer ( + question_id int8 not null, + training_run_id int8 not null, + answer varchar(255) not null, + primary key (question_id, training_run_id), + foreign key (question_id) references question, + foreign key (training_run_id) references training_run, + unique (question_id, training_run_id) +); + +create table question_phase_result ( + question_phase_result_id bigserial not null, + training_run_id int8 not null, + question_phase_relation_id int8 not null, + achieved_result int4 not null, + primary key (question_phase_result_id), + foreign key (question_phase_relation_id) references question_phase_relation +); + -- ACCESS TOKEN create table access_token ( id bigserial not null, diff --git a/src/main/resources/db/migration/V2__db_adaptive_trainings_sequences.sql b/src/main/resources/db/migration/V2__db_adaptive_trainings_sequences.sql index 5f55c926c77fe2ef7849167902e332e517d74637..1f09c3ec2cac7ee24056a160d89858508c825b28 100644 --- a/src/main/resources/db/migration/V2__db_adaptive_trainings_sequences.sql +++ b/src/main/resources/db/migration/V2__db_adaptive_trainings_sequences.sql @@ -4,3 +4,4 @@ CREATE SEQUENCE question_seq AS bigint INCREMENT 50 MINVALUE 1; CREATE SEQUENCE question_choice_seq AS bigint INCREMENT 50 MINVALUE 1; CREATE SEQUENCE question_phase_seq AS bigint INCREMENT 50 MINVALUE 1; CREATE SEQUENCE decision_matrix_row_seq AS bigint INCREMENT 50 MINVALUE 1; +CREATE SEQUENCE question_phase_result_seq AS bigint INCREMENT 50 MINVALUE 1; diff --git a/src/test/java/cz/muni/ics/kypo/training/adaptive/DemoApplicationTests.java b/src/test/java/cz/muni/ics/kypo/training/adaptive/AdaptiveTrainingApplicationTests.java similarity index 86% rename from src/test/java/cz/muni/ics/kypo/training/adaptive/DemoApplicationTests.java rename to src/test/java/cz/muni/ics/kypo/training/adaptive/AdaptiveTrainingApplicationTests.java index 53d7b0d9f3dca61608a2618b7c3b75c0bec1c5f1..df77eecfaf85d71e1e440c477d13a356f8393670 100644 --- a/src/test/java/cz/muni/ics/kypo/training/adaptive/DemoApplicationTests.java +++ b/src/test/java/cz/muni/ics/kypo/training/adaptive/AdaptiveTrainingApplicationTests.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(classes = TestAdaptiveTrainingConfiguration.class) -class DemoApplicationTests { +class AdaptiveTrainingApplicationTests { @Test void contextLoads() {