diff --git a/src/main/java/nl/tudelft/submit/config/AsyncConfig.java b/src/main/java/nl/tudelft/submit/config/AsyncConfig.java index 9befdd6f331ce0752fd3faaf5c133454207365b5..41fe001e5c5cc31f03bebd7c32b0f1ddcb689d7d 100644 --- a/src/main/java/nl/tudelft/submit/config/AsyncConfig.java +++ b/src/main/java/nl/tudelft/submit/config/AsyncConfig.java @@ -22,10 +22,12 @@ import java.util.concurrent.Executor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration @EnableAsync +@EnableScheduling public class AsyncConfig { @Bean diff --git a/src/main/java/nl/tudelft/submit/controller/GradeController.java b/src/main/java/nl/tudelft/submit/controller/GradeController.java index c5cec0b6dfb3b4b5b769b9320fde657b25d38cc6..ab90de82662ec3df9f9b12951326097621e56815 100644 --- a/src/main/java/nl/tudelft/submit/controller/GradeController.java +++ b/src/main/java/nl/tudelft/submit/controller/GradeController.java @@ -136,10 +136,12 @@ public class GradeController { } } - Optional<CalculatedScore> score = gradeService.executeFormula(new TestGradingFormula( - null, formulaTest.getFormula(), formulaTest.getType()), variables); + Optional<Integer> score = gradeService.executeFormula(formulaTest.getFormula(), formulaTest.getType(), + variables); - return new TestGradeViewDTO(score.map(gradeService::getScoreDisplayString).orElse("Invalid formula")); + return new TestGradeViewDTO( + score.map(s -> gradeService.getScoreDisplayString(s, formulaTest.getType())) + .orElse("Invalid formula")); } } diff --git a/src/main/java/nl/tudelft/submit/controller/ScriptController.java b/src/main/java/nl/tudelft/submit/controller/ScriptController.java index 1def27abb9529fff8dcf2eb795d9c4bb4ca33543..0eb1040f15b152baae08d74d268c5b8d4ec8f185 100644 --- a/src/main/java/nl/tudelft/submit/controller/ScriptController.java +++ b/src/main/java/nl/tudelft/submit/controller/ScriptController.java @@ -25,6 +25,7 @@ import nl.tudelft.submit.dto.id.ScriptTrainIdDTO; import nl.tudelft.submit.dto.id.ScriptWagonIdDTO; import nl.tudelft.submit.dto.patch.script.ScriptPatchDTO; import nl.tudelft.submit.dto.patch.script.ScriptWagonPatchDTO; +import nl.tudelft.submit.dto.view.script.ScriptTrainResultViewDTO; import nl.tudelft.submit.model.Version; import nl.tudelft.submit.model.script.Script; import nl.tudelft.submit.model.script.ScriptTrain; @@ -52,8 +53,7 @@ public class ScriptController { @PostMapping(value = "/feedback", consumes = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("@authorizationService.isSubmissionKeyValid(#feedback.submissionId, #feedback.scriptId, #feedback.key)") public ResponseEntity<Void> addScriptFeedback(@RequestBody ScriptFeedbackReceiveDTO feedback) { - scriptService.addScriptFeedback(feedback, - scriptService.canKeyBeUsedForGrading(feedback.getSubmissionId(), feedback.getScriptId())); + scriptService.addScriptFeedback(feedback); return ResponseEntity.ok(null); } @@ -72,7 +72,17 @@ public class ScriptController { model.addAttribute("script", ScriptCreateDTO.builder().wagon(new ScriptWagonIdDTO()).build()); model.addAttribute("scriptPatch", new ScriptPatchDTO()); - return "/version/train"; + return "/script/train"; + } + + @GetMapping("/status/{trainId}/{submissionId}") + @PreAuthorize("@authorizationService.canViewSubmission(#submissionId)") + public String getScriptStatus(@PathVariable Long trainId, @PathVariable Long submissionId, Model model) { + ScriptTrainResultViewDTO train = scriptService.getScriptTrainResult(trainId, submissionId); + + model.addAttribute("train", train); + + return "/script/status"; } @PostMapping("/train/assignment/{assignmentId}") diff --git a/src/main/java/nl/tudelft/submit/dto/create/script/ScriptFeedbackReceiveDTO.java b/src/main/java/nl/tudelft/submit/dto/create/script/ScriptFeedbackReceiveDTO.java index 3bf86ea4fa163f29f73323b3069ca25d3fe50d1a..3e9a8e277b5650c448c0e6078e0f51b21c639a6c 100644 --- a/src/main/java/nl/tudelft/submit/dto/create/script/ScriptFeedbackReceiveDTO.java +++ b/src/main/java/nl/tudelft/submit/dto/create/script/ScriptFeedbackReceiveDTO.java @@ -23,7 +23,6 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import nl.tudelft.labracore.api.dto.GradeCreateDTO; import nl.tudelft.labracore.api.dto.RoleSummaryDTO; @Data @@ -43,8 +42,6 @@ public class ScriptFeedbackReceiveDTO { private String textualFeedback; - private GradeCreateDTO.SchemeEnum scoreType; - private Integer score; @NotNull diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptFeedbackViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptFeedbackViewDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..49826a60fd3504b1bf1d59c9acec09f5402cb199 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptFeedbackViewDTO.java @@ -0,0 +1,43 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.dto.view.script; + +import javax.persistence.*; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import nl.tudelft.labracore.api.dto.RoleSummaryDTO; +import nl.tudelft.submit.model.script.ScriptResult; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ScriptFeedbackViewDTO { + + private Long id; + + private String feedback; + + private RoleSummaryDTO.TypeEnum visibleFor; + + private ScriptResult result; + +} diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptResultViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptResultViewDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..d755bc299b3327d4551879cc1ae7cc9d1b427fa1 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptResultViewDTO.java @@ -0,0 +1,52 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.dto.view.script; + +import java.time.LocalDateTime; +import java.util.List; + +import lombok.*; +import nl.tudelft.librador.dto.view.View; +import nl.tudelft.submit.enums.ScriptStatus; +import nl.tudelft.submit.model.labracore.SubmitSubmission; +import nl.tudelft.submit.model.script.ScriptResult; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class ScriptResultViewDTO extends View<ScriptResult> { + + private Long id; + + private SubmitSubmission submission; + + private ScriptStatus status; + + private Integer score; + + private ScriptViewDTO script; + + private LocalDateTime timestamp; + + private String autaSubmissionId; + + private List<ScriptFeedbackViewDTO> feedback; + +} diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainResultViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainResultViewDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..8f39054645a9cffd02af572a3960b35086e0e3a5 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainResultViewDTO.java @@ -0,0 +1,59 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.dto.view.script; + +import java.util.List; + +import lombok.*; +import nl.tudelft.librador.SpringContext; +import nl.tudelft.librador.dto.view.View; +import nl.tudelft.submit.enums.ScriptStatus; +import nl.tudelft.submit.model.labracore.SubmitSubmission; +import nl.tudelft.submit.model.script.ScriptTrainResult; +import nl.tudelft.submit.repository.script.ScriptWagonResultRepository; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class ScriptTrainResultViewDTO extends View<ScriptTrainResult> { + + private Long id; + + private SubmitSubmission submission; + + private ScriptStatus status; + + private Integer score; + + private ScriptTrainViewDTO train; + + @PostApply + private List<ScriptWagonResultViewDTO> wagons; + + @Override + public void postApply() { + this.wagons = View.convert(SpringContext.getBean(ScriptWagonResultRepository.class) + .findAllByWagonTrainIdAndSubmissionId(train.getId(), submission.getId()), + ScriptWagonResultViewDTO.class); + + super.postApply(); + } + +} diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainViewDTO.java index fd50a515777f4bcab87f8d908f5163b9ee271925..2dda76a05ff974962c37905c9f754cde9f286817 100644 --- a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainViewDTO.java +++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainViewDTO.java @@ -17,11 +17,10 @@ */ package nl.tudelft.submit.dto.view.script; -import java.util.List; - import lombok.*; +import nl.tudelft.labracore.lib.api.GradeScheme; import nl.tudelft.librador.dto.view.View; -import nl.tudelft.submit.dto.id.VersionIdDTO; +import nl.tudelft.submit.model.grading.GradePart; import nl.tudelft.submit.model.script.ScriptTrain; @Data @@ -33,8 +32,12 @@ public class ScriptTrainViewDTO extends View<ScriptTrain> { private Long id; - private VersionIdDTO version; + private String name; + + private String formula; + + private GradeScheme gradeScheme; - private List<ScriptWagonViewDTO> wagons; + private GradePart gradePart = new GradePart(); } diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptViewDTO.java index 2fa28d4e41b8bb3d332bd7b1fea4a7b809ef8c28..d93293babddaebbe02b742b675092b06c4114e3c 100644 --- a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptViewDTO.java +++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptViewDTO.java @@ -17,9 +17,12 @@ */ package nl.tudelft.submit.dto.view.script; +import javax.persistence.*; + import lombok.*; +import nl.tudelft.labracore.lib.api.GradeScheme; import nl.tudelft.librador.dto.view.View; -import nl.tudelft.submit.dto.id.ScriptWagonIdDTO; +import nl.tudelft.submit.model.grading.GradePart; import nl.tudelft.submit.model.script.Script; @Data @@ -35,6 +38,8 @@ public class ScriptViewDTO extends View<Script> { private String link; - private ScriptWagonIdDTO wagon; + private GradeScheme gradeScheme; + + private GradePart gradePart; } diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonResultViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonResultViewDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..41af2915f8b02e9828c7f25fa60430b0344543b4 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonResultViewDTO.java @@ -0,0 +1,59 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.dto.view.script; + +import java.util.List; + +import lombok.*; +import nl.tudelft.librador.SpringContext; +import nl.tudelft.librador.dto.view.View; +import nl.tudelft.submit.enums.ScriptStatus; +import nl.tudelft.submit.model.labracore.SubmitSubmission; +import nl.tudelft.submit.model.script.ScriptWagonResult; +import nl.tudelft.submit.repository.script.ScriptResultRepository; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class ScriptWagonResultViewDTO extends View<ScriptWagonResult> { + + private Long id; + + private SubmitSubmission submission; + + private ScriptStatus status; + + private Integer score; + + private ScriptWagonViewDTO wagon; + + @PostApply + private List<ScriptResultViewDTO> scripts; + + @Override + public void postApply() { + this.scripts = View.convert(SpringContext.getBean(ScriptResultRepository.class) + .findAllByScriptWagonIdAndSubmissionId(wagon.getId(), submission.getId()), + ScriptResultViewDTO.class); + + super.postApply(); + } + +} diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonViewDTO.java index 499d3c4ba0f7ffefdb1ff683e0e2dd4811dec147..d298c9592d76517a1eaeb25a095f8168b8b5ee20 100644 --- a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonViewDTO.java +++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonViewDTO.java @@ -17,11 +17,12 @@ */ package nl.tudelft.submit.dto.view.script; -import java.util.List; +import javax.persistence.*; import lombok.*; +import nl.tudelft.labracore.lib.api.GradeScheme; import nl.tudelft.librador.dto.view.View; -import nl.tudelft.submit.dto.id.ScriptTrainIdDTO; +import nl.tudelft.submit.model.grading.GradePart; import nl.tudelft.submit.model.script.ScriptWagon; @Data @@ -35,8 +36,10 @@ public class ScriptWagonViewDTO extends View<ScriptWagon> { private String name; - private List<ScriptViewDTO> scripts; + private String formula; - private ScriptTrainIdDTO train; + private GradeScheme gradeScheme; + + private GradePart gradePart = new GradePart(); } diff --git a/src/main/java/nl/tudelft/submit/enums/GradeCalculation.java b/src/main/java/nl/tudelft/submit/enums/GradeCalculation.java new file mode 100644 index 0000000000000000000000000000000000000000..8d2963c8aca3dae9c17fdd4b256472a68233f019 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/enums/GradeCalculation.java @@ -0,0 +1,24 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.enums; + +public enum GradeCalculation { + + FORMULA, AUTOMATIC; + +} diff --git a/src/main/java/nl/tudelft/submit/enums/ScriptStatus.java b/src/main/java/nl/tudelft/submit/enums/ScriptStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..46b742e7cab980420fdf627d41c5eb90eae91165 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/enums/ScriptStatus.java @@ -0,0 +1,24 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.enums; + +public enum ScriptStatus { + + NOT_STARTED, RUNNING, DONE, FAILED, PROCESSED; + +} diff --git a/src/main/java/nl/tudelft/submit/exception/InvalidFormulaException.java b/src/main/java/nl/tudelft/submit/exception/InvalidFormulaException.java index 54f68c67b8d5c1a34b6be25c4c83de5037eb5902..a859b06c735ed86845682d6cb286c599e78210fe 100644 --- a/src/main/java/nl/tudelft/submit/exception/InvalidFormulaException.java +++ b/src/main/java/nl/tudelft/submit/exception/InvalidFormulaException.java @@ -17,12 +17,10 @@ */ package nl.tudelft.submit.exception; -import nl.tudelft.submit.model.grading.GradingFormula; - public class InvalidFormulaException extends Exception { - public InvalidFormulaException(GradingFormula<?> formula) { - super("Formula is invalid: " + formula.getFormula()); + public InvalidFormulaException(String formula) { + super("Formula is invalid: " + formula); } } diff --git a/src/main/java/nl/tudelft/submit/model/Version.java b/src/main/java/nl/tudelft/submit/model/Version.java index 7f2e0422e0ae1f0c652e5a067b0db92d2efae413..c8e51e8de74f4cb19c7cae52a3d32a7c919ff3c3 100644 --- a/src/main/java/nl/tudelft/submit/model/Version.java +++ b/src/main/java/nl/tudelft/submit/model/Version.java @@ -26,10 +26,7 @@ import javax.persistence.Id; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; import nl.tudelft.submit.dto.helper.VersionDescription; import nl.tudelft.submit.model.labracore.SubmitGroup; import nl.tudelft.submit.model.script.ScriptTrain; @@ -55,10 +52,12 @@ public class Version { @NotNull private Long assignmentId; - @Builder.Default @ManyToMany + @Builder.Default + @ToString.Exclude private List<SubmitGroup> groups = new ArrayList<>(); @OneToOne(mappedBy = "version") + @ToString.Exclude private ScriptTrain scriptTrain; } diff --git a/src/main/java/nl/tudelft/submit/model/grading/GradePart.java b/src/main/java/nl/tudelft/submit/model/grading/GradePart.java new file mode 100644 index 0000000000000000000000000000000000000000..0245bcc1a664ddb00c3d3eed27a3d28c80d02bd8 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/model/grading/GradePart.java @@ -0,0 +1,45 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.model.grading; + +import javax.persistence.Embeddable; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +public class GradePart { + + @Min(0) + @NotNull + @Builder.Default + private Integer weight = 0; + + @NotNull + @Builder.Default + private Boolean required = false; + +} diff --git a/src/main/java/nl/tudelft/submit/model/script/AbstractScriptResult.java b/src/main/java/nl/tudelft/submit/model/script/AbstractScriptResult.java new file mode 100644 index 0000000000000000000000000000000000000000..c205272929957e32a5bf52bebd5994b3e40ac9f9 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/model/script/AbstractScriptResult.java @@ -0,0 +1,52 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.model.script; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import nl.tudelft.submit.enums.ScriptStatus; +import nl.tudelft.submit.model.labracore.SubmitSubmission; + +@Data +@SuperBuilder +@MappedSuperclass +@NoArgsConstructor +@AllArgsConstructor +public abstract class AbstractScriptResult { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + @NotNull + @ManyToOne + private SubmitSubmission submission; + + @NotNull + @Builder.Default + private ScriptStatus status = ScriptStatus.NOT_STARTED; + + private Integer score; + +} diff --git a/src/main/java/nl/tudelft/submit/model/script/Script.java b/src/main/java/nl/tudelft/submit/model/script/Script.java index 1b9b8a6280b8a5ac06337d79b8df2ad0237d95be..154e6e6d514c3856b9325b78da6531f862fe2bcc 100644 --- a/src/main/java/nl/tudelft/submit/model/script/Script.java +++ b/src/main/java/nl/tudelft/submit/model/script/Script.java @@ -17,10 +17,16 @@ */ package nl.tudelft.submit.model.script; +import java.util.ArrayList; +import java.util.List; + import javax.persistence.*; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import lombok.*; +import nl.tudelft.labracore.lib.api.GradeScheme; +import nl.tudelft.submit.model.grading.GradePart; @Data @Entity @@ -33,15 +39,31 @@ public class Script { @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; + @NotBlank private String name; @NotBlank private String link; + @NotNull + @Builder.Default + private GradeScheme gradeScheme = GradeScheme.DUTCH_GRADE; + + @NotNull + @Embedded + @Builder.Default + private GradePart gradePart = new GradePart(); + @ManyToOne @ToString.Exclude private ScriptWagon wagon; + @Builder.Default + @ToString.Exclude + @EqualsAndHashCode.Exclude + @OneToMany(mappedBy = "script") + private List<ScriptResult> results = new ArrayList<>(); + /** * Returns whether the script is a script for AuTA. * @@ -50,5 +72,4 @@ public class Script { public boolean isAutaScript() { return link.startsWith("auta:"); } - } diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptFeedback.java b/src/main/java/nl/tudelft/submit/model/script/ScriptFeedback.java new file mode 100644 index 0000000000000000000000000000000000000000..67be967b00af4246e75e1aa1d6bfeec285caa165 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/model/script/ScriptFeedback.java @@ -0,0 +1,55 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.model.script; + +import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import nl.tudelft.labracore.api.dto.RoleSummaryDTO; + +@Data +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ScriptFeedback { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + @Lob + @Size(max = 16384) + @NotBlank + private String feedback; + + @NotNull + @Builder.Default + private RoleSummaryDTO.TypeEnum visibleFor = RoleSummaryDTO.TypeEnum.STUDENT; + + @NotNull + @ManyToOne + private ScriptResult result; + +} diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptResult.java b/src/main/java/nl/tudelft/submit/model/script/ScriptResult.java new file mode 100644 index 0000000000000000000000000000000000000000..42ad80d71765436cd3c048fef3cbbdc6bde43891 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/model/script/ScriptResult.java @@ -0,0 +1,61 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.model.script; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; + +import lombok.*; +import lombok.experimental.SuperBuilder; +import nl.tudelft.submit.enums.ScriptStatus; + +@Data +@Entity +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class ScriptResult extends AbstractScriptResult { + + @NotNull + @ManyToOne + private Script script; + + @NotNull + @Builder.Default + private LocalDateTime timestamp = LocalDateTime.now(); + + private String autaSubmissionId; + + @NotNull + @Builder.Default + @ToString.Exclude + @OneToMany(mappedBy = "result") + private List<ScriptFeedback> feedback = new ArrayList<>(); + + public void setStatus(ScriptStatus status) { + super.setStatus(status); + this.timestamp = LocalDateTime.now(); + } + +} diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptTrain.java b/src/main/java/nl/tudelft/submit/model/script/ScriptTrain.java index bc969f310f781cbb98e9458b604df80c6d39de2b..8bfcef8e4e1468136ac4e2a38adf704a8aedf745 100644 --- a/src/main/java/nl/tudelft/submit/model/script/ScriptTrain.java +++ b/src/main/java/nl/tudelft/submit/model/script/ScriptTrain.java @@ -21,26 +21,45 @@ import java.util.ArrayList; import java.util.List; import javax.persistence.*; +import javax.validation.constraints.NotNull; import lombok.*; +import nl.tudelft.labracore.lib.api.GradeScheme; import nl.tudelft.submit.model.Version; +import nl.tudelft.submit.model.grading.GradePart; @Data @Entity @Builder -@AllArgsConstructor @NoArgsConstructor +@AllArgsConstructor public class ScriptTrain { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; + private String formula; + + @NotNull + @Builder.Default + private GradeScheme gradeScheme = GradeScheme.DUTCH_GRADE; + + @Embedded + private GradePart gradePart; + @OneToOne private Version version; - @Builder.Default @OneToMany(mappedBy = "train") + @Builder.Default + @ToString.Exclude @EqualsAndHashCode.Exclude private List<ScriptWagon> wagons = new ArrayList<>(); + + @Builder.Default + @ToString.Exclude + @EqualsAndHashCode.Exclude + @OneToMany(mappedBy = "train") + private List<ScriptTrainResult> results = new ArrayList<>(); } diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptTrainResult.java b/src/main/java/nl/tudelft/submit/model/script/ScriptTrainResult.java new file mode 100644 index 0000000000000000000000000000000000000000..b96c6e3d9bb9891561b50f4d59bc65e6d8791068 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/model/script/ScriptTrainResult.java @@ -0,0 +1,40 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.model.script; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.validation.constraints.NotNull; + +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Data +@Entity +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class ScriptTrainResult extends AbstractScriptResult { + + @NotNull + @ManyToOne + private ScriptTrain train; + +} diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptWagon.java b/src/main/java/nl/tudelft/submit/model/script/ScriptWagon.java index c3eaa1507980f3f8324929ed9a0d590bb7107b1f..10f7f76e51219d8346fe028ace142c9bc6e995d0 100644 --- a/src/main/java/nl/tudelft/submit/model/script/ScriptWagon.java +++ b/src/main/java/nl/tudelft/submit/model/script/ScriptWagon.java @@ -21,8 +21,12 @@ import java.util.ArrayList; import java.util.List; import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import lombok.*; +import nl.tudelft.labracore.lib.api.GradeScheme; +import nl.tudelft.submit.model.grading.GradePart; @Data @Entity @@ -35,14 +39,34 @@ public class ScriptWagon { @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; + @NotBlank private String name; + private String formula; + + @NotNull + @Builder.Default + private GradeScheme gradeScheme = GradeScheme.DUTCH_GRADE; + + @NotNull + @Embedded + @Builder.Default + private GradePart gradePart = new GradePart(); + + @ManyToOne + @ToString.Exclude + private ScriptTrain train; + @OneToMany(mappedBy = "wagon") @Builder.Default + @ToString.Exclude @EqualsAndHashCode.Exclude private List<Script> scripts = new ArrayList<>(); - @ManyToOne + @Builder.Default @ToString.Exclude - private ScriptTrain train; + @EqualsAndHashCode.Exclude + @OneToMany(mappedBy = "wagon") + private List<ScriptWagonResult> results = new ArrayList<>(); + } diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptWagonResult.java b/src/main/java/nl/tudelft/submit/model/script/ScriptWagonResult.java new file mode 100644 index 0000000000000000000000000000000000000000..17a980f894db7551de216ab45ba857a43ce34e40 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/model/script/ScriptWagonResult.java @@ -0,0 +1,40 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.model.script; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.validation.constraints.NotNull; + +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Data +@Entity +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class ScriptWagonResult extends AbstractScriptResult { + + @NotNull + @ManyToOne + private ScriptWagon wagon; + +} diff --git a/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java b/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java index 79debd59b056663a54ab612d2323b3d80f861763..2e5e86e78a0da767a2d8b32adbc337bc01c40d5f 100644 --- a/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java +++ b/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java @@ -47,9 +47,6 @@ public class SubmissionKey { @NotNull private String key; - @NotNull - private Boolean canGrade; - @NotNull private LocalDateTime expires; diff --git a/src/main/java/nl/tudelft/submit/repository/script/AbstractScriptResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/AbstractScriptResultRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..0443dfcf9df6cfd3d5e7b7abdf1730557a3e11dd --- /dev/null +++ b/src/main/java/nl/tudelft/submit/repository/script/AbstractScriptResultRepository.java @@ -0,0 +1,46 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.repository.script; + +import java.util.List; + +import nl.tudelft.submit.enums.ScriptStatus; +import nl.tudelft.submit.model.script.AbstractScriptResult; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; + +@NoRepositoryBean +public interface AbstractScriptResultRepository<R extends AbstractScriptResult> + extends JpaRepository<R, Long> { + + /** + * Finds the Result by id or throws a ResourceNotFoundException. + * + * @param id the id of the script + * @return the Result object + */ + default R findByIdOrThrow(Long id) { + return findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Result was not found: " + id)); + } + + List<R> findAllByStatus(ScriptStatus status); + +} diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptFeedbackRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptFeedbackRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..4f455e3c0429f53641aa4961039ae2cd0e6b3769 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptFeedbackRepository.java @@ -0,0 +1,36 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.repository.script; + +import java.util.List; + +import nl.tudelft.submit.model.script.ScriptFeedback; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScriptFeedbackRepository extends JpaRepository<ScriptFeedback, Long> { + + List<ScriptFeedback> findAllByResultId(Long resultId); + + default List<ScriptFeedback> findAllByTrainId(Long trainId) { + return findAllByResultScriptWagonTrainId(trainId); + } + + List<ScriptFeedback> findAllByResultScriptWagonTrainId(Long trainId); + +} diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptRepository.java index 5f5fe5764420e186430dae493f4e01e8523586eb..d92f7fa2cd20bface1dc1f81feaaeb89f2da9bdc 100644 --- a/src/main/java/nl/tudelft/submit/repository/script/ScriptRepository.java +++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptRepository.java @@ -17,6 +17,7 @@ */ package nl.tudelft.submit.repository.script; +import java.util.List; import java.util.Optional; import nl.tudelft.submit.model.script.Script; @@ -39,4 +40,6 @@ public interface ScriptRepository extends JpaRepository<Script, Long> { Optional<Script> findById(Long id); + List<Script> findAllByWagonId(Long id); + } diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..c699370d53bfa58d5c8c15ac45dd0e83b5dcc53d --- /dev/null +++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java @@ -0,0 +1,32 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.repository.script; + +import java.util.List; + +import nl.tudelft.submit.model.script.ScriptResult; + +public interface ScriptResultRepository extends AbstractScriptResultRepository<ScriptResult> { + + ScriptResult findByScriptIdAndSubmissionId(Long scriptId, Long submissionId); + + List<ScriptResult> findAllByScriptId(Long scriptId); + + List<ScriptResult> findAllByScriptWagonIdAndSubmissionId(Long wagonId, Long submissionId); + +} diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptTrainResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptTrainResultRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..3d37ecd90820b70a36080013648ee422b868ce15 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptTrainResultRepository.java @@ -0,0 +1,30 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.repository.script; + +import java.util.List; + +import nl.tudelft.submit.model.script.ScriptTrainResult; + +public interface ScriptTrainResultRepository extends AbstractScriptResultRepository<ScriptTrainResult> { + + List<ScriptTrainResult> findAllByTrainId(Long trainId); + + ScriptTrainResult findByTrainIdAndSubmissionId(Long trainId, Long submissionId); + +} diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonRepository.java index 8a268a4a20bfe951774e59e3e0e66835db9259b0..18bdb25379411ad055cd2d9c860fdd5f396070ca 100644 --- a/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonRepository.java +++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonRepository.java @@ -17,6 +17,8 @@ */ package nl.tudelft.submit.repository.script; +import java.util.List; + import nl.tudelft.submit.model.script.ScriptWagon; import org.springframework.data.jpa.repository.JpaRepository; @@ -35,4 +37,6 @@ public interface ScriptWagonRepository extends JpaRepository<ScriptWagon, Long> .orElseThrow(() -> new ResourceNotFoundException("ScriptWagonId was not found: " + id)); } + List<ScriptWagon> findAllByTrainId(Long trainId); + } diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonResultRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..4c847620bc7efdf66ce02242f43cdfc757a367a5 --- /dev/null +++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonResultRepository.java @@ -0,0 +1,32 @@ +/* + * Submit + * Copyright (C) 2020 - Delft University of Technology + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package nl.tudelft.submit.repository.script; + +import java.util.List; + +import nl.tudelft.submit.model.script.ScriptWagonResult; + +public interface ScriptWagonResultRepository extends AbstractScriptResultRepository<ScriptWagonResult> { + + List<ScriptWagonResult> findAllByWagonId(Long wagonId); + + ScriptWagonResult findByWagonIdAndSubmissionId(Long wagonId, Long submissionId); + + List<ScriptWagonResult> findAllByWagonTrainIdAndSubmissionId(Long wagonId, Long submissionId); + +} diff --git a/src/main/java/nl/tudelft/submit/security/AuthorizationService.java b/src/main/java/nl/tudelft/submit/security/AuthorizationService.java index a4cc67989158d400bc7c29b00c3831fa154cf612..a1ea8dd1c13f6d7f852cddb66fb7f2ccbdfc3089 100644 --- a/src/main/java/nl/tudelft/submit/security/AuthorizationService.java +++ b/src/main/java/nl/tudelft/submit/security/AuthorizationService.java @@ -27,7 +27,7 @@ import nl.tudelft.labracore.lib.security.LabradorUserDetails; import nl.tudelft.labracore.lib.security.user.DefaultRole; import nl.tudelft.labracore.lib.security.user.Person; import nl.tudelft.submit.cache.*; -import nl.tudelft.submit.dto.view.FeedbackViewDTO; +import nl.tudelft.submit.model.Feedback; import nl.tudelft.submit.model.script.SubmissionKey; import nl.tudelft.submit.repository.SubmissionKeyRepository; import nl.tudelft.submit.repository.VersionRepository; @@ -976,6 +976,17 @@ public class AuthorizationService { // region Submission + /** + * Checks whether the authenticated user can view a submission. + * + * @param submissionId The id of the submission + * @return True iff the user can view the submission + */ + public boolean canViewSubmission(Long submissionId) { + Long groupId = submissionCache.getOrThrow(submissionId).getGroup().getId(); + return canViewGroup(groupId); + } + /** * Checks whether the authenticated user can resubmit a submission. * @@ -1130,7 +1141,7 @@ public class AuthorizationService { * @param feedback The feedback to view * @return True iff the user can view the feedback */ - public boolean canViewGroupFeedback(Long groupId, FeedbackViewDTO feedback) { + public boolean canViewGroupFeedback(Long groupId, Feedback feedback) { if (feedback.getVisibleFor() == null) { return true; } diff --git a/src/main/java/nl/tudelft/submit/service/FormulaService.java b/src/main/java/nl/tudelft/submit/service/FormulaService.java index 94695033fcd1fed0c0e12c3c5954afa005e14045..50b730fa4f13e15d2ea2dd099c4065ac73279d6d 100644 --- a/src/main/java/nl/tudelft/submit/service/FormulaService.java +++ b/src/main/java/nl/tudelft/submit/service/FormulaService.java @@ -176,9 +176,9 @@ public class FormulaService { * @param formula The formula to parse * @return The resulting expression */ - public Expression parseFormula(GradingFormula<?> formula) throws InvalidFormulaException { + public Expression parseFormula(String formula) throws InvalidFormulaException { Result<Token, Expression> result = parser - .parse(new TokenInput(tokenise(input(formula.getFormula())))); + .parse(new TokenInput(tokenise(input(formula)))); if (!result.isSuccess()) { throw new InvalidFormulaException(formula); } diff --git a/src/main/java/nl/tudelft/submit/service/GradeService.java b/src/main/java/nl/tudelft/submit/service/GradeService.java index f514d5adc7dc3b67ae58eca7b272f6983283e25d..594d691e4cd7f431cb5fc147ed5b0ed60c898acd 100644 --- a/src/main/java/nl/tudelft/submit/service/GradeService.java +++ b/src/main/java/nl/tudelft/submit/service/GradeService.java @@ -116,13 +116,15 @@ public class GradeService { * Executes a grading formula. * * @param formula The formula to execute + * @param scheme The grading scheme + * @parma variables The formula variables * @return The resulting score */ - public <E_ID extends Serializable> Optional<CalculatedScore> executeFormula( - GradingFormula<E_ID> formula, Map<String, Object> variables) { + public Optional<Integer> executeFormula(String formula, GradeScheme scheme, + Map<String, Object> variables) { try { Object result = formulaService.parseFormula(formula).execute(variables); - return Optional.ofNullable(convertToScore(formula, result)); + return Optional.ofNullable(convertToScore(result, scheme)); } catch (InvalidFormulaException e) { return Optional.empty(); } catch (Exception e) { @@ -132,27 +134,24 @@ public class GradeService { } /** - * Converts a grading formula and its result to a CalculatedScore. + * Converts a grading result to a score. * - * @param formula The formula used to calculate the result - * @param result The result to convert - * @return The calculated score object + * @param result The result to convert + * @param scheme The grading scheme */ - public <E_ID extends Serializable, FORMULA extends GradingFormula<E_ID>> CalculatedScore convertToScore( - FORMULA formula, Object result) { + public Integer convertToScore(Object result, GradeScheme scheme) { if (result == null) { return null; } - if ((result.equals("PASSED") || result.equals("FAILED")) - && formula.getType() != GradeScheme.PASS_FAIL) { + if ((result.equals("PASSED") || result.equals("FAILED")) && scheme != GradeScheme.PASS_FAIL) { return null; } - Double score = result.equals("PASSED") ? 1.0 - : result.equals("FAILED") ? 0.0 - : result instanceof Integer ? ((Integer) result).doubleValue() : (Double) result; - - return formula.calculateScore(score); + Integer res = result.equals("PASSED") ? 1 + : result.equals("FAILED") ? 0 + : result instanceof Integer ? (Integer) result + : (int) ((double) result * (scheme.getMax() / 10)); + return scheme.getMin() <= res && scheme.getMax() >= res ? res : null; } /** @@ -269,12 +268,15 @@ public class GradeService { } // Calculate grade - executeFormula(formula, variables).ifPresent(score -> { - score.setId(scoreId); - moduleScoreRepository.save((ModuleCalculatedScore) score); - - sendUpdateNotification(module, person, score); - }); + // executeFormula(formula.getFormula(), formula.getType(), variables).ifPresent(score -> { + // moduleScoreRepository.save(ModuleCalculatedScore.builder() + // .id(scoreId) + // .score(score) + // .type(formula.getType()) + // .build()); + // + // sendUpdateNotification(module, person, score); + // }); TODO } /** @@ -406,9 +408,11 @@ public class GradeService { .max(Comparator.comparing(SubmissionDetailsDTO::getSubmissionTime)); if (latest.isPresent() && !latest.get().getGrades().isEmpty()) { GradeSummaryDTO score = getScoreToLoad(latest.get()); - variables.put(varName, toFormulaVariable(score)); + variables.put(varName, + toFormulaVariable(score.getScore(), GradeScheme.valueOf(score.getScheme().name()))); variables.put(varName + "_isScript", score.getIsScriptGraded()); - variables.put(varName + "_noScript", score.getIsScriptGraded() ? 0.0 : toFormulaVariable(score)); + variables.put(varName + "_noScript", score.getIsScriptGraded() ? 0.0 + : toFormulaVariable(score.getScore(), GradeScheme.valueOf(score.getScheme().name()))); } else { variables.put(varName, 0.0); variables.put(varName + "_isScript", false); @@ -484,10 +488,10 @@ public class GradeService { moduleScoreRepository.findById(module.getId(), personId) .map(CalculatedScore::getScore).orElse(0.0)); } - executeFormula(formula, variables).ifPresent(score -> { - score.setId(new CalculatedScore.Id(editionId.getId(), personId.getId())); - editionScoreRepository.save((EditionCalculatedScore) score); - }); + // executeFormula(formula, variables).ifPresent(score -> { + // score.setId(new CalculatedScore.Id(editionId.getId(), personId.getId())); + // editionScoreRepository.save((EditionCalculatedScore) score); + // }); } /** @@ -585,6 +589,26 @@ public class GradeService { return score.getScore() >= edition.getPassingScore(); } + /** + * Returns the display string for a score. + * + * @param score The score to display + * @param scheme The grade scheme + * @return The formatted score + */ + public String getScoreDisplayString(Integer score, GradeScheme scheme) { + switch (scheme) { + case PASS_FAIL: + return score == 0 ? "Failed" : "Passed"; + case DUTCH_GRADE: + return GRADE_FORMAT_10.format(score / 10.0); + case DUTCH_GRADE_1000: + return GRADE_FORMAT_100.format(score / 100.0); + default: + return Integer.toString(score); + } + } + /** * Returns the display string for a score. * @@ -643,16 +667,19 @@ public class GradeService { * * @return The value of this score as usable object for a variable */ - public Object toFormulaVariable(GradeSummaryDTO score) { - switch (score.getScheme()) { + public Object toFormulaVariable(Integer score, GradeScheme scheme) { + if (score == null || score == null) { + return 0.0; + } + switch (scheme) { case PASS_FAIL: - return score.getScore() == 0 ? 0.0 : 1.0; + return score == 0 ? 0.0 : 1.0; case DUTCH_GRADE: - return score.getScore() / 10.0; + return score / 10.0; case DUTCH_GRADE_1000: - return score.getScore() / 100.0; + return score / 100.0; default: - return score.getScore().doubleValue(); + return score.doubleValue(); } } diff --git a/src/main/java/nl/tudelft/submit/service/ScriptService.java b/src/main/java/nl/tudelft/submit/service/ScriptService.java index 921b857006c5678dc298ddd03e4c4281134fb1f3..f16fa6d2a1bbdd5ad22cf6a2a350d88d9601b8c9 100644 --- a/src/main/java/nl/tudelft/submit/service/ScriptService.java +++ b/src/main/java/nl/tudelft/submit/service/ScriptService.java @@ -20,7 +20,6 @@ package nl.tudelft.submit.service; import java.io.File; import java.time.LocalDateTime; import java.util.*; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import javax.transaction.Transactional; @@ -29,6 +28,7 @@ import nl.tudelft.labracore.api.AssignmentControllerApi; import nl.tudelft.labracore.api.StudentGroupControllerApi; import nl.tudelft.labracore.api.SubmissionControllerApi; import nl.tudelft.labracore.api.dto.*; +import nl.tudelft.labracore.lib.api.GradeScheme; import nl.tudelft.librador.dto.view.View; import nl.tudelft.submit.dto.create.FeedbackCreateDTO; import nl.tudelft.submit.dto.create.NotificationCreateDTO; @@ -41,17 +41,15 @@ import nl.tudelft.submit.dto.id.VersionIdDTO; import nl.tudelft.submit.dto.patch.script.ScriptPatchDTO; import nl.tudelft.submit.dto.patch.script.ScriptTrainPatchDTO; import nl.tudelft.submit.dto.patch.script.ScriptWagonPatchDTO; -import nl.tudelft.submit.dto.view.script.ScriptWagonViewDTO; +import nl.tudelft.submit.dto.view.script.ScriptTrainResultViewDTO; import nl.tudelft.submit.enums.NotificationPreference; +import nl.tudelft.submit.enums.ScriptStatus; import nl.tudelft.submit.model.Version; -import nl.tudelft.submit.model.script.Script; -import nl.tudelft.submit.model.script.ScriptTrain; -import nl.tudelft.submit.model.script.ScriptWagon; -import nl.tudelft.submit.model.script.SubmissionKey; +import nl.tudelft.submit.model.labracore.SubmitSubmission; +import nl.tudelft.submit.model.script.*; import nl.tudelft.submit.repository.SubmissionKeyRepository; -import nl.tudelft.submit.repository.script.ScriptRepository; -import nl.tudelft.submit.repository.script.ScriptTrainRepository; -import nl.tudelft.submit.repository.script.ScriptWagonRepository; +import nl.tudelft.submit.repository.labracore.SubmitSubmissionRepository; +import nl.tudelft.submit.repository.script.*; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; @@ -60,6 +58,7 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.http.*; import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -103,9 +102,24 @@ public class ScriptService { @Autowired private ScriptWagonRepository scriptWagonRepository; + @Autowired + private ScriptTrainResultRepository scriptTrainResultRepository; + + @Autowired + private ScriptWagonResultRepository scriptWagonResultRepository; + + @Autowired + private ScriptResultRepository scriptResultRepository; + @Autowired private SubmissionKeyRepository submissionKeyRepository; + @Autowired + private SubmitSubmissionRepository submissionRepository; + + @Autowired + private ScriptFeedbackRepository scriptFeedbackRepository; + @Autowired private SubmissionService submissionService; @@ -133,6 +147,9 @@ public class ScriptService { @Autowired private NotificationService notificationService; + @Autowired + private FormulaService formulaService; + @Autowired private EmailService emailService; @@ -191,41 +208,32 @@ public class ScriptService { * @param autaToken A valid access token * @param autaAssignmentId The id of the assignment in AuTA * @param autaSubmissionId The id of the submission in AuTA - * @return The feedback from AuTA + * @return The feedback from AuTA, if it is done */ - public AutaFeedback pollAuta(String autaToken, String autaAssignmentId, String autaSubmissionId) { - AutaFeedback feedback; - int secondsPassed = 0; - do { - try { - Thread.sleep(10000L); - secondsPassed += 10; - } catch (InterruptedException e) { - e.printStackTrace(); - } - - HttpHeaders headers = new HttpHeaders(); - headers.set("Auth-Token", autaToken); + public Optional<AutaFeedback> pollAuta(String autaToken, String autaAssignmentId, + String autaSubmissionId) { + HttpHeaders headers = new HttpHeaders(); + headers.set("Auth-Token", autaToken); - HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>( - new LinkedMultiValueMap<>(), headers); + HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>( + new LinkedMultiValueMap<>(), headers); - ResponseEntity<AutaFeedback> response = restTemplate.exchange( - autaUrl + "api/v1/assignment/{aid}/submission/{sid}", HttpMethod.GET, requestEntity, - AutaFeedback.class, autaAssignmentId, autaSubmissionId); - feedback = response.getBody(); - } while (feedback.getPipelineLog().getReportDone() == null && secondsPassed <= timeout); + ResponseEntity<AutaFeedback> response = restTemplate.exchange( + autaUrl + "api/v1/assignment/{aid}/submission/{sid}", HttpMethod.GET, requestEntity, + AutaFeedback.class, autaAssignmentId, autaSubmissionId); + AutaFeedback feedback = response.getBody(); - return feedback; + return feedback.getPipelineLog().getReportDone() == null ? Optional.of(feedback) : Optional.empty(); } /** * Saves feedback from AuTA in the database as feedback and grade. * + * @param scriptId The id of the script * @param submissionId The id of the submission * @param autaFeedback The feedback from AuTA */ - public boolean saveAutaFeedback(Long submissionId, AutaFeedback autaFeedback, boolean canGrade) { + public boolean saveAutaFeedback(Long scriptId, Long submissionId, AutaFeedback autaFeedback) { List<AutaResults> results = getAutaResults(autaFeedback.getResults()); List<AutaReport> reports = results.stream().flatMap(r -> r.getReports().stream()) .collect(Collectors.toList()); @@ -235,44 +243,33 @@ public class ScriptService { return false; } - StringBuilder feedback = new StringBuilder(); + StringBuilder feedbackBuilder = new StringBuilder(); boolean verdictPassed = true; for (AutaReport report : reports) { if (report.getVerdict().equals("FAIL")) { verdictPassed = false; } for (AutaNote note : report.getNotes().getNotes()) { - feedback.append(note.getMessage()).append("\n"); + feedbackBuilder.append(note.getMessage()).append("\n"); } } - LocalDateTime now = LocalDateTime.now(); - - Long gradeId = canGrade ? gradeService.addGrade(new GradeCreateDTO() - .isScriptGraded(true) - .scheme(GradeCreateDTO.SchemeEnum.PASS_FAIL) - .gradedAt(now) - .score(verdictPassed ? 1 : 0) - .submission(new SubmissionIdDTO().id(submissionId))) : null; + ScriptResult result = scriptResultRepository.findByScriptIdAndSubmissionId(scriptId, submissionId); + String feedback = feedbackBuilder.toString(); - String textualFeedback = feedback.toString(); - if (!textualFeedback.isBlank()) { - feedbackService.addFeedback(FeedbackCreateDTO.builder() - .isScriptFeedback(true) + if (!feedback.isBlank()) { + ScriptFeedback f = scriptFeedbackRepository.save(ScriptFeedback.builder() + .feedback(feedback) .visibleFor(RoleSummaryDTO.TypeEnum.STUDENT) - .authorId(-1L) - .textualFeedback(textualFeedback) - .timestamp(now) - .submissionId(submissionId) - .gradeId(gradeId) + .result(result) .build()); + result.getFeedback().add(f); } - if (canGrade) { - sendUpdateNotification(submissionId, gradeId); - submissionService.setPendingState(submissionId, false); - gradeService.updateGradesAfterGradedSubmission(submissionId); - } + result.setScore(verdictPassed ? 1 : 0); + + result.setStatus(ScriptStatus.DONE); + scriptResultRepository.save(result); return verdictPassed; } @@ -342,18 +339,18 @@ public class ScriptService { /** * Sends a submission to AuTA. * - * @param submissionId The id of the submission - * @param autaAssignmentId The id of the assignment in AuTA - * @return A future with the feedback + * @param submissionId The id of the submission + * @param script The script to run */ - @Async - public CompletableFuture<AutaFeedback> runAutaScript(Long submissionId, String autaAssignmentId) { + @Transactional + public void runAutaScript(Long submissionId, Script script) { String token = loginToAuta(); - String autaSubmissionId = uploadSubmissionToAuta(token, autaAssignmentId, submissionId); - - AutaFeedback feedback = pollAuta(token, autaAssignmentId, autaSubmissionId); + String autaSubmissionId = uploadSubmissionToAuta(token, script.getLink().substring(5), submissionId); - return CompletableFuture.completedFuture(feedback); + ScriptResult result = scriptResultRepository.findByScriptIdAndSubmissionId(script.getId(), + submissionId); + result.setAutaSubmissionId(autaSubmissionId); + scriptResultRepository.save(result); } // endregion @@ -382,6 +379,19 @@ public class ScriptService { return scriptTrainRepository.findByIdOrThrow(id); } + /** + * Retrieves a script train result from the database. + * + * @param trainId The id of the script train. + * @param submissionId The id of the submission. + * @return The script train result view. + */ + @Transactional + public ScriptTrainResultViewDTO getScriptTrainResult(Long trainId, Long submissionId) { + return View.convert(scriptTrainResultRepository.findByTrainIdAndSubmissionId(trainId, submissionId), + ScriptTrainResultViewDTO.class); + } + /** * Retrieves a scriptTrain from the database or creates a new scriptTrain. * @@ -407,8 +417,8 @@ public class ScriptService { * @param id The id of the scriptWagon. * @return The scriptWagon in View object. */ - public ScriptWagonViewDTO getScriptWagonById(Long id) { - return View.convert(scriptWagonRepository.findByIdOrThrow(id), ScriptWagonViewDTO.class); + public ScriptWagon getScriptWagonById(Long id) { + return scriptWagonRepository.findByIdOrThrow(id); } /** @@ -432,18 +442,6 @@ public class ScriptService { return scriptTrainRepository.findByIdOrThrow(trainId).getVersion().getAssignmentId(); } - /** - * Checks whether a submission id can be graded by a script. - * - * @param submissionId The id of the submission - * @param scriptId The id of the script - * @return True iff the submission can be graded - */ - public boolean canKeyBeUsedForGrading(Long submissionId, Long scriptId) { - return submissionKeyRepository.findBySubmissionIdAndScriptIdOrThrow(submissionId, scriptId) - .getCanGrade(); - } - // endregion // region Add methods @@ -581,56 +579,71 @@ public class ScriptService { * @param submissionId The id of the submission */ @Async + @Transactional public void runScriptTrain(Long trainId, Long submissionId) { - submissionService.setPendingState(submissionId, true); + SubmitSubmission submission = submissionRepository.findByIdOrThrow(submissionId); + submission.setScriptPending(true); + ScriptTrain train = getScriptTrainById(trainId); - Iterator<ScriptWagon> wagons = train.getWagons().iterator(); - while (wagons.hasNext()) { - ScriptWagon wagon = wagons.next(); - if (!runScriptWagon(wagon, submissionId, !wagons.hasNext())) - break; + scriptTrainResultRepository + .save(ScriptTrainResult.builder().train(train).submission(submission).build()); + for (ScriptWagon wagon : train.getWagons()) { + initScriptResultsInWagon(wagon, submission); + } + } + + /** + * Inits the results for a script wagon. + * + * @param wagon The wagon + * @param submission The submission the wagon is for + */ + private void initScriptResultsInWagon(ScriptWagon wagon, SubmitSubmission submission) { + scriptWagonResultRepository + .save(ScriptWagonResult.builder().wagon(wagon).submission(submission).build()); + for (Script script : wagon.getScripts()) { + scriptResultRepository.save(ScriptResult.builder().script(script).submission(submission).build()); } } /** * Runs all scripts in a wagon. * - * @param wagon The wagon - * @param submissionId The id of the submission - * @param isLast Whether the wagon is the last in the train - * @return TODO Whether the scripts in the wagon are passing + * @param wagon The wagon + * @param submissionId The id of the submission */ - public boolean runScriptWagon(ScriptWagon wagon, Long submissionId, boolean isLast) { - Iterator<Script> scripts = wagon.getScripts().iterator(); - while (scripts.hasNext()) { - Script script = scripts.next(); - runScript(script, submissionId, isLast && !scripts.hasNext()); + @Transactional + public void runScriptWagon(ScriptWagon wagon, Long submissionId) { + for (Script script : scriptRepository.findAllByWagonId(wagon.getId())) { + runScript(script, submissionId); } - return true; + ScriptWagonResult result = scriptWagonResultRepository.findByWagonIdAndSubmissionId(wagon.getId(), + submissionId); + result.setStatus(ScriptStatus.RUNNING); + scriptWagonResultRepository.save(result); } /** * Sends a submission to a script. * - * @param script The script - * @param submissionId The id of the submission - * @param isLast Whether the script is the last in the train - * @return True iff the feedback is passing (only works for AuTA scripts atm). + * @param script The script + * @param submissionId The id of the submission */ - private boolean runScript(Script script, Long submissionId, boolean isLast) { + @Transactional + public void runScript(Script script, Long submissionId) { if (script.isAutaScript()) { - CompletableFuture<AutaFeedback> feedback = runAutaScript(submissionId, - script.getLink().substring(5)); - return feedback - .thenApply(f -> saveAutaFeedback(submissionId, f, isLast)) - .join(); + runAutaScript(submissionId, script); } else { List<File> files = fileService.getAllFiles(storageDir + "/submissions", submissionId); Resource resource = new FileSystemResource(files.get(0)); sendFileToURL(resource, script.getLink(), submissionId, script.getId(), - generateKey(submissionId, script.getId(), isLast), isLast); - return true; + generateKey(submissionId, script.getId()), script.getGradeScheme()); } + + ScriptResult result = scriptResultRepository.findByScriptIdAndSubmissionId(script.getId(), + submissionId); + result.setStatus(ScriptStatus.RUNNING); + scriptResultRepository.save(result); } /** @@ -638,10 +651,9 @@ public class ScriptService { * * @param submissionId The id of the submission * @param scriptId The id of the script - * @param grade Whether the script is allowed to grade the submission * @return The key */ - private String generateKey(Long submissionId, Long scriptId, boolean grade) { + private String generateKey(Long submissionId, Long scriptId) { String keyHash = Hashing.sha512() .hashString(scriptId + Long.toString(System.currentTimeMillis()) + submissionId, Charsets.UTF_8) @@ -650,7 +662,6 @@ public class ScriptService { .submissionId(submissionId) .key(keyHash) .scriptId(scriptId) - .canGrade(grade) .expires(LocalDateTime.now().plusHours(1)) .build(); submissionKeyRepository.findBySubmissionIdAndScriptId(submissionId, scriptId) @@ -661,11 +672,15 @@ public class ScriptService { /** * Sends a file to a URL. * - * @param file The file to send - * @param url The URL to send to + * @param file The file to send + * @param url The URL to send to + * @param submissionId The id of the submission + * @param scriptId The id of the script + * @param key The key + * @param gradeScheme The scheme to grade with */ private void sendFileToURL(Resource file, String url, Long submissionId, Long scriptId, String key, - boolean grade) { + GradeScheme gradeScheme) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); @@ -674,7 +689,7 @@ public class ScriptService { body.add("submissionId", submissionId); body.add("scriptId", scriptId); body.add("key", key); - body.add("grade", grade); + body.add("gradeScheme", gradeScheme); HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers); @@ -686,37 +701,237 @@ public class ScriptService { * Adds the feedback from a script. * * @param feedback The feedback to add - * @param grade Whether the feedback should be used for a grade */ - public void addScriptFeedback(ScriptFeedbackReceiveDTO feedback, boolean grade) { - LocalDateTime now = LocalDateTime.now(); - - FeedbackCreateDTO feedbackCreate = FeedbackCreateDTO.builder() - .submissionId(feedback.getSubmissionId()) - .isScriptFeedback(true) - .textualFeedback(feedback.getTextualFeedback()) - .authorId(feedback.getScriptId()) - .timestamp(now) - .visibleFor(feedback.getVisibleFor()) - .build(); + @Transactional + public void addScriptFeedback(ScriptFeedbackReceiveDTO feedback) { + ScriptResult result = scriptResultRepository.findByScriptIdAndSubmissionId(feedback.getScriptId(), + feedback.getSubmissionId()); + + if (feedback.getTextualFeedback() != null && !feedback.getTextualFeedback().isBlank()) { + ScriptFeedback f = scriptFeedbackRepository.save(ScriptFeedback.builder() + .feedback(feedback.getTextualFeedback()) + .visibleFor(feedback.getVisibleFor()) + .result(result) + .build()); + result.getFeedback().add(f); + } - if (grade) { - GradeCreateDTO gradeCreate = new GradeCreateDTO() - .gradedAt(now) - .isScriptGraded(true) - .scheme(feedback.getScoreType()) - .submission(new SubmissionIdDTO().id(feedback.getSubmissionId())) - .score(feedback.getScore()); - Long gradeId = gradeService.addGrade(gradeCreate); - sendUpdateNotification(feedback.getSubmissionId(), gradeId); - gradeService.updateGradesAfterGradedSubmission(feedback.getSubmissionId()); - feedbackCreate.setGradeId(gradeId); - submissionService.setPendingState(feedback.getSubmissionId(), false); + if (result.getScore() == null && feedback.getScore() != null) { + result.setScore(feedback.getScore()); } - feedbackService.addFeedback(feedbackCreate); + result.setStatus(ScriptStatus.DONE); } // endregion + // region Scheduled train management + + @Scheduled(fixedRate = 60000) + public void checkScriptStatuses() { + checkScriptsForTimeouts(); + + finishDoneWagons(); + finishDoneTrains(); + + startWaitingTrains(); + startWaitingWagons(); + + processFinishedTrains(); + + System.out.println(scriptTrainResultRepository.findAll()); + System.out.println(scriptWagonResultRepository.findAll()); + System.out.println(scriptResultRepository.findAll()); + } + + @Transactional + protected void checkScriptsForTimeouts() { + List<ScriptResult> running = scriptResultRepository.findAllByStatus(ScriptStatus.RUNNING); + + LocalDateTime timeoutBefore = LocalDateTime.now().minusSeconds(timeout); + List<ScriptResult> timedOut = running.stream().filter(r -> r.getTimestamp().isBefore(timeoutBefore)) + .collect(Collectors.toList()); + for (ScriptResult r : timedOut) { + r.setStatus(ScriptStatus.FAILED); + + if (r.getScript().getGradePart().getRequired()) { + ScriptWagonResult wagonRes = scriptWagonResultRepository.findByWagonIdAndSubmissionId( + r.getScript().getWagon().getId(), r.getSubmission().getId()); + wagonRes.setStatus(ScriptStatus.FAILED); + scriptWagonResultRepository.save(wagonRes); + + ScriptTrainResult trainRes = scriptTrainResultRepository.findByTrainIdAndSubmissionId( + r.getScript().getWagon().getTrain().getId(), r.getSubmission().getId()); + trainRes.setStatus(ScriptStatus.FAILED); + scriptTrainResultRepository.save(trainRes); + } + } + } + + @Transactional + protected void finishDoneWagons() { + List<ScriptWagonResult> running = scriptWagonResultRepository.findAllByStatus(ScriptStatus.RUNNING); + for (ScriptWagonResult wagon : running) { + List<ScriptResult> res = scriptResultRepository.findAllByScriptWagonIdAndSubmissionId( + wagon.getWagon().getId(), wagon.getSubmission().getId()); + if (res.stream().allMatch( + r -> r.getStatus() == ScriptStatus.DONE || r.getStatus() == ScriptStatus.FAILED)) { + wagon.setStatus(ScriptStatus.DONE); + + if (wagon.getWagon().getFormula() != null) { + Map<String, Object> variables = res.stream().collect(Collectors.toMap( + r -> gradeService.generateVariableName(r.getScript().getName()), + r -> gradeService.toFormulaVariable(r.getScore(), + r.getScript().getGradeScheme()))); + gradeService + .executeFormula(wagon.getWagon().getFormula(), wagon.getWagon().getGradeScheme(), + variables) + .ifPresent(wagon::setScore); + } else if (wagon.getWagon().getGradeScheme() == GradeScheme.PASS_FAIL) { + boolean fail = res.stream() + .filter(r -> r.getScript().getGradeScheme() == GradeScheme.PASS_FAIL) + .filter(r -> r.getScript().getGradePart().getRequired()) + .anyMatch(r -> r.getScore() != null && r.getScore() == 0); + wagon.setScore(fail ? 0 : 1); + } else { + Optional<Integer> weightedSum = res.stream() + .filter(r -> r.getScript().getGradeScheme() != GradeScheme.PASS_FAIL) + .map(r -> r.getScore() == null ? 0 + : r.getScript().getGradePart().getWeight() * r.getScore()) + .reduce(Integer::sum); + Optional<Integer> sumOfWeights = res.stream() + .filter(r -> r.getScript().getGradeScheme() != GradeScheme.PASS_FAIL) + .map(r -> r.getScript().getGradePart().getWeight()) + .reduce(Integer::sum) + .filter(s -> s != 0); + weightedSum.flatMap(w -> sumOfWeights.map(s -> (int) Math.round((double) w / s))) + .ifPresent(wagon::setScore); + } + + scriptWagonResultRepository.save(wagon); + } + } + } + + @Transactional + protected void finishDoneTrains() { + List<ScriptTrainResult> running = scriptTrainResultRepository.findAllByStatus(ScriptStatus.RUNNING); + for (ScriptTrainResult train : running) { + List<ScriptWagonResult> res = scriptWagonResultRepository.findAllByWagonTrainIdAndSubmissionId( + train.getTrain().getId(), train.getSubmission().getId()); + if (res.stream().allMatch(r -> r.getStatus() == ScriptStatus.DONE)) { + train.setStatus(ScriptStatus.DONE); + + if (train.getTrain().getFormula() != null) { + Map<String, Object> variables = res.stream().collect(Collectors.toMap( + r -> gradeService.generateVariableName(r.getWagon().getName()), + r -> gradeService.toFormulaVariable(r.getScore(), + r.getWagon().getGradeScheme()))); + gradeService + .executeFormula(train.getTrain().getFormula(), train.getTrain().getGradeScheme(), + variables) + .ifPresent(train::setScore); + } else if (train.getTrain().getGradeScheme() == GradeScheme.PASS_FAIL) { + boolean fail = res.stream() + .filter(r -> r.getWagon().getGradeScheme() == GradeScheme.PASS_FAIL) + .filter(r -> r.getWagon().getGradePart().getRequired()) + .anyMatch(r -> r.getScore() != null && r.getScore() == 0); + train.setScore(fail ? 0 : 1); + } else { + Optional<Integer> weightedSum = res.stream() + .filter(r -> r.getWagon().getGradeScheme() != GradeScheme.PASS_FAIL) + .map(r -> r.getScore() == null ? 0 + : r.getWagon().getGradePart().getWeight() * r.getScore()) + .reduce(Integer::sum); + Optional<Integer> sumOfWeights = res.stream() + .filter(r -> r.getWagon().getGradeScheme() != GradeScheme.PASS_FAIL) + .map(r -> r.getWagon().getGradePart().getWeight()) + .reduce(Integer::sum) + .filter(s -> s != 0); + weightedSum.flatMap(w -> sumOfWeights.map(s -> (int) Math.round((double) w / s))) + .ifPresent(train::setScore); + } + + scriptTrainResultRepository.save(train); + } + } + } + + @Transactional + protected void startWaitingTrains() { + List<ScriptTrainResult> waiting = scriptTrainResultRepository + .findAllByStatus(ScriptStatus.NOT_STARTED); + for (ScriptTrainResult train : waiting) { + List<ScriptWagon> wagons = scriptWagonRepository.findAllByTrainId(train.getTrain().getId()); + if (!wagons.isEmpty()) { + runScriptWagon(wagons.get(0), train.getSubmission().getId()); + train.setStatus(ScriptStatus.RUNNING); + scriptTrainResultRepository.save(train); + } + } + } + + @Transactional + protected void startWaitingWagons() { + List<ScriptWagonResult> waiting = scriptWagonResultRepository + .findAllByStatus(ScriptStatus.NOT_STARTED); + for (ScriptWagonResult wagon : waiting) { + ScriptWagonResult prev = getPreviousWagon(wagon); + if (prev != null && prev.getStatus() == ScriptStatus.DONE && isNotRequiredOrPassing(prev)) { + runScriptWagon(wagon.getWagon(), wagon.getSubmission().getId()); + } + } + } + + private ScriptWagonResult getPreviousWagon(ScriptWagonResult wagon) { + List<ScriptWagon> wagons = scriptWagonRepository + .findAllByTrainId(wagon.getWagon().getTrain().getId()); + int index = wagons.stream().map(ScriptWagon::getId).collect(Collectors.toList()) + .indexOf(wagon.getWagon().getId()); + if (index == 0) { + return null; + } + return scriptWagonResultRepository.findByWagonIdAndSubmissionId(wagons.get(index - 1).getId(), + wagon.getSubmission().getId()); + } + + private boolean isNotRequiredOrPassing(ScriptWagonResult wagon) { + return !wagon.getWagon().getGradePart().getRequired() + || (wagon.getScore() != null && wagon.getWagon().getGradeScheme() != GradeScheme.PASS_FAIL) + || Integer.valueOf(1).equals(wagon.getScore()); + } + + @Transactional + protected void processFinishedTrains() { + List<ScriptTrainResult> done = scriptTrainResultRepository.findAllByStatus(ScriptStatus.DONE); + for (ScriptTrainResult train : done) { + LocalDateTime now = LocalDateTime.now(); + + Long gradeId = null; + if (train.getScore() != null) { + gradeId = gradeService.addGrade(new GradeCreateDTO() + .score(train.getScore()) + .scheme(GradeCreateDTO.SchemeEnum.valueOf(train.getTrain().getGradeScheme().name())) + .submission(new SubmissionIdDTO().id(train.getSubmission().getId())) + .gradedAt(now) + .isScriptGraded(true)); + } + + feedbackService.addFeedback(FeedbackCreateDTO.builder() + .visibleFor(RoleSummaryDTO.TypeEnum.STUDENT) + .submissionId(train.getSubmission().getId()) + .authorId(train.getTrain().getId()) + .gradeId(gradeId) + .isScriptFeedback(true) + .textualFeedback("Script feedback: " + train.getTrain().getId()) + .timestamp(now).build()); + + train.setStatus(ScriptStatus.PROCESSED); + train.getSubmission().setScriptPending(false); + scriptTrainResultRepository.save(train); + submissionRepository.save(train.getSubmission()); + } + } + + // endregion } diff --git a/src/main/java/nl/tudelft/submit/service/StatisticsService.java b/src/main/java/nl/tudelft/submit/service/StatisticsService.java index 9fb972dfe57a8977588a52f2e811b9cc86573d3f..d4f6f3670efa5901b7df52339bd0eae7fcaf7131 100644 --- a/src/main/java/nl/tudelft/submit/service/StatisticsService.java +++ b/src/main/java/nl/tudelft/submit/service/StatisticsService.java @@ -726,8 +726,7 @@ public class StatisticsService { GradeScheme gradeType) { double average = grades.values().stream().mapToDouble(GradeSummaryDTO::getScore).average() .getAsDouble(); - return (Double) gradeService.toFormulaVariable(new GradeSummaryDTO().score((int) Math.round(average)) - .scheme(GradeSummaryDTO.SchemeEnum.valueOf(gradeType.name()))); + return (Double) gradeService.toFormulaVariable((int) Math.round(average), gradeType); } /** @@ -756,8 +755,7 @@ public class StatisticsService { double median = sorted.length % 2 == 0 ? (sorted[sorted.length / 2] + sorted[sorted.length / 2 + 1]) / 2.0 : sorted[sorted.length / 2]; - return (Double) gradeService.toFormulaVariable(new GradeSummaryDTO().score((int) Math.round(median)) - .scheme(GradeSummaryDTO.SchemeEnum.valueOf(gradeType.name()))); + return (Double) gradeService.toFormulaVariable((int) Math.round(median), gradeType); } // endregion diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 55ea0d2b178942c99eb966b58c820150a7788bf4..8f50150dbde7c4372721c08f41a7ec801d61b335 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -333,3 +333,12 @@ script.remove_script.confirm = Are you sure you want to remove {0}? script.link = Link script.link.enter = Enter link script.grading_script = Grading +script.status = Script status +script.weight = Weight +script.score = Score + +script.status.not_started = Not started +script.status.running = Running +script.status.done = Done +script.status.failed = Failed +script.status.processed = Processed diff --git a/src/main/resources/templates/assignment/view.html b/src/main/resources/templates/assignment/view.html index b6412c5d7f4189cca65034145af23e2305b0aa87..6e2be1c1a08d2b03946cdbbd54b4e1c20fbff7e4 100644 --- a/src/main/resources/templates/assignment/view.html +++ b/src/main/resources/templates/assignment/view.html @@ -130,7 +130,7 @@ <div th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}" th:if="${isTransparent}" th:remove="tag"> <h2 class="subtitle" th:text="#{script.train}"></h2> - <div th:replace="~{version/wagon_list :: list}"></div> + <div th:replace="~{script/wagon_list :: list}"></div> </div> <div th:if="${@authorizationService.isStudentInAssignment(assignment.id)}" th:remove="tag"> @@ -170,14 +170,14 @@ <div th:replace="~{assignment/submit :: overlay('assignment')}"></div> <div th:replace="~{assignment/add_train :: overlay}"></div> <div th:if="${isTransparent and not @authorizationService.isStudentInAssignment(assignment.id)}" th:remove="tag"> - <div th:replace="~{version/add_wagon :: overlay}"></div> + <div th:replace="~{script/add_wagon :: overlay}"></div> <div th:each="wagon : ${train.wagons}" th:remove="tag"> - <div th:replace="~{version/add_script :: overlay}"></div> - <div th:replace="~{version/edit_wagon :: overlay}"></div> - <div th:replace="~{version/remove_wagon :: overlay}"></div> + <div th:replace="~{script/add_script :: overlay}"></div> + <div th:replace="~{script/edit_wagon :: overlay}"></div> + <div th:replace="~{script/remove_wagon :: overlay}"></div> <div th:each="script : ${wagon.scripts}" th:remove="tag"> - <div th:replace="~{version/edit_script :: overlay}"></div> - <div th:replace="~{version/remove_script :: overlay}"></div> + <div th:replace="~{script/edit_script :: overlay}"></div> + <div th:replace="~{script/remove_script :: overlay}"></div> </div> </div> </div> diff --git a/src/main/resources/templates/version/add_script.html b/src/main/resources/templates/script/add_script.html similarity index 100% rename from src/main/resources/templates/version/add_script.html rename to src/main/resources/templates/script/add_script.html diff --git a/src/main/resources/templates/version/add_wagon.html b/src/main/resources/templates/script/add_wagon.html similarity index 100% rename from src/main/resources/templates/version/add_wagon.html rename to src/main/resources/templates/script/add_wagon.html diff --git a/src/main/resources/templates/version/edit_script.html b/src/main/resources/templates/script/edit_script.html similarity index 100% rename from src/main/resources/templates/version/edit_script.html rename to src/main/resources/templates/script/edit_script.html diff --git a/src/main/resources/templates/version/edit_wagon.html b/src/main/resources/templates/script/edit_wagon.html similarity index 100% rename from src/main/resources/templates/version/edit_wagon.html rename to src/main/resources/templates/script/edit_wagon.html diff --git a/src/main/resources/templates/version/remove_script.html b/src/main/resources/templates/script/remove_script.html similarity index 100% rename from src/main/resources/templates/version/remove_script.html rename to src/main/resources/templates/script/remove_script.html diff --git a/src/main/resources/templates/version/remove_wagon.html b/src/main/resources/templates/script/remove_wagon.html similarity index 100% rename from src/main/resources/templates/version/remove_wagon.html rename to src/main/resources/templates/script/remove_wagon.html diff --git a/src/main/resources/templates/script/status.html b/src/main/resources/templates/script/status.html new file mode 100644 index 0000000000000000000000000000000000000000..0f6ef40571d18d0aa5e0c5d2278a0b74c4c16d5d --- /dev/null +++ b/src/main/resources/templates/script/status.html @@ -0,0 +1,72 @@ +<!-- + + Submit + Copyright (C) 2020 - Delft University of Technology + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +--> +<!DOCTYPE html> +<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" + layout:decorate="~{container}"> +<head> + <title th:text="#{script.status}"></title> +</head> + +<body> + +<div layout:fragment="breadcrumbs"> +</div> + +<div layout:fragment="sidebar" class="hidden"> +</div> + +<div layout:fragment="content"> + + <h1 class="title" th:text="#{script.status}"></h1> + + <table class="table"> + <tr class="table_header"> + <th th:text="#{general.name}"></th> + <th th:text="#{script.weight}"></th> + <th th:text="#{script.status}"></th> + <th th:text="#{script.score}"></th> + <th></th> + </tr> + <th:block th:each="wagon : ${train.wagons}"> + <tr> + <td th:text="${wagon.wagon.name}"></td> + <td th:text="${wagon.wagon.gradePart.weight}"></td> + <td th:text="#{|script.status.${#strings.toLowerCase(wagon.status.name())}|}"></td> + <td th:text="${wagon.score == null} ? '' : ${@gradeService.getScoreDisplayString(wagon.score, wagon.wagon.gradeScheme)}"></td> + <td><div class="table_actions"> + </div></td> + </tr> + <th:block th:each="script : ${wagon.scripts}"> + <tr> + <td th:text="${script.script.name}"></td> + <td th:text="${script.script.gradePart.weight}"></td> + <td th:text="#{|script.status.${#strings.toLowerCase(script.status.name())}|}"></td> + <td th:text="${script.score == null} ? '' : ${@gradeService.getScoreDisplayString(script.score, script.script.gradeScheme)}"></td> + <td><div class="table_actions"> + </div></td> + </tr> + </th:block> + </th:block> + </table> + +</div> + +</body> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/version/train.html b/src/main/resources/templates/script/train.html similarity index 85% rename from src/main/resources/templates/version/train.html rename to src/main/resources/templates/script/train.html index 7ac80db27082a7df14615a45619b19913d8ff5e5..cb05c4de671b457eb94035baed0915b7606568d5 100644 --- a/src/main/resources/templates/version/train.html +++ b/src/main/resources/templates/script/train.html @@ -50,16 +50,16 @@ <div layout:fragment="content"> <h1 class="title" th:text="#{script.train}"></h1> - <div th:replace="~{version/wagon_list :: list}"></div> + <div th:replace="~{script/wagon_list :: list}"></div> - <div th:replace="~{version/add_wagon :: overlay}"></div> + <div th:replace="~{script/add_wagon :: overlay}"></div> <div th:each="wagon : ${train.wagons}" th:remove="tag"> - <div th:replace="~{version/add_script :: overlay}"></div> - <div th:replace="~{version/edit_wagon :: overlay}"></div> - <div th:replace="~{version/remove_wagon :: overlay}"></div> + <div th:replace="~{script/add_script :: overlay}"></div> + <div th:replace="~{script/edit_wagon :: overlay}"></div> + <div th:replace="~{script/remove_wagon :: overlay}"></div> <div th:each="script : ${wagon.scripts}" th:remove="tag"> - <div th:replace="~{version/edit_script :: overlay}"></div> - <div th:replace="~{version/remove_script :: overlay}"></div> + <div th:replace="~{script/edit_script :: overlay}"></div> + <div th:replace="~{script/remove_script :: overlay}"></div> </div> </div> diff --git a/src/main/resources/templates/version/wagon_list.html b/src/main/resources/templates/script/wagon_list.html similarity index 94% rename from src/main/resources/templates/version/wagon_list.html rename to src/main/resources/templates/script/wagon_list.html index 604fe6da37a7ecf3ec5f29a967b9a200817b536e..0be105eba38368d7e39f88a1620e562fa48a59a4 100644 --- a/src/main/resources/templates/version/wagon_list.html +++ b/src/main/resources/templates/script/wagon_list.html @@ -40,7 +40,6 @@ <div th:each="script, sIter : ${wagon.scripts}" class="list_subitem"> <div class="list_subitem_content"> <span th:text="${script.name}"></span> - <span th:if="${iter.count == iter.size and sIter.count == sIter.size}" class="tag" th:text="#{script.grading_script}"></span> </div> <div class="list_actions"> <button class="text-button" th:onclick="|toggleOverlay('edit-script-${script.id}-overlay')|" th:text="#{general.edit}"></button> diff --git a/src/main/resources/templates/submission/feedback.html b/src/main/resources/templates/submission/feedback.html index 2a36663289a157bfd630dff8c16a24f4ce0662f0..c2e1b881355b1f5198c0a1680efa30b0f346491d 100644 --- a/src/main/resources/templates/submission/feedback.html +++ b/src/main/resources/templates/submission/feedback.html @@ -57,7 +57,7 @@ <span th:if="${submission.feedback.isEmpty()}" th:text="#{submission.no_feedback}"></span> <div th:each="feedback : ${submission.feedback}" th:if="${@authorizationService.canViewGroupFeedback(submission.group.id, feedback)}" th:remove="tag"> - <span th:if="${feedback.isScriptFeedback}" th:text="#{submission.feedback_from(${feedback.authorId == -1 ? 'script' : @scriptService.getScriptById(feedback.authorId).name})}"></span> + <span th:if="${feedback.isScriptFeedback}" th:text="#{submission.feedback_from('script')}"></span> <span th:unless="${feedback.isScriptFeedback}" th:text="#{submission.feedback_by(${@personCacheManager.getOrThrow(feedback.authorId)?.username})}"></span> <div> <span class="tag" th:text="${feedback.gradeId == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(grades[feedback.gradeId])}"></span> diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index 965ef0fe6ec05bfe0c94fdff7221a785fee79487..3f0f3cde673440040090e7b3a22b5621f22e2d30 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -18,8 +18,6 @@ submit: websocketURL: http://localhost:8083 - cache: - person-timeout: 60 filesys: # changing these options mid-operation may cause loss of data block-size: 2