Skip to content

Commit 65ea569

Browse files
committed
decouple programmingsubmission & correction round
1 parent b6e792f commit 65ea569

File tree

6 files changed

+212
-153
lines changed

6 files changed

+212
-153
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package edu.kit.kastel.sdq.artemis4j.grading;
2+
3+
import edu.kit.kastel.sdq.artemis4j.ArtemisNetworkException;
4+
import edu.kit.kastel.sdq.artemis4j.client.ProgrammingSubmissionDTO;
5+
import edu.kit.kastel.sdq.artemis4j.client.ResultDTO;
6+
import edu.kit.kastel.sdq.artemis4j.grading.metajson.AnnotationMappingException;
7+
import edu.kit.kastel.sdq.artemis4j.grading.penalty.GradingConfig;
8+
9+
import java.util.Optional;
10+
11+
/**
12+
* For API users, this is used in place of an assessment whenever we don't want to do
13+
* the (expensive) deserialization of the assessment.
14+
* The only semantic difference to an assessment is that this class does *not* imply a lock.
15+
* <p>
16+
* Internally, this is just a glorified ResultDTO wrapper because we need a place
17+
* to store the submission
18+
* @param result
19+
* @param submission
20+
*/
21+
public record PackedAssessment(ResultDTO result, CorrectionRound round, ProgrammingSubmission submission) {
22+
public boolean isSubmitted() {
23+
return result.completionDate() != null;
24+
}
25+
26+
public User getAssessor() {
27+
return new User(result.assessor());
28+
}
29+
30+
/**
31+
* Locks and opens the assessment for this submission.
32+
* <p>
33+
* If the submission has not been assessed by you, this might not be possible.
34+
*
35+
* @param config the config for the exercise
36+
* @return the assessment if there are results for this submission
37+
* @throws AnnotationMappingException If the annotations that were already
38+
* present could not be mapped given the
39+
* gradingConfig
40+
*/
41+
public Optional<Assessment> open(GradingConfig config) throws MoreRecentSubmissionException, ArtemisNetworkException, AnnotationMappingException {
42+
return this.submission.getExercise().tryLockSubmission(this.submission.getId(), this.round, config);
43+
}
44+
45+
/**
46+
* Opens the assessment for this submission without locking it.
47+
* <p>
48+
* If the submission has not been assessed by you, you might not be able to
49+
* change the assessment.
50+
*
51+
* @param config the config for the exercise
52+
* @return the assessment if there are results for this submission
53+
* @throws AnnotationMappingException If the annotations that were already
54+
* present could not be mapped given the
55+
* gradingConfig
56+
*/
57+
public Assessment openWithoutLock(GradingConfig config) throws ArtemisNetworkException, AnnotationMappingException {
58+
return new Assessment(this.result, config, this.submission, this.round);
59+
}
60+
61+
public void cancel() throws ArtemisNetworkException {
62+
ProgrammingSubmissionDTO.cancelAssessment(this.getConnection().getClient(), this.submission.getId());
63+
}
64+
65+
private ArtemisConnection getConnection() {
66+
return this.submission.getConnection();
67+
}
68+
}

src/main/java/edu/kit/kastel/sdq/artemis4j/grading/ProgrammingExercise.java

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,52 @@ public boolean hasSecondCorrectionRound() {
7474
return this.dto.secondCorrectionEnabled() != null && this.dto.secondCorrectionEnabled();
7575
}
7676

