| 1 | package edu.ucsb.cs156.frontiers.controllers; | |
| 2 | ||
| 3 | import com.fasterxml.jackson.core.JsonProcessingException; | |
| 4 | import edu.ucsb.cs156.frontiers.entities.Course; | |
| 5 | import edu.ucsb.cs156.frontiers.entities.Job; | |
| 6 | import edu.ucsb.cs156.frontiers.entities.RosterStudent; | |
| 7 | import edu.ucsb.cs156.frontiers.entities.User; | |
| 8 | import edu.ucsb.cs156.frontiers.enums.InsertStatus; | |
| 9 | import edu.ucsb.cs156.frontiers.enums.OrgStatus; | |
| 10 | import edu.ucsb.cs156.frontiers.enums.RosterStatus; | |
| 11 | import edu.ucsb.cs156.frontiers.errors.EntityNotFoundException; | |
| 12 | import edu.ucsb.cs156.frontiers.errors.NoLinkedOrganizationException; | |
| 13 | import edu.ucsb.cs156.frontiers.jobs.RemoveStudentsJob; | |
| 14 | import edu.ucsb.cs156.frontiers.jobs.UpdateOrgMembershipJob; | |
| 15 | import edu.ucsb.cs156.frontiers.models.LoadResult; | |
| 16 | import edu.ucsb.cs156.frontiers.models.RosterStudentDTO; | |
| 17 | import edu.ucsb.cs156.frontiers.models.UpsertResponse; | |
| 18 | import edu.ucsb.cs156.frontiers.repositories.CourseRepository; | |
| 19 | import edu.ucsb.cs156.frontiers.repositories.RosterStudentRepository; | |
| 20 | import edu.ucsb.cs156.frontiers.services.CanvasService; | |
| 21 | import edu.ucsb.cs156.frontiers.services.CurrentUserService; | |
| 22 | import edu.ucsb.cs156.frontiers.services.OrganizationMemberService; | |
| 23 | import edu.ucsb.cs156.frontiers.services.UpdateUserService; | |
| 24 | import edu.ucsb.cs156.frontiers.services.jobs.JobService; | |
| 25 | import edu.ucsb.cs156.frontiers.utilities.CanonicalFormConverter; | |
| 26 | import io.swagger.v3.oas.annotations.Operation; | |
| 27 | import io.swagger.v3.oas.annotations.Parameter; | |
| 28 | import io.swagger.v3.oas.annotations.tags.Tag; | |
| 29 | import java.security.NoSuchAlgorithmException; | |
| 30 | import java.security.spec.InvalidKeySpecException; | |
| 31 | import java.util.ArrayList; | |
| 32 | import java.util.List; | |
| 33 | import java.util.Optional; | |
| 34 | import lombok.extern.slf4j.Slf4j; | |
| 35 | import org.springframework.beans.factory.annotation.Autowired; | |
| 36 | import org.springframework.http.HttpStatus; | |
| 37 | import org.springframework.http.ResponseEntity; | |
| 38 | import org.springframework.security.access.AccessDeniedException; | |
| 39 | import org.springframework.security.access.prepost.PreAuthorize; | |
| 40 | import org.springframework.transaction.annotation.Transactional; | |
| 41 | import org.springframework.web.bind.annotation.DeleteMapping; | |
| 42 | import org.springframework.web.bind.annotation.GetMapping; | |
| 43 | import org.springframework.web.bind.annotation.PathVariable; | |
| 44 | import org.springframework.web.bind.annotation.PostMapping; | |
| 45 | import org.springframework.web.bind.annotation.PutMapping; | |
| 46 | import org.springframework.web.bind.annotation.RequestMapping; | |
| 47 | import org.springframework.web.bind.annotation.RequestParam; | |
| 48 | import org.springframework.web.bind.annotation.RestController; | |
| 49 | import org.springframework.web.server.ResponseStatusException; | |
| 50 | ||
| 51 | @Tag(name = "RosterStudents") | |
| 52 | @RequestMapping("/api/rosterstudents") | |
| 53 | @RestController | |
| 54 | @Slf4j | |
| 55 | public class RosterStudentsController extends ApiController { | |
| 56 | ||
| 57 | @Autowired private JobService jobService; | |
| 58 | @Autowired private OrganizationMemberService organizationMemberService; | |
| 59 | ||
| 60 | @Autowired private RosterStudentRepository rosterStudentRepository; | |
| 61 | ||
| 62 | @Autowired private CourseRepository courseRepository; | |
| 63 | ||
| 64 | @Autowired private UpdateUserService updateUserService; | |
| 65 | ||
| 66 | @Autowired private CurrentUserService currentUserService; | |
| 67 | @Autowired private CanvasService canvasService; | |
| 68 | ||
| 69 | /** | |
| 70 | * This method creates a new RosterStudent. It is important to keep the code in this method | |
| 71 | * consistent with the code for adding multiple roster students from a CSV | |
| 72 | * | |
| 73 | * @return the created RosterStudent | |
| 74 | */ | |
| 75 | @Operation(summary = "Create a new roster student") | |
| 76 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
| 77 | @PostMapping("/post") | |
| 78 | public ResponseEntity<UpsertResponse> postRosterStudent( | |
| 79 | @Parameter(name = "studentId") @RequestParam String studentId, | |
| 80 | @Parameter(name = "firstName") @RequestParam String firstName, | |
| 81 | @Parameter(name = "lastName") @RequestParam String lastName, | |
| 82 | @Parameter(name = "email") @RequestParam String email, | |
| 83 | @Parameter(name = "courseId") @RequestParam Long courseId, | |
| 84 | @Parameter(name = "section") @RequestParam(required = false) String section) | |
| 85 | throws EntityNotFoundException { | |
| 86 | ||
| 87 | // Get Course or else throw an error | |
| 88 | ||
| 89 | Course course = | |
| 90 | courseRepository | |
| 91 | .findById(courseId) | |
| 92 |
1
1. lambda$postRosterStudent$0 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$postRosterStudent$0 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(Course.class, courseId)); |
| 93 | ||
| 94 | RosterStudent rosterStudent = | |
| 95 | RosterStudent.builder() | |
| 96 | .studentId(studentId) | |
| 97 | .firstName(firstName) | |
| 98 | .lastName(lastName) | |
| 99 | .email(email.strip()) | |
| 100 |
1
1. postRosterStudent : negated conditional → KILLED |
.section(section != null ? section : "") |
| 101 | .build(); | |
| 102 | ||
| 103 | UpsertResponse upsertResponse = upsertStudent(rosterStudent, course, RosterStatus.MANUAL); | |
| 104 |
1
1. postRosterStudent : negated conditional → KILLED |
if (upsertResponse.getInsertStatus() == InsertStatus.REJECTED) { |
| 105 |
1
1. postRosterStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::postRosterStudent → KILLED |
return ResponseEntity.status(HttpStatus.CONFLICT).body(upsertResponse); |
| 106 | } else { | |
| 107 | rosterStudent = rosterStudentRepository.save(upsertResponse.rosterStudent()); | |
| 108 |
1
1. postRosterStudent : removed call to edu/ucsb/cs156/frontiers/services/UpdateUserService::attachUserToRosterStudent → KILLED |
updateUserService.attachUserToRosterStudent(rosterStudent); |
| 109 |
1
1. postRosterStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::postRosterStudent → KILLED |
return ResponseEntity.ok(upsertResponse); |
| 110 | } | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * This method returns a list of roster students for a given course. | |
| 115 | * | |
| 116 | * @return a list of all courses. | |
| 117 | */ | |
| 118 | @Operation(summary = "List all roster students for a course") | |
| 119 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
| 120 | @GetMapping("/course/{courseId}") | |
| 121 | public Iterable<RosterStudentDTO> rosterStudentForCourse( | |
| 122 | @Parameter(name = "courseId") @PathVariable Long courseId) throws EntityNotFoundException { | |
| 123 | courseRepository | |
| 124 | .findById(courseId) | |
| 125 |
1
1. lambda$rosterStudentForCourse$1 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$rosterStudentForCourse$1 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(Course.class, courseId)); |
| 126 | Iterable<RosterStudent> rosterStudents = | |
| 127 | rosterStudentRepository.findByCourseIdOrderByFirstNameAscLastNameAscIgnoreCase(courseId); | |
| 128 | Iterable<RosterStudentDTO> rosterStudentDTOs = | |
| 129 | () -> | |
| 130 |
1
1. lambda$rosterStudentForCourse$2 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$rosterStudentForCourse$2 → KILLED |
java.util.stream.StreamSupport.stream(rosterStudents.spliterator(), false) |
| 131 | .map(RosterStudentDTO::new) | |
| 132 | .iterator(); | |
| 133 |
1
1. rosterStudentForCourse : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::rosterStudentForCourse → KILLED |
return rosterStudentDTOs; |
| 134 | } | |
| 135 | ||
| 136 | public static UpsertResponse upsertStudent( | |
| 137 | RosterStudent student, Course course, RosterStatus rosterStatus) { | |
| 138 | String convertedEmail = CanonicalFormConverter.convertToValidEmail(student.getEmail()).strip(); | |
| 139 | Optional<RosterStudent> existingStudent = | |
| 140 | course.getRosterStudents().stream() | |
| 141 | .filter( | |
| 142 |
2
1. lambda$upsertStudent$3 : replaced boolean return with true for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$upsertStudent$3 → KILLED 2. lambda$upsertStudent$3 : replaced boolean return with false for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$upsertStudent$3 → KILLED |
filteringStudent -> student.getStudentId().equals(filteringStudent.getStudentId())) |
| 143 | .findFirst(); | |
| 144 | Optional<RosterStudent> existingStudentByEmail = | |
| 145 | course.getRosterStudents().stream() | |
| 146 |
2
1. lambda$upsertStudent$4 : replaced boolean return with false for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$upsertStudent$4 → KILLED 2. lambda$upsertStudent$4 : replaced boolean return with true for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$upsertStudent$4 → KILLED |
.filter(filteringStudent -> convertedEmail.equals(filteringStudent.getEmail())) |
| 147 | .findFirst(); | |
| 148 |
2
1. upsertStudent : negated conditional → KILLED 2. upsertStudent : negated conditional → KILLED |
if (existingStudent.isPresent() && existingStudentByEmail.isPresent()) { |
| 149 |
1
1. upsertStudent : negated conditional → KILLED |
if (existingStudent.get().getId().equals(existingStudentByEmail.get().getId())) { |
| 150 | RosterStudent existingStudentObj = existingStudent.get(); | |
| 151 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setRosterStatus → KILLED |
existingStudentObj.setRosterStatus(rosterStatus); |
| 152 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setFirstName → KILLED |
existingStudentObj.setFirstName(student.getFirstName()); |
| 153 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setLastName → KILLED |
existingStudentObj.setLastName(student.getLastName()); |
| 154 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setSection → KILLED |
existingStudentObj.setSection(student.getSection()); |
| 155 |
1
1. upsertStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::upsertStudent → KILLED |
return new UpsertResponse(InsertStatus.UPDATED, existingStudentObj); |
| 156 | } else { | |
| 157 |
1
1. upsertStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::upsertStudent → KILLED |
return new UpsertResponse(InsertStatus.REJECTED, student); |
| 158 | } | |
| 159 |
2
1. upsertStudent : negated conditional → KILLED 2. upsertStudent : negated conditional → KILLED |
} else if (existingStudent.isPresent() || existingStudentByEmail.isPresent()) { |
| 160 | RosterStudent existingStudentObj = | |
| 161 |
1
1. upsertStudent : negated conditional → KILLED |
existingStudent.isPresent() ? existingStudent.get() : existingStudentByEmail.get(); |
| 162 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setRosterStatus → KILLED |
existingStudentObj.setRosterStatus(rosterStatus); |
| 163 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setFirstName → KILLED |
existingStudentObj.setFirstName(student.getFirstName()); |
| 164 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setLastName → KILLED |
existingStudentObj.setLastName(student.getLastName()); |
| 165 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setSection → KILLED |
existingStudentObj.setSection(student.getSection()); |
| 166 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setEmail → KILLED |
existingStudentObj.setEmail(convertedEmail); |
| 167 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setStudentId → KILLED |
existingStudentObj.setStudentId(student.getStudentId()); |
| 168 |
1
1. upsertStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::upsertStudent → KILLED |
return new UpsertResponse(InsertStatus.UPDATED, existingStudentObj); |
| 169 | } else { | |
| 170 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setCourse → KILLED |
student.setCourse(course); |
| 171 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setEmail → KILLED |
student.setEmail(convertedEmail); |
| 172 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setRosterStatus → KILLED |
student.setRosterStatus(rosterStatus); |
| 173 | // if an installationID exists, orgStatus should be set to JOINCOURSE. if it doesn't exist | |
| 174 | // (null), set orgStatus to PENDING. | |
| 175 |
1
1. upsertStudent : negated conditional → KILLED |
if (course.getInstallationId() != null) { |
| 176 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setOrgStatus → KILLED |
student.setOrgStatus(OrgStatus.JOINCOURSE); |
| 177 | } else { | |
| 178 |
1
1. upsertStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setOrgStatus → KILLED |
student.setOrgStatus(OrgStatus.PENDING); |
| 179 | } | |
| 180 |
1
1. upsertStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::upsertStudent → KILLED |
return new UpsertResponse(InsertStatus.INSERTED, student); |
| 181 | } | |
| 182 | } | |
| 183 | ||
| 184 | @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #courseId)") | |
| 185 | @PostMapping("/updateCourseMembership") | |
| 186 | public Job updateCourseMembership( | |
| 187 | @Parameter(name = "courseId", description = "Course ID") @RequestParam Long courseId) | |
| 188 | throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException { | |
| 189 | Course course = | |
| 190 | courseRepository | |
| 191 | .findById(courseId) | |
| 192 |
1
1. lambda$updateCourseMembership$5 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$updateCourseMembership$5 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(Course.class, courseId)); |
| 193 |
2
1. updateCourseMembership : negated conditional → KILLED 2. updateCourseMembership : negated conditional → KILLED |
if (course.getInstallationId() == null || course.getOrgName() == null) { |
| 194 | throw new NoLinkedOrganizationException(course.getCourseName()); | |
| 195 | } else { | |
| 196 | UpdateOrgMembershipJob job = | |
| 197 | UpdateOrgMembershipJob.builder() | |
| 198 | .rosterStudentRepository(rosterStudentRepository) | |
| 199 | .organizationMemberService(organizationMemberService) | |
| 200 | .course(course) | |
| 201 | .build(); | |
| 202 | ||
| 203 |
1
1. updateCourseMembership : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::updateCourseMembership → KILLED |
return jobService.runAsJob(job); |
| 204 | } | |
| 205 | } | |
| 206 | ||
| 207 | @Operation( | |
| 208 | summary = | |
| 209 | "Allow roster student to join a course by generating an invitation to the linked Github Org") | |
| 210 | @PreAuthorize("hasRole('ROLE_USER')") | |
| 211 | @PutMapping("/joinCourse") | |
| 212 | public ResponseEntity<String> joinCourseOnGitHub( | |
| 213 | @Parameter( | |
| 214 | name = "rosterStudentId", | |
| 215 | description = "Roster Student joining a course on GitHub") | |
| 216 | @RequestParam | |
| 217 | Long rosterStudentId) | |
| 218 | throws NoSuchAlgorithmException, InvalidKeySpecException, JsonProcessingException { | |
| 219 | ||
| 220 | User currentUser = currentUserService.getUser(); | |
| 221 | RosterStudent rosterStudent = | |
| 222 | rosterStudentRepository | |
| 223 | .findById(rosterStudentId) | |
| 224 |
1
1. lambda$joinCourseOnGitHub$6 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$joinCourseOnGitHub$6 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(RosterStudent.class, rosterStudentId)); |
| 225 | ||
| 226 |
2
1. joinCourseOnGitHub : negated conditional → KILLED 2. joinCourseOnGitHub : negated conditional → KILLED |
if (rosterStudent.getUser() == null || currentUser.getId() != rosterStudent.getUser().getId()) { |
| 227 | throw new AccessDeniedException("User not authorized join the course as this roster student"); | |
| 228 | } | |
| 229 | ||
| 230 |
1
1. joinCourseOnGitHub : negated conditional → KILLED |
if (rosterStudent.getRosterStatus() == RosterStatus.DROPPED) { |
| 231 | throw new AccessDeniedException( | |
| 232 | "You have dropped this course. Please contact your instructor."); | |
| 233 | } | |
| 234 | ||
| 235 |
1
1. joinCourseOnGitHub : negated conditional → KILLED |
if (rosterStudent.getGithubId() != null |
| 236 |
1
1. joinCourseOnGitHub : negated conditional → KILLED |
&& rosterStudent.getGithubLogin() != null |
| 237 |
1
1. joinCourseOnGitHub : negated conditional → KILLED |
&& (rosterStudent.getOrgStatus() == OrgStatus.MEMBER |
| 238 |
1
1. joinCourseOnGitHub : negated conditional → KILLED |
|| rosterStudent.getOrgStatus() == OrgStatus.OWNER)) { |
| 239 |
1
1. joinCourseOnGitHub : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::joinCourseOnGitHub → KILLED |
return ResponseEntity.badRequest() |
| 240 | .body("This user has already linked a Github account to this course."); | |
| 241 | } | |
| 242 | ||
| 243 |
1
1. joinCourseOnGitHub : negated conditional → KILLED |
if (rosterStudent.getCourse().getOrgName() == null |
| 244 |
1
1. joinCourseOnGitHub : negated conditional → KILLED |
|| rosterStudent.getCourse().getInstallationId() == null) { |
| 245 |
1
1. joinCourseOnGitHub : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::joinCourseOnGitHub → KILLED |
return ResponseEntity.badRequest() |
| 246 | .body("Course has not been set up. Please ask your instructor for help."); | |
| 247 | } | |
| 248 |
1
1. joinCourseOnGitHub : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setGithubId → KILLED |
rosterStudent.setGithubId(currentUser.getGithubId()); |
| 249 |
1
1. joinCourseOnGitHub : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setGithubLogin → KILLED |
rosterStudent.setGithubLogin(currentUser.getGithubLogin()); |
| 250 | OrgStatus status = organizationMemberService.inviteOrganizationMember(rosterStudent); | |
| 251 |
1
1. joinCourseOnGitHub : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setOrgStatus → KILLED |
rosterStudent.setOrgStatus(status); |
| 252 | rosterStudentRepository.save(rosterStudent); | |
| 253 |
1
1. joinCourseOnGitHub : negated conditional → KILLED |
if (status == OrgStatus.INVITED) { |
| 254 |
1
1. joinCourseOnGitHub : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::joinCourseOnGitHub → KILLED |
return ResponseEntity.accepted().body("Successfully invited student to Organization"); |
| 255 |
2
1. joinCourseOnGitHub : negated conditional → KILLED 2. joinCourseOnGitHub : negated conditional → KILLED |
} else if (status == OrgStatus.MEMBER || status == OrgStatus.OWNER) { |
| 256 |
1
1. joinCourseOnGitHub : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::joinCourseOnGitHub → KILLED |
return ResponseEntity.accepted() |
| 257 | .body("Already in organization - set status to %s".formatted(status.toString())); | |
| 258 | } else { | |
| 259 |
1
1. joinCourseOnGitHub : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::joinCourseOnGitHub → KILLED |
return ResponseEntity.internalServerError().body("Could not invite student to Organization"); |
| 260 | } | |
| 261 | } | |
| 262 | ||
| 263 | @Operation(summary = "Get Associated Roster Students with a User") | |
| 264 | @PreAuthorize("hasRole('ROLE_USER')") | |
| 265 | @GetMapping("/associatedRosterStudents") | |
| 266 | public Iterable<RosterStudent> getAssociatedRosterStudents() { | |
| 267 | User currentUser = currentUserService.getUser(); | |
| 268 | Iterable<RosterStudent> rosterStudents = rosterStudentRepository.findAllByUser((currentUser)); | |
| 269 |
1
1. getAssociatedRosterStudents : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::getAssociatedRosterStudents → KILLED |
return rosterStudents; |
| 270 | } | |
| 271 | ||
| 272 | @Operation(summary = "Update a roster student") | |
| 273 | @PreAuthorize("@CourseSecurity.hasRosterStudentManagementPermissions(#root, #id)") | |
| 274 | @PutMapping("/update") | |
| 275 | public RosterStudent updateRosterStudent( | |
| 276 | @Parameter(name = "id") @RequestParam Long id, | |
| 277 | @Parameter(name = "firstName") @RequestParam(required = false) String firstName, | |
| 278 | @Parameter(name = "lastName") @RequestParam(required = false) String lastName, | |
| 279 | @Parameter(name = "studentId") @RequestParam(required = false) String studentId, | |
| 280 | @Parameter(name = "section") @RequestParam(required = false) String section) | |
| 281 | throws EntityNotFoundException { | |
| 282 | ||
| 283 |
3
1. updateRosterStudent : negated conditional → KILLED 2. updateRosterStudent : negated conditional → KILLED 3. updateRosterStudent : negated conditional → KILLED |
if (firstName == null |
| 284 | || lastName == null | |
| 285 | || studentId == null | |
| 286 |
1
1. updateRosterStudent : negated conditional → KILLED |
|| firstName.trim().isEmpty() |
| 287 |
1
1. updateRosterStudent : negated conditional → KILLED |
|| lastName.trim().isEmpty() |
| 288 |
1
1. updateRosterStudent : negated conditional → KILLED |
|| studentId.trim().isEmpty()) { |
| 289 | throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Required fields cannot be empty"); | |
| 290 | } | |
| 291 | ||
| 292 | RosterStudent rosterStudent = | |
| 293 | rosterStudentRepository | |
| 294 | .findById(id) | |
| 295 |
1
1. lambda$updateRosterStudent$7 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$updateRosterStudent$7 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(RosterStudent.class, id)); |
| 296 | ||
| 297 |
1
1. updateRosterStudent : negated conditional → KILLED |
if (!rosterStudent.getStudentId().trim().equals(studentId.trim())) { |
| 298 | Optional<RosterStudent> existingStudent = | |
| 299 | rosterStudentRepository.findByCourseIdAndStudentId( | |
| 300 | rosterStudent.getCourse().getId(), studentId.trim()); | |
| 301 |
1
1. updateRosterStudent : negated conditional → KILLED |
if (existingStudent.isPresent()) { |
| 302 | throw new ResponseStatusException( | |
| 303 | HttpStatus.BAD_REQUEST, "Student ID already exists in this course"); | |
| 304 | } | |
| 305 | } | |
| 306 | ||
| 307 |
1
1. updateRosterStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setFirstName → KILLED |
rosterStudent.setFirstName(firstName.trim()); |
| 308 |
1
1. updateRosterStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setLastName → KILLED |
rosterStudent.setLastName(lastName.trim()); |
| 309 |
1
1. updateRosterStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setStudentId → KILLED |
rosterStudent.setStudentId(studentId.trim()); |
| 310 | ||
| 311 |
1
1. updateRosterStudent : negated conditional → KILLED |
if (section != null) { |
| 312 |
1
1. updateRosterStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setSection → KILLED |
rosterStudent.setSection(section.trim()); |
| 313 | } | |
| 314 | ||
| 315 |
1
1. updateRosterStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::updateRosterStudent → KILLED |
return rosterStudentRepository.save(rosterStudent); |
| 316 | } | |
| 317 | ||
| 318 | @Operation( | |
| 319 | summary = "Restore a roster student", | |
| 320 | description = "Makes a student who previously dropped the course able to join and interact") | |
| 321 | @PreAuthorize("@CourseSecurity.hasRosterStudentManagementPermissions(#root, #id)") | |
| 322 | @PutMapping("/restore") | |
| 323 | public RosterStudent restoreRosterStudent(@Parameter(name = "id") @RequestParam Long id) | |
| 324 | throws EntityNotFoundException { | |
| 325 | RosterStudent rosterStudent = | |
| 326 | rosterStudentRepository | |
| 327 | .findById(id) | |
| 328 |
1
1. lambda$restoreRosterStudent$8 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$restoreRosterStudent$8 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(RosterStudent.class, id)); |
| 329 |
1
1. restoreRosterStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setRosterStatus → KILLED |
rosterStudent.setRosterStatus(RosterStatus.MANUAL); |
| 330 |
1
1. restoreRosterStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::restoreRosterStudent → KILLED |
return rosterStudentRepository.save(rosterStudent); |
| 331 | } | |
| 332 | ||
| 333 | @Operation(summary = "Delete a roster student") | |
| 334 | @PreAuthorize("@CourseSecurity.hasRosterStudentManagementPermissions(#root, #id)") | |
| 335 | @DeleteMapping("/delete") | |
| 336 | @Transactional | |
| 337 | public ResponseEntity<String> deleteRosterStudent( | |
| 338 | @Parameter(name = "id") @RequestParam Long id, | |
| 339 | @Parameter( | |
| 340 | name = "removeFromOrg", | |
| 341 | description = "Whether to remove student from GitHub organization") | |
| 342 | @RequestParam(defaultValue = "true") | |
| 343 | boolean removeFromOrg) | |
| 344 | throws EntityNotFoundException { | |
| 345 | RosterStudent rosterStudent = | |
| 346 | rosterStudentRepository | |
| 347 | .findById(id) | |
| 348 |
1
1. lambda$deleteRosterStudent$9 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$deleteRosterStudent$9 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(RosterStudent.class, id)); |
| 349 | Course course = rosterStudent.getCourse(); | |
| 350 | ||
| 351 | boolean orgRemovalAttempted = false; | |
| 352 | boolean orgRemovalSuccessful = false; | |
| 353 | String orgRemovalErrorMessage = null; | |
| 354 | ||
| 355 | // Try to remove the student from the organization if they have a GitHub login | |
| 356 | // and removeFromOrg parameter is true | |
| 357 |
1
1. deleteRosterStudent : negated conditional → KILLED |
if (removeFromOrg |
| 358 |
1
1. deleteRosterStudent : negated conditional → KILLED |
&& rosterStudent.getGithubLogin() != null |
| 359 |
1
1. deleteRosterStudent : negated conditional → KILLED |
&& course.getOrgName() != null |
| 360 |
1
1. deleteRosterStudent : negated conditional → KILLED |
&& course.getInstallationId() != null) { |
| 361 | orgRemovalAttempted = true; | |
| 362 | try { | |
| 363 |
1
1. deleteRosterStudent : removed call to edu/ucsb/cs156/frontiers/services/OrganizationMemberService::removeOrganizationMember → KILLED |
organizationMemberService.removeOrganizationMember(rosterStudent); |
| 364 | orgRemovalSuccessful = true; | |
| 365 | } catch (Exception e) { | |
| 366 | log.error("Error removing student from organization: {}", e.getMessage()); | |
| 367 | orgRemovalErrorMessage = e.getMessage(); | |
| 368 | // Continue with deletion even if organization removal fails | |
| 369 | } | |
| 370 | } | |
| 371 | ||
| 372 |
1
1. deleteRosterStudent : negated conditional → KILLED |
if (!rosterStudent.getTeamMembers().isEmpty()) { |
| 373 | rosterStudent | |
| 374 | .getTeamMembers() | |
| 375 |
1
1. deleteRosterStudent : removed call to java/util/List::forEach → KILLED |
.forEach( |
| 376 | teamMember -> { | |
| 377 | teamMember.getTeam().getTeamMembers().remove(teamMember); | |
| 378 |
1
1. lambda$deleteRosterStudent$10 : removed call to edu/ucsb/cs156/frontiers/entities/TeamMember::setTeam → KILLED |
teamMember.setTeam(null); |
| 379 | }); | |
| 380 | } | |
| 381 | ||
| 382 | rosterStudent.getCourse().getRosterStudents().remove(rosterStudent); | |
| 383 |
1
1. deleteRosterStudent : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setCourse → KILLED |
rosterStudent.setCourse(null); |
| 384 |
1
1. deleteRosterStudent : removed call to edu/ucsb/cs156/frontiers/repositories/RosterStudentRepository::delete → KILLED |
rosterStudentRepository.delete(rosterStudent); |
| 385 | ||
| 386 |
1
1. deleteRosterStudent : negated conditional → KILLED |
if (!orgRemovalAttempted) { |
| 387 |
1
1. deleteRosterStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::deleteRosterStudent → KILLED |
return ResponseEntity.ok( |
| 388 | "Successfully deleted roster student and removed him/her from the course list"); | |
| 389 |
1
1. deleteRosterStudent : negated conditional → KILLED |
} else if (orgRemovalSuccessful) { |
| 390 |
1
1. deleteRosterStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::deleteRosterStudent → KILLED |
return ResponseEntity.ok( |
| 391 | "Successfully deleted roster student and removed him/her from the course list and organization"); | |
| 392 | } else { | |
| 393 |
1
1. deleteRosterStudent : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::deleteRosterStudent → KILLED |
return ResponseEntity.ok( |
| 394 | "Successfully deleted roster student but there was an error removing them from the course organization: " | |
| 395 | + orgRemovalErrorMessage); | |
| 396 | } | |
| 397 | } | |
| 398 | ||
| 399 | /** | |
| 400 | * Upload Roster students for Course from Canvas. It is important to keep the code in this method | |
| 401 | * consistent with the code in uploadRosterStudentsCSV. | |
| 402 | * | |
| 403 | * @param courseId the internal course ID in Frontiers | |
| 404 | * @return LoadResult with counts of inserted, updated, dropped students and any rejected students | |
| 405 | */ | |
| 406 | @Operation(summary = "Upload Roster students for Course from Canvas") | |
| 407 | @PreAuthorize("@CourseSecurity.hasInstructorPermissions(#root, #courseId)") | |
| 408 | @PostMapping("/canvas/sync/students") | |
| 409 | public ResponseEntity<LoadResult> uploadRosterFromCanvas( | |
| 410 | @Parameter(name = "courseId") @RequestParam Long courseId) { | |
| 411 | ||
| 412 | Course course = | |
| 413 | courseRepository | |
| 414 | .findById(courseId) | |
| 415 |
1
1. lambda$uploadRosterFromCanvas$11 : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$uploadRosterFromCanvas$11 → KILLED |
.orElseThrow(() -> new EntityNotFoundException(Course.class, courseId.toString())); |
| 416 | ||
| 417 | course.getRosterStudents().stream() | |
| 418 |
2
1. lambda$uploadRosterFromCanvas$12 : negated conditional → KILLED 2. lambda$uploadRosterFromCanvas$12 : replaced boolean return with true for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$uploadRosterFromCanvas$12 → KILLED |
.filter(filteredStudent -> filteredStudent.getRosterStatus() == RosterStatus.ROSTER) |
| 419 |
2
1. lambda$uploadRosterFromCanvas$13 : removed call to edu/ucsb/cs156/frontiers/entities/RosterStudent::setRosterStatus → KILLED 2. uploadRosterFromCanvas : removed call to java/util/stream/Stream::forEach → KILLED |
.forEach(student -> student.setRosterStatus(RosterStatus.DROPPED)); |
| 420 | ||
| 421 | int counts[] = {0, 0}; | |
| 422 | List<RosterStudent> rejectedStudents = new ArrayList<>(); | |
| 423 | ||
| 424 | List<RosterStudent> canvasStudents = canvasService.getCanvasRoster(course); | |
| 425 | for (RosterStudent rosterStudent : canvasStudents) { | |
| 426 | UpsertResponse upsertResponse = upsertStudent(rosterStudent, course, RosterStatus.ROSTER); | |
| 427 |
1
1. uploadRosterFromCanvas : negated conditional → KILLED |
if (upsertResponse.getInsertStatus() == InsertStatus.REJECTED) { |
| 428 | rejectedStudents.add(upsertResponse.rosterStudent()); | |
| 429 | } else { | |
| 430 | InsertStatus s = upsertResponse.getInsertStatus(); | |
| 431 |
1
1. uploadRosterFromCanvas : negated conditional → KILLED |
if (s == InsertStatus.INSERTED) { |
| 432 | course.getRosterStudents().add(upsertResponse.rosterStudent()); | |
| 433 | } | |
| 434 |
1
1. uploadRosterFromCanvas : Replaced integer addition with subtraction → KILLED |
counts[s.ordinal()]++; |
| 435 | } | |
| 436 | } | |
| 437 | ||
| 438 |
1
1. uploadRosterFromCanvas : negated conditional → KILLED |
if (rejectedStudents.isEmpty()) { |
| 439 | List<RosterStudent> droppedStudents = | |
| 440 | course.getRosterStudents().stream() | |
| 441 |
2
1. lambda$uploadRosterFromCanvas$14 : replaced boolean return with true for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::lambda$uploadRosterFromCanvas$14 → KILLED 2. lambda$uploadRosterFromCanvas$14 : negated conditional → KILLED |
.filter(student -> student.getRosterStatus() == RosterStatus.DROPPED) |
| 442 | .toList(); | |
| 443 | LoadResult successfulResult = | |
| 444 | new LoadResult( | |
| 445 | counts[InsertStatus.INSERTED.ordinal()], | |
| 446 | counts[InsertStatus.UPDATED.ordinal()], | |
| 447 | droppedStudents.size(), | |
| 448 | List.of()); | |
| 449 | rosterStudentRepository.saveAll(course.getRosterStudents()); | |
| 450 |
1
1. uploadRosterFromCanvas : removed call to edu/ucsb/cs156/frontiers/services/UpdateUserService::attachUsersToRosterStudents → KILLED |
updateUserService.attachUsersToRosterStudents(course.getRosterStudents()); |
| 451 | RemoveStudentsJob job = | |
| 452 | RemoveStudentsJob.builder() | |
| 453 | .students(droppedStudents) | |
| 454 | .organizationMemberService(organizationMemberService) | |
| 455 | .rosterStudentRepository(rosterStudentRepository) | |
| 456 | .build(); | |
| 457 | jobService.runAsJob(job); | |
| 458 |
1
1. uploadRosterFromCanvas : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::uploadRosterFromCanvas → KILLED |
return ResponseEntity.ok(successfulResult); |
| 459 | } else { | |
| 460 | LoadResult conflictResult = new LoadResult(0, 0, 0, rejectedStudents); | |
| 461 |
1
1. uploadRosterFromCanvas : replaced return value with null for edu/ucsb/cs156/frontiers/controllers/RosterStudentsController::uploadRosterFromCanvas → KILLED |
return ResponseEntity.status(HttpStatus.CONFLICT).body(conflictResult); |
| 462 | } | |
| 463 | } | |
| 464 | } | |
Mutations | ||
| 92 |
1.1 |
|
| 100 |
1.1 |
|
| 104 |
1.1 |
|
| 105 |
1.1 |
|
| 108 |
1.1 |
|
| 109 |
1.1 |
|
| 125 |
1.1 |
|
| 130 |
1.1 |
|
| 133 |
1.1 |
|
| 142 |
1.1 2.2 |
|
| 146 |
1.1 2.2 |
|
| 148 |
1.1 2.2 |
|
| 149 |
1.1 |
|
| 151 |
1.1 |
|
| 152 |
1.1 |
|
| 153 |
1.1 |
|
| 154 |
1.1 |
|
| 155 |
1.1 |
|
| 157 |
1.1 |
|
| 159 |
1.1 2.2 |
|
| 161 |
1.1 |
|
| 162 |
1.1 |
|
| 163 |
1.1 |
|
| 164 |
1.1 |
|
| 165 |
1.1 |
|
| 166 |
1.1 |
|
| 167 |
1.1 |
|
| 168 |
1.1 |
|
| 170 |
1.1 |
|
| 171 |
1.1 |
|
| 172 |
1.1 |
|
| 175 |
1.1 |
|
| 176 |
1.1 |
|
| 178 |
1.1 |
|
| 180 |
1.1 |
|
| 192 |
1.1 |
|
| 193 |
1.1 2.2 |
|
| 203 |
1.1 |
|
| 224 |
1.1 |
|
| 226 |
1.1 2.2 |
|
| 230 |
1.1 |
|
| 235 |
1.1 |
|
| 236 |
1.1 |
|
| 237 |
1.1 |
|
| 238 |
1.1 |
|
| 239 |
1.1 |
|
| 243 |
1.1 |
|
| 244 |
1.1 |
|
| 245 |
1.1 |
|
| 248 |
1.1 |
|
| 249 |
1.1 |
|
| 251 |
1.1 |
|
| 253 |
1.1 |
|
| 254 |
1.1 |
|
| 255 |
1.1 2.2 |
|
| 256 |
1.1 |
|
| 259 |
1.1 |
|
| 269 |
1.1 |
|
| 283 |
1.1 2.2 3.3 |
|
| 286 |
1.1 |
|
| 287 |
1.1 |
|
| 288 |
1.1 |
|
| 295 |
1.1 |
|
| 297 |
1.1 |
|
| 301 |
1.1 |
|
| 307 |
1.1 |
|
| 308 |
1.1 |
|
| 309 |
1.1 |
|
| 311 |
1.1 |
|
| 312 |
1.1 |
|
| 315 |
1.1 |
|
| 328 |
1.1 |
|
| 329 |
1.1 |
|
| 330 |
1.1 |
|
| 348 |
1.1 |
|
| 357 |
1.1 |
|
| 358 |
1.1 |
|
| 359 |
1.1 |
|
| 360 |
1.1 |
|
| 363 |
1.1 |
|
| 372 |
1.1 |
|
| 375 |
1.1 |
|
| 378 |
1.1 |
|
| 383 |
1.1 |
|
| 384 |
1.1 |
|
| 386 |
1.1 |
|
| 387 |
1.1 |
|
| 389 |
1.1 |
|
| 390 |
1.1 |
|
| 393 |
1.1 |
|
| 415 |
1.1 |
|
| 418 |
1.1 2.2 |
|
| 419 |
1.1 2.2 |
|
| 427 |
1.1 |
|
| 431 |
1.1 |
|
| 434 |
1.1 |
|
| 438 |
1.1 |
|
| 441 |
1.1 2.2 |
|
| 450 |
1.1 |
|
| 458 |
1.1 |
|
| 461 |
1.1 |