diff --git a/package.json b/package.json index 3395e11..209a580 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "dependencies": { "@f0rce/ace-widget": "1.0.2", - "@polymer/polymer": "3.5.1", + "@polymer/polymer": "3.5.2", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin/bundles": "24.5.1", "@vaadin/common-frontend": "0.0.19", @@ -19,29 +19,30 @@ "@vaadin/vaadin-usage-statistics": "2.1.3", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "2.29.3", - "lit": "3.1.4", + "lit": "3.2.1", "print-js": "1.6.0", "proj4": "2.12.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.23.1" + "react-router-dom": "6.26.2" }, "devDependencies": { - "@babel/preset-react": "7.24.7", - "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@vitejs/plugin-react": "4.3.1", - "async": "3.2.5", - "glob": "10.4.1", + "@babel/preset-react": "7.25.7", + "@preact/signals-react-transform": "0.4.0", + "@rollup/plugin-replace": "6.0.1", + "@rollup/pluginutils": "5.1.2", + "@types/react": "18.3.11", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "4.3.3", + "async": "3.2.6", + "glob": "10.4.5", "rollup-plugin-brotli": "3.1.0", "rollup-plugin-visualizer": "5.12.0", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.4.5", - "vite": "5.3.3", - "vite-plugin-checker": "0.6.4", + "typescript": "5.6.3", + "vite": "5.4.9", + "vite-plugin-checker": "0.8.0", "workbox-build": "7.1.1", "workbox-core": "7.1.0", "workbox-precaching": "7.1.0" @@ -49,7 +50,7 @@ "vaadin": { "dependencies": { "@f0rce/ace-widget": "1.0.2", - "@polymer/polymer": "3.5.1", + "@polymer/polymer": "3.5.2", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin/bundles": "24.5.1", "@vaadin/common-frontend": "0.0.19", @@ -64,34 +65,35 @@ "@vaadin/vaadin-usage-statistics": "2.1.3", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "2.29.3", - "lit": "3.1.4", + "lit": "3.2.1", "print-js": "1.6.0", "proj4": "2.12.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.23.1" + "react-router-dom": "6.26.2" }, "devDependencies": { - "@babel/preset-react": "7.24.7", - "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@vitejs/plugin-react": "4.3.1", - "async": "3.2.5", - "glob": "10.4.1", + "@babel/preset-react": "7.25.7", + "@preact/signals-react-transform": "0.4.0", + "@rollup/plugin-replace": "6.0.1", + "@rollup/pluginutils": "5.1.2", + "@types/react": "18.3.11", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "4.3.3", + "async": "3.2.6", + "glob": "10.4.5", "rollup-plugin-brotli": "3.1.0", "rollup-plugin-visualizer": "5.12.0", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.4.5", - "vite": "5.3.3", - "vite-plugin-checker": "0.6.4", + "typescript": "5.6.3", + "vite": "5.4.9", + "vite-plugin-checker": "0.8.0", "workbox-build": "7.1.1", "workbox-core": "7.1.0", "workbox-precaching": "7.1.0" }, - "hash": "1a0f17d48b329307b5862bc57499307d1b89f7d89260121c2b7189f76957c436" + "hash": "2dc40a4f634ae025081ca2239cba00b14a35fe94ab78ac0a4dd3023d882081d5" }, "overrides": { "@vaadin/bundles": "$@vaadin/bundles", diff --git a/src/main/java/com/primefactorsolutions/model/Candidate.java b/src/main/java/com/primefactorsolutions/model/Candidate.java index f9f0d73..976b9b4 100644 --- a/src/main/java/com/primefactorsolutions/model/Candidate.java +++ b/src/main/java/com/primefactorsolutions/model/Candidate.java @@ -6,8 +6,6 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import java.util.List; - @Entity @Data @AllArgsConstructor @@ -16,7 +14,4 @@ import java.util.List; public class Candidate extends BaseEntity { @Column(unique = true) private String email; - - @OneToMany(fetch = FetchType.EAGER, mappedBy = "candidate") - private List assessments; } diff --git a/src/main/java/com/primefactorsolutions/model/EmployeePosition.java b/src/main/java/com/primefactorsolutions/model/EmployeePosition.java new file mode 100644 index 0000000..c368ec6 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/model/EmployeePosition.java @@ -0,0 +1,8 @@ +package com.primefactorsolutions.model; + +public enum EmployeePosition { + JUNIOR_DEV, + JUNIOR_QA, + SENIOR_DEV, + SENIOR_QA +} diff --git a/src/main/java/com/primefactorsolutions/model/Evaluation.java b/src/main/java/com/primefactorsolutions/model/Evaluation.java new file mode 100644 index 0000000..bb7102f --- /dev/null +++ b/src/main/java/com/primefactorsolutions/model/Evaluation.java @@ -0,0 +1,30 @@ +package com.primefactorsolutions.model; + +import com.google.common.collect.Lists; +import io.hypersistence.utils.hibernate.type.json.JsonType; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.Type; + +import java.util.List; + +@Entity +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class Evaluation extends BaseEntity { + @ManyToOne + private Candidate candidate; + private EmployeePosition candidatePosition; + private Employee interviewer; + @Type(JsonType.class) + @Column(columnDefinition = "json") + @ColumnDefault("JSON_ARRAY()") + private List skillEvaluations = Lists.newArrayList(); + private Integer points; + @OneToMany(fetch = FetchType.EAGER, mappedBy = "evaluation") + private List exams; +} diff --git a/src/main/java/com/primefactorsolutions/model/Assessment.java b/src/main/java/com/primefactorsolutions/model/Exam.java similarity index 65% rename from src/main/java/com/primefactorsolutions/model/Assessment.java rename to src/main/java/com/primefactorsolutions/model/Exam.java index bae943a..06c9d6e 100644 --- a/src/main/java/com/primefactorsolutions/model/Assessment.java +++ b/src/main/java/com/primefactorsolutions/model/Exam.java @@ -15,30 +15,33 @@ import java.util.Optional; @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) -public class Assessment extends BaseEntity { +public class Exam extends BaseEntity { @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL}) - @JoinTable(name = "ASSESSMENT_QUESTIONS", joinColumns = @JoinColumn(name = "assessment_id"), + @JoinTable(name = "EXAM_QUESTIONS", joinColumns = @JoinColumn(name = "exam_id"), inverseJoinColumns = @JoinColumn(name = "question_id")) private List questions = new ArrayList<>(); - @OneToMany(fetch = FetchType.EAGER, mappedBy = "assessment", cascade = {CascadeType.ALL}) + @OneToMany(fetch = FetchType.EAGER, mappedBy = "exam", cascade = {CascadeType.ALL}) private List submissions = new ArrayList<>(); @ManyToOne private Candidate candidate; + @ManyToOne + private Evaluation evaluation; + @OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL}) - @JoinColumn(name = "ASSESSMENT_ID") - private List assessmentEvents = new ArrayList<>(); + @JoinColumn(name = "EXAM_ID") + private List examEvents = new ArrayList<>(); public Submission getCurrentSubmission() { return submissions.getLast(); } public Long getRemainingTimeSeconds() { - final Optional started = assessmentEvents.stream() - .filter(e -> e.getStatus() == AssessmentStatus.STARTED) - .map(AssessmentEvent::getTimestamp) + final Optional started = examEvents.stream() + .filter(e -> e.getStatus() == ExamStatus.STARTED) + .map(ExamEvent::getTimestamp) .findFirst(); final Integer totalTimeMinutes = questions.stream() @@ -52,24 +55,24 @@ public class Assessment extends BaseEntity { } public Instant getStartingTime() { - final Optional started = assessmentEvents.stream() - .filter(e -> e.getStatus() == AssessmentStatus.STARTED) - .map(AssessmentEvent::getTimestamp) + final Optional started = examEvents.stream() + .filter(e -> e.getStatus() == ExamStatus.STARTED) + .map(ExamEvent::getTimestamp) .findFirst(); return started.orElse(null); } public boolean isCompleted() { - return assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.COMPLETED) - .map(AssessmentEvent::getTimestamp) + return examEvents.stream().filter(e -> e.getStatus() == ExamStatus.COMPLETED) + .map(ExamEvent::getTimestamp) .findFirst() .isPresent(); } public boolean isStarted() { - return assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.STARTED) - .map(AssessmentEvent::getTimestamp) + return examEvents.stream().filter(e -> e.getStatus() == ExamStatus.STARTED) + .map(ExamEvent::getTimestamp) .findFirst() .isPresent(); } diff --git a/src/main/java/com/primefactorsolutions/model/AssessmentEvent.java b/src/main/java/com/primefactorsolutions/model/ExamEvent.java similarity index 79% rename from src/main/java/com/primefactorsolutions/model/AssessmentEvent.java rename to src/main/java/com/primefactorsolutions/model/ExamEvent.java index aa07c7a..c883c12 100644 --- a/src/main/java/com/primefactorsolutions/model/AssessmentEvent.java +++ b/src/main/java/com/primefactorsolutions/model/ExamEvent.java @@ -13,7 +13,7 @@ import java.time.Instant; @EqualsAndHashCode(callSuper = true) @AllArgsConstructor @NoArgsConstructor -public class AssessmentEvent extends BaseEntity { +public class ExamEvent extends BaseEntity { private Instant timestamp; - private AssessmentStatus status; + private ExamStatus status; } diff --git a/src/main/java/com/primefactorsolutions/model/AssessmentStatus.java b/src/main/java/com/primefactorsolutions/model/ExamStatus.java similarity index 72% rename from src/main/java/com/primefactorsolutions/model/AssessmentStatus.java rename to src/main/java/com/primefactorsolutions/model/ExamStatus.java index 430e37f..21e5398 100644 --- a/src/main/java/com/primefactorsolutions/model/AssessmentStatus.java +++ b/src/main/java/com/primefactorsolutions/model/ExamStatus.java @@ -1,6 +1,6 @@ package com.primefactorsolutions.model; -public enum AssessmentStatus { +public enum ExamStatus { CREATED, STARTED, COMPLETED diff --git a/src/main/java/com/primefactorsolutions/model/SkillEvaluation.java b/src/main/java/com/primefactorsolutions/model/SkillEvaluation.java new file mode 100644 index 0000000..c2a197b --- /dev/null +++ b/src/main/java/com/primefactorsolutions/model/SkillEvaluation.java @@ -0,0 +1,4 @@ +package com.primefactorsolutions.model; + +public record SkillEvaluation (SkillType skillType, SkillLevel level, String comments) { +} diff --git a/src/main/java/com/primefactorsolutions/model/SkillLevel.java b/src/main/java/com/primefactorsolutions/model/SkillLevel.java new file mode 100644 index 0000000..9a7eb2a --- /dev/null +++ b/src/main/java/com/primefactorsolutions/model/SkillLevel.java @@ -0,0 +1,8 @@ +package com.primefactorsolutions.model; + +public enum SkillLevel { + NEEDS_IMPROVEMENT, + FAIR, + VERY_GOOD, + EXCELLENT +} diff --git a/src/main/java/com/primefactorsolutions/model/SkillType.java b/src/main/java/com/primefactorsolutions/model/SkillType.java new file mode 100644 index 0000000..53fb9db --- /dev/null +++ b/src/main/java/com/primefactorsolutions/model/SkillType.java @@ -0,0 +1,8 @@ +package com.primefactorsolutions.model; + +public enum SkillType { + TECHNICAL_KNOWLEDGE, + ASSERTIVENESS, + COMPLETENESS_AND_CONCISENESS, + COMMUNICATION +} diff --git a/src/main/java/com/primefactorsolutions/model/Submission.java b/src/main/java/com/primefactorsolutions/model/Submission.java index a2186f4..67b151b 100644 --- a/src/main/java/com/primefactorsolutions/model/Submission.java +++ b/src/main/java/com/primefactorsolutions/model/Submission.java @@ -23,7 +23,7 @@ public class Submission extends BaseEntity { private Question question; @Lob - private String response; + private String text; @Type(JsonType.class) @Column(columnDefinition = "json") @@ -32,5 +32,5 @@ public class Submission extends BaseEntity { private SubmissionStatus submissionStatus; @ManyToOne - private Assessment assessment; + private Exam exam; } diff --git a/src/main/java/com/primefactorsolutions/repositories/AssessmentRepository.java b/src/main/java/com/primefactorsolutions/repositories/ExamRepository.java similarity index 51% rename from src/main/java/com/primefactorsolutions/repositories/AssessmentRepository.java rename to src/main/java/com/primefactorsolutions/repositories/ExamRepository.java index 81813d8..1bf2bb7 100644 --- a/src/main/java/com/primefactorsolutions/repositories/AssessmentRepository.java +++ b/src/main/java/com/primefactorsolutions/repositories/ExamRepository.java @@ -1,9 +1,9 @@ package com.primefactorsolutions.repositories; -import com.primefactorsolutions.model.Assessment; +import com.primefactorsolutions.model.Exam; import org.springframework.data.jpa.repository.JpaRepository; import java.util.UUID; -public interface AssessmentRepository extends JpaRepository { +public interface ExamRepository extends JpaRepository { } diff --git a/src/main/java/com/primefactorsolutions/service/AssessmentService.java b/src/main/java/com/primefactorsolutions/service/AssessmentService.java deleted file mode 100644 index 71dd423..0000000 --- a/src/main/java/com/primefactorsolutions/service/AssessmentService.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.primefactorsolutions.service; - -import com.primefactorsolutions.model.*; -import com.primefactorsolutions.repositories.AssessmentRepository; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.mail.SimpleMailMessage; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.stereotype.Service; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -@Service -@AllArgsConstructor -@Slf4j -public class AssessmentService { - - private final AssessmentRepository assessmentRepository; - private final EntityManager entityManager; - private final JavaMailSender emailSender; - - public Assessment createOrUpdate(final Assessment assessment) { - final Assessment saved = assessmentRepository.save(assessment); - - return saved; - } - - public void sendEmail(final Assessment assessment) { - try { - final String evaluationLink = String.format("https://careers.primefactorsolutions.com/evaluation/%s", - assessment.getId()); - final SimpleMailMessage message = new SimpleMailMessage(); - message.setFrom("no-reply@primefactorsolutions.com"); - message.setBcc("no-reply@primefactorsolutions.com"); - message.setTo(assessment.getCandidate().getEmail()); - message.setSubject("PFS - Evaluacion Tecnica"); - message.setText(String.format("Estimado candidato,\n\nGracias por su candidatura. En esta etapa del " - + "proceso de seleccion, usted debe completar " - + "una evaluacion tecnica de programacion en JAVA. La prueba tiene una duracion de 30 minutos y " - + "puede completarla cuando tenga una buena " - + "conexion de internet.\n\n" - + "Haga click aca: " + evaluationLink + "\n\n" - + "Exito!")); - - emailSender.send(message); - log.info("Sent email to {}", assessment.getCandidate().getEmail()); - } catch (Exception e) { - log.error("Error sending email to {}", assessment.getCandidate().getEmail(), e); - throw e; - } - } - - public List getAssessments() { - return assessmentRepository.findAll(); - } - - public Assessment getAssessment(final UUID id) { - return assessmentRepository.findById(id).orElse(null); - } - - public Assessment startAssessment(final UUID id) { - final Assessment assessment = assessmentRepository.findById(id).get(); - if (assessment.isStarted()) { - return assessment; - } - - assessment.getAssessmentEvents().add(new AssessmentEvent(Instant.now(), AssessmentStatus.STARTED)); - - return assessmentRepository.save(assessment); - } - - public Submission getNextSubmission(final UUID assessmentId, final UUID currSubmissionId) { - return getNextSubmission(assessmentId, currSubmissionId, true); - } - - public Submission getNextSubmission(final UUID assessmentId, final UUID currSubmissionId, - final boolean checkCompleted) { - final Assessment assessment = assessmentRepository.findById(assessmentId).get(); - - Assessment saved; - if (currSubmissionId == null) { - if (checkCompleted && assessment.isCompleted()) { - return null; - } - - final Question firstQuestion = assessment.getQuestions().stream().findFirst().get(); - final Optional submissionToReturn = assessment.getSubmissions().stream() - .filter(s -> s.getQuestion().equals(firstQuestion)) - .findFirst(); - - if (submissionToReturn.isEmpty()) { - final Submission result = new Submission(firstQuestion, firstQuestion.getContent(), Map.of(), - SubmissionStatus.FAIL, assessment); - assessment.getSubmissions().add(result); - } - - saved = assessmentRepository.save(assessment); - final Optional submissionToReturn2 = saved.getSubmissions().stream() - .filter(s -> s.getQuestion().equals(firstQuestion)) - .findFirst(); - - return submissionToReturn2.get(); - } - - final Submission currSubmission = assessment.getSubmissions().stream() - .filter(s -> s.getId().equals(currSubmissionId)) - .findFirst().get(); - final Question currQuestion = currSubmission.getQuestion(); - int idx = assessment.getQuestions().indexOf(currQuestion); - - if (idx == assessment.getQuestions().size() - 1) { - return null; - } - - final Question nextQuestion = assessment.getQuestions().get(idx + 1); - - final Optional submissionToReturn = assessment.getSubmissions().stream() - .filter(s -> s.getQuestion().equals(nextQuestion)) - .findFirst(); - - if (submissionToReturn.isEmpty()) { - final Submission result = new Submission(nextQuestion, nextQuestion.getContent(), Map.of(), - SubmissionStatus.FAIL, assessment); - assessment.getSubmissions().add(result); - } - - saved = assessmentRepository.save(assessment); - final Optional submissionToReturn2 = saved.getSubmissions().stream() - .filter(s -> s.getQuestion().equals(nextQuestion)) - .findFirst(); - - return submissionToReturn2.get(); - } - - public Submission getPrevSubmission(final UUID assessmentId, final Submission currSubmission) { - if (currSubmission == null) { - return null; - } - - final Question currQuestion = currSubmission.getQuestion(); - Assessment assessment = assessmentRepository.findById(assessmentId).get(); - int idx = assessment.getQuestions().indexOf(currQuestion); - - if (idx == 0) { - return null; - } - - final Question prevQuestion = assessment.getQuestions().get(idx - 1); - - return assessment.getSubmissions().stream() - .filter(s -> s.getQuestion().equals(prevQuestion)) - .findFirst().orElseThrow(() -> new IllegalStateException("submission invalid")); - } - - public Assessment completeAssessment(final UUID id) { - Assessment assessment = assessmentRepository.findById(id).get(); - Optional completed = assessment.getAssessmentEvents().stream() - .filter(e -> e.getStatus() == AssessmentStatus.COMPLETED) - .findFirst(); - - if (completed.isPresent()) { - return assessment; - } - - assessment.getAssessmentEvents().add(new AssessmentEvent(Instant.now(), AssessmentStatus.COMPLETED)); - Assessment saved = assessmentRepository.save(assessment); - - return saved; - } - - public void saveSubmission(final UUID id, final Submission currSubmission) { - Assessment assessment = assessmentRepository.findById(id).get(); - final Submission submission = assessment.getSubmissions().stream() - .filter(s -> s.getId().equals(currSubmission.getId())) - .findFirst().get(); - - submission.setResponse(currSubmission.getResponse()); - Assessment saved = assessmentRepository.save(assessment); - } - - @Transactional - public Assessment saveAssessment(final Assessment assessment) { - Candidate merged = entityManager.merge(assessment.getCandidate()); - List mergedQuestions = assessment.getQuestions().stream().map(entityManager::merge) - .collect(Collectors.toList()); - - assessment.setCandidate(merged); - assessment.setQuestions(mergedQuestions); - - return assessmentRepository.save(assessment); - } -} diff --git a/src/main/java/com/primefactorsolutions/service/CandidateService.java b/src/main/java/com/primefactorsolutions/service/CandidateService.java index 4c3fefb..c31598a 100644 --- a/src/main/java/com/primefactorsolutions/service/CandidateService.java +++ b/src/main/java/com/primefactorsolutions/service/CandidateService.java @@ -13,8 +13,8 @@ import java.util.UUID; public class CandidateService { private final CandidateRepository candidateRepository; - public Candidate createOrUpdate(final Candidate assessment) { - final Candidate saved = candidateRepository.save(assessment); + public Candidate createOrUpdate(final Candidate candidate) { + final Candidate saved = candidateRepository.save(candidate); return saved; } diff --git a/src/main/java/com/primefactorsolutions/service/ExamService.java b/src/main/java/com/primefactorsolutions/service/ExamService.java new file mode 100644 index 0000000..d0f4a84 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/service/ExamService.java @@ -0,0 +1,198 @@ +package com.primefactorsolutions.service; + +import com.primefactorsolutions.model.*; +import com.primefactorsolutions.repositories.ExamRepository; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +@Slf4j +public class ExamService { + + private final ExamRepository examRepository; + private final EntityManager entityManager; + private final JavaMailSender emailSender; + + public Exam createOrUpdate(final Exam exam) { + return examRepository.save(exam); + } + + public void sendEmail(final Exam exam) { + try { + final String evaluationLink = String.format("https://careers.primefactorsolutions.com/evaluation/%s", + exam.getId()); + final SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom("no-reply@primefactorsolutions.com"); + message.setBcc("no-reply@primefactorsolutions.com"); + message.setTo(exam.getCandidate().getEmail()); + message.setSubject("PFS - Evaluacion Tecnica"); + message.setText(String.format("Estimado candidato,\n\nGracias por su candidatura. En esta etapa del " + + "proceso de seleccion, usted debe completar " + + "una evaluacion tecnica de programacion en JAVA. La prueba tiene una duracion de 30 minutos y " + + "puede completarla cuando tenga una buena " + + "conexion de internet.\n\n" + + "Haga click aca: " + evaluationLink + "\n\n" + + "Exito!")); + + emailSender.send(message); + log.info("Sent email to {}", exam.getCandidate().getEmail()); + } catch (Exception e) { + log.error("Error sending email to {}", exam.getCandidate().getEmail(), e); + throw e; + } + } + + public List getExams() { + return examRepository.findAll(); + } + + public Exam getExam(final UUID id) { + return examRepository.findById(id).orElse(null); + } + + public Exam startExam(final UUID id) { + final Exam exam = examRepository.findById(id).get(); + + if (exam.isStarted()) { + return exam; + } + + exam.getExamEvents().add(new ExamEvent(Instant.now(), ExamStatus.STARTED)); + + return examRepository.save(exam); + } + + public Submission getNextSubmission(final UUID examId, final UUID currSubmissionId) { + return getNextSubmission(examId, currSubmissionId, true); + } + + public Submission getNextSubmission(final UUID examId, final UUID currSubmissionId, + final boolean checkCompleted) { + final Exam exam = examRepository.findById(examId).get(); + + Exam saved; + if (currSubmissionId == null) { + if (checkCompleted && exam.isCompleted()) { + return null; + } + + final Question firstQuestion = exam.getQuestions().stream().findFirst().get(); + final Optional submissionToReturn = exam.getSubmissions().stream() + .filter(s -> s.getQuestion().equals(firstQuestion)) + .findFirst(); + + if (submissionToReturn.isEmpty()) { + final Submission result = new Submission(firstQuestion, firstQuestion.getContent(), Map.of(), + SubmissionStatus.FAIL, exam); + exam.getSubmissions().add(result); + } + + saved = examRepository.save(exam); + final Optional submissionToReturn2 = saved.getSubmissions().stream() + .filter(s -> s.getQuestion().equals(firstQuestion)) + .findFirst(); + + return submissionToReturn2.get(); + } + + final Submission currSubmission = exam.getSubmissions().stream() + .filter(s -> s.getId().equals(currSubmissionId)) + .findFirst().get(); + final Question currQuestion = currSubmission.getQuestion(); + int idx = exam.getQuestions().indexOf(currQuestion); + + if (idx == exam.getQuestions().size() - 1) { + return null; + } + + final Question nextQuestion = exam.getQuestions().get(idx + 1); + + final Optional submissionToReturn = exam.getSubmissions().stream() + .filter(s -> s.getQuestion().equals(nextQuestion)) + .findFirst(); + + if (submissionToReturn.isEmpty()) { + final Submission result = new Submission(nextQuestion, nextQuestion.getContent(), Map.of(), + SubmissionStatus.FAIL, exam); + exam.getSubmissions().add(result); + } + + saved = examRepository.save(exam); + final Optional submissionToReturn2 = saved.getSubmissions().stream() + .filter(s -> s.getQuestion().equals(nextQuestion)) + .findFirst(); + + return submissionToReturn2.get(); + } + + public Submission getPrevSubmission(final UUID examId, final Submission currSubmission) { + if (currSubmission == null) { + return null; + } + + final Question currQuestion = currSubmission.getQuestion(); + Exam exam = examRepository.findById(examId).get(); + int idx = exam.getQuestions().indexOf(currQuestion); + + if (idx == 0) { + return null; + } + + final Question prevQuestion = exam.getQuestions().get(idx - 1); + + return exam.getSubmissions().stream() + .filter(s -> s.getQuestion().equals(prevQuestion)) + .findFirst().orElseThrow(() -> new IllegalStateException("submission invalid")); + } + + public Exam completeExam(final UUID id) { + Exam exam = examRepository.findById(id).get(); + Optional completed = exam.getExamEvents().stream() + .filter(e -> e.getStatus() == ExamStatus.COMPLETED) + .findFirst(); + + if (completed.isPresent()) { + return exam; + } + + exam.getExamEvents().add(new ExamEvent(Instant.now(), ExamStatus.COMPLETED)); + Exam saved = examRepository.save(exam); + + return saved; + } + + public void saveSubmission(final UUID id, final Submission currSubmission) { + Exam exam = examRepository.findById(id).get(); + final Submission submission = exam.getSubmissions().stream() + .filter(s -> s.getId().equals(currSubmission.getId())) + .findFirst().get(); + + submission.setText(currSubmission.getText()); + Exam saved = examRepository.save(exam); + } + + @Transactional + public Exam saveExam(final Exam exam) { + Candidate merged = entityManager.merge(exam.getCandidate()); + List mergedQuestions = exam.getQuestions().stream().map(entityManager::merge) + .collect(Collectors.toList()); + + exam.setCandidate(merged); + exam.setQuestions(mergedQuestions); + + return examRepository.save(exam); + } +} diff --git a/src/main/java/com/primefactorsolutions/views/EvaluationView.java b/src/main/java/com/primefactorsolutions/views/EvaluationView.java index 84a1a36..9e60275 100644 --- a/src/main/java/com/primefactorsolutions/views/EvaluationView.java +++ b/src/main/java/com/primefactorsolutions/views/EvaluationView.java @@ -5,7 +5,7 @@ import com.hilerio.ace.AceEditor; import com.hilerio.ace.AceMode; import com.hilerio.ace.AceTheme; import com.primefactorsolutions.model.*; -import com.primefactorsolutions.service.AssessmentService; +import com.primefactorsolutions.service.ExamService; import com.primefactorsolutions.service.CompilerService; import com.vaadin.flow.component.*; import com.vaadin.flow.component.button.Button; @@ -60,13 +60,13 @@ import java.util.stream.Collectors; public class EvaluationView extends Main implements HasUrlParameter { private final CompilerService compilerService; - private final AssessmentService assessmentService; + private final ExamService examService; private AceEditor questionEditor = null; private Dialog dialog = null; private Dialog completeDialog = null; private AceEditor result = null; - private Assessment assessment = null; + private Exam exam = null; private Submission currSubmission = null; private Boolean isCompleted = false; @@ -83,9 +83,9 @@ public class EvaluationView extends Main implements HasUrlParameter { private H3 questionTitle = null; private Text questionDescription = null; - public EvaluationView(final CompilerService compilerService, final AssessmentService assessmentService) { + public EvaluationView(final CompilerService compilerService, final ExamService examService) { this.compilerService = compilerService; - this.assessmentService = assessmentService; + this.examService = examService; addClassNames(Display.FLEX, Flex.GROW, Height.FULL); @@ -128,8 +128,8 @@ public class EvaluationView extends Main implements HasUrlParameter { dialog.add(dialogLayout); final Button saveButton = new Button("Guardar y Siguiente", e -> { - this.currSubmission.setResponse(this.questionEditor.getValue()); - this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); + this.currSubmission.setText(this.questionEditor.getValue()); + this.examService.saveSubmission(exam.getId(), this.currSubmission); dialog.close(); goToNext(); }); @@ -156,9 +156,9 @@ public class EvaluationView extends Main implements HasUrlParameter { final Button completeButton = new Button("Terminar", e -> { completeDialog.close(); - this.currSubmission.setResponse(this.questionEditor.getValue()); - this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); - this.assessment = assessmentService.completeAssessment(assessment.getId()); + this.currSubmission.setText(this.questionEditor.getValue()); + this.examService.saveSubmission(exam.getId(), this.currSubmission); + this.exam = examService.completeExam(exam.getId()); goToCompleted(); updateUI(); }); @@ -196,20 +196,20 @@ public class EvaluationView extends Main implements HasUrlParameter { final MenuBar navMenuBar = new MenuBar(); prev = navMenuBar.addItem("Anterior pregunta", (ComponentEventListener>) menuItemClickEvent -> { - this.currSubmission.setResponse(this.questionEditor.getValue()); - this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); - this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), this.currSubmission); + this.currSubmission.setText(this.questionEditor.getValue()); + this.examService.saveSubmission(exam.getId(), this.currSubmission); + this.currSubmission = this.examService.getPrevSubmission(exam.getId(), this.currSubmission); updateUI(); }); next = navMenuBar.addItem("Siguiente pregunta", (ComponentEventListener>) menuItemClickEvent -> { - this.currSubmission.setResponse(this.questionEditor.getValue()); - this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); + this.currSubmission.setText(this.questionEditor.getValue()); + this.examService.saveSubmission(exam.getId(), this.currSubmission); goToNext(); }); reset = navMenuBar.addItem("Reiniciar pregunta (deshacer todos los cambios)", (ComponentEventListener>) menuItemClickEvent -> { - this.currSubmission.setResponse(this.currSubmission.getQuestion().getContent()); + this.currSubmission.setText(this.currSubmission.getQuestion().getContent()); this.questionEditor.setValue(this.currSubmission.getQuestion().getContent()); }); @@ -252,9 +252,9 @@ public class EvaluationView extends Main implements HasUrlParameter { start = new Button("Empezar"); start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); start.addClickListener((ComponentEventListener>) buttonClickEvent -> { - this.assessment = this.assessmentService.startAssessment(this.assessment.getId()); + this.exam = this.examService.startExam(this.exam.getId()); - if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getCandidate().getEmail())) { + if (tf.getValue().trim().equalsIgnoreCase(this.exam.getCandidate().getEmail())) { this.getUI().get().getPage().reload(); } else { Notification notification = new Notification(); @@ -316,7 +316,7 @@ public class EvaluationView extends Main implements HasUrlParameter { } private void goToNext() { - Submission found = this.assessmentService.getNextSubmission(assessment.getId(), + Submission found = this.examService.getNextSubmission(exam.getId(), this.currSubmission.getId()); if (found == null) { @@ -343,9 +343,9 @@ public class EvaluationView extends Main implements HasUrlParameter { timer.setMinutes(true); timer.addTimerEndEvent((ComponentEventListener) timerEndedEvent -> { Notification.show("Tiempo completado.", 5_000, Notification.Position.TOP_CENTER); - this.currSubmission.setResponse(this.questionEditor.getValue()); - this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); - this.assessment = assessmentService.completeAssessment(assessment.getId()); + this.currSubmission.setText(this.questionEditor.getValue()); + this.examService.saveSubmission(exam.getId(), this.currSubmission); + this.exam = examService.completeExam(exam.getId()); goToCompleted(); updateUI(); }); @@ -362,13 +362,13 @@ public class EvaluationView extends Main implements HasUrlParameter { } private void updateUI() { - if (assessment == null || !assessment.isStarted()) { + if (exam == null || !exam.isStarted()) { editorSection.setVisible(false); startSection.setVisible(true); sidebar.setVisible(false); } else { if (currSubmission != null) { - questionEditor.setValue(this.currSubmission.getResponse()); + questionEditor.setValue(this.currSubmission.getText()); questionTitle.setText(this.currSubmission.getQuestion().getTitle()); questionDescription.setText(this.currSubmission.getQuestion().getDescription()); } @@ -377,10 +377,10 @@ public class EvaluationView extends Main implements HasUrlParameter { startSection.setVisible(false); sidebar.setVisible(true); - prev.setEnabled(currSubmission != null && !assessment.isFirst(currSubmission)); - next.setEnabled(currSubmission == null || !assessment.isLast(currSubmission)); + prev.setEnabled(currSubmission != null && !exam.isFirst(currSubmission)); + next.setEnabled(currSubmission == null || !exam.isLast(currSubmission)); - if (this.assessment.isCompleted()) { + if (this.exam.isCompleted()) { goToCompleted(); this.editorSection.setVisible(false); this.sidebar.setVisible(false); @@ -390,15 +390,15 @@ public class EvaluationView extends Main implements HasUrlParameter { if (dl.getChildren().collect(Collectors.toList()).isEmpty()) { dl.add( - createItem("Candidato:", assessment.getCandidate().getEmail()), - createItem("Hora de inicio:", Optional.ofNullable(assessment.getStartingTime()) + createItem("Candidato:", exam.getCandidate().getEmail()), + createItem("Hora de inicio:", Optional.ofNullable(exam.getStartingTime()) .map(t -> ZonedDateTime.ofInstant(t, ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) .orElse("N/A")) ); } - final Long remainingTime = this.assessment.getRemainingTimeSeconds(); + final Long remainingTime = this.exam.getRemainingTimeSeconds(); timer.pause(); timer.setStartTime(remainingTime > 0 ? remainingTime : 3); timer.setMinutes(true); @@ -434,18 +434,18 @@ public class EvaluationView extends Main implements HasUrlParameter { @Override public void setParameter(final BeforeEvent beforeEvent, final String s) { - this.assessment = this.assessmentService.getAssessment(UUID.fromString(s)); + this.exam = this.examService.getExam(UUID.fromString(s)); - if (this.assessment == null) { + if (this.exam == null) { throw new NotFoundException(); } - if (this.assessment.isCompleted()) { + if (this.exam.isCompleted()) { goToCompleted(); } - this.currSubmission = this.assessment.isStarted() - ? this.assessmentService.getNextSubmission(assessment.getId(), null) + this.currSubmission = this.exam.isStarted() + ? this.examService.getNextSubmission(exam.getId(), null) : null; updateUI(); diff --git a/src/main/java/com/primefactorsolutions/views/MainLayout.java b/src/main/java/com/primefactorsolutions/views/MainLayout.java index 076a92b..b6f0ea4 100644 --- a/src/main/java/com/primefactorsolutions/views/MainLayout.java +++ b/src/main/java/com/primefactorsolutions/views/MainLayout.java @@ -4,7 +4,7 @@ import com.primefactorsolutions.model.Employee; import com.primefactorsolutions.views.employee.DocumentsListView; import com.primefactorsolutions.views.employee.EmployeesListView; import com.primefactorsolutions.views.admin.TimeOffListView; -import com.primefactorsolutions.views.assessment.AssessmentsListView; +import com.primefactorsolutions.views.assessment.ExamsListView; import com.primefactorsolutions.views.assessment.CandidatesListView; import com.primefactorsolutions.views.assessment.QuestionsListView; import com.primefactorsolutions.views.timeoff.TimeOffRequestsListView; @@ -148,7 +148,7 @@ public class MainLayout extends AppLayout { SideNavItem recruiting = new SideNavItem("Recruiting"); recruiting.setPrefixComponent(LineAwesomeIcon.BUSINESS_TIME_SOLID.create()); - recruiting.addItem(new SideNavItem("Assessments", AssessmentsListView.class, + recruiting.addItem(new SideNavItem("Exams", ExamsListView.class, LineAwesomeIcon.RIBBON_SOLID.create())); recruiting.addItem(new SideNavItem("Candidates", CandidatesListView.class, LineAwesomeIcon.USER.create())); diff --git a/src/main/java/com/primefactorsolutions/views/SubmissionView.java b/src/main/java/com/primefactorsolutions/views/SubmissionView.java index 639b060..315e554 100644 --- a/src/main/java/com/primefactorsolutions/views/SubmissionView.java +++ b/src/main/java/com/primefactorsolutions/views/SubmissionView.java @@ -3,9 +3,9 @@ package com.primefactorsolutions.views; import com.hilerio.ace.AceEditor; import com.hilerio.ace.AceMode; import com.hilerio.ace.AceTheme; -import com.primefactorsolutions.model.Assessment; +import com.primefactorsolutions.model.Exam; import com.primefactorsolutions.model.Submission; -import com.primefactorsolutions.service.AssessmentService; +import com.primefactorsolutions.service.ExamService; import com.primefactorsolutions.service.CompilerService; import com.vaadin.flow.component.ClickEvent; import com.vaadin.flow.component.ComponentEventListener; @@ -43,11 +43,11 @@ import java.util.stream.Collectors; public class SubmissionView extends Main implements HasUrlParameter { private final CompilerService compilerService; - private final AssessmentService assessmentService; + private final ExamService examService; private AceEditor questionEditor = null; private AceEditor result = null; private Dialog dialog = null; - private Assessment assessment = null; + private Exam exam = null; private Submission currSubmission = null; private MenuItem prev = null; private MenuItem next = null; @@ -56,9 +56,9 @@ public class SubmissionView extends Main implements HasUrlParameter { private Section editorSection = null; private H3 questionTitle = null; - public SubmissionView(final CompilerService compilerService, final AssessmentService assessmentService) { + public SubmissionView(final CompilerService compilerService, final ExamService examService) { this.compilerService = compilerService; - this.assessmentService = assessmentService; + this.examService = examService; addClassNames(Display.FLEX, Flex.GROW, Height.FULL); @@ -130,13 +130,13 @@ public class SubmissionView extends Main implements HasUrlParameter { prev = navMenuBar.addItem("Anterior pregunta", (ComponentEventListener>) menuItemClickEvent -> { log.info(">>> prev"); - this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), + this.currSubmission = this.examService.getPrevSubmission(exam.getId(), this.currSubmission); updateUI(); }); next = navMenuBar.addItem("Siguiente pregunta", (ComponentEventListener>) menuItemClickEvent -> { - this.currSubmission.setResponse(this.questionEditor.getValue()); + this.currSubmission.setText(this.questionEditor.getValue()); goToNext(); }); @@ -185,7 +185,7 @@ public class SubmissionView extends Main implements HasUrlParameter { private void goToNext() { log.info(">>> next"); - Submission found = this.assessmentService.getNextSubmission(assessment.getId(), + Submission found = this.examService.getNextSubmission(exam.getId(), this.currSubmission.getId(), false); if (found != null) { @@ -208,25 +208,25 @@ public class SubmissionView extends Main implements HasUrlParameter { } private void updateUI() { - if (assessment == null || !assessment.isStarted()) { + if (exam == null || !exam.isStarted()) { editorSection.setVisible(false); sidebar.setVisible(false); } else { if (currSubmission != null) { - questionEditor.setValue(this.currSubmission.getResponse()); + questionEditor.setValue(this.currSubmission.getText()); questionTitle.setText(this.currSubmission.getQuestion().getTitle()); } editorSection.setVisible(true); sidebar.setVisible(true); - prev.setEnabled(currSubmission != null && !assessment.isFirst(currSubmission)); - next.setEnabled(currSubmission == null || !assessment.isLast(currSubmission)); + prev.setEnabled(currSubmission != null && !exam.isFirst(currSubmission)); + next.setEnabled(currSubmission == null || !exam.isLast(currSubmission)); if (dl.getChildren().collect(Collectors.toList()).isEmpty()) { dl.add( - createItem("Candidato:", assessment.getCandidate().getEmail()), - createItem("Hora de inicio:", Optional.ofNullable(assessment.getStartingTime()) + createItem("Candidato:", exam.getCandidate().getEmail()), + createItem("Hora de inicio:", Optional.ofNullable(exam.getStartingTime()) .map(t -> ZonedDateTime.ofInstant(t, ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) .orElse("N/A")) @@ -258,14 +258,14 @@ public class SubmissionView extends Main implements HasUrlParameter { @Override public void setParameter(final BeforeEvent beforeEvent, final String s) { - this.assessment = this.assessmentService.getAssessment(UUID.fromString(s)); + this.exam = this.examService.getExam(UUID.fromString(s)); - if (this.assessment == null) { + if (this.exam == null) { throw new NotFoundException(); } - this.currSubmission = this.assessment.isStarted() - ? this.assessmentService.getNextSubmission(assessment.getId(), null, false) + this.currSubmission = this.exam.isStarted() + ? this.examService.getNextSubmission(exam.getId(), null, false) : null; updateUI(); diff --git a/src/main/java/com/primefactorsolutions/views/assessment/CandidateView.java b/src/main/java/com/primefactorsolutions/views/assessment/CandidateView.java index 474beb5..c30625e 100644 --- a/src/main/java/com/primefactorsolutions/views/assessment/CandidateView.java +++ b/src/main/java/com/primefactorsolutions/views/assessment/CandidateView.java @@ -21,7 +21,7 @@ import java.util.UUID; @SpringComponent @Scope("prototype") -@PageTitle("Assessments") +@PageTitle("Candidates") @Route(value = "/candidates", layout = MainLayout.class) @PermitAll public class CandidateView extends BaseEntityForm implements HasUrlParameter { diff --git a/src/main/java/com/primefactorsolutions/views/assessment/AssessmentView.java b/src/main/java/com/primefactorsolutions/views/assessment/ExamView.java similarity index 68% rename from src/main/java/com/primefactorsolutions/views/assessment/AssessmentView.java rename to src/main/java/com/primefactorsolutions/views/assessment/ExamView.java index d8c2ac0..eda8721 100644 --- a/src/main/java/com/primefactorsolutions/views/assessment/AssessmentView.java +++ b/src/main/java/com/primefactorsolutions/views/assessment/ExamView.java @@ -1,9 +1,9 @@ package com.primefactorsolutions.views.assessment; import com.primefactorsolutions.model.Candidate; -import com.primefactorsolutions.model.Assessment; +import com.primefactorsolutions.model.Exam; import com.primefactorsolutions.model.Question; -import com.primefactorsolutions.service.AssessmentService; +import com.primefactorsolutions.service.ExamService; import com.primefactorsolutions.service.QuestionService; import com.primefactorsolutions.service.CandidateService; import com.primefactorsolutions.views.MainLayout; @@ -28,20 +28,20 @@ import java.util.UUID; @SpringComponent @PermitAll @Scope("prototype") -@PageTitle("Assessments") -@Route(value = "/assessments", layout = MainLayout.class) +@PageTitle("Exams") +@Route(value = "/exams", layout = MainLayout.class) @Uses(ComboBox.class) -public class AssessmentView extends BeanValidationForm implements HasUrlParameter { - private final AssessmentService assessmentService; +public class ExamView extends BeanValidationForm implements HasUrlParameter { + private final ExamService examService; private ComboBox candidate = null; private SubListSelector questions = null; - public AssessmentView(final AssessmentService assessmentService, final QuestionService questionService, - final CandidateService candidateService) { - super(Assessment.class); + public ExamView(final ExamService examService, final QuestionService questionService, + final CandidateService candidateService) { + super(Exam.class); - this.assessmentService = assessmentService; + this.examService = examService; candidate = new ComboBox<>("Candidate", candidateService.getCandidates()); candidate.setItemLabelGenerator((ItemLabelGenerator) Candidate::getEmail); @@ -52,8 +52,8 @@ public class AssessmentView extends BeanValidationForm implements Ha questions.setReadOnly(false); questions.setAvailableOptions(questionService.getQuestions()); - setSavedHandler((SavedHandler) assessment -> { - final var saved = this.assessmentService.saveAssessment(assessment); + setSavedHandler((SavedHandler) exam -> { + final var saved = this.examService.saveExam(exam); setEntityWithEnabledSave(saved); }); } @@ -61,11 +61,11 @@ public class AssessmentView extends BeanValidationForm implements Ha @Override public void setParameter(final BeforeEvent beforeEvent, final String s) { if (StringUtils.isNotBlank(s) && !"new".equals(s)) { - final var assessment = assessmentService.getAssessment(UUID.fromString(s)); + final var exam = examService.getExam(UUID.fromString(s)); - setEntityWithEnabledSave(assessment); + setEntityWithEnabledSave(exam); } else { - setEntityWithEnabledSave(new Assessment()); + setEntityWithEnabledSave(new Exam()); } } diff --git a/src/main/java/com/primefactorsolutions/views/assessment/AssessmentsListView.java b/src/main/java/com/primefactorsolutions/views/assessment/ExamsListView.java similarity index 62% rename from src/main/java/com/primefactorsolutions/views/assessment/AssessmentsListView.java rename to src/main/java/com/primefactorsolutions/views/assessment/ExamsListView.java index 59e4132..7720ed2 100644 --- a/src/main/java/com/primefactorsolutions/views/assessment/AssessmentsListView.java +++ b/src/main/java/com/primefactorsolutions/views/assessment/ExamsListView.java @@ -1,7 +1,7 @@ package com.primefactorsolutions.views.assessment; -import com.primefactorsolutions.model.Assessment; -import com.primefactorsolutions.service.AssessmentService; +import com.primefactorsolutions.model.Exam; +import com.primefactorsolutions.service.ExamService; import com.primefactorsolutions.views.BaseView; import com.primefactorsolutions.views.MainLayout; import com.primefactorsolutions.views.SubmissionView; @@ -25,48 +25,48 @@ import org.vaadin.firitin.components.grid.VGrid; @SpringComponent @Scope("prototype") -@PageTitle("Assessments") -@Route(value = "/assessments", layout = MainLayout.class) +@PageTitle("Exams") +@Route(value = "/exams", layout = MainLayout.class) @PermitAll -public class AssessmentsListView extends BaseView { +public class ExamsListView extends BaseView { - public AssessmentsListView(final AuthenticationContext authenticationContext, - final AssessmentService assessmentService) { + public ExamsListView(final AuthenticationContext authenticationContext, + final ExamService examService) { super(authenticationContext); final HorizontalLayout hl = new HorizontalLayout(); - final Button addAssessment = new Button("Add Assessment"); - addAssessment.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - addAssessment.addClickListener(buttonClickEvent -> - getUI().flatMap(ui -> ui.navigate(AssessmentView.class, "new"))); - hl.add(addAssessment); + final Button addExam = new Button("Add Exam"); + addExam.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + addExam.addClickListener(buttonClickEvent -> + getUI().flatMap(ui -> ui.navigate(ExamView.class, "new"))); + hl.add(addExam); - final VGrid grid = new VGrid<>(Assessment.class); + final VGrid grid = new VGrid<>(Exam.class); grid.setColumns("id", "candidate.email"); - final Grid.Column statusColumn = grid.addColumn(assessment -> - assessment.getAssessmentEvents().isEmpty() + final Grid.Column statusColumn = grid.addColumn(exam -> + exam.getExamEvents().isEmpty() ? "N/A" - : assessment.getAssessmentEvents().getLast().getStatus().name()); + : exam.getExamEvents().getLast().getStatus().name()); statusColumn.setHeader("Status"); - grid.addComponentColumn(assessment -> MenuBarUtils.menuBar( + grid.addComponentColumn(exam -> MenuBarUtils.menuBar( Pair.of("View", __ -> - getUI().flatMap(ui -> ui.navigate(SubmissionView.class, assessment.getId().toString()))), + getUI().flatMap(ui -> ui.navigate(SubmissionView.class, exam.getId().toString()))), Pair.of("Copy", __ -> ClientsideClipboard.writeToClipboard( - String.format("email: %s link: https://careers.primefactorsolutions.com/evaluation/%s", - assessment.getCandidate().getEmail(), - assessment.getId()))), + String.format("email: %s link: https://intra.primefactorsolutions.com/evaluation/%s", + exam.getCandidate().getEmail(), + exam.getId()))), Pair.of("Email", __ -> { ConfirmDialog dialog = new ConfirmDialog(); dialog.setHeader("Send Link Email"); dialog.setText(String.format("Enviar link por email al candidato %s?", - assessment.getCandidate().getEmail())); + exam.getCandidate().getEmail())); dialog.setCancelable(true); dialog.setConfirmText("Enviar"); dialog.setConfirmButtonTheme("primary"); dialog.addConfirmListener(confirmEvent -> { try { - assessmentService.sendEmail(assessment); + examService.sendEmail(exam); } catch (Exception e) { Notification.show("Error sending email: " + e.getMessage(), 10_000, Notification.Position.TOP_CENTER); @@ -76,7 +76,7 @@ public class AssessmentsListView extends BaseView { }) )); - grid.setDataProvider(new ListDataProvider<>(assessmentService.getAssessments())); + grid.setDataProvider(new ListDataProvider<>(examService.getExams())); grid.setAllRowsVisible(true); getCurrentPageLayout().add(hl, grid); diff --git a/src/main/java/com/primefactorsolutions/views/assessment/QuestionView.java b/src/main/java/com/primefactorsolutions/views/assessment/QuestionView.java index 5496c20..aa5c22f 100644 --- a/src/main/java/com/primefactorsolutions/views/assessment/QuestionView.java +++ b/src/main/java/com/primefactorsolutions/views/assessment/QuestionView.java @@ -20,7 +20,7 @@ import java.util.UUID; @SpringComponent @Scope("prototype") -@PageTitle("Assessments") +@PageTitle("Questions") @Route(value = "/questions", layout = MainLayout.class) @PermitAll public class QuestionView extends BaseEntityForm implements HasUrlParameter { diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index d53a0c1..baa4373 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -3,10 +3,10 @@ insert into candidate (id, version, email) values ('23471ab3-f639-4d2b-9541-7227 insert into question (id, version, content, title, description) values ('a7e00ff8-da41-4624-b31c-1b13c3f2e3ae', 1, 'foo bar', 'q1', 'lorem ipsum'); insert into question (id, version, content, title, description) values ('8a4b213c-ca81-4c38-b56d-d7028c2dde88', 1, 'foo buzz', 'q2', 'lorem ipsum'); -insert into assessment (id, version, candidate_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 1, '23471ab3-f639-4d2b-9541-7227f4ea7ee6'); +insert into exam (id, version, candidate_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 1, '23471ab3-f639-4d2b-9541-7227f4ea7ee6'); -insert into assessment_questions (assessment_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 'a7e00ff8-da41-4624-b31c-1b13c3f2e3ae'); -insert into assessment_questions (assessment_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', '8a4b213c-ca81-4c38-b56d-d7028c2dde88'); +insert into exam_questions (exam_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 'a7e00ff8-da41-4624-b31c-1b13c3f2e3ae'); +insert into exam_questions (exam_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', '8a4b213c-ca81-4c38-b56d-d7028c2dde88'); insert into team (id, version, name) values ('b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 1, 'ABC'); insert into team (id, version, name) values ('6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 1, 'XYZ'); diff --git a/src/main/resources/questions/foobar.java.txt b/src/main/resources/questions/foobar.java.txt index cd1e81d..0d0e48e 100644 --- a/src/main/resources/questions/foobar.java.txt +++ b/src/main/resources/questions/foobar.java.txt @@ -2,7 +2,7 @@ package com.primefactorsolutions; import java.util.*; import java.lang.reflect.*; -import java.util.stream.Collectors; +import java.util.stream.*; public class TestClass { @@ -12,9 +12,9 @@ public class TestClass { return "TODO"; } - // ------------- NO MODIFICAR DESDE ESTA LINEA -------------------- + // PRUEBAS UNITARIAS public static class Tests { public static Map.Entry testNInvalido() { boolean result = getFooBar(-1).equals(""); @@ -29,7 +29,7 @@ public class TestClass { } } - public static Map run() { + public static Map run () { Map results = Arrays.stream(Tests.class.getDeclaredMethods()) .map(m -> { try { diff --git a/src/main/resources/questions/palindroma.java.txt b/src/main/resources/questions/palindroma.java.txt index 601c837..1102dfe 100644 --- a/src/main/resources/questions/palindroma.java.txt +++ b/src/main/resources/questions/palindroma.java.txt @@ -2,48 +2,48 @@ package com.primefactorsolutions; import java.util.*; import java.lang.reflect.*; -import java.util.stream.Collectors; +import java.util.stream.*; public class TestClass { // ------------ IMPLEMENTAR AQUI -------------------------------- - public static String getPalindroma(String s) { + public static String getPalindromo(String s) { return "TODO"; } - // ------------- NO MODIFICAR DESDE ESTA LINEA -------------------- + // PRUEBAS UNITARIAS public static class Tests { - public static Map.Entry testPalindromaValido() { - boolean result = getPalindroma("Ab").equals("ABBA"); + public static Map.Entry testPalindromoValido() { + boolean result = getPalindromo("Ab").equals("ABBA"); - return Map.entry("palindrome es valido", result); + return Map.entry("palindromo es valido", result); } - public static Map.Entry testPalindromaVacio() { - boolean result = getPalindroma("").equals(""); + public static Map.Entry testPalindromoVacio() { + boolean result = getPalindromo("").equals(""); - return Map.entry("palindrome vacio es valido", result); + return Map.entry("palindromo vacio es valido", result); } - public static Map.Entry testPalindromaUnico() { - boolean result = getPalindroma("z").equals("Z"); + public static Map.Entry testPalindromoUnico() { + boolean result = getPalindromo("z").equals("Z"); - return Map.entry("palindrome un solo caracter es valido", result); + return Map.entry("palindromo un solo caracter es valido", result); } - public static Map.Entry testPalindromaUnico() { - boolean result = getPalindroma("abba").equals("ABBA"); + public static Map.Entry testPalindromoEntrada() { + boolean result = getPalindromo("abba").equals("ABBA"); - return Map.entry("palindrome un solo caracter es valido", result); + return Map.entry("palindromo en entrada es valido", result); } - public static Map.Entry testPalindromaNull() { - boolean result = getPalindroma(null) == null; + public static Map.Entry testPalindromoNull() { + boolean result = getPalindromo(null) == null; - return Map.entry("palindrome null es valido", result); + return Map.entry("palindromo null es valido", result); } } diff --git a/src/test/java/com/primefactorsolutions/service/AssessmentServiceTests.java b/src/test/java/com/primefactorsolutions/service/AssessmentServiceTests.java deleted file mode 100644 index 6199445..0000000 --- a/src/test/java/com/primefactorsolutions/service/AssessmentServiceTests.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.primefactorsolutions.service; - -import com.primefactorsolutions.model.Assessment; -import com.primefactorsolutions.model.AssessmentEvent; -import com.primefactorsolutions.model.AssessmentStatus; -import com.primefactorsolutions.repositories.AssessmentRepository; -import jakarta.persistence.EntityManager; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mail.javamail.JavaMailSender; - -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class AssessmentServiceTests { - - @Mock - private AssessmentRepository assessmentRepository; - @Mock - private EntityManager entityManager; - @Mock - private JavaMailSender emailSender; - @InjectMocks - private AssessmentService assessmentService; - - @Test - public void testAlreadyStartedAssessment() { - final var aid = UUID.randomUUID(); - final var assessment = new Assessment(); - assessment.setId(aid); - assessment.setAssessmentEvents(List.of(new AssessmentEvent(Instant.now(), AssessmentStatus.STARTED))); - - when(assessmentRepository.findById(eq(aid))) - .thenReturn(Optional.of(assessment)); - - final var started = assessmentService.startAssessment(aid); - - Assertions.assertThat(assessment).isEqualTo(started); - } -} diff --git a/src/test/java/com/primefactorsolutions/service/ExamServiceTests.java b/src/test/java/com/primefactorsolutions/service/ExamServiceTests.java new file mode 100644 index 0000000..171c364 --- /dev/null +++ b/src/test/java/com/primefactorsolutions/service/ExamServiceTests.java @@ -0,0 +1,50 @@ +package com.primefactorsolutions.service; + +import com.primefactorsolutions.model.Exam; +import com.primefactorsolutions.model.ExamEvent; +import com.primefactorsolutions.model.ExamStatus; +import com.primefactorsolutions.repositories.ExamRepository; +import jakarta.persistence.EntityManager; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mail.javamail.JavaMailSender; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class ExamServiceTests { + + @Mock + private ExamRepository examRepository; + @Mock + private EntityManager entityManager; + @Mock + private JavaMailSender emailSender; + @InjectMocks + private ExamService examService; + + @Test + public void testAlreadyStartedExam() { + final var aid = UUID.randomUUID(); + final var exam = new Exam(); + exam.setId(aid); + exam.setExamEvents(List.of(new ExamEvent(Instant.now(), ExamStatus.STARTED))); + + when(examRepository.findById(eq(aid))) + .thenReturn(Optional.of(exam)); + + final var started = examService.startExam(aid); + + Assertions.assertThat(exam).isEqualTo(started); + } +}