77-
/**
78-
* Fetches all submissions for this exercise. This may fetch *many* submissions,
79-
* and does not cache the result, so be careful.
80-
*
81-
* @param correctionRound The correction round to fetch submissions for
82-
* @param filterAssessedByTutor Whether to only fetch submissions that the
83-
* current user has assessed
84-
* @return a list of submissions
85-
* @throws ArtemisNetworkException if the request fails
86-
*/
87-
public List<ProgrammingSubmission> fetchSubmissions(CorrectionRound correctionRound, boolean filterAssessedByTutor)
77+
// /**
78+
// * Fetches all submissions for this exercise. This may fetch *many* submissions,
79+
// * and does not cache the result, so be careful.
80+
// *
81+
// * @param correctionRound The correction round to fetch submissions for
82+
// * @param filterAssessedByTutor Whether to only fetch submissions that the
83+
// * current user has assessed
84+
// * @return a list of submissions
85+
// * @throws ArtemisNetworkException if the request fails
86+
// */
87+
// public List<ProgrammingSubmission> fetchSubmissions(CorrectionRound correctionRound, boolean filterAssessedByTutor)
88+
// throws ArtemisNetworkException {
89+
//
90+
// if (correctionRound == CorrectionRound.SECOND && !this.hasSecondCorrectionRound()) {
91+
// throw new IllegalArgumentException("This exercise does not have a second correction round");
92+
// }
93+
//
94+
// if (correctionRound == CorrectionRound.REVIEW) {
95+
// throw new IllegalArgumentException("Can't fetch submissions for the review 'round'");
96+
// }
97+
//
98+
// return ProgrammingSubmissionDTO.fetchAll(
99+
// this.getConnection().getClient(),
100+
// this.getId(),
101+
// correctionRound.toArtemis(),
102+
// filterAssessedByTutor)
103+
// .stream()
104+
// .map(submissionDto -> new ProgrammingSubmission(submissionDto, this))
105+
// .toList();
106+
// }
107+
108+
public List<ProgrammingSubmissionWithResults> fetchAllSubmissions()
88109
throws ArtemisNetworkException {
110+
// Artemis ignores the correction round since assessedByTutor is false
111+
return ProgrammingSubmissionDTO.fetchAll(
112+
this.getConnection().getClient(),
113+
this.getId(),
114+
0,
115+
true)
116+
.stream()
117+
.map(dto -> new ProgrammingSubmissionWithResults(new ProgrammingSubmission(dto, this)))
118+
.toList();
119+
}
89120

121+
public List<PackedAssessment> fetchMyAssessments(CorrectionRound correctionRound)
122+
throws ArtemisNetworkException {
90123
if (correctionRound == CorrectionRound.SECOND && !this.hasSecondCorrectionRound()) {
91124
throw new IllegalArgumentException("This exercise does not have a second correction round");
92125
}
@@ -95,33 +128,25 @@ public List<ProgrammingSubmission> fetchSubmissions(CorrectionRound correctionRo
95128
throw new IllegalArgumentException("Can't fetch submissions for the review 'round'");
96129
}
97130

98-
return ProgrammingSubmissionDTO.fetchAll(
131+
var submissions = ProgrammingSubmissionDTO.fetchAll(
99132
this.getConnection().getClient(),
100133
this.getId(),
101134
correctionRound.toArtemis(),
102-
filterAssessedByTutor)
135+
true)
103136
.stream()
104-
.map(submissionDto -> new ProgrammingSubmission(submissionDto, this, correctionRound))
137+
.map(submissionDto -> new ProgrammingSubmission(submissionDto, this))
105138
.toList();
106-
}
107139

