Stats chart is now operational.

This commit is contained in:
neviyn 2018-09-26 16:27:34 +01:00
parent 56f8c19520
commit 498478b577
7 changed files with 281 additions and 79 deletions

View File

@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull; import lombok.NonNull;
/** /**
@ -28,7 +29,7 @@ public class AverageStatsChartJs {
teamwork = new ArrayList<>(), teamwork = new ArrayList<>(),
knowledge = new ArrayList<>(); knowledge = new ArrayList<>();
for(AverageStats averageStats: inputData){ for(AverageStats averageStats: inputData){
labels.add(averageStats.getDate().toString()); labels.add(averageStats.getDate().toString("yyyy-MM-dd"));
monitoring.add(averageStats.getMonitoring()); monitoring.add(averageStats.getMonitoring());
control.add(averageStats.getControl()); control.add(averageStats.getControl());
conservatism.add(averageStats.getConservatism()); conservatism.add(averageStats.getConservatism());
@ -42,7 +43,6 @@ public class AverageStatsChartJs {
datasets.add(new Dataset("Knowledge", "#000", knowledge)); datasets.add(new Dataset("Knowledge", "#000", knowledge));
} }
@AllArgsConstructor
class Dataset { class Dataset {
@NonNull @NonNull
@JsonProperty @JsonProperty
@ -52,8 +52,22 @@ public class AverageStatsChartJs {
@JsonProperty @JsonProperty
private final String backgroundColor; private final String backgroundColor;
@NonNull
@JsonProperty
private final String borderColor;
@NonNull @NonNull
@JsonProperty @JsonProperty
private final List<Double> data; private final List<Double> data;
@JsonProperty
private final boolean fill = false;
Dataset(String label, String color, List<Double> data){
this.label = label;
this.backgroundColor = color;
this.borderColor = color;
this.data = data;
}
} }
} }

View File

@ -2,10 +2,18 @@ package uk.co.neviyn.Observations.dao;
import io.dropwizard.hibernate.AbstractDAO; import io.dropwizard.hibernate.AbstractDAO;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.joda.time.DateTime;
import uk.co.neviyn.Observations.api.AverageStats; import uk.co.neviyn.Observations.api.AverageStats;
import uk.co.neviyn.Observations.core.Observation; import uk.co.neviyn.Observations.core.Observation;
import uk.co.neviyn.Observations.core.Site; import uk.co.neviyn.Observations.core.Site;
import uk.co.neviyn.Observations.core.Tutor;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ObservationDao extends AbstractDAO<Observation> { public class ObservationDao extends AbstractDAO<Observation> {
@ -25,16 +33,27 @@ public class ObservationDao extends AbstractDAO<Observation> {
return currentSession().createQuery("from Observation", Observation.class).list(); return currentSession().createQuery("from Observation", Observation.class).list();
} }
public List<AverageStats> averageStatsForAll(){ // TODO: Add tutor filter
final String hql = "select new uk.co.neviyn.Observations.api.AverageStats(avg(obs.monitoring), avg(obs.control), avg(obs.conservatism), " + public List<AverageStats> averageStats(Site site, Tutor tutor, DateTime startDate, DateTime endDate, String whom){
"avg(obs.teamwork), avg(obs.knowledge), obs.date) from Observation obs group by obs.date order by obs.date"; CriteriaBuilder builder = currentSession().getCriteriaBuilder();
return currentSession().createQuery(hql, AverageStats.class).list(); CriteriaQuery<AverageStats> criteriaQuery = builder.createQuery(AverageStats.class);
} Root<Observation> root = criteriaQuery.from(Observation.class);
criteriaQuery.multiselect(builder.avg(root.get("monitoring")), builder.avg(root.get("control")),
public List<AverageStats> averageStatsForSite(Site site){ builder.avg(root.get("conservatism")), builder.avg(root.get("teamwork")), builder.avg(root.get("knowledge")),
final String hql = "select new uk.co.neviyn.Observations.api.AverageStats(avg(obs.monitoring), avg(obs.control), avg(obs.conservatism)," + root.get("date"));
"avg(obs.teamwork), avg(obs.knowledge), obs.date) from Observation obs where obs.site = :site " + criteriaQuery.groupBy(root.get("date"));
"group by obs.date order by obs.date"; List<Predicate> predicates = new ArrayList<>();
return currentSession().createQuery(hql, AverageStats.class).setParameter("site", site).list(); if(site != null)
predicates.add(builder.equal(root.get("site"), site));
if(startDate != null)
predicates.add(builder.greaterThanOrEqualTo(root.get("date"), startDate));
if(endDate != null)
predicates.add(builder.lessThanOrEqualTo(root.get("date"), endDate));
if(whom != null)
predicates.add(builder.equal(root.get("whom"), whom));
criteriaQuery.having(predicates.toArray(new Predicate[0]));
Query<AverageStats> query = currentSession().createQuery(criteriaQuery);
System.out.println(query.getQueryString());
return query.getResultList();
} }
} }

View File

@ -5,7 +5,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import uk.co.neviyn.Observations.api.AverageStats;
import uk.co.neviyn.Observations.api.AverageStatsChartJs; import uk.co.neviyn.Observations.api.AverageStatsChartJs;
import uk.co.neviyn.Observations.api.NewObservation; import uk.co.neviyn.Observations.api.NewObservation;
import uk.co.neviyn.Observations.core.Observation; import uk.co.neviyn.Observations.core.Observation;
@ -16,12 +15,10 @@ import uk.co.neviyn.Observations.dao.ObservationDao;
import uk.co.neviyn.Observations.dao.SiteDao; import uk.co.neviyn.Observations.dao.SiteDao;
import uk.co.neviyn.Observations.dao.TutorDao; import uk.co.neviyn.Observations.dao.TutorDao;
import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.ws.rs.*; import javax.ws.rs.*;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
@RequiredArgsConstructor @RequiredArgsConstructor
@ -58,32 +55,28 @@ public class ObservationResource {
return observation.getId(); return observation.getId();
} }
@Path("/average/all") @Path("/average")
@GET @GET
@UnitOfWork @UnitOfWork
public List<AverageStats> averageObservationScores() { public AverageStatsChartJs averageObservationScores(@QueryParam("site") Integer siteId, @QueryParam("tutor") Integer tutorId,
return dao.averageStatsForAll(); @QueryParam("startDate") String startDate,
} @QueryParam("endDate") String endDate, @QueryParam("whom") String whom) {
Site site = null;
@Path("/average/all/chartjs") Tutor tutor = null;
@GET DateTime start = null;
@UnitOfWork DateTime end = null;
public AverageStatsChartJs averageStatsChartJs() { try{
return new AverageStatsChartJs(averageObservationScores()); site = siteDao.get(siteId);
} } catch (Exception ignored){}
try{
@Path("/average/{id}") tutor = tutorDao.get(tutorId);
@GET } catch (Exception ignored){}
@UnitOfWork try{
public List<AverageStats> averageObservationScoresForSite(@PathParam("id") long siteId) { start = DateTime.parse(startDate);
Site site = siteDao.get(siteId); } catch (Exception ignored){}
return dao.averageStatsForSite(site); try{
} end = DateTime.parse(endDate);
} catch (Exception ignored) {}
@Path("/average/{id}/chartjs") return new AverageStatsChartJs(dao.averageStats(site, tutor, start, end, whom));
@GET
@UnitOfWork
public AverageStatsChartJs averageObservationScoresForSiteChartJs(@PathParam("id") long siteId) {
return new AverageStatsChartJs(averageObservationScoresForSite(siteId));
} }
} }

View File

@ -64,20 +64,20 @@ public class ObservationDaoTest extends DaoTestBase {
} }
@Test @Test
public void averageStatsForAll() { public void averageStatsNoParam() {
Observation otherEntry = SerializationUtils.clone(observation); Observation otherEntry = SerializationUtils.clone(observation);
otherEntry.setMonitoring(5); otherEntry.setMonitoring(5);
otherEntry.setControl(5); otherEntry.setControl(5);
otherEntry.setConservatism(5); otherEntry.setConservatism(5);
otherEntry.setTeamwork(5); otherEntry.setTeamwork(5);
Observation differentDay = SerializationUtils.clone(observation); Observation differentDay = SerializationUtils.clone(observation);
differentDay.setDate(DateTime.now()); differentDay.setDate(DateTime.parse("2018-09-20"));
testRule.inTransaction(() -> { testRule.inTransaction(() -> {
dao.persist(observation); dao.persist(observation);
dao.persist(otherEntry); dao.persist(otherEntry);
dao.persist(differentDay); dao.persist(differentDay);
}); });
List<AverageStats> stats = dao.averageStatsForAll(); List<AverageStats> stats = dao.averageStats(null, null, null, null, null);
assertNotNull(stats); assertNotNull(stats);
assertEquals(2, stats.size()); assertEquals(2, stats.size());
final AverageStats statObject = stats.get(0); final AverageStats statObject = stats.get(0);
@ -89,26 +89,22 @@ public class ObservationDaoTest extends DaoTestBase {
} }
@Test @Test
public void averageStatsForSite() { public void averageStatsWithSite() {
Observation otherEntry = SerializationUtils.clone(observation); Observation otherEntry = SerializationUtils.clone(observation);
otherEntry.setMonitoring(5); otherEntry.setMonitoring(5);
otherEntry.setControl(5); otherEntry.setControl(5);
otherEntry.setConservatism(5); otherEntry.setConservatism(5);
otherEntry.setTeamwork(5); otherEntry.setTeamwork(5);
Observation differentDay = SerializationUtils.clone(observation);
differentDay.setDate(DateTime.parse("2018-09-20"));
Observation hiddenEntity = SerializationUtils.clone(observation);
Site hiddenSite = Site.builder().name("Area 51").build();
hiddenEntity.setSite(hiddenSite);
testRule.inTransaction(() -> { testRule.inTransaction(() -> {
dao.persist(observation); dao.persist(observation);
dao.persist(otherEntry); dao.persist(otherEntry);
siteDao.persist(hiddenSite); dao.persist(differentDay);
dao.persist(hiddenEntity);
}); });
List<AverageStats> stats = dao.averageStatsForSite(site); List<AverageStats> stats = dao.averageStats(site, null, null, null, null);
assertNotNull(stats); assertNotNull(stats);
assertEquals(1, stats.size()); assertEquals(2, stats.size());
final AverageStats statObject = stats.get(0); final AverageStats statObject = stats.get(0);
assertEquals(3, statObject.getMonitoring(), 0); assertEquals(3, statObject.getMonitoring(), 0);
assertEquals(3.5, statObject.getControl(), 0); assertEquals(3.5, statObject.getControl(), 0);
@ -116,4 +112,108 @@ public class ObservationDaoTest extends DaoTestBase {
assertEquals(4.5, statObject.getTeamwork(), 0); assertEquals(4.5, statObject.getTeamwork(), 0);
assertEquals(5, statObject.getKnowledge(), 0); assertEquals(5, statObject.getKnowledge(), 0);
} }
@Test
public void averageStatsWithStartDate() {
Observation otherEntry = SerializationUtils.clone(observation);
otherEntry.setMonitoring(5);
otherEntry.setControl(5);
otherEntry.setConservatism(5);
otherEntry.setTeamwork(5);
Observation differentDay = SerializationUtils.clone(observation);
differentDay.setDate(DateTime.parse("2018-09-20"));
testRule.inTransaction(() -> {
dao.persist(observation);
dao.persist(otherEntry);
dao.persist(differentDay);
});
List<AverageStats> stats = dao.averageStats(null, null, DateTime.parse("2018-09-01").withTimeAtStartOfDay(), null, null);
assertNotNull(stats);
assertEquals(2, stats.size());
// Change one observation to outside range
differentDay.setDate(DateTime.parse("2018-08-20"));
testRule.inTransaction(() -> {
dao.persist(differentDay);
});
stats = dao.averageStats(null, null, DateTime.parse("2018-09-01").withTimeAtStartOfDay(), null, null);
assertNotNull(stats);
assertEquals(1, stats.size());
}
@Test
public void averageStatsWithEndDate() {
Observation otherEntry = SerializationUtils.clone(observation);
otherEntry.setMonitoring(5);
otherEntry.setControl(5);
otherEntry.setConservatism(5);
otherEntry.setTeamwork(5);
Observation differentDay = SerializationUtils.clone(observation);
differentDay.setDate(DateTime.parse("2018-09-20"));
testRule.inTransaction(() -> {
dao.persist(observation);
dao.persist(otherEntry);
dao.persist(differentDay);
});
List<AverageStats> stats = dao.averageStats(null, null, null, DateTime.parse("2018-10-01").withTimeAtStartOfDay(), null);
assertNotNull(stats);
assertEquals(2, stats.size());
// Change one observation to outside range
differentDay.setDate(DateTime.parse("2018-10-20"));
testRule.inTransaction(() -> {
dao.persist(differentDay);
});
stats = dao.averageStats(null, null, null, DateTime.parse("2018-10-01").withTimeAtStartOfDay(), null);
assertNotNull(stats);
assertEquals(1, stats.size());
}
@Test
public void averageStatsWithBothDate() {
Observation otherEntry = SerializationUtils.clone(observation);
otherEntry.setMonitoring(5);
otherEntry.setControl(5);
otherEntry.setConservatism(5);
otherEntry.setTeamwork(5);
otherEntry.setDate(DateTime.parse("2018-08-20"));
Observation differentDay = SerializationUtils.clone(observation);
differentDay.setDate(DateTime.parse("2018-10-20"));
testRule.inTransaction(() -> {
dao.persist(observation);
dao.persist(otherEntry);
dao.persist(differentDay);
});
List<AverageStats> stats = dao.averageStats(site, null, null, null, null);
assertNotNull(stats);
assertEquals(3, stats.size());
stats = dao.averageStats(null, null, DateTime.parse("2018-09-01").withTimeAtStartOfDay(), DateTime.parse("2018-10-01").withTimeAtStartOfDay(), null);
assertNotNull(stats);
System.out.println(stats.toString());
assertEquals(1, stats.size());
}
@Test
public void averageStatsWithWhom() {
Observation otherEntry = SerializationUtils.clone(observation);
otherEntry.setMonitoring(5);
otherEntry.setControl(5);
otherEntry.setConservatism(5);
otherEntry.setTeamwork(5);
Observation differentDay = SerializationUtils.clone(observation);
differentDay.setDate(DateTime.parse("2018-09-20"));
testRule.inTransaction(() -> {
dao.persist(observation);
dao.persist(otherEntry);
dao.persist(differentDay);
});
List<AverageStats> stats = dao.averageStats(null, null, null, null, "Group A");
assertNotNull(stats);
assertEquals(2, stats.size());
differentDay.setWhom("Group B");
testRule.inTransaction(() -> {
dao.persist(differentDay);
});
stats = dao.averageStats(null, null, null, null, "Group A");
assertNotNull(stats);
assertEquals(1, stats.size());
}
} }

View File

@ -1,11 +1,12 @@
<script> <script>
import { Line } from "vue-chartjs"; import { Line, mixins } from "vue-chartjs";
export default { export default {
extends: Line, extends: Line,
props: ["data", "options"], mixins: [mixins.reactiveProp],
props: ["chartData", "options"],
mounted() { mounted() {
this.renderChart(this.data, this.options); this.renderChart(this.chartData, this.options);
} }
}; };
</script> </script>

View File

@ -17,47 +17,101 @@
<b-form-select v-model="siteSelection" :options="siteOptions" style="text-align:center;" /> <b-form-select v-model="siteSelection" :options="siteOptions" style="text-align:center;" />
</b-form-group> </b-form-group>
</b-col> </b-col>
<b-col>
<b-form-group label="Who">
<b-form-input v-model="whom" type="text" style="text-align:center;"></b-form-input>
</b-form-group>
</b-col>
<b-col> <b-col>
<b-form-group label="From"> <b-form-group label="From">
<date-picker v-model="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" :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-button v-on:click="getFilteredAverage()">Refresh</b-button>
</b-col>
</b-row>
<b-row>
<b-col>
<stats-view v-if="chartData != null && chartData != {}" v-bind:chartData="chartData" :options="options" />
</b-col>
</b-row> </b-row>
<stats-view v-if="chartData != null && chartData != {}" :data="chartData" :options="options" />
</b-container> </b-container>
</template> </template>
<script> <script>
import Vue from "vue"; import Vue from "vue";
import StatsView from "../components/StatsView"; import StatsView from "../components/StatsView";
var moment = require("moment");
export default { export default {
name: "stats", name: "stats",
components: { StatsView }, components: { StatsView },
data: function() { data: function() {
return { return {
chartData: null, chartData: null,
options: null, options: {
responsive: true,
tooltips: {
mode: "index",
intersect: false
},
hover: {
mode: "nearest",
intersect: true
},
scales: {
xAxes: [
{
display: true,
scaleLabel: {
display: true,
labelString: "Date"
}
}
],
yAxes: [
{
display: true,
scaleLabel: {
display: true,
labelString: "Average Score"
}
}
]
}
},
errorStatus: null, errorStatus: null,
errorMessage: null, errorMessage: null,
startDate: new Date(), startDate: moment().subtract(7, "days"),
endDate: new Date(), endDate: moment(),
dateOptions: { dateOptions: {
format: "DD/MM/YYYY", format: "DD/MM/YYYY",
useCurrent: false useCurrent: false
}, },
siteSelection: null, siteSelection: null,
siteOptions: null siteOptions: [],
tutorSelection: null,
tutorOptions: null,
whom: null
}; };
}, },
methods: { methods: {
getAllTimeAverage: function() { getFilteredAverage: function() {
Vue.axios Vue.axios
.get("/observation/average/all/chartjs") .get("/observation/average", {
params: {
site: this.siteSelection,
tutor: this.tutorSelection,
startDate: moment(this.startDate).format("YYYY-MM-DD"),
endDate: moment(this.endDate).format("YYYY-MM-DD"),
whom: this.whom
}
})
.then(response => { .then(response => {
this.chartData = response.data; this.chartData = response.data;
}) })
@ -66,15 +120,36 @@ export default {
this.errorMessage = error.response.data; this.errorMessage = error.response.data;
this.$refs.errorModal.show(); this.$refs.errorModal.show();
}); });
},
getTutors: function() {
if (this.siteSelection != null) {
Vue.axios
.get("/site/" + this.siteSelection + "/tutors")
.then(response => {
this.tutorOptions = response.data;
});
}
},
changeStartDate: function(e) {
this.startDate = e.date;
},
changeEndDate: function(e) {
this.endDate = e.date;
}
},
watch: {
siteSelection: function() {
this.tutorOptions = null;
this.tutorSelection = null;
this.getTutors();
} }
}, },
mounted() { mounted() {
this.getAllTimeAverage(); this.getFilteredAverage();
Vue.axios Vue.axios
.get("/site/all") .get("/site/all")
.then(response => { .then(response => {
this.siteOptions = response.data; this.siteOptions = response.data;
this.loaded = true;
}) })
.catch(error => { .catch(error => {
this.errorStatus = error.response.status; this.errorStatus = error.response.status;