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/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..ee063121d 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; @@ -51,8 +52,8 @@ private static Class getToolClass(String tool) { case "fetch": return FetchAndProcessGraderEmail.class; - case "importFromD2L" : - return GradesFromD2LImporter.class; + case "importFromCanvas" : + return GradesFromCanvasImporter.class; case "importFromProjectReports" : return ProjectGradesImporter.class; @@ -96,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"); diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2L.java b/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2L.java deleted file mode 100644 index 765e35d61..000000000 --- a/grader/src/main/java/edu/pdx/cs410J/grader/GradesFromD2L.java +++ /dev/null @@ -1,157 +0,0 @@ -package edu.pdx.cs410J.grader; - -import java.util.*; - -public class GradesFromD2L { - private List students = new ArrayList<>(); - - public static D2LStudentBuilder newStudent() { - return new D2LStudentBuilder(); - } - - public void addStudent(D2LStudent student) { - this.students.add(student); - } - - public Optional findStudentInGradebookForD2LStudent(D2LStudent d2lStudent, GradeBook book) { - return book.studentsStream().filter((Student student) -> { - if (haveSameD2LId(d2lStudent, student)) { - return true; - - } else if (studentIdIsSameAsD2LId(d2lStudent, student)) { - student.setD2LId(d2lStudent.getD2lId()); - return true; - - } else if (haveSameFirstAndLastNameIgnoringCase(d2lStudent, student)) { - student.setD2LId(d2lStudent.getD2lId()); - return true; - - } else { - return false; - } - }).findAny(); - } - - private boolean studentIdIsSameAsD2LId(D2LStudent d2lStudent, Student student) { - return d2lStudent.getD2lId().equals(student.getId()); - } - - private boolean haveSameFirstAndLastNameIgnoringCase(D2LStudent 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()); - } - - public List getStudents() { - return this.students; - } - - public Optional findAssignmentInGradebookForD2lQuiz(String quizName, GradeBook gradebook) { - Assignment assignment = gradebook.getAssignment(quizName); - if (assignment != null) { - return Optional.ofNullable(assignment); - } - - return findAssignmentInGradebookLike(quizName, gradebook); - } - - private Optional findAssignmentInGradebookLike(String quizName, GradeBook gradebook) { - for (String assignmentName : gradebook.getAssignmentNames()) { - Assignment assignment = gradebook.getAssignment(assignmentName); - if (quizName.startsWith(assignmentName)) { - return Optional.ofNullable(assignment); - } - - if (quizName.startsWith(assignment.getDescription())) { - return Optional.of(assignment); - } - } - return Optional.empty(); - } - - static class D2LStudent { - 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) { - this.firstName = firstName; - this.lastName = lastName; - this.d2lId = d2lId; - } - - public String getD2lId() { - return d2lId; - } - - public String getFirstName() { - return firstName; - } - - public String getLastName() { - return lastName; - } - - public void setScore(String quizName, double score) { - this.scores.put(quizName, score); - } - - public Double getScore(String quizName) { - return this.scores.get(quizName); - } - - public Iterable getQuizNames() { - return this.scores.keySet(); - } - - @Override - public String toString() { - return "D2L student \"" + getFirstName() + " " + getLastName() + "\" with id " + getD2lId(); - } - } - - static class D2LStudentBuilder { - private String firstName; - private String lastName; - private String d2lId; - - private D2LStudentBuilder() { - - } - - public D2LStudentBuilder setFirstName(String firstName) { - this.firstName = firstName; - return this; - } - - public D2LStudentBuilder setLastName(String lastName) { - this.lastName = lastName; - return this; - } - - public D2LStudentBuilder setD2LId(String d2lId) { - this.d2lId = d2lId; - return this; - } - - public D2LStudent create() { - if (firstName == null) { - throw new IllegalStateException("Missing first name"); - } - - if (lastName == null) { - throw new IllegalStateException("Missing last name"); - } - - if (d2lId == null) { - throw new IllegalStateException("Missing d2l Id"); - } - - return new D2LStudent(firstName, lastName, d2lId); - } - } -} 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 new file mode 100644 index 000000000..442e71e3b --- /dev/null +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParser.java @@ -0,0 +1,208 @@ +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<>(); + 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); + } + + } catch (CsvValidationException ex) { + throw new IOException("While parsing CSV", ex); + } + + } + + private void addStudentAndGradesFromLineOfCsv(String[] studentLine) { + GradesFromCanvas.CanvasStudent student = createStudentFrom(studentLine); + + if (!student.getFirstName().equals("Test")) { + this.grades.addStudent(student); + } + + addGradesFromLineOfCsv(student, studentLine); + } + + private void addGradesFromLineOfCsv(GradesFromCanvas.CanvasStudent student, String[] studentLine) { + this.columnToAssignment.forEach((column, assignment) -> { + String score = studentLine[column]; + if (!isEmptyString(score)) { + student.setScore(assignment.getName(), parseScore(score)); + } + }); + + } + + private boolean isEmptyString(String score) { + return "".equals(score); + } + + private double parseScore(String score) { + return Double.parseDouble(score); + } + + private GradesFromCanvas.CanvasStudent createStudentFrom(String[] studentLine) { + String studentName = studentLine[studentNameColumn]; + Pattern studentNamePattern = Pattern.compile("(.*), (.*)"); + Matcher matcher = studentNamePattern.matcher(studentName); + if (matcher.matches()) { + GradesFromCanvas.CanvasStudentBuilder builder = GradesFromCanvas.newStudent(); + builder.setFirstName(matcher.group(2)); + builder.setLastName(matcher.group(1)); + builder.setLoginId(studentLine[studentIdColumn]); + + return builder.create(); + + } else { + throw new IllegalStateException("Can't parse student name \"" + studentName + "\""); + } + } + + 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 GradesFromCanvas getGrades() { + return this.grades; + } + + 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/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java new file mode 100644 index 000000000..309406fb3 --- /dev/null +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvas.java @@ -0,0 +1,161 @@ +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.*; + +public class GradesFromCanvas { + private final List students = new ArrayList<>(); + + public static CanvasStudentBuilder newStudent() { + return new CanvasStudentBuilder(); + } + + public void addStudent(CanvasStudent student) { + this.students.add(student); + } + + public Optional findStudentInGradebookForCanvasStudent(CanvasStudent canvasStudent, GradeBook book) { + return book.studentsStream().filter((Student student) -> { + if (haveSameLoginId(canvasStudent, student)) { + return true; + + } else if (studentIdIsSameAsLoginId(canvasStudent, student)) { + student.setD2LId(canvasStudent.getLoginId()); + return true; + + } else if (haveSameFirstAndLastNameIgnoringCase(canvasStudent, student)) { + student.setD2LId(canvasStudent.getLoginId()); + return true; + + } else { + return false; + } + }).findAny(); + } + + private boolean studentIdIsSameAsLoginId(CanvasStudent d2lStudent, Student student) { + return d2lStudent.getLoginId().equals(student.getId()); + } + + private boolean haveSameFirstAndLastNameIgnoringCase(CanvasStudent d2lStudent, Student student) { + return d2lStudent.getFirstName().equalsIgnoreCase(student.getFirstName()) && + d2lStudent.getLastName().equalsIgnoreCase(student.getLastName()); + } + + private boolean haveSameLoginId(CanvasStudent canvasStudent, Student student) { + return canvasStudent.getLoginId().equals(student.getD2LId()); + } + + public List getStudents() { + return this.students; + } + + public Optional findAssignmentInGradebookForCanvasQuiz(String canvasQuizName, GradeBook gradebook) { + Assignment assignment = gradebook.getAssignment(canvasQuizName); + if (assignment != null) { + return Optional.of(assignment); + } + + return findAssignmentInGradebookLike(canvasQuizName, gradebook); + } + + private Optional findAssignmentInGradebookLike(String canvasQuizName, GradeBook gradebook) { + for (String assignmentName : gradebook.getAssignmentNames()) { + Assignment assignment = gradebook.getAssignment(assignmentName); + if (canvasQuizName.startsWith(assignmentName)) { + return Optional.ofNullable(assignment); + } + + if (canvasQuizName.contains(assignment.getDescription())) { + return Optional.of(assignment); + } + } + return Optional.empty(); + } + + static class CanvasStudent { + private final String firstName; + private final String lastName; + private final String loginId; + private final Map scores = new HashMap<>(); + + public CanvasStudent(String firstName, String lastName, String loginId) { + this.firstName = firstName; + this.lastName = lastName; + this.loginId = loginId; + } + + public String getLoginId() { + return loginId; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public void setScore(String quizName, double score) { + this.scores.put(quizName, score); + } + + public Double getScore(String quizName) { + return this.scores.get(quizName); + } + + public Iterable getAssignmentNames() { + return this.scores.keySet(); + } + + @Override + public String toString() { + return "Canvas student \"" + getFirstName() + " " + getLastName() + "\" with id " + getLoginId(); + } + } + + static class CanvasStudentBuilder { + private String firstName; + private String lastName; + private String loginId; + + private CanvasStudentBuilder() { + + } + + public CanvasStudentBuilder setFirstName(String firstName) { + this.firstName = firstName; + return this; + } + + public CanvasStudentBuilder setLastName(String lastName) { + this.lastName = lastName; + return this; + } + + public CanvasStudentBuilder setLoginId(String loginId) { + this.loginId = loginId; + return this; + } + + public CanvasStudent create() { + if (firstName == null) { + throw new IllegalStateException("Missing first name"); + } + + if (lastName == null) { + throw new IllegalStateException("Missing last name"); + } + + if (loginId == null) { + throw new IllegalStateException("Missing login Id"); + } + + return new CanvasStudent(firstName, lastName, loginId); + } + } +} diff --git a/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporter.java b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporter.java new file mode 100644 index 000000000..43f82b159 --- /dev/null +++ b/grader/src/main/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporter.java @@ -0,0 +1,117 @@ +package edu.pdx.cs410J.grader.canvas; + +import edu.pdx.cs410J.ParserException; +import edu.pdx.cs410J.grader.*; +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/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 deleted file mode 100644 index 971970f0c..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 (GradesFromD2L.D2LStudent 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 new file mode 100644 index 000000000..b80274a4e --- /dev/null +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/CanvasGradesCSVParserTest.java @@ -0,0 +1,85 @@ +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 java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +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)); + } + + @Test + void canReadStudentsFromCSV() throws IOException { + CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); + List students = parser.getGrades().getStudents(); + assertThat(students.get(0).getFirstName(), equalTo("First1")); + assertThat(students.get(0).getLastName(), equalTo("Last1")); + 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).getLoginId(), equalTo("student2")); + } + + @Test + void testStudentIsNotReadFromCSV() throws IOException { + CanvasGradesCSVParser parser = new CanvasGradesCSVParser(new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("canvas.csv")))); + List students = parser.getGrades().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.getGrades().getStudents(); + + CanvasGradesCSVParser.Assignment quiz1 = getAssignment(assignments, "Quiz 1: Programming Background"); + 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.getName()), nullValue()); + assertThat(students.get(1).getScore(quiz2.getName()), 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 + "\"")); + } +} diff --git a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LImporterTest.java b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporterTest.java similarity index 66% rename from grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LImporterTest.java rename to grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasImporterTest.java index 86e1f69c6..9f46bf215 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LImporterTest.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; @@ -7,7 +8,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 +18,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 +39,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 +65,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/canvas/GradesFromCanvasTest.java similarity index 51% rename from grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LTest.java rename to grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java index 3b4560774..7f62589a3 100644 --- a/grader/src/test/java/edu/pdx/cs410J/grader/GradesFromD2LTest.java +++ b/grader/src/test/java/edu/pdx/cs410J/grader/canvas/GradesFromCanvasTest.java @@ -1,16 +1,19 @@ -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; 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 +21,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 +35,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 +61,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 +75,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 +86,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 +100,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 +111,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 +126,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 +145,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 +154,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 +170,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 +178,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 +188,26 @@ 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)); + + } + + @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)); } 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