Skip to content

Commit

Permalink
Programming exercises: Add server support for feedback suggestions (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pal03377 authored Nov 25, 2023
1 parent bd0d757 commit d1ee9ca
Show file tree
Hide file tree
Showing 109 changed files with 2,326 additions and 654 deletions.
8 changes: 6 additions & 2 deletions docs/admin/setup/athena.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ HTTP. We need to extend the configuration in the file
.. code:: yaml
artemis:
# ...
athena:
# ...
athena:
url: http://localhost:5000
secret: abcdef12345
modules:
# See https://github.com/ls1intum/Athena for a list of available modules
text: module_text_cofee
programming: module_programming_themisml
The secret can be any string. For more detailed instructions on how to set it up in Athena, refer to the Athena documentation_.

Expand Down
3 changes: 1 addition & 2 deletions docs/dev/setup/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ You can override the following configuration options in this file.
name: Artemis
email: [email protected]
athena:
url: http://localhost:5000
secret: abcdef12345
# If you want to use Athena, look at the dedicated section in the config.
Change all entries with ``<...>`` with proper values, e.g. your TUM
Online account credentials to connect to the given instances of JIRA,
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public final class Constants {

public static final String APOLLON_CONVERSION_API_PATH = "/api/apollon/convert-to-pdf";

public static final String ATHENA_PROGRAMMING_EXERCISE_REPOSITORY_API_PATH = "/api/public/athena/programming-exercises/";

// short names should have at least 3 characters and must start with a letter
public static final String SHORT_NAME_REGEX = "^[a-zA-Z][a-zA-Z0-9]{2,}";

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/domain/Exercise.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ public abstract class Exercise extends BaseExercise implements LearningObject {
@Column(name = "second_correction_enabled")
private Boolean secondCorrectionEnabled = false;

@Column(name = "feedback_suggestions_enabled") // enables Athena
private Boolean feedbackSuggestionsEnabled = false;

@ManyToOne
@JsonView(QuizView.Before.class)
private Course course;
Expand Down Expand Up @@ -780,6 +783,14 @@ public void setSecondCorrectionEnabled(boolean secondCorrectionEnabled) {
this.secondCorrectionEnabled = secondCorrectionEnabled;
}

public boolean getFeedbackSuggestionsEnabled() {
return Boolean.TRUE.equals(feedbackSuggestionsEnabled);
}

public void setFeedbackSuggestionsEnabled(boolean feedbackSuggestionsEnabled) {
this.feedbackSuggestionsEnabled = feedbackSuggestionsEnabled;
}

public List<GradingCriterion> getGradingCriteria() {
return gradingCriteria;
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/domain/Feedback.java
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,17 @@ public boolean isTestFeedback() {
return this.type == FeedbackType.AUTOMATIC && !isStaticCodeAnalysisFeedback() && !isSubmissionPolicyFeedback();
}

/**
* Checks whether the feedback was given manually by a tutor.
* (This includes feedback that is automatically created by Athena and approved by tutors.)
*
* @return true if it is a manual feedback else false
*/
@JsonIgnore
public boolean isManualFeedback() {
return this.type == FeedbackType.MANUAL || this.type == FeedbackType.MANUAL_UNREFERENCED;
}

/**
* Returns the Artemis static code analysis category to which this feedback belongs. The method returns an empty
* String, if the feedback is not static code analysis feedback.
Expand Down
7 changes: 0 additions & 7 deletions src/main/java/de/tum/in/www1/artemis/domain/TextBlockRef.java

This file was deleted.

17 changes: 0 additions & 17 deletions src/main/java/de/tum/in/www1/artemis/domain/TextExercise.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

import javax.persistence.*;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.in.www1.artemis.domain.enumeration.AssessmentType;
import de.tum.in.www1.artemis.domain.enumeration.ExerciseType;

/**
Expand Down Expand Up @@ -35,21 +33,6 @@ public void setExampleSolution(String exampleSolution) {
this.exampleSolution = exampleSolution;
}

@JsonIgnore
public boolean isFeedbackSuggestionsEnabled() {
return getAssessmentType() == AssessmentType.SEMI_AUTOMATIC;
}

/**
* Disable feedback suggestions for this exercise by setting the assessment type to MANUAL.
* Only changes the assessment type if feedback suggestions are currently enabled.
*/
public void disableFeedbackSuggestions() {
if (isFeedbackSuggestionsEnabled()) {
setAssessmentType(AssessmentType.MANUAL);
}
}

/**
* set all sensitive information to null, so no info with respect to the solution gets leaked to students through json
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,24 @@ default boolean toggleSecondCorrection(Exercise exercise) {
""")
Set<Exercise> getAllExercisesUserParticipatedInWithEagerParticipationsSubmissionsResultsFeedbacksTestCasesByUserId(long userId);

/**
* Finds all exercises filtered by feedback suggestions and due date.
*
* @param feedbackSuggestionsEnabled - filter by feedback suggestions enabled
* @param dueDate - filter by due date
* @return Set of Exercises
*/
Set<Exercise> findByFeedbackSuggestionsEnabledAndDueDateIsAfter(boolean feedbackSuggestionsEnabled, ZonedDateTime dueDate);

/**
* Find all exercises feedback suggestions (Athena) and with *Due Date* in the future.
*
* @return Set of Exercises
*/
default Set<Exercise> findAllFeedbackSuggestionsEnabledExercisesWithFutureDueDate() {
return findByFeedbackSuggestionsEnabledAndDueDateIsAfter(true, ZonedDateTime.now());
}

/**
* For an explanation, see {@link de.tum.in.www1.artemis.web.rest.ExamResource#getAllExercisesWithPotentialPlagiarismForExam(long,long)}
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,23 @@ default ProgrammingExercise findByIdElseThrow(Long programmingExerciseId) throws
return findById(programmingExerciseId).orElseThrow(() -> new EntityNotFoundException("Programming Exercise", programmingExerciseId));
}

/**
* Find a programming exercise by its id, with grading criteria loaded, and throw an EntityNotFoundException if it cannot be found
*
* @param exerciseId of the programming exercise.
* @return The programming exercise related to the given id
*/
@Query("""
SELECT DISTINCT e FROM ProgrammingExercise e
LEFT JOIN FETCH e.gradingCriteria
WHERE e.id = :exerciseId
""")
Optional<ProgrammingExercise> findByIdWithGradingCriteria(long exerciseId);

default ProgrammingExercise findByIdWithGradingCriteriaElseThrow(long exerciseId) {
return findByIdWithGradingCriteria(exerciseId).orElseThrow(() -> new EntityNotFoundException("Programming Exercise", exerciseId));
}

/**
* Find a programming exercise by its id and fetch related plagiarism detection config.
* Throws an EntityNotFoundException if the exercise cannot be found.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ Optional<ProgrammingExerciseStudentParticipation> findWithSubmissionsAndEagerStu

List<ProgrammingExerciseStudentParticipation> findByExerciseId(Long exerciseId);

@EntityGraph(type = LOAD, attributePaths = { "submissions" })
List<ProgrammingExerciseStudentParticipation> findWithSubmissionsById(long participationId);

@EntityGraph(type = LOAD, attributePaths = { "submissions" })
List<ProgrammingExerciseStudentParticipation> findWithSubmissionsByExerciseId(Long exerciseId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@
@Repository
public interface ProgrammingSubmissionRepository extends JpaRepository<ProgrammingSubmission, Long> {

/**
* Load programming submission only
*
* @param submissionId the submissionId
* @return programming submission
*/
@NotNull
default ProgrammingSubmission findByIdElseThrow(long submissionId) {
return findById(submissionId).orElseThrow(() -> new EntityNotFoundException("ProgrammingSubmission", submissionId));
}

@EntityGraph(type = LOAD, attributePaths = { "results.feedbacks" })
ProgrammingSubmission findFirstByParticipationIdAndCommitHashOrderByIdDesc(Long participationId, String commitHash);

Expand Down Expand Up @@ -52,18 +63,12 @@ public interface ProgrammingSubmissionRepository extends JpaRepository<Programmi
@Query("select s from ProgrammingSubmission s left join s.participation p left join p.exercise e where p.id = :#{#participationId} and (s.type = 'INSTRUCTOR' or s.type = 'TEST' or e.dueDate is null or s.submissionDate <= e.dueDate) order by s.submissionDate desc")
List<ProgrammingSubmission> findGradedByParticipationIdOrderBySubmissionDateDesc(@Param("participationId") Long participationId, Pageable pageable);

@Query("""
SELECT s
FROM ProgrammingSubmission s
WHERE s.participation.id = :participationId
AND (s.type <> 'ILLEGAL' OR s.type IS NULL)
ORDER BY s.submissionDate DESC
""")
List<ProgrammingSubmission> findLatestLegalSubmissionForParticipation(@Param("participationId") Long participationId, Pageable pageable);

@EntityGraph(type = LOAD, attributePaths = "results")
Optional<ProgrammingSubmission> findWithEagerResultsById(Long submissionId);

@EntityGraph(type = LOAD, attributePaths = "results.feedbacks")
Optional<ProgrammingSubmission> findWithEagerResultsAndFeedbacksById(Long submissionId);

@EntityGraph(type = LOAD, attributePaths = { "results", "results.feedbacks", "results.feedbacks.testCase", "results.assessor" })
Optional<ProgrammingSubmission> findWithEagerResultsFeedbacksTestCasesAssessorById(long submissionId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.util.Optional;
import java.util.Set;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand Down Expand Up @@ -442,6 +444,34 @@ default Submission findByIdWithResultsElseThrow(long submissionId) {
return findWithEagerResultsAndAssessorById(submissionId).orElseThrow(() -> new EntityNotFoundException("Submission", +submissionId));
}

/**
* Gets all latest submitted Submissions, only one per participation
*
* @param exerciseId the ID of the exercise
* @param pageable the pagination information for the query
* @return Page of Submissions
*/
@Query("""
SELECT s FROM Submission s
WHERE s.participation.exercise.id = :exerciseId
AND s.submitted = true
AND s.submissionDate = (
SELECT MAX(s2.submissionDate)
FROM Submission s2
WHERE s2.participation.id = s.participation.id
AND s2.submitted = true
)
""")
Page<Submission> findLatestSubmittedSubmissionsByExerciseId(@Param("exerciseId") long exerciseId, Pageable pageable);

/**
* Gets all submitted Submissions for the given exercise. Note that you usually only want the latest submissions.
*
* @param exerciseId the ID of the exercise
* @return Set of Submissions
*/
Set<Submission> findByParticipation_ExerciseIdAndSubmittedIsTrue(long exerciseId);

default Submission findByIdElseThrow(long submissionId) {
return findById(submissionId).orElseThrow(() -> new EntityNotFoundException("Submission", submissionId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;

Expand All @@ -16,7 +15,6 @@
import org.springframework.stereotype.Repository;

import de.tum.in.www1.artemis.domain.TextExercise;
import de.tum.in.www1.artemis.domain.enumeration.AssessmentType;
import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException;

/**
Expand All @@ -38,8 +36,6 @@ public interface TextExerciseRepository extends JpaRepository<TextExercise, Long
@EntityGraph(type = LOAD, attributePaths = { "teamAssignmentConfig", "categories", "competencies", "plagiarismDetectionConfig" })
Optional<TextExercise> findWithEagerTeamAssignmentConfigAndCategoriesAndCompetenciesAndPlagiarismDetectionConfigById(Long exerciseId);

List<TextExercise> findByAssessmentTypeAndDueDateIsAfter(AssessmentType assessmentType, ZonedDateTime dueDate);

@Query("select textExercise from TextExercise textExercise left join fetch textExercise.exampleSubmissions exampleSubmissions left join fetch exampleSubmissions.submission submission left join fetch submission.results result left join fetch result.feedbacks left join fetch submission.blocks left join fetch result.assessor left join fetch textExercise.teamAssignmentConfig where textExercise.id = :#{#exerciseId}")
Optional<TextExercise> findByIdWithExampleSubmissionsAndResults(@Param("exerciseId") Long exerciseId);

Expand All @@ -51,6 +47,18 @@ default TextExercise findByIdElseThrow(long exerciseId) {
return findById(exerciseId).orElseThrow(() -> new EntityNotFoundException("Text Exercise", exerciseId));
}

@Query("""
SELECT DISTINCT e FROM TextExercise e
LEFT JOIN FETCH e.gradingCriteria
WHERE e.id = :exerciseId
""")
Optional<TextExercise> findByIdWithGradingCriteria(long exerciseId);

@NotNull
default TextExercise findByIdWithGradingCriteriaElseThrow(long exerciseId) {
return findByIdWithGradingCriteria(exerciseId).orElseThrow(() -> new EntityNotFoundException("Text Exercise", exerciseId));
}

@NotNull
default TextExercise findByIdWithExampleSubmissionsAndResultsElseThrow(long exerciseId) {
return findByIdWithExampleSubmissionsAndResults(exerciseId).orElseThrow(() -> new EntityNotFoundException("Text Exercise", exerciseId));
Expand All @@ -60,13 +68,4 @@ default TextExercise findByIdWithExampleSubmissionsAndResultsElseThrow(long exer
default TextExercise findByIdWithStudentParticipationsAndSubmissionsElseThrow(long exerciseId) {
return findWithStudentParticipationsAndSubmissionsById(exerciseId).orElseThrow(() -> new EntityNotFoundException("Text Exercise", exerciseId));
}

/**
* Find all exercises with *Due Date* in the future.
*
* @return List of Text Exercises
*/
default List<TextExercise> findAllAutomaticAssessmentTextExercisesWithFutureDueDate() {
return findByAssessmentTypeAndDueDateIsAfter(AssessmentType.SEMI_AUTOMATIC, ZonedDateTime.now());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

import javax.validation.constraints.NotNull;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand Down Expand Up @@ -67,16 +65,6 @@ default TextSubmission findByIdElseThrow(long submissionId) {
@EntityGraph(type = LOAD, attributePaths = { "blocks" })
Set<TextSubmission> findByParticipation_ExerciseIdAndSubmittedIsTrue(long exerciseId);

/**
* Gets all TextSubmissions which are submitted and loads all blocks
*
* @param exerciseId the ID of the exercise
* @param pageable the pagination information for the query
* @return Set of Text Submissions
*/
@EntityGraph(type = LOAD, attributePaths = { "blocks" })
Page<TextSubmission> findByParticipation_ExerciseIdAndSubmittedIsTrue(long exerciseId, Pageable pageable);

@NotNull
default TextSubmission getTextSubmissionWithResultAndTextBlocksAndFeedbackByResultIdElseThrow(long resultId) {
return findWithEagerResultAndTextBlocksAndFeedbackByResults_Id(resultId) // TODO should be EntityNotFoundException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import de.tum.in.www1.artemis.domain.participation.StudentParticipation;
import de.tum.in.www1.artemis.exception.EmptyFileException;
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.service.connectors.athena.AthenaSubmissionSelectionService;
import de.tum.in.www1.artemis.service.exam.ExamDateService;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException;
Expand All @@ -44,9 +45,9 @@ public FileUploadSubmissionService(FileUploadSubmissionRepository fileUploadSubm
ParticipationService participationService, UserRepository userRepository, StudentParticipationRepository studentParticipationRepository, FileService fileService,
AuthorizationCheckService authCheckService, FeedbackRepository feedbackRepository, ExamDateService examDateService, ExerciseDateService exerciseDateService,
CourseRepository courseRepository, ParticipationRepository participationRepository, ComplaintRepository complaintRepository, FeedbackService feedbackService,
FilePathService filePathService) {
FilePathService filePathService, Optional<AthenaSubmissionSelectionService> athenaSubmissionSelectionService) {
super(submissionRepository, userRepository, authCheckService, resultRepository, studentParticipationRepository, participationService, feedbackRepository, examDateService,
exerciseDateService, courseRepository, participationRepository, complaintRepository, feedbackService);
exerciseDateService, courseRepository, participationRepository, complaintRepository, feedbackService, athenaSubmissionSelectionService);
this.fileUploadSubmissionRepository = fileUploadSubmissionRepository;
this.fileService = fileService;
this.exerciseDateService = exerciseDateService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import de.tum.in.www1.artemis.domain.participation.StudentParticipation;
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.service.compass.CompassService;
import de.tum.in.www1.artemis.service.connectors.athena.AthenaSubmissionSelectionService;
import de.tum.in.www1.artemis.service.exam.ExamDateService;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;

Expand All @@ -45,9 +46,10 @@ public ModelingSubmissionService(ModelingSubmissionRepository modelingSubmission
CompassService compassService, UserRepository userRepository, SubmissionVersionService submissionVersionService, ParticipationService participationService,
StudentParticipationRepository studentParticipationRepository, AuthorizationCheckService authCheckService, FeedbackRepository feedbackRepository,
ExamDateService examDateService, ExerciseDateService exerciseDateService, CourseRepository courseRepository, ParticipationRepository participationRepository,
ModelElementRepository modelElementRepository, ComplaintRepository complaintRepository, FeedbackService feedbackService) {
ModelElementRepository modelElementRepository, ComplaintRepository complaintRepository, FeedbackService feedbackService,
Optional<AthenaSubmissionSelectionService> athenaSubmissionSelectionService) {
super(submissionRepository, userRepository, authCheckService, resultRepository, studentParticipationRepository, participationService, feedbackRepository, examDateService,
exerciseDateService, courseRepository, participationRepository, complaintRepository, feedbackService);
exerciseDateService, courseRepository, participationRepository, complaintRepository, feedbackService, athenaSubmissionSelectionService);
this.modelingSubmissionRepository = modelingSubmissionRepository;
this.compassService = compassService;
this.submissionVersionService = submissionVersionService;
Expand Down
Loading

0 comments on commit d1ee9ca

Please sign in to comment.