Fixed viewing observations and stats.

This commit is contained in:
neviyn 2018-10-05 12:26:13 +01:00
parent 76e19e9253
commit e6e69b2df9
8 changed files with 397 additions and 324 deletions

View File

@ -66,6 +66,11 @@
<groupId>joda-time</groupId> <groupId>joda-time</groupId>
<artifactId>joda-time</artifactId> <artifactId>joda-time</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
<version>2.9.5</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -27,12 +27,14 @@ class Controller {
} }
@GetMapping("/site/{id}/tutors") @GetMapping("/site/{id}/tutors")
fun getTutorsForSite(@PathVariable(value = "id") id: Long): List<NameValue> = fun getTutorsForSite(@PathVariable(value = "id") id: Long): List<NameValue>? {
siteRepository.findById(id).map { site -> val site = siteRepository.findById(id)
site.tutors.map { NameValue(it.name, it.id) } if (site.isPresent)
}.get() return site.map { site1 -> site1.tutors.map { NameValue(it.name, it.id) } }.get()
return null
}
@GetMapping @GetMapping("/tutor")
fun getAllTutors(): List<NameValue> = tutorRepository.findAll().map { NameValue(it.name, it.id) } fun getAllTutors(): List<NameValue> = tutorRepository.findAll().map { NameValue(it.name, it.id) }
@PostMapping("/tutor") @PostMapping("/tutor")
@ -55,11 +57,12 @@ class Controller {
@PostMapping("/observation") @PostMapping("/observation")
fun addObservation(@Valid @RequestBody newObservation: NewObservation): Long { fun addObservation(@Valid @RequestBody newObservation: NewObservation): Long {
println(newObservation)
val site = siteRepository.findById(newObservation.site).get() val site = siteRepository.findById(newObservation.site).get()
val tutors = tutorRepository.findAllById(newObservation.tutors).toSet() val tutors = tutorRepository.findAllById(newObservation.tutors).toSet()
val overallScores = newObservation.entries.asSequence().groupBy { it.type } val overallScores = newObservation.entries.asSequence().groupBy { it.type }
.map { entry -> entry.key to entry.value.asSequence().mapNotNull { it.rating }.average() } .map { entry -> entry.key to entry.value.asSequence().mapNotNull { it.rating }.average() }
.map { it.first to if(it.second > 0) it.second else null }.toList() .map { it.first to if (it.second > 0) it.second else null }.toList()
.toMap() .toMap()
var observation = Observation( var observation = Observation(
site = site, site = site,
@ -80,51 +83,56 @@ class Controller {
it.observations.add(observation) it.observations.add(observation)
tutorRepository.save(it) tutorRepository.save(it)
} }
println(observation)
return observation.id return observation.id
} }
@GetMapping("/observations") @PostMapping("/observations")
fun getObservations(@Valid @RequestBody observationsRequest: ObservationsRequest): List<Observation>? { fun getObservations(@Valid @RequestBody observationsRequest: ObservationsRequest): List<Observation> {
print(observationsRequest)
if (observationsRequest.tutor != null) { if (observationsRequest.tutor != null) {
val tutor = tutorRepository.findById(observationsRequest.tutor).get() return tutorRepository.findById(observationsRequest.tutor).map {
return if (observationsRequest.whom == null) { when {
observationRepository.findByTutorsAndDateBetween(tutor = tutor, (observationsRequest.whom == null || observationsRequest.whom.isEmpty()) -> observationRepository.findByTutorsAndDateBetween(tutor = it,
startDate = observationsRequest.startDate, startDate = observationsRequest.startDate,
endDate = observationsRequest.endDate) endDate = observationsRequest.endDate)
} else { else -> observationRepository.findByTutorsAndWhomAndDateBetween(tutor = it,
observationRepository.findByTutorsAndWhomAndDateBetween(tutor = tutor,
whom = observationsRequest.whom, whom = observationsRequest.whom,
startDate = observationsRequest.startDate, startDate = observationsRequest.startDate,
endDate = observationsRequest.endDate) endDate = observationsRequest.endDate)
} }
}.orElse(listOf())
} }
if (observationsRequest.site != null) { if (observationsRequest.site != null) {
val site = siteRepository.findById(observationsRequest.site).get() return siteRepository.findById(observationsRequest.site).map {
return if (observationsRequest.whom == null) { when {
observationRepository.findBySiteAndDateBetween(site = site, (observationsRequest.whom == null || observationsRequest.whom.isEmpty()) -> observationRepository.findBySiteAndDateBetween(site = it,
startDate = observationsRequest.startDate, startDate = observationsRequest.startDate,
endDate = observationsRequest.endDate) endDate = observationsRequest.endDate)
} else { else -> observationRepository.findBySiteAndWhomAndDateBetween(site = it,
observationRepository.findBySiteAndWhomAndDateBetween(site = site,
whom = observationsRequest.whom, whom = observationsRequest.whom,
startDate = observationsRequest.startDate, startDate = observationsRequest.startDate,
endDate = observationsRequest.endDate) endDate = observationsRequest.endDate)
} }
}.orElse(listOf())
} }
return null return listOf()
} }
@GetMapping("/observations/chartdata") @PostMapping("/observations/chartdata")
fun getObservationsChartData(@Valid @RequestBody observationsRequest: ObservationsRequest): ChartData? { fun getObservationsChartData(@Valid @RequestBody observationsRequest: ObservationsRequest): ChartData? {
val data = getObservations(observationsRequest) ?: return null val data = getObservations(observationsRequest)
val groupedData = data.asSequence().groupBy { it.date }.map{ entry -> AverageData( if(data.isEmpty()) return ChartData(listOf(), listOf())
val groupedData = data.asSequence().groupBy { it.date }.map { entry ->
AverageData(
entry.value.asSequence().mapNotNull { it.monitoring }.average(), entry.value.asSequence().mapNotNull { it.monitoring }.average(),
entry.value.asSequence().mapNotNull { it.control }.average(), entry.value.asSequence().mapNotNull { it.control }.average(),
entry.value.asSequence().mapNotNull { it.conservatism }.average(), entry.value.asSequence().mapNotNull { it.conservatism }.average(),
entry.value.asSequence().mapNotNull { it.teamwork }.average(), entry.value.asSequence().mapNotNull { it.teamwork }.average(),
entry.value.asSequence().mapNotNull { it.knowledge }.average(), entry.value.asSequence().mapNotNull { it.knowledge }.average(),
entry.key entry.key
)}.toList() )
}.toList()
val dates = groupedData.map { it.date.toString("yyyy-MM-dd") } val dates = groupedData.map { it.date.toString("yyyy-MM-dd") }
return ChartData( return ChartData(
labels = dates, labels = dates,

View File

@ -1,5 +1,6 @@
package uk.co.neviyn.observationdatabase package uk.co.neviyn.observationdatabase
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import org.joda.time.LocalDate import org.joda.time.LocalDate
import javax.persistence.* import javax.persistence.*
@ -43,6 +44,7 @@ data class Observation(
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0, val id: Long = 0,
@JsonIgnore
@ManyToOne @ManyToOne
val site: Site, val site: Site,
@Column(nullable = false) @Column(nullable = false)
@ -60,6 +62,7 @@ data class Observation(
val knowledge: Double?, val knowledge: Double?,
@ElementCollection @ElementCollection
val entries: List<Entry>, val entries: List<Entry>,
@JsonIgnore
@ManyToMany(mappedBy = "observations") @ManyToMany(mappedBy = "observations")
val tutors: Set<Tutor> val tutors: Set<Tutor>
) )
@ -72,10 +75,10 @@ data class Entry(
@Column(nullable = false) @Column(nullable = false)
@JsonProperty @JsonProperty
val rating: Int, val rating: Int,
@Column(nullable = false) @Column(nullable = false, columnDefinition = "TEXT")
@JsonProperty @JsonProperty
val strengths: String, val strengths: String,
@Column(nullable = false) @Column(nullable = false, columnDefinition = "TEXT")
@JsonProperty @JsonProperty
val improvements: String val improvements: String
) )

View File

@ -1 +1,3 @@
spring.jpa.properties.jadira.usertype.autoRegisterUserTypes = true spring.jpa.properties.jadira.usertype.autoRegisterUserTypes = true
spring.datasource.url=jdbc:h2:file:./database
spring.jpa.hibernate.ddl-auto=update

View File

@ -1,18 +1,22 @@
<template> <template>
<div id="app"> <div id="app">
<!-- <b-navbar toggleable="md" type="dark" variant="info">
<div id="nav"> <b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
<router-link to="/">Home</router-link> | <b-navbar-brand to="/">Observation Database</b-navbar-brand>
<router-link to="/observation">Observation</router-link> <b-collapse is-nav id="nav_collapse">
</div> <b-navbar-nav>
-->
<b-navbar id="nav" variant="info">
<b-navbar-brand to="/">Training Observations</b-navbar-brand>
<b-navbar-nav class="ml-auto">
<b-nav-item to="/">New Observation</b-nav-item> <b-nav-item to="/">New Observation</b-nav-item>
</b-navbar-nav> </b-navbar-nav>
<!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto">
<b-nav-item-dropdown text="Admin" right>
<b-dropdown-item to="/newsite">New Site</b-dropdown-item>
<b-dropdown-item to="/newtutor">New Tutor</b-dropdown-item>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-collapse>
</b-navbar> </b-navbar>
<br /> <br/>
<transition> <transition>
<router-view/> <router-view/>
</transition> </transition>
@ -20,18 +24,19 @@
</template> </template>
<style> <style>
#app { #app {
font-family: "Avenir", Helvetica, Arial, sans-serif; font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
text-align: center; text-align: center;
color: #2c3e50; color: #2c3e50;
} }
#nav {
padding: 10px;
}
#nav a.router-link-exact-active { #nav {
padding: 10px;
}
#nav a.router-link-exact-active {
color: #42b983; color: #42b983;
} }
</style> </style>

