Skip to content

Commit

Permalink
review round
Browse files Browse the repository at this point in the history
  • Loading branch information
Feuermagier committed Dec 12, 2024
1 parent 9a41a9f commit b6e792f
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
public enum AnnotationSource {
MANUAL_FIRST_ROUND,
MANUAL_SECOND_ROUND,
REVIEW,
AUTOGRADER,
UNKNOWN
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public final class Annotation {
private final AnnotationSource source;
private String customMessage;
private Double customScore;
private boolean deletedInReview;
// If not empty, this list contains classifiers that are used to group annotations.
// For example, all annotations that are related, could have the classifier ["a"],
// then they would be grouped together.
Expand All @@ -52,6 +53,7 @@ public Annotation(AnnotationDTO dto, MistakeType mistakeType) {
this.customScore = dto.customPenaltyForJSON();
this.classifiers = dto.classifiers() != null ? dto.classifiers() : List.of();
this.annotationLimit = dto.annotationLimit();
this.deletedInReview = dto.deletedInReview() != null && dto.deletedInReview();
}

Annotation(
Expand Down Expand Up @@ -207,6 +209,14 @@ public AnnotationSource getSource() {
return source;
}

public void setDeletedInReview(boolean deletedInReview) {
this.deletedInReview = deletedInReview;
}

public boolean isDeletedInReview() {
return this.deletedInReview;
}

/**
* Serializes this annotation to its metajson format
*/
Expand All @@ -221,7 +231,8 @@ public AnnotationDTO toDTO() {
customScore,
source,
classifiers,
annotationLimit);
annotationLimit,
deletedInReview);
}

@Override
Expand Down
33 changes: 21 additions & 12 deletions src/main/java/edu/kit/kastel/sdq/artemis4j/grading/Assessment.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ public class Assessment extends ArtemisConnectionHolder {
private final List<TestResult> testResults;
private final ProgrammingSubmission programmingSubmission;
private final GradingConfig config;
private final int correctionRound;
private final CorrectionRound correctionRound;
private final Locale studentLocale;

public Assessment(
ResultDTO result, GradingConfig config, ProgrammingSubmission programmingSubmission, int correctionRound)
ResultDTO result,
GradingConfig config,
ProgrammingSubmission programmingSubmission,
CorrectionRound correctionRound)
throws AnnotationMappingException, ArtemisNetworkException {
this(result, config, programmingSubmission, correctionRound, Locale.GERMANY);
}
Expand All @@ -72,7 +75,7 @@ public Assessment(
ResultDTO result,
GradingConfig config,
ProgrammingSubmission programmingSubmission,
int correctionRound,
CorrectionRound correctionRound,
Locale studentLocale)
throws AnnotationMappingException, ArtemisNetworkException {
super(programmingSubmission);
Expand Down Expand Up @@ -151,8 +154,7 @@ public Annotation addPredefinedAnnotation(
throw new IllegalArgumentException("Mistake type is a custom annotation");
}

var source =
this.correctionRound == 0 ? AnnotationSource.MANUAL_FIRST_ROUND : AnnotationSource.MANUAL_SECOND_ROUND;
var source = this.correctionRound.toAnnotationSource();
var annotation = new Annotation(mistakeType, filePath, startLine, endLine, customMessage, null, source);
this.annotations.add(annotation);
return annotation;
Expand Down Expand Up @@ -180,8 +182,7 @@ public Annotation addCustomAnnotation(
"Custom annotations with positive scores are not allowed for this exercise");
}

var source =
this.correctionRound == 0 ? AnnotationSource.MANUAL_FIRST_ROUND : AnnotationSource.MANUAL_SECOND_ROUND;
var source = this.correctionRound.toAnnotationSource();
var annotation = new Annotation(mistakeType, filePath, startLine, endLine, customMessage, customScore, source);
this.annotations.add(annotation);
return annotation;
Expand Down Expand Up @@ -250,7 +251,7 @@ public void submit() throws AnnotationMappingException, ArtemisNetworkException
* submission is the same.
*/
public String exportAssessment() throws AnnotationMappingException {
String header = this.programmingSubmission.getId() + ";" + this.correctionRound + ";";
String header = this.programmingSubmission.getId() + ";" + this.correctionRound.toArtemis() + ";";
return header + MetaFeedbackMapper.serializeAnnotations(this.annotations);
}

Expand All @@ -265,7 +266,7 @@ public void importAssessment(String exportedAssessment) throws AnnotationMapping
if (Integer.parseInt(parts[0]) != this.programmingSubmission.getId()) {
throw new IllegalArgumentException("Submission ID does not match");
}
if (Integer.parseInt(parts[1]) != this.correctionRound) {
if (Integer.parseInt(parts[1]) != this.correctionRound.toArtemis()) {
throw new IllegalArgumentException("Correction round does not match");
}
} catch (NumberFormatException e) {
Expand Down Expand Up @@ -327,7 +328,9 @@ public double getMaxPoints() {
* total points for the annotations.
*/
public Optional<Points> calculatePointsForMistakeType(MistakeType mistakeType) {
var annotationsWithType = this.getAnnotations(mistakeType);
var annotationsWithType = this.getAnnotations(mistakeType).stream()
.filter(a -> !a.isDeletedInReview())
.toList();
if (annotationsWithType.isEmpty()) {
return Optional.empty();
}
Expand All @@ -342,6 +345,7 @@ public Points calculatePointsForRatingGroup(RatingGroup ratingGroup) {
double points = this.annotations.stream()
.filter(a -> a.getMistakeType().getRatingGroup().equals(ratingGroup))
.filter(a -> a.getMistakeType().shouldScore())
.filter(a -> !a.isDeletedInReview())
.collect(Collectors.groupingBy(Annotation::getMistakeType))
.entrySet()
.stream()
Expand All @@ -361,7 +365,7 @@ public GradingConfig getConfig() {
return config;
}

public int getCorrectionRound() {
public CorrectionRound getCorrectionRound() {
return correctionRound;
}

Expand Down Expand Up @@ -423,7 +427,7 @@ private List<FeedbackDTO> packAssessmentForArtemis() throws AnnotationMappingExc
.map(this::createInlineFeedback)
.toList());

// We have on (or more if they are too long) global feedback per rating group
// We have one (or more if they are too long) global feedback per rating group
// These feedbacks deduct points
feedbacks.addAll(this.config.getRatingGroups().stream()
.flatMap(r -> this.createGlobalFeedback(r).stream())
Expand Down Expand Up @@ -460,6 +464,7 @@ private FeedbackDTO createInlineFeedback(Map.Entry<Integer, List<Annotation>> an
"File " + sampleAnnotation.getFilePathWithoutType() + " at line " + sampleAnnotation.getDisplayLine();
String reference = "file:" + sampleAnnotation.getFilePath() + "_line:" + sampleAnnotation.getStartLine();
String detailText = annotations.getValue().stream()
.filter(a -> !a.isDeletedInReview())
.map(a -> {
if (a.getMistakeType().isCustomAnnotation()) {
return MANUAL_FEEDBACK_CUSTOM_PENALTY
Expand Down Expand Up @@ -523,6 +528,10 @@ private List<FeedbackDTO> createGlobalFeedback(RatingGroup ratingGroup) {

// Individual annotations
for (var annotation : this.getAnnotations(mistakeType)) {
if (annotation.isDeletedInReview()) {
continue;
}

// For custom annotations, we have '* <file> at line <line> (<score>P)'
// Otherwise, it's just '* <file> at line <line>'
// Lines are zero-indexed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* Licensed under EPL-2.0 2024. */
package edu.kit.kastel.sdq.artemis4j.grading;

import edu.kit.kastel.sdq.artemis4j.client.AnnotationSource;

public enum CorrectionRound {
FIRST,
SECOND,
REVIEW;

public int toArtemis() {
return switch (this) {
case FIRST -> 0;
case SECOND, REVIEW -> 1;
};
}

public AnnotationSource toAnnotationSource() {
return switch (this) {
case FIRST -> AnnotationSource.MANUAL_FIRST_ROUND;
case SECOND -> AnnotationSource.MANUAL_SECOND_ROUND;
case REVIEW -> AnnotationSource.REVIEW;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,29 @@ public boolean hasSecondCorrectionRound() {
* @return a list of submissions
* @throws ArtemisNetworkException if the request fails
*/
public List<ProgrammingSubmission> fetchSubmissions(int correctionRound, boolean filterAssessedByTutor)
public List<ProgrammingSubmission> fetchSubmissions(CorrectionRound correctionRound, boolean filterAssessedByTutor)
throws ArtemisNetworkException {

if (correctionRound == CorrectionRound.SECOND && !this.hasSecondCorrectionRound()) {
throw new IllegalArgumentException("This exercise does not have a second correction round");
}

if (correctionRound == CorrectionRound.REVIEW) {
throw new IllegalArgumentException("Can't fetch submissions for the review 'round'");
}

return ProgrammingSubmissionDTO.fetchAll(
this.getConnection().getClient(), this.getId(), correctionRound, filterAssessedByTutor)
this.getConnection().getClient(),
this.getId(),
correctionRound.toArtemis(),
filterAssessedByTutor)
.stream()
.map(submissionDto -> new ProgrammingSubmission(submissionDto, this, correctionRound))
.toList();
}

public List<ProgrammingSubmission> fetchSubmissions(int correctionRound) throws ArtemisNetworkException {
public List<ProgrammingSubmission> fetchSubmissions(CorrectionRound correctionRound)
throws ArtemisNetworkException {
return this.fetchSubmissions(
correctionRound,
!this.getCourse().isInstructor(this.getConnection().getAssessor()));
Expand All @@ -103,9 +116,9 @@ public List<ProgrammingSubmission> fetchSubmissions(int correctionRound) throws
* Fetches all submissions from correction round 1 and 2 (if enabled).
*/
public List<ProgrammingSubmission> fetchSubmissions() throws ArtemisNetworkException {
List<ProgrammingSubmission> submissions = new ArrayList<>(this.fetchSubmissions(0));
List<ProgrammingSubmission> submissions = new ArrayList<>(this.fetchSubmissions(CorrectionRound.FIRST));
if (this.hasSecondCorrectionRound()) {
submissions.addAll(this.fetchSubmissions(1));
submissions.addAll(this.fetchSubmissions(CorrectionRound.SECOND));
}

return submissions;
Expand All @@ -118,14 +131,14 @@ public List<ProgrammingSubmission> fetchSubmissions() throws ArtemisNetworkExcep
* @return An empty optional if no submission was available to lock, otherwise
* the assessment
*/
public Optional<Assessment> tryLockNextSubmission(int correctionRound, GradingConfig gradingConfig)
public Optional<Assessment> tryLockNextSubmission(CorrectionRound correctionRound, GradingConfig gradingConfig)
throws AnnotationMappingException, ArtemisNetworkException {
this.assertGradingConfigValid(gradingConfig);

// This line already locks the submission, but doesn't tell us what the relevant
// ResultDTO is
var nextSubmissionDto = ProgrammingSubmissionDTO.lockNextSubmission(
this.getConnection().getClient(), this.getId(), correctionRound);
this.getConnection().getClient(), this.getId(), correctionRound.toArtemis());
if (nextSubmissionDto.isEmpty()) {
return Optional.empty();
}
Expand Down Expand Up @@ -156,11 +169,13 @@ public Optional<Assessment> tryLockNextSubmission(int correctionRound, GradingCo
* corresponding student (i.e.
* participation)
*/
public Optional<Assessment> tryLockSubmission(long submissionId, int correctionRound, GradingConfig gradingConfig)
public Optional<Assessment> tryLockSubmission(
long submissionId, CorrectionRound correctionRound, GradingConfig gradingConfig)
throws AnnotationMappingException, ArtemisNetworkException, MoreRecentSubmissionException {
this.assertGradingConfigValid(gradingConfig);

var locked = ProgrammingSubmissionDTO.lock(this.getConnection().getClient(), submissionId, correctionRound);
var locked = ProgrammingSubmissionDTO.lock(
this.getConnection().getClient(), submissionId, correctionRound.toArtemis());

if (locked.id() != submissionId) {
// Artemis automatically returns the most recent submission associated with the
Expand Down Expand Up @@ -188,11 +203,11 @@ public Optional<Assessment> tryLockSubmission(long submissionId, int correctionR
return Optional.of(new Assessment(result, gradingConfig, submission, correctionRound));
}

public int fetchOwnSubmissionCount(int correctionRound) throws ArtemisNetworkException {
public int fetchOwnSubmissionCount(CorrectionRound correctionRound) throws ArtemisNetworkException {
return this.fetchSubmissions(correctionRound, true).size();
}

public int fetchLockedSubmissionCount(int correctionRound) throws ArtemisNetworkException {
public int fetchLockedSubmissionCount(CorrectionRound correctionRound) throws ArtemisNetworkException {
return (int) this.fetchSubmissions(correctionRound, true).stream()
.filter(s -> !s.isSubmitted())
.count();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
public class ProgrammingSubmission extends ArtemisConnectionHolder {
private final ProgrammingSubmissionDTO dto;

private final int correctionRound;
private final CorrectionRound correctionRound;
private final User student;
private final ProgrammingExercise exercise;

public ProgrammingSubmission(ProgrammingSubmissionDTO dto, ProgrammingExercise exercise, int correctionRound) {
public ProgrammingSubmission(
ProgrammingSubmissionDTO dto, ProgrammingExercise exercise, CorrectionRound correctionRound) {
super(exercise);

this.dto = dto;
Expand Down Expand Up @@ -78,7 +79,7 @@ public ProgrammingExercise getExercise() {
return exercise;
}

public int getCorrectionRound() {
public CorrectionRound getCorrectionRound() {
return this.correctionRound;
}

Expand Down Expand Up @@ -220,7 +221,7 @@ public Optional<ResultDTO> getRelevantResult() {
return Optional.of(results.get(0));
} else {
// More than one result, so probably multiple correction rounds
return Optional.of(results.get(this.correctionRound));
return Optional.of(results.get(this.correctionRound.toArtemis()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ public record AnnotationDTO(
@JsonProperty Double customPenaltyForJSON,
@JsonProperty AnnotationSource source,
@JsonProperty List<String> classifiers,
@JsonProperty Integer annotationLimit) {}
@JsonProperty Integer annotationLimit,
@JsonProperty Boolean deletedInReview) {}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import edu.kit.kastel.sdq.artemis4j.client.ArtemisInstance;
import edu.kit.kastel.sdq.artemis4j.grading.ArtemisConnection;
import edu.kit.kastel.sdq.artemis4j.grading.CorrectionRound;
import edu.kit.kastel.sdq.artemis4j.grading.autograder.AutograderFailedException;
import edu.kit.kastel.sdq.artemis4j.grading.autograder.AutograderRunner;
import edu.kit.kastel.sdq.artemis4j.grading.penalty.GradingConfig;
Expand Down Expand Up @@ -46,8 +47,9 @@ void testLogin() throws ArtemisClientException, IOException {
// Check how many locks we hold across the entire course
System.out.println("Currently " + course.fetchLockedSubmissionCount() + " submissions locked in the course");
// We can also look at this value for a specific exercise
System.out.println("Currently " + course.getProgrammingExerciseById(47).fetchLockedSubmissionCount(0)
+ " submissions locked in exercise");
System.out.println(
"Currently " + course.getProgrammingExerciseById(47).fetchLockedSubmissionCount(CorrectionRound.FIRST)
+ " submissions locked in exercise");

// Get the first exercise (not the exercise with id 0!) in the course
var exercise = course.getProgrammingExercises().get(0);
Expand All @@ -64,7 +66,8 @@ void testLogin() throws ArtemisClientException, IOException {
// You can also use tryLockNextSubmission(correctionRound, gradingConfig) to
// request the next submission to grade
// without supplying an id
var assessment = exercise.tryLockSubmission(538, 0, gradingConfig).orElseThrow();
var assessment = exercise.tryLockSubmission(538, CorrectionRound.FIRST, gradingConfig)
.orElseThrow();
assessment.clearAnnotations();

// Let's clone the test repository & submission into a temporary directory
Expand Down
7 changes: 5 additions & 2 deletions src/test/java/edu/kit/kastel/sdq/artemis4j/ExamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import edu.kit.kastel.sdq.artemis4j.client.ArtemisInstance;
import edu.kit.kastel.sdq.artemis4j.grading.ArtemisConnection;
import edu.kit.kastel.sdq.artemis4j.grading.Assessment;
import edu.kit.kastel.sdq.artemis4j.grading.CorrectionRound;
import edu.kit.kastel.sdq.artemis4j.grading.Course;
import edu.kit.kastel.sdq.artemis4j.grading.Exam;
import edu.kit.kastel.sdq.artemis4j.grading.ExamExerciseGroup;
Expand Down Expand Up @@ -49,7 +50,8 @@ void testExamAssessment() throws ArtemisClientException, IOException {
GradingConfig config =
GradingConfig.readFromString(Files.readString(Path.of("src/test/resources/config.json")), exercise);

ProgrammingSubmission roundOneSubmission = findSubmission(exercise.fetchSubmissions(0), STUDENT_USER);
ProgrammingSubmission roundOneSubmission =
findSubmission(exercise.fetchSubmissions(CorrectionRound.FIRST), STUDENT_USER);
Assessment roundOneAssessment = roundOneSubmission.tryLock(config).orElseThrow();
roundOneAssessment.clearAnnotations();
roundOneAssessment.addCustomAnnotation(
Expand All @@ -61,7 +63,8 @@ void testExamAssessment() throws ArtemisClientException, IOException {
-2.0);
roundOneAssessment.submit();

ProgrammingSubmission roundTwoSubmission = findSubmission(exercise.fetchSubmissions(1), STUDENT_USER);
ProgrammingSubmission roundTwoSubmission =
findSubmission(exercise.fetchSubmissions(CorrectionRound.SECOND), STUDENT_USER);
Assessment roundTwoAssessment = roundTwoSubmission.tryLock(config).orElseThrow();
roundTwoAssessment.addCustomAnnotation(
config.getMistakeTypeById("custom"),
Expand Down

0 comments on commit b6e792f

Please sign in to comment.