From d8288611c2488b17b19cbfde094b71e56739292e Mon Sep 17 00:00:00 2001 From: Nathan Cannon Date: Thu, 17 Sep 2020 13:52:26 +0100 Subject: [PATCH] Removed unused "Group Session" functionality --- backend/pom.xml | 19 +- .../controller/GroupSessionController.kt | 218 --------- .../ObservationDatabaseApplicationTests.kt | 6 - .../controller/GroupSessionControllerTest.kt | 167 ------- frontend/src/router.js | 13 - frontend/src/views/GroupSession.vue | 375 -------------- frontend/src/views/GroupSessionInput.vue | 462 ------------------ frontend/src/views/Home.vue | 9 - 8 files changed, 12 insertions(+), 1257 deletions(-) delete mode 100644 backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt delete mode 100644 backend/src/test/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionControllerTest.kt delete mode 100644 frontend/src/views/GroupSession.vue delete mode 100644 frontend/src/views/GroupSessionInput.vue diff --git a/backend/pom.xml b/backend/pom.xml index e1e2154..72165cc 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -21,9 +21,9 @@ UTF-8 UTF-8 1.8 - 1.3.40 + 1.4.10 1.8 - 2.1.6.RELEASE + 2.3.3.RELEASE @@ -103,11 +103,12 @@ joda-time joda-time + 2.10.6 com.fasterxml.jackson.datatype jackson-datatype-joda - 2.9.9 + 2.11.2 org.jadira.usertype @@ -117,19 +118,23 @@ com.github.ben-manes.caffeine caffeine - 2.6.2 + 2.8.5 org.simplejavamail simple-java-mail - 6.0.3 + 6.4.3 org.jetbrains.kotlinx kotlinx-coroutines-core - 1.3.0-M2 + 1.3.9 + + + javax.validation + validation-api + 2.0.1.Final - org.springframework.boot spring-boot-starter-test 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 deleted file mode 100644 index a41fbe9..0000000 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt +++ /dev/null @@ -1,218 +0,0 @@ -package uk.co.neviyn.observationdatabase.controller - -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -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.http.HttpStatus -import org.springframework.messaging.simp.SimpMessagingTemplate -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.server.ResponseStatusException -import uk.co.neviyn.observationdatabase.Email -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.ObservationRepository -import uk.co.neviyn.observationdatabase.SiteRepository -import uk.co.neviyn.observationdatabase.TutorRepository -import java.net.Inet4Address -import java.net.NetworkInterface -import javax.validation.Valid - -@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 observationRepository: ObservationRepository - @Autowired - lateinit var websocketMessenger: SimpMessagingTemplate - @Autowired - lateinit var mailer: Email - - /** - * Start a new Group Observation session - */ - @PostMapping("/start") - fun startGroupObservation(@Valid @RequestBody 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.") - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Site required") - } - if (tutors.isEmpty() || tutors.size != initData.tutors.size) { - logger.info("Attempted to add Observation without a tutor") - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Tutor required") - } - val sessionId = GroupSessionManager.startNewSession(site.get(), tutors, initData.type, initData.scenarioTitles) - return getConnectionDetails().plus("id" to sessionId.toString()) - } - - /** - * Reconnect to the last Group Observation session - */ - @GetMapping("/recover") - fun reconnectToGroupObservation(): Map { - if (GroupSessionManager.isValid()) { - logger.info("Previous group observation requested, current id ${GroupSessionManager.sessionId}") - return getConnectionDetails().plus(mapOf("id" to GroupSessionManager.sessionId.toString(), "scenarios" to GroupSessionManager.asScenarioView())) - } - logger.info("Tried to recover a session but no session is active") - throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "No group session is currently running") - } - - /** - * Check whether there is an active group session with [id] - */ - @GetMapping("/valid/{id}") - fun checkGroupObservationValidityById(@PathVariable id: Int): Map { - if (GroupSessionManager.isValid(id)) { - return mapOf("titles" to GroupSessionManager.scenarioTitles!!) - } - if (GroupSessionManager.isValid()) { - logger.warn("Group observation requested with id $id but id is currently ${GroupSessionManager.sessionId})") - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Session ID incorrect") - } - logger.warn("Group observation requested with id $id but there is no valid session") - throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "No group session is currently running") - } - - /** - * Check whether there is any valid group session - */ - @GetMapping("/valid") - fun checkGroupObservationValidity(): Boolean { - return GroupSessionManager.isValid() - } - - /** - * Save an Observation to the database - */ - fun saveObservation(observation: Observation): Observation { - logger.debug("Saving new Observation to database") - val committedObservation = observationRepository.save(observation) - logger.debug("Adding Observation data to Tutor records") - committedObservation.tutors.forEach { - it.observations.add(committedObservation) - tutorRepository.save(it) - } - logger.debug("Observation addition completed") - return committedObservation - } - - /** - * Get the current observation data for a user with [name] in the current session. - */ - @GetMapping("/participant/{name}") - fun getParticipantData(@PathVariable name: String): GroupObservation { - if (GroupSessionManager.participantExistsInSession(name)) - return GroupSessionManager.getObservationDataForParticipant(name)!! - throw ResponseStatusException(HttpStatus.NOT_FOUND, "No participant with the name:'$name'") - } - - /** - * Submit an observation to be added to the session state and actual database - */ - @PostMapping("/submit") - fun addGroupObservation(@Valid @RequestBody observationData: GroupObservation) { - val titles = observationData.scenarios.map { it.title } - if (GroupSessionManager.scenarioTitles!!.size != titles.size || !GroupSessionManager.scenarioTitles!!.containsAll(titles)) { - logger.warn("Received scenario data but titles did not match\nInput:$titles\nRequired:${GroupSessionManager.scenarioTitles}") - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Submission data contains non-matching title(s)") - } - GroupSessionManager.updateObservationData(observationData) - websocketMessenger.convertAndSend("/ws/scenarios", mapOf("scenarios" to GroupSessionManager.asScenarioView())) - } - - @PostMapping("/complete") - fun pushObservationsToDatabase(): Map { - if (GroupSessionManager.isValid() && GroupSessionManager.dataComplete()) { - logger.info("Completing session ${GroupSessionManager.sessionId}") - val tutors = tutorRepository.findAllById(GroupSessionManager.tutors!!).toSet() - val observations = mutableListOf() - GroupSessionManager.observations.values.forEach { x -> - val observation = Observation( - site = GroupSessionManager.site!!, - date = LocalDate.now(), - type = GroupSessionManager.trainingType!!, - observed = x.scenarios.joinToString { it.title }, - monitoring = x.scenarios.map { it.monitoringRating }.average(), - conservatism = x.scenarios.map { it.conservatismRating }.average(), - controlProcedural = x.scenarios.map { it.controlProceduralRating }.average(), - control = x.scenarios.map { it.controlRating }.average(), - teamworkCommunications = x.scenarios.map { it.teamworkCommunicationsRating }.average(), - teamworkLeadership = x.scenarios.map { it.teamworkLeadershipRating }.average(), - teamworkWorkload = x.scenarios.map { it.teamworkWorkloadRating }.average(), - knowledge = x.scenarios.map { it.knowledgeRating }.average(), - scenarios = x.scenarios, - tutors = tutors, - person = x.person.toUpperCase() - ) - saveObservation(observation) - observations.add(observation) - } - GroupSessionManager.invalidate() - if (this::environment.isInitialized && environment.getProperty("smtp.autosendoncomplete")!!.toBoolean()) - GlobalScope.launch { - if (::mailer.isInitialized) - mailer.sendObservationData(observations) - else - logger.error("Mailer has not been initialized.") - } - else - logger.debug("Environment or Mailer is unavailable") - websocketMessenger.convertAndSend("/ws/status", mapOf("status" to "complete")) - return mapOf("success" to "The submission was successfully completed.") - } else if (!GroupSessionManager.dataComplete()) { - logger.info("Tried to complete a session whilst data was incomplete\n${GroupSessionManager.observations}") - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Data is incomplete") - } - throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "No valid session") - } - - /** - * Get details needed to connect to this group session on the LAN - */ - @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 - } - return if (ipv4 != null && this::environment.isInitialized) - mapOf("ip" to ipv4, "port" to environment["local.server.port"]!!) - else if (ipv4 == null) { - logger.error("IP Address could not be determined") - mapOf("error" to "Could not determine IP Address") - } else { - logger.error("Port could not be determined, environment not initialised") - mapOf("error" to "Could not determine port") - } - } -} 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 56badab..aab7451 100644 --- a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationDatabaseApplicationTests.kt +++ b/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/ObservationDatabaseApplicationTests.kt @@ -1,13 +1,11 @@ package uk.co.neviyn.observationdatabase -import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertNotNull import org.junit.Test 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) @@ -16,13 +14,9 @@ class ObservationDatabaseApplicationTests { @Autowired lateinit var observationsController: ObservationsController - @Autowired - lateinit var groupSessionController: GroupSessionController @Test fun contextLoads() { assertNotNull(observationsController) - assertNotNull(groupSessionController) - assertFalse(GroupSessionManager.isValid()) } } diff --git a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionControllerTest.kt b/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionControllerTest.kt deleted file mode 100644 index f86f3e2..0000000 --- a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionControllerTest.kt +++ /dev/null @@ -1,167 +0,0 @@ -package uk.co.neviyn.observationdatabase.controller - -import org.joda.time.LocalDate -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnitRunner -import org.springframework.messaging.simp.SimpMessagingTemplate -import org.springframework.web.server.ResponseStatusException -import uk.co.neviyn.observationdatabase.Email -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.ObservationRepository -import uk.co.neviyn.observationdatabase.Scenario -import uk.co.neviyn.observationdatabase.Site -import uk.co.neviyn.observationdatabase.SiteRepository -import uk.co.neviyn.observationdatabase.TrainingType -import uk.co.neviyn.observationdatabase.Tutor -import uk.co.neviyn.observationdatabase.TutorRepository -import java.util.Optional - -@RunWith(MockitoJUnitRunner::class) -class GroupSessionControllerTest { - - @InjectMocks - lateinit var controller: GroupSessionController - - @Mock - lateinit var websocketMessenger: SimpMessagingTemplate - @Mock - lateinit var siteRepository: SiteRepository - @Mock - lateinit var tutorRepository: TutorRepository - @Mock - lateinit var observationRepository: ObservationRepository - @Mock - lateinit var mailer: Email - - @After - fun tearDown() { - GroupSessionManager.invalidate() - } - - @Test - fun testStartSession() { - val site = Site(1, "Test site") - Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) - Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) - controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) - assertTrue(GroupSessionManager.isValid()) - } - - @Test(expected = ResponseStatusException::class) - fun testStartSession_NoSite() { - controller.startGroupObservation(GroupObservationInit(0, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) - } - - @Test(expected = ResponseStatusException::class) - fun testStartSession_NoTutor() { - val site = Site(1, "Test site") - Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) - controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(0), listOf("Sample title"))) - } - - @Test - fun testRecoverSession() { - val site = Site(1, "Test site") - Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) - Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) - controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) - assertNotNull(controller.reconnectToGroupObservation()) - } - - @Test(expected = ResponseStatusException::class) - fun testRecoverSession_NoActiveSession() { - controller.reconnectToGroupObservation() - } - - @Test - fun testCompleteSession() { - val site = Site(1, "Test site") - val tutor = Tutor(1, "Mr X", site) - Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) - Mockito.doReturn(listOf(tutor)).`when`(tutorRepository).findAllById(listOf(1)) - val person = "A Student" - val scenario = Scenario(0, "Sample title", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "") - Mockito.doReturn(Observation(1, site, LocalDate.now(), TrainingType.INITIAL, "Sample title", 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, listOf(scenario), setOf(tutor), person)).`when`(observationRepository).save(any()) - controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) - controller.addGroupObservation(GroupObservation("A Student", listOf(scenario))) - controller.pushObservationsToDatabase() - verify(observationRepository, times(1)).save(any()) - verify(websocketMessenger, times(1)).convertAndSend("/ws/scenarios", mapOf("scenarios" to mapOf("Sample title" to listOf(scenario.copy(title = "A Student"))))) - assertEquals(1, tutor.observations.size) - } - - @Test(expected = ResponseStatusException::class) - fun testCompleteSession_NoObservationData() { - val site = Site(1, "Test site") - Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) - Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) - controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) - controller.pushObservationsToDatabase() - } - - @Test(expected = ResponseStatusException::class) - fun testCompleteSession_PartialObservationData() { - val site = Site(1, "Test site") - Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) - Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) - controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) - controller.addGroupObservation(GroupObservation("A Student", listOf(Scenario(0, "Sample title", 0, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "")))) - verify(websocketMessenger, times(1)).convertAndSend("/ws/scenarios", mapOf("scenarios" to mapOf("Sample title" to listOf(Scenario(0, "A Student", 0, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", ""))))) - controller.pushObservationsToDatabase() - } - - @Test(expected = ResponseStatusException::class) - fun testSubmit_NonMatchingTitles() { - val site = Site(1, "Test site") - Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) - Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) - controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) - controller.addGroupObservation(GroupObservation("A Student", listOf(Scenario(0, "Different Title", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "")))) - } - - @Test - fun testSubmit_PartialData() { - val site = Site(1, "Test site") - Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) - Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) - controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) - controller.addGroupObservation(GroupObservation("A Student", listOf(Scenario(0, "Sample title", 0, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "")))) - assertEquals(1, GroupSessionManager.observations.size) - assertEquals("A Student", GroupSessionManager.observations.keys.first()) - assertEquals("A Student", GroupSessionManager.observations.values.first().person) - } - - @Test(expected = ResponseStatusException::class) - fun testGetParticipantData_NoParticipant() { - controller.getParticipantData("Someone") - } - - @Test(expected = ResponseStatusException::class) - fun testGetParticipantData_WrongName() { - val testData = GroupObservation("A Student", listOf(Scenario(0, "Sample title", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", ""))) - GroupSessionManager.observations["A Student"] = testData - controller.getParticipantData("Another Student") - } - - @Test - fun testGetParticipantData() { - val testData = GroupObservation("A Student", listOf(Scenario(0, "Sample title", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", ""))) - GroupSessionManager.observations["A Student"] = testData - val output = controller.getParticipantData("A Student") - assertEquals(testData, output) - } -} diff --git a/frontend/src/router.js b/frontend/src/router.js index cbd74ce..755e63a 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -9,8 +9,6 @@ const ViewObservations = () => import("./views/ViewObservations.vue"); const ObservationComplete = () => import("./views/ObservationComplete.vue"); const DBError = () => import("./views/DatabaseUnavailable.vue"); const About = () => import("./views/About.vue"); -const GroupSession = () => import("./views/GroupSession.vue"); -const GroupSessionInput = () => import("./views/GroupSessionInput.vue"); Vue.use(Router); @@ -60,17 +58,6 @@ export default new Router({ path: "/about", name: "about", component: About - }, - { - path: "/groupsession", - name: "groupsession", - component: GroupSession - }, - { - path: "/groupsession/:id", - name: "groupsessioninput", - component: GroupSessionInput, - props: true } ] }); diff --git a/frontend/src/views/GroupSession.vue b/frontend/src/views/GroupSession.vue deleted file mode 100644 index 05b7a26..0000000 --- a/frontend/src/views/GroupSession.vue +++ /dev/null @@ -1,375 +0,0 @@ - - diff --git a/frontend/src/views/GroupSessionInput.vue b/frontend/src/views/GroupSessionInput.vue deleted file mode 100644 index be0a676..0000000 --- a/frontend/src/views/GroupSessionInput.vue +++ /dev/null @@ -1,462 +0,0 @@ - - - diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index 4997dcd..309c644 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -27,15 +27,6 @@ > -