rename assessment to exam
Some checks failed
Builder / Build-Project (push) Failing after 33s

This commit is contained in:
alex 2025-01-05 14:50:31 -05:00
parent 9188913cf4
commit 084a612a1f
27 changed files with 485 additions and 428 deletions

View File

@ -4,7 +4,7 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@f0rce/ace-widget": "1.0.2", "@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-component-factory/vcf-pdf-viewer": "2.0.1",
"@vaadin/bundles": "24.5.1", "@vaadin/bundles": "24.5.1",
"@vaadin/common-frontend": "0.0.19", "@vaadin/common-frontend": "0.0.19",
@ -19,29 +19,30 @@
"@vaadin/vaadin-usage-statistics": "2.1.3", "@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0", "construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"lit": "3.1.4", "lit": "3.2.1",
"print-js": "1.6.0", "print-js": "1.6.0",
"proj4": "2.12.1", "proj4": "2.12.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-router-dom": "6.23.1" "react-router-dom": "6.26.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-react": "7.24.7", "@babel/preset-react": "7.25.7",
"@rollup/plugin-replace": "5.0.7", "@preact/signals-react-transform": "0.4.0",
"@rollup/pluginutils": "5.1.0", "@rollup/plugin-replace": "6.0.1",
"@types/react": "18.3.3", "@rollup/pluginutils": "5.1.2",
"@types/react-dom": "18.3.0", "@types/react": "18.3.11",
"@vitejs/plugin-react": "4.3.1", "@types/react-dom": "18.3.1",
"async": "3.2.5", "@vitejs/plugin-react": "4.3.3",
"glob": "10.4.1", "async": "3.2.6",
"glob": "10.4.5",
"rollup-plugin-brotli": "3.1.0", "rollup-plugin-brotli": "3.1.0",
"rollup-plugin-visualizer": "5.12.0", "rollup-plugin-visualizer": "5.12.0",
"strip-css-comments": "5.0.0", "strip-css-comments": "5.0.0",
"transform-ast": "2.4.4", "transform-ast": "2.4.4",
"typescript": "5.4.5", "typescript": "5.6.3",
"vite": "5.3.3", "vite": "5.4.9",
"vite-plugin-checker": "0.6.4", "vite-plugin-checker": "0.8.0",
"workbox-build": "7.1.1", "workbox-build": "7.1.1",
"workbox-core": "7.1.0", "workbox-core": "7.1.0",
"workbox-precaching": "7.1.0" "workbox-precaching": "7.1.0"
@ -49,7 +50,7 @@
"vaadin": { "vaadin": {
"dependencies": { "dependencies": {
"@f0rce/ace-widget": "1.0.2", "@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-component-factory/vcf-pdf-viewer": "2.0.1",
"@vaadin/bundles": "24.5.1", "@vaadin/bundles": "24.5.1",
"@vaadin/common-frontend": "0.0.19", "@vaadin/common-frontend": "0.0.19",
@ -64,34 +65,35 @@
"@vaadin/vaadin-usage-statistics": "2.1.3", "@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0", "construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"lit": "3.1.4", "lit": "3.2.1",
"print-js": "1.6.0", "print-js": "1.6.0",
"proj4": "2.12.1", "proj4": "2.12.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-router-dom": "6.23.1" "react-router-dom": "6.26.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-react": "7.24.7", "@babel/preset-react": "7.25.7",
"@rollup/plugin-replace": "5.0.7", "@preact/signals-react-transform": "0.4.0",
"@rollup/pluginutils": "5.1.0", "@rollup/plugin-replace": "6.0.1",
"@types/react": "18.3.3", "@rollup/pluginutils": "5.1.2",
"@types/react-dom": "18.3.0", "@types/react": "18.3.11",
"@vitejs/plugin-react": "4.3.1", "@types/react-dom": "18.3.1",
"async": "3.2.5", "@vitejs/plugin-react": "4.3.3",
"glob": "10.4.1", "async": "3.2.6",
"glob": "10.4.5",
"rollup-plugin-brotli": "3.1.0", "rollup-plugin-brotli": "3.1.0",
"rollup-plugin-visualizer": "5.12.0", "rollup-plugin-visualizer": "5.12.0",
"strip-css-comments": "5.0.0", "strip-css-comments": "5.0.0",
"transform-ast": "2.4.4", "transform-ast": "2.4.4",
"typescript": "5.4.5", "typescript": "5.6.3",
"vite": "5.3.3", "vite": "5.4.9",
"vite-plugin-checker": "0.6.4", "vite-plugin-checker": "0.8.0",
"workbox-build": "7.1.1", "workbox-build": "7.1.1",
"workbox-core": "7.1.0", "workbox-core": "7.1.0",
"workbox-precaching": "7.1.0" "workbox-precaching": "7.1.0"
}, },
"hash": "1a0f17d48b329307b5862bc57499307d1b89f7d89260121c2b7189f76957c436" "hash": "2dc40a4f634ae025081ca2239cba00b14a35fe94ab78ac0a4dd3023d882081d5"
}, },
"overrides": { "overrides": {
"@vaadin/bundles": "$@vaadin/bundles", "@vaadin/bundles": "$@vaadin/bundles",

View File

@ -6,8 +6,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
@Entity @Entity
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@ -16,7 +14,4 @@ import java.util.List;
public class Candidate extends BaseEntity { public class Candidate extends BaseEntity {
@Column(unique = true) @Column(unique = true)
private String email; private String email;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "candidate")
private List<Assessment> assessments;
} }

View File

@ -0,0 +1,8 @@
package com.primefactorsolutions.model;
public enum EmployeePosition {
JUNIOR_DEV,
JUNIOR_QA,
SENIOR_DEV,
SENIOR_QA
}

View File

@ -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<SkillEvaluation> skillEvaluations = Lists.newArrayList();
private Integer points;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "evaluation")
private List<Exam> exams;
}

