diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Exercise.java b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Exercise.java index 2c7ae1b53009..7503427a81fc 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Exercise.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Exercise.java @@ -613,7 +613,7 @@ public Set findResultsFilteredForStudents(Participation participation) { boolean isAssessmentOver = getAssessmentDueDate() == null || getAssessmentDueDate().isBefore(ZonedDateTime.now()); if (!isAssessmentOver) { // This allows the showing of preliminary feedback in case the assessment due date is set before its over. - if (this instanceof TextExercise) { + if (this instanceof TextExercise || this instanceof ModelingExercise) { return participation.getResults().stream().filter(result -> result.getAssessmentType() == AssessmentType.AUTOMATIC_ATHENA).collect(Collectors.toSet()); } return Set.of(); diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Submission.java b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Submission.java index 8c7d55930eaa..326507d47dd4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Submission.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/Submission.java @@ -220,11 +220,11 @@ public List getManualResults() { /** * This method is necessary to ignore Athena results in the assessment view * - * @return non athena automatic results including null results + * @return non athena automatic results excluding null results */ @JsonIgnore public List getNonAthenaResults() { - return results.stream().filter(result -> result == null || !result.isAthenaAutomatic()).collect(Collectors.toCollection(ArrayList::new)); + return results.stream().filter(result -> result != null && !result.isAthenaAutomatic()).collect(Collectors.toCollection(ArrayList::new)); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ParticipationService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ParticipationService.java index 5590e5c810f4..9074ad8ec1f8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ParticipationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ParticipationService.java @@ -715,16 +715,16 @@ public List findByExerciseAndStudentIdWithEagerSubmissions } /** - * Get the text exercise participation with the Latest Submissions and its results + * Retrieves a StudentParticipation with its latest Submission and associated Result. * - * @param participationId the id of the participation - * @return the participation with latest submission and result - * @throws EntityNotFoundException + * @param participationId The unique identifier of the participation to retrieve. + * @return A StudentParticipation object containing the latest submission and result. + * @throws EntityNotFoundException If no StudentParticipation is found with the given ID. */ - public StudentParticipation findTextExerciseParticipationWithLatestSubmissionAndResultElseThrow(Long participationId) throws EntityNotFoundException { + public StudentParticipation findExerciseParticipationWithLatestSubmissionAndResultElseThrow(Long participationId) throws EntityNotFoundException { Optional participation = participationRepository.findByIdWithLatestSubmissionAndResult(participationId); if (participation.isEmpty() || !(participation.get() instanceof StudentParticipation studentParticipation)) { - throw new EntityNotFoundException("No text exercise participation found with id " + participationId); + throw new EntityNotFoundException("No exercise participation found with id " + participationId); } return studentParticipation; } diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java index fc72035f5f70..2669e856337f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/SubmissionService.java @@ -787,6 +787,7 @@ private List getSubmissionsWithComplaintsFromComplai // add each submission with its complaint to the DTO submissions.stream().filter(submission -> submission.getResultWithComplaint() != null).forEach(submission -> { // get the complaint which belongs to the submission + submission.setResults(submission.getNonAthenaResults()); Complaint complaintOfSubmission = complaintMap.get(submission.getResultWithComplaint().getId()); prepareComplaintAndSubmission(complaintOfSubmission, submission); submissionWithComplaintDTOs.add(new SubmissionWithComplaintDTO(submission, complaintOfSubmission)); diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/web/AbstractSubmissionResource.java b/src/main/java/de/tum/cit/aet/artemis/exercise/web/AbstractSubmissionResource.java index 337f58213e7b..3dc507d37b64 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/web/AbstractSubmissionResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/web/AbstractSubmissionResource.java @@ -82,7 +82,7 @@ protected ResponseEntity> getAllSubmissions(Long exerciseId, bo submission.getParticipation().setExercise(null); } // Important for exercises with Athena results - if (assessedByTutor) { + if (assessedByTutor && !examMode) { submission.setResults(submission.getNonAthenaResults()); } }); diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java b/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java index a9f3d94ae2d2..6559c28b9d93 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java @@ -81,7 +81,9 @@ import de.tum.cit.aet.artemis.exercise.service.ExerciseDateService; import de.tum.cit.aet.artemis.exercise.service.ParticipationAuthorizationCheckService; import de.tum.cit.aet.artemis.exercise.service.ParticipationService; +import de.tum.cit.aet.artemis.fileupload.domain.FileUploadExercise; import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; +import de.tum.cit.aet.artemis.modeling.service.ModelingExerciseFeedbackService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseStudentParticipation; @@ -167,6 +169,8 @@ public class ParticipationResource { private final TextExerciseFeedbackService textExerciseFeedbackService; + private final ModelingExerciseFeedbackService modelingExerciseFeedbackService; + public ParticipationResource(ParticipationService participationService, ProgrammingExerciseParticipationService programmingExerciseParticipationService, CourseRepository courseRepository, QuizExerciseRepository quizExerciseRepository, ExerciseRepository exerciseRepository, ProgrammingExerciseRepository programmingExerciseRepository, AuthorizationCheckService authCheckService, @@ -176,7 +180,8 @@ public ParticipationResource(ParticipationService participationService, Programm ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, SubmissionRepository submissionRepository, ResultRepository resultRepository, ExerciseDateService exerciseDateService, InstanceMessageSendService instanceMessageSendService, QuizBatchService quizBatchService, SubmittedAnswerRepository submittedAnswerRepository, QuizSubmissionService quizSubmissionService, GradingScaleService gradingScaleService, - ProgrammingExerciseCodeReviewFeedbackService programmingExerciseCodeReviewFeedbackService, TextExerciseFeedbackService textExerciseFeedbackService) { + ProgrammingExerciseCodeReviewFeedbackService programmingExerciseCodeReviewFeedbackService, TextExerciseFeedbackService textExerciseFeedbackService, + ModelingExerciseFeedbackService modelingExerciseFeedbackService) { this.participationService = participationService; this.programmingExerciseParticipationService = programmingExerciseParticipationService; this.quizExerciseRepository = quizExerciseRepository; @@ -203,6 +208,7 @@ public ParticipationResource(ParticipationService participationService, Programm this.gradingScaleService = gradingScaleService; this.programmingExerciseCodeReviewFeedbackService = programmingExerciseCodeReviewFeedbackService; this.textExerciseFeedbackService = textExerciseFeedbackService; + this.modelingExerciseFeedbackService = modelingExerciseFeedbackService; } /** @@ -363,7 +369,7 @@ public ResponseEntity requestFeedback(@PathVariable Long e Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId); - if (!(exercise instanceof TextExercise) && !(exercise instanceof ProgrammingExercise)) { + if (exercise instanceof QuizExercise || exercise instanceof FileUploadExercise) { throw new BadRequestAlertException("Unsupported exercise type", "participation", "unsupported type"); } @@ -393,7 +399,7 @@ private ResponseEntity handleExerciseFeedbackRequest(Exerc participation = studentParticipationRepository.findByIdWithResultsElseThrow(participation.getId()); // Check submission requirements - if (exercise instanceof TextExercise) { + if (exercise instanceof TextExercise || exercise instanceof ModelingExercise) { if (submissionRepository.findAllByParticipationId(participation.getId()).isEmpty()) { throw new BadRequestAlertException("You need to submit at least once", "participation", "preconditions not met"); } @@ -416,6 +422,9 @@ else if (exercise instanceof ProgrammingExercise) { if (exercise instanceof TextExercise) { updatedParticipation = textExerciseFeedbackService.handleNonGradedFeedbackRequest(exercise.getId(), participation, (TextExercise) exercise); } + else if (exercise instanceof ModelingExercise) { + updatedParticipation = modelingExerciseFeedbackService.handleNonGradedFeedbackRequest(participation, (ModelingExercise) exercise); + } else { updatedParticipation = programmingExerciseCodeReviewFeedbackService.handleNonGradedFeedbackRequest(exercise.getId(), (ProgrammingExerciseStudentParticipation) participation, (ProgrammingExercise) exercise); diff --git a/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseFeedbackService.java b/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseFeedbackService.java new file mode 100644 index 000000000000..e6e229e79f6a --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseFeedbackService.java @@ -0,0 +1,196 @@ +package de.tum.cit.aet.artemis.modeling.service; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import de.tum.cit.aet.artemis.assessment.domain.AssessmentType; +import de.tum.cit.aet.artemis.assessment.domain.Feedback; +import de.tum.cit.aet.artemis.assessment.domain.FeedbackType; +import de.tum.cit.aet.artemis.assessment.domain.Result; +import de.tum.cit.aet.artemis.assessment.repository.ResultRepository; +import de.tum.cit.aet.artemis.assessment.service.ResultService; +import de.tum.cit.aet.artemis.assessment.web.ResultWebsocketService; +import de.tum.cit.aet.artemis.athena.dto.ModelingFeedbackDTO; +import de.tum.cit.aet.artemis.athena.service.AthenaFeedbackSuggestionsService; +import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; +import de.tum.cit.aet.artemis.core.exception.InternalServerErrorException; +import de.tum.cit.aet.artemis.core.exception.NetworkingException; +import de.tum.cit.aet.artemis.exercise.domain.Submission; +import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; +import de.tum.cit.aet.artemis.exercise.service.ParticipationService; +import de.tum.cit.aet.artemis.exercise.service.SubmissionService; +import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; +import de.tum.cit.aet.artemis.modeling.domain.ModelingSubmission; + +@Profile(PROFILE_CORE) +@Service +public class ModelingExerciseFeedbackService { + + private static final Logger log = LoggerFactory.getLogger(ModelingExerciseFeedbackService.class); + + private final Optional athenaFeedbackSuggestionsService; + + private final ResultWebsocketService resultWebsocketService; + + private final SubmissionService submissionService; + + private final ParticipationService participationService; + + private final ResultService resultService; + + private final ResultRepository resultRepository; + + public ModelingExerciseFeedbackService(Optional athenaFeedbackSuggestionsService, SubmissionService submissionService, + ResultService resultService, ResultRepository resultRepository, ResultWebsocketService resultWebsocketService, ParticipationService participationService) { + this.athenaFeedbackSuggestionsService = athenaFeedbackSuggestionsService; + this.submissionService = submissionService; + this.resultService = resultService; + this.resultRepository = resultRepository; + this.resultWebsocketService = resultWebsocketService; + this.participationService = participationService; + } + + private void checkRateLimitOrThrow(StudentParticipation participation) { + + List athenaResults = participation.getResults().stream().filter(result -> result.getAssessmentType() == AssessmentType.AUTOMATIC_ATHENA).toList(); + + if (athenaResults.size() >= 10) { + throw new BadRequestAlertException("Maximum number of AI feedback requests reached.", "participation", "preconditions not met"); + } + } + + /** + * Handles the request for generating feedback for a modeling exercise. + * Unlike programming exercises a tutor is not notified if Athena is not available. + * + * @param participation the student participation associated with the exercise. + * @param modelingExercise the modeling exercise object. + * @return StudentParticipation updated modeling exercise for an AI assessment + */ + public StudentParticipation handleNonGradedFeedbackRequest(StudentParticipation participation, ModelingExercise modelingExercise) { + if (this.athenaFeedbackSuggestionsService.isPresent()) { + this.checkRateLimitOrThrow(participation); + CompletableFuture.runAsync(() -> this.generateAutomaticNonGradedFeedback(participation, modelingExercise)); + } + return participation; + } + + /** + * Generates automatic non-graded feedback for a modeling exercise submission. + * This method leverages the Athena service to generate feedback based on the latest submission. + * + * @param participation the student participation associated with the exercise. + * @param modelingExercise the modeling exercise object. + */ + public void generateAutomaticNonGradedFeedback(StudentParticipation participation, ModelingExercise modelingExercise) { + log.debug("Using athena to generate (modeling exercise) feedback request: {}", modelingExercise.getId()); + + Optional submissionOptional = participationService.findExerciseParticipationWithLatestSubmissionAndResultElseThrow(participation.getId()) + .findLatestSubmission(); + + if (submissionOptional.isEmpty()) { + throw new BadRequestAlertException("No legal submissions found", "submission", "noSubmission"); + } + + Submission submission = submissionOptional.get(); + + Result automaticResult = createInitialResult(participation, submission); + + try { + this.resultWebsocketService.broadcastNewResult(participation, automaticResult); + + log.debug("Submission id: {}", submission.getId()); + + List feedbacks = getAthenaFeedback(modelingExercise, (ModelingSubmission) submission); + + double totalFeedbackScore = calculateTotalFeedbackScore(feedbacks, modelingExercise); + + automaticResult.setCompletionDate(ZonedDateTime.now()); + automaticResult.setScore(Math.max(0, Math.min(totalFeedbackScore, 100))); + automaticResult.setSuccessful(true); + + automaticResult = this.resultRepository.save(automaticResult); + resultService.storeFeedbackInResult(automaticResult, feedbacks, true); + submissionService.saveNewResult(submission, automaticResult); + this.resultWebsocketService.broadcastNewResult(participation, automaticResult); + } + catch (Exception e) { + log.error("Could not generate feedback for exercise ID: {} and participation ID: {}", modelingExercise.getId(), participation.getId(), e); + throw new InternalServerErrorException("Something went wrong... AI Feedback could not be generated"); + } + } + + /** + * Creates an initial Result object for the automatic feedback. + * + * @param participation the student participation + * @param submission the submission to which the result is associated + * @return the initial Result object + */ + private Result createInitialResult(StudentParticipation participation, Submission submission) { + Result result = new Result(); + result.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA); + result.setRated(true); + result.setScore(0.0); + result.setSuccessful(null); + result.setSubmission(submission); + result.setParticipation(participation); + return result; + } + + /** + * Retrieves feedback from the Athena service. + * + * @param modelingExercise the modeling exercise + * @param submission the modeling submission + * @return a list of Feedback objects + * @throws NetworkingException if there's a problem communicating with Athena + */ + private List getAthenaFeedback(ModelingExercise modelingExercise, ModelingSubmission submission) throws NetworkingException { + return this.athenaFeedbackSuggestionsService.orElseThrow().getModelingFeedbackSuggestions(modelingExercise, submission, false).stream() + .filter(feedbackItem -> feedbackItem.description() != null).map(this::convertToFeedback).toList(); + } + + /** + * Converts an Athena feedback suggestion to a Feedback object. + * + * @param feedbackItem the Athena feedback suggestion + * @return the Feedback object + */ + private Feedback convertToFeedback(ModelingFeedbackDTO feedbackItem) { + Feedback feedback = new Feedback(); + feedback.setText(feedbackItem.title()); + feedback.setDetailText(feedbackItem.description()); + feedback.setHasLongFeedbackText(false); + feedback.setType(FeedbackType.AUTOMATIC); + feedback.setCredits(feedbackItem.credits()); + return feedback; + } + + /** + * Calculates the total feedback score based on the list of feedbacks and the exercise's max points. + * + * @param feedbacks the list of feedbacks + * @param modelingExercise the modeling exercise + * @return the total feedback score + */ + private double calculateTotalFeedbackScore(List feedbacks, ModelingExercise modelingExercise) { + double totalCredits = feedbacks.stream().mapToDouble(Feedback::getCredits).sum(); + Double maxPoints = modelingExercise.getMaxPoints(); + + if (maxPoints == null || maxPoints == 0) { + throw new IllegalArgumentException("Maximum points must be greater than zero."); + } + + return (totalCredits / maxPoints) * 100; + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingSubmissionService.java b/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingSubmissionService.java index cfba2463185b..320d041325e9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingSubmissionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingSubmissionService.java @@ -124,14 +124,19 @@ public ModelingSubmission handleModelingSubmission(ModelingSubmission modelingSu throw new AccessForbiddenException(); } - // remove result from submission (in the unlikely case it is passed here), so that students cannot inject a result - modelingSubmission.setResults(new ArrayList<>()); - // update submission properties // NOTE: from now on we always set submitted to true to prevent problems here! Except for late submissions of course exercises to prevent issues in auto-save if (exercise.isExamExercise() || exerciseDateService.isBeforeDueDate(participation)) { modelingSubmission.setSubmitted(true); } + + // if athena results are present, then create a new submission on submit + if (modelingSubmission.getParticipation() != null && modelingSubmission.getParticipation().getResults() != null + && !modelingSubmission.getParticipation().getResults().isEmpty()) { + log.debug("Creating a new submission due to Athena results for user: {}", user.getLogin()); + modelingSubmission.setId(null); + } + modelingSubmission = save(modelingSubmission, exercise, user, participation); return modelingSubmission; } diff --git a/src/main/java/de/tum/cit/aet/artemis/modeling/web/ModelingSubmissionResource.java b/src/main/java/de/tum/cit/aet/artemis/modeling/web/ModelingSubmissionResource.java index 95a41a0f695a..e48f94b8e955 100644 --- a/src/main/java/de/tum/cit/aet/artemis/modeling/web/ModelingSubmissionResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/modeling/web/ModelingSubmissionResource.java @@ -3,6 +3,7 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import de.tum.cit.aet.artemis.assessment.domain.AssessmentType; import de.tum.cit.aet.artemis.assessment.domain.GradingCriterion; import de.tum.cit.aet.artemis.assessment.domain.Result; import de.tum.cit.aet.artemis.assessment.repository.GradingCriterionRepository; @@ -44,6 +46,7 @@ import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; import de.tum.cit.aet.artemis.exercise.repository.SubmissionRepository; +import de.tum.cit.aet.artemis.exercise.service.ExerciseDateService; import de.tum.cit.aet.artemis.exercise.web.AbstractSubmissionResource; import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; import de.tum.cit.aet.artemis.modeling.domain.ModelingSubmission; @@ -332,7 +335,9 @@ public ResponseEntity getLatestSubmissionForModelingEditor(@ // make sure only the latest submission and latest result is sent to the client participation.setSubmissions(null); - participation.setResults(null); + if (ExerciseDateService.isAfterAssessmentDueDate(modelingExercise)) { + participation.setResults(null); + } // do not send the result to the client if the assessment is not finished if (modelingSubmission.getLatestResult() != null @@ -340,6 +345,17 @@ public ResponseEntity getLatestSubmissionForModelingEditor(@ modelingSubmission.setResults(new ArrayList<>()); } + if (!ExerciseDateService.isAfterAssessmentDueDate(modelingExercise)) { + // We want to have the preliminary feedback before the assessment due date too + Set participationResults = participation.getResults(); + if (participationResults != null) { + List athenaResults = participationResults.stream().filter(result -> result.getAssessmentType() == AssessmentType.AUTOMATIC_ATHENA).toList(); + modelingSubmission.setResults(athenaResults); + Set athenaResultsSet = new HashSet<>(athenaResults); + participation.setResults(athenaResultsSet); + } + } + if (modelingSubmission.getLatestResult() != null && !authCheckService.isAtLeastTeachingAssistantForExercise(modelingExercise)) { modelingSubmission.getLatestResult().filterSensitiveInformation(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/text/service/TextExerciseFeedbackService.java b/src/main/java/de/tum/cit/aet/artemis/text/service/TextExerciseFeedbackService.java index 69f345c535cd..14a2558f6c9d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/text/service/TextExerciseFeedbackService.java +++ b/src/main/java/de/tum/cit/aet/artemis/text/service/TextExerciseFeedbackService.java @@ -35,8 +35,6 @@ public class TextExerciseFeedbackService { private static final Logger log = LoggerFactory.getLogger(TextExerciseFeedbackService.class); - public static final String NON_GRADED_FEEDBACK_SUGGESTION = "NonGradedFeedbackSuggestion:"; - private final Optional athenaFeedbackSuggestionsService; private final ResultWebsocketService resultWebsocketService; @@ -98,7 +96,7 @@ public void generateAutomaticNonGradedFeedback(StudentParticipation participatio log.debug("Using athena to generate (text exercise) feedback request: {}", textExercise.getId()); // athena takes over the control here - var submissionOptional = participationService.findTextExerciseParticipationWithLatestSubmissionAndResultElseThrow(participation.getId()).findLatestSubmission(); + var submissionOptional = participationService.findExerciseParticipationWithLatestSubmissionAndResultElseThrow(participation.getId()).findLatestSubmission(); if (submissionOptional.isEmpty()) { throw new BadRequestAlertException("No legal submissions found", "submission", "noSubmission"); diff --git a/src/main/webapp/app/exercises/modeling/assess/modeling-assessment-editor/modeling-assessment-editor.component.ts b/src/main/webapp/app/exercises/modeling/assess/modeling-assessment-editor/modeling-assessment-editor.component.ts index e6610b4edaf0..fe7a6017459f 100644 --- a/src/main/webapp/app/exercises/modeling/assess/modeling-assessment-editor/modeling-assessment-editor.component.ts +++ b/src/main/webapp/app/exercises/modeling/assess/modeling-assessment-editor/modeling-assessment-editor.component.ts @@ -237,8 +237,8 @@ export class ModelingAssessmentEditorComponent implements OnInit { if (this.result?.feedbacks) { this.result = this.modelingAssessmentService.convertResult(this.result); - } else { - this.result!.feedbacks = []; + } else if (this.result) { + this.result.feedbacks = []; } // Only load suggestions for new assessments, they don't make sense later. diff --git a/src/main/webapp/app/exercises/shared/feedback-suggestion/exercise-feedback-suggestion-options.component.html b/src/main/webapp/app/exercises/shared/feedback-suggestion/exercise-feedback-suggestion-options.component.html index 55d7f5ce03a4..95d72394f220 100644 --- a/src/main/webapp/app/exercises/shared/feedback-suggestion/exercise-feedback-suggestion-options.component.html +++ b/src/main/webapp/app/exercises/shared/feedback-suggestion/exercise-feedback-suggestion-options.component.html @@ -18,7 +18,7 @@ > - @if (this.exercise.type === ExerciseType.TEXT) { + @if (this.exercise.type === ExerciseType.TEXT || this.exercise.type === ExerciseType.MODELING) {
{ - if (result.participation && result.participation.exercise && result.participation.exercise.type === ExerciseType.TEXT) { + const exerciseType = result.participation?.exercise?.type; + if (exerciseType === ExerciseType.TEXT || exerciseType === ExerciseType.MODELING) { return result.assessmentType === AssessmentType.AUTOMATIC_ATHENA; } else return ( diff --git a/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.html b/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.html index 148fdfdef932..53a99d1f440a 100644 --- a/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.html +++ b/src/main/webapp/app/overview/exercise-details/exercise-details-student-actions.component.html @@ -251,7 +251,7 @@ [hideLabelMobile]="false" [routerLink]="['/courses', courseId, 'exercises', exercise.type + '-exercises', exercise.id, 'participate', gradedParticipation!.id]" > - @if (exercise.allowFeedbackRequests && athenaEnabled && exercise.type === ExerciseType.TEXT) { + @if (exercise.allowFeedbackRequests && athenaEnabled && (exercise.type === ExerciseType.TEXT || exercise.type === ExerciseType.MODELING)) {