init impl

This commit is contained in:
alex 2024-08-05 14:49:25 -04:00
parent b48e5191d7
commit 7366e68ea6
36 changed files with 1485 additions and 102 deletions

4
Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM openjdk:17-jdk-slim
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

29
pom.xml
View File

@ -88,6 +88,35 @@
<artifactId>vaadin-testbench-junit5</artifactId> <artifactId>vaadin-testbench-junit5</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>net.openhft</groupId>
<artifactId>compiler</artifactId>
<version>2.26ea0</version>
</dependency>
<dependency>
<groupId>com.hilerio</groupId>
<artifactId>ace-widget</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-63</artifactId>
<version>3.7.3</version>
</dependency>
<dependency>
<groupId>in.virit</groupId>
<artifactId>viritin</artifactId>
<version>2.8.22</version>
</dependency>
<dependency>
<groupId>com.flowingcode.addons</groupId>
<artifactId>simple-timer</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,32 @@
This directory is automatically generated by Vaadin and contains the pre-compiled
frontend files/resources for your project (frontend development bundle).
It should be added to Version Control System and committed, so that other developers
do not have to compile it again.
Frontend development bundle is automatically updated when needed:
- an npm/pnpm package is added with @NpmPackage or directly into package.json
- CSS, JavaScript or TypeScript files are added with @CssImport, @JsModule or @JavaScript
- Vaadin add-on with front-end customizations is added
- Custom theme imports/assets added into 'theme.json' file
- Exported web component is added.
If your project development needs a hot deployment of the frontend changes,
you can switch Flow to use Vite development server (default in Vaadin 23.3 and earlier versions):
- set `vaadin.frontend.hotdeploy=true` in `application.properties`
- configure `vaadin-maven-plugin`:
```
<configuration>
<frontendHotdeploy>true</frontendHotdeploy>
</configuration>
```
- configure `jetty-maven-plugin`:
```
<configuration>
<systemProperties>
<vaadin.frontend.hotdeploy>true</vaadin.frontend.hotdeploy>
</systemProperties>
</configuration>
```
Read more [about Vaadin development mode](https://vaadin.com/docs/next/flow/configuration/development-mode#precompiled-bundle).

BIN
src/main/bundles/dev.bundle Normal file

Binary file not shown.

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<!--
This file is auto-generated by Vaadin.
-->
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body, #outlet {
height: 100vh;
width: 100%;
margin: 0;
}
</style>
<!-- index.ts is included here automatically (either by the dev server or during the build) -->
</head>
<body>
<!-- This outlet div is where the views are rendered -->
<div id="outlet"></div>
</body>
</html>

View File

@ -0,0 +1,23 @@
package com.primefactorsolutions.model;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class AppUser extends BaseEntity {
private String email;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "appUser")
private List<Assessment> assessments;
}

View File

@ -0,0 +1,64 @@
package com.primefactorsolutions.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Assessment extends BaseEntity {
@OneToMany(fetch = FetchType.EAGER)
private List<Question> questions;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "assessment", cascade = {CascadeType.ALL})
private List<Submission> submissions;
@ManyToOne
private AppUser appUser;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "assessment", cascade = {CascadeType.ALL})
private List<AssessmentEvent> assessmentEvents = List.of();
public Submission getCurrentSubmission() {
return submissions.getLast();
}
public Long getRemainingTimeSeconds() {
final Optional<Instant> started = assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.STARTED)
.map(AssessmentEvent::getTimestamp)
.findFirst();
return started.map(instant -> 300L - (Instant.now().getEpochSecond() - instant.getEpochSecond()))
.orElse(300L);
}
public Instant getStartingTime() {
final Optional<Instant> started = assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.STARTED)
.map(AssessmentEvent::getTimestamp)
.findFirst();
return started.orElse(null);
}
public boolean isCompleted() {
return assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.COMPLETED)
.map(AssessmentEvent::getTimestamp)
.findFirst()
.isPresent();
}
public boolean isFirst(Submission currSubmission) {
return getQuestions().indexOf(currSubmission.getQuestion()) == 0;
}
public boolean isLast(Submission currSubmission) {
return getQuestions().indexOf(currSubmission.getQuestion()) == getQuestions().size() - 1;
}
}

View File

@ -0,0 +1,23 @@
package com.primefactorsolutions.model;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Entity
@Data
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
public class AssessmentEvent extends BaseEntity {
private Instant timestamp;
private AssessmentStatus status;
@ManyToOne
private Assessment assessment;
}

View File

@ -0,0 +1,7 @@
package com.primefactorsolutions.model;
public enum AssessmentStatus {
CREATED,
STARTED,
COMPLETED
}

