From 0cff4fa909de04ebcdfdfa00bd49fbe9f7962dd6 Mon Sep 17 00:00:00 2001 From: Christian Ziegner Date: Wed, 17 Apr 2019 09:19:38 +0200 Subject: [PATCH] Improvements for tutor dashboard (#291) * display massage indicating that submission is locked * delete corresponding submission when deleting example submission * fix more efficient loading of submitted submissions without result * prevent errors when no feedbacks exist * load next optimal submission upon starting new assessment to prevent opening assessment editor for locked submission * prevent error when there are no complaints * disable Compass * handle case when starting assessment in exercise dashboard but no submissions waiting for assessment available * fix 'assess next submission' button when diagram type is not supported by Compass * fix redirect when no submission is loaded * adjust transactions to prevent example solution and explanation from getting removed from the exercise --- .../artemis/domain/ExampleSubmission.java | 2 +- .../in/www1/artemis/domain/Participation.java | 1 + .../repository/ParticipationRepository.java | 6 +- .../service/ModelingSubmissionService.java | 90 ++++++++++---- .../artemis/service/ParticipationService.java | 4 +- .../service/TextSubmissionService.java | 36 ++++-- .../service/compass/CompassService.java | 4 +- .../web/rest/ModelingAssessmentResource.java | 39 +----- .../web/rest/ModelingSubmissionResource.java | 96 ++++++++++----- .../web/rest/ParticipationResource.java | 3 +- .../web/rest/TextSubmissionResource.java | 17 +-- .../exercise-list.component.html | 2 +- .../modeling-exercise.model.ts | 5 + .../modeling-submission.service.ts | 12 +- ...deling-assessment-dashboard.component.html | 12 +- .../modeling-assessment-editor.component.html | 3 +- .../modeling-assessment-editor.component.ts | 113 ++++++++++++------ .../modeling-submission.component.html | 2 +- .../modeling-submission.component.ts | 2 +- .../tutor-exercise-dashboard.component.html | 2 +- .../tutor-exercise-dashboard.component.ts | 20 ++-- 21 files changed, 284 insertions(+), 187 deletions(-) diff --git a/src/main/java/de/tum/in/www1/artemis/domain/ExampleSubmission.java b/src/main/java/de/tum/in/www1/artemis/domain/ExampleSubmission.java index 53b652452326..10a827175886 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/ExampleSubmission.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/ExampleSubmission.java @@ -32,7 +32,7 @@ public class ExampleSubmission implements Serializable { @ManyToOne private Exercise exercise; - @OneToOne + @OneToOne(cascade = CascadeType.REMOVE) @JoinColumn(unique = true) private Submission submission; diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Participation.java b/src/main/java/de/tum/in/www1/artemis/domain/Participation.java index 7c0077590053..5a91dc0a0d3b 100644 --- a/src/main/java/de/tum/in/www1/artemis/domain/Participation.java +++ b/src/main/java/de/tum/in/www1/artemis/domain/Participation.java @@ -93,6 +93,7 @@ public class Participation implements Serializable { // objects would cause more issues (Subclasses don't work properly for Proxy objects) // and the gain from fetching lazy here is minimal @ManyToOne + @JsonIgnoreProperties("participations") @JsonView(QuizView.Before.class) private Exercise exercise; diff --git a/src/main/java/de/tum/in/www1/artemis/repository/ParticipationRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/ParticipationRepository.java index 791879bf79ed..43546269a981 100644 --- a/src/main/java/de/tum/in/www1/artemis/repository/ParticipationRepository.java +++ b/src/main/java/de/tum/in/www1/artemis/repository/ParticipationRepository.java @@ -42,11 +42,7 @@ public interface ParticipationRepository extends JpaRepository findByExerciseIdAndStudentIdWithEagerResults(@Param("exerciseId") Long exerciseId, @Param("studentId") Long studentId); - @Query("select distinct participation from Participation participation left join fetch participation.submissions where participation.exercise.id = :#{#exerciseId}") - List findByExerciseIdWithEagerSubmissions(@Param("exerciseId") Long exerciseId); - - // TODO: this call does not work at the moment - @Query("select distinct participation from Participation participation left join fetch participation.submissions submission where participation.exercise.id = :#{#exerciseId} and submission.submitted = true and submission.result is null") + @Query("select distinct participation from Participation participation left join fetch participation.submissions submission left join fetch submission.result result where participation.exercise.id = :#{#exerciseId} and submission.submitted = true and result is null") List findByExerciseIdWithEagerSubmittedSubmissionsWithoutResults(@Param("exerciseId") Long exerciseId); @Query("select distinct participation from Participation participation left join fetch participation.results where participation.id = :#{#participationId}") diff --git a/src/main/java/de/tum/in/www1/artemis/service/ModelingSubmissionService.java b/src/main/java/de/tum/in/www1/artemis/service/ModelingSubmissionService.java index 42d302309102..1eb8991bc8ea 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/ModelingSubmissionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/ModelingSubmissionService.java @@ -25,6 +25,8 @@ public class ModelingSubmissionService { private final ModelingSubmissionRepository modelingSubmissionRepository; + private final ResultService resultService; + private final ResultRepository resultRepository; private final CompassService compassService; @@ -33,9 +35,10 @@ public class ModelingSubmissionService { private final ParticipationRepository participationRepository; - public ModelingSubmissionService(ModelingSubmissionRepository modelingSubmissionRepository, ResultRepository resultRepository, CompassService compassService, - ParticipationService participationService, ParticipationRepository participationRepository) { + public ModelingSubmissionService(ModelingSubmissionRepository modelingSubmissionRepository, ResultService resultService, ResultRepository resultRepository, CompassService compassService, + ParticipationService participationService, ParticipationRepository participationRepository) { this.modelingSubmissionRepository = modelingSubmissionRepository; + this.resultService = resultService; this.resultRepository = resultRepository; this.compassService = compassService; this.participationService = participationService; @@ -69,11 +72,30 @@ public List getModelingSubmissions(Long exerciseId, boolean return submissions; } + @Transactional + public ModelingSubmission getLockedModelingSubmission(Long submissionId, ModelingExercise modelingExercise) { + ModelingSubmission modelingSubmission = findOneWithEagerResultAndFeedback(submissionId); + lockSubmission(modelingSubmission, modelingExercise); + return modelingSubmission; + } + + @Transactional + public ModelingSubmission getLockedModelingSubmissionWithoutResult(ModelingExercise modelingExercise) { + ModelingSubmission modelingSubmission = getModelingSubmissionWithoutResult(modelingExercise) + .orElseThrow(() -> new EntityNotFoundException("Modeling submission for exercise " + modelingExercise.getId() + " could not be found")); + lockSubmission(modelingSubmission, modelingExercise); + return modelingSubmission; + } + /** - * Given an exercise, find a random modeling submission for that exercise which still doesn't have any result. We relay for the randomness to `findAny()`, which return any - * element of the stream. While it is not mathematically random, it is not deterministic https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#findAny-- + * Given an exercise, find a modeling submission for that exercise which still doesn't have any result. + * If the diagram type is supported by Compass we get the next optimal submission from Compass, i.e. the submission + * for which an assessment means the most knowledge gain for the automatic assessment mechanism. + * If it's not supported by Compass we just get a random submission without assessment. We relay for the randomness + * to `findAny()`, which return any element of the stream. While it is not mathematically random, it is not + * deterministic https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#findAny-- * - * @param modelingExercise the modeling exercise for which we want to get a modeling submission + * @param modelingExercise the modeling exercise for which we want to get a modeling submission without result * @return a modeling submission without any result */ @Transactional(readOnly = true) @@ -86,16 +108,9 @@ public Optional getModelingSubmissionWithoutResult(ModelingE } } // otherwise return any submission that is not assessed - // TODO: optimize performance - return this.participationService.findByExerciseIdWithEagerSubmittedSubmissionsWithoutResults(modelingExercise.getId()).stream() - .peek(participation -> participation.getExercise().setParticipations(null)) - // Map to Latest Submission - .map(Participation::findLatestModelingSubmission).filter(Optional::isPresent).map(Optional::get) - // It needs to be submitted to be ready for assessment - .filter(Submission::isSubmitted).filter(modelingSubmission -> { - Result result = resultRepository.findDistinctBySubmissionId(modelingSubmission.getId()).orElse(null); - return result == null; - }).findAny(); + return participationService.findByExerciseIdWithEagerSubmittedSubmissionsWithoutResults(modelingExercise.getId()).stream() + // map to latest submission + .map(Participation::findLatestModelingSubmission).filter(Optional::isPresent).map(Optional::get).findAny(); } /** @@ -131,11 +146,17 @@ public List getAllModelingSubmissionsByTutorForExercise(Long * * @param modelingSubmission the submission to notifyCompass * @param modelingExercise the exercise to notifyCompass in - * @param participation the participation where the result should be saved + * @param username the name of the corresponding user * @return the modelingSubmission entity */ @Transactional(rollbackFor = Exception.class) - public ModelingSubmission save(ModelingSubmission modelingSubmission, ModelingExercise modelingExercise, Participation participation) { + public ModelingSubmission save(ModelingSubmission modelingSubmission, ModelingExercise modelingExercise, String username) { + + Optional optionalParticipation = participationService.findOneByExerciseIdAndStudentLoginAnyState(modelingExercise.getId(), username); + if (!optionalParticipation.isPresent()) { + throw new EntityNotFoundException("No participation found for " + username + " in exercise " + modelingExercise.getId()); + } + Participation participation = optionalParticipation.get(); // update submission properties modelingSubmission.setSubmissionDate(ZonedDateTime.now()); @@ -167,12 +188,35 @@ else if (modelingExercise.getDueDate() != null && !modelingExercise.isEnded()) { return modelingSubmission; } + /** + * Soft lock the submission to prevent other tutors from receiving and assessing it. + * We remove the model from the models waiting for assessment in Compass to prevent other tutors from retrieving it + * in the first place. + * Additionally, we set the assessor and save the result to soft lock the assessment in the client, i.e. the client + * will not allow tutors to assess a model when an assessor is already assigned. If no result exists for this + * submission we create one first. + * + * @param modelingSubmission the submission to lock + * @param modelingExercise the exercise to which the submission belongs to (needed for Compass) + */ + private void lockSubmission(ModelingSubmission modelingSubmission, ModelingExercise modelingExercise) { + if (modelingSubmission.getResult() == null) { + setNewResult(modelingSubmission); + } + if (modelingSubmission.getResult().getAssessor() == null) { + if (compassService.isSupported(modelingExercise.getDiagramType())) { + compassService.removeModelWaitingForAssessment(modelingExercise.getId(), modelingSubmission.getId()); + } + resultService.setAssessor(modelingSubmission.getResult()); + } + } + /** * Creates and sets new Result object in given submission and stores changes to the database. * * @param submission */ - public void setNewResult(ModelingSubmission submission) { + private void setNewResult(ModelingSubmission submission) { Result result = new Result(); result.setSubmission(submission); submission.setResult(result); @@ -193,13 +237,19 @@ public void notifyCompass(ModelingSubmission modelingSubmission, ModelingExercis } } + public ModelingSubmission findOne(Long id) { + return modelingSubmissionRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("Modeling submission with id \"" + id + "\" does not exist")); + } + public ModelingSubmission findOneWithEagerResult(Long id) { - return modelingSubmissionRepository.findByIdWithEagerResult(id).orElseThrow(() -> new EntityNotFoundException("Modeling submission with id \"" + id + "\" does not exist")); + return modelingSubmissionRepository.findByIdWithEagerResult(id) + .orElseThrow(() -> new EntityNotFoundException("Modeling submission with id \"" + id + "\" does not exist")); } public ModelingSubmission findOneWithEagerResultAndFeedback(Long id) { return modelingSubmissionRepository.findByIdWithEagerResultAndFeedback(id) - .orElseThrow(() -> new EntityNotFoundException("Modeling submission with id \"" + id + "\" does not exist")); + .orElseThrow(() -> new EntityNotFoundException("Modeling submission with id \"" + id + "\" does not exist")); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/service/ParticipationService.java b/src/main/java/de/tum/in/www1/artemis/service/ParticipationService.java index 3966bf94d53c..689fdaabd4d2 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/ParticipationService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/ParticipationService.java @@ -469,9 +469,7 @@ public List findByExerciseIdAndStudentIdWithEagerResults(Long exe @Transactional(readOnly = true) public List findByExerciseIdWithEagerSubmittedSubmissionsWithoutResults(Long exerciseId) { - // TODO optimize performance - List participations = participationRepository.findByExerciseIdWithEagerSubmissions(exerciseId); - return participations; + return participationRepository.findByExerciseIdWithEagerSubmittedSubmissionsWithoutResults(exerciseId); } @Transactional(readOnly = true) diff --git a/src/main/java/de/tum/in/www1/artemis/service/TextSubmissionService.java b/src/main/java/de/tum/in/www1/artemis/service/TextSubmissionService.java index 923b6f608b21..d94da7412961 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/TextSubmissionService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/TextSubmissionService.java @@ -1,13 +1,16 @@ package de.tum.in.www1.artemis.service; +import java.security.Principal; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; import de.tum.in.www1.artemis.domain.*; import de.tum.in.www1.artemis.domain.enumeration.InitializationState; @@ -37,6 +40,30 @@ public TextSubmissionService(TextSubmissionRepository textSubmissionRepository, this.resultRepository = resultRepository; } + /** + * Handles text submissions sent from the client and saves them in the database. + * + * @param textSubmission the text submission that should be saved + * @param textExercise the corresponding text exercise + * @param principal the user principal + * @return the saved text submission + */ + @Transactional + public TextSubmission handleTextSubmission(TextSubmission textSubmission, TextExercise textExercise, Principal principal) { + if (textSubmission.isExampleSubmission() == Boolean.TRUE) { + textSubmission = save(textSubmission); + } + else { + Optional optionalParticipation = participationService.findOneByExerciseIdAndStudentLoginAnyState(textExercise.getId(), principal.getName()); + if (!optionalParticipation.isPresent()) { + throw new ResponseStatusException(HttpStatus.FAILED_DEPENDENCY, "No participation found for " + principal.getName() + " in exercise " + textExercise.getId()); + } + Participation participation = optionalParticipation.get(); + textSubmission = save(textSubmission, textExercise, participation); + } + return textSubmission; + } + /** * Saves the given submission. Furthermore, the submission is added to the AutomaticSubmissionService, if not submitted yet. Is used for creating and updating text submissions. * If it is used for a submit action, Compass is notified about the new model. Rolls back if inserting fails - occurs for concurrent createTextSubmission() calls. @@ -106,16 +133,9 @@ public TextSubmission save(TextSubmission textSubmission) { */ @Transactional(readOnly = true) public Optional getTextSubmissionWithoutResult(TextExercise textExercise) { - // TODO: optimize performance return this.participationService.findByExerciseIdWithEagerSubmittedSubmissionsWithoutResults(textExercise.getId()).stream() - .peek(participation -> participation.getExercise().setParticipations(null)) // Map to Latest Submission - .map(Participation::findLatestTextSubmission).filter(Optional::isPresent).map(Optional::get) - // It needs to be submitted to be ready for assessment - .filter(Submission::isSubmitted).filter(textSubmission -> { - Result result = resultRepository.findDistinctBySubmissionId(textSubmission.getId()).orElse(null); - return result == null; - }).findAny(); + .map(Participation::findLatestTextSubmission).filter(Optional::isPresent).map(Optional::get).findAny(); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/service/compass/CompassService.java b/src/main/java/de/tum/in/www1/artemis/service/compass/CompassService.java index 05c0e4eb8596..2f5bab182b7e 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/compass/CompassService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/compass/CompassService.java @@ -80,7 +80,9 @@ public CompassService(ResultRepository resultRepository, ModelingExerciseReposit public boolean isSupported(DiagramType diagramType) { // at the moment, we only support class diagrams - return diagramType == DiagramType.ClassDiagram; + // TODO CZ: enable class diagrams again +// return diagramType == DiagramType.ClassDiagram; + return false; } /** diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingAssessmentResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingAssessmentResource.java index fb16e9876bfa..1cc3a0937732 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingAssessmentResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingAssessmentResource.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,8 +17,6 @@ import de.tum.in.www1.artemis.domain.*; import de.tum.in.www1.artemis.domain.modeling.ModelingExercise; import de.tum.in.www1.artemis.domain.modeling.ModelingSubmission; -import de.tum.in.www1.artemis.repository.ModelingSubmissionRepository; -import de.tum.in.www1.artemis.repository.ResultRepository; import de.tum.in.www1.artemis.service.*; import de.tum.in.www1.artemis.service.compass.CompassService; import de.tum.in.www1.artemis.service.compass.conflict.Conflict; @@ -54,14 +51,11 @@ public class ModelingAssessmentResource extends AssessmentResource { private final ModelingSubmissionService modelingSubmissionService; - private final ModelingSubmissionRepository modelingSubmissionRepository; - private final ExampleSubmissionService exampleSubmissionService; public ModelingAssessmentResource(AuthorizationCheckService authCheckService, UserService userService, CompassService compassService, ModelingExerciseService modelingExerciseService, AuthorizationCheckService authCheckService1, CourseService courseService, - ModelingAssessmentService modelingAssessmentService, ModelingSubmissionService modelingSubmissionService, ModelingSubmissionRepository modelingSubmissionRepository, - ExampleSubmissionService exampleSubmissionService, ResultRepository resultRepository) { + ModelingAssessmentService modelingAssessmentService, ModelingSubmissionService modelingSubmissionService, ExampleSubmissionService exampleSubmissionService) { super(authCheckService, userService); this.compassService = compassService; this.modelingExerciseService = modelingExerciseService; @@ -70,37 +64,6 @@ public ModelingAssessmentResource(AuthorizationCheckService authCheckService, Us this.exampleSubmissionService = exampleSubmissionService; this.modelingAssessmentService = modelingAssessmentService; this.modelingSubmissionService = modelingSubmissionService; - this.modelingSubmissionRepository = modelingSubmissionRepository; - } - - @DeleteMapping("/exercises/{exerciseId}/optimal-model-submissions") - @PreAuthorize("hasAnyRole('TA', 'INSTRUCTOR', 'ADMIN')") - public ResponseEntity resetOptimalModels(@PathVariable Long exerciseId) { - ModelingExercise modelingExercise = modelingExerciseService.findOne(exerciseId); - checkAuthorization(modelingExercise); - if (compassService.isSupported(modelingExercise.getDiagramType())) { - compassService.resetModelsWaitingForAssessment(exerciseId); - } - return ResponseEntity.noContent().build(); - } - - // TODO MJ add api documentation (returns list of submission ids as array) - @GetMapping("/exercises/{exerciseId}/optimal-model-submissions") - @PreAuthorize("hasAnyRole('TA', 'INSTRUCTOR', 'ADMIN')") - @Transactional - public ResponseEntity getNextOptimalModelSubmissions(@PathVariable Long exerciseId) { - ModelingExercise modelingExercise = modelingExerciseService.findOne(exerciseId); - checkAuthorization(modelingExercise); - if (compassService.isSupported(modelingExercise.getDiagramType())) { - Set optimalModelSubmissions = compassService.getModelsWaitingForAssessment(exerciseId); - if (optimalModelSubmissions.isEmpty()) { - return ResponseEntity.ok(new Long[] {}); // empty - } - return ResponseEntity.ok(optimalModelSubmissions.toArray(new Long[] {})); - } - else { - return ResponseEntity.ok(new Long[] {}); // empty - } } @GetMapping("/modeling-submissions/{submissionId}/partial-assessment") diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingSubmissionResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingSubmissionResource.java index 5ec0bf9ec877..0b7e1ede76f1 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingSubmissionResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ModelingSubmissionResource.java @@ -2,18 +2,19 @@ import static de.tum.in.www1.artemis.web.rest.util.ResponseUtil.badRequest; import static de.tum.in.www1.artemis.web.rest.util.ResponseUtil.forbidden; +import static de.tum.in.www1.artemis.web.rest.util.ResponseUtil.notFound; import java.net.URISyntaxException; import java.security.Principal; import java.util.List; import java.util.Optional; +import java.util.Set; import org.slf4j.*; import org.springframework.http.*; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.*; import org.springframework.web.bind.annotation.*; -import org.springframework.web.server.ResponseStatusException; import de.tum.in.www1.artemis.domain.*; import de.tum.in.www1.artemis.domain.modeling.ModelingExercise; @@ -21,7 +22,6 @@ import de.tum.in.www1.artemis.service.*; import de.tum.in.www1.artemis.service.compass.CompassService; import de.tum.in.www1.artemis.web.rest.errors.*; -import io.github.jhipster.web.util.ResponseUtil; import io.swagger.annotations.*; /** @@ -80,7 +80,6 @@ public ModelingSubmissionResource(ModelingSubmissionService modelingSubmissionSe */ @PostMapping("/exercises/{exerciseId}/modeling-submissions") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) // TODO MJ return 201 CREATED with location header instead of ModelingSubmission // TODO MJ merge with update public ResponseEntity createModelingSubmission(@PathVariable Long exerciseId, Principal principal, @RequestBody ModelingSubmission modelingSubmission) { @@ -89,12 +88,8 @@ public ResponseEntity createModelingSubmission(@PathVariable throw new BadRequestAlertException("A new modelingSubmission cannot already have an ID", ENTITY_NAME, "idexists"); } ModelingExercise modelingExercise = modelingExerciseService.findOne(exerciseId); - Optional participation = participationService.findOneByExerciseIdAndStudentLoginAnyState(exerciseId, principal.getName()); - if (!participation.isPresent()) { - throw new ResponseStatusException(HttpStatus.FAILED_DEPENDENCY, "No participation found for " + principal.getName() + " in exercise " + exerciseId); - } checkAuthorization(modelingExercise); - modelingSubmission = modelingSubmissionService.save(modelingSubmission, modelingExercise, participation.get()); + modelingSubmission = modelingSubmissionService.save(modelingSubmission, modelingExercise, principal.getName()); hideDetails(modelingSubmission); return ResponseEntity.ok(modelingSubmission); } @@ -112,19 +107,14 @@ public ResponseEntity createModelingSubmission(@PathVariable */ @PutMapping("/exercises/{exerciseId}/modeling-submissions") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - @Transactional public ResponseEntity updateModelingSubmission(@PathVariable Long exerciseId, Principal principal, @RequestBody ModelingSubmission modelingSubmission) { log.debug("REST request to update ModelingSubmission : {}", modelingSubmission.getModel()); if (modelingSubmission.getId() == null) { return createModelingSubmission(exerciseId, principal, modelingSubmission); } ModelingExercise modelingExercise = modelingExerciseService.findOne(exerciseId); - Optional participation = participationService.findOneByExerciseIdAndStudentLoginAnyState(exerciseId, principal.getName()); - if (!participation.isPresent()) { - throw new ResponseStatusException(HttpStatus.FAILED_DEPENDENCY, "No participation found for " + principal.getName() + " in exercise " + exerciseId); - } checkAuthorization(modelingExercise); - modelingSubmission = modelingSubmissionService.save(modelingSubmission, modelingExercise, participation.get()); + modelingSubmission = modelingSubmissionService.save(modelingSubmission, modelingExercise, principal.getName()); hideDetails(modelingSubmission); return ResponseEntity.ok(modelingSubmission); } @@ -161,24 +151,14 @@ public ResponseEntity> getAllModelingSubmissions(@PathV */ @GetMapping("/modeling-submissions/{submissionId}") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - @Transactional public ResponseEntity getModelingSubmission(@PathVariable Long submissionId) { log.debug("REST request to get ModelingSubmission with id: {}", submissionId); - ModelingSubmission modelingSubmission = modelingSubmissionService.findOneWithEagerResultAndFeedback(submissionId); - ModelingExercise modelingExercise = (ModelingExercise) modelingSubmission.getParticipation().getExercise(); + // TODO CZ: include exerciseId in path to get exercise for auth check more easily? + ModelingExercise modelingExercise = (ModelingExercise) modelingSubmissionService.findOne(submissionId).getParticipation().getExercise(); if (!authCheckService.isAtLeastTeachingAssistantForExercise(modelingExercise)) { return forbidden(); } - if (modelingSubmission.getResult() == null) { - modelingSubmissionService.setNewResult(modelingSubmission); - } - if (modelingSubmission.getResult().getAssessor() == null) { - if (compassService.isSupported(modelingExercise.getDiagramType())) { - compassService.removeModelWaitingForAssessment(modelingExercise.getId(), submissionId); - } - // we set the assessor and save the result to soft lock the assessment (so that it cannot be edited by another tutor) - resultService.setAssessor(modelingSubmission.getResult()); - } + ModelingSubmission modelingSubmission = modelingSubmissionService.getLockedModelingSubmission(submissionId, modelingExercise); // Make sure the exercise is connected to the participation in the json response modelingSubmission.getParticipation().setExercise(modelingExercise); hideDetails(modelingSubmission); @@ -192,11 +172,10 @@ public ResponseEntity getModelingSubmission(@PathVariable Lo */ @GetMapping(value = "/exercises/{exerciseId}/modeling-submission-without-assessment") @PreAuthorize("hasAnyRole('TA', 'INSTRUCTOR', 'ADMIN')") - @Transactional(readOnly = true) - public ResponseEntity getModelingSubmissionWithoutAssessment(@PathVariable Long exerciseId) { + public ResponseEntity getModelingSubmissionWithoutAssessment(@PathVariable Long exerciseId, + @RequestParam(value = "lock", defaultValue = "false") boolean lockSubmission) { log.debug("REST request to get a text submission without assessment"); Exercise exercise = exerciseService.findOne(exerciseId); - if (!authCheckService.isAtLeastTeachingAssistantForExercise(exercise)) { return forbidden(); } @@ -204,9 +183,62 @@ public ResponseEntity getModelingSubmissionWithoutAssessment return badRequest(); } - Optional modelingSubmissionWithoutAssessment = this.modelingSubmissionService.getModelingSubmissionWithoutResult((ModelingExercise) exercise); + ModelingSubmission modelingSubmission; + if (lockSubmission) { + modelingSubmission = modelingSubmissionService.getLockedModelingSubmissionWithoutResult((ModelingExercise) exercise); + } else { + Optional optionalModelingSubmission = + modelingSubmissionService.getModelingSubmissionWithoutResult((ModelingExercise) exercise); + if (!optionalModelingSubmission.isPresent()) { + return notFound(); + } + modelingSubmission = optionalModelingSubmission.get(); + } + + // Make sure the exercise is connected to the participation in the json response + modelingSubmission.getParticipation().setExercise(exercise); + hideDetails(modelingSubmission); + return ResponseEntity.ok(modelingSubmission); + } - return ResponseUtil.wrapOrNotFound(modelingSubmissionWithoutAssessment); + // TODO MJ add api documentation (returns list of submission ids as array) + @GetMapping("/exercises/{exerciseId}/optimal-model-submissions") + @PreAuthorize("hasAnyRole('TA', 'INSTRUCTOR', 'ADMIN')") + @Transactional + public ResponseEntity getNextOptimalModelSubmissions(@PathVariable Long exerciseId) { + ModelingExercise modelingExercise = modelingExerciseService.findOne(exerciseId); + checkAuthorization(modelingExercise); + if (compassService.isSupported(modelingExercise.getDiagramType())) { + // ask Compass for optimal submission to assess if diagram type is supported + Set optimalModelSubmissions = compassService.getModelsWaitingForAssessment(exerciseId); + if (optimalModelSubmissions.isEmpty()) { + return ResponseEntity.ok(new Long[] {}); // empty + } + return ResponseEntity.ok(optimalModelSubmissions.toArray(new Long[] {})); + } + else { + // if diagram type is not supported get any (not optimal) submission that is not assessed + Optional optionalModelingSubmission = + participationService.findByExerciseIdWithEagerSubmittedSubmissionsWithoutResults(modelingExercise.getId()).stream() + // map to latest submission + .map(Participation::findLatestModelingSubmission).filter(Optional::isPresent).map(Optional::get).findAny(); + if (!optionalModelingSubmission.isPresent()) + { + return ResponseEntity.ok(new Long[] {}); // empty + } + return ResponseEntity.ok(new Long[] {optionalModelingSubmission.get().getId()}); + } + } + + @DeleteMapping("/exercises/{exerciseId}/optimal-model-submissions") + @PreAuthorize("hasAnyRole('TA', 'INSTRUCTOR', 'ADMIN')") + public ResponseEntity resetOptimalModels(@PathVariable Long exerciseId) { + ModelingExercise modelingExercise = modelingExerciseService.findOne(exerciseId); + checkAuthorization(modelingExercise); + if (compassService.isSupported(modelingExercise.getDiagramType())) { + compassService.resetModelsWaitingForAssessment(exerciseId); + } + return ResponseEntity.noContent().build(); } private void hideDetails(ModelingSubmission modelingSubmission) { diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/ParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/ParticipationResource.java index 1610dcd56ce5..3decba99e148 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/ParticipationResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/ParticipationResource.java @@ -246,7 +246,8 @@ public ResponseEntity> getAllParticipationsForExercise(@Path /** * GET /exercise/{exerciseId}/participation-without-assessment Given an exerciseId of a text exercise, retrieve a participation where the latest submission has no assessment - * returns 404 If any, it creates the result and assign to the tutor, as a draft + * returns 404 If any, it creates the result and assign to the tutor, as a draft. This also involves a soft lock (setting the assessor of the result) so that other tutors + * cannot assess the submission. TODO unify this approach with the one for modeling exercises * * @param exerciseId the id of the exercise of which we want a submission * @return a student participation diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/TextSubmissionResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/TextSubmissionResource.java index 411ff78e8c41..48e35be00932 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/TextSubmissionResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/TextSubmissionResource.java @@ -76,7 +76,6 @@ public TextSubmissionResource(TextSubmissionRepository textSubmissionRepository, */ @PostMapping("/exercises/{exerciseId}/text-submissions") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) public ResponseEntity createTextSubmission(@PathVariable Long exerciseId, Principal principal, @RequestBody TextSubmission textSubmission) { log.debug("REST request to save TextSubmission : {}", textSubmission); if (textSubmission.getId() != null) { @@ -98,7 +97,6 @@ public ResponseEntity createTextSubmission(@PathVariable Long ex */ @PutMapping("/exercises/{exerciseId}/text-submissions") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - @Transactional public ResponseEntity updateTextSubmission(@PathVariable Long exerciseId, Principal principal, @RequestBody TextSubmission textSubmission) { log.debug("REST request to update TextSubmission : {}", textSubmission); if (textSubmission.getId() == null) { @@ -112,21 +110,12 @@ public ResponseEntity updateTextSubmission(@PathVariable Long ex private ResponseEntity handleTextSubmission(@PathVariable Long exerciseId, Principal principal, @RequestBody TextSubmission textSubmission) { TextExercise textExercise = textExerciseService.findOne(exerciseId); ResponseEntity responseFailure = this.checkExerciseValidity(textExercise); - if (responseFailure != null) + if (responseFailure != null) { return responseFailure; - - if (textSubmission.isExampleSubmission() == Boolean.TRUE) { - textSubmission = textSubmissionService.save(textSubmission); - } - else { - Optional optionalParticipation = participationService.findOneByExerciseIdAndStudentLoginAnyState(exerciseId, principal.getName()); - if (!optionalParticipation.isPresent()) { - throw new ResponseStatusException(HttpStatus.FAILED_DEPENDENCY, "No participation found for " + principal.getName() + " in exercise " + exerciseId); - } - Participation participation = optionalParticipation.get(); - textSubmission = textSubmissionService.save(textSubmission, textExercise, participation); } + textSubmission = textSubmissionService.handleTextSubmission(textSubmission, textExercise, principal); + hideDetails(textSubmission); return ResponseEntity.ok(textSubmission); } diff --git a/src/main/webapp/app/course-list/exercise-list/exercise-list.component.html b/src/main/webapp/app/course-list/exercise-list/exercise-list.component.html index 272b36160383..ec0d2bd86f2a 100644 --- a/src/main/webapp/app/course-list/exercise-list/exercise-list.component.html +++ b/src/main/webapp/app/course-list/exercise-list/exercise-list.component.html @@ -49,7 +49,7 @@ > ) => this.convertArrayResponse(res)); } - getModelingSubmissionForExerciseWithoutAssessment(exerciseId: number): Observable> { - return this.http - .get(`api/exercises/${exerciseId}/modeling-submission-without-assessment`, { - observe: 'response', - }) - .map((res: HttpResponse) => this.convertResponse(res)); + getModelingSubmissionForExerciseWithoutAssessment(exerciseId: number, lock?: boolean): Observable { + let url = `api/exercises/${exerciseId}/modeling-submission-without-assessment`; + if (lock) { + url += '?lock=true'; + } + return this.http.get(url); } getSubmission(submissionId: number): Observable { diff --git a/src/main/webapp/app/modeling-assessment-editor/modeling-assessment-dashboard.component.html b/src/main/webapp/app/modeling-assessment-editor/modeling-assessment-dashboard.component.html index 6b753b5c8568..6a227c48e81b 100644 --- a/src/main/webapp/app/modeling-assessment-editor/modeling-assessment-dashboard.component.html +++ b/src/main/webapp/app/modeling-assessment-editor/modeling-assessment-dashboard.component.html @@ -4,7 +4,7 @@

-
+