From 6f4da05ddc4c634260fda476e04039936a3bee5d Mon Sep 17 00:00:00 2001 From: Nathan Cannon Date: Thu, 28 Feb 2019 15:07:29 +0000 Subject: [PATCH] Group sessions now show current data in a scenario format. Remote users are now informed when session is closed. --- .../co/neviyn/observationdatabase/Entity.kt | 14 +- .../GroupSessionManager.kt | 39 +- .../co/neviyn/observationdatabase/Security.kt | 2 +- .../controller/GroupSessionController.kt | 58 +- frontend/src/views/GroupSession.vue | 166 +++-- frontend/src/views/GroupSessionInput.vue | 608 +++++++++--------- 6 files changed, 499 insertions(+), 388 deletions(-) diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Entity.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Entity.kt index cb34340..1855325 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Entity.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Entity.kt @@ -147,7 +147,19 @@ data class Scenario( @OneToOne(cascade = [CascadeType.ALL]) val knowledge: RatingComponent -) +) { + fun ratingsAllValid(): Boolean { + val limitation = 1..5 + return monitoring.rating in limitation && + controlProcedural.rating in limitation && + control.rating in limitation && + conservatism.rating in limitation && + teamworkCommunications.rating in limitation && + teamworkLeadership.rating in limitation && + teamworkWorkload.rating in limitation && + knowledge.rating in limitation + } +} @Entity data class Person( diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/GroupSessionManager.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/GroupSessionManager.kt index 2fc44fb..e165084 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/GroupSessionManager.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/GroupSessionManager.kt @@ -15,7 +15,7 @@ object GroupSessionManager { var tutors: List? = null var trainingType: TrainingType? = null var scenarioTitles: List? = null - var observations: MutableList = mutableListOf() + var observations: MutableMap = mutableMapOf() fun startNewSession(site: Site, tutors: Set, trainingType: TrainingType, scenarioTitles: List): Int { logger.info("Starting new Group Session") @@ -29,11 +29,19 @@ object GroupSessionManager { this.tutors = tutors.map { it.id } this.trainingType = trainingType this.scenarioTitles = scenarioTitles - observations = mutableListOf() + observations = mutableMapOf() logger.debug("Group session initialized") return sessionId } + fun invalidate() { + this.site = null + this.tutors = null + this.trainingType = null + this.scenarioTitles = null + this.observations = mutableMapOf() + } + fun isValid(sessionId: Int): Boolean { return isValid() && sessionId == this.sessionId } @@ -42,7 +50,30 @@ object GroupSessionManager { return site != null && tutors != null && trainingType != null && scenarioTitles != null } - fun addObservation(observation: Observation) { - observations.add(observation) + fun asScenarioView(): MutableMap> { + val output: MutableMap> = mutableMapOf() + scenarioTitles?.forEach { title -> + output[title] = mutableListOf() + } + observations.forEach { k, v -> + v.scenarios.forEach { + output[it.title]?.add(it.copy(title = k)) + } + } + return output + } + + fun dataComplete(): Boolean { + observations.values.forEach { + it.scenarios.forEach { x -> + if (!x.ratingsAllValid()) + return false + } + } + return true + } + + fun updateObservationData(input: GroupObservation) { + observations[input.person] = input } } \ No newline at end of file diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Security.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Security.kt index aec45c3..2689b2d 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Security.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Security.kt @@ -40,7 +40,7 @@ class CustomWebSecurityConfigurerAdapter : WebSecurityConfigurerAdapter() { @Throws(Exception::class) override fun configure(http: HttpSecurity) { http.csrf().disable().authorizeRequests() - .antMatchers(HttpMethod.POST, "/api/site", "/api/tutor", "/api/observation", "/api/grpob/start").authenticated() + .antMatchers(HttpMethod.POST, "/api/site", "/api/tutor", "/api/observation", "/api/grpob/start", "/api/grpob/complete").authenticated() .anyRequest().permitAll() .and() .httpBasic() 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 index 0963400..156caa7 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt @@ -73,7 +73,7 @@ class GroupSessionController { 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(), "observations" to GroupSessionManager.observations)) + 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") return mapOf("error" to "No session currently active") @@ -119,29 +119,41 @@ class GroupSessionController { */ @PostMapping("/submit") fun addGroupObservation(@Valid @RequestBody observationData: GroupObservation) { - val tutors = tutorRepository.findAllById(GroupSessionManager.tutors!!).toSet() - 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 = tutors, - person = personRepository.findFirstByNameLike(observationData.person.toUpperCase()) ?: personRepository.save(Person(name = observationData.person.toUpperCase())) - ) - observation = saveObservation(observation) - GroupSessionManager.addObservation(observation) - websocketMessenger.convertAndSend("/ws/observations", mapOf("observations" to GroupSessionManager.observations)) + GroupSessionManager.updateObservationData(observationData) + websocketMessenger.convertAndSend("/ws/scenarios", mapOf("scenarios" to GroupSessionManager.asScenarioView())) + } + + @PostMapping("/complete") + fun pushObservationsToDatabase(): Map { + if (GroupSessionManager.isValid() && GroupSessionManager.dataComplete()) { + val tutors = tutorRepository.findAllById(GroupSessionManager.tutors!!).toSet() + GroupSessionManager.observations.values.forEach { x -> + saveObservation(Observation( + site = GroupSessionManager.site!!, + date = LocalDate.now(), + type = GroupSessionManager.trainingType!!, + observed = x.scenarios.joinToString { it.title }, + monitoring = x.scenarios.map { it.monitoring.rating }.average(), + conservatism = x.scenarios.map { it.conservatism.rating }.average(), + controlProcedural = x.scenarios.map { it.controlProcedural.rating }.average(), + control = x.scenarios.map { it.control.rating }.average(), + teamworkCommunications = x.scenarios.map { it.teamworkCommunications.rating }.average(), + teamworkLeadership = x.scenarios.map { it.teamworkLeadership.rating }.average(), + teamworkWorkload = x.scenarios.map { it.teamworkWorkload.rating }.average(), + knowledge = x.scenarios.map { it.knowledge.rating }.average(), + scenarios = x.scenarios, + tutors = tutors, + person = personRepository.findFirstByNameLike(x.person.toUpperCase()) + ?: personRepository.save(Person(name = x.person.toUpperCase())) + )) + } + GroupSessionManager.invalidate() + websocketMessenger.convertAndSend("/ws/status", mapOf("status" to "complete")) + return mapOf("success" to "The submission was successfully completed.") + } else if (!GroupSessionManager.dataComplete()) { + return mapOf("error" to "Data is incomplete") } + return mapOf("error" to "Session was not valid") } /** diff --git a/frontend/src/views/GroupSession.vue b/frontend/src/views/GroupSession.vue index aad1462..60424f6 100644 --- a/frontend/src/views/GroupSession.vue +++ b/frontend/src/views/GroupSession.vue @@ -1,17 +1,47 @@