View File

@ -0,0 +1,51 @@
package com.primefactorsolutions.model;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.Version;
import java.util.UUID;
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Version
private int version;
public UUID getId() {
return id;
}
public void setId(final UUID id) {
this.id = id;
}
public int getVersion() {
return version;
}
@Override
public int hashCode() {
if (getId() != null) {
return getId().hashCode();
}
return super.hashCode();
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof BaseEntity that)) {
return false; // null or not an AbstractEntity class
}
if (getId() != null) {
return getId().equals(that.getId());
}
return super.equals(that);
}
}

View File

@ -0,0 +1,16 @@
package com.primefactorsolutions.model;
import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Question extends BaseEntity{
private String content;
}

View File

@ -0,0 +1,34 @@
package com.primefactorsolutions.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Type;
import java.util.Map;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Submission extends BaseEntity {
@ManyToOne
private Question question;
private String response;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private Map<String, Object> results;
private SubmissionStatus submissionStatus;
@ManyToOne
private Assessment assessment;
}

View File

@ -0,0 +1,6 @@
package com.primefactorsolutions.model;
public enum SubmissionStatus {
OK,
FAIL
}

View File

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

View File

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

View File

@ -0,0 +1,129 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.repositories.AssessmentRepository;
import lombok.Data;
import org.checkerframework.checker.units.qual.A;
import org.springframework.stereotype.Service;
import javax.swing.text.html.Option;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@Service
@Data
public class AssessmentService {
private final AssessmentRepository assessmentRepository;
public Assessment createOrUpdate(final Assessment assessment) {
final Assessment saved = assessmentRepository.save(assessment);
return saved;
}
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();
final Optional<AssessmentEvent> started = assessment.getAssessmentEvents().stream()
.filter(e -> e.getStatus() == AssessmentStatus.STARTED)
.findFirst();
if (started.isPresent()) {
return assessment;
}
assessment.getAssessmentEvents().add(new AssessmentEvent(Instant.now(), AssessmentStatus.STARTED, assessment));
assessmentRepository.save(assessment);
return assessment;
}
public Submission getNextSubmission(final Assessment assessment, final Submission currSubmission) {
if (currSubmission == null) {
if (assessment.isCompleted()) {
return null;
}
final Question firstQuestion = assessment.getQuestions().stream().findFirst().get();
final Submission submissionToReturn = new Submission(firstQuestion, firstQuestion.getContent(), Map.of(), SubmissionStatus.FAIL, assessment);
assessment.getSubmissions().add(submissionToReturn);
assessmentRepository.save(assessment);
return submissionToReturn;
}
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();
final Submission result;
if (submissionToReturn.isEmpty()) {
result = new Submission(nextQuestion, nextQuestion.getContent(), Map.of(), SubmissionStatus.FAIL, assessment);
assessment.getSubmissions().add(result);
} else {
result = submissionToReturn.get();
}
assessmentRepository.save(assessment);
return result;
}
public Submission getPrevSubmission(final Assessment assessment, final Submission currSubmission) {
if (currSubmission == null) {
return null;
}
final Question currQuestion = currSubmission.getQuestion();
int idx = assessment.getQuestions().indexOf(currQuestion);
if (idx == 0) {
return null;
}
final Question prevQuestion = assessment.getQuestions().get(idx - 1);
assessmentRepository.save(assessment);
return assessment.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(prevQuestion))
.findFirst().orElseThrow(() -> new IllegalStateException("submission invalid"));
}
public Assessment completeAssessment(final UUID id, final Submission submission) {
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));
assessmentRepository.save(assessment);
return assessment;
}
}

View File

@ -0,0 +1,55 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.service.compiler.InMemoryClass;
import com.primefactorsolutions.service.compiler.InMemoryFileManager;
import com.primefactorsolutions.service.compiler.JavaSourceFromString;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Slf4j
public class CompilerService {
private static final Map<Boolean, String> RESULT_MESSAGE = Map.of(Boolean.TRUE, "OK", Boolean.FALSE, "ERROR");
@SneakyThrows
public Optional<String> doCompile(final String javaCode) {
final String qualifiedClassName = "com.primefactorsolutions.TestClass";
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
final InMemoryFileManager manager = new InMemoryFileManager(compiler.getStandardFileManager(null, null, null));
final List<JavaFileObject> sourceFiles = Collections.singletonList(new JavaSourceFromString(qualifiedClassName, javaCode));
final JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics,
List.of("-proc:full", "-Xlint:-options"), null, sourceFiles);
boolean result = task.call();
if (!result) {
final String errors = diagnostics.getDiagnostics().stream()
.map(String::valueOf)
.collect(Collectors.joining("\n"));
return Optional.of(errors);
} else {
final ClassLoader classLoader = manager.getClassLoader(null);
final Class<?> clazz = classLoader.loadClass(qualifiedClassName);
final InMemoryClass instanceOfClass = (InMemoryClass) clazz.newInstance();
final Map<String, Boolean> results = instanceOfClass.runCode();
return Optional.of(results.entrySet().stream()
.map(e -> String.format("%-50s ... %4s", e.getKey(), RESULT_MESSAGE.get(e.getValue())))
.collect(Collectors.joining("\n")));
}
}
}

