Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 117 additions & 18 deletions src/main/kotlin/org/dropProject/controllers/TeacherAPIController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.springframework.ui.ModelMap
import org.springframework.web.bind.annotation.*
import java.nio.file.Files
import java.security.Principal
import java.util.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

Expand All @@ -65,7 +66,7 @@ class TeacherAPIController(
val reportService: ReportService,
val projectGroupRepository: ProjectGroupRepository,
val assignmentACLRepository: AssignmentACLRepository,
val studentService: StudentService
val studentService: StudentService,
) {

val LOG = LoggerFactory.getLogger(this.javaClass.name)
Expand All @@ -88,11 +89,13 @@ class TeacherAPIController(
value = "Get the latest submissions by each group for the assignment identified by the assignmentID variable",
response = SubmissionInfo::class, responseContainer = "List", ignoreJsonView = false
)
fun getAssignmentLatestSubmissions(@PathVariable assignmentId: String, model: ModelMap,
principal: Principal, request: HttpServletRequest): ResponseEntity<List<AssignmentLatestSubmissionsResponse>> {
fun getAssignmentLatestSubmissions(
@PathVariable assignmentId: String, model: ModelMap,
principal: Principal, request: HttpServletRequest
): ResponseEntity<List<AssignmentLatestSubmissionsResponse>> {
assignmentService.getAllSubmissionsForAssignment(assignmentId, principal, model, request, mode = "summary")

val result = (model["submissions"] as List<SubmissionInfo>).map{
val result = (model["submissions"] as List<SubmissionInfo>).map {
AssignmentLatestSubmissionsResponse(it.projectGroup, it.lastSubmission, it.allSubmissions.size)
}

Expand All @@ -105,8 +108,10 @@ class TeacherAPIController(
value = "",
response = Submission::class, responseContainer = "List", ignoreJsonView = false
)
fun getGroupAssignmentSubmissions(@PathVariable assignmentId: String, @PathVariable groupId: Long, model: ModelMap,
principal: Principal, request: HttpServletRequest): ResponseEntity<List<Submission>> {
fun getGroupAssignmentSubmissions(
@PathVariable assignmentId: String, @PathVariable groupId: Long, model: ModelMap,
principal: Principal, request: HttpServletRequest
): ResponseEntity<List<Submission>> {

val submissions = submissionRepository
.findByGroupAndAssignmentIdOrderBySubmissionDateDescStatusDateDesc(ProjectGroup(groupId), assignmentId)
Expand All @@ -120,8 +125,10 @@ class TeacherAPIController(
@GetMapping(value = ["/download/{submissionId}"], produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
@JsonView(JSONViews.TeacherAPI::class)
@ApiOperation(value = "Download the mavenized zip file for this submission")
fun downloadProject(@PathVariable submissionId: Long, principal: Principal,
request: HttpServletRequest, response: HttpServletResponse): FileSystemResource {
fun downloadProject(
@PathVariable submissionId: Long, principal: Principal,
request: HttpServletRequest, response: HttpServletResponse
): FileSystemResource {

val submission = submissionRepository.findById(submissionId).orElse(null)
if (submission != null) {
Expand All @@ -131,8 +138,10 @@ class TeacherAPIController(
throw AccessDeniedException("${principal.realName()} is not allowed to view this report")
}

val projectFolder = assignmentTeacherFiles.getProjectFolderAsFile(submission,
wasRebuilt = submission.getStatus() == SubmissionStatus.VALIDATED_REBUILT)
val projectFolder = assignmentTeacherFiles.getProjectFolderAsFile(
submission,
wasRebuilt = submission.getStatus() == SubmissionStatus.VALIDATED_REBUILT
)

if (!Files.exists(projectFolder.toPath())) {
throw ResourceNotFoundException()
Expand All @@ -157,8 +166,10 @@ class TeacherAPIController(
@GetMapping(value = ["/submissions/{submissionId}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@JsonView(JSONViews.TeacherAPI::class)
@ApiOperation(value = "Get the build report associated with this submission")
fun getBuildReport(@PathVariable submissionId: Long, principal: Principal,
request: HttpServletRequest): ResponseEntity<FullBuildReport> {
fun getBuildReport(
@PathVariable submissionId: Long, principal: Principal,
request: HttpServletRequest
): ResponseEntity<FullBuildReport> {

val report = reportService.buildReport(submissionId, principal, request)

Expand All @@ -172,11 +183,15 @@ class TeacherAPIController(
@GetMapping(value = ["/studentHistory/{studentId}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@JsonView(JSONViews.TeacherAPI::class)
@ApiOperation(value = "Get the student's student history")
fun getStudentHistory(@PathVariable studentId: String,
principal: Principal, request: HttpServletRequest): ResponseEntity<StudentHistory> {

return ResponseEntity.ok().body(studentService.getStudentHistory(studentId, principal)
?: throw ResourceNotFoundException())
fun getStudentHistory(
@PathVariable studentId: String,
principal: Principal, request: HttpServletRequest
): ResponseEntity<StudentHistory> {

return ResponseEntity.ok().body(
studentService.getStudentHistory(studentId, principal)
?: throw ResourceNotFoundException()
)
}

@GetMapping(value = ["/studentSearch/{query}"], produces = [MediaType.APPLICATION_JSON_VALUE])
Expand Down Expand Up @@ -205,10 +220,73 @@ class TeacherAPIController(
return true
}

@Suppress("UNCHECKED_CAST")
@GetMapping(
value = ["/assignments/{assignmentId}/previewMarkBestSubmissions"],
produces = [MediaType.APPLICATION_JSON_VALUE]
)
@JsonView(JSONViews.TeacherAPI::class)
@ApiOperation(value = "Get a list of each group's best submissions for the assignment")
fun previewMarkBestSubmissions(
@PathVariable assignmentId: String, model: ModelMap,
@RequestParam ignoreOutsideOfDeadlineSubmissions: Boolean = false,
request: HttpServletRequest, principal: Principal
): ResponseEntity<List<Submission>> {

assignmentService.getAllSubmissionsForAssignment(assignmentId, principal, model, request, mode = "summary")

val groupSubmissions = (model["submissions"] as List<SubmissionInfo>).groupBy { it.projectGroup }
val bestSubmissions = mutableListOf<Submission>()

for (value in groupSubmissions.values) {
if (value.firstOrNull() == null
|| value.first().allSubmissions.any
{ it.submissionDate > ((model["assignment"] as Assignment).dueDate ?: Date()) }
) {

continue
}

submissionService.fillIndicatorsFor(value.first().allSubmissions)

fun getScore(submission: Submission) =
submission.teacherTests?.progress ?: 0

val sortedSubmissions = value.first().allSubmissions.sortedByDescending { getScore(it) }
val topScore = getScore(sortedSubmissions.first())

val bestSubmission = value.firstOrNull()?.allSubmissions
?.filter { getScore(it) == topScore }
?.maxByOrNull { it.submissionDate }

if (bestSubmission != null) {
bestSubmissions.add(bestSubmission)
}
}

return ResponseEntity.ok(bestSubmissions)
}

@PostMapping(value = ["/markMultipleAsFinal"],
consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@JsonView(JSONViews.TeacherAPI::class)
@ApiOperation(value = "Mark multiple assignments as final")
fun markMultipleAsFinal(@RequestBody submissions: List<Long>, principal: Principal): Boolean {

for (submission in submissions) {
markAsFinal(submission, principal)
}

return true
}

@GetMapping(value = ["/assignmentSearch/{query}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@JsonView(JSONViews.TeacherAPI::class)
@ApiOperation(value = "Get all assignments that match the query value")
fun searchAssignments(@PathVariable("query") query: String, principal: Principal): ResponseEntity<List<StudentListResponse>> {
fun searchAssignments(
@PathVariable("query") query: String,
principal: Principal
): ResponseEntity<List<StudentListResponse>> {
val result = assignmentRepository.findAll()
.filter {
val acl = assignmentACLRepository.findByAssignmentId(it.id)
Expand All @@ -225,4 +303,25 @@ class TeacherAPIController(

return ResponseEntity(result, HttpStatus.OK)
}

@GetMapping(value = ["/assignments/{assignmentId}/toggleState"], produces = [MediaType.APPLICATION_JSON_VALUE])
@JsonView(JSONViews.TeacherAPI::class)
@ApiOperation(value = "Deactivate the assignment")
fun toggleAssignmentState(
@PathVariable("assignmentId") assignmentId: String,
principal: Principal
): Boolean {

val assignment = assignmentRepository.findById(assignmentId).orElse(null) ?: return false
val acl = assignmentACLRepository.findByAssignmentId(assignmentId)

if (principal.realName() != assignment.ownerUserId && acl.find { it.userId == principal.realName() } == null) {
return false
}

assignment.active = !assignment.active
assignmentRepository.save(assignment)

return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ import org.springframework.test.context.TestPropertySource
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.transaction.annotation.Transactional
import java.sql.Timestamp
import java.time.LocalDateTime
import kotlin.test.assertFalse
import kotlin.test.assertTrue

@RunWith(SpringRunner::class)
@AutoConfigureMockMvc
Expand Down Expand Up @@ -614,4 +617,72 @@ class TeacherAPIControllerTests: APIControllerTests {
.header("authorization", testsHelper.header("teacher1", token)))
.andExpect(status().isOk()).andExpect(content().string("false"))
}

@Test
@DirtiesContext
fun `try to mark the best submissions as final`() {
assigneeRepository.deleteAll()
assignmentRepository.deleteAll()
authorRepository.deleteAll()

setup()

val token = generateToken("teacher1", mutableListOf(SimpleGrantedAuthority("ROLE_TEACHER")), mvc)

this.mvc.perform(
get("/api/teacher/assignments/testJavaProj/previewMarkBestSubmissions")
.contentType(MediaType.APPLICATION_JSON)
.header("authorization", testsHelper.header("teacher1", token)))
.andExpect(status().isOk()).andExpect(content().json("""
[
{"id":2,
"submissionDate":"2019-01-02T11:05:03.000+00:00",
"status":"VALIDATED",
"statusDate":"2019-01-02T11:05:03.000+00:00",
"markedAsFinal":false,
"teacherTests":{"numTests":4,
"numFailures":0,
"numErrors":0,
"numSkipped":0,
"ellapsed":0.007,
"numMandatoryOK":0,
"numMandatoryNOK":0 },
"overdue":false,
"group":{"id":1,
"authors":[{"id":1,
"name":"Student 1"}]}}]
""".trimIndent()))

this.mvc.perform(
post("/api/teacher/markMultipleAsFinal")
.contentType(MediaType.APPLICATION_JSON)
.content("[1, 2]")
.header("authorization", testsHelper.header("teacher1", token)))
.andExpect(status().isOk)

val submissions = submissionRepository.findByStatusOrderByStatusDate(SubmissionStatus.VALIDATED.code)
.groupBy { it.id }
assertFalse { submissions[1]?.first()?.markedAsFinal == true } // first submission gets unmarked since 1 and 2 are made by the same student
assertTrue { submissions[2]?.first()?.markedAsFinal == true }
}

@Test
@DirtiesContext
fun `try to deactivate an assignment`() {
assigneeRepository.deleteAll()
assignmentRepository.deleteAll()
authorRepository.deleteAll()

setup()

val token = generateToken("teacher1", mutableListOf(SimpleGrantedAuthority("ROLE_TEACHER")), mvc)

this.mvc.perform(
get("/api/teacher/assignments/testJavaProj/toggleState")
.contentType(MediaType.APPLICATION_JSON)
.header("authorization", testsHelper.header("teacher1", token)))
.andExpect(status().isOk()).andExpect(content().string("true"))

assertFalse { assignmentRepository.getReferenceById("testJavaProj").active }
}
}