newUI #1
@ -21,9 +21,9 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<kotlin.version>1.3.40</kotlin.version>
|
<kotlin.version>1.4.10</kotlin.version>
|
||||||
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
|
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
|
||||||
<spring.version>2.1.6.RELEASE</spring.version>
|
<spring.version>2.3.3.RELEASE</spring.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@ -103,11 +103,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>joda-time</groupId>
|
<groupId>joda-time</groupId>
|
||||||
<artifactId>joda-time</artifactId>
|
<artifactId>joda-time</artifactId>
|
||||||
|
<version>2.10.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
<artifactId>jackson-datatype-joda</artifactId>
|
<artifactId>jackson-datatype-joda</artifactId>
|
||||||
<version>2.9.9</version>
|
<version>2.11.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jadira.usertype</groupId>
|
<groupId>org.jadira.usertype</groupId>
|
||||||
@ -117,19 +118,23 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
<artifactId>caffeine</artifactId>
|
<artifactId>caffeine</artifactId>
|
||||||
<version>2.6.2</version>
|
<version>2.8.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.simplejavamail</groupId>
|
<groupId>org.simplejavamail</groupId>
|
||||||
<artifactId>simple-java-mail</artifactId>
|
<artifactId>simple-java-mail</artifactId>
|
||||||
<version>6.0.3</version>
|
<version>6.4.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlinx</groupId>
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
<artifactId>kotlinx-coroutines-core</artifactId>
|
<artifactId>kotlinx-coroutines-core</artifactId>
|
||||||
<version>1.3.0-M2</version>
|
<version>1.3.9</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.validation</groupId>
|
||||||
|
<artifactId>validation-api</artifactId>
|
||||||
|
<version>2.0.1.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
@ -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<String, String> {
|
|
||||||
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<String, Any> {
|
|
||||||
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<String, Any> {
|
|
||||||
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<String, String> {
|
|
||||||
if (GroupSessionManager.isValid() && GroupSessionManager.dataComplete()) {
|
|
||||||
logger.info("Completing session ${GroupSessionManager.sessionId}")
|
|
||||||
val tutors = tutorRepository.findAllById(GroupSessionManager.tutors!!).toSet()
|
|
||||||
val observations = mutableListOf<Observation>()
|
|
||||||
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<String, String> {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,11 @@
|
|||||||
package uk.co.neviyn.observationdatabase
|
package uk.co.neviyn.observationdatabase
|
||||||
|
|
||||||
import junit.framework.TestCase.assertFalse
|
|
||||||
import junit.framework.TestCase.assertNotNull
|
import junit.framework.TestCase.assertNotNull
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
import org.springframework.test.context.junit4.SpringRunner
|
import org.springframework.test.context.junit4.SpringRunner
|
||||||
import uk.co.neviyn.observationdatabase.controller.GroupSessionController
|
|
||||||
import uk.co.neviyn.observationdatabase.controller.ObservationsController
|
import uk.co.neviyn.observationdatabase.controller.ObservationsController
|
||||||
|
|
||||||
@RunWith(SpringRunner::class)
|
@RunWith(SpringRunner::class)
|
||||||
@ -16,13 +14,9 @@ class ObservationDatabaseApplicationTests {
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
lateinit var observationsController: ObservationsController
|
lateinit var observationsController: ObservationsController
|
||||||
@Autowired
|
|
||||||
lateinit var groupSessionController: GroupSessionController
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun contextLoads() {
|
fun contextLoads() {
|
||||||
assertNotNull(observationsController)
|
assertNotNull(observationsController)
|
||||||
assertNotNull(groupSessionController)
|
|
||||||
assertFalse(GroupSessionManager.isValid())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,8 +9,6 @@ const ViewObservations = () => import("./views/ViewObservations.vue");
|
|||||||
const ObservationComplete = () => import("./views/ObservationComplete.vue");
|
const ObservationComplete = () => import("./views/ObservationComplete.vue");
|
||||||
const DBError = () => import("./views/DatabaseUnavailable.vue");
|
const DBError = () => import("./views/DatabaseUnavailable.vue");
|
||||||
const About = () => import("./views/About.vue");
|
const About = () => import("./views/About.vue");
|
||||||
const GroupSession = () => import("./views/GroupSession.vue");
|
|
||||||
const GroupSessionInput = () => import("./views/GroupSessionInput.vue");
|
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
@ -60,17 +58,6 @@ export default new Router({
|
|||||||
path: "/about",
|
path: "/about",
|
||||||
name: "about",
|
name: "about",
|
||||||
component: About
|
component: About
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/groupsession",
|
|
||||||
name: "groupsession",
|
|
||||||
component: GroupSession
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/groupsession/:id",
|
|
||||||
name: "groupsessioninput",
|
|
||||||
component: GroupSessionInput,
|
|
||||||
props: true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -1,375 +0,0 @@
|
|||||||
<template>
|
|
||||||
<b-container fluid>
|
|
||||||
<b-row v-if="active">
|
|
||||||
<b-col cols="12" md="3">
|
|
||||||
<vue-qrcode v-model="qrdata" :options="{ width: 250 }"></vue-qrcode>
|
|
||||||
<p>
|
|
||||||
Scan the code or navigate to
|
|
||||||
<br />
|
|
||||||
{{ qrdata }}
|
|
||||||
</p>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" md="9">
|
|
||||||
<b-card-group v-for="(item, index) in data" v-bind:key="index">
|
|
||||||
<b-card
|
|
||||||
border-variant="secondary"
|
|
||||||
header-border-variant="secondary"
|
|
||||||
:header="index"
|
|
||||||
>
|
|
||||||
<b-card-text>
|
|
||||||
<b-table :fields="tableFields" :items="item"></b-table>
|
|
||||||
</b-card-text>
|
|
||||||
</b-card>
|
|
||||||
</b-card-group>
|
|
||||||
</b-col>
|
|
||||||
<b-col>
|
|
||||||
<b-button size="lg" variant="primary" v-on:click="showCompletionModal()"
|
|
||||||
>Finish Session</b-button
|
|
||||||
>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row v-else-if="complete">
|
|
||||||
<b-col>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h2>Session Complete</h2>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<p>
|
|
||||||
Observation data for this session is now saved in the database.
|
|
||||||
</p>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-button
|
|
||||||
size="lg"
|
|
||||||
variant="primary"
|
|
||||||
v-on:click="window.location.reload()"
|
|
||||||
>Start a New Session</b-button
|
|
||||||
>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row v-else>
|
|
||||||
<b-col>
|
|
||||||
<b-form id="submission-form" novalidate @submit="onSubmit">
|
|
||||||
<b-row align-h="center">
|
|
||||||
<b-col>
|
|
||||||
<b-form-group label="Site">
|
|
||||||
<b-form-select
|
|
||||||
v-model="site"
|
|
||||||
:options="siteOptions"
|
|
||||||
style="text-align:center;"
|
|
||||||
required
|
|
||||||
></b-form-select>
|
|
||||||
</b-form-group>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row align-h="center">
|
|
||||||
<b-col>
|
|
||||||
<b-form-group label="Type">
|
|
||||||
<b-form-select
|
|
||||||
v-model="type"
|
|
||||||
style="text-align:center;"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option :value="null">Select a training type</option>
|
|
||||||
<option value="INITIAL">INITIAL</option>
|
|
||||||
<option value="CONTINUING">CONTINUING</option>
|
|
||||||
</b-form-select>
|
|
||||||
</b-form-group>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row align-h="center">
|
|
||||||
<b-col>
|
|
||||||
<b-form-group label="Tutor(s)">
|
|
||||||
<p v-if="site == null">Select a site first.</p>
|
|
||||||
<b-form-checkbox-group
|
|
||||||
v-model="tutors"
|
|
||||||
:options="tutorOptions"
|
|
||||||
></b-form-checkbox-group>
|
|
||||||
</b-form-group>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row
|
|
||||||
v-for="(item, index) in scenarioTitles"
|
|
||||||
v-bind:key="index"
|
|
||||||
class="border bottom-buffer"
|
|
||||||
fluid
|
|
||||||
>
|
|
||||||
<b-col>
|
|
||||||
<b-form-input
|
|
||||||
v-model="item.data"
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter scenario description."
|
|
||||||
></b-form-input>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="1">
|
|
||||||
<b-button
|
|
||||||
v-on:click="scenarioTitles.splice(index, 1)"
|
|
||||||
variant="danger"
|
|
||||||
>
|
|
||||||
<b>Delete</b>
|
|
||||||
</b-button>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row align-h="center">
|
|
||||||
<b-col>
|
|
||||||
<b-button
|
|
||||||
v-on:click="scenarioTitles.push({ data: '' })"
|
|
||||||
size="lg"
|
|
||||||
variant="primary"
|
|
||||||
>Add Another Scenario</b-button
|
|
||||||
>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<br />
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row align-h="center">
|
|
||||||
<b-col>
|
|
||||||
<b-button type="submit" size="lg" variant="primary"
|
|
||||||
>Start</b-button
|
|
||||||
>
|
|
||||||
</b-col>
|
|
||||||
<b-col>
|
|
||||||
<b-button
|
|
||||||
size="lg"
|
|
||||||
variant="secondary"
|
|
||||||
v-on:click="connectToPrevious()"
|
|
||||||
>Connect to Previous Session</b-button
|
|
||||||
>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-form>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-modal
|
|
||||||
id="submissionModal"
|
|
||||||
ref="submissionModal"
|
|
||||||
title="Enter password to confirm session start"
|
|
||||||
@ok="handleOk"
|
|
||||||
@shown="clearPassword"
|
|
||||||
>
|
|
||||||
<form @submit.stop.prevent="handleSubmit">
|
|
||||||
<b-form-input
|
|
||||||
type="password"
|
|
||||||
placeholder="Enter password"
|
|
||||||
v-model="submitPassword"
|
|
||||||
></b-form-input>
|
|
||||||
</form>
|
|
||||||
</b-modal>
|
|
||||||
<b-modal
|
|
||||||
id="completionModal"
|
|
||||||
ref="completionModal"
|
|
||||||
title="Enter password to confirm submission"
|
|
||||||
@ok="handleSubmitComplete"
|
|
||||||
@shown="clearPassword"
|
|
||||||
>
|
|
||||||
<form @submit.stop.prevent="handleSubmit">
|
|
||||||
<b-form-input
|
|
||||||
type="password"
|
|
||||||
placeholder="Enter password"
|
|
||||||
v-model="submitPassword"
|
|
||||||
></b-form-input>
|
|
||||||
</form>
|
|
||||||
</b-modal>
|
|
||||||
</b-container>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import Vue from "vue";
|
|
||||||
import VueQrcode from "@chenfengyuan/vue-qrcode";
|
|
||||||
import webstomp from "webstomp-client";
|
|
||||||
import SockJS from "sockjs-client";
|
|
||||||
export default {
|
|
||||||
name: "groupsession",
|
|
||||||
title: "Group Session",
|
|
||||||
components: { VueQrcode },
|
|
||||||
data: function() {
|
|
||||||
return {
|
|
||||||
active: false,
|
|
||||||
stompclient: null,
|
|
||||||
complete: false,
|
|
||||||
qrdata: "N/A",
|
|
||||||
data: [{ person: { name: "No data yet received." } }],
|
|
||||||
site: null,
|
|
||||||
tutors: null,
|
|
||||||
siteOptions: [],
|
|
||||||
tutorOptions: [],
|
|
||||||
scenarioTitles: [{ data: "" }, { data: "" }, { data: "" }],
|
|
||||||
type: null,
|
|
||||||
submitPassword: null,
|
|
||||||
tableFields: [
|
|
||||||
{ key: "title", label: "Name" },
|
|
||||||
{ key: "monitoring.rating", label: "Monitoring" },
|
|
||||||
{ key: "controlProcedural.rating", label: "Control Procedural" },
|
|
||||||
{ key: "control.rating", label: "Control" },
|
|
||||||
{ key: "conservatism.rating", label: "Conservatism" },
|
|
||||||
{
|
|
||||||
key: "teamworkCommunications.rating",
|
|
||||||
label: "Teamwork Communications"
|
|
||||||
},
|
|
||||||
{ key: "teamworkLeadership.rating", label: "Teamwork Leadership" },
|
|
||||||
{ key: "teamworkWorkload.rating", label: "Teamwork Workload" },
|
|
||||||
{ key: "knowledge.rating", label: "Knowledge" }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
site: function() {
|
|
||||||
this.tutorOptions = [];
|
|
||||||
this.getTutors();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
Vue.axios
|
|
||||||
.get("/site")
|
|
||||||
.then(response => {
|
|
||||||
this.siteOptions = response.data;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (error.response.status === 404) {
|
|
||||||
//this.$router.push("/dberror");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
startSession: function() {
|
|
||||||
var self = this;
|
|
||||||
let axiosConfig = {
|
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: this.submitPassword
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Vue.axios
|
|
||||||
.post(
|
|
||||||
"/grpob/start",
|
|
||||||
{
|
|
||||||
site: self.site,
|
|
||||||
tutors: self.tutors,
|
|
||||||
scenarioTitles: self.scenarioTitles.map(x => x.data),
|
|
||||||
type: self.type
|
|
||||||
},
|
|
||||||
axiosConfig
|
|
||||||
)
|
|
||||||
.then(function(response) {
|
|
||||||
if (!("error" in response.data)) {
|
|
||||||
self.setupSession(response.data);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
self.active = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
connectToPrevious: function() {
|
|
||||||
var self = this;
|
|
||||||
Vue.axios
|
|
||||||
.get("/grpob/recover")
|
|
||||||
.then(function(response) {
|
|
||||||
if (!("error" in response.data)) {
|
|
||||||
self.setupSession(response.data);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
if (error.response.status === 404) {
|
|
||||||
this.$router.push("/dberror");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setupSession: function(rdata) {
|
|
||||||
var self = this;
|
|
||||||
self.qrdata = `http://${rdata.ip}:${rdata.port}/#/groupsession/${
|
|
||||||
rdata.id
|
|
||||||
}`;
|
|
||||||
self.active = true;
|
|
||||||
self.stompclient = webstomp.over(
|
|
||||||
new SockJS("http://127.0.0.1:8080/websocket", {
|
|
||||||
heartbeat: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
self.stompclient.connect([], function() {
|
|
||||||
self.stompclient.subscribe("/ws/scenarios", function(incomingData) {
|
|
||||||
self.data = JSON.parse(incomingData.body).scenarios;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
self.$refs.submissionModal.hide();
|
|
||||||
self.clearPassword();
|
|
||||||
},
|
|
||||||
getTutors: function() {
|
|
||||||
if (this.site != null) {
|
|
||||||
Vue.axios
|
|
||||||
.get("/site/" + this.site + "/tutors")
|
|
||||||
.then(response => {
|
|
||||||
this.tutorOptions = response.data;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (error.response.status === 404) {
|
|
||||||
this.$router.push("/dberror");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSubmit: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
var form = document.getElementById("submission-form");
|
|
||||||
if (form.checkValidity()) {
|
|
||||||
this.showModal();
|
|
||||||
}
|
|
||||||
form.classList.add("was-validated");
|
|
||||||
},
|
|
||||||
showModal() {
|
|
||||||
this.$refs.submissionModal.show();
|
|
||||||
},
|
|
||||||
showCompletionModal() {
|
|
||||||
this.$refs.completionModal.show();
|
|
||||||
},
|
|
||||||
clearPassword() {
|
|
||||||
this.submitPassword = null;
|
|
||||||
},
|
|
||||||
handleOk(evt) {
|
|
||||||
// Prevent modal from closing
|
|
||||||
evt.preventDefault();
|
|
||||||
if (this.submitPassword !== null) {
|
|
||||||
this.startSession();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSubmitComplete(evt) {
|
|
||||||
// Prevent modal from closing
|
|
||||||
var self = this;
|
|
||||||
evt.preventDefault();
|
|
||||||
if (this.submitPassword !== null) {
|
|
||||||
let axiosConfig = {
|
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: this.submitPassword
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Vue.axios
|
|
||||||
.post("/grpob/complete", {}, axiosConfig)
|
|
||||||
.then(function(response) {
|
|
||||||
if ("success" in response.data) {
|
|
||||||
self.complete = true;
|
|
||||||
self.active = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (this.stompclient != null) {
|
|
||||||
this.stompclient.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,462 +0,0 @@
|
|||||||
<template>
|
|
||||||
<b-container fluid>
|
|
||||||
<b-container v-if="error !== null">
|
|
||||||
<p>An error has occurred.</p>
|
|
||||||
<p>{{ error.status }}</p>
|
|
||||||
<p>{{ error.data }}</p>
|
|
||||||
</b-container>
|
|
||||||
<b-container v-else-if="complete">
|
|
||||||
<h2>Submission Complete</h2>
|
|
||||||
<p>Thank you.</p>
|
|
||||||
<p>
|
|
||||||
This observation session is now closed and your data submitted to the
|
|
||||||
database.
|
|
||||||
</p>
|
|
||||||
</b-container>
|
|
||||||
<b-container v-else-if="!valid">
|
|
||||||
<p>Getting session data from server</p>
|
|
||||||
</b-container>
|
|
||||||
<b-container v-else-if="scenarios.length === 0">
|
|
||||||
<p>
|
|
||||||
No scenarios defined for this session, please setup a new group session
|
|
||||||
</p>
|
|
||||||
</b-container>
|
|
||||||
<b-container v-else fluid>
|
|
||||||
<b-form>
|
|
||||||
<b-row align-h="center">
|
|
||||||
<b-col>
|
|
||||||
<b-form-group label="Participant">
|
|
||||||
<b-form-input
|
|
||||||
v-model="participant"
|
|
||||||
type="text"
|
|
||||||
style="text-align:center;"
|
|
||||||
placeholder="Enter your name (Initial and Surname)"
|
|
||||||
required
|
|
||||||
></b-form-input>
|
|
||||||
</b-form-group>
|
|
||||||
</b-col>
|
|
||||||
<b-col>
|
|
||||||
<b-button v-on:click="attemptReconnect()">Reconnect </b-button>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row
|
|
||||||
v-for="(item, index) in scenarios"
|
|
||||||
v-bind:key="index"
|
|
||||||
class="border bottom-buffer"
|
|
||||||
>
|
|
||||||
<b-col>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-input
|
|
||||||
v-model="item.title"
|
|
||||||
type="text"
|
|
||||||
readonly
|
|
||||||
></b-form-input>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col cols="12" md="6" xl="3" class="border">
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h5>Monitoring</h5>
|
|
||||||
<score-selector
|
|
||||||
:score-value="item.monitoringRating"
|
|
||||||
v-on:newselection="
|
|
||||||
item.monitoringRating = $event;
|
|
||||||
actuallySubmit();
|
|
||||||
"
|
|
||||||
></score-selector>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.monitoringStrengths"
|
|
||||||
placeholder="Strengths"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="strength"
|
|
||||||
></b-form-textarea>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.monitoringImprovements"
|
|
||||||
placeholder="AFIs"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="afi"
|
|
||||||
></b-form-textarea>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" md="6" xl="3" class="border">
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h5>Control Procedural</h5>
|
|
||||||
<score-selector
|
|
||||||
:score-value="item.controlProceduralRating"
|
|
||||||
v-on:newselection="
|
|
||||||
item.controlProceduralRating = $event;
|
|
||||||
actuallySubmit();
|
|
||||||
"
|
|
||||||
></score-selector>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.controlProceduralStrengths"
|
|
||||||
placeholder="Strengths"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="strength"
|
|
||||||
></b-form-textarea>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.controlProceduralImprovements"
|
|
||||||
placeholder="AFIs"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="afi"
|
|
||||||
></b-form-textarea>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" md="6" xl="3" class="border">
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h5>Control</h5>
|
|
||||||
<score-selector
|
|
||||||
:score-value="item.controlRating"
|
|
||||||
v-on:newselection="
|
|
||||||
item.controlRating = $event;
|
|
||||||
actuallySubmit();
|
|
||||||
"
|
|
||||||
></score-selector>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.controlStrengths"
|
|
||||||
placeholder="Strengths"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="strength"
|
|
||||||
></b-form-textarea>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.controlImprovements"
|
|
||||||
placeholder="AFIs"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="afi"
|
|
||||||
></b-form-textarea>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" md="6" xl="3" class="border">
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h5>Conservatism</h5>
|
|
||||||
<score-selector
|
|
||||||
:score-value="item.conservatismRating"
|
|
||||||
v-on:newselection="
|
|
||||||
item.conservatismRating = $event;
|
|
||||||
actuallySubmit();
|
|
||||||
"
|
|
||||||
></score-selector>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.conservatismStrengths"
|
|
||||||
placeholder="Strengths"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="strength"
|
|
||||||
></b-form-textarea>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.conservatismImprovements"
|
|
||||||
placeholder="AFIs"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="afi"
|
|
||||||
></b-form-textarea>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" md="6" xl="3" class="border">
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h5>Teamwork Communications</h5>
|
|
||||||
<score-selector
|
|
||||||
:score-value="item.teamworkCommunicationsRating"
|
|
||||||
v-on:newselection="
|
|
||||||
item.teamworkCommunicationsRating = $event;
|
|
||||||
actuallySubmit();
|
|
||||||
"
|
|
||||||
></score-selector>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.teamworkCommunicationsStrengths"
|
|
||||||
placeholder="Strengths"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="strength"
|
|
||||||
></b-form-textarea>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.teamworkCommunicationsImprovements"
|
|
||||||
placeholder="AFIs"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="afi"
|
|
||||||
></b-form-textarea>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" md="6" xl="3" class="border">
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h5>Teamwork Leadership</h5>
|
|
||||||
<score-selector
|
|
||||||
:score-value="item.teamworkLeadershipRating"
|
|
||||||
v-on:newselection="
|
|
||||||
item.teamworkLeadershipRating = $event;
|
|
||||||
actuallySubmit();
|
|
||||||
"
|
|
||||||
></score-selector>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.teamworkLeadershipStrengths"
|
|
||||||
placeholder="Strengths"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="strength"
|
|
||||||
></b-form-textarea>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.teamworkLeadershipImprovements"
|
|
||||||
placeholder="AFIs"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="afi"
|
|
||||||
></b-form-textarea>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" md="6" xl="3" class="border">
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h5>Teamwork Workload</h5>
|
|
||||||
<score-selector
|
|
||||||
:score-value="item.teamworkWorkloadRating"
|
|
||||||
v-on:newselection="
|
|
||||||
item.teamworkWorkloadRating = $event;
|
|
||||||
actuallySubmit();
|
|
||||||
"
|
|
||||||
></score-selector>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.teamworkWorkloadStrengths"
|
|
||||||
placeholder="Strengths"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="strength"
|
|
||||||
></b-form-textarea>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.teamworkWorkloadImprovements"
|
|
||||||
placeholder="AFIs"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="afi"
|
|
||||||
></b-form-textarea>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" md="6" xl="3" class="border">
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<h5>Knowledge</h5>
|
|
||||||
<score-selector
|
|
||||||
:score-value="item.knowledgeRating"
|
|
||||||
v-on:newselection="
|
|
||||||
item.knowledgeRating = $event;
|
|
||||||
actuallySubmit();
|
|
||||||
"
|
|
||||||
></score-selector>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.knowledgeStrengths"
|
|
||||||
placeholder="Strengths"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="strength"
|
|
||||||
></b-form-textarea>
|
|
||||||
<b-form-textarea
|
|
||||||
v-model="item.knowledgeImprovements"
|
|
||||||
placeholder="AFIs"
|
|
||||||
:rows="1"
|
|
||||||
:max-rows="2"
|
|
||||||
no-resize
|
|
||||||
class="afi"
|
|
||||||
></b-form-textarea>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<br />
|
|
||||||
<b-button variant="primary" v-on:click="actuallySubmit()"
|
|
||||||
>Update</b-button
|
|
||||||
>
|
|
||||||
</b-form>
|
|
||||||
</b-container>
|
|
||||||
</b-container>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import Vue from "vue";
|
|
||||||
import ScoreSelector from "../components/ScoreSelector.vue";
|
|
||||||
import webstomp from "webstomp-client";
|
|
||||||
import SockJS from "sockjs-client";
|
|
||||||
export default {
|
|
||||||
name: "groupsessioninput",
|
|
||||||
title: "Group Session - Input",
|
|
||||||
props: ["id"],
|
|
||||||
components: { ScoreSelector },
|
|
||||||
data: function() {
|
|
||||||
return {
|
|
||||||
scenarios: [],
|
|
||||||
participant: null,
|
|
||||||
valid: false,
|
|
||||||
complete: false,
|
|
||||||
error: null,
|
|
||||||
stompclient: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
let self = this;
|
|
||||||
if (this.id != null) {
|
|
||||||
Vue.axios
|
|
||||||
.get(`/grpob/valid/${this.id}`)
|
|
||||||
.then(function(response) {
|
|
||||||
if (response.data.titles != null) {
|
|
||||||
response.data.titles.forEach(function(x) {
|
|
||||||
self.addAnotherObservation(x);
|
|
||||||
});
|
|
||||||
self.valid = true;
|
|
||||||
self.setupSession();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
if (error.response.status === 404) {
|
|
||||||
self.$router.push("/dberror");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.error = error.response;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addAnotherObservation: function(newTitle) {
|
|
||||||
this.scenarios.push({
|
|
||||||
title: newTitle,
|
|
||||||
monitoringRating: 0,
|
|
||||||
monitoringStrengths: "",
|
|
||||||
monitoringImprovements: "",
|
|
||||||
controlProceduralRating: 0,
|
|
||||||
controlProceduralStrengths: "",
|
|
||||||
controlProceduralImprovements: "",
|
|
||||||
controlRating: 0,
|
|
||||||
controlStrengths: "",
|
|
||||||
controlImprovements: "",
|
|
||||||
conservatismRating: 0,
|
|
||||||
conservatismStrengths: "",
|
|
||||||
conservatismImprovements: "",
|
|
||||||
teamworkCommunicationsRating: 0,
|
|
||||||
teamworkCommunicationsStrengths: "",
|
|
||||||
teamworkCommunicationsImprovements: "",
|
|
||||||
teamworkLeadershipRating: 0,
|
|
||||||
teamworkLeadershipStrengths: "",
|
|
||||||
teamworkLeadershipImprovements: "",
|
|
||||||
teamworkWorkloadRating: 0,
|
|
||||||
teamworkWorkloadStrengths: "",
|
|
||||||
teamworkWorkloadImprovements: "",
|
|
||||||
knowledgeRating: 0,
|
|
||||||
knowledgeStrengths: "",
|
|
||||||
knowledgeImprovements: ""
|
|
||||||
});
|
|
||||||
},
|
|
||||||
actuallySubmit() {
|
|
||||||
var self = this;
|
|
||||||
var payload = {
|
|
||||||
person: self.participant,
|
|
||||||
scenarios: self.scenarios
|
|
||||||
};
|
|
||||||
Vue.axios.post("/grpob/submit", payload).catch(function(error) {
|
|
||||||
self.error = error;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setupSession: function() {
|
|
||||||
var self = this;
|
|
||||||
self.stompclient = webstomp.over(
|
|
||||||
new SockJS(`http://${window.location.host}/websocket`, {
|
|
||||||
heartbeat: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
self.stompclient.connect([], function() {
|
|
||||||
self.stompclient.subscribe("/ws/status", function(incomingData) {
|
|
||||||
var data = JSON.parse(incomingData.body);
|
|
||||||
if (data.status === "complete") {
|
|
||||||
self.complete = true;
|
|
||||||
self.stompclient.disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
attemptReconnect: function() {
|
|
||||||
var self = this;
|
|
||||||
Vue.axios
|
|
||||||
.get(`/grpob/participant/${self.participant}`)
|
|
||||||
.then(function(response) {
|
|
||||||
self.scenarios = response.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
.strength {
|
|
||||||
background-color: honeydew;
|
|
||||||
}
|
|
||||||
.afi {
|
|
||||||
background-color: mistyrose;
|
|
||||||
}
|
|
||||||
.bottom-buffer {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -27,15 +27,6 @@
|
|||||||
>
|
>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<!--
|
|
||||||
<b-row class="my-3" align-h="center">
|
|
||||||
<b-col lg="6" sm="12">
|
|
||||||
<b-button class="scale-in-center" size="lg" to="/groupsession" block
|
|
||||||
><v-icon name="users" /> Start a Group Session</b-button
|
|
||||||
>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
-->
|
|
||||||
</b-container>
|
</b-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user