View File

@ -0,0 +1,29 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.AppUser;
import com.primefactorsolutions.repositories.UserRepository;
import lombok.Data;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
@Data
public class UserService {
private final UserRepository userRepository;
public AppUser createOrUpdate(final AppUser assessment) {
final AppUser saved = userRepository.save(assessment);
return saved;
}
public List<AppUser> getUsers() {
return userRepository.findAll();
}
public AppUser getUser(final UUID id) {
return userRepository.findById(id).orElse(null);
}
}

View File

@ -0,0 +1,8 @@
package com.primefactorsolutions.service.compiler;
import java.util.Map;
public interface InMemoryClass {
Map<String, Boolean> runCode();
}

View File

@ -0,0 +1,30 @@
package com.primefactorsolutions.service.compiler;
import static java.util.Objects.requireNonNull;
import java.util.Map;
public class InMemoryClassLoader extends ClassLoader {
private final InMemoryFileManager manager;
public InMemoryClassLoader(ClassLoader parent, InMemoryFileManager manager) {
super(parent);
this.manager = requireNonNull(manager, "manager must not be null");
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Map<String, JavaClassAsBytes> compiledClasses = manager
.getBytesMap();
if (compiledClasses.containsKey(name)) {
byte[] bytes = compiledClasses.get(name)
.getBytes();
return defineClass(name, bytes, 0, bytes.length);
} else {
throw new ClassNotFoundException();
}
}
}

View File

@ -0,0 +1,52 @@
package com.primefactorsolutions.service.compiler;
import java.util.Hashtable;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardJavaFileManager;
public class InMemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private final Map<String, JavaClassAsBytes> compiledClasses;
private final ClassLoader loader;
public InMemoryFileManager(StandardJavaFileManager standardManager) {
super(standardManager);
this.compiledClasses = new Hashtable<>();
this.loader = new InMemoryClassLoader(this.getClass()
.getClassLoader(),
this
);
}
/**
* Used to get the class loader for our compiled class. It creates an anonymous class extending
* the SecureClassLoader which uses the byte code created by the compiler and stored in the
* JavaClassObject, and returns the Class for it
*
* @param location where to place or search for file objects.
*/
@Override
public ClassLoader getClassLoader(Location location) {
return loader;
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind,
FileObject sibling) {
JavaClassAsBytes classAsBytes = new JavaClassAsBytes(
className, kind);
compiledClasses.put(className, classAsBytes);
return classAsBytes;
}
public Map<String, JavaClassAsBytes> getBytesMap() {
return compiledClasses;
}
}

View File

@ -0,0 +1,26 @@
package com.primefactorsolutions.service.compiler;
import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URI;
public class JavaClassAsBytes extends SimpleJavaFileObject {
protected ByteArrayOutputStream bos =
new ByteArrayOutputStream();
public JavaClassAsBytes(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/')
+ kind.extension), kind);
}
public byte[] getBytes() {
return bos.toByteArray();
}
@Override
public OutputStream openOutputStream() {
return bos;
}
}

View File

@ -0,0 +1,22 @@
package com.primefactorsolutions.service.compiler;
import javax.tools.SimpleJavaFileObject;
import java.net.URI;
import static java.util.Objects.requireNonNull;
public class JavaSourceFromString extends SimpleJavaFileObject {
private String sourceCode;
public JavaSourceFromString(String name, String sourceCode) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension),
Kind.SOURCE);
this.sourceCode = requireNonNull(sourceCode, "sourceCode must not be null");
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return sourceCode;
}
}

View File