View File

@ -15,30 +15,33 @@ import java.util.Optional;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Assessment extends BaseEntity { public class Exam extends BaseEntity {
@ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL}) @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")) inverseJoinColumns = @JoinColumn(name = "question_id"))
private List<Question> questions = new ArrayList<>(); private List<Question> questions = new ArrayList<>();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "assessment", cascade = {CascadeType.ALL}) @OneToMany(fetch = FetchType.EAGER, mappedBy = "exam", cascade = {CascadeType.ALL})
private List<Submission> submissions = new ArrayList<>(); private List<Submission> submissions = new ArrayList<>();
@ManyToOne @ManyToOne
private Candidate candidate; private Candidate candidate;
@ManyToOne
private Evaluation evaluation;
@OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL}) @OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
@JoinColumn(name = "ASSESSMENT_ID") @JoinColumn(name = "EXAM_ID")
private List<AssessmentEvent> assessmentEvents = new ArrayList<>(); private List<ExamEvent> examEvents = new ArrayList<>();
public Submission getCurrentSubmission() { public Submission getCurrentSubmission() {
return submissions.getLast(); return submissions.getLast();
} }
public Long getRemainingTimeSeconds() { public Long getRemainingTimeSeconds() {
final Optional<Instant> started = assessmentEvents.stream() final Optional<Instant> started = examEvents.stream()
.filter(e -> e.getStatus() == AssessmentStatus.STARTED) .filter(e -> e.getStatus() == ExamStatus.STARTED)
.map(AssessmentEvent::getTimestamp) .map(ExamEvent::getTimestamp)
.findFirst(); .findFirst();
final Integer totalTimeMinutes = questions.stream() final Integer totalTimeMinutes = questions.stream()
@ -52,24 +55,24 @@ public class Assessment extends BaseEntity {
} }
public Instant getStartingTime() { public Instant getStartingTime() {
final Optional<Instant> started = assessmentEvents.stream() final Optional<Instant> started = examEvents.stream()
.filter(e -> e.getStatus() == AssessmentStatus.STARTED) .filter(e -> e.getStatus() == ExamStatus.STARTED)
.map(AssessmentEvent::getTimestamp) .map(ExamEvent::getTimestamp)
.findFirst(); .findFirst();
return started.orElse(null); return started.orElse(null);
} }
public boolean isCompleted() { public boolean isCompleted() {
return assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.COMPLETED) return examEvents.stream().filter(e -> e.getStatus() == ExamStatus.COMPLETED)
.map(AssessmentEvent::getTimestamp) .map(ExamEvent::getTimestamp)
.findFirst() .findFirst()
.isPresent(); .isPresent();
} }
public boolean isStarted() { public boolean isStarted() {
return assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.STARTED) return examEvents.stream().filter(e -> e.getStatus() == ExamStatus.STARTED)
.map(AssessmentEvent::getTimestamp) .map(ExamEvent::getTimestamp)
.findFirst() .findFirst()
.isPresent(); .isPresent();
} }

View File

@ -13,7 +13,7 @@ import java.time.Instant;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class AssessmentEvent extends BaseEntity { public class ExamEvent extends BaseEntity {
private Instant timestamp; private Instant timestamp;
private AssessmentStatus status; private ExamStatus status;
} }

View File

