From 498478b5775ac64f4f947f091b2e6a9654241502 Mon Sep 17 00:00:00 2001 From: Nathan Cannon Date: Wed, 26 Sep 2018 16:27:34 +0100 Subject: [PATCH] Stats chart is now operational. --- .../Observations/api/AverageStatsChartJs.java | 18 ++- .../Observations/dao/ObservationDao.java | 41 ++++-- .../resources/ObservationResource.java | 49 +++---- .../Observations/dao/ObservationDaoTest.java | 126 ++++++++++++++++-- frontend/src/components/StatsView.vue | 7 +- frontend/src/views/Observation.vue | 22 +-- frontend/src/views/Stats.vue | 97 ++++++++++++-- 7 files changed, 281 insertions(+), 79 deletions(-) diff --git a/backend/src/main/java/uk/co/neviyn/Observations/api/AverageStatsChartJs.java b/backend/src/main/java/uk/co/neviyn/Observations/api/AverageStatsChartJs.java index ec74294..eb007a0 100644 --- a/backend/src/main/java/uk/co/neviyn/Observations/api/AverageStatsChartJs.java +++ b/backend/src/main/java/uk/co/neviyn/Observations/api/AverageStatsChartJs.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.NonNull; /** @@ -28,7 +29,7 @@ public class AverageStatsChartJs { teamwork = new ArrayList<>(), knowledge = new ArrayList<>(); for(AverageStats averageStats: inputData){ - labels.add(averageStats.getDate().toString()); + labels.add(averageStats.getDate().toString("yyyy-MM-dd")); monitoring.add(averageStats.getMonitoring()); control.add(averageStats.getControl()); conservatism.add(averageStats.getConservatism()); @@ -42,7 +43,6 @@ public class AverageStatsChartJs { datasets.add(new Dataset("Knowledge", "#000", knowledge)); } - @AllArgsConstructor class Dataset { @NonNull @JsonProperty @@ -52,8 +52,22 @@ public class AverageStatsChartJs { @JsonProperty private final String backgroundColor; + @NonNull + @JsonProperty + private final String borderColor; + @NonNull @JsonProperty private final List data; + + @JsonProperty + private final boolean fill = false; + + Dataset(String label, String color, List data){ + this.label = label; + this.backgroundColor = color; + this.borderColor = color; + this.data = data; + } } } diff --git a/backend/src/main/java/uk/co/neviyn/Observations/dao/ObservationDao.java b/backend/src/main/java/uk/co/neviyn/Observations/dao/ObservationDao.java index e12ca2c..c0d9744 100644 --- a/backend/src/main/java/uk/co/neviyn/Observations/dao/ObservationDao.java +++ b/backend/src/main/java/uk/co/neviyn/Observations/dao/ObservationDao.java @@ -2,10 +2,18 @@ package uk.co.neviyn.Observations.dao; import io.dropwizard.hibernate.AbstractDAO; 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.core.Observation; 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; public class ObservationDao extends AbstractDAO { @@ -25,16 +33,27 @@ public class ObservationDao extends AbstractDAO { return currentSession().createQuery("from Observation", Observation.class).list(); } - public List averageStatsForAll(){ - final String hql = "select new uk.co.neviyn.Observations.api.AverageStats(avg(obs.monitoring), avg(obs.control), avg(obs.conservatism), " + - "avg(obs.teamwork), avg(obs.knowledge), obs.date) from Observation obs group by obs.date order by obs.date"; - return currentSession().createQuery(hql, AverageStats.class).list(); - } - - public List averageStatsForSite(Site site){ - final String hql = "select new uk.co.neviyn.Observations.api.AverageStats(avg(obs.monitoring), avg(obs.control), avg(obs.conservatism)," + - "avg(obs.teamwork), avg(obs.knowledge), obs.date) from Observation obs where obs.site = :site " + - "group by obs.date order by obs.date"; - return currentSession().createQuery(hql, AverageStats.class).setParameter("site", site).list(); + // TODO: Add tutor filter + public List averageStats(Site site, Tutor tutor, DateTime startDate, DateTime endDate, String whom){ + CriteriaBuilder builder = currentSession().getCriteriaBuilder(); + CriteriaQuery criteriaQuery = builder.createQuery(AverageStats.class); + Root root = criteriaQuery.from(Observation.class); + criteriaQuery.multiselect(builder.avg(root.get("monitoring")), builder.avg(root.get("control")), + builder.avg(root.get("conservatism")), builder.avg(root.get("teamwork")), builder.avg(root.get("knowledge")), + root.get("date")); + criteriaQuery.groupBy(root.get("date")); + List predicates = new ArrayList<>(); + 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 query = currentSession().createQuery(criteriaQuery); + System.out.println(query.getQueryString()); + return query.getResultList(); } } diff --git a/backend/src/main/java/uk/co/neviyn/Observations/resources/ObservationResource.java b/backend/src/main/java/uk/co/neviyn/Observations/resources/ObservationResource.java index 7900c13..e96562f 100644 --- a/backend/src/main/java/uk/co/neviyn/Observations/resources/ObservationResource.java +++ b/backend/src/main/java/uk/co/neviyn/Observations/resources/ObservationResource.java @@ -5,7 +5,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; 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.NewObservation; 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.TutorDao; -import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import java.util.HashSet; -import java.util.List; import java.util.Set; @RequiredArgsConstructor @@ -58,32 +55,28 @@ public class ObservationResource { return observation.getId(); } - @Path("/average/all") + @Path("/average") @GET @UnitOfWork - public List averageObservationScores() { - return dao.averageStatsForAll(); - } - - @Path("/average/all/chartjs") - @GET - @UnitOfWork - public AverageStatsChartJs averageStatsChartJs() { - return new AverageStatsChartJs(averageObservationScores()); - } - - @Path("/average/{id}") - @GET - @UnitOfWork - public List averageObservationScoresForSite(@PathParam("id") long siteId) { - Site site = siteDao.get(siteId); - return dao.averageStatsForSite(site); - } - - @Path("/average/{id}/chartjs") - @GET - @UnitOfWork - public AverageStatsChartJs averageObservationScoresForSiteChartJs(@PathParam("id") long siteId) { - return new AverageStatsChartJs(averageObservationScoresForSite(siteId)); + public AverageStatsChartJs averageObservationScores(@QueryParam("site") Integer siteId, @QueryParam("tutor") Integer tutorId, + @QueryParam("startDate") String startDate, + @QueryParam("endDate") String endDate, @QueryParam("whom") String whom) { + Site site = null; + Tutor tutor = null; + DateTime start = null; + DateTime end = null; + try{ + site = siteDao.get(siteId); + } catch (Exception ignored){} + try{ + tutor = tutorDao.get(tutorId); + } catch (Exception ignored){} + try{ + start = DateTime.parse(startDate); + } catch (Exception ignored){} + try{ + end = DateTime.parse(endDate); + } catch (Exception ignored) {} + return new AverageStatsChartJs(dao.averageStats(site, tutor, start, end, whom)); } } diff --git a/backend/src/test/java/uk/co/neviyn/Observations/dao/ObservationDaoTest.java b/backend/src/test/java/uk/co/neviyn/Observations/dao/ObservationDaoTest.java index 63fc3f5..4aef3ea 100644 --- a/backend/src/test/java/uk/co/neviyn/Observations/dao/ObservationDaoTest.java +++ b/backend/src/test/java/uk/co/neviyn/Observations/dao/ObservationDaoTest.java @@ -64,20 +64,20 @@ public class ObservationDaoTest extends DaoTestBase { } @Test - public void averageStatsForAll() { + public void averageStatsNoParam() { 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.now()); + differentDay.setDate(DateTime.parse("2018-09-20")); testRule.inTransaction(() -> { dao.persist(observation); dao.persist(otherEntry); dao.persist(differentDay); }); - List stats = dao.averageStatsForAll(); + List stats = dao.averageStats(null, null, null, null, null); assertNotNull(stats); assertEquals(2, stats.size()); final AverageStats statObject = stats.get(0); @@ -89,26 +89,22 @@ public class ObservationDaoTest extends DaoTestBase { } @Test - public void averageStatsForSite() { + public void averageStatsWithSite() { Observation otherEntry = SerializationUtils.clone(observation); otherEntry.setMonitoring(5); otherEntry.setControl(5); otherEntry.setConservatism(5); otherEntry.setTeamwork(5); - - - Observation hiddenEntity = SerializationUtils.clone(observation); - Site hiddenSite = Site.builder().name("Area 51").build(); - hiddenEntity.setSite(hiddenSite); + Observation differentDay = SerializationUtils.clone(observation); + differentDay.setDate(DateTime.parse("2018-09-20")); testRule.inTransaction(() -> { dao.persist(observation); dao.persist(otherEntry); - siteDao.persist(hiddenSite); - dao.persist(hiddenEntity); + dao.persist(differentDay); }); - List stats = dao.averageStatsForSite(site); + List stats = dao.averageStats(site, null, null, null, null); assertNotNull(stats); - assertEquals(1, stats.size()); + assertEquals(2, stats.size()); final AverageStats statObject = stats.get(0); assertEquals(3, statObject.getMonitoring(), 0); assertEquals(3.5, statObject.getControl(), 0); @@ -116,4 +112,108 @@ public class ObservationDaoTest extends DaoTestBase { assertEquals(4.5, statObject.getTeamwork(), 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 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 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 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 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()); + } } \ No newline at end of file diff --git a/frontend/src/components/StatsView.vue b/frontend/src/components/StatsView.vue index e315aac..ebf7634 100644 --- a/frontend/src/components/StatsView.vue +++ b/frontend/src/components/StatsView.vue @@ -1,11 +1,12 @@ \ No newline at end of file diff --git a/frontend/src/views/Observation.vue b/frontend/src/views/Observation.vue index 1e8ba7d..5db9c8e 100644 --- a/frontend/src/views/Observation.vue +++ b/frontend/src/views/Observation.vue @@ -184,17 +184,17 @@ export default { e.stopPropagation(); var form = document.getElementById("submission-form"); console.log({ - siteId: this.site, - tutorIds: this.tutors, - observed: this.description, - whom: this.whom, - type: this.type, - monitoring: this.totals[0], - control: this.totals[1], - conservatism: this.totals[2], - teamwork: this.totals[3], - knowledge: this.totals[4], - rawData: JSON.parse(JSON.stringify(this.observations)) + siteId: this.site, + tutorIds: this.tutors, + observed: this.description, + whom: this.whom, + type: this.type, + monitoring: this.totals[0], + control: this.totals[1], + conservatism: this.totals[2], + teamwork: this.totals[3], + knowledge: this.totals[4], + rawData: JSON.parse(JSON.stringify(this.observations)) }); if (form.checkValidity()) { Vue.axios diff --git a/frontend/src/views/Stats.vue b/frontend/src/views/Stats.vue index dc4d9aa..7427684 100644 --- a/frontend/src/views/Stats.vue +++ b/frontend/src/views/Stats.vue @@ -17,47 +17,101 @@ + + + + + - + - + + + Refresh + + + + + + -