@ -0,0 +1,51 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Assessment;
import com.primefactorsolutions.service.AssessmentService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.form.BeanValidationForm;
import java.util.List;
import java.util.UUID;
@SpringComponent
@Scope("prototype")
@PageTitle("Assessments")
@Route(value = "/assessments", layout = MainLayout.class)
public class AssessmentView extends BeanValidationForm<Assessment> implements HasUrlParameter<String> {
private final AssessmentService assessmentService;
private final TextField appUserName = new TextField();
public AssessmentView(AssessmentService assessmentService) {
super(Assessment.class);
setEntityWithEnabledSave(new Assessment());
this.assessmentService = assessmentService;
setSavedHandler((SavedHandler<Assessment>) assessment -> {
System.out.println(assessment);
});
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
var assessment = assessmentService.getAssessment(UUID.fromString(s));
}
}
@Override
protected List<Component> getFormComponents() {
return List.of(appUserName);
}
}

View File

@ -0,0 +1,61 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Assessment;
import com.primefactorsolutions.service.AssessmentService;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.spring.annotation.SpringComponent;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.VGrid;
import java.util.stream.Stream;
@SpringComponent
@Scope("prototype")
@PageTitle("Assessments")
@Route(value = "/assessments", layout = MainLayout.class)
public class AssessmentsListView extends Main {
public AssessmentsListView(final AssessmentService assessmentService) {
final VGrid<Assessment> grid = new VGrid<>(Assessment.class);
grid.setDataProvider(new DataProvider<Assessment, Object>() {
@Override
public boolean isInMemory() {
return false;
}
@Override
public int size(Query<Assessment, Object> query) {
return 0;
}
@Override
public Stream<Assessment> fetch(Query<Assessment, Object> query) {
return null;
}
@Override
public void refreshItem(Assessment assessment) {
}
@Override
public void refreshAll() {
}
@Override
public Registration addDataProviderListener(DataProviderListener<Assessment> dataProviderListener) {
return null;
}
});
add(grid);
}
}

View File

@ -0,0 +1,14 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
@PageTitle("Assessment")
@Route(value = "/evaluation/completed", layout = MainLayout.class)
public class EvaluationCompletedView extends Main {
public EvaluationCompletedView() {
add(new Text("La evaluacion ha sido completada. Nos contactaremos con usted."));
}
}

View File

@ -0,0 +1,414 @@
package com.primefactorsolutions.views;
import com.flowingcode.vaadin.addons.simpletimer.SimpleTimer;
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.CompilerService;
import com.vaadin.flow.component.*;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.*;
import com.vaadin.flow.component.html.DescriptionList.Description;
import com.vaadin.flow.component.html.DescriptionList.Term;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.menubar.MenuBar;
import com.vaadin.flow.component.menubar.MenuBarVariant;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.theme.lumo.LumoUtility.Background;
import com.vaadin.flow.theme.lumo.LumoUtility.BoxSizing;
import com.vaadin.flow.theme.lumo.LumoUtility.Display;
import com.vaadin.flow.theme.lumo.LumoUtility.Flex;
import com.vaadin.flow.theme.lumo.LumoUtility.FlexDirection;
import com.vaadin.flow.theme.lumo.LumoUtility.FontSize;
import com.vaadin.flow.theme.lumo.LumoUtility.FontWeight;
import com.vaadin.flow.theme.lumo.LumoUtility.Gap;
import com.vaadin.flow.theme.lumo.LumoUtility.Height;
import com.vaadin.flow.theme.lumo.LumoUtility.Margin;
import com.vaadin.flow.theme.lumo.LumoUtility.Overflow;
import com.vaadin.flow.theme.lumo.LumoUtility.Padding;
import com.vaadin.flow.theme.lumo.LumoUtility.TextColor;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Scope;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@PageTitle("Evaluacion")
@SpringComponent
@Scope("prototype")
@Route(value = "/evaluation", layout = MainLayout.class)
public class EvaluationView extends Main implements HasUrlParameter<String> {
final CompilerService compilerService;
final AssessmentService assessmentService;
AceEditor questionEditor = null;
Dialog dialog = null;
Dialog completeDialog = null;
AceEditor result = null;
Assessment assessment = null;
Submission currSubmission = null;
Boolean isCompleted = false;
Button start = null;
MenuItem prev = null;
MenuItem next = null;
Section sidebar = null;
SimpleTimer timer = null;
DescriptionList dl = null;
Section editorSection = null;
Section startSection = null;
Section completedSection = null;
public EvaluationView(final CompilerService compilerService, final AssessmentService assessmentService) {
this.compilerService = compilerService;
this.assessmentService = assessmentService;
addClassNames(Display.FLEX, Flex.GROW, Height.FULL);
initStartSection();
initCompletedSection();
initEditorSection();
initResultDialog();
initCompleteDialog();
initSidebar();
add(completedSection, startSection, editorSection, sidebar, dialog);
updateUI();
}
private void initResultDialog() {
dialog = new Dialog();
dialog.setHeaderTitle("Resultados");
result = new AceEditor();
result.setTheme(AceTheme.xcode);
result.setMode(AceMode.text);
result.setFontSize(12);
result.setHeight("100%");
result.setWidth("100%");
result.setReadOnly(true);
result.setMinlines(20);
result.setMaxlines(30);
result.setDisplayIndentGuides(false);
result.setShowGutter(false);
final VerticalLayout dialogLayout = new VerticalLayout(result);
dialogLayout.setPadding(false);
dialogLayout.setSpacing(false);
dialogLayout.setAlignItems(FlexComponent.Alignment.STRETCH);
dialogLayout.getStyle()
.set("width", "800px")
.set("max-width", "100%");
dialog.add(dialogLayout);
final Button saveButton = new Button("Guardar y Siguiente", e -> {
this.currSubmission.setResponse(this.questionEditor.getValue());
goToNext();
dialog.close();
});
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
final Button closeButton = new Button("Cerrar", e -> dialog.close());
dialog.getFooter().add(closeButton, saveButton);
}
private void initCompleteDialog() {
completeDialog = new Dialog();
completeDialog.setHeaderTitle("Terminar Evaluacion");
final VerticalLayout dialogLayout = new VerticalLayout(new Text("Desea terminar la evaluacion? No podra editar las respuestas despues de terminar."));
dialogLayout.setPadding(false);
dialogLayout.setSpacing(false);
dialogLayout.setAlignItems(FlexComponent.Alignment.STRETCH);
dialogLayout.getStyle()
.set("width", "800px")
.set("max-width", "100%");
completeDialog.add(dialogLayout);
final Button completeButton = new Button("Terminar", e -> {
completeDialog.close();
this.assessment = assessmentService.completeAssessment(assessment.getId(), currSubmission);
goToCompleted();
updateUI();
});
completeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
final Button closeButton = new Button("Cerrar", e -> completeDialog.close());
completeDialog.getFooter().add(closeButton, completeButton);
}
private void initEditorSection() {
editorSection = new Section();
editorSection.addClassNames(Display.FLEX, FlexDirection.COLUMN, Flex.GROW, Height.FULL);
questionEditor = getCodeEditor();
final MenuBar runMenuBar = new MenuBar();
runMenuBar.addThemeVariants(MenuBarVariant.LUMO_PRIMARY);
runMenuBar.addItem("Verificar/Ejecutar", (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
final String javaCode = questionEditor.getValue();
final Optional<String> result = this.compilerService.doCompile(javaCode);
if (result.isPresent()) {
this.result.setValue(result.get());
this.dialog.open();
}
});
final MenuBar navMenuBar = new MenuBar();
prev = navMenuBar.addItem("Anterior pregunta", (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
System.out.println(">>> prev");
this.currSubmission.setResponse(this.questionEditor.getValue());
this.currSubmission = this.assessmentService.getPrevSubmission(assessment, this.currSubmission);
updateUI();
});
next = navMenuBar.addItem("Siguiente pregunta", (ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setResponse(this.questionEditor.getValue());
goToNext();
});
final Div menuBar = new Div();
menuBar.add(runMenuBar, navMenuBar);
setInlineBlock(runMenuBar);
setInlineBlock(navMenuBar);
editorSection.add(menuBar, questionEditor);
}
private void initCompletedSection() {
completedSection = new Section();
completedSection.addClassNames(Display.FLEX, FlexDirection.COLUMN, Flex.GROW, Height.FULL);
completedSection.setVisible(false);
Div completedHelp = new Div();
completedHelp.add(new Text("Evaluacion ha sido completada. Gracias!"));
completedSection.add(completedHelp);
}
private void initStartSection() {
startSection = new Section();
startSection.addClassNames(Display.FLEX, FlexDirection.COLUMN, Flex.GROW, Height.FULL);
Div startHelp = new Div();
startHelp.add(new Text("Bienvenido(a) al examen de evaluacion de PFS. Apriete el boton 'Empezar' para empezar la evaluacion. Tiene 30 minutos para completar."));
startHelp.add(new HtmlComponent("br"));
startHelp.add(new Text("Las preguntas son de implementacion de codigo en JAVA. Las preguntas deben poder compilar y pasar los tests incluidos en el codigo. Puede pasar un pregunta o volver a una pregunta anterior."));
TextField tf = new TextField();
tf.setLabel("Ingrese su email (donde recibio el link de la evaluacion):");
start = new Button("Empezar");
start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
start.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
System.out.println(">>> start");
this.assessment = this.assessmentService.startAssessment(this.assessment.getId());
if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getAppUser().getEmail())) {
this.currSubmission = this.assessmentService.getNextSubmission(assessment, this.currSubmission);
updateUI();
} else {
Notification notification = new Notification();
notification.addThemeVariants(NotificationVariant.LUMO_ERROR);
notification.setPosition(Notification.Position.TOP_CENTER);
Div text = new Div(new Text("Email invalido. Verifique que el email corresponde al email donde recibio el enlace a la evaluacion."));
Button closeButton = new Button(new Icon("lumo", "cross"));
closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
closeButton.setAriaLabel("Close");
closeButton.addClickListener(event -> {
notification.close();
});
HorizontalLayout layout = new HorizontalLayout(text, closeButton);
layout.setAlignItems(FlexComponent.Alignment.CENTER);
notification.add(layout);
notification.open();
}
});
startSection.add(startHelp, tf, start);
}
@NotNull
private AceEditor getCodeEditor() {
final AceEditor questionEditor;
questionEditor = new AceEditor();
questionEditor.setTheme(AceTheme.xcode);
questionEditor.setMode(AceMode.java);
questionEditor.setFontSize(15);
questionEditor.setHeight("100%");
questionEditor.setWidth("100%");
questionEditor.setReadOnly(false);
questionEditor.setShowInvisibles(true);
questionEditor.setShowGutter(false);
questionEditor.setShowPrintMargin(false);
questionEditor.setDisplayIndentGuides(false);
questionEditor.setUseWorker(false);
questionEditor.setSofttabs(true);
questionEditor.setTabSize(4);
questionEditor.setWrap(true);
questionEditor.setMinlines(20);
questionEditor.setMaxlines(30);
// aceEditor.setPlaceholder("... Edit ...");
questionEditor.setAutoComplete(true);
questionEditor.setCustomAutoCompletion(new String[] { "TEST", "TEST2", "TEST3" });
questionEditor.setHighlightActiveLine(true);
questionEditor.setHighlightSelectedWord(false);
return questionEditor;
}
private void setInlineBlock(MenuBar menuBar) {
menuBar.getStyle().set("display", "inline-block");
}
private void goToNext() {
System.out.println(">>> next");
this.currSubmission = this.assessmentService.getNextSubmission(assessment, this.currSubmission);
updateUI();
}
private void initSidebar() {
sidebar = new Section();
sidebar.addClassNames(Background.CONTRAST_5, BoxSizing.BORDER, Display.FLEX, FlexDirection.COLUMN,
Flex.SHRINK_NONE, Overflow.AUTO, Padding.LARGE);
sidebar.setWidth("256px");
dl = new DescriptionList();
dl.addClassNames(Display.FLEX, FlexDirection.COLUMN, Gap.LARGE, Margin.Bottom.SMALL, Margin.Top.NONE,
FontSize.SMALL);
final Text text = new Text("Tiempo restante:");
timer = new SimpleTimer(0);
timer.setMinutes(true);
timer.addTimerEndEvent((ComponentEventListener<SimpleTimer.TimerEndedEvent>) timerEndedEvent -> {
Notification.show("Tiempo completado.", 5, Notification.Position.TOP_CENTER);
this.assessment = assessmentService.completeAssessment(assessment.getId(), currSubmission);
goToCompleted();
updateUI();
});
timer.setFractions(false);
final Button completeButton = new Button("Terminar evaluacion");
completeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
completeButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.completeDialog.open();
});
// Add it all together
sidebar.add(dl, text, timer, completeButton);
}
private void updateUI() {
if (currSubmission == null) {
editorSection.setVisible(false);
startSection.setVisible(true);
sidebar.setVisible(false);
} else {
questionEditor.setValue(this.currSubmission.getResponse());
editorSection.setVisible(true);
startSection.setVisible(false);
sidebar.setVisible(true);
}
if (this.assessment != null) {
prev.setEnabled(currSubmission != null && !assessment.isFirst(currSubmission));
next.setEnabled(currSubmission == null || !assessment.isLast(currSubmission));
if (this.assessment.isCompleted()) {
goToCompleted();
this.editorSection.setVisible(false);
this.sidebar.setVisible(false);
this.startSection.setVisible(false);
this.completedSection.setVisible(true);
}
if (dl.getChildren().collect(Collectors.toList()).isEmpty()) {
dl.add(
createItem("Candidato:", assessment.getAppUser().getEmail()),
createItem("Hora comienzo:", Optional.ofNullable(assessment.getStartingTime())
.map(t -> ZonedDateTime.ofInstant(t, ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
.orElse("N/A"))
);
}
timer.pause();
timer.setStartTime(this.assessment.getRemainingTimeSeconds());
timer.setMinutes(true);
timer.reset();
timer.start();
}
}
private Div createItem(String label, String value) {
return new Div(createTerm(label), createDescription(value));
}
// private Div createBadgeItem(String label, String value) {
// return new Div(createTerm(label), createDescription(value, "badge"));
// }
private Term createTerm(String label) {
Term term = new Term(label);
term.addClassNames(FontWeight.MEDIUM, TextColor.SECONDARY);
return term;
}
private Description createDescription(String value, String... themeNames) {
Description desc = new Description(value);
desc.addClassName(Margin.Left.NONE);
for (String themeName : themeNames) {
desc.getElement().getThemeList().add(themeName);
}
return desc;
}
@Override
public void setParameter(BeforeEvent beforeEvent, String s) {
this.assessment = this.assessmentService.getAssessment(UUID.fromString(s));
if (this.assessment.isCompleted()) {
goToCompleted();
}
this.currSubmission = this.assessment.getSubmissions().isEmpty()
? null
: this.assessmentService.getNextSubmission(assessment, null);
updateUI();
}
private void goToCompleted() {
this.isCompleted = true;
}
}

View File

@ -1,6 +1,5 @@
package com.primefactorsolutions.views; package com.primefactorsolutions.views;
import com.primefactorsolutions.views.codeeditor.CodeEditorView;
import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle; import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.html.Footer; import com.vaadin.flow.component.html.Footer;
@ -9,10 +8,8 @@ import com.vaadin.flow.component.html.Header;
import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.Scroller; import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.sidenav.SideNav; import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.theme.lumo.LumoUtility; import com.vaadin.flow.theme.lumo.LumoUtility;
import org.vaadin.lineawesome.LineAwesomeIcon;
/** /**
* The main view is a top-level placeholder for other views. * The main view is a top-level placeholder for other views.
@ -50,7 +47,7 @@ public class MainLayout extends AppLayout {
private SideNav createNavigation() { private SideNav createNavigation() {
SideNav nav = new SideNav(); SideNav nav = new SideNav();
nav.addItem(new SideNavItem("CodeEditor", CodeEditorView.class, LineAwesomeIcon.EDIT.create())); //nav.addItem(new SideNavItem("CodeEditor", CodeEditorView.class, LineAwesomeIcon.EDIT.create()));
return nav; return nav;
} }

View File

@ -0,0 +1,14 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
@PageTitle("Assessment")
@Route(value = "", layout = MainLayout.class)
public class MainView extends Main {
public MainView() {
add(new Text("welcome"));
}
}

View File

@ -0,0 +1,51 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.AppUser;
import com.primefactorsolutions.service.UserService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.form.BeanValidationForm;
import java.util.List;
import java.util.UUID;
@SpringComponent
@Scope("prototype")
@PageTitle("Assessments")
@Route(value = "/users", layout = MainLayout.class)
public class UserView extends BeanValidationForm<AppUser> implements HasUrlParameter<String> {
private final UserService userService;
private final TextField appUserName = new TextField();
public UserView(UserService userService) {
super(AppUser.class);
setEntityWithEnabledSave(new AppUser("foo@bar.com", List.of()));
this.userService = userService;
setSavedHandler((SavedHandler<AppUser>) appUser -> {
System.out.println(appUser);
});
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
var assessment = userService.getUser(UUID.fromString(s));
}
}
@Override
protected List<Component> getFormComponents() {
return List.of(appUserName);
}
}

View File

@ -0,0 +1,64 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.AppUser;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.spring.annotation.SpringComponent;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.VGrid;
import java.util.List;
import java.util.stream.Stream;
@SpringComponent
@Scope("prototype")
@PageTitle("Users")
@Route(value = "/users", layout = MainLayout.class)
public class UsersListView extends Main {
public UsersListView() {
final VGrid<AppUser> grid = new VGrid<>(AppUser.class);
grid.setDataProvider(new DataProvider<AppUser, Object>() {
@Override
public boolean isInMemory() {
return false;
}
@Override
public int size(Query<AppUser, Object> query) {
return 1;
}
@Override
public Stream<AppUser> fetch(Query<AppUser, Object> query) {
int limit = query.getLimit();
int pagerSize = query.getPageSize();
int page = query.getPage();
return Stream.of(new AppUser("foo@bar.com", List.of()));
}
@Override
public void refreshItem(AppUser appUser) {
}
@Override
public void refreshAll() {
}
@Override
public Registration addDataProviderListener(DataProviderListener<AppUser> dataProviderListener) {
return null;
}
});
add(grid);
}
}

File diff suppressed because one or more lines are too long

View File

@ -6,5 +6,13 @@ spring.mustache.check-template-location = false
vaadin.launch-browser=true vaadin.launch-browser=true
# To improve the performance during development. # To improve the performance during development.
# For more information https://vaadin.com/docs/latest/integrations/spring/configuration#special-configuration-parameters # For more information https://vaadin.com/docs/latest/integrations/spring/configuration#special-configuration-parameters
vaadin.allowed-packages = com.vaadin,org.vaadin,com.primefactorsolutions vaadin.allowed-packages = com.vaadin,org.vaadin,com.primefactorsolutions,com.hilerio.ace,com.flowingcode.vaadin
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=sa
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.defer-datasource-initialization = true spring.jpa.defer-datasource-initialization = true
spring.h2.console.enabled=true

View File

@ -0,0 +1,10 @@
insert into app_user (id, version, email) values ('23471ab3-f639-4d2b-9541-7227f4ea7ee6', 1, 'foo@bar.com');
insert into question (id, version, content) values ('a7e00ff8-da41-4624-b31c-1b13c3f2e3ae', 1, 'foo bar');
insert into question (id, version, content) values ('8a4b213c-ca81-4c38-b56d-d7028c2dde88', 1, 'foo buzz');
insert into assessment(id, version, app_user_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 1, '23471ab3-f639-4d2b-9541-7227f4ea7ee6');
insert into ASSESSMENT_QUESTIONS (assessment_id, questions_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 'a7e00ff8-da41-4624-b31c-1b13c3f2e3ae');
insert into ASSESSMENT_QUESTIONS (assessment_id, questions_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', '8a4b213c-ca81-4c38-b56d-d7028c2dde88');

View File

@ -0,0 +1,55 @@
package com.primefactorsolutions;
import com.primefactorsolutions.service.compiler.InMemoryClass;
import java.util.*;
import java.lang.reflect.*;
import java.util.stream.Collectors;
public class TestClass implements InMemoryClass {
// ------------ IMPLEMENTAR AQUI --------------------------------
/**
*
*/
public static int getFibonaci(int n) {
// TODO: implementar aca
throw new RuntimeException("not implemented yet");
}
// ------------- NO MODIFICAR DESDE ESTA LINEA --------------------
public static class Tests {
public static Map.Entry<String, Boolean> testFibonaciSingle() {
boolean result = getFibonaci(1) == 0;
return Map.entry("fibonaci primer", result);
}
public static Map.Entry<String, Boolean> testFibonaciInvalido() {
try {
getFibonaci(-1);
return Map.entry("palindrome vacio es valido", false);
} catch (IllegalArgumentException e) {
return Map.entry("palindrome vacio es valido", true);
}
}
}
@Override
public Map<String, Boolean> runCode() {
Map<String, Boolean> results = Arrays.stream(Tests.class.getDeclaredMethods())
.map(m -> {
try {
return (Map.Entry<String, Boolean>) m.invoke(null);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
return results;
}
}

View File

@ -0,0 +1,60 @@
package com.primefactorsolutions;
import com.primefactorsolutions.service.compiler.InMemoryClass;
import java.util.*;
import java.lang.reflect.*;
import java.util.stream.Collectors;
public class TestClass implements InMemoryClass {
// ------------ IMPLEMENTAR AQUI --------------------------------
public static String getPalindroma(String s) {
return "TODO";
}
// ------------- NO MODIFICAR DESDE ESTA LINEA --------------------
public static class Tests {
public static Map.Entry<String, Boolean> testPalindromaValido() {
boolean result = getPalindroma("Ab").equals("ABBA");
return Map.entry("palindrome es valido", result);
}
public static Map.Entry<String, Boolean> testPalindromaVacio() {
boolean result = getPalindroma("").equals("");
return Map.entry("palindrome vacio es valido", result);
}
public static Map.Entry<String, Boolean> testPalindromaUnico() {
boolean result = getPalindroma("z").equals("ZZ");
return Map.entry("palindrome una solo caracter es valido", result);
}
public static Map.Entry<String, Boolean> testPalindromaNull() {
boolean result = getPalindroma(null) == null;
return Map.entry("palindrome null es valido", result);
}
}
@Override
public Map<String, Boolean> runCode() {
Map<String, Boolean> results = Arrays.stream(Tests.class.getDeclaredMethods())
.map(m -> {
try {
return (Map.Entry<String, Boolean>) m.invoke(null);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
return results;
}
}