@ -1,6 +1,6 @@
package com.primefactorsolutions.model; package com.primefactorsolutions.model;
public enum AssessmentStatus { public enum ExamStatus {
CREATED, CREATED,
STARTED, STARTED,
COMPLETED COMPLETED

View File

@ -0,0 +1,4 @@
package com.primefactorsolutions.model;
public record SkillEvaluation (SkillType skillType, SkillLevel level, String comments) {
}

View File

@ -0,0 +1,8 @@
package com.primefactorsolutions.model;
public enum SkillLevel {
NEEDS_IMPROVEMENT,
FAIR,
VERY_GOOD,
EXCELLENT
}

View File

@ -0,0 +1,8 @@
package com.primefactorsolutions.model;
public enum SkillType {
TECHNICAL_KNOWLEDGE,
ASSERTIVENESS,
COMPLETENESS_AND_CONCISENESS,
COMMUNICATION
}

View File

@ -23,7 +23,7 @@ public class Submission extends BaseEntity {
private Question question; private Question question;
@Lob @Lob
private String response; private String text;
@Type(JsonType.class) @Type(JsonType.class)
@Column(columnDefinition = "json") @Column(columnDefinition = "json")
@ -32,5 +32,5 @@ public class Submission extends BaseEntity {
private SubmissionStatus submissionStatus; private SubmissionStatus submissionStatus;
@ManyToOne @ManyToOne
private Assessment assessment; private Exam exam;
} }

View File

@ -1,9 +1,9 @@
package com.primefactorsolutions.repositories; package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.Assessment; import com.primefactorsolutions.model.Exam;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID; import java.util.UUID;
public interface AssessmentRepository extends JpaRepository<Assessment, UUID> { public interface ExamRepository extends JpaRepository<Exam, UUID> {
} }

View File

@ -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<Assessment> 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<Submission> 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<Submission> 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<Submission> 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<Submission> 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<AssessmentEvent> 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<Question> mergedQuestions = assessment.getQuestions().stream().map(entityManager::merge)
.collect(Collectors.toList());
assessment.setCandidate(merged);
assessment.setQuestions(mergedQuestions);
return assessmentRepository.save(assessment);
}
}

View File

@ -13,8 +13,8 @@ import java.util.UUID;
public class CandidateService { public class CandidateService {
private final CandidateRepository candidateRepository; private final CandidateRepository candidateRepository;
public Candidate createOrUpdate(final Candidate assessment) { public Candidate createOrUpdate(final Candidate candidate) {
final Candidate saved = candidateRepository.save(assessment); final Candidate saved = candidateRepository.save(candidate);
return saved; return saved;
} }

View File

@ -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<Exam> 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<Submission> 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<Submission> 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<Submission> 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<Submission> 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<ExamEvent> 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<Question> mergedQuestions = exam.getQuestions().stream().map(entityManager::merge)
.collect(Collectors.toList());
exam.setCandidate(merged);
exam.setQuestions(mergedQuestions);
return examRepository.save(exam);
}
}

View File

