Skip to content

Commit 7d3adf4

Browse files
Merge pull request #533 from JoyOfCodingPDX/issue-531/improve-find-ungraded-submissions
Improve find ungraded submissions tools
2 parents 169d5ad + a3b2d09 commit 7d3adf4

8 files changed

Lines changed: 1720 additions & 269 deletions

File tree

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
## Feature Description
2+
3+
This feature provides a way to identify submission grades that have not been recorded in the system. It helps
4+
instructors ensure that all student submissions are accounted for and graded appropriately.
5+
6+
This is a new feature of the FindUngradedSubmissions tool, which scans through student submissions and compares them
7+
against recorded grades to identify any discrepancies.
8+
9+
This will feature will require a new command line parameter to the main() method of FindUngradedSubmissions. The first
10+
parameter will now be the name of a gradebook XML file that contains the recorded grades. Subsequent parameters will
11+
continue to be the names of one or more submission files/directories to scan for untested, ungraded, and now unrecorded
12+
grades.
13+
14+
Here's the new command line usage:
15+
16+
```
17+
$ java -jar target/grader-1.5.2-SNAPSHOT.jar findUngradedSubmissions
18+
Usage: java FindUngradedSubmissions -includeReason gradeBookXmlFile submissionZipOrDirectory+
19+
```
20+
21+
The FindUngradedSubmissions class will now output the names of the .out files whose grades do not match what has been
22+
recorded in the student's gradebook XML file.
23+
24+
```
25+
0 submissions need to be tested:
26+
0 submissions need to be graded:
27+
3 submissions have unrecorded grades:
28+
./fred.out
29+
./jane.out
30+
./alex.out
31+
```
32+
33+
## Implementation Ideas
34+
35+
The `SubmissionAnalysis` record will need a new gradeNeedsToBeRecorded() property.
36+
37+
The gradebook XML file should be parsed into a GradeBook object. Information about student grades can be obtained from
38+
the GradeBook.
39+
40+
The name of the testOutput file begins with the student's login ID, which can be used to look up the recorded grade in
41+
the GradeBook XML file. For instance, the student id of `fred` corresponds to the `fred.out` test output file.
42+
43+
In order to determine if a submission's grade is unrecorded, we'll need to know which project assigment is associated
44+
with the testOutput (`.out`) file. The project can be determined from a line that looks like this in the `.out` file:
45+
46+
```
47+
The Joy of Coding Project 1: edu.pdx.cs410J.studentId.Project1
48+
```
49+
50+
In this example, the project assignment is "Project1". This is the name of the assignment in the GradeBook XML file.
51+
52+
The grade should be considered unrecorded if the recorded grade is missing or different from the grade determined in the
53+
GradeBook XML file.
54+
55+
## Test Cases
56+
57+
A submission is considered graded if there is a line like "4.6 out of 5.0" in the testOutput `.out` file, even if
58+
that grade is not recorded in the GradeBook XML file. In this case, the submission should be considered unrecorded.
59+
60+
A submission with a line like " out of 5.0" on line 7 is not considered to be graded.
61+
62+
If a line like " out of 5.0" appears on a line after line 7, the submission should be considered graded. We can assume
63+
that there is a note from the grade in the testOutput .out file that instructs the student to fix a fundamental flaw and
64+
resubmit. Since there is no grade, the submission does not need to be recorded in the gradebook, and it should not be
65+
considered unrecorded.
66+
67+
If a submission hasn't been graded yet, it should not be considered unrecorded.
68+
69+
If the submission has been graded (that is, a grade appears in the testOutput `.out` file) and there is no grade for the
70+
assignment in the GradeBook, the submission should be considered unrecorded.
71+
72+
If the submission has been graded and the grade in the GradeBook is different from the grade in the testOutput `.out`,
73+
the submission should be considered unrecorded.
74+
75+
If the submission has been graded and the grade in the GradeBook matches the grade in the testOutput `.out`, the
76+
submission should not be considered unrecorded.
77+
78+
If the submission has not been graded, it should not be considered unrecorded regardless of what is in the GradeBook.
79+
80+
There should be an end-to-end integration test for that includes parsing a gradebook XML file and multiple submission
81+
files (.out) that verify that some .out files have grades that need to be recorded and other .out files don't. This
82+
test should be implemented in a new integration test class called `FindUnrecordedSubmissionGradesIT` in the src/it/java
83+
directory. It should use the invokeMain() from InvokeMainTestCase to run the FindUngradedSubmissions main() method with
84+
the appropriate command line arguments and validate that the expected output is written to standard output. The files
85+
that the test uses should be placed in the src/it/resources directory. Or they could be generated programmatically by
86+
the test and placed in a temporary directory injected with JUnit 5's @TempDir annotation.
87+
88+
## Implementation Steps
89+
90+
### 1. Update the `SubmissionAnalysis` record
91+
92+
Add a new boolean property `gradeNeedsToBeRecorded` to the `SubmissionAnalysis` record (around line 196).
93+
94+
**Changes:**
95+
96+
- Add `boolean gradeNeedsToBeRecorded` as the fifth parameter to the record
97+
- Update all places that construct `SubmissionAnalysis` objects to pass `false` for this parameter initially
98+
99+
### 2. Create a new interface `GradeBookProvider`
100+
101+
Add a new interface in the `FindUngradedSubmissions` class to provide access to the GradeBook.
102+
103+
**Interface signature:**
104+
105+
```java
106+
interface GradeBookProvider {
107+
Optional<GradeBook> getGradeBook();
108+
}
109+
```
110+
111+
This allows for testability by mocking the gradebook access in tests.
112+
113+
### 3. Add GradeBookProvider to the constructor
114+
115+
Update the `FindUngradedSubmissions` constructor to accept an optional `GradeBookProvider` parameter.
116+
117+
**Changes:**
118+
119+
- Add `GradeBookProvider` field to the class (around line 24-27)
120+
- Update the `@VisibleForTesting` constructor (around line 30) to accept a `GradeBookProvider` parameter
121+
- Update the default constructor (around line 36) to pass `null` for the gradebook provider
122+
123+
### 4. Extend `TestOutputDetails` record
124+
125+
Add fields to capture the project assignment name and the grade from the test output file.
126+
127+
**Changes:**
128+
129+
- Add `String projectName` field to the `TestOutputDetails` record (around line 193)
130+
- Add `Double grade` field to the `TestOutputDetails` record (around line 193)
131+
- Update all places that construct `TestOutputDetails` objects
132+
133+
### 5. Update `TestOutputDetailsProviderFromTestOutputFile`
134+
135+
Modify the `TestOutputDetailsProviderFromTestOutputFile` class (around line 233) to extract:
136+
137+
- The project name from lines matching the pattern: `The Joy of Coding Project \\d+: edu.pdx.cs.\\w+.\\w+.(\\w+)`
138+
- The grade value (already has `parseGrade` method at line 271)
139+
140+
**Changes to `TestOutputDetailsCreator` inner class:**
141+
142+
- Add `String projectName` field
143+
- Add `Double grade` field
144+
- In the `accept(String line)` method, add logic to:
145+
- Extract project name using a regex pattern
146+
- Capture the grade value (already extracts it, just need to store it)
147+
- Update `createTestOutputDetails()` to include the new fields
148+
149+
### 6. Update the `analyzeSubmission` method
150+
151+
Modify the `analyzeSubmission` method (around line 38) to determine if a grade needs to be recorded.
152+
153+
**New logic after determining `needsToBeGraded`:**
154+
155+
```java
156+
boolean gradeNeedsToBeRecorded = false;
157+
if(!needsToBeGraded &&gradeBookProvider !=null){
158+
gradeNeedsToBeRecorded =
159+
160+
checkIfGradeNeedsRecording(submission, testOutput);
161+
}
162+
```
163+
164+
### 7. Create a new method `checkIfGradeNeedsRecording`
165+
166+
Add a new private method to check if a grade needs to be recorded.
167+
168+
**Method signature:**
169+
170+
```java
171+
private boolean checkIfGradeNeedsRecording(SubmissionDetails submission, TestOutputDetails testOutput)
172+
```
173+
174+
**Logic:**
175+
176+
- Return false if testOutput doesn't have a project name or grade
177+
- Get the GradeBook from the provider (return false if not available)
178+
- Get the student from the gradebook using `submission.studentId()`
179+
- If student not found, return true (unrecorded)
180+
- Get the grade for the assignment using `student.getGrade(testOutput.projectName())`
181+
- If grade is null, return true (unrecorded)
182+
- If grade.getScore() != testOutput.grade(), return true (unrecorded)
183+
- Otherwise return false
184+
185+
### 8. Update the `main` method
186+
187+
Modify the `main` method to:
188+
189+
- Parse the new command-line parameter for the gradebook XML file
190+
- Create a `GradeBookProvider` implementation that loads the gradebook
191+
- Pass the provider to the `FindUngradedSubmissions` constructor
192+
193+
**Changes:**
194+
195+
- Update usage message (around line 89) to show the new syntax
196+
- Parse the first non-option argument as the gradebook XML file path
197+
- Remaining arguments are submission files/directories
198+
- Create a `GradeBookProviderFromXmlFile` implementation
199+
- Track `gradeNeedsToBeRecorded` submissions in the analysis loop (around line 111)
200+
- Add a third call to `printOutAnalyses` for unrecorded grades
201+
202+
### 9. Create `GradeBookProviderFromXmlFile` implementation
203+
204+
Add a new static inner class that implements `GradeBookProvider`.
205+
206+
**Implementation:**
207+
208+
```java
209+
private static class GradeBookProviderFromXmlFile implements GradeBookProvider {
210+
private final String xmlFilePath;
211+
private GradeBook gradeBook;
212+
213+
GradeBookProviderFromXmlFile(String xmlFilePath) {
214+
this.xmlFilePath = xmlFilePath;
215+
}
216+
217+
@Override
218+
public Optional<GradeBook> getGradeBook() {
219+
if (gradeBook == null && xmlFilePath != null) {
220+
try {
221+
XmlGradeBookParser parser = new XmlGradeBookParser(xmlFilePath);
222+
gradeBook = parser.parse();
223+
} catch (Exception e) {
224+
System.err.println("Error loading gradebook: " + e.getMessage());
225+
return Optional.empty();
226+
}
227+
}
228+
return Optional.ofNullable(gradeBook);
229+
}
230+
}
231+
```
232+
233+
### 10. Update test file imports and add new tests
234+
235+
In `FindUngradedSubmissionsTest.java`, add test cases for the new functionality:
236+
237+
**Test cases to add:**
238+
239+
- `submissionWithUngradedTestOutputDoesNotNeedToBeRecorded()` - verify that if submission isn't graded, it's not
240+
considered unrecorded
241+
- `submissionWithGradeNotInGradeBookNeedsToBeRecorded()` - verify missing grade in gradebook is detected
242+
- `submissionWithDifferentGradeInGradeBookNeedsToBeRecorded()` - verify mismatched grades are detected
243+
- `submissionWithMatchingGradeInGradeBookDoesNotNeedToBeRecorded()` - verify matching grades are not flagged
244+
- `parseProjectNameFromTestOutputLine()` - verify project name extraction works
245+
- `testOutputDetailsIncludesProjectNameAndGrade()` - verify TestOutputDetails captures all needed info
246+
247+
### 11. Integration Testing
248+
249+
Create or update integration tests to verify:
250+
251+
- Command-line parsing with the new gradebook parameter
252+
- End-to-end flow with actual XML files
253+
- Output formatting shows the three categories correctly
254+
255+
### Summary of Key Classes to Modify
256+
257+
1. `FindUngradedSubmissions.java` - main implementation
258+
2. `FindUngradedSubmissionsTest.java` - unit tests
259+
3. New imports needed:
260+
- `edu.pdx.cs.joy.grader.gradebook.GradeBook`
261+
- `edu.pdx.cs.joy.grader.gradebook.Grade`
262+
- `edu.pdx.cs.joy.grader.gradebook.Student`
263+
- `edu.pdx.cs.joy.grader.gradebook.XmlGradeBookParser`
264+
- `java.util.Optional`

0 commit comments

Comments
 (0)