108-
public List<ProgrammingSubmission> fetchSubmissions(CorrectionRound correctionRound)
109-
throws ArtemisNetworkException {
110-
return this.fetchSubmissions(
111-
correctionRound,
112-
!this.getCourse().isInstructor(this.getConnection().getAssessor()));
113-
}
114-
115-
/**
116-
* Fetches all submissions from correction round 1 and 2 (if enabled).
117-
*/
118-
public List<ProgrammingSubmission> fetchSubmissions() throws ArtemisNetworkException {
119-
List<ProgrammingSubmission> submissions = new ArrayList<>(this.fetchSubmissions(CorrectionRound.FIRST));
120-
if (this.hasSecondCorrectionRound()) {
121-
submissions.addAll(this.fetchSubmissions(CorrectionRound.SECOND));
140+
var assessments = new ArrayList<PackedAssessment>(submissions.size());
141+
for (var submission : submissions) {
142+
// TODO this may return more than one result for instructors
143+
var results = submission.getDTO().nonAutomaticResults();
144+
if (results.size() != 1) {
145+
throw new IllegalStateException("Too many non-automatic results");
146+
}
147+
assessments.add(new PackedAssessment(results.get(0), correctionRound, submission));
122148
}
123-
124-
return submissions;
149+
return assessments;
125150
}
126151

127152
/**
@@ -178,9 +203,8 @@ public Optional<Assessment> tryLockSubmission(
178203
this.getConnection().getClient(), submissionId, correctionRound.toArtemis());
179204

180205
if (locked.id() != submissionId) {
181-
// Artemis automatically returns the most recent submission associated with the
182-
// same participation
183-
// as the requested submission
206+
// Artemis automatically returns the most recent submission that is associated with the
207+
// participation of the requested submission
184208
throw new MoreRecentSubmissionException(
185209
submissionId, locked.id(), locked.participation().id());
186210
}
@@ -199,16 +223,16 @@ public Optional<Assessment> tryLockSubmission(
199223
return Optional.empty();
200224
}
201225

202-
var submission = new ProgrammingSubmission(locked, this, correctionRound);
226+
var submission = new ProgrammingSubmission(locked, this);
203227
return Optional.of(new Assessment(result, gradingConfig, submission, correctionRound));
204228
}
205229

206-
public int fetchOwnSubmissionCount(CorrectionRound correctionRound) throws ArtemisNetworkException {
207-
return this.fetchSubmissions(correctionRound, true).size();
230+
public int fetchOwnAssessmentCount(CorrectionRound correctionRound) throws ArtemisNetworkException {
231+
return this.fetchMyAssessments(correctionRound).size();
208232
}
209233

210234
public int fetchLockedSubmissionCount(CorrectionRound correctionRound) throws ArtemisNetworkException {
211-
return (int) this.fetchSubmissions(correctionRound, true).stream()
235+
return (int) this.fetchMyAssessments(correctionRound).stream()
212236
.filter(s -> !s.isSubmitted())
213237
.count();
214238
}
Lines changed: 17 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
/* Licensed under EPL-2.0 2024. */
22
package edu.kit.kastel.sdq.artemis4j.grading;
33

4-
import java.nio.file.Path;
5-
import java.time.ZonedDateTime;
6-
import java.util.Objects;
7-
import java.util.Optional;
8-
94
import edu.kit.kastel.sdq.artemis4j.ArtemisClientException;
105
import edu.kit.kastel.sdq.artemis4j.ArtemisNetworkException;
11-
import edu.kit.kastel.sdq.artemis4j.client.AssessmentType;
126
import edu.kit.kastel.sdq.artemis4j.client.ProgrammingSubmissionDTO;
137
import edu.kit.kastel.sdq.artemis4j.client.ResultDTO;
148
import edu.kit.kastel.sdq.artemis4j.grading.metajson.AnnotationMappingException;
159
import edu.kit.kastel.sdq.artemis4j.grading.penalty.GradingConfig;
1610

11+
import java.nio.file.Path;
12+
import java.time.ZonedDateTime;
13+
import java.util.Objects;
14+
import java.util.Optional;
15+
1716
/**
1817
* A student's programming submission. A submission essentially consists of the
1918
* URL of a student's Git repository, along with a commit hash. We do not model
@@ -22,12 +21,11 @@
2221
public class ProgrammingSubmission extends ArtemisConnectionHolder {
2322
private final ProgrammingSubmissionDTO dto;
2423

25-
private final CorrectionRound correctionRound;
2624
private final User student;
2725
private final ProgrammingExercise exercise;
2826

2927
public ProgrammingSubmission(
30-
ProgrammingSubmissionDTO dto, ProgrammingExercise exercise, CorrectionRound correctionRound) {
28+
ProgrammingSubmissionDTO dto, ProgrammingExercise exercise) {
3129
super(exercise);
3230

3331
this.dto = dto;
@@ -40,8 +38,6 @@ public ProgrammingSubmission(
4038
} else {
4139
this.student = null;
4240
}
43-
44-
this.correctionRound = correctionRound;
4541
}
4642

4743
public long getId() {
@@ -79,10 +75,6 @@ public ProgrammingExercise getExercise() {
7975
return exercise;
8076
}
8177

82-
public CorrectionRound getCorrectionRound() {
83-
return this.correctionRound;
84-
}
85-
8678
public ZonedDateTime getSubmissionDate() {
8779
return this.dto.submissionDate();
8880
}
@@ -95,17 +87,6 @@ public Optional<ResultDTO> getLatestResult() {
9587
}
9688
}
9789

98-
/**
99-
* Get the assessor of this assessment.
100-
* <p>
101-
* This is the user who has locked the submission.
102-
*
103-
* @return the assessor or empty if the submission has not been assessed
104-
*/
105-
public Optional<User> getAssessor() {
106-
return this.getRelevantResult().map(ResultDTO::assessor).map(User::new);
107-
}
108-
10990
/**
11091
* Clones the submission, including the test repository, into the given path,
11192
* and checks out the submitted commit. This method uses the user's VCS access token, potentially creating a new one.
@@ -131,7 +112,9 @@ public ClonedProgrammingSubmission cloneViaSSHInto(Path target) throws ArtemisCl
131112
}
132113

133114
/**
134-
* Tries to lock this submission. Locking is reentrant.
115+
* Prefer the methods on PackedAssessment!
116+
* <p>
117+
* Tries to lock this submission for a given correction round. Locking is reentrant.
135118
*
136119
* @return An empty optional if a *different* user has already locked the
137120
* submission, otherwise the assessment
@@ -144,46 +127,21 @@ public ClonedProgrammingSubmission cloneViaSSHInto(Path target) throws ArtemisCl
144127
* corresponding student (i.e.
145128
* participation)
146129
*/
147-
public Optional<Assessment> tryLock(GradingConfig gradingConfig)
130+
public Optional<Assessment> tryLock(GradingConfig gradingConfig, CorrectionRound correctionRound)
148131
throws AnnotationMappingException, ArtemisNetworkException, MoreRecentSubmissionException {
149-
return this.exercise.tryLockSubmission(this.getId(), this.getCorrectionRound(), gradingConfig);
132+
return this.exercise.tryLockSubmission(this.getId(), correctionRound, gradingConfig);
150133
}
151134

152-
public boolean isSubmitted() {
153-
var result = this.getRelevantResult();
154-
if (result.isEmpty() || result.get().completionDate() == null) {
155-
return false;
156-
}
157-
158-
var assessmentType = result.get().assessmentType();
159-
return assessmentType == AssessmentType.MANUAL || assessmentType == AssessmentType.SEMI_AUTOMATIC;
135+
public boolean isBuildFailed() {
136+
return this.dto.buildFailed();
160137
}
161138

162139
/**
163-
* Opens the assessment for this submission.
164-
* <p>
165-
* If the submission has not been assessed by you, you might not be able to
166-
* change the assessment.
167-
*
168-
* @param config the config for the exercise
169-
* @return the assessment if there are results for this submission
170-
* @throws AnnotationMappingException If the annotations that were already
171-
* present could not be mapped given the
172-
* gradingConfig
140+
* Be VERY careful with the dto's results, since their number may differ depending on the source of the dto.
141+
* @return the corresponding dto
173142
*/
174-
public Optional<Assessment> openAssessment(GradingConfig config)
175-
throws AnnotationMappingException, ArtemisNetworkException {
176-
ResultDTO resultDTO = this.getRelevantResult().orElse(null);
177-
178-
if (resultDTO != null) {
179-
return Optional.of(new Assessment(resultDTO, config, this, this.correctionRound));
180-
}
181-
182-
return Optional.empty();
183-
}
184-
185-
public boolean isBuildFailed() {
186-
return this.dto.buildFailed();
143+
public ProgrammingSubmissionDTO getDTO() {
144+
return this.dto;
187145
}
188146

189147
@Override
@@ -198,30 +156,4 @@ public boolean equals(Object o) {
198156
public int hashCode() {
199157
return Objects.hashCode(this.getId());
200158
}
201-
202-
/**
203-
* Returns the relevant result for this submission.
204-
* <p>
205-
* The difference between this method and {@link #getLatestResult()} is that
206-
* when a submission has multiple results from different correction rounds, this
207-
* method will return the result for the current correction round. If you want the
208-
* latest result regardless of the correction round, use {@link #getLatestResult()}.
209-
*
210-
* @return the relevant result, if present
211-
*/
212-
public Optional<ResultDTO> getRelevantResult() {
213-
var results = this.dto.nonAutomaticResults();
214-
215-
if (results.isEmpty()) {
216-
return Optional.empty();
217-
} else if (results.size() == 1) {
218-
// We only have one result, so the submission has
219-
// probably been created for a specific correction round,
220-
// or we only have one correction round
221-
return Optional.of(results.get(0));
222-
} else {
223-
// More than one result, so probably multiple correction rounds
224-
return Optional.of(results.get(this.correctionRound.toArtemis()));
225-
}
226-
}
227159
}

0 commit comments

Comments
 (0)