diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Api.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Api.kt index 920729b..e3f72f7 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Api.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Api.kt @@ -62,6 +62,18 @@ data class ChartDataset( constructor(label: String, color: String, data: List) : this(label, color, color, data) } +data class AverageData( + val monitoring: Double, + val controlProcedural: Double, + val control: Double, + val conservatism: Double, + val teamworkCommunications: Double, + val teamworkLeadership: Double, + val teamworkWorkload: Double, + val knowledge: Double, + val date: LocalDate +) + data class AfiPieChart( val labels: List = listOf("Monitoring", "Knowledge", "Control", "Conservatism", "Teamwork"), val datasets: List diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt new file mode 100644 index 0000000..4dd0537 --- /dev/null +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt @@ -0,0 +1,120 @@ +package uk.co.neviyn.observationdatabase.controller + +import org.joda.time.LocalDate +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.core.env.Environment +import org.springframework.core.env.get +import org.springframework.web.bind.annotation.CrossOrigin +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import uk.co.neviyn.observationdatabase.GroupObservation +import uk.co.neviyn.observationdatabase.GroupObservationInit +import uk.co.neviyn.observationdatabase.GroupSessionManager +import uk.co.neviyn.observationdatabase.Observation +import uk.co.neviyn.observationdatabase.Person +import uk.co.neviyn.observationdatabase.PersonRepository +import uk.co.neviyn.observationdatabase.SiteRepository +import uk.co.neviyn.observationdatabase.TutorRepository +import java.net.Inet4Address +import java.net.NetworkInterface + +@RestController +@RequestMapping("/api/grpob") +@CrossOrigin +class GroupSessionController { + + private val logger: Logger = LoggerFactory.getLogger(javaClass)!! + + @Autowired + lateinit var environment: Environment + @Autowired + lateinit var siteRepository: SiteRepository + @Autowired + lateinit var tutorRepository: TutorRepository + @Autowired + lateinit var observationsController: ObservationsController + @Autowired + lateinit var personRepository: PersonRepository + + @PostMapping("/start") + fun startGroupObservation(initData: GroupObservationInit): Map { + val site = siteRepository.findById(initData.site) + val tutors = tutorRepository.findAllById(initData.tutors).toSet() + if (!site.isPresent) { + logger.info("Attempted to add Observation without a site.") + return mapOf("error" to "Site required") + } + if (tutors.isEmpty() || tutors.size != initData.tutors.size) { + logger.info("Attempted to add Observation without a tutor") + return mapOf("error" to "Tutor(s) required") + } + val sessionId = GroupSessionManager.startNewSession(site.get(), tutors, initData.type, initData.scenarioTitles) + return getConnectionDetails().plus("id" to sessionId.toString()) + } + + @GetMapping("/recover") + fun reconnectToGroupObservation(): Map { + logger.debug("Previous group observation requested") + return getConnectionDetails().plus(mapOf("id" to GroupSessionManager.sessionId.toString(), "observations" to GroupSessionManager.observations)) + } + + @GetMapping("/valid/{id}") + fun checkGroupObservationValidityById(@PathVariable id: Int): Map { + if (GroupSessionManager.isValid(id)) { + return mapOf("titles" to GroupSessionManager.scenarioTitles!!) + } + logger.warn("Group observation requested with id $id but there is no valid session") + return mapOf("error" to "no valid session") + } + + @GetMapping("/valid") + fun checkGroupObservationValidity(): Boolean { + return GroupSessionManager.isValid() + } + + @PostMapping("/submit") + fun addGroupObservation(observationData: GroupObservation) { + if (GroupSessionManager.isValid()) { + var observation = Observation( + site = GroupSessionManager.site!!, + date = LocalDate.now(), + type = GroupSessionManager.trainingType!!, + observed = observationData.scenarios.joinToString { it.title }, + monitoring = observationData.scenarios.map { it.monitoring.rating }.average(), + conservatism = observationData.scenarios.map { it.conservatism.rating }.average(), + controlProcedural = observationData.scenarios.map { it.controlProcedural.rating }.average(), + control = observationData.scenarios.map { it.control.rating }.average(), + teamworkCommunications = observationData.scenarios.map { it.teamworkCommunications.rating }.average(), + teamworkLeadership = observationData.scenarios.map { it.teamworkLeadership.rating }.average(), + teamworkWorkload = observationData.scenarios.map { it.teamworkWorkload.rating }.average(), + knowledge = observationData.scenarios.map { it.knowledge.rating }.average(), + scenarios = observationData.scenarios, + tutors = GroupSessionManager.tutors!!, + person = personRepository.findFirstByNameLike(observationData.person.toUpperCase()) ?: personRepository.save(Person(name = observationData.person.toUpperCase())) + ) + observation = observationsController.saveObservation(observation) + GroupSessionManager.addObservation(observation) + } + } + + @GetMapping("/address") + fun getConnectionDetails(): Map { + var ipv4: String? = null + var retryCount = 0 + while (ipv4 == null && retryCount < 3) { + ipv4 = NetworkInterface.getNetworkInterfaces().asSequence() + .filter { !it.isLoopback }.map { x -> x.inetAddresses.asSequence() + .filter { it is Inet4Address }.map { it.hostAddress } }.flatten().firstOrNull() + retryCount++ + Thread.sleep(1_000) // Sleep for 1 second + } + if (ipv4 != null) + return mapOf("ip" to ipv4, "port" to environment["local.server.port"]) + return mapOf("error" to "Could not determine IP Address") + } +} \ No newline at end of file diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Controller.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/ObservationsController.kt similarity index 71% rename from backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Controller.kt rename to backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/ObservationsController.kt index 903ad3e..0c8c96a 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Controller.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/ObservationsController.kt @@ -1,33 +1,44 @@ -package uk.co.neviyn.observationdatabase +package uk.co.neviyn.observationdatabase.controller import org.joda.time.LocalDate import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.cache.annotation.Cacheable -import org.springframework.core.env.Environment -import org.springframework.core.env.get +import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import org.springframework.web.bind.annotation.CrossOrigin -import java.net.Inet4Address -import java.net.NetworkInterface +import uk.co.neviyn.observationdatabase.AfiPieChart +import uk.co.neviyn.observationdatabase.AfiPieChartDataset +import uk.co.neviyn.observationdatabase.AverageData +import uk.co.neviyn.observationdatabase.ChartData +import uk.co.neviyn.observationdatabase.ChartDataset +import uk.co.neviyn.observationdatabase.NameValue +import uk.co.neviyn.observationdatabase.NewObservation +import uk.co.neviyn.observationdatabase.NewSite +import uk.co.neviyn.observationdatabase.NewTutor +import uk.co.neviyn.observationdatabase.Observation +import uk.co.neviyn.observationdatabase.ObservationRepository +import uk.co.neviyn.observationdatabase.ObservationsRequest +import uk.co.neviyn.observationdatabase.Person +import uk.co.neviyn.observationdatabase.PersonRepository +import uk.co.neviyn.observationdatabase.Site +import uk.co.neviyn.observationdatabase.SiteRepository +import uk.co.neviyn.observationdatabase.Tutor +import uk.co.neviyn.observationdatabase.TutorRepository import javax.validation.Valid @RestController @RequestMapping("/api") @CrossOrigin -class Controller { +class ObservationsController { private val logger: Logger = LoggerFactory.getLogger(javaClass)!! - @Autowired - lateinit var environment: Environment - @Autowired lateinit var siteRepository: SiteRepository @Autowired @@ -148,8 +159,8 @@ class Controller { logger.debug("Observation contains a tutor") return tutorRepository.findById(observationsRequest.tutor).map { observationRepository.findByTutorsAndDateBetweenOrderByDateAsc(tutor = it, - startDate = observationsRequest.startDate, - endDate = observationsRequest.endDate) + startDate = observationsRequest.startDate, + endDate = observationsRequest.endDate) }.orElse(listOf()) } else if (observationsRequest.person != null && observationsRequest.person.isNotEmpty() && observationsRequest.site != null) { logger.debug("Observation contains a person") @@ -160,8 +171,8 @@ class Controller { logger.debug("Observation contains a site") return siteRepository.findById(observationsRequest.site).map { observationRepository.findBySiteAndDateBetweenOrderByDateAsc(site = it, - startDate = observationsRequest.startDate, - endDate = observationsRequest.endDate) + startDate = observationsRequest.startDate, + endDate = observationsRequest.endDate) }.orElse(listOf()) } else { logger.error("Observation request contains no data from which a request can be made\n$observationsRequest") @@ -262,93 +273,4 @@ class Controller { } return AfiPieChart(AfiPieChartDataset(monitoring, knowledge, control, conservatism, teamwork)) } - - @PostMapping("/grpob/start") - fun startGroupObservation(initData: GroupObservationInit): Map { - val site = siteRepository.findById(initData.site) - val tutors = tutorRepository.findAllById(initData.tutors).toSet() - if (!site.isPresent) { - logger.info("Attempted to add Observation without a site.") - return mapOf("error" to "Site required") - } - if (tutors.isEmpty() || tutors.size != initData.tutors.size) { - logger.info("Attempted to add Observation without a tutor") - return mapOf("error" to "Tutor(s) required") - } - val sessionId = GroupSessionManager.startNewSession(site.get(), tutors, initData.type, initData.scenarioTitles) - return getConnectionDetails().plus("id" to sessionId.toString()) - } - - @GetMapping("/grpob/recover") - fun reconnectToGroupObservation(): Map { - logger.debug("Previous group observation requested") - return getConnectionDetails().plus(mapOf("id" to GroupSessionManager.sessionId.toString(), "observations" to GroupSessionManager.observations)) - } - - @GetMapping("/grpob/valid/{id}") - fun checkGroupObservationValidityById(@PathVariable id: Int): Map { - if (GroupSessionManager.isValid(id)) { - return mapOf("titles" to GroupSessionManager.scenarioTitles!!) - } - logger.warn("Group observation requested with id $id but there is no valid session") - return mapOf("error" to "no valid session") - } - - @GetMapping("/grpob/valid") - fun checkGroupObservationValidity(): Boolean { - return GroupSessionManager.isValid() - } - - @PostMapping("/grpob/submit") - fun addGroupObservation(observationData: GroupObservation) { - if (GroupSessionManager.isValid()) { - var observation = Observation( - site = GroupSessionManager.site!!, - date = LocalDate.now(), - type = GroupSessionManager.trainingType!!, - observed = observationData.scenarios.joinToString { it.title }, - monitoring = observationData.scenarios.map { it.monitoring.rating }.average(), - conservatism = observationData.scenarios.map { it.conservatism.rating }.average(), - controlProcedural = observationData.scenarios.map { it.controlProcedural.rating }.average(), - control = observationData.scenarios.map { it.control.rating }.average(), - teamworkCommunications = observationData.scenarios.map { it.teamworkCommunications.rating }.average(), - teamworkLeadership = observationData.scenarios.map { it.teamworkLeadership.rating }.average(), - teamworkWorkload = observationData.scenarios.map { it.teamworkWorkload.rating }.average(), - knowledge = observationData.scenarios.map { it.knowledge.rating }.average(), - scenarios = observationData.scenarios, - tutors = GroupSessionManager.tutors!!, - person = personRepository.findFirstByNameLike(observationData.person.toUpperCase()) ?: personRepository.save(Person(name = observationData.person.toUpperCase())) - ) - observation = saveObservation(observation) - GroupSessionManager.addObservation(observation) - } - } - - @GetMapping("/address") - fun getConnectionDetails(): Map { - var ipv4: String? = null - var retryCount = 0 - while (ipv4 == null && retryCount < 3) { - ipv4 = NetworkInterface.getNetworkInterfaces().asSequence() - .filter { !it.isLoopback }.map { x -> x.inetAddresses.asSequence() - .filter { it is Inet4Address }.map { it.hostAddress } }.flatten().firstOrNull() - retryCount++ - Thread.sleep(1_000) // Sleep for 1 second - } - if (ipv4 != null) - return mapOf("ip" to ipv4, "port" to environment["local.server.port"]) - return mapOf("error" to "Could not determine IP Address") - } -} - -data class AverageData( - val monitoring: Double, - val controlProcedural: Double, - val control: Double, - val conservatism: Double, - val teamworkCommunications: Double, - val teamworkLeadership: Double, - val teamworkWorkload: Double, - val knowledge: Double, - val date: LocalDate -) \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationDatabaseApplicationTests.kt b/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationDatabaseApplicationTests.kt index 291496b..c6a0dad 100644 --- a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationDatabaseApplicationTests.kt +++ b/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationDatabaseApplicationTests.kt @@ -6,16 +6,21 @@ import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit4.SpringRunner +import uk.co.neviyn.observationdatabase.controller.GroupSessionController +import uk.co.neviyn.observationdatabase.controller.ObservationsController @RunWith(SpringRunner::class) @SpringBootTest class ObservationDatabaseApplicationTests { @Autowired - lateinit var controller: Controller + lateinit var observationsController: ObservationsController + @Autowired + lateinit var groupSessionController: GroupSessionController @Test fun contextLoads() { - assertNotNull(controller) + assertNotNull(observationsController) + assertNotNull(groupSessionController) } } diff --git a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ControllerTest.kt b/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationsControllerTest.kt similarity index 97% rename from backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ControllerTest.kt rename to backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationsControllerTest.kt index d20a4a5..cba18de 100644 --- a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ControllerTest.kt +++ b/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationsControllerTest.kt @@ -7,13 +7,14 @@ import org.junit.runner.RunWith import org.mockito.* import org.mockito.Mockito.* import org.mockito.junit.MockitoJUnitRunner +import uk.co.neviyn.observationdatabase.controller.ObservationsController import java.util.* @RunWith(MockitoJUnitRunner::class) -class ControllerTest { +class ObservationsControllerTest { @InjectMocks - lateinit var controller: Controller + lateinit var controller: ObservationsController @Mock lateinit var siteRepository: SiteRepository