@ -5,7 +5,7 @@ import com.hilerio.ace.AceEditor;
import com.hilerio.ace.AceMode; import com.hilerio.ace.AceMode;
import com.hilerio.ace.AceTheme; import com.hilerio.ace.AceTheme;
import com.primefactorsolutions.model.*; import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.AssessmentService; import com.primefactorsolutions.service.ExamService;
import com.primefactorsolutions.service.CompilerService; import com.primefactorsolutions.service.CompilerService;
import com.vaadin.flow.component.*; import com.vaadin.flow.component.*;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
@ -60,13 +60,13 @@ import java.util.stream.Collectors;
public class EvaluationView extends Main implements HasUrlParameter<String> { public class EvaluationView extends Main implements HasUrlParameter<String> {
private final CompilerService compilerService; private final CompilerService compilerService;
private final AssessmentService assessmentService; private final ExamService examService;
private AceEditor questionEditor = null; private AceEditor questionEditor = null;
private Dialog dialog = null; private Dialog dialog = null;
private Dialog completeDialog = null; private Dialog completeDialog = null;
private AceEditor result = null; private AceEditor result = null;
private Assessment assessment = null; private Exam exam = null;
private Submission currSubmission = null; private Submission currSubmission = null;
private Boolean isCompleted = false; private Boolean isCompleted = false;
@ -83,9 +83,9 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
private H3 questionTitle = null; private H3 questionTitle = null;
private Text questionDescription = 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.compilerService = compilerService;
this.assessmentService = assessmentService; this.examService = examService;
addClassNames(Display.FLEX, Flex.GROW, Height.FULL); addClassNames(Display.FLEX, Flex.GROW, Height.FULL);
@ -128,8 +128,8 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
dialog.add(dialogLayout); dialog.add(dialogLayout);
final Button saveButton = new Button("Guardar y Siguiente", e -> { final Button saveButton = new Button("Guardar y Siguiente", e -> {
this.currSubmission.setResponse(this.questionEditor.getValue()); this.currSubmission.setText(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); this.examService.saveSubmission(exam.getId(), this.currSubmission);
dialog.close(); dialog.close();
goToNext(); goToNext();
}); });
@ -156,9 +156,9 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
final Button completeButton = new Button("Terminar", e -> { final Button completeButton = new Button("Terminar", e -> {
completeDialog.close(); completeDialog.close();
this.currSubmission.setResponse(this.questionEditor.getValue()); this.currSubmission.setText(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); this.examService.saveSubmission(exam.getId(), this.currSubmission);
this.assessment = assessmentService.completeAssessment(assessment.getId()); this.exam = examService.completeExam(exam.getId());
goToCompleted(); goToCompleted();
updateUI(); updateUI();
}); });
@ -196,20 +196,20 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
final MenuBar navMenuBar = new MenuBar(); final MenuBar navMenuBar = new MenuBar();
prev = navMenuBar.addItem("Anterior pregunta", prev = navMenuBar.addItem("Anterior pregunta",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> { (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setResponse(this.questionEditor.getValue()); this.currSubmission.setText(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); this.examService.saveSubmission(exam.getId(), this.currSubmission);
this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), this.currSubmission); this.currSubmission = this.examService.getPrevSubmission(exam.getId(), this.currSubmission);
updateUI(); updateUI();
}); });
next = navMenuBar.addItem("Siguiente pregunta", next = navMenuBar.addItem("Siguiente pregunta",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> { (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setResponse(this.questionEditor.getValue()); this.currSubmission.setText(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); this.examService.saveSubmission(exam.getId(), this.currSubmission);
goToNext(); goToNext();
}); });
reset = navMenuBar.addItem("Reiniciar pregunta (deshacer todos los cambios)", reset = navMenuBar.addItem("Reiniciar pregunta (deshacer todos los cambios)",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> { (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setResponse(this.currSubmission.getQuestion().getContent()); this.currSubmission.setText(this.currSubmission.getQuestion().getContent());
this.questionEditor.setValue(this.currSubmission.getQuestion().getContent()); this.questionEditor.setValue(this.currSubmission.getQuestion().getContent());
}); });
@ -252,9 +252,9 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
start = new Button("Empezar"); start = new Button("Empezar");
start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
start.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> { start.addClickListener((ComponentEventListener<ClickEvent<Button>>) 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(); this.getUI().get().getPage().reload();
} else { } else {
Notification notification = new Notification(); Notification notification = new Notification();
@ -316,7 +316,7 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
} }
private void goToNext() { private void goToNext() {
Submission found = this.assessmentService.getNextSubmission(assessment.getId(), Submission found = this.examService.getNextSubmission(exam.getId(),
this.currSubmission.getId()); this.currSubmission.getId());
if (found == null) { if (found == null) {
@ -343,9 +343,9 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
timer.setMinutes(true); timer.setMinutes(true);
timer.addTimerEndEvent((ComponentEventListener<SimpleTimer.TimerEndedEvent>) timerEndedEvent -> { timer.addTimerEndEvent((ComponentEventListener<SimpleTimer.TimerEndedEvent>) timerEndedEvent -> {
Notification.show("Tiempo completado.", 5_000, Notification.Position.TOP_CENTER); Notification.show("Tiempo completado.", 5_000, Notification.Position.TOP_CENTER);
this.currSubmission.setResponse(this.questionEditor.getValue()); this.currSubmission.setText(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission); this.examService.saveSubmission(exam.getId(), this.currSubmission);
this.assessment = assessmentService.completeAssessment(assessment.getId()); this.exam = examService.completeExam(exam.getId());
goToCompleted(); goToCompleted();
updateUI(); updateUI();
}); });
@ -362,13 +362,13 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
} }
private void updateUI() { private void updateUI() {
if (assessment == null || !assessment.isStarted()) { if (exam == null || !exam.isStarted()) {
editorSection.setVisible(false); editorSection.setVisible(false);
startSection.setVisible(true); startSection.setVisible(true);
sidebar.setVisible(false); sidebar.setVisible(false);
} else { } else {
if (currSubmission != null) { if (currSubmission != null) {
questionEditor.setValue(this.currSubmission.getResponse()); questionEditor.setValue(this.currSubmission.getText());
questionTitle.setText(this.currSubmission.getQuestion().getTitle()); questionTitle.setText(this.currSubmission.getQuestion().getTitle());
questionDescription.setText(this.currSubmission.getQuestion().getDescription()); questionDescription.setText(this.currSubmission.getQuestion().getDescription());
} }
@ -377,10 +377,10 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
startSection.setVisible(false); startSection.setVisible(false);
sidebar.setVisible(true); sidebar.setVisible(true);
prev.setEnabled(currSubmission != null && !assessment.isFirst(currSubmission)); prev.setEnabled(currSubmission != null && !exam.isFirst(currSubmission));
next.setEnabled(currSubmission == null || !assessment.isLast(currSubmission)); next.setEnabled(currSubmission == null || !exam.isLast(currSubmission));
if (this.assessment.isCompleted()) { if (this.exam.isCompleted()) {
goToCompleted(); goToCompleted();
this.editorSection.setVisible(false); this.editorSection.setVisible(false);
this.sidebar.setVisible(false); this.sidebar.setVisible(false);
@ -390,15 +390,15 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
if (dl.getChildren().collect(Collectors.toList()).isEmpty()) { if (dl.getChildren().collect(Collectors.toList()).isEmpty()) {
dl.add( dl.add(
createItem("Candidato:", assessment.getCandidate().getEmail()), createItem("Candidato:", exam.getCandidate().getEmail()),
createItem("Hora de inicio:", Optional.ofNullable(assessment.getStartingTime()) createItem("Hora de inicio:", Optional.ofNullable(exam.getStartingTime())
.map(t -> ZonedDateTime.ofInstant(t, .map(t -> ZonedDateTime.ofInstant(t,
ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
.orElse("N/A")) .orElse("N/A"))
); );
} }
final Long remainingTime = this.assessment.getRemainingTimeSeconds(); final Long remainingTime = this.exam.getRemainingTimeSeconds();
timer.pause(); timer.pause();
timer.setStartTime(remainingTime > 0 ? remainingTime : 3); timer.setStartTime(remainingTime > 0 ? remainingTime : 3);
timer.setMinutes(true); timer.setMinutes(true);
@ -434,18 +434,18 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
@Override @Override
public void setParameter(final BeforeEvent beforeEvent, final String s) { 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(); throw new NotFoundException();
} }
if (this.assessment.isCompleted()) { if (this.exam.isCompleted()) {
goToCompleted(); goToCompleted();
} }
this.currSubmission = this.assessment.isStarted() this.currSubmission = this.exam.isStarted()
? this.assessmentService.getNextSubmission(assessment.getId(), null) ? this.examService.getNextSubmission(exam.getId(), null)
: null; : null;
updateUI(); updateUI();

View File

@ -4,7 +4,7 @@ import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.views.employee.DocumentsListView; import com.primefactorsolutions.views.employee.DocumentsListView;
import com.primefactorsolutions.views.employee.EmployeesListView; import com.primefactorsolutions.views.employee.EmployeesListView;
import com.primefactorsolutions.views.admin.TimeOffListView; 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.CandidatesListView;
import com.primefactorsolutions.views.assessment.QuestionsListView; import com.primefactorsolutions.views.assessment.QuestionsListView;
import com.primefactorsolutions.views.timeoff.TimeOffRequestsListView; import com.primefactorsolutions.views.timeoff.TimeOffRequestsListView;
@ -148,7 +148,7 @@ public class MainLayout extends AppLayout {
SideNavItem recruiting = new SideNavItem("Recruiting"); SideNavItem recruiting = new SideNavItem("Recruiting");
recruiting.setPrefixComponent(LineAwesomeIcon.BUSINESS_TIME_SOLID.create()); 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())); LineAwesomeIcon.RIBBON_SOLID.create()));
recruiting.addItem(new SideNavItem("Candidates", CandidatesListView.class, recruiting.addItem(new SideNavItem("Candidates", CandidatesListView.class,
LineAwesomeIcon.USER.create())); LineAwesomeIcon.USER.create()));

View File

@ -3,9 +3,9 @@ package com.primefactorsolutions.views;
import com.hilerio.ace.AceEditor; import com.hilerio.ace.AceEditor;
import com.hilerio.ace.AceMode; import com.hilerio.ace.AceMode;
import com.hilerio.ace.AceTheme; import com.hilerio.ace.AceTheme;
import com.primefactorsolutions.model.Assessment; import com.primefactorsolutions.model.Exam;
import com.primefactorsolutions.model.Submission; import com.primefactorsolutions.model.Submission;
import com.primefactorsolutions.service.AssessmentService; import com.primefactorsolutions.service.ExamService;
import com.primefactorsolutions.service.CompilerService; import com.primefactorsolutions.service.CompilerService;
import com.vaadin.flow.component.ClickEvent; import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.ComponentEventListener;
@ -43,11 +43,11 @@ import java.util.stream.Collectors;
public class SubmissionView extends Main implements HasUrlParameter<String> { public class SubmissionView extends Main implements HasUrlParameter<String> {
private final CompilerService compilerService; private final CompilerService compilerService;
private final AssessmentService assessmentService; private final ExamService examService;
private AceEditor questionEditor = null; private AceEditor questionEditor = null;
private AceEditor result = null; private AceEditor result = null;
private Dialog dialog = null; private Dialog dialog = null;
private Assessment assessment = null; private Exam exam = null;
private Submission currSubmission = null; private Submission currSubmission = null;
private MenuItem prev = null; private MenuItem prev = null;
private MenuItem next = null; private MenuItem next = null;
@ -56,9 +56,9 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
private Section editorSection = null; private Section editorSection = null;
private H3 questionTitle = 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.compilerService = compilerService;
this.assessmentService = assessmentService; this.examService = examService;
addClassNames(Display.FLEX, Flex.GROW, Height.FULL); addClassNames(Display.FLEX, Flex.GROW, Height.FULL);
@ -130,13 +130,13 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
prev = navMenuBar.addItem("Anterior pregunta", prev = navMenuBar.addItem("Anterior pregunta",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> { (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
log.info(">>> prev"); log.info(">>> prev");
this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), this.currSubmission = this.examService.getPrevSubmission(exam.getId(),
this.currSubmission); this.currSubmission);
updateUI(); updateUI();
}); });
next = navMenuBar.addItem("Siguiente pregunta", next = navMenuBar.addItem("Siguiente pregunta",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> { (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setResponse(this.questionEditor.getValue()); this.currSubmission.setText(this.questionEditor.getValue());
goToNext(); goToNext();
}); });
@ -185,7 +185,7 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
private void goToNext() { private void goToNext() {
log.info(">>> next"); log.info(">>> next");
Submission found = this.assessmentService.getNextSubmission(assessment.getId(), Submission found = this.examService.getNextSubmission(exam.getId(),
this.currSubmission.getId(), false); this.currSubmission.getId(), false);
if (found != null) { if (found != null) {
@ -208,25 +208,25 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
} }
private void updateUI() { private void updateUI() {
if (assessment == null || !assessment.isStarted()) { if (exam == null || !exam.isStarted()) {
editorSection.setVisible(false); editorSection.setVisible(false);
sidebar.setVisible(false); sidebar.setVisible(false);
} else { } else {
if (currSubmission != null) { if (currSubmission != null) {
questionEditor.setValue(this.currSubmission.getResponse()); questionEditor.setValue(this.currSubmission.getText());
questionTitle.setText(this.currSubmission.getQuestion().getTitle()); questionTitle.setText(this.currSubmission.getQuestion().getTitle());
} }
editorSection.setVisible(true); editorSection.setVisible(true);
sidebar.setVisible(true); sidebar.setVisible(true);
prev.setEnabled(currSubmission != null && !assessment.isFirst(currSubmission)); prev.setEnabled(currSubmission != null && !exam.isFirst(currSubmission));
next.setEnabled(currSubmission == null || !assessment.isLast(currSubmission)); next.setEnabled(currSubmission == null || !exam.isLast(currSubmission));
if (dl.getChildren().collect(Collectors.toList()).isEmpty()) { if (dl.getChildren().collect(Collectors.toList()).isEmpty()) {
dl.add( dl.add(
createItem("Candidato:", assessment.getCandidate().getEmail()), createItem("Candidato:", exam.getCandidate().getEmail()),
createItem("Hora de inicio:", Optional.ofNullable(assessment.getStartingTime()) createItem("Hora de inicio:", Optional.ofNullable(exam.getStartingTime())
.map(t -> ZonedDateTime.ofInstant(t, .map(t -> ZonedDateTime.ofInstant(t,
ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
.orElse("N/A")) .orElse("N/A"))
@ -258,14 +258,14 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
@Override @Override
public void setParameter(final BeforeEvent beforeEvent, final String s) { 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(); throw new NotFoundException();
} }
this.currSubmission = this.assessment.isStarted() this.currSubmission = this.exam.isStarted()
? this.assessmentService.getNextSubmission(assessment.getId(), null, false) ? this.examService.getNextSubmission(exam.getId(), null, false)
: null; : null;
updateUI(); updateUI();

View File

@ -21,7 +21,7 @@ import java.util.UUID;
@SpringComponent @SpringComponent
@Scope("prototype") @Scope("prototype")
@PageTitle("Assessments") @PageTitle("Candidates")
@Route(value = "/candidates", layout = MainLayout.class) @Route(value = "/candidates", layout = MainLayout.class)
@PermitAll @PermitAll
public class CandidateView extends BaseEntityForm<Candidate> implements HasUrlParameter<String> { public class CandidateView extends BaseEntityForm<Candidate> implements HasUrlParameter<String> {

View File

@ -1,9 +1,9 @@
package com.primefactorsolutions.views.assessment; package com.primefactorsolutions.views.assessment;
import com.primefactorsolutions.model.Candidate; import com.primefactorsolutions.model.Candidate;
import com.primefactorsolutions.model.Assessment; import com.primefactorsolutions.model.Exam;
import com.primefactorsolutions.model.Question; import com.primefactorsolutions.model.Question;
import com.primefactorsolutions.service.AssessmentService; import com.primefactorsolutions.service.ExamService;
import com.primefactorsolutions.service.QuestionService; import com.primefactorsolutions.service.QuestionService;
import com.primefactorsolutions.service.CandidateService; import com.primefactorsolutions.service.CandidateService;
import com.primefactorsolutions.views.MainLayout; import com.primefactorsolutions.views.MainLayout;
@ -28,20 +28,20 @@ import java.util.UUID;
@SpringComponent @SpringComponent
@PermitAll @PermitAll
@Scope("prototype") @Scope("prototype")
@PageTitle("Assessments") @PageTitle("Exams")
@Route(value = "/assessments", layout = MainLayout.class) @Route(value = "/exams", layout = MainLayout.class)
@Uses(ComboBox.class) @Uses(ComboBox.class)
public class AssessmentView extends BeanValidationForm<Assessment> implements HasUrlParameter<String> { public class ExamView extends BeanValidationForm<Exam> implements HasUrlParameter<String> {
private final AssessmentService assessmentService; private final ExamService examService;
private ComboBox<Candidate> candidate = null; private ComboBox<Candidate> candidate = null;
private SubListSelector<Question> questions = null; private SubListSelector<Question> questions = null;
public AssessmentView(final AssessmentService assessmentService, final QuestionService questionService, public ExamView(final ExamService examService, final QuestionService questionService,
final CandidateService candidateService) { final CandidateService candidateService) {
super(Assessment.class); super(Exam.class);
this.assessmentService = assessmentService; this.examService = examService;
candidate = new ComboBox<>("Candidate", candidateService.getCandidates()); candidate = new ComboBox<>("Candidate", candidateService.getCandidates());
candidate.setItemLabelGenerator((ItemLabelGenerator<Candidate>) Candidate::getEmail); candidate.setItemLabelGenerator((ItemLabelGenerator<Candidate>) Candidate::getEmail);
@ -52,8 +52,8 @@ public class AssessmentView extends BeanValidationForm<Assessment> implements Ha
questions.setReadOnly(false); questions.setReadOnly(false);
questions.setAvailableOptions(questionService.getQuestions()); questions.setAvailableOptions(questionService.getQuestions());
setSavedHandler((SavedHandler<Assessment>) assessment -> { setSavedHandler((SavedHandler<Exam>) exam -> {
final var saved = this.assessmentService.saveAssessment(assessment); final var saved = this.examService.saveExam(exam);
setEntityWithEnabledSave(saved); setEntityWithEnabledSave(saved);
}); });
} }
@ -61,11 +61,11 @@ public class AssessmentView extends BeanValidationForm<Assessment> implements Ha
@Override @Override
public void setParameter(final BeforeEvent beforeEvent, final String s) { public void setParameter(final BeforeEvent beforeEvent, final String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(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 { } else {
setEntityWithEnabledSave(new Assessment()); setEntityWithEnabledSave(new Exam());
} }
} }

View File

@ -1,7 +1,7 @@
package com.primefactorsolutions.views.assessment; package com.primefactorsolutions.views.assessment;
import com.primefactorsolutions.model.Assessment; import com.primefactorsolutions.model.Exam;
import com.primefactorsolutions.service.AssessmentService; import com.primefactorsolutions.service.ExamService;
import com.primefactorsolutions.views.BaseView; import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout; import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.SubmissionView; import com.primefactorsolutions.views.SubmissionView;
@ -25,48 +25,48 @@ import org.vaadin.firitin.components.grid.VGrid;
@SpringComponent @SpringComponent
@Scope("prototype") @Scope("prototype")
@PageTitle("Assessments") @PageTitle("Exams")
@Route(value = "/assessments", layout = MainLayout.class) @Route(value = "/exams", layout = MainLayout.class)
@PermitAll @PermitAll
public class AssessmentsListView extends BaseView { public class ExamsListView extends BaseView {
public AssessmentsListView(final AuthenticationContext authenticationContext, public ExamsListView(final AuthenticationContext authenticationContext,
final AssessmentService assessmentService) { final ExamService examService) {
super(authenticationContext); super(authenticationContext);
final HorizontalLayout hl = new HorizontalLayout(); final HorizontalLayout hl = new HorizontalLayout();
final Button addAssessment = new Button("Add Assessment"); final Button addExam = new Button("Add Exam");
addAssessment.addThemeVariants(ButtonVariant.LUMO_PRIMARY); addExam.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
addAssessment.addClickListener(buttonClickEvent -> addExam.addClickListener(buttonClickEvent ->
getUI().flatMap(ui -> ui.navigate(AssessmentView.class, "new"))); getUI().flatMap(ui -> ui.navigate(ExamView.class, "new")));
hl.add(addAssessment); hl.add(addExam);
final VGrid<Assessment> grid = new VGrid<>(Assessment.class); final VGrid<Exam> grid = new VGrid<>(Exam.class);
grid.setColumns("id", "candidate.email"); grid.setColumns("id", "candidate.email");
final Grid.Column<Assessment> statusColumn = grid.addColumn(assessment -> final Grid.Column<Exam> statusColumn = grid.addColumn(exam ->
assessment.getAssessmentEvents().isEmpty() exam.getExamEvents().isEmpty()
? "N/A" ? "N/A"
: assessment.getAssessmentEvents().getLast().getStatus().name()); : exam.getExamEvents().getLast().getStatus().name());
statusColumn.setHeader("Status"); statusColumn.setHeader("Status");
grid.addComponentColumn(assessment -> MenuBarUtils.menuBar( grid.addComponentColumn(exam -> MenuBarUtils.menuBar(
Pair.of("View", __ -> 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", __ -> Pair.of("Copy", __ ->
ClientsideClipboard.writeToClipboard( ClientsideClipboard.writeToClipboard(
String.format("email: %s link: https://careers.primefactorsolutions.com/evaluation/%s", String.format("email: %s link: https://intra.primefactorsolutions.com/evaluation/%s",
assessment.getCandidate().getEmail(), exam.getCandidate().getEmail(),
assessment.getId()))), exam.getId()))),
Pair.of("Email", __ -> { Pair.of("Email", __ -> {
ConfirmDialog dialog = new ConfirmDialog(); ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Send Link Email"); dialog.setHeader("Send Link Email");
dialog.setText(String.format("Enviar link por email al candidato %s?", dialog.setText(String.format("Enviar link por email al candidato %s?",
assessment.getCandidate().getEmail())); exam.getCandidate().getEmail()));
dialog.setCancelable(true); dialog.setCancelable(true);
dialog.setConfirmText("Enviar"); dialog.setConfirmText("Enviar");
dialog.setConfirmButtonTheme("primary"); dialog.setConfirmButtonTheme("primary");
dialog.addConfirmListener(confirmEvent -> { dialog.addConfirmListener(confirmEvent -> {
try { try {
assessmentService.sendEmail(assessment); examService.sendEmail(exam);
} catch (Exception e) { } catch (Exception e) {
Notification.show("Error sending email: " + e.getMessage(), 10_000, Notification.show("Error sending email: " + e.getMessage(), 10_000,
Notification.Position.TOP_CENTER); 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); grid.setAllRowsVisible(true);
getCurrentPageLayout().add(hl, grid); getCurrentPageLayout().add(hl, grid);

View File

@ -20,7 +20,7 @@ import java.util.UUID;
@SpringComponent @SpringComponent
@Scope("prototype") @Scope("prototype")
@PageTitle("Assessments") @PageTitle("Questions")
@Route(value = "/questions", layout = MainLayout.class) @Route(value = "/questions", layout = MainLayout.class)
@PermitAll @PermitAll
public class QuestionView extends BaseEntityForm<Question> implements HasUrlParameter<String> { public class QuestionView extends BaseEntityForm<Question> implements HasUrlParameter<String> {

View File

@ -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 ('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 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 exam_questions (exam_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', '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 ('b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 1, 'ABC');
insert into team (id, version, name) values ('6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 1, 'XYZ'); insert into team (id, version, name) values ('6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 1, 'XYZ');

View File

@ -2,7 +2,7 @@ package com.primefactorsolutions;
import java.util.*; import java.util.*;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.util.stream.Collectors; import java.util.stream.*;
public class TestClass { public class TestClass {
@ -12,9 +12,9 @@ public class TestClass {
return "TODO"; return "TODO";
} }
// ------------- NO MODIFICAR DESDE ESTA LINEA -------------------- // ------------- NO MODIFICAR DESDE ESTA LINEA --------------------
// PRUEBAS UNITARIAS
public static class Tests { public static class Tests {
public static Map.Entry<String, Boolean> testNInvalido() { public static Map.Entry<String, Boolean> testNInvalido() {
boolean result = getFooBar(-1).equals(""); boolean result = getFooBar(-1).equals("");

View File

@ -2,48 +2,48 @@ package com.primefactorsolutions;
import java.util.*; import java.util.*;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.util.stream.Collectors; import java.util.stream.*;
public class TestClass { public class TestClass {
// ------------ IMPLEMENTAR AQUI -------------------------------- // ------------ IMPLEMENTAR AQUI --------------------------------
public static String getPalindroma(String s) { public static String getPalindromo(String s) {
return "TODO"; return "TODO";
} }
// ------------- NO MODIFICAR DESDE ESTA LINEA -------------------- // ------------- NO MODIFICAR DESDE ESTA LINEA --------------------
// PRUEBAS UNITARIAS
public static class Tests { public static class Tests {
public static Map.Entry<String, Boolean> testPalindromaValido() { public static Map.Entry<String, Boolean> testPalindromoValido() {
boolean result = getPalindroma("Ab").equals("ABBA"); boolean result = getPalindromo("Ab").equals("ABBA");
return Map.entry("palindrome es valido", result); return Map.entry("palindromo es valido", result);
} }
public static Map.Entry<String, Boolean> testPalindromaVacio() { public static Map.Entry<String, Boolean> testPalindromoVacio() {
boolean result = getPalindroma("").equals(""); boolean result = getPalindromo("").equals("");
return Map.entry("palindrome vacio es valido", result); return Map.entry("palindromo vacio es valido", result);
} }
public static Map.Entry<String, Boolean> testPalindromaUnico() { public static Map.Entry<String, Boolean> testPalindromoUnico() {
boolean result = getPalindroma("z").equals("Z"); 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<String, Boolean> testPalindromaUnico() { public static Map.Entry<String, Boolean> testPalindromoEntrada() {
boolean result = getPalindroma("abba").equals("ABBA"); 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<String, Boolean> testPalindromaNull() { public static Map.Entry<String, Boolean> testPalindromoNull() {
boolean result = getPalindroma(null) == null; boolean result = getPalindromo(null) == null;
return Map.entry("palindrome null es valido", result); return Map.entry("palindromo null es valido", result);
} }
} }

View File

@ -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);
}
}

View File

@ -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);
}
}