View File

@ -5,18 +5,20 @@
<h3 class="modal-title w-100">{{ errorStatus }}</h3> <h3 class="modal-title w-100">{{ errorStatus }}</h3>
</div> </div>
<div class="d-block"> <div class="d-block">
<br /> <br/>
<span style="font-size:20px;" v-html="errorMessage" /> <span style="font-size:20px;" v-html="errorMessage"/>
<button class="btn btn-warning" @click="$refs.errorModal.hide()"> <button class="btn btn-warning" @click="$refs.errorModal.hide()">
<v-icon name="exclamation-circle" /> Dismiss</button> <v-icon name="exclamation-circle"/>
Dismiss
</button>
</div> </div>
</b-modal> </b-modal>
<b-container v-if="errorMessage"> <b-container v-if="errorMessage">
<b-alert variant="danger" show>An error occurred, refresh and try again.</b-alert> <b-alert variant="danger" show>An error occurred, refresh and try again.</b-alert>
</b-container> </b-container>
<b-container v-if="!loaded && !errorMessage"> <b-container v-if="!loaded && !errorMessage">
<v-icon name="spinner" spin scale="2" /> <v-icon name="spinner" spin scale="2"/>
<br /><br /> <br/><br/>
<p>Loading Site Data</p> <p>Loading Site Data</p>
</b-container> </b-container>
<b-container v-if="loaded"> <b-container v-if="loaded">
@ -24,28 +26,32 @@
<b-row align-h="center"> <b-row align-h="center">
<b-col> <b-col>
<b-form-group label="Site"> <b-form-group label="Site">
<b-form-select :value="site" @input="setSite" :options="siteOptions" style="text-align:center;"></b-form-select> <b-form-select :value="site" @input="setSite" :options="siteOptions"
style="text-align:center;" required></b-form-select>
</b-form-group> </b-form-group>
</b-col> </b-col>
</b-row> </b-row>
<b-row align-h="center"> <b-row align-h="center">
<b-col> <b-col>
<b-form-group label="Who"> <b-form-group label="Who">
<b-form-input :value="whom" @input="setWhom" type="text" style="text-align:center;"></b-form-input> <b-form-input :value="whom" @input="setWhom" type="text"
style="text-align:center;" required></b-form-input>
</b-form-group> </b-form-group>
</b-col> </b-col>
</b-row> </b-row>
<b-row align-h="center"> <b-row align-h="center">
<b-col> <b-col>
<b-form-group label="Description"> <b-form-group label="Description">
<b-form-input :value="description" @input="setDescription" type="text" style="text-align:center;"></b-form-input> <b-form-input :value="description" @input="setDescription" type="text"
style="text-align:center;" required></b-form-input>
</b-form-group> </b-form-group>
</b-col> </b-col>
</b-row> </b-row>
<b-row align-h="center"> <b-row align-h="center">
<b-col> <b-col>
<b-form-group label="Type"> <b-form-group label="Type">
<b-form-select :value="type" @input="setType" style="text-align:center;"> <b-form-select :value="type" @input="setType" style="text-align:center;"
required>
<option :value=null>Select a training type</option> <option :value=null>Select a training type</option>
<option value="INITIAL">INITIAL</option> <option value="INITIAL">INITIAL</option>
<option value="CONTINUING">CONTINUING</option> <option value="CONTINUING">CONTINUING</option>
@ -57,8 +63,10 @@
<b-col> <b-col>
<b-form-group label="Tutor(s)"> <b-form-group label="Tutor(s)">
<p v-if="site == null">Select a site first.</p> <p v-if="site == null">Select a site first.</p>
<v-icon name="spinner" spin v-if="loadingTutors" /> <v-icon name="spinner" spin v-if="loadingTutors"/>
<b-form-checkbox-group :value="tutors" @input="setTutors" :options="tutorOptions"></b-form-checkbox-group> <b-form-checkbox-group :value="tutors" @input="setTutors"
:options="tutorOptions"
required></b-form-checkbox-group>
</b-form-group> </b-form-group>
</b-col> </b-col>
</b-row> </b-row>
@ -73,11 +81,12 @@
</template> </template>
<script> <script>
import Vue from "vue"; import Vue from "vue";
import "vue-awesome/icons/spinner"; import "vue-awesome/icons/spinner";
import "vue-awesome/icons/exclamation-circle"; import "vue-awesome/icons/exclamation-circle";
import { mapState, mapMutations } from "vuex"; import {mapState, mapMutations} from "vuex";
export default {
export default {
name: "home", name: "home",
data() { data() {
return { return {
@ -107,7 +116,7 @@ export default {
...mapState(["site", "description", "type", "tutors", "whom"]) ...mapState(["site", "description", "type", "tutors", "whom"])
}, },
watch: { watch: {
site: function() { site: function () {
this.loadingTutors = true; this.loadingTutors = true;
this.tutorOptions = []; this.tutorOptions = [];
this.tutors = []; this.tutors = [];
@ -123,7 +132,7 @@ export default {
"setWhom", "setWhom",
"resetStore" "resetStore"
]), ]),
getTutors: function() { getTutors: function () {
if (this.site != null) { if (this.site != null) {
Vue.axios.get("/site/" + this.site + "/tutors").then(response => { Vue.axios.get("/site/" + this.site + "/tutors").then(response => {
this.tutorOptions = response.data; this.tutorOptions = response.data;
@ -131,7 +140,7 @@ export default {
}); });
} }
}, },
onSubmit: function(e) { onSubmit: function (e) {
console.log("submit"); console.log("submit");
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -141,5 +150,5 @@ export default {
} }
} }
} }
}; };
</script> </script>

View File

@ -14,12 +14,17 @@
<b-row> <b-row>
<b-col> <b-col>
<b-form-group label="Site"> <b-form-group label="Site">
<b-form-select v-model="siteSelection" :options="siteOptions" style="text-align:center;" /> <b-form-select class="text-center" v-model="siteSelection" :options="siteOptions" />
</b-form-group>
</b-col>
<b-col>
<b-form-group label="Tutor">
<b-form-select class="text-center" v-model="tutorSelection" :options="tutorOptions" />
</b-form-group> </b-form-group>
</b-col> </b-col>
<b-col> <b-col>
<b-form-group label="Who"> <b-form-group label="Who">
<b-form-input v-model="whom" type="text" style="text-align:center;"></b-form-input> <b-form-input class="text-center" v-model="whom" type="text"></b-form-input>
</b-form-group> </b-form-group>
</b-col> </b-col>
<b-col> <b-col>
@ -103,12 +108,12 @@ export default {
methods: { methods: {
getFilteredAverage: function() { getFilteredAverage: function() {
Vue.axios Vue.axios
.get("/observations/chartdata", { .post("/observations/chartdata", {
site: this.siteSelection, 'site': this.siteSelection,
tutor: this.tutorSelection, 'tutor': this.tutorSelection,
startDate: moment(this.startDate).format("YYYY-MM-DD"), 'startDate': moment(this.startDate).format("YYYY-MM-DD"),
endDate: moment(this.endDate).format("YYYY-MM-DD"), 'endDate': moment(this.endDate).format("YYYY-MM-DD"),
whom: this.whom 'whom': this.whom
}) })
.then(response => { .then(response => {
this.chartData = response.data; this.chartData = response.data;
@ -137,7 +142,7 @@ export default {
}, },
watch: { watch: {
siteSelection: function() { siteSelection: function() {
this.tutorOptions = null; this.tutorOptions = [];
this.tutorSelection = null; this.tutorSelection = null;
this.getTutors(); this.getTutors();
} }
@ -145,7 +150,7 @@ export default {
mounted() { mounted() {
this.getFilteredAverage(); this.getFilteredAverage();
Vue.axios Vue.axios
.get("/site/all") .get("/site")
.then(response => { .then(response => {
this.siteOptions = response.data; this.siteOptions = response.data;
}) })

View File

@ -1,44 +1,59 @@
<template> <template>
<b-container> <b-container fluid>
<b-modal ref="errorModal" class="text-center" centered hide-header hide-footer>
<div class="modal-header">
<h3 class="modal-title w-100">{{ errorStatus }}</h3>
</div>
<div class="d-block">
<br/>
<span style="font-size:20px;" v-html="errorMessage"/>
<button class="btn btn-warning" @click="$refs.errorModal.hide()">
<v-icon name="exclamation-circle"/>
Dismiss
</button>
</div>
</b-modal>
<b-row> <b-row>
<b-col> <b-col>
<b-form-group label="Site"> <b-form-group label="Site">
<b-form-select v-model="siteSelection" :options="siteOptions" style="text-align:center;" /> <b-form-select class="text-center" v-model="siteSelection"
:options="siteOptions"/>
</b-form-group>
</b-col>
<b-col>
<b-form-group label="Tutor">
<b-form-select class="text-center" v-model="tutorSelection"
:options="tutorOptions"/>
</b-form-group> </b-form-group>
</b-col> </b-col>
<b-col> <b-col>
<b-form-group label="Who"> <b-form-group label="Who">
<b-form-input v-model="whom" type="text" style="text-align:center;"></b-form-input> <b-form-input v-model="whom" type="text" class="text-center"></b-form-input>
</b-form-group> </b-form-group>
</b-col> </b-col>
<b-col> <b-col>
<b-form-group label="From"> <b-form-group label="From">
<date-picker v-model="startDate" @dp-change="changeStartDate" value="startDate" :config="dateOptions" /> <date-picker v-model="startDate" @dp-change="changeStartDate" value="startDate"
:config="dateOptions"/>
</b-form-group> </b-form-group>
</b-col> </b-col>
<b-col> <b-col>
<b-form-group label="To"> <b-form-group label="To">
<date-picker v-model="endDate" @dp-change="changeEndDate" value="endDate" :config="dateOptions" /> <date-picker v-model="endDate" @dp-change="changeEndDate" value="endDate"
:config="dateOptions"/>
</b-form-group> </b-form-group>
</b-col> </b-col>
<b-col> <b-col>
<b-button v-on:click="getFiltered()">Refresh</b-button> <b-button v-on:click="getFiltered()">Refresh</b-button>
</b-col> </b-col>
</b-row> </b-row>
<b-container v-if="observationData != null"> <b-container v-if="observationData != null" fluid>
<b-container v-for="(observation, index) in observationData" v-bind:key="index"> <b-card no-body>
<b-row> <b-tabs pills card vertical>
<b-col> <b-tab v-for="(observation, index) in observationData" v-bind:key="index">
<p>{{ observation.date }}-{{ observation.type }}/{{ observation.observed }}-{{ obsevation.whom }}</p> <p slot="title">{{ observation.date }}<br/>{{ observation.whom }} {{ observation.type }}<br/>{{ observation.observed }}</p>
</b-col> <div v-for="(entry, index2) in observation.entries" v-bind:key="index2">
</b-row> <p>{{ entry.type }} - {{ entry.rating }}</p>
<b-row v-for="(entry, index) in observation.observations" v-bind:key="index">
<b-row>
<b-col cols="3">
<p>{{ entry.type }}</p>
<p>{{ entry.rating }}</p>
</b-col>
<b-col>
<b-form-group label="Strengths"> <b-form-group label="Strengths">
<b-form-textarea :value="entry.strengths" readonly> <b-form-textarea :value="entry.strengths" readonly>
</b-form-textarea> </b-form-textarea>
@ -47,18 +62,21 @@
<b-form-textarea :value="entry.improvements" readonly> <b-form-textarea :value="entry.improvements" readonly>
</b-form-textarea> </b-form-textarea>
</b-form-group> </b-form-group>
</b-col> </div>
</b-row> </b-tab>
</b-row> </b-tabs>
</b-container> </b-card>
</b-container> </b-container>
</b-container> </b-container>
</template> </template>
<script> <script>
export default { import Vue from "vue";
var moment = require("moment");
export default {
name: "viewobservations", name: "viewobservations",
data: function() { data: function () {
return { return {
startDate: moment().subtract(7, "days"), startDate: moment().subtract(7, "days"),
endDate: moment(), endDate: moment(),
@ -69,14 +87,16 @@ export default {
siteSelection: null, siteSelection: null,
siteOptions: [], siteOptions: [],
tutorSelection: null, tutorSelection: null,
tutorOptions: null, tutorOptions: [],
whom: null, whom: null,
observationData: null observationData: null,
errorStatus: null,
errorMessage: null
}; };
}, },
mounted() { mounted() {
Vue.axios Vue.axios
.get("/site/all") .get("/site")
.then(response => { .then(response => {
this.siteOptions = response.data; this.siteOptions = response.data;
}) })
@ -85,9 +105,9 @@ export default {
}); });
}, },
methods: { methods: {
getFiltered: function() { getFiltered: function () {
Vue.axios Vue.axios
.get("/observations", { .post("/observations", {
site: this.siteSelection, site: this.siteSelection,
tutor: this.tutorSelection, tutor: this.tutorSelection,
startDate: moment(this.startDate).format("YYYY-MM-DD"), startDate: moment(this.startDate).format("YYYY-MM-DD"),
@ -95,7 +115,7 @@ export default {
whom: this.whom whom: this.whom
}) })
.then(response => { .then(response => {
this.chartData = response.data; this.observationData = response.data;
}) })
.catch(error => { .catch(error => {
this.errorStatus = error.response.status; this.errorStatus = error.response.status;
@ -103,12 +123,28 @@ export default {
this.$refs.errorModal.show(); this.$refs.errorModal.show();
}); });
}, },
changeStartDate: function(e) { changeStartDate: function (e) {
this.startDate = e.date; this.startDate = e.date;
}, },
changeEndDate: function(e) { changeEndDate: function (e) {
this.endDate = e.date; this.endDate = e.date;
},
getTutors: function () {
if (this.siteSelection != null) {
Vue.axios
.get("/site/" + this.siteSelection + "/tutors")
.then(response => {
this.tutorOptions = response.data;
});
} }
} }
}; },
watch: {
siteSelection: function () {
this.tutorOptions = [];
this.tutorSelection = null;
this.getTutors();
}
}
};
</script> </script>