From 20c383e308fad71677b47400ae3103eac61b48cf Mon Sep 17 00:00:00 2001 From: David Whitlock Date: Sat, 5 Feb 2022 21:30:23 -0800 Subject: [PATCH 1/7] Begin to address #380 by adding code that parses a CSV exported from Canvas. --- .../grader/canvas/CanvasGradesCSVParser.java | 150 ++++++++++++++++++ .../canvas/CanvasGradesCSVParserTest.java | 45 ++++++ .../edu/pdx/cs410J/grader/canvas/canvas.csv | 5 + 3 files changed, 200 insertions(+) create mode 100644 grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java create mode 100644 grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java create mode 100644 grader/src/test/resources/edu/pdx/cs410J/grader/canvas/canvas.csv diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java new file mode 100644 index 000000000..9986a45f0 --- /dev/null +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java @@ -0,0 +1,150 @@ +package edu.pdx.cs410J.grader.canvas; + +import com.google.common.annotations.VisibleForTesting; +import com.opencsv.CSVReader; +import com.opencsv.exceptions.CsvValidationException; + +import java.io.IOException; +import java.io.Reader; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CanvasGradesCSVParser { + private static final Pattern assignmentNamePattern = Pattern.compile("(.+) \\((\\d+)\\)"); + private static final String[] ignoredColumnNames = new String[] { + "ID", + "SIS User ID", + "Section", + "Getting Ready Current Points", + "Getting Ready Final Points", + "Getting Ready Current Score", + "Getting Ready Unposted Current Score", + "Getting Ready Final Score", + "Getting Ready Unposted Final Score", + "Assignments Current Points", + "Assignments Final Points", + "Assignments Current Score", + "Assignments Unposted Current Score", + "Assignments Final Score", + "Assignments Unposted Final Score", + "Imported Assignments Current Points", + "Imported Assignments Final Points", + "Imported Assignments Current Score", + "Imported Assignments Unposted Current Score", + "Imported Assignments Final Score", + "Imported Assignments Unposted Final Score", + "Current Points", + "Final Points", + "Current Score", + "Unposted Current Score", + "Final Score", + "Unposted Final Score" + }; + + private int studentNameColumn; + private int studentIdColumn; + private final SortedMap columnToAssignment = new TreeMap<>(); + + public CanvasGradesCSVParser(Reader reader) throws IOException { + CSVReader csv = new CSVReader(reader); + try { + extractColumnNamesFromFirstLineOfCsv(csv.readNext()); + extractPossiblePointsFromSecondLineOfCsv(csv.readNext()); + + } catch (CsvValidationException ex) { + throw new IOException("While parsing CSV", ex); + } + + } + + private void extractPossiblePointsFromSecondLineOfCsv(String[] secondLine) { + this.columnToAssignment.forEach((column, assignment) -> { + String possiblePointsText = secondLine[column]; + try { + assignment.setPossiblePoints(Double.parseDouble(possiblePointsText)); + + } catch (NumberFormatException ex) { + throw new IllegalStateException("Can't parse points \"" + possiblePointsText + "\" for " + assignment.getName()); + } + }); + + } + + private void extractColumnNamesFromFirstLineOfCsv(String[] firstLine) { + for (int i = 0; i < firstLine.length; i++) { + String cell = firstLine[i]; + switch (cell) { + case "Student": + this.studentNameColumn = i; + break; + case "SIS Login ID": + this.studentIdColumn = i; + break; + default: + if (!isColumnIgnored(cell)) { + addAssignment(cell, i); + } + } + } + + } + + @VisibleForTesting + static Assignment createAssignment(String assignmentText) { + Matcher matcher = assignmentNamePattern.matcher(assignmentText); + if (matcher.matches()) { + String name = matcher.group(1); + String idText = matcher.group(2); + return new Assignment(name, Integer.parseInt(idText)); + + } else { + throw new IllegalStateException("Can't create Assignment from \"" + assignmentText + "\""); + } + } + + private void addAssignment(String assignmentText, int column) { + this.columnToAssignment.put(column, createAssignment(assignmentText)); + + } + + private boolean isColumnIgnored(String columnName) { + for (String ignored : ignoredColumnNames) { + if (ignored.equals(columnName)) { + return true; + } + } + return false; + } + + public List getAssignments() { + return new ArrayList<>(this.columnToAssignment.values()); + } + + public static class Assignment { + private final String name; + private final int id; + private double pointsPossible; + + public Assignment(String name, int id) { + this.name = name; + this.id = id; + } + + public String getName() { + return name; + } + + public int getId() { + return id; + } + + public double getPointsPossible() { + return pointsPossible; + } + + void setPossiblePoints(double possiblePoints) { + this.pointsPossible = possiblePoints; + } + } +} diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java new file mode 100644 index 000000000..d9b3405e7 --- /dev/null +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java @@ -0,0 +1,45 @@ +package edu.pdx.cs410J.grader.canvas; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.Objects; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class CanvasGradesCSVParserTest { + + @Test + void canCreateAssignmentFromText() { + String assignmentText = "Name (123)"; + CanvasGradesCSVParser.Assignment assignment = CanvasGradesCSVParser.createAssignment(assignmentText); + assertThat(assignment.getName(), equalTo("Name")); + assertThat(assignment.getId(), equalTo(123)); + } + + @Test + void canCreateAssignmentFromMultiWordText() { + String assignmentText = "Two Names (123)"; + CanvasGradesCSVParser.Assignment assignment = CanvasGradesCSVParser.createAssignment(assignmentText); + assertThat(assignment.getName(), equalTo("Two Names")); + assertThat(assignment.getId(), equalTo(123)); + } + + @Test + void canReadAssignmentsFromCSV() throws IOException { + CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); + List assignments = parser.getAssignments(); + assertThat(assignments.get(0).getName(), equalTo("End of Term Survey")); + assertThat(assignments.get(0).getId(), equalTo(95625)); + assertThat(assignments.get(0).getPointsPossible(), equalTo(3.00)); + assertThat(assignments.get(1).getName(), equalTo("Midterm Survey")); + assertThat(assignments.get(1).getId(), equalTo(95626)); + assertThat(assignments.get(1).getPointsPossible(), equalTo(3.00)); + assertThat(assignments.get(2).getName(), equalTo("Project 1 POA")); + assertThat(assignments.get(2).getId(), equalTo(187669)); + assertThat(assignments.get(2).getPointsPossible(), equalTo(1.00)); + } +} diff --git a/grader/src/test/resources/edu/pdx/cs410J/grader/canvas/canvas.csv b/grader/src/test/resources/edu/pdx/cs410J/grader/canvas/canvas.csv new file mode 100644 index 000000000..7c775c608 --- /dev/null +++ b/grader/src/test/resources/edu/pdx/cs410J/grader/canvas/canvas.csv @@ -0,0 +1,5 @@ +Student,ID,SIS User ID,SIS Login ID,Section,End of Term Survey (95625),Midterm Survey (95626),Project 1 POA (187669),Project 2 POA (187671),Project 3 POA (187672),Project 4 POA (187675),Project 5 POA (187676),Project 6 POA (187677),Project 1 (187685),Java Koans (187710),Project 2 (187730),Project 3 (187752),Project 4 (187770),Project 5 (187774),Project 6 (187784),Quiz 1: Programming Background (95628),Quiz 2: Java Language and OOP (95629),Quiz 3: Language API (95630),Quiz 4: Java I/O and Collections (95631),Quiz 5: Web and REST (95632),Quiz 6: Android (95634),Reflections on Pair Programming (95636),Reflections on Mob Programming (95637),Getting Ready Current Points,Getting Ready Final Points,Getting Ready Current Score,Getting Ready Unposted Current Score,Getting Ready Final Score,Getting Ready Unposted Final Score,Assignments Current Points,Assignments Final Points,Assignments Current Score,Assignments Unposted Current Score,Assignments Final Score,Assignments Unposted Final Score,Imported Assignments Current Points,Imported Assignments Final Points,Imported Assignments Current Score,Imported Assignments Unposted Current Score,Imported Assignments Final Score,Imported Assignments Unposted Final Score,Current Points,Final Points,Current Score,Unposted Current Score,Final Score,Unposted Final Score + Points Possible,,,,,3.00,3.00,1.00,1.00,1.00,1.00,1.00,1.00,5.00,6.00,6.00,7.00,7.00,9.00,12.00,3.00,3.00,3.00,3.00,3.00,3.00,3.50,3.50,(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only),(read only) +"Last1, First1",88888,1c3520f5-f919-4b3d-9fd9-888888888888,student1,CS-410P-069: TOP: Adv Prog In Java,,,,,,,,,,,,,,,,3.00,,2.75,,,,0.00,,0.00,0.00,,,,,0.00,0.00,,,0.00,0.00,5.75,5.75,95.83,95.83,23.00,23.00,5.75,5.75,95.83,95.83,6.46,6.46 +"Last2, First2",99999,c23ae015-c492-48f6-a683-999999999999,student2,CS-510-090: TOP: Adv Prog In Java,,,,,,,,,,,,,,,,2.85,3.00,3.00,0.00,,,,,0.00,0.00,,,,,0.00,0.00,,,0.00,0.00,8.85,8.85,98.33,98.33,35.40,35.40,8.85,8.85,98.33,98.33,9.94,9.94 +"Student, Test",77777,,6d2ccd9f49e3636350c5460509f17069a1118e7b,CS-410P-069: TOP: Adv Prog In Java and CS-510-090: TOP: Adv Prog In Java,,,,,,,,,,,,,,,,,,,,,,,,0.00,0.00,,,,,0.00,0.00,,,0.00,0.00,0.00,0.00,,,0.00,0.00,0.00,0.00,,,0.00,0.00 From b5357bb511f48b533983f96a2ef1d34db5110e46 Mon Sep 17 00:00:00 2001 From: David Whitlock Date: Sun, 6 Feb 2022 18:55:30 -0800 Subject: [PATCH 2/7] Parse grades from a Canvas CSV. --- .../grader/canvas/CanvasGradesCSVParser.java | 86 +++++++++++++++++++ .../canvas/CanvasGradesCSVParserTest.java | 42 ++++++++- 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java index 9986a45f0..b8b129452 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java @@ -45,12 +45,17 @@ public class CanvasGradesCSVParser { private int studentNameColumn; private int studentIdColumn; private final SortedMap columnToAssignment = new TreeMap<>(); + private final List students = new ArrayList<>(); public CanvasGradesCSVParser(Reader reader) throws IOException { CSVReader csv = new CSVReader(reader); try { extractColumnNamesFromFirstLineOfCsv(csv.readNext()); extractPossiblePointsFromSecondLineOfCsv(csv.readNext()); + String[] studentLine; + while ((studentLine = csv.readNext()) != null) { + addStudentAndGradesFromLineOfCsv(studentLine); + } } catch (CsvValidationException ex) { throw new IOException("While parsing CSV", ex); @@ -58,6 +63,50 @@ public CanvasGradesCSVParser(Reader reader) throws IOException { } + private void addStudentAndGradesFromLineOfCsv(String[] studentLine) { + Student student = createStudentFrom(studentLine); + + if (!student.getFirstName().equals("Test")) { + this.students.add(student); + } + + addGradesFromLineOfCsv(student, studentLine); + } + + private void addGradesFromLineOfCsv(Student student, String[] studentLine) { + this.columnToAssignment.forEach((column, assignment) -> { + String score = studentLine[column]; + if (!isEmptyString(score)) { + student.setScore(assignment, parseScore(score)); + } + }); + + } + + private boolean isEmptyString(String score) { + return "".equals(score); + } + + private double parseScore(String score) { + return Double.parseDouble(score); + } + + private Student createStudentFrom(String[] studentLine) { + String studentName = studentLine[studentNameColumn]; + Pattern studentNamePattern = Pattern.compile("(.*), (.*)"); + Matcher matcher = studentNamePattern.matcher(studentName); + if (matcher.matches()) { + String firstName = matcher.group(2); + String lastName = matcher.group(1); + String studentId = studentLine[studentIdColumn]; + + return new Student(firstName, lastName, studentId); + + } else { + throw new IllegalStateException("Can't parse student name \"" + studentName + "\""); + } + } + private void extractPossiblePointsFromSecondLineOfCsv(String[] secondLine) { this.columnToAssignment.forEach((column, assignment) -> { String possiblePointsText = secondLine[column]; @@ -121,6 +170,10 @@ public List getAssignments() { return new ArrayList<>(this.columnToAssignment.values()); } + public List getStudents() { + return this.students; + } + public static class Assignment { private final String name; private final int id; @@ -147,4 +200,37 @@ void setPossiblePoints(double possiblePoints) { this.pointsPossible = possiblePoints; } } + + public static class Student { + private final String firstName; + private final String lastName; + private final String id; + private final Map scores = new HashMap<>(); + + private Student(String firstName, String lastName, String studentId) { + this.firstName = firstName; + this.lastName = lastName; + this.id = studentId; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getId() { + return id; + } + + public Double getScore(Assignment assignment) { + return this.scores.get(assignment); + } + + public void setScore(Assignment assignment, double score) { + this.scores.put(assignment, score); + } + } } diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java index d9b3405e7..868a9ce15 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java @@ -6,9 +6,10 @@ import java.io.InputStreamReader; import java.util.List; import java.util.Objects; +import java.util.Optional; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.*; public class CanvasGradesCSVParserTest { @@ -42,4 +43,43 @@ void canReadAssignmentsFromCSV() throws IOException { assertThat(assignments.get(2).getId(), equalTo(187669)); assertThat(assignments.get(2).getPointsPossible(), equalTo(1.00)); } + + @Test + void canReadStudentsFromCSV() throws IOException { + CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); + List students = parser.getStudents(); + assertThat(students.get(0).getFirstName(), equalTo("First1")); + assertThat(students.get(0).getLastName(), equalTo("Last1")); + assertThat(students.get(0).getId(), equalTo("student1")); + assertThat(students.get(1).getFirstName(), equalTo("First2")); + assertThat(students.get(1).getLastName(), equalTo("Last2")); + assertThat(students.get(1).getId(), equalTo("student2")); + } + + @Test + void testStudentIsNotReadFromCSV() throws IOException { + CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); + List students = parser.getStudents(); + assertThat(students, hasSize(2)); + } + + @Test + void canReadGradesFromCSV() throws IOException { + CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); + List assignments = parser.getAssignments(); + List students = parser.getStudents(); + + CanvasGradesCSVParser.Assignment quiz1 = getAssignment(assignments, "Quiz 1: Programming Background"); + assertThat(students.get(0).getScore(quiz1), equalTo(3.00)); + assertThat(students.get(1).getScore(quiz1), equalTo(2.85)); + + CanvasGradesCSVParser.Assignment quiz2 = getAssignment(assignments, "Quiz 2: Java Language and OOP"); + assertThat(students.get(0).getScore(quiz2), nullValue()); + assertThat(students.get(1).getScore(quiz2), equalTo(3.00)); + } + + private CanvasGradesCSVParser.Assignment getAssignment(List assignments, String assignmentName) { + Optional optional = assignments.stream().filter(assignment -> assignment.getName().equals(assignmentName)).findFirst(); + return optional.orElseThrow(() -> new IllegalStateException("Can't find assignment \"" + assignmentName + "\"")); + } } From 602e76905cef35544dedb16197efed58d1d999d8 Mon Sep 17 00:00:00 2001 From: David Whitlock Date: Sun, 6 Feb 2022 20:45:22 -0800 Subject: [PATCH 3/7] Use the GradesFromCanvas class in the CanvasGradesCSVParser. Rename a bunch of D2L classes to be Canvas. --- .../pdx/cs410J/grader/D2LGradesCSVParser.java | 169 ------------- .../edu/pdx/cs410J/grader/GraderTools.java | 2 +- ...adesFromD2L.java => GradesFromCanvas.java} | 54 ++--- .../grader/GradesFromCanvasImporter.java | 117 +++++++++ .../cs410J/grader/GradesFromD2LImporter.java | 116 --------- .../grader/canvas/CanvasGradesCSVParser.java | 61 ++--- .../cs410J/grader/D2LGradesCSVParserTest.java | 223 ------------------ .../grader/D2LStudentPropertyMatcher.java | 12 +- ...java => GradesFromCanvasImporterTest.java} | 20 +- ...D2LTest.java => GradesFromCanvasTest.java} | 58 ++--- .../canvas/CanvasGradesCSVParserTest.java | 19 +- 11 files changed, 217 insertions(+), 634 deletions(-) delete mode 100644 grader/src/main/java/edu/pdx/cs410J/grader/D2LGradesCSVParser.java rename grader/src/main/java/edu/pdx/cs410J/grader/{GradesFromD2L.java => GradesFromCanvas.java} (63%) create mode 100644 grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvasImporter.java delete mode 100644 grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2LImporter.java delete mode 100644 grader/src/test/java/edu/pdx/cs410J/grader/D2LGradesCSVParserTest.java rename grader/src/test/java/edu/pdx/cs410J/grader/{GradesFromD2LImporterTest.java => GradesFromCanvasImporterTest.java} (68%) rename grader/src/test/java/edu/pdx/cs410J/grader/{GradesFromD2LTest.java => GradesFromCanvasTest.java} (58%) diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/D2LGradesCSVParser.java b/grader/src/main/java/edu/pdx/cs410J/grader/D2LGradesCSVParser.java deleted file mode 100644 index 5a03e99dc..000000000 --- a/grader/src/main/java/edu/pdx/cs410J/grader/D2LGradesCSVParser.java +++ /dev/null @@ -1,169 +0,0 @@ -package edu.pdx.cs410J.grader; - -import com.opencsv.CSVReader; -import com.opencsv.exceptions.CsvValidationException; - -import java.io.IOException; -import java.io.Reader; -import java.util.HashMap; -import java.util.Map; - -public class D2LGradesCSVParser { - - private static final String[] ignoredColumnNames = new String[] { - "Calculated Final Grade Numerator", - "Calculated Final Grade Denominator", - "Adjusted Final Grade Numerator", - "Adjusted Final Grade Denominator", - "End-of-Line Indicator" - }; - private final GradesFromD2L grades; - private int usernameColumn = -1; - private int lastNameColumn = -1; - private int firstNameColumn = -1; - private int emailColumn; - private final Map quizColumnsAndNames = new HashMap<>(); - - public D2LGradesCSVParser(Reader reader) throws IOException { - CSVReader csv = new CSVReader( reader ); - try { - String[] firstLine = csv.readNext(); - extractColumnNamesFromFirstLineOfCsv(firstLine); - - grades = new GradesFromD2L(); - - String[] studentLine; - while ((studentLine = csv.readNext()) != null) { - addStudentAndGradesFromLineOfCsv(studentLine); - } - - } catch (CsvValidationException ex) { - throw new IOException("While parsing CSV", ex); - } - } - - private void addStudentAndGradesFromLineOfCsv(String[] line) { - GradesFromD2L.D2LStudentBuilder builder = GradesFromD2L.newStudent(); - builder.setFirstName(getFirstNameFromLine(line)); - builder.setLastName(getLastNameFromLine(line)); - builder.setD2LId(getUsernameFromLine(line)); - - GradesFromD2L.D2LStudent student = builder.create(); - - if (student.getD2lId().startsWith("guest")) { - return; - } - - this.grades.addStudent(student); - - addGradesFromLineOfCsv(student, line); - } - - private void addGradesFromLineOfCsv(GradesFromD2L.D2LStudent student, String[] line) { - this.quizColumnsAndNames.forEach((index, quizName) -> { - String score = line[index]; - if (!isEmptyString(score)) { - student.setScore(quizName, parseScore(score)); - } - }); - - } - - private boolean isEmptyString(String score) { - return "".equals(score); - } - - private double parseScore(String score) { - return Double.parseDouble(score); - } - - private String getUsernameFromLine(String[] line) { - String username = line[getUsernameColumn()]; - if (username.charAt(0) == '#') { - username = username.substring(1); - } - return username; - } - - private String getLastNameFromLine(String[] line) { - return line[getLastNameColumn()]; - } - - private String getFirstNameFromLine(String[] line) { - return line[getFirstNameColumn()]; - } - - private void extractColumnNamesFromFirstLineOfCsv(String[] firstLine) { - for (int i = 0; i < firstLine.length; i++) { - String cell = firstLine[i]; - switch (cell) { - case "Username": - usernameColumn = i; - break; - case "Last Name": - lastNameColumn = i; - break; - case "First Name": - firstNameColumn = i; - break; - case "Email": - emailColumn = i; - break; - default: - if (!isColumnIgnored(cell)) { - addQuiz(extractQuizName(cell), i); - } - } - } - - } - - private void addQuiz(String quizName, int column) { - quizColumnsAndNames.put(column, quizName); - } - - private String extractQuizName(String cell) { - // Programming Background Quiz Points Grade - int index = cell.indexOf(" Points Grade"); - if (index > -1) { - return cell.substring(0, index); - - } else { - return cell; - } - } - - public boolean isColumnIgnored(String columnName) { - for (String ignored : D2LGradesCSVParser.ignoredColumnNames) { - if (ignored.equals(columnName)) { - return true; - } - } - - return false; - } - - public int getUsernameColumn() { - return usernameColumn; - } - - public int getLastNameColumn() { - return lastNameColumn; - } - - public int getFirstNameColumn() { - return firstNameColumn; - } - - public int getEmailColumn() { - return emailColumn; - } - - public Iterable getQuizNames() { - return quizColumnsAndNames.values(); - } - - public GradesFromD2L getGrades() { - return grades; - } -} diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java b/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java index 70b7f86df..79937c50f 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java @@ -52,7 +52,7 @@ private static Class getToolClass(String tool) { return FetchAndProcessGraderEmail.class; case "importFromD2L" : - return GradesFromD2LImporter.class; + return GradesFromCanvasImporter.class; case "importFromProjectReports" : return ProjectGradesImporter.class; diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2L.java b/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvas.java similarity index 63% rename from grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2L.java rename to grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvas.java index 765e35d61..e7cfd10b9 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2L.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvas.java @@ -2,28 +2,28 @@ import java.util.*; -public class GradesFromD2L { - private List students = new ArrayList<>(); +public class GradesFromCanvas { + private List students = new ArrayList<>(); - public static D2LStudentBuilder newStudent() { - return new D2LStudentBuilder(); + public static CanvasStudentBuilder newStudent() { + return new CanvasStudentBuilder(); } - public void addStudent(D2LStudent student) { + public void addStudent(CanvasStudent student) { this.students.add(student); } - public Optional findStudentInGradebookForD2LStudent(D2LStudent d2lStudent, GradeBook book) { + public Optional findStudentInGradebookForCanvasStudent(CanvasStudent d2lStudent, GradeBook book) { return book.studentsStream().filter((Student student) -> { if (haveSameD2LId(d2lStudent, student)) { return true; } else if (studentIdIsSameAsD2LId(d2lStudent, student)) { - student.setD2LId(d2lStudent.getD2lId()); + student.setD2LId(d2lStudent.getLoginId()); return true; } else if (haveSameFirstAndLastNameIgnoringCase(d2lStudent, student)) { - student.setD2LId(d2lStudent.getD2lId()); + student.setD2LId(d2lStudent.getLoginId()); return true; } else { @@ -32,24 +32,24 @@ public Optional findStudentInGradebookForD2LStudent(D2LStudent d2lStude }).findAny(); } - private boolean studentIdIsSameAsD2LId(D2LStudent d2lStudent, Student student) { - return d2lStudent.getD2lId().equals(student.getId()); + private boolean studentIdIsSameAsD2LId(CanvasStudent d2lStudent, Student student) { + return d2lStudent.getLoginId().equals(student.getId()); } - private boolean haveSameFirstAndLastNameIgnoringCase(D2LStudent d2lStudent, Student student) { + private boolean haveSameFirstAndLastNameIgnoringCase(CanvasStudent d2lStudent, Student student) { return d2lStudent.getFirstName().equalsIgnoreCase(student.getFirstName()) && d2lStudent.getLastName().equalsIgnoreCase(student.getLastName()); } - private boolean haveSameD2LId(D2LStudent d2lStudent, Student student) { - return d2lStudent.getD2lId().equals(student.getD2LId()); + private boolean haveSameD2LId(CanvasStudent d2lStudent, Student student) { + return d2lStudent.getLoginId().equals(student.getD2LId()); } - public List getStudents() { + public List getStudents() { return this.students; } - public Optional findAssignmentInGradebookForD2lQuiz(String quizName, GradeBook gradebook) { + public Optional findAssignmentInGradebookForCanvasQuiz(String quizName, GradeBook gradebook) { Assignment assignment = gradebook.getAssignment(quizName); if (assignment != null) { return Optional.ofNullable(assignment); @@ -72,19 +72,19 @@ private Optional findAssignmentInGradebookLike(String quizName, Grad return Optional.empty(); } - static class D2LStudent { + public static class CanvasStudent { private final String firstName; private final String lastName; private final String d2lId; private Map scores = new HashMap<>(); - public D2LStudent(String firstName, String lastName, String d2lId) { + public CanvasStudent(String firstName, String lastName, String d2lId) { this.firstName = firstName; this.lastName = lastName; this.d2lId = d2lId; } - public String getD2lId() { + public String getLoginId() { return d2lId; } @@ -104,41 +104,41 @@ public Double getScore(String quizName) { return this.scores.get(quizName); } - public Iterable getQuizNames() { + public Iterable getAssignmentNames() { return this.scores.keySet(); } @Override public String toString() { - return "D2L student \"" + getFirstName() + " " + getLastName() + "\" with id " + getD2lId(); + return "D2L student \"" + getFirstName() + " " + getLastName() + "\" with id " + getLoginId(); } } - static class D2LStudentBuilder { + public static class CanvasStudentBuilder { private String firstName; private String lastName; private String d2lId; - private D2LStudentBuilder() { + private CanvasStudentBuilder() { } - public D2LStudentBuilder setFirstName(String firstName) { + public CanvasStudentBuilder setFirstName(String firstName) { this.firstName = firstName; return this; } - public D2LStudentBuilder setLastName(String lastName) { + public CanvasStudentBuilder setLastName(String lastName) { this.lastName = lastName; return this; } - public D2LStudentBuilder setD2LId(String d2lId) { + public CanvasStudentBuilder setLoginId(String d2lId) { this.d2lId = d2lId; return this; } - public D2LStudent create() { + public CanvasStudent create() { if (firstName == null) { throw new IllegalStateException("Missing first name"); } @@ -151,7 +151,7 @@ public D2LStudent create() { throw new IllegalStateException("Missing d2l Id"); } - return new D2LStudent(firstName, lastName, d2lId); + return new CanvasStudent(firstName, lastName, d2lId); } } } diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvasImporter.java b/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvasImporter.java new file mode 100644 index 000000000..5258b2053 --- /dev/null +++ b/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvasImporter.java @@ -0,0 +1,117 @@ +package edu.pdx.cs410J.grader; + +import edu.pdx.cs410J.ParserException; +import edu.pdx.cs410J.grader.canvas.CanvasGradesCSVParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.List; +import java.util.Optional; + +public class GradesFromCanvasImporter { + + private static final Logger logger = LoggerFactory.getLogger("edu.pdx.cs410J.grader"); + + public static void importGradesFromCanvas(GradesFromCanvas canvasGrades, GradeBook gradebook) { + List students = canvasGrades.getStudents(); + logger.debug("Importing grades for " + students.size() + " students"); + + for (GradesFromCanvas.CanvasStudent canvasStudent : students) { + Optional student = canvasGrades.findStudentInGradebookForCanvasStudent(canvasStudent, gradebook); + if (student.isEmpty()) { + String message = "Could not find student " + canvasStudent + " in gradebook"; + System.err.println(message); + + } else { + for (String assignmentName : canvasStudent.getAssignmentNames()) { + Optional optional = canvasGrades.findAssignmentInGradebookForCanvasQuiz(assignmentName, gradebook); + Assignment assignment = optional.orElseThrow(() -> new IllegalStateException("No assignment named \"" + assignmentName + "\" in gradebook")); + + Double score = canvasStudent.getScore(assignmentName); + logger.debug("Recording grade of " + score + " for " + student.get() + " for " + assignment.getName()); + student.get().setGrade(assignment.getName(), new Grade(assignment, score)); + } + } + } + } + + public static void main(String[] args) throws IOException, ParserException { + String canvasCsvFileName = null; + String gradeBookFileName = null; + + for (String arg : args) { + if (canvasCsvFileName == null) { + canvasCsvFileName = arg; + + } else if (gradeBookFileName == null) { + gradeBookFileName = arg; + + } else { + usage("Extraneous command line argument: " + arg); + } + } + + if (canvasCsvFileName == null) { + usage("Missing Canvas CSV file name"); + } + + if (gradeBookFileName == null) { + usage("Missing grade book file name"); + } + + GradesFromCanvas canvasGrades = parseCanvasCsvFile(canvasCsvFileName); + GradeBook gradebook = parseGradeBook(gradeBookFileName); + importGradesFromCanvas(canvasGrades, gradebook); + + saveGradeBookIfModified(gradebook, gradeBookFileName); + } + + private static void saveGradeBookIfModified(GradeBook gradeBook, String gradeBookFileName) { + if (gradeBook.isDirty()) { + File file = new File(gradeBookFileName); + try { + XmlDumper dumper = new XmlDumper(file); + dumper.dump(gradeBook); + + } catch (IOException e) { + usage("Can't write grade book in \"" + gradeBookFileName + "\""); + } + } + } + + private static GradeBook parseGradeBook(String gradeBookFileName) throws IOException, ParserException { + File gradeBookFile = new File(gradeBookFileName); + if (!gradeBookFile.exists()) { + usage("Grade Book file \"" + gradeBookFile + "\" does not exist"); + } + + XmlGradeBookParser parser = new XmlGradeBookParser(gradeBookFile); + return parser.parse(); + } + + private static GradesFromCanvas parseCanvasCsvFile(String canvasCsvFileName) throws IOException { + File canvasCsvFile = new File(canvasCsvFileName); + if (!canvasCsvFile.exists()) { + usage("Canvas CSV file \"" + canvasCsvFile + "\" does not exist"); + } + + CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new FileReader(canvasCsvFile)); + return parser.getGrades(); + } + + private static void usage(String message) { + PrintStream err = System.err; + + err.println("+++ " + message); + err.println(); + err.println("usage: java GradesFromCanvasImporter canvasGradesCsvFileName gradeBookFileName"); + err.println(" canvasGradesCsvFileName Name of the CSV grades file exported from Canvas"); + err.println(" gradeBookFileName Gradebook file"); + err.println(); + err.println("Imports grades from Canvas into a gradebook"); + err.println(); + + System.exit(1); + } +} diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2LImporter.java b/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2LImporter.java deleted file mode 100644 index 73af8e221..000000000 --- a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2LImporter.java +++ /dev/null @@ -1,116 +0,0 @@ -package edu.pdx.cs410J.grader; - -import edu.pdx.cs410J.ParserException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.util.List; -import java.util.Optional; - -public class GradesFromD2LImporter { - - private static final Logger logger = LoggerFactory.getLogger("edu.pdx.cs410J.grader"); - - public static void importGradesFromD2L(GradesFromD2L d2lGrades, GradeBook gradebook) { - List students = d2lGrades.getStudents(); - logger.debug("Importing grades for " + students.size() + " students"); - - for (GradesFromD2L.D2LStudent d2LStudent : students) { - Optional student = d2lGrades.findStudentInGradebookForD2LStudent(d2LStudent, gradebook); - if (!student.isPresent()) { - String message = "Could not find student " + d2LStudent + " in gradebook"; - System.err.println(message); - - } else { - for (String quizName : d2LStudent.getQuizNames()) { - Optional optional = d2lGrades.findAssignmentInGradebookForD2lQuiz(quizName, gradebook); - Assignment quiz = optional.orElseThrow(() -> new IllegalStateException("No quiz named \"" + quizName + "\" in gradebook")); - - Double score = d2LStudent.getScore(quizName); - logger.debug("Recording grade of " + score + " for " + student.get() + " for " + quiz.getName()); - student.get().setGrade(quiz.getName(), new Grade(quiz, score)); - } - } - } - } - - public static void main(String[] args) throws IOException, ParserException { - String d2lCsvFileName = null; - String gradeBookFileName = null; - - for (String arg : args) { - if (d2lCsvFileName == null) { - d2lCsvFileName = arg; - - } else if (gradeBookFileName == null) { - gradeBookFileName = arg; - - } else { - usage("Extraneous command line argument: " + arg); - } - } - - if (d2lCsvFileName == null) { - usage("Missing D2L CSV file name"); - } - - if (gradeBookFileName == null) { - usage("Missing grade book file name"); - } - - GradesFromD2L d2lGrades = parseD2lCsvFile(d2lCsvFileName); - GradeBook gradebook = parseGradeBook(gradeBookFileName); - importGradesFromD2L(d2lGrades, gradebook); - - saveGradeBookIfModified(gradebook, gradeBookFileName); - } - - private static void saveGradeBookIfModified(GradeBook gradeBook, String gradeBookFileName) { - if (gradeBook.isDirty()) { - File file = new File(gradeBookFileName); - try { - XmlDumper dumper = new XmlDumper(file); - dumper.dump(gradeBook); - - } catch (IOException e) { - usage("Can't write grade book in \"" + gradeBookFileName + "\""); - } - } - } - - private static GradeBook parseGradeBook(String gradeBookFileName) throws IOException, ParserException { - File gradeBookFile = new File(gradeBookFileName); - if (!gradeBookFile.exists()) { - usage("Grade Book file \"" + gradeBookFile + "\" does not exist"); - } - - XmlGradeBookParser parser = new XmlGradeBookParser(gradeBookFile); - return parser.parse(); - } - - private static GradesFromD2L parseD2lCsvFile(String d2lCsvFileName) throws IOException { - File d2lCsvFile = new File(d2lCsvFileName); - if (!d2lCsvFile.exists()) { - usage("D2L CSV file \"" + d2lCsvFile + "\" does not exist"); - } - - D2LGradesCSVParser parser = new D2LGradesCSVParser(new FileReader(d2lCsvFile)); - return parser.getGrades(); - } - - private static void usage(String message) { - PrintStream err = System.err; - - err.println("+++ " + message); - err.println(); - err.println("usage: java GradesFromD2LImporter d2lGradesCsvFileName gradeBookFileName"); - err.println(" d2lGradesCsvFileName Name of the CSV grades file exported from D2L"); - err.println(" gradeBookFileName Gradebook file"); - err.println(); - err.println("Imports grades from D2L into a gradebook"); - err.println(); - - System.exit(1); - } -} diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java index b8b129452..6a7dc1f0b 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java @@ -3,6 +3,7 @@ import com.google.common.annotations.VisibleForTesting; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; +import edu.pdx.cs410J.grader.GradesFromCanvas; import java.io.IOException; import java.io.Reader; @@ -45,13 +46,16 @@ public class CanvasGradesCSVParser { private int studentNameColumn; private int studentIdColumn; private final SortedMap columnToAssignment = new TreeMap<>(); - private final List students = new ArrayList<>(); + private final GradesFromCanvas grades; public CanvasGradesCSVParser(Reader reader) throws IOException { CSVReader csv = new CSVReader(reader); try { extractColumnNamesFromFirstLineOfCsv(csv.readNext()); extractPossiblePointsFromSecondLineOfCsv(csv.readNext()); + + grades = new GradesFromCanvas(); + String[] studentLine; while ((studentLine = csv.readNext()) != null) { addStudentAndGradesFromLineOfCsv(studentLine); @@ -64,20 +68,20 @@ public CanvasGradesCSVParser(Reader reader) throws IOException { } private void addStudentAndGradesFromLineOfCsv(String[] studentLine) { - Student student = createStudentFrom(studentLine); + GradesFromCanvas.CanvasStudent student = createStudentFrom(studentLine); if (!student.getFirstName().equals("Test")) { - this.students.add(student); + this.grades.addStudent(student); } addGradesFromLineOfCsv(student, studentLine); } - private void addGradesFromLineOfCsv(Student student, String[] studentLine) { + private void addGradesFromLineOfCsv(GradesFromCanvas.CanvasStudent student, String[] studentLine) { this.columnToAssignment.forEach((column, assignment) -> { String score = studentLine[column]; if (!isEmptyString(score)) { - student.setScore(assignment, parseScore(score)); + student.setScore(assignment.getName(), parseScore(score)); } }); @@ -91,16 +95,17 @@ private double parseScore(String score) { return Double.parseDouble(score); } - private Student createStudentFrom(String[] studentLine) { + private GradesFromCanvas.CanvasStudent createStudentFrom(String[] studentLine) { String studentName = studentLine[studentNameColumn]; Pattern studentNamePattern = Pattern.compile("(.*), (.*)"); Matcher matcher = studentNamePattern.matcher(studentName); if (matcher.matches()) { - String firstName = matcher.group(2); - String lastName = matcher.group(1); - String studentId = studentLine[studentIdColumn]; + GradesFromCanvas.CanvasStudentBuilder builder = GradesFromCanvas.newStudent(); + builder.setFirstName(matcher.group(2)); + builder.setLastName(matcher.group(1)); + builder.setLoginId(studentLine[studentIdColumn]); - return new Student(firstName, lastName, studentId); + return builder.create(); } else { throw new IllegalStateException("Can't parse student name \"" + studentName + "\""); @@ -170,8 +175,8 @@ public List getAssignments() { return new ArrayList<>(this.columnToAssignment.values()); } - public List getStudents() { - return this.students; + public GradesFromCanvas getGrades() { + return this.grades; } public static class Assignment { @@ -201,36 +206,4 @@ void setPossiblePoints(double possiblePoints) { } } - public static class Student { - private final String firstName; - private final String lastName; - private final String id; - private final Map scores = new HashMap<>(); - - private Student(String firstName, String lastName, String studentId) { - this.firstName = firstName; - this.lastName = lastName; - this.id = studentId; - } - - public String getFirstName() { - return firstName; - } - - public String getLastName() { - return lastName; - } - - public String getId() { - return id; - } - - public Double getScore(Assignment assignment) { - return this.scores.get(assignment); - } - - public void setScore(Assignment assignment, double score) { - this.scores.put(assignment, score); - } - } } diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/D2LGradesCSVParserTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/D2LGradesCSVParserTest.java deleted file mode 100644 index 6f8f058ef..000000000 --- a/grader/src/test/java/edu/pdx/cs410J/grader/D2LGradesCSVParserTest.java +++ /dev/null @@ -1,223 +0,0 @@ -package edu.pdx.cs410J.grader; - -import com.google.common.collect.Lists; -import org.hamcrest.Matcher; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -public class D2LGradesCSVParserTest { - - @Test - public void ignoreExpectedColumns() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - - assertThat(parser.isColumnIgnored("Calculated Final Grade Numerator"), is(true)); - assertThat(parser.isColumnIgnored("Calculated Final Grade Denominator"), is(true)); - assertThat(parser.isColumnIgnored("Adjusted Final Grade Numerator"), is(true)); - assertThat(parser.isColumnIgnored("Adjusted Final Grade Denominator"), is(true)); - assertThat(parser.isColumnIgnored("End-of-Line Indicator"), is(true)); - } - - @Test - public void doNotIgnoreNonIgnoredColumns() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - - assertThat(parser.isColumnIgnored("Username"), is(false)); - assertThat(parser.isColumnIgnored("First Name"), is(false)); - assertThat(parser.isColumnIgnored("Last Name"), is(false)); - assertThat(parser.isColumnIgnored("Email"), is(false)); - } - - @Test - public void canParseCsvWithNoStudents() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createEmptyCsv().getReader()); - GradesFromD2L grades = parser.getGrades(); - assertThat(grades.getStudents(), hasSize(0)); - } - - @Test - public void usernameIsFirstColumn() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - assertThat(parser.getUsernameColumn(), equalTo(0)); - } - - @Test - public void lastNameIsSecondColumn() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - assertThat(parser.getLastNameColumn(), equalTo(1)); - } - - @Test - public void firstNameIsThirdColumn() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - assertThat(parser.getFirstNameColumn(), equalTo(2)); - } - - @Test - public void emailIsFourthColumn() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - assertThat(parser.getEmailColumn(), equalTo(3)); - } - - @Test - public void expectedQuizNames() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - assertThat(parser.getQuizNames(), hasItem("Programming Background Quiz")); - assertThat(parser.getQuizNames(), hasItem("Java Language and OOP Quiz")); - assertThat(parser.getQuizNames(), hasItem("Language API Quiz")); - } - - @Test - public void quizNameDoNotContainIgnoredColumns() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - - assertThat(parser.getQuizNames(), not(hasItem("Calculated Final Grade Numerator"))); - assertThat(parser.getQuizNames(), not(hasItem("Calculated Final Grade Denominator"))); - assertThat(parser.getQuizNames(), not(hasItem("Adjusted Final Grade Numerator"))); - assertThat(parser.getQuizNames(), not(hasItem("Adjusted Final Grade Denominator"))); - assertThat(parser.getQuizNames(), not(hasItem("Adjusted Final Grade Denominator"))); - assertThat(parser.getQuizNames(), not(hasItem("End-of-Line Indicator"))); - } - - - @Test - public void studentInformationIsNotConsideredAQuiz() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - - assertThat(parser.getQuizNames(), not(hasItem("Username"))); - assertThat(parser.getQuizNames(), not(hasItem("First Name"))); - assertThat(parser.getQuizNames(), not(hasItem("Last Name"))); - assertThat(parser.getQuizNames(), not(hasItem("Email"))); - } - - @Test - public void gradesPopulatedWithStudents() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - - GradesFromD2L grades = parser.getGrades(); - assertThat(grades.getStudents(), hasStudentWithFirstName("Student")); - assertThat(grades.getStudents(), hasStudentWithLastName("One")); - assertThat(grades.getStudents(), hasStudentWithD2LId("student1")); - } - - @Test - public void gradesPopulatedWithStudents2() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - - GradesFromD2L grades = parser.getGrades(); - assertThat(grades.getStudents(), hasStudentWithFirstName("Student")); - assertThat(grades.getStudents(), hasStudentWithLastName("Two")); - assertThat(grades.getStudents(), hasStudentWithD2LId("student2")); - } - - @Test - public void gradesPopulatedWithScores() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - - GradesFromD2L grades = parser.getGrades(); - Optional student = getStudentWithId("student1", grades); - assertThat("Could not find student", student.isPresent(), is(true)); - assertThat(student.get().getScore("Programming Background Quiz"), equalTo(4.0)); - } - - @Test - public void gradesPopulatedWithScores2() throws IOException { - D2LGradesCSVParser parser = new D2LGradesCSVParser(createCsv().getReader()); - - GradesFromD2L grades = parser.getGrades(); - Optional student = getStudentWithId("student2", grades); - assertThat("Could not find student", student.isPresent(), is(true)); - assertThat(student.get().getScore("Programming Background Quiz"), equalTo(3.0)); - } - - private java.util.Optional getStudentWithId(String d2lId, GradesFromD2L grades) { - return grades.getStudents().stream().filter(s -> s.getD2lId().equals(d2lId)).findAny(); - } - - private Matcher> hasStudentWithD2LId(String d2lId) { - return new D2LStudentPropertyMatcher(GradesFromD2L.D2LStudent::getD2lId, "d2l id", d2lId); - } - - private Matcher> hasStudentWithFirstName(String firstName) { - return new D2LStudentPropertyMatcher(GradesFromD2L.D2LStudent::getFirstName, "first name", firstName); - } - - private Matcher> hasStudentWithLastName(String lastName) { - return new D2LStudentPropertyMatcher(GradesFromD2L.D2LStudent::getLastName, "last name", lastName); - } - - private CSV createCsv() { - CSV csv = createEmptyCsv(); - csv.addLine("student1","One","Student","student1@email.com","4","","","","","","4","24","","",""); - csv.addLine("student2","Two","Student","student2@email.com","3","","","","","","4","24","","",""); - return csv; - } - - private CSV createEmptyCsv() { - CSV csv = new CSV(); - csv.addLine("Username", "Last Name", "First Name", "Email", "Programming Background Quiz Points Grade ", "Java Language and OOP Quiz Points Grade ", "Language API Quiz Points Grade ", "Java IO and Collections Quiz Points Grade ", "Web and REST Quiz Points Grade ", "Google Web Toolkit Quiz Points Grade ", "Calculated Final Grade Numerator", "Calculated Final Grade Denominator", "Adjusted Final Grade Numerator", "Adjusted Final Grade Denominator", "End-of-Line Indicator"); - return csv; - } - - private class CSV { - List> lines = Lists.newArrayList(); - public void addLine(String... cells) { - lines.add(Arrays.asList(cells)); - } - - public Reader getReader() { - StringWriter writer = new StringWriter(); - for (List line : lines) { - for (String cell : line) { - writer.write(cell); - writer.write(","); - } - writer.write("\n"); - } - - return new StringReader(writer.toString()); - } - } - - @Test - public void guestStudentIsIgnored() throws IOException { - CSV csv = createEmptyCsv(); - csv.addLine("guest1234","Two","Student","student2@email.com","3","","","","","","4","24","","",""); - - D2LGradesCSVParser parser = new D2LGradesCSVParser(csv.getReader()); - - GradesFromD2L grades = parser.getGrades(); - Optional student = getStudentWithId("guest1234", grades); - assertThat(student.isPresent(), equalTo(false)); - } - - @Test - public void poundSignAtStartOfUsernameIsIgnored() throws IOException { - CSV csv = createEmptyCsv(); - csv.addLine("#student1","One","Student","student1@email.com","4","","","","","","4","24","","",""); - csv.addLine("student2","Two","Student","student2@email.com","3","","","","","","4","24","","",""); - - D2LGradesCSVParser parser = new D2LGradesCSVParser(csv.getReader()); - - GradesFromD2L grades = parser.getGrades(); - assertThat(grades.getStudents(), hasStudentWithD2LId("student1")); - assertThat(grades.getStudents(), hasStudentWithD2LId("student2")); - } - - // No students - // No assignments - // No grades - // Some missing grades -} diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/D2LStudentPropertyMatcher.java b/grader/src/test/java/edu/pdx/cs410J/grader/D2LStudentPropertyMatcher.java index 971970f0c..7d894f5b6 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/D2LStudentPropertyMatcher.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/D2LStudentPropertyMatcher.java @@ -10,21 +10,21 @@ import java.util.function.Function; import java.util.stream.Stream; -public class D2LStudentPropertyMatcher extends TypeSafeMatcher> { +public class D2LStudentPropertyMatcher extends TypeSafeMatcher> { private final String expectedValue; - private final Function accessor; + private final Function accessor; private final String propertyName; - public D2LStudentPropertyMatcher(Function accessor, String propertyName, String expectedValue) { + public D2LStudentPropertyMatcher(Function accessor, String propertyName, String expectedValue) { this.expectedValue = expectedValue; this.accessor = accessor; this.propertyName = propertyName; } @Override - protected boolean matchesSafely(List students) { - for (GradesFromD2L.D2LStudent student : students) { + protected boolean matchesSafely(List students) { + for (GradesFromCanvas.CanvasStudent student : students) { if (this.accessor.apply(student).equals(expectedValue)) { return true; } @@ -39,7 +39,7 @@ public void describeTo(Description description) { } @Override - protected void describeMismatchSafely(List students, Description mismatchDescription) { + protected void describeMismatchSafely(List students, Description mismatchDescription) { mismatchDescription.appendText("the students' " + propertyName + "s were: "); Stream actualValues = students.stream().map(accessor); mismatchDescription.appendValueList("", ",", "", toIterable(actualValues)); diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LImporterTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasImporterTest.java similarity index 68% rename from grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LImporterTest.java rename to grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasImporterTest.java index 86e1f69c6..54d15a31b 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LImporterTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasImporterTest.java @@ -7,7 +7,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -public class GradesFromD2LImporterTest { +public class GradesFromCanvasImporterTest { @Test public void errorWhenD2LQuizDoesNotExistInGradebook() { @@ -17,13 +17,13 @@ public void errorWhenD2LQuizDoesNotExistInGradebook() { Student student = new Student(studentId); gradebook.addStudent(student); - GradesFromD2L d2l = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2LStudent = GradesFromD2L.newStudent().setFirstName("first").setLastName("last").setD2LId(studentId).create(); + GradesFromCanvas d2l = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2LStudent = GradesFromCanvas.newStudent().setFirstName("first").setLastName("last").setLoginId(studentId).create(); d2LStudent.setScore("quiz", 3.4); d2l.addStudent(d2LStudent); assertThrows(IllegalStateException.class, () -> - GradesFromD2LImporter.importGradesFromD2L(d2l, gradebook) + GradesFromCanvasImporter.importGradesFromCanvas(d2l, gradebook) ); } @@ -38,12 +38,12 @@ public void importScoreFromD2LWhenStudentIdMatchesD2LId() { Student student = new Student(studentId); gradebook.addStudent(student); - GradesFromD2L d2l = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2LStudent = GradesFromD2L.newStudent().setFirstName("first").setLastName("last").setD2LId(studentId).create(); + GradesFromCanvas d2l = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2LStudent = GradesFromCanvas.newStudent().setFirstName("first").setLastName("last").setLoginId(studentId).create(); d2LStudent.setScore(quizName, score); d2l.addStudent(d2LStudent); - GradesFromD2LImporter.importGradesFromD2L(d2l, gradebook); + GradesFromCanvasImporter.importGradesFromCanvas(d2l, gradebook); assertThat(student.getGradeNames(), hasItem(quizName)); assertThat(student.getGrade(quizName).getScore(), equalTo(score)); @@ -64,12 +64,12 @@ public void importScoreFromD2LWhenStudentNameMatchesD2LName() { student.setLastName(lastName); gradebook.addStudent(student); - GradesFromD2L d2l = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2LStudent = GradesFromD2L.newStudent().setFirstName(firstName).setLastName(lastName).setD2LId("d2LstudentId").create(); + GradesFromCanvas d2l = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2LStudent = GradesFromCanvas.newStudent().setFirstName(firstName).setLastName(lastName).setLoginId("d2LstudentId").create(); d2LStudent.setScore(quizName, score); d2l.addStudent(d2LStudent); - GradesFromD2LImporter.importGradesFromD2L(d2l, gradebook); + GradesFromCanvasImporter.importGradesFromCanvas(d2l, gradebook); assertThat(student.getGradeNames(), hasItem(quizName)); assertThat(student.getGrade(quizName).getScore(), equalTo(score)); diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasTest.java similarity index 58% rename from grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LTest.java rename to grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasTest.java index 3b4560774..1029f5e87 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasTest.java @@ -5,12 +5,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class GradesFromD2LTest { +public class GradesFromCanvasTest { @Test public void noStudentInGradebookThatMatchesD2LStudent() { - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName("firstName").setLastName("lastName").setD2LId("d2lId").create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName("firstName").setLastName("lastName").setLoginId("d2lId").create(); grades.addStudent(d2lStudent); GradeBook book = new GradeBook("test"); @@ -18,13 +18,13 @@ public void noStudentInGradebookThatMatchesD2LStudent() { student.setD2LId("notD2LId"); book.addStudent(student); - assertThat(grades.findStudentInGradebookForD2LStudent(d2lStudent, book).isPresent(), is(false)); + assertThat(grades.findStudentInGradebookForCanvasStudent(d2lStudent, book).isPresent(), is(false)); } @Test public void matchStudentByD2LId() { - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName("firstName").setLastName("lastName").setD2LId("d2lId").create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName("firstName").setLastName("lastName").setLoginId("d2lId").create(); grades.addStudent(d2lStudent); GradeBook book = new GradeBook("test"); @@ -32,22 +32,22 @@ public void matchStudentByD2LId() { student.setD2LId("d2lId"); book.addStudent(student); - assertThat(grades.findStudentInGradebookForD2LStudent(d2lStudent, book).get(), equalTo(student)); + assertThat(grades.findStudentInGradebookForCanvasStudent(d2lStudent, book).get(), equalTo(student)); } @Test public void matchStudentByIdAndD2LId() { String studentId = "studentId"; - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName("firstName").setLastName("lastName").setD2LId(studentId).create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName("firstName").setLastName("lastName").setLoginId(studentId).create(); grades.addStudent(d2lStudent); GradeBook book = new GradeBook("test"); Student student = new Student(studentId); book.addStudent(student); - assertThat(grades.findStudentInGradebookForD2LStudent(d2lStudent, book).get(), equalTo(student)); + assertThat(grades.findStudentInGradebookForCanvasStudent(d2lStudent, book).get(), equalTo(student)); assertThat(student.isDirty(), is(true)); assertThat(student.getD2LId(), equalTo(studentId)); } @@ -58,8 +58,8 @@ public void matchStudentByFirstAndLastName() { String firstName = "firstName"; String lastName = "lastName"; - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName(firstName).setLastName(lastName).setD2LId(d2lId).create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName(firstName).setLastName(lastName).setLoginId(d2lId).create(); grades.addStudent(d2lStudent); GradeBook book = new GradeBook("test"); @@ -72,7 +72,7 @@ public void matchStudentByFirstAndLastName() { assertThat(student.getD2LId(), is(nullValue())); assertThat(student.isDirty(), is(false)); - assertThat(grades.findStudentInGradebookForD2LStudent(d2lStudent, book).get(), equalTo(student)); + assertThat(grades.findStudentInGradebookForCanvasStudent(d2lStudent, book).get(), equalTo(student)); assertThat(student.getD2LId(), equalTo(d2lId)); assertThat(student.isDirty(), is(true)); } @@ -83,8 +83,8 @@ public void matchStudentByFirstAndLastNameIgnoringCase() { String firstName = "firstName"; String lastName = "lastName"; - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName(firstName).setLastName(lastName).setD2LId(d2lId).create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName(firstName).setLastName(lastName).setLoginId(d2lId).create(); grades.addStudent(d2lStudent); GradeBook book = new GradeBook("test"); @@ -97,7 +97,7 @@ public void matchStudentByFirstAndLastNameIgnoringCase() { assertThat(student.getD2LId(), is(nullValue())); assertThat(student.isDirty(), is(false)); - assertThat(grades.findStudentInGradebookForD2LStudent(d2lStudent, book).get(), equalTo(student)); + assertThat(grades.findStudentInGradebookForCanvasStudent(d2lStudent, book).get(), equalTo(student)); assertThat(student.getD2LId(), equalTo(d2lId)); assertThat(student.isDirty(), is(true)); } @@ -108,8 +108,8 @@ public void matchStudentDifferentFirstAndLastNameButSameD2LId() { String firstName = "firstName"; String lastName = "lastName"; - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName(firstName).setLastName(lastName).setD2LId(d2lId).create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName(firstName).setLastName(lastName).setLoginId(d2lId).create(); grades.addStudent(d2lStudent); GradeBook book = new GradeBook("test"); @@ -123,14 +123,14 @@ public void matchStudentDifferentFirstAndLastNameButSameD2LId() { assertThat(student.getD2LId(), equalTo(d2lId)); assertThat(student.isDirty(), is(false)); - assertThat(grades.findStudentInGradebookForD2LStudent(d2lStudent, book).get(), equalTo(student)); + assertThat(grades.findStudentInGradebookForCanvasStudent(d2lStudent, book).get(), equalTo(student)); assertThat(student.getD2LId(), equalTo(d2lId)); assertThat(student.isDirty(), is(false)); } @Test public void gradeIsSet() { - GradesFromD2L.D2LStudent student = GradesFromD2L.newStudent().setFirstName("first").setLastName("last").setD2LId("id").create(); + GradesFromCanvas.CanvasStudent student = GradesFromCanvas.newStudent().setFirstName("first").setLastName("last").setLoginId("id").create(); String quizName = "quizName"; double score = 3.4; student.setScore(quizName, score); @@ -142,8 +142,8 @@ public void gradeIsSet() { public void matchQuizWithSameName() { String quizName = "quizName"; - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName("first").setLastName("last").setD2LId("id").create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName("first").setLastName("last").setLoginId("id").create(); grades.addStudent(d2lStudent); d2lStudent.setScore(quizName, 3.6); @@ -151,15 +151,15 @@ public void matchQuizWithSameName() { Assignment assignment = new Assignment(quizName, 4.0); book.addAssignment(assignment); - assertThat(grades.findAssignmentInGradebookForD2lQuiz(quizName, book).get(), equalTo(assignment)); + assertThat(grades.findAssignmentInGradebookForCanvasQuiz(quizName, book).get(), equalTo(assignment)); } @Test public void matchQuizWithPrefixThatIsTheSameAsQuizNameInD2L() { String quizName = "quizName"; - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName("first").setLastName("last").setD2LId("id").create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName("first").setLastName("last").setLoginId("id").create(); grades.addStudent(d2lStudent); d2lStudent.setScore(quizName + " Quiz", 3.6); @@ -167,7 +167,7 @@ public void matchQuizWithPrefixThatIsTheSameAsQuizNameInD2L() { Assignment assignment = new Assignment(quizName, 4.0); book.addAssignment(assignment); - assertThat(grades.findAssignmentInGradebookForD2lQuiz(quizName, book).orElseGet(() -> null), equalTo(assignment)); + assertThat(grades.findAssignmentInGradebookForCanvasQuiz(quizName, book).orElseGet(() -> null), equalTo(assignment)); } @@ -175,8 +175,8 @@ public void matchQuizWithPrefixThatIsTheSameAsQuizNameInD2L() { public void matchQuizWithDescriptionPrefixThatIsTheSameAsQuizNameInD2L() { String quizName = "quizName"; - GradesFromD2L grades = new GradesFromD2L(); - GradesFromD2L.D2LStudent d2lStudent = GradesFromD2L.newStudent().setFirstName("first").setLastName("last").setD2LId("id").create(); + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName("first").setLastName("last").setLoginId("id").create(); grades.addStudent(d2lStudent); d2lStudent.setScore(quizName + " Quiz", 3.6); @@ -185,7 +185,7 @@ public void matchQuizWithDescriptionPrefixThatIsTheSameAsQuizNameInD2L() { assignment.setDescription(quizName); book.addAssignment(assignment); - assertThat(grades.findAssignmentInGradebookForD2lQuiz(quizName, book).orElseGet(() -> null), equalTo(assignment)); + assertThat(grades.findAssignmentInGradebookForCanvasQuiz(quizName, book).orElseGet(() -> null), equalTo(assignment)); } diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java index 868a9ce15..1cf745f97 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java @@ -1,5 +1,6 @@ package edu.pdx.cs410J.grader.canvas; +import edu.pdx.cs410J.grader.GradesFromCanvas; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -47,19 +48,19 @@ void canReadAssignmentsFromCSV() throws IOException { @Test void canReadStudentsFromCSV() throws IOException { CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); - List students = parser.getStudents(); + List students = parser.getGrades().getStudents(); assertThat(students.get(0).getFirstName(), equalTo("First1")); assertThat(students.get(0).getLastName(), equalTo("Last1")); - assertThat(students.get(0).getId(), equalTo("student1")); + assertThat(students.get(0).getLoginId(), equalTo("student1")); assertThat(students.get(1).getFirstName(), equalTo("First2")); assertThat(students.get(1).getLastName(), equalTo("Last2")); - assertThat(students.get(1).getId(), equalTo("student2")); + assertThat(students.get(1).getLoginId(), equalTo("student2")); } @Test void testStudentIsNotReadFromCSV() throws IOException { CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); - List students = parser.getStudents(); + List students = parser.getGrades().getStudents(); assertThat(students, hasSize(2)); } @@ -67,15 +68,15 @@ void testStudentIsNotReadFromCSV() throws IOException { void canReadGradesFromCSV() throws IOException { CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); List assignments = parser.getAssignments(); - List students = parser.getStudents(); + List students = parser.getGrades().getStudents(); CanvasGradesCSVParser.Assignment quiz1 = getAssignment(assignments, "Quiz 1: Programming Background"); - assertThat(students.get(0).getScore(quiz1), equalTo(3.00)); - assertThat(students.get(1).getScore(quiz1), equalTo(2.85)); + assertThat(students.get(0).getScore(quiz1.getName()), equalTo(3.00)); + assertThat(students.get(1).getScore(quiz1.getName()), equalTo(2.85)); CanvasGradesCSVParser.Assignment quiz2 = getAssignment(assignments, "Quiz 2: Java Language and OOP"); - assertThat(students.get(0).getScore(quiz2), nullValue()); - assertThat(students.get(1).getScore(quiz2), equalTo(3.00)); + assertThat(students.get(0).getScore(quiz2.getName()), nullValue()); + assertThat(students.get(1).getScore(quiz2.getName()), equalTo(3.00)); } private CanvasGradesCSVParser.Assignment getAssignment(List assignments, String assignmentName) { From 8359b0d6370be63270161b7588f7ae74fc6e3b38 Mon Sep 17 00:00:00 2001 From: David Whitlock Date: Sun, 6 Feb 2022 20:52:08 -0800 Subject: [PATCH 4/7] Move more classes into the edu.pdx.cs410J.grader.canvas package. --- .../edu/pdx/cs410J/grader/GraderTools.java | 1 + .../grader/canvas/CanvasGradesCSVParser.java | 1 - .../grader/{ => canvas}/GradesFromCanvas.java | 6 +- .../GradesFromCanvasImporter.java | 4 +- .../grader/D2LStudentPropertyMatcher.java | 66 ------------------- .../canvas/CanvasGradesCSVParserTest.java | 1 - .../GradesFromCanvasImporterTest.java | 3 +- .../{ => canvas}/GradesFromCanvasTest.java | 5 +- 8 files changed, 14 insertions(+), 73 deletions(-) rename grader/src/main/java/edu/pdx/cs410J/grader/{ => canvas}/GradesFromCanvas.java (96%) rename grader/src/main/java/edu/pdx/cs410J/grader/{ => canvas}/GradesFromCanvasImporter.java (97%) delete mode 100644 grader/src/test/java/edu/pdx/cs410J/grader/D2LStudentPropertyMatcher.java rename grader/src/test/java/edu/pdx/cs410J/grader/{ => canvas}/GradesFromCanvasImporterTest.java (97%) rename grader/src/test/java/edu/pdx/cs410J/grader/{ => canvas}/GradesFromCanvasTest.java (97%) diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java b/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java index 79937c50f..a75eb307a 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java @@ -3,6 +3,7 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import com.google.common.annotations.VisibleForTesting; +import edu.pdx.cs410J.grader.canvas.GradesFromCanvasImporter; import edu.pdx.cs410J.grader.poa.ui.PlanOfAttackGrader; import org.slf4j.LoggerFactory; diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java index 6a7dc1f0b..442e71e3b 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java @@ -3,7 +3,6 @@ import com.google.common.annotations.VisibleForTesting; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; -import edu.pdx.cs410J.grader.GradesFromCanvas; import java.io.IOException; import java.io.Reader; diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvas.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java similarity index 96% rename from grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvas.java rename to grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java index e7cfd10b9..d5d247f1f 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvas.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java @@ -1,4 +1,8 @@ -package edu.pdx.cs410J.grader; +package edu.pdx.cs410J.grader.canvas; + +import edu.pdx.cs410J.grader.Assignment; +import edu.pdx.cs410J.grader.GradeBook; +import edu.pdx.cs410J.grader.Student; import java.util.*; diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvasImporter.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporter.java similarity index 97% rename from grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvasImporter.java rename to grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporter.java index 5258b2053..43f82b159 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromCanvasImporter.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporter.java @@ -1,7 +1,7 @@ -package edu.pdx.cs410J.grader; +package edu.pdx.cs410J.grader.canvas; import edu.pdx.cs410J.ParserException; -import edu.pdx.cs410J.grader.canvas.CanvasGradesCSVParser; +import edu.pdx.cs410J.grader.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/D2LStudentPropertyMatcher.java b/grader/src/test/java/edu/pdx/cs410J/grader/D2LStudentPropertyMatcher.java deleted file mode 100644 index 7d894f5b6..000000000 --- a/grader/src/test/java/edu/pdx/cs410J/grader/D2LStudentPropertyMatcher.java +++ /dev/null @@ -1,66 +0,0 @@ -package edu.pdx.cs410J.grader; - -import org.hamcrest.Description; -import org.hamcrest.TypeSafeMatcher; - -import java.util.Iterator; -import java.util.List; -import java.util.Spliterator; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Stream; - -public class D2LStudentPropertyMatcher extends TypeSafeMatcher> { - - private final String expectedValue; - private final Function accessor; - private final String propertyName; - - public D2LStudentPropertyMatcher(Function accessor, String propertyName, String expectedValue) { - this.expectedValue = expectedValue; - this.accessor = accessor; - this.propertyName = propertyName; - } - - @Override - protected boolean matchesSafely(List students) { - for (GradesFromCanvas.CanvasStudent student : students) { - if (this.accessor.apply(student).equals(expectedValue)) { - return true; - } - } - - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("a student with a " + propertyName + " of ").appendValue(expectedValue); - } - - @Override - protected void describeMismatchSafely(List students, Description mismatchDescription) { - mismatchDescription.appendText("the students' " + propertyName + "s were: "); - Stream actualValues = students.stream().map(accessor); - mismatchDescription.appendValueList("", ",", "", toIterable(actualValues)); - } - - private Iterable toIterable(final Stream stream) { - return new Iterable() { - @Override - public Iterator iterator() { - return stream.iterator(); - } - - @Override - public void forEach(Consumer action) { - stream.forEach(action); - } - - @Override - public Spliterator spliterator() { - return stream.spliterator(); - } - }; - } -} diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java index 1cf745f97..b80274a4e 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java @@ -1,6 +1,5 @@ package edu.pdx.cs410J.grader.canvas; -import edu.pdx.cs410J.grader.GradesFromCanvas; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasImporterTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporterTest.java similarity index 97% rename from grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasImporterTest.java rename to grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporterTest.java index 54d15a31b..9f46bf215 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasImporterTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporterTest.java @@ -1,5 +1,6 @@ -package edu.pdx.cs410J.grader; +package edu.pdx.cs410J.grader.canvas; +import edu.pdx.cs410J.grader.*; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java similarity index 97% rename from grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasTest.java rename to grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java index 1029f5e87..9bbe58dd2 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromCanvasTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java @@ -1,5 +1,8 @@ -package edu.pdx.cs410J.grader; +package edu.pdx.cs410J.grader.canvas; +import edu.pdx.cs410J.grader.Assignment; +import edu.pdx.cs410J.grader.GradeBook; +import edu.pdx.cs410J.grader.Student; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; From e120ce83fc7f6dc4a6ec39afd571d1962bf4ff42 Mon Sep 17 00:00:00 2001 From: David Whitlock Date: Sun, 6 Feb 2022 20:57:05 -0800 Subject: [PATCH 5/7] Remove more references to "D2L". --- .../grader/canvas/GradesFromCanvas.java | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java index d5d247f1f..9934beb36 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java @@ -7,7 +7,7 @@ import java.util.*; public class GradesFromCanvas { - private List students = new ArrayList<>(); + private final List students = new ArrayList<>(); public static CanvasStudentBuilder newStudent() { return new CanvasStudentBuilder(); @@ -17,17 +17,17 @@ public void addStudent(CanvasStudent student) { this.students.add(student); } - public Optional findStudentInGradebookForCanvasStudent(CanvasStudent d2lStudent, GradeBook book) { + public Optional findStudentInGradebookForCanvasStudent(CanvasStudent canvasStudent, GradeBook book) { return book.studentsStream().filter((Student student) -> { - if (haveSameD2LId(d2lStudent, student)) { + if (haveSameLoginId(canvasStudent, student)) { return true; - } else if (studentIdIsSameAsD2LId(d2lStudent, student)) { - student.setD2LId(d2lStudent.getLoginId()); + } else if (studentIdIsSameAsLoginId(canvasStudent, student)) { + student.setD2LId(canvasStudent.getLoginId()); return true; - } else if (haveSameFirstAndLastNameIgnoringCase(d2lStudent, student)) { - student.setD2LId(d2lStudent.getLoginId()); + } else if (haveSameFirstAndLastNameIgnoringCase(canvasStudent, student)) { + student.setD2LId(canvasStudent.getLoginId()); return true; } else { @@ -36,7 +36,7 @@ public Optional findStudentInGradebookForCanvasStudent(CanvasStudent d2 }).findAny(); } - private boolean studentIdIsSameAsD2LId(CanvasStudent d2lStudent, Student student) { + private boolean studentIdIsSameAsLoginId(CanvasStudent d2lStudent, Student student) { return d2lStudent.getLoginId().equals(student.getId()); } @@ -45,8 +45,8 @@ private boolean haveSameFirstAndLastNameIgnoringCase(CanvasStudent d2lStudent, S d2lStudent.getLastName().equalsIgnoreCase(student.getLastName()); } - private boolean haveSameD2LId(CanvasStudent d2lStudent, Student student) { - return d2lStudent.getLoginId().equals(student.getD2LId()); + private boolean haveSameLoginId(CanvasStudent canvasStudent, Student student) { + return canvasStudent.getLoginId().equals(student.getD2LId()); } public List getStudents() { @@ -56,7 +56,7 @@ public List getStudents() { public Optional findAssignmentInGradebookForCanvasQuiz(String quizName, GradeBook gradebook) { Assignment assignment = gradebook.getAssignment(quizName); if (assignment != null) { - return Optional.ofNullable(assignment); + return Optional.of(assignment); } return findAssignmentInGradebookLike(quizName, gradebook); @@ -76,20 +76,20 @@ private Optional findAssignmentInGradebookLike(String quizName, Grad return Optional.empty(); } - public static class CanvasStudent { + static class CanvasStudent { private final String firstName; private final String lastName; - private final String d2lId; - private Map scores = new HashMap<>(); + private final String loginId; + private final Map scores = new HashMap<>(); - public CanvasStudent(String firstName, String lastName, String d2lId) { + public CanvasStudent(String firstName, String lastName, String loginId) { this.firstName = firstName; this.lastName = lastName; - this.d2lId = d2lId; + this.loginId = loginId; } public String getLoginId() { - return d2lId; + return loginId; } public String getFirstName() { @@ -114,14 +114,14 @@ public Iterable getAssignmentNames() { @Override public String toString() { - return "D2L student \"" + getFirstName() + " " + getLastName() + "\" with id " + getLoginId(); + return "Canvas student \"" + getFirstName() + " " + getLastName() + "\" with id " + getLoginId(); } } - public static class CanvasStudentBuilder { + static class CanvasStudentBuilder { private String firstName; private String lastName; - private String d2lId; + private String loginId; private CanvasStudentBuilder() { @@ -137,8 +137,8 @@ public CanvasStudentBuilder setLastName(String lastName) { return this; } - public CanvasStudentBuilder setLoginId(String d2lId) { - this.d2lId = d2lId; + public CanvasStudentBuilder setLoginId(String loginId) { + this.loginId = loginId; return this; } @@ -151,11 +151,11 @@ public CanvasStudent create() { throw new IllegalStateException("Missing last name"); } - if (d2lId == null) { - throw new IllegalStateException("Missing d2l Id"); + if (loginId == null) { + throw new IllegalStateException("Missing login Id"); } - return new CanvasStudent(firstName, lastName, d2lId); + return new CanvasStudent(firstName, lastName, loginId); } } } From ae35329146358d9e147afe02aecf0fb3fd30fed0 Mon Sep 17 00:00:00 2001 From: David Whitlock Date: Sun, 6 Feb 2022 21:03:53 -0800 Subject: [PATCH 6/7] Bump version of grader jar to 2022.2.0 because we're making noticeable changes with importing grades from Canvas instead of D2L. --- grader/pom.xml | 2 +- grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/grader/pom.xml b/grader/pom.xml index 135748363..25cc3fee7 100644 --- a/grader/pom.xml +++ b/grader/pom.xml @@ -8,7 +8,7 @@ io.github.davidwhitlock.cs410J grader grader - 2022.1.1 + 2022.2.0 jar http://www.cs.pdx.edu/~whitlock diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java b/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java index a75eb307a..ee063121d 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/GraderTools.java @@ -52,7 +52,7 @@ private static Class getToolClass(String tool) { case "fetch": return FetchAndProcessGraderEmail.class; - case "importFromD2L" : + case "importFromCanvas" : return GradesFromCanvasImporter.class; case "importFromProjectReports" : @@ -97,7 +97,7 @@ private static void usage(String message) { err.println(" gradebook The Grade Book GUI"); err.println(" fetch Fetch student surveys or projects from the Grader's"); err.println(" emails account"); - err.println(" importFromD2L Import grades from a D2L CSV"); + err.println(" importFromCanvas Import grades from a Canvas CSV"); err.println(" importFromProjectReports Import grades from graded project reports"); err.println(" mailFileToStudent Email text files to students"); err.println(" gradePOAs Tool for downloading and grading POAs"); From 34c33c65d90ef5e24da12d1658631587555eeaca Mon Sep 17 00:00:00 2001 From: David Whitlock Date: Sat, 12 Feb 2022 10:45:16 -0800 Subject: [PATCH 7/7] Match a Canvas quiz name to a gradebook quiz name if the quiz's description appears anywhere in the name of the Canvas quiz. --- .../grader/canvas/GradesFromCanvas.java | 12 ++++++------ .../grader/canvas/GradesFromCanvasTest.java | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java index 9934beb36..309406fb3 100644 --- a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java @@ -53,23 +53,23 @@ public List getStudents() { return this.students; } - public Optional findAssignmentInGradebookForCanvasQuiz(String quizName, GradeBook gradebook) { - Assignment assignment = gradebook.getAssignment(quizName); + public Optional findAssignmentInGradebookForCanvasQuiz(String canvasQuizName, GradeBook gradebook) { + Assignment assignment = gradebook.getAssignment(canvasQuizName); if (assignment != null) { return Optional.of(assignment); } - return findAssignmentInGradebookLike(quizName, gradebook); + return findAssignmentInGradebookLike(canvasQuizName, gradebook); } - private Optional findAssignmentInGradebookLike(String quizName, GradeBook gradebook) { + private Optional findAssignmentInGradebookLike(String canvasQuizName, GradeBook gradebook) { for (String assignmentName : gradebook.getAssignmentNames()) { Assignment assignment = gradebook.getAssignment(assignmentName); - if (quizName.startsWith(assignmentName)) { + if (canvasQuizName.startsWith(assignmentName)) { return Optional.ofNullable(assignment); } - if (quizName.startsWith(assignment.getDescription())) { + if (canvasQuizName.contains(assignment.getDescription())) { return Optional.of(assignment); } } diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java index 9bbe58dd2..7f62589a3 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java @@ -192,4 +192,23 @@ public void matchQuizWithDescriptionPrefixThatIsTheSameAsQuizNameInD2L() { } + @Test + void matchQuizWithDescriptionThatIsContainedInQuizNameInCanvas() { + String quizDescription = "Quiz Description"; + + GradesFromCanvas grades = new GradesFromCanvas(); + GradesFromCanvas.CanvasStudent d2lStudent = GradesFromCanvas.newStudent().setFirstName("first").setLastName("last").setLoginId("id").create(); + grades.addStudent(d2lStudent); + String canvasQuizName = "Quiz 1: " + quizDescription + " (12345)"; + d2lStudent.setScore(canvasQuizName, 3.6); + + GradeBook book = new GradeBook("test"); + Assignment assignment = new Assignment("quiz1", 4.0); + assignment.setDescription(quizDescription); + book.addAssignment(assignment); + + assertThat(grades.findAssignmentInGradebookForCanvasQuiz(canvasQuizName, book).orElseGet(() -> null), equalTo(assignment)); + + } + }