From 7a7c66c967c424b1549c158c04f825a220da8c07 Mon Sep 17 00:00:00 2001 From: Matija Obreza Date: Tue, 18 Dec 2018 17:42:43 +0100 Subject: [PATCH] KPI Execution supports `group by` as additional dimensions - alias field added --- .../server/model/kpi/DimensionKey.java | 6 + .../genesys2/server/model/kpi/Execution.java | 123 +++++++---- .../server/model/kpi/ExecutionDimension.java | 20 +- .../server/model/kpi/ExecutionGroup.java | 193 ++++++++++++++++++ .../server/service/impl/KPIServiceImpl.java | 108 ++++++---- .../liquibase/liquibase-changeLog.yml | 52 +++++ src/main/resources/log4j.properties | 1 + .../test/server/services/KPIServiceTest.java | 124 ++++++++++- src/test/resources/log4j.properties | 1 + 9 files changed, 543 insertions(+), 85 deletions(-) create mode 100644 src/main/java/org/genesys2/server/model/kpi/ExecutionGroup.java diff --git a/src/main/java/org/genesys2/server/model/kpi/DimensionKey.java b/src/main/java/org/genesys2/server/model/kpi/DimensionKey.java index 21c13f2e3..adac90d8b 100644 --- a/src/main/java/org/genesys2/server/model/kpi/DimensionKey.java +++ b/src/main/java/org/genesys2/server/model/kpi/DimensionKey.java @@ -115,4 +115,10 @@ public class DimensionKey implements EntityId, Serializable { return true; } + public static DimensionKey createFor(String name, String value) { + DimensionKey dk = new DimensionKey(); + dk.name = name; + dk.value = value; + return dk; + } } diff --git a/src/main/java/org/genesys2/server/model/kpi/Execution.java b/src/main/java/org/genesys2/server/model/kpi/Execution.java index 0a27ecb6e..6e40d7e18 100644 --- a/src/main/java/org/genesys2/server/model/kpi/Execution.java +++ b/src/main/java/org/genesys2/server/model/kpi/Execution.java @@ -20,7 +20,9 @@ import java.util.ArrayList; import java.util.List; import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; @@ -86,32 +88,42 @@ public class Execution extends AuditedVersionedModel implements SelfCleaning { @JoinColumn(name = "parameterId") private KPIParameter parameter; - /** This joins the _base.link in the query and allows us to count properties of collections */ + /** + * This joins the _base.link in the query and allows us to count properties of + * collections + */ @Size(min = 1, max = 50) - @Pattern(regexp="[a-z]([a-zA-Z0-9_]+)") + @Pattern(regexp = "[a-z]([a-zA-Z0-9_]+)") @Column(length = 50) private String link; - + /** This is the property of {@link #parameter} we're observing */ @NotNull @Size(max = 30) @Column(nullable = false, length = 30) private String property = "id"; - @OneToMany(orphanRemoval = true, fetch = FetchType.EAGER, cascade = { CascadeType.ALL }) + @OneToMany(orphanRemoval = true, fetch = FetchType.LAZY, cascade = { CascadeType.ALL }) @JoinColumn(name = "executionId") - private List dimensions = new ArrayList(); + private List executionDimensions = new ArrayList(); + + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "execution_group", joinColumns = @JoinColumn(name = "executionId")) + private List groups = new ArrayList(); @JsonIgnore - @OneToMany(mappedBy="execution", orphanRemoval = true, fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }) + @OneToMany(mappedBy = "execution", orphanRemoval = true, fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }) private List runs; @PrePersist @PreUpdate private void preupdate() { trimStringsToNull(); + if (groups != null) { + groups.forEach(group -> group.trimStringsToNull()); + } } - + public String getName() { return name; } @@ -140,17 +152,17 @@ public class Execution extends AuditedVersionedModel implements SelfCleaning { public ExecutionType getType() { return type; } - + /** - * Set the name of the collection to join to the query. The {@link #property} then referes - * to the joined collection. + * Set the name of the collection to join to the query. The {@link #property} + * then referes to the joined collection. * * @param link Collection property of the entity to join */ public void setLink(String link) { this.link = link; } - + public String getLink() { return link; } @@ -182,66 +194,80 @@ public class Execution extends AuditedVersionedModel implements SelfCleaning { ped.setLink(link); ped.setField(field); - dimensions.add(ped); + executionDimensions.add(ped); } public String query() { StringBuffer sb = new StringBuffer(), where = new StringBuffer(); - String alias = "_base"; + + final String aliasDereferenced = "PC"; + final String aliasParameter = "PA"; + sb.append("select "); + + for (ExecutionGroup group : groups) { + sb.append(group.toJpa()); + if (group.getAlias() != null) { + sb.append(" as ").append(group.toName()); + } + sb.append(", "); + } + switch (type) { case SUM: - sb.append("sum(").append(alias).append(".").append(property).append(")"); + sb.append("sum(").append(link == null ? aliasParameter : aliasDereferenced).append(".").append(property).append(")"); break; case AVERAGE: - sb.append("avg(").append(alias).append(".").append(property).append(")"); + sb.append("avg(").append(link == null ? aliasParameter : aliasDereferenced).append(".").append(property).append(")"); sb.append(", "); - sb.append("stddev(").append(alias).append(".").append(property).append(")"); + sb.append("stddev(").append(link == null ? aliasParameter : aliasDereferenced).append(".").append(property).append(")"); break; case COUNT: default: - sb.append("count(distinct ").append(alias).append(".").append(property).append(")"); + sb.append("count(distinct ").append(link == null ? aliasParameter : aliasDereferenced).append(".").append(property).append(")"); } sb.append(" from "); sb.append(parameter.getEntity()); - sb.append(" ").append(link == null ? alias : "X"); + sb.append(" ").append(aliasParameter); - int pedC = 0; - for (ExecutionDimension ped : dimensions) { - pedC++; - if (ped.getLink() != null) { + int execDimCounter = 0; + for (ExecutionDimension execDim : executionDimensions) { + execDimCounter++; + // System.err.println("DIM" + execDimCounter + " " + execDim); + if (execDim.getLink() != null) { sb.append(" inner join "); - sb.append(link == null ? alias : "X").append("."); - sb.append(ped.getLink()); - sb.append(" _ped").append(pedC).append(" "); + sb.append(aliasParameter).append("."); + sb.append(execDim.getLink()); + sb.append(" ED").append(execDimCounter).append(" "); } - if (pedC > 1) + if (execDimCounter > 1) where.append(" and "); - if (ped.getLink() == null) { - where.append("( ").append(link == null ? alias : "X").append(".").append(ped.getField()).append(" = ?").append(pedC).append(" )"); + if (execDim.getLink() == null) { + where.append("( ").append(aliasParameter).append(".").append(execDim.getField()).append(" = ?").append(execDimCounter).append( + " )"); } else { - where.append("( _ped").append(pedC).append(".").append(ped.getField()).append(" = ?").append(pedC).append(" )"); + where.append("( ED").append(execDimCounter).append(".").append(execDim.getField()).append(" = ?").append(execDimCounter).append(" )"); } } if (link != null) { // We're joining a collection to count it's property sb.append(" inner join "); - sb.append("X."); + sb.append(aliasParameter).append("."); sb.append(link); - sb.append(" ").append(alias); + sb.append(" ").append(aliasDereferenced); } if (where.length() > 0 || parameter.getCondition() != null) { sb.append(" where "); if (parameter.getCondition() != null) { - sb.append(link == null ? alias : "X").append(".").append(parameter.getCondition()); + sb.append(aliasParameter).append(".").append(parameter.getCondition()); } - if (dimensions.size() > 0) { + if (executionDimensions.size() > 0) { if (parameter.getCondition() != null) { sb.append(" and "); } @@ -249,17 +275,33 @@ public class Execution extends AuditedVersionedModel implements SelfCleaning { } } + if (!groups.isEmpty()) { + sb.append(" group by "); + int groupCounter = 0; + for (ExecutionGroup group : groups) { + groupCounter++; + if (groupCounter > 1) { + sb.append(", "); + } + sb.append(group.toJpa()); + } + } + return sb.toString(); } + public void setExecutionDimensions(List executionDimensions) { + this.executionDimensions = executionDimensions; + } + public List getExecutionDimensions() { - return dimensions; + return executionDimensions; } - + public Dimension getDimension(int depth) { - if (depth >= dimensions.size()) + if (depth >= executionDimensions.size()) return null; - return dimensions.get(depth).getDimension(); + return executionDimensions.get(depth).getDimension(); } public String getTitle() { @@ -270,4 +312,11 @@ public class Execution extends AuditedVersionedModel implements SelfCleaning { this.title = title; } + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } } diff --git a/src/main/java/org/genesys2/server/model/kpi/ExecutionDimension.java b/src/main/java/org/genesys2/server/model/kpi/ExecutionDimension.java index 7dbdb359a..3013ca893 100644 --- a/src/main/java/org/genesys2/server/model/kpi/ExecutionDimension.java +++ b/src/main/java/org/genesys2/server/model/kpi/ExecutionDimension.java @@ -49,12 +49,18 @@ public class ExecutionDimension extends BasicModel implements SelfCleaning { @Pattern(regexp = "[a-z][a-zA-Z0-9_\\.\\(\\)]*") @Column(length = 100, nullable = true) private String link; - + /** Field in the Parameter (e.g. accession.instCode) or Linked entity ({@link #link} (e.g. accession.institute.code) **/ @Size(max=100) @Pattern(regexp = "[_a-z][a-zA-Z0-9_\\.\\(\\)]*") @Column(length = 100, nullable = false) private String field; + + /** Alias: how we store the key name in the observation **/ + @Size(max=100) + @Pattern(regexp = "[_a-z][a-zA-Z0-9_\\.]*") + @Column(length = 100) + private String alias; @PrePersist @PreUpdate @@ -85,14 +91,22 @@ public class ExecutionDimension extends BasicModel implements SelfCleaning { public void setLink(String link) { this.link = link; } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } @Override public String toString() { - return "id=" + getId() + " link=" + link + " field=" + field + " dim=" + dimension.getName(); + return "id=" + getId() + " link=" + link + " field=" + field + " alias=" + alias + " dim=" + dimension.getName(); } public String toName() { - return (link == null ? "" : link + ".") + field; + return alias == null ? ((link == null ? "" : link + ".") + field) : alias; } } diff --git a/src/main/java/org/genesys2/server/model/kpi/ExecutionGroup.java b/src/main/java/org/genesys2/server/model/kpi/ExecutionGroup.java new file mode 100644 index 000000000..2e0c22c0d --- /dev/null +++ b/src/main/java/org/genesys2/server/model/kpi/ExecutionGroup.java @@ -0,0 +1,193 @@ +/* + * Copyright 2018 Global Crop Diversity Trust + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.genesys2.server.model.kpi; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +import org.genesys.blocks.model.SelfCleaning; + +// TODO: Auto-generated Javadoc +/** + * GroupBy for Executions. + */ +@Embeddable +public class ExecutionGroup implements SelfCleaning, Serializable { + + private static final long serialVersionUID = 1390895207879490387L; + + /** + * Supported operations. + */ + public static enum Operation { + + /** The year. */ + YEAR, + /** The month. */ + MONTH, + /** The day. */ + DAY, + /** The length. */ + LENGTH + } + + @NotNull + @Size(max = 100) + @Pattern(regexp = "[_a-z][a-zA-Z0-9_\\.\\(\\)]*") + @Column(length = 100, nullable = false) + private String field; + + @Column(length = 20) + @Enumerated(EnumType.STRING) + private Operation op; + + /** The alias. */ + @Size(max = 100) + @Pattern(regexp = "[_a-z][a-zA-Z0-9_]+") + @Column(length = 100) + private String alias; + + + @PrePersist + @PreUpdate + private void preupdate() { + trimStringsToNull(); + } + + /** + * Gets the field. + * + * @return the field + */ + public String getField() { + return field; + } + + /** + * Sets the field. + * + * @param field the new field + */ + public void setField(String field) { + this.field = field; + } + + /** + * Gets the op. + * + * @return the op + */ + public Operation getOp() { + return op; + } + + /** + * Sets the op. + * + * @param op the new op + */ + public void setOp(Operation op) { + this.op = op; + } + + /** + * Sets the alias. + * + * @param alias the new alias + */ + public void setAlias(String alias) { + this.alias = alias; + } + + /** + * Gets the alias. + * + * @return the alias + */ + public String getAlias() { + return alias; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((alias == null) ? 0 : alias.hashCode()); + result = prime * result + ((field == null) ? 0 : field.hashCode()); + result = prime * result + ((op == null) ? 0 : op.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ExecutionGroup other = (ExecutionGroup) obj; + if (alias == null) { + if (other.alias != null) + return false; + } else if (!alias.equals(other.alias)) + return false; + if (field == null) { + if (other.field != null) + return false; + } else if (!field.equals(other.field)) + return false; + if (op != other.op) + return false; + return true; + } + + public String toName() { + return alias == null ? field : alias; + } + + /** + * Generate JPA statement for this group + * + * @return JPA fragment + */ + public String toJpa() { + StringBuffer sb = new StringBuffer(); + if (op != null) { + sb.append(op.toString()).append("("); + } + sb.append(field); + if (op != null) { + sb.append(")"); + } + return sb.toString(); + } +} diff --git a/src/main/java/org/genesys2/server/service/impl/KPIServiceImpl.java b/src/main/java/org/genesys2/server/service/impl/KPIServiceImpl.java index 649681f96..310af5cc2 100644 --- a/src/main/java/org/genesys2/server/service/impl/KPIServiceImpl.java +++ b/src/main/java/org/genesys2/server/service/impl/KPIServiceImpl.java @@ -36,6 +36,7 @@ import org.apache.commons.lang3.StringUtils; import org.genesys2.server.model.kpi.Dimension; import org.genesys2.server.model.kpi.DimensionKey; import org.genesys2.server.model.kpi.Execution; +import org.genesys2.server.model.kpi.ExecutionGroup; import org.genesys2.server.model.kpi.ExecutionRun; import org.genesys2.server.model.kpi.JpaDimension; import org.genesys2.server.model.kpi.KPIParameter; @@ -230,37 +231,66 @@ public class KPIServiceImpl implements KPIService { } } - private Observation getSingleObservation(Query query, Object... params) { + private List getObservationResults(Execution execution, Query query, Object... params) { for (int i = 0; i < params.length; i++) { LOG.debug("\t?{} = {}", (i + 1), params[i]); query.setParameter(i + 1, params[i]); } - Object res = query.getSingleResult(); + List results = query.getResultList(); + List observations = new ArrayList<>(results.size()); - if (res == null) { - return null; - - } else if (res instanceof Object[]) { - Object[] r = (Object[]) res; - Observation observation = new Observation(); - if (r[0] != null) { - observation.setValue(((Number) r[0]).doubleValue()); - } - if (r.length > 1 && r[1] != null) { - observation.setStdDev(((Number) r[1]).doubleValue()); - } - return observation; - - } else if (res instanceof Number) { - Number r = (Number) res; - Observation observation = new Observation(); - observation.setValue(r.doubleValue()); - return observation; + for (Object res : results) { + LOG.trace("Query result: {}", res); + + if (res == null) { + continue; + + } else if (res instanceof Object[]) { + // case of group by or AVERAGE+STDEV + Object[] r = (Object[]) res; + // LOG.trace("OBS: {}", r); + + Observation observation = new Observation(); + + int pos=0; + { + // Query is designed to first return groupBy's + List groups = execution.getGroups(); + for (ExecutionGroup group : groups) { + String groupValue = r[pos] == null ? "" : r[pos].toString(); + DimensionKey dk = dimensionKeyRepository.findByNameAndValue(group.toName(), groupValue); + if (dk == null) { + dk = DimensionKey.createFor(group.toName(), groupValue); + } + observation.getDimensions().add(dk); + pos++; + } + } + + if (r[pos] != null) { + observation.setValue(((Number) r[pos]).doubleValue()); + } + pos++; + + if (r.length > pos && r[pos] != null) { + observation.setStdDev(((Number) r[pos]).doubleValue()); + } - } else { - throw new RuntimeException("Unrecognized return type " + res.getClass() + " for " + res); + observations.add(observation); + + } else if (res instanceof Number) { + Number r = (Number) res; + Observation observation = new Observation(); + observation.setValue(r.doubleValue()); + observations.add(observation); + + } else { + throw new RuntimeException("Unrecognized return type " + res.getClass() + " for " + res); + } } + + return observations; } // readonly mode @@ -403,8 +433,10 @@ public class KPIServiceImpl implements KPIService { // execute if (LOG.isDebugEnabled()) LOG.debug("Executing: {} params={}", execution.getName(), params); - Observation res = getSingleObservation(query, params.toArray()); - registerObservation(res, execution, params.toArray(), results); + List res = getObservationResults(execution, query, params.toArray()); + res.forEach(observed -> { + registerObservation(observed, execution, params.toArray(), results); + }); } else { // Recurse Set values = null; @@ -422,6 +454,10 @@ public class KPIServiceImpl implements KPIService { LOG.debug("Observation is null, skipping"); return; } + if (observed.getValue() == 0d) { + LOG.debug("Observation value is 0d, skipping"); + return; + } KPIParameter parameter = execution.getParameter(); if (LOG.isDebugEnabled()) { @@ -431,23 +467,18 @@ public class KPIServiceImpl implements KPIService { } for (int i = 0; i < conditions.length; i++) { - String name = execution.getDimension(i).getName(); - String value = conditions[i].toString(); + String name = execution.getExecutionDimensions().get(i).toName(); + String value = conditions[i] == null ? "" : conditions[i].toString(); LOG.debug(" dk name={} val={}", name, conditions[i].toString()); DimensionKey dk = dimensionKeyRepository.findByNameAndValue(name, value); if (dk == null) { - dk = new DimensionKey(); - dk.setName(execution.getDimension(i).getName()); - dk.setValue(conditions[i].toString()); + dk = DimensionKey.createFor(name, value); } - if (LOG.isDebugEnabled()) - LOG.debug("\t\t{}", dk); - observed.getDimensions().add(dk); + LOG.debug("\t\t{}", dk); + observed.getDimensions().add(i, dk); } - if (LOG.isDebugEnabled()) - LOG.debug("OBSERVATION: {}", observed); - + LOG.debug("OBSERVATION: {}", observed); results.add(observed); } @@ -515,6 +546,10 @@ public class KPIServiceImpl implements KPIService { target.getExecutionDimensions().clear(); target.getExecutionDimensions().addAll(source.getExecutionDimensions().stream().filter(executionDimension -> !target.getExecutionDimensions().contains(executionDimension)).collect(Collectors.toList())); } + if (source.getGroups() != null) { + target.getGroups().clear(); + target.getGroups().addAll(source.getGroups()); + } target.setLink(source.getLink()); target.setProperty(source.getProperty()); target.setParameter(source.getParameter()); @@ -540,6 +575,7 @@ public class KPIServiceImpl implements KPIService { if (execution.getRuns() != null) { execution.getRuns().size(); } + execution.getGroups().size(); } return execution; } diff --git a/src/main/resources/liquibase/liquibase-changeLog.yml b/src/main/resources/liquibase/liquibase-changeLog.yml index 6529e96e6..4cb6c4f81 100644 --- a/src/main/resources/liquibase/liquibase-changeLog.yml +++ b/src/main/resources/liquibase/liquibase-changeLog.yml @@ -4550,3 +4550,55 @@ databaseChangeLog: name: link type: varchar(50) +- changeSet: + id: 1545154679616-1 + author: mobreza (generated) + changes: + - createTable: + columns: + - column: + constraints: + nullable: false + name: executionId + type: BIGINT + - column: + name: alias + type: VARCHAR(100) + - column: + constraints: + nullable: false + name: field + type: VARCHAR(100) + - column: + name: op + type: VARCHAR(20) + tableName: execution_group + - createIndex: + columns: + - column: + name: executionId + indexName: FK_9u6fghbndlxyt1p6t319seir5 + tableName: execution_group + - addForeignKeyConstraint: + baseColumnNames: executionId + baseTableName: execution_group + constraintName: FK_9u6fghbndlxyt1p6t319seir5 + deferrable: false + initiallyDeferred: false + onDelete: NO ACTION + onUpdate: NO ACTION + referencedColumnNames: id + referencedTableName: kpiexecution + validate: true + +- changeSet: + id: 1545160286000-1 + author: matijaobreza + comment: Add `alias` to KPI ExecutionDimension + changes: + - addColumn: + tableName: kpiexecutiondimension + columns: + - column: + name: alias + type: varchar(100) diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties index b6d63c044..a9c71b305 100644 --- a/src/main/resources/log4j.properties +++ b/src/main/resources/log4j.properties @@ -29,6 +29,7 @@ log4j.rootLogger=error, stdout log4j.category.org.genesys2=warn log4j.category.org.genesys=warn log4j.category.org.genesys2.server.api=warn +#log4j.category.org.genesys2.server.service.impl.KPIServiceImpl=trace log4j.category.org.genesys2.server.service.worker.KpiScheduledExecutor=info log4j.category.liquibase=debug #log4j.category.springfox=trace diff --git a/src/test/java/org/genesys/test/server/services/KPIServiceTest.java b/src/test/java/org/genesys/test/server/services/KPIServiceTest.java index e38215095..9353a074d 100644 --- a/src/test/java/org/genesys/test/server/services/KPIServiceTest.java +++ b/src/test/java/org/genesys/test/server/services/KPIServiceTest.java @@ -29,6 +29,8 @@ import org.genesys2.server.model.impl.Organization; import org.genesys2.server.model.kpi.BooleanDimension; import org.genesys2.server.model.kpi.Execution; import org.genesys2.server.model.kpi.Execution.ExecutionType; +import org.genesys2.server.model.kpi.ExecutionGroup; +import org.genesys2.server.model.kpi.ExecutionGroup.Operation; import org.genesys2.server.model.kpi.ExecutionRun; import org.genesys2.server.model.kpi.JpaDimension; import org.genesys2.server.model.kpi.KPIParameter; @@ -96,6 +98,8 @@ public class KPIServiceTest extends AbstractServicesTest { institute.setElevation(RandomUtils.nextDouble(0, 2000)); // Replaced by code institute.setvCode(i > 1 && RandomUtils.nextBoolean() ? null : "INS" + (i - 2)); + // Generate type + institute.setType("TYPE" + i % 3); institutes.add(instituteRepository.save(institute)); } assertThat(instituteRepository.count(), is(10l)); @@ -306,9 +310,9 @@ public class KPIServiceTest extends AbstractServicesTest { // }); assertThat(results, hasSize(2)); assertThat(results.stream().collect(Collectors.summingDouble(Observation::getValue)), is(10d)); // total institutes - assertThat(results.get(0).getDimensions().get(0).getName(), is("yesno")); + assertThat(results.get(0).getDimensions().get(0).getName(), is("allowMaterialRequests")); assertThat(results.get(0).getDimensions().get(0).getValue(), isOneOf("true", "false")); - assertThat(results.get(1).getDimensions().get(0).getName(), is("yesno")); + assertThat(results.get(1).getDimensions().get(0).getName(), is("allowMaterialRequests")); assertThat(results.get(1).getDimensions().get(0).getValue(), isOneOf("true", "false")); kpiService.delete(loaded); @@ -338,9 +342,9 @@ public class KPIServiceTest extends AbstractServicesTest { // }); assertThat(results, hasSize(2)); assertThat(results.stream().collect(Collectors.summingDouble(Observation::getValue)), is(209d)); // total accessionCount - assertThat(results.get(0).getDimensions().get(0).getName(), is("yesno")); + assertThat(results.get(0).getDimensions().get(0).getName(), is("allowMaterialRequests")); assertThat(results.get(0).getDimensions().get(0).getValue(), isOneOf("true", "false")); - assertThat(results.get(1).getDimensions().get(0).getName(), is("yesno")); + assertThat(results.get(1).getDimensions().get(0).getName(), is("allowMaterialRequests")); assertThat(results.get(1).getDimensions().get(0).getValue(), isOneOf("true", "false")); kpiService.delete(loaded); @@ -361,7 +365,7 @@ public class KPIServiceTest extends AbstractServicesTest { exec1.setProperty("accessionCount"); JpaDimension dimInst = kpiService.save(createJpaDimension("institute.code", "Institute codes", FaoInstitute.class, "code")); - exec1.addDimension(dimInst, null, "vCode"); + exec1.addDimension(dimInst, null, "code"); BooleanDimension dimBool = kpiService.save(createBoolDimension("yesno", "Boolean yes and no dimension")); exec1.addDimension(dimBool, null, "allowMaterialRequests"); @@ -376,14 +380,14 @@ public class KPIServiceTest extends AbstractServicesTest { // observations.forEach(r -> { // System.err.println(r); // }); - assertThat(observations, hasSize(20)); // 10 institutes * 2 booleans = 20 + assertThat(observations, hasSize(10)); run = kpiService.findLastExecutionRun(exec1); assertThat(run, notNullValue()); assertThat(run.getTimestamp(), notNullValue()); Page obs = kpiService.listObservations(run, Maps.newHashMap(), new PageRequest(0, 20)); - assertThat(obs.getTotalElements(), is(20l)); - assertThat(obs.getContent(), hasSize(20)); + assertThat(obs.getTotalElements(), is(10l)); + assertThat(obs.getContent(), hasSize(10)); kpiService.deleteObservations(run); kpiService.delete(loaded); @@ -452,7 +456,7 @@ public class KPIServiceTest extends AbstractServicesTest { assertThat(results, hasSize(2)); assertThat(results.stream().collect(Collectors.summingDouble(Observation::getValue)), is(3d)); // 1+2 for (int i = 0; i < 2; i++) { - assertThat(results.get(i).getDimensions().get(0).getName(), is("yesno")); + assertThat(results.get(i).getDimensions().get(0).getName(), is("allowMaterialRequests")); assertThat(results.get(i).getDimensions().get(0).getValue(), isOneOf("true", "false")); assertThat(results.get(i).getValue(), is(results.get(i).getDimensions().get(0).getValue().equals("true") ? 2d : 1d)); // allows=true has 2 org slugs, false has 1 } @@ -460,8 +464,110 @@ public class KPIServiceTest extends AbstractServicesTest { kpiService.delete(loaded); assertThat(execRepository.count(), is(0l)); } + + + @Test + public void testExecutionGroupingComplex() { + KPIParameter param1 = kpiService.save(createParameter("institute", FaoInstitute.class, "Institute")); + Execution exec1 = new Execution(); + exec1.setName("institute.count"); + exec1.setTitle("Institute count"); + exec1.setParameter(param1); + exec1.setProperty("id"); + exec1.setGroups(Lists.newArrayList(groupBy("PA.type", null, "type"))); + exec1.setLink("networks"); // @OneToMany in FaoInstitute + exec1.setProperty("slug"); // a property in Organization + BooleanDimension dim1 = kpiService.save(createBoolDimension("yesno", "Boolean yes and no dimension")); + exec1.addDimension(dim1, null, "allowMaterialRequests"); + + Execution loaded = kpiService.save(exec1); + assertThat(loaded, notNullValue()); + assertThat(loaded.getId(), notNullValue()); + assertThat(loaded.getName(), is("institute.count")); + assertThat(loaded.getTitle(), is("Institute count")); + // defaults + assertThat(loaded.getProperty(), is("slug")); + assertThat(loaded.getLink(), is("networks")); + assertThat(loaded.getType(), is(Execution.ExecutionType.COUNT)); + assertThat(loaded.getExecutionDimensions(), hasSize(1)); + + List results = kpiService.execute(loaded); + assertThat(results, notNullValue()); + // results.forEach(r -> { + // System.err.println(r); + // }); + assertThat(results, hasSize(6)); + assertThat(results.stream().collect(Collectors.summingDouble(Observation::getValue)), is(9d)); // number of institutes + for (int i = 0; i < 3; i++) { + assertThat(results.get(i).getValue(), is(1d)); + assertThat(results.get(i).getDimensions().get(0).getName(), is("allowMaterialRequests")); + assertThat(results.get(i).getDimensions().get(0).getValue(), is("false")); + assertThat(results.get(i).getDimensions().get(1).getName(), is("type")); + assertThat(results.get(i).getDimensions().get(1).getValue(), isOneOf("TYPE1", "TYPE2", "TYPE0")); + } + for (int i = 3; i < 6; i++) { + assertThat(results.get(i).getValue(), is(2d)); + assertThat(results.get(i).getDimensions().get(0).getName(), is("allowMaterialRequests")); + assertThat(results.get(i).getDimensions().get(0).getValue(), is("true")); + assertThat(results.get(i).getDimensions().get(1).getName(), is("type")); + assertThat(results.get(i).getDimensions().get(1).getValue(), isOneOf("TYPE1", "TYPE2", "TYPE0")); + } + + kpiService.delete(loaded); + assertThat(execRepository.count(), is(0l)); + } + + + + @Test + public void testExecutionGroup1() { + KPIParameter param1 = kpiService.save(createParameter("institute", FaoInstitute.class, "Institute")); + Execution exec1 = new Execution(); + exec1.setName("institute.count"); + exec1.setTitle("Institute count"); + exec1.setParameter(param1); + exec1.setProperty("id"); + exec1.setGroups(Lists.newArrayList(groupBy("PA.type", null, "type"))); + + Execution loaded = kpiService.save(exec1); + assertThat(loaded, notNullValue()); + assertThat(loaded.getId(), notNullValue()); + assertThat(loaded.getName(), is("institute.count")); + assertThat(loaded.getTitle(), is("Institute count")); + // defaults + assertThat(loaded.getProperty(), is("id")); + assertThat(loaded.getLink(), nullValue()); + assertThat(loaded.getType(), is(Execution.ExecutionType.COUNT)); + + List results = kpiService.execute(loaded); + assertThat(results, notNullValue()); + // results.forEach(r -> { + // System.err.println(r); + // }); + assertThat(results, hasSize(3)); + assertThat(results.stream().collect(Collectors.summingDouble(Observation::getValue)), is(10d)); // number of institutes + assertThat(results.get(0).getValue(), is(4d)); + assertThat(results.get(0).getDimensions().get(0).getName(), is("type")); + assertThat(results.get(0).getDimensions().get(0).getValue(), is("TYPE0")); + for (int i = 1; i < 3; i++) { + assertThat(results.get(i).getValue(), is(3d)); + assertThat(results.get(i).getDimensions().get(0).getName(), is("type")); + assertThat(results.get(i).getDimensions().get(0).getValue(), isOneOf("TYPE1", "TYPE2")); + } + + kpiService.delete(loaded); + assertThat(execRepository.count(), is(0l)); + } + private ExecutionGroup groupBy(String field, Operation op, String alias) { + ExecutionGroup group = new ExecutionGroup(); + group.setField(field); + group.setOp(op); + group.setAlias(alias); + return group; + } + private BooleanDimension createBoolDimension(String name, String title) { BooleanDimension dim = new BooleanDimension(); dim.setName(name); diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties index 17b32a914..ca157dd69 100644 --- a/src/test/resources/log4j.properties +++ b/src/test/resources/log4j.properties @@ -32,6 +32,7 @@ log4j.rootLogger=warn, stdout #log4j.category.org.genesys2=info log4j.category.org.genesys.blocks.security=error log4j.category.org.genesys2.server=warn +#log4j.category.org.genesys2.server.service.impl.KPIServiceImpl=trace #log4j.category.org.genesys.test=debug #log4j.category.org.genesys2.server.service.worker.AccessionUploader=trace #log4j.category.org.genesys2.server.service.worker.AccessionCounter=trace -- GitLab