dhis2-devs team mailing list archive
-
dhis2-devs team
-
Mailing list archive
-
Message #25301
[Branch ~dhis2-devs-core/dhis2/trunk] Rev 12515: Merge from Jim Grace for branch lp:~dhis2-devs-core/dhis2/alerts. Implements blueprint 'alerts' /...
Merge authors:
Jim Grace (dhis2-c)
------------------------------------------------------------
revno: 12515 [merge]
committer: Lars Helge Øverland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Tue 2013-10-08 21:10:40 +0200
message:
Merge from Jim Grace for branch lp:~dhis2-devs-core/dhis2/alerts. Implements blueprint 'alerts' / surveillance and monitoring feature. Well done.
added:
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRule.js
modified:
dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java
dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java
dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java
dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java
dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java
dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java
dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java
dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java
dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java
dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml
dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml
dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml
dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java
dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java
dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java
dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/commons.js
dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/AddValidationRuleAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ExportValidationResultAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/GetPeriodTypesAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ShowUpdateValidationRuleFormAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/UpdateValidationRuleAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java
dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml
dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties
dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/struts.xml
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleForm.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/expressionBuilderForm.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/general.js
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRule.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/runValidationForm.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/searchResult.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleForm.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRule.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultDetailsForm.vm
dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm
--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk
Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java 2013-08-23 15:56:19 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java 2013-10-08 17:20:57 +0000
@@ -29,6 +29,7 @@
*/
import java.util.Collection;
+import java.util.Date;
import java.util.Map;
import org.hisp.dhis.dataelement.DataElement;
@@ -257,7 +258,32 @@
*/
int getDataValueCount( int days );
- Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit unit );
+ /**
+ * Returns a map of values indexed by DataElementOperand.
+ *
+ * @param dataElements collection of DataElements to fetch for
+ * @param period period for which to fetch the values
+ * @param unit OrganisationUnit for which to fetch the values
+ * @return
+ */
+ Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source );
+
+ /**
+ * Returns a map of values indexed by DataElementOperand.
+ *
+ * In the (unlikely) event that the same dataElement/optionCombo is found in
+ * more than one period for the same organisationUnit and date, the value
+ * is returned from the period with the shortest duration.
+ *
+ * @param dataElements collection of DataElements to fetch for
+ * @param date date which must be present in the period
+ * @param unit OrganisationUnit for which to fetch the values
+ * @param periodTypes allowable period types in which to find the data
+ * @param lastUpdatedMap map in which to return the lastUpdated date for each value
+ * @return
+ */
+ Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+ Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap );
/**
* Gets a Collection of DeflatedDataValues.
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java 2013-08-23 15:56:19 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java 2013-10-08 17:20:57 +0000
@@ -250,9 +250,35 @@
*/
int getDataValueCount( Date date );
- Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit unit );
+ /**
+ * Returns a map of values indexed by DataElementOperand.
+ *
+ * @param dataElements collection of DataElements to fetch for
+ * @param period period for which to fetch the values
+ * @param unit OrganisationUnit for which to fetch the values
+ * @param lastUpdatedMap optional map in which to return the lastUpdated date for each value
+ * @return
+ */
+ Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source );
/**
+ * Returns a map of values indexed by DataElementOperand.
+ *
+ * In the (unlikely) event that the same dataElement/optionCombo is found in
+ * more than one period for the same organisationUnit and date, the value
+ * is returned from the period with the shortest duration.
+ *
+ * @param dataElements collection of DataElements to fetch for
+ * @param date date which must be present in the period
+ * @param unit OrganisationUnit for which to fetch the values
+ * @param periodTypes allowable period types in which to find the data
+ * @param lastUpdatedMap map in which to return the lastUpdated date for each value
+ * @return
+ */
+ Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+ Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap );
+
+ /**
* Gets a Collection of DeflatedDataValues.
*
* @param dataElementId the DataElement identifier.
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java 2013-10-03 10:21:15 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java 2013-10-08 17:20:57 +0000
@@ -137,6 +137,24 @@
Map<String, Double> constantMap, Integer days );
/**
+ * Generates the calculated value for the given expression base on the values
+ * supplied in the value map, constant map and days.
+ *
+ * @param expression the expression which holds the formula for the calculation.
+ * @param valueMap the mapping between data element operands and values to
+ * use in the calculation.
+ * @param constantMap the mapping between the constant uid and value to use
+ * in the calculation.
+ * @param days the number of days to use in the calculation.
+ * @param set of data element operands that have values but they are incomplete
+ * (for example due to aggregation from organisationUnit children where
+ * not all children had a value.)
+ * @return the calculated value as a double.
+ */
+ Double getExpressionValue( Expression expression, Map<DataElementOperand, Double> valueMap,
+ Map<String, Double> constantMap, Integer days, Set<DataElementOperand> incompleteValues );
+
+ /**
* Returns the uids of the data element totals in the given expression.
*
* @param expression the expression.
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java 2013-10-08 13:23:38 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java 2013-10-08 19:10:40 +0000
@@ -34,6 +34,7 @@
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
import org.apache.commons.lang.StringUtils;
import org.hisp.dhis.attribute.AttributeValue;
import org.hisp.dhis.common.BaseIdentifiableObject;
@@ -46,6 +47,7 @@
import org.hisp.dhis.common.view.UuidView;
import org.hisp.dhis.dataelement.DataElement;
import org.hisp.dhis.dataset.DataSet;
+import org.hisp.dhis.period.PeriodType;
import org.hisp.dhis.user.User;
import java.util.ArrayList;
@@ -536,6 +538,26 @@
return dataElements;
}
+ public Map<PeriodType, Set<DataElement>> getDataElementsInDataSetsByPeriodType()
+ {
+ Map<PeriodType,Set<DataElement>> map = new HashMap<PeriodType,Set<DataElement>>();
+
+ for ( DataSet dataSet : dataSets )
+ {
+ Set<DataElement> dataElements = map.get( dataSet.getPeriodType() );
+
+ if ( dataElements == null )
+ {
+ dataElements = new HashSet<DataElement>();
+ map.put( dataSet.getPeriodType(), dataElements );
+ }
+
+ dataElements.addAll( dataSet.getDataElements() );
+ }
+
+ return map;
+ }
+
public void updateParent( OrganisationUnit newParent )
{
if ( this.parent != null && this.parent.getChildren() != null )
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java 2013-10-01 16:44:42 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java 2013-10-08 17:20:57 +0000
@@ -79,6 +79,7 @@
final String KEY_SCHEDULE_AGGREGATE_QUERY_BUILDER_TASK_STRATEGY = "scheduleAggregateQueryBuilderTackStrategy";
final String KEY_CONFIGURATION = "keyConfig";
final String KEY_ACCOUNT_RECOVERY = "keyAccountRecovery";
+ final String KEY_LAST_ALERT_RUN = "keyLastAlertRun";
final String DEFAULT_SCHEDULE_AGGREGATE_QUERY_BUILDER_TASK_STRATEGY = "lastMonth";
final String DEFAULT_FLAG = "dhis2";
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java 2013-08-23 15:56:19 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java 2013-10-08 19:10:40 +0000
@@ -38,7 +38,7 @@
* @version $Id: ValidationResult.java 5277 2008-05-27 15:48:42Z larshelg $
*/
public class ValidationResult
- implements Serializable
+ implements Serializable, Comparable<ValidationResult>
{
/**
* Determines if a de-serialized file is compatible with this class.
@@ -74,7 +74,7 @@
}
// -------------------------------------------------------------------------
- // Equals, hashCode and toString
+ // Equals, compareTo, hashCode and toString
// -------------------------------------------------------------------------
@Override
@@ -150,6 +150,39 @@
return true;
}
+ public int compareTo( ValidationResult other )
+ {
+ int result = source.getName().compareTo( other.source.getName() );
+
+ if ( result == 0 )
+ {
+ result = period.getStartDate().compareTo( other.period.getStartDate() );
+
+ if ( result == 0 )
+ {
+ result = period.getEndDate().compareTo( other.period.getEndDate() );
+
+ if ( result == 0 )
+ {
+ result = validationImportanceOrder( validationRule.getImportance() )
+ - validationImportanceOrder( other.validationRule.getImportance() );
+
+ if ( result == 0 )
+ {
+ result = validationRule.getLeftSide().getDescription()
+ .compareTo( other.validationRule.getLeftSide().getDescription() );
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private int validationImportanceOrder ( String importance )
+ {
+ return ( importance.equals("high") ? 0 : importance.equals("medium") ? 1 : 2 );
+ }
+
@Override
public String toString()
{
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java 2013-09-11 15:26:20 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java 2013-10-08 19:10:40 +0000
@@ -28,13 +28,9 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonView;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import java.util.HashSet;
+import java.util.Set;
+
import org.hisp.dhis.common.BaseIdentifiableObject;
import org.hisp.dhis.common.DxfNamespaces;
import org.hisp.dhis.common.IdentifiableObject;
@@ -42,12 +38,18 @@
import org.hisp.dhis.common.adapter.JacksonPeriodTypeSerializer;
import org.hisp.dhis.common.view.DetailedView;
import org.hisp.dhis.common.view.ExportView;
+import org.hisp.dhis.dataelement.DataElement;
import org.hisp.dhis.expression.Expression;
import org.hisp.dhis.expression.Operator;
import org.hisp.dhis.period.PeriodType;
-import java.util.HashSet;
-import java.util.Set;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
/**
* @author Kristian Nordal
@@ -61,22 +63,90 @@
*/
private static final long serialVersionUID = -9058559806538024350L;
+ public static final String IMPORTANCE_HIGH = "high";
+ public static final String IMPORTANCE_MEDIUM = "medium";
+ public static final String IMPORTANCE_LOW = "low";
+
+ public static final String RULE_TYPE_VALIDATION = "validation";
+ public static final String RULE_TYPE_MONITORING = "monitoring";
+
public static final String TYPE_STATISTICAL = "statistical";
public static final String TYPE_ABSOLUTE = "absolute";
+ /**
+ * A description of the ValidationRule.
+ */
private String description;
+ /**
+ * The user-assigned importance of this rule (e.g. high, medium or low).
+ */
+ private String importance;
+
+ /**
+ * Whether this is a VALIDATION or MONITORING type rule.
+ */
+ private String ruleType;
+
+ /**
+ * Whether this is a STATISTICAL or ABSOLUTE rule (only ABSOLUTE rules are currently implemented!)
+ */
private String type;
+ /**
+ * The comparison operator to compare left and right expressions in the rule.
+ */
private Operator operator;
+ /**
+ * The left-side expression to be compared against the right side.
+ */
private Expression leftSide;
+ /**
+ * The right-side expression to be compared against the left side.
+ */
private Expression rightSide;
+ /**
+ * The set of ValidationRuleGroups to which this ValidationRule belongs.
+ */
private Set<ValidationRuleGroup> groups = new HashSet<ValidationRuleGroup>();
+ /**
+ * The organisation unit level at which this rule is evaluated (Monitoring-type rules only).
+ */
+ private Integer organisationUnitLevel;
+
+ /**
+ * The type of period in which this rule is evaluated.
+ */
private PeriodType periodType;
+
+ /**
+ * The number of sequential right-side periods from which to collect samples
+ * to average (Monitoring-type rules only). Sequential periods are those
+ * immediately preceding (or immediately following in previous years) the selected period.
+ */
+ private Integer sequentialSampleCount; // Number of sequential right-side samples to average.
+
+ /**
+ * The number of annual right-side periods from which to collect samples
+ * to average (Monitoring-type rules only). Annual periods are from previous
+ * years. Samples collected from previous years can also include sequential
+ * periods adjacent to the equivalent period in previous years.
+ */
+ private Integer annualSampleCount; // Number of (previous) annual right-side samples to average.
+
+ /**
+ * The number of high values sampled from previous periods that are discarded before averaging.
+ */
+ private Integer highOutliers;
+
+ /**
+ * The number of low values sampled from previous periods that are discarded before averaging.
+ */
+ private Integer lowOutliers;
// -------------------------------------------------------------------------
// Constructors
@@ -96,34 +166,87 @@
this.leftSide = leftSide;
this.rightSide = rightSide;
}
-
+
// -------------------------------------------------------------------------
// Logic
// -------------------------------------------------------------------------
+ /**
+ * Clears the left-side and right-side expressions. This can be useful, for example,
+ * before changing the validation rule period type, because the data elements
+ * allowed in the expressions depend on the period type.
+ */
public void clearExpressions()
{
this.leftSide = null;
this.rightSide = null;
}
+ /**
+ * Joins a validation rule group.
+ *
+ * @param validationRuleGroup the group to join.
+ */
public void addValidationRuleGroup( ValidationRuleGroup validationRuleGroup )
{
groups.add( validationRuleGroup );
validationRuleGroup.getMembers().add( this );
}
+ /**
+ * Leaves a validation rule group.
+ *
+ * @param validationRuleGroup the group to leave.
+ */
public void removeValidationRuleGroup( ValidationRuleGroup validationRuleGroup )
{
groups.remove( validationRuleGroup );
validationRuleGroup.getMembers().remove( this );
}
-
+
+ /**
+ * Gets the validation rule description, but returns the validation rule name
+ * if there is no description.
+ *
+ * @return the description (or name).
+ */
public String getDescriptionNameFallback()
{
return description != null && !description.trim().isEmpty() ? description : name;
}
+ /**
+ * Gets the data elements to evaluate for the current period. For validation-type
+ * rules this means all data elements. For monitoring-type rules this means just
+ * the left side elements.
+ *
+ * @return the data elements to evaluate for the current period.
+ */
+ public Set<DataElement> getCurrentDataElements()
+ {
+ Set<DataElement> currentDataElements = leftSide.getDataElementsInExpression();
+
+ if ( RULE_TYPE_VALIDATION.equals( ruleType ) )
+ {
+ currentDataElements = new HashSet<DataElement>( currentDataElements );
+ currentDataElements.addAll( rightSide.getDataElementsInExpression() );
+ }
+
+ return currentDataElements;
+ }
+
+ /**
+ * Gets the data elements to compare against for past periods. For validation-type
+ * rules this returns null. For monitoring-type rules this is just the
+ * right side elements.
+ *
+ * @return the data elements to evaluate for past periods.
+ */
+ public Set<DataElement> getPastDataElements()
+ {
+ return RULE_TYPE_VALIDATION.equals( ruleType ) ? null : rightSide.getDataElementsInExpression();
+ }
+
// -------------------------------------------------------------------------
// Set and get methods
// -------------------------------------------------------------------------
@@ -142,6 +265,45 @@
}
@JsonProperty
+ @JsonView( {DetailedView.class, ExportView.class} )
+ @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+ public String getImportance()
+ {
+ return importance != null && !importance.isEmpty() ? importance : IMPORTANCE_MEDIUM;
+ }
+
+ public void setImportance( String importance )
+ {
+ this.importance = importance;
+ }
+
+ @JsonProperty
+ @JsonView( {DetailedView.class, ExportView.class} )
+ @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+ public Integer getOrganisationUnitLevel()
+ {
+ return organisationUnitLevel;
+ }
+
+ public void setOrganisationUnitLevel( Integer organisationUnitLevel )
+ {
+ this.organisationUnitLevel = organisationUnitLevel;
+ }
+
+ @JsonProperty
+ @JsonView( {DetailedView.class, ExportView.class} )
+ @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+ public String getRuleType()
+ {
+ return ruleType != null && !ruleType.isEmpty() ? ruleType : RULE_TYPE_VALIDATION;
+ }
+
+ public void setRuleType( String ruleType )
+ {
+ this.ruleType = ruleType;
+ }
+
+ @JsonProperty
@JsonSerialize( using = JacksonPeriodTypeSerializer.class )
@JsonDeserialize( using = JacksonPeriodTypeDeserializer.class )
@JsonView( {DetailedView.class, ExportView.class} )
@@ -159,6 +321,58 @@
@JsonProperty
@JsonView( {DetailedView.class, ExportView.class} )
@JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+ public Integer getSequentialSampleCount()
+ {
+ return sequentialSampleCount;
+ }
+
+ public void setSequentialSampleCount( Integer sequentialSampleCount )
+ {
+ this.sequentialSampleCount = sequentialSampleCount;
+ }
+
+ @JsonProperty
+ @JsonView( {DetailedView.class, ExportView.class} )
+ @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+ public Integer getAnnualSampleCount()
+ {
+ return annualSampleCount;
+ }
+
+ public void setAnnualSampleCount( Integer annualSampleCount )
+ {
+ this.annualSampleCount = annualSampleCount;
+ }
+
+ @JsonProperty
+ @JsonView( {DetailedView.class, ExportView.class} )
+ @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+ public Integer getHighOutliers()
+ {
+ return highOutliers;
+ }
+
+ public void setHighOutliers( Integer highOutliers )
+ {
+ this.highOutliers = highOutliers;
+ }
+
+ @JsonProperty
+ @JsonView( {DetailedView.class, ExportView.class} )
+ @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+ public Integer getLowOutliers()
+ {
+ return lowOutliers;
+ }
+
+ public void setLowOutliers( Integer lowOutliers )
+ {
+ this.lowOutliers = lowOutliers;
+ }
+
+ @JsonProperty
+ @JsonView( {DetailedView.class, ExportView.class} )
+ @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
public Operator getOperator()
{
return operator;
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java 2013-09-11 15:26:20 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java 2013-09-27 17:05:36 +0000
@@ -34,12 +34,14 @@
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
import org.hisp.dhis.common.BaseIdentifiableObject;
import org.hisp.dhis.common.DxfNamespaces;
import org.hisp.dhis.common.IdentifiableObject;
import org.hisp.dhis.common.annotation.Scanned;
import org.hisp.dhis.common.view.DetailedView;
import org.hisp.dhis.common.view.ExportView;
+import org.hisp.dhis.user.UserAuthorityGroup;
import java.util.HashSet;
import java.util.Set;
@@ -60,6 +62,10 @@
@Scanned
private Set<ValidationRule> members = new HashSet<ValidationRule>();
+
+ private Set<UserAuthorityGroup> userAuthorityGroupsToAlert = new HashSet<UserAuthorityGroup>();
+
+
// -------------------------------------------------------------------------
// Constructors
@@ -129,6 +135,21 @@
this.members = members;
}
+ @JsonProperty
+ @JsonSerialize( contentAs = BaseIdentifiableObject.class )
+ @JsonView( { DetailedView.class } )
+ @JacksonXmlElementWrapper( localName = "userRolesToAlert", namespace = DxfNamespaces.DXF_2_0)
+ @JacksonXmlProperty( localName = "userRoleToAlert", namespace = DxfNamespaces.DXF_2_0)
+ public Set<UserAuthorityGroup> getUserAuthorityGroupsToAlert()
+ {
+ return userAuthorityGroupsToAlert;
+ }
+
+ public void setUserAuthorityGroupsToAlert( Set<UserAuthorityGroup> userAuthorityGroupsToAlert )
+ {
+ this.userAuthorityGroupsToAlert = userAuthorityGroupsToAlert;
+ }
+
@Override
public void mergeWith( IdentifiableObject other )
{
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java 2013-08-23 15:56:19 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java 2013-10-08 17:20:57 +0000
@@ -44,7 +44,8 @@
{
String ID = ValidationRuleService.class.getName();
- int MAX_VIOLATIONS = 500;
+ int MAX_INTERACTIVE_VIOLATIONS = 500;
+ int MAX_ALERT_VIOLATIONS = 100000;
// -------------------------------------------------------------------------
// ValidationRule business logic
@@ -91,6 +92,11 @@
*/
Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source );
+ /**
+ * For a nightly alert run, run validation tests and notify users as requested of any validations.
+ */
+ void alertRun();
+
// -------------------------------------------------------------------------
// ValidationRule
// -------------------------------------------------------------------------
@@ -172,13 +178,6 @@
*/
Collection<ValidationRule> getValidationRulesByDataElements( Collection<DataElement> dataElements );
- /**
- * Get all data elements associated with any validation rule.
- *
- * @return a collection of data elements.
- */
- Collection<DataElement> getDataElementsInValidationRules();
-
// -------------------------------------------------------------------------
// ValidationRuleGroup
// -------------------------------------------------------------------------
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java 2013-10-08 17:20:57 +0000
@@ -32,6 +32,7 @@
import java.util.Calendar;
import java.util.Collection;
+import java.util.Date;
import java.util.Map;
import org.hisp.dhis.dataelement.DataElement;
@@ -190,9 +191,15 @@
return dataValueStore.getDataValueCount( cal.getTime() );
}
- public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit unit )
- {
- return dataValueStore.getDataValueMap( dataElements, period, unit );
+ public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source )
+ {
+ return dataValueStore.getDataValueMap( dataElements, period, source );
+ }
+
+ public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+ Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap )
+ {
+ return dataValueStore.getDataValueMap( dataElements, date, source, periodTypes, lastUpdatedMap );
}
public Collection<DeflatedDataValue> getDeflatedDataValues( int dataElementId, int periodId, Collection<Integer> sourceIds )
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java 2013-10-08 19:10:40 +0000
@@ -57,6 +57,7 @@
import org.hisp.dhis.system.objectmapper.DataValueRowMapper;
import org.hisp.dhis.system.objectmapper.DeflatedDataValueRowMapper;
import org.hisp.dhis.system.util.ConversionUtils;
+import org.hisp.dhis.system.util.DateUtils;
import org.hisp.dhis.system.util.MathUtils;
import org.hisp.dhis.system.util.TextUtils;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -431,7 +432,7 @@
return rs != null ? rs.intValue() : 0;
}
- public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit unit )
+ public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source )
{
Map<DataElementOperand, Double> map = new HashMap<DataElementOperand, Double>();
@@ -441,13 +442,13 @@
}
final String sql =
- "select de.uid, coc.uid, value " +
+ "select de.uid, coc.uid, dv.value " +
"from datavalue dv " +
"join dataelement de on dv.dataelementid = de.dataelementid " +
"join categoryoptioncombo coc on dv.categoryoptioncomboid = coc.categoryoptioncomboid " +
"where dv.dataelementid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( DataElement.class, dataElements ) ) + ") " +
"and dv.periodid = " + period.getId() + " " +
- "and dv.sourceid = " + unit.getId();
+ "and dv.sourceid = " + source.getId();
SqlRowSet rowSet = jdbcTemplate.queryForRowSet( sql );
@@ -466,6 +467,68 @@
return map;
}
+ public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+ Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap )
+ {
+ Map<DataElementOperand, Double> map = new HashMap<DataElementOperand, Double>();
+
+ if ( dataElements.isEmpty() || periodTypes.isEmpty() )
+ {
+ return map;
+ }
+
+ final String sql =
+ "select de.uid, coc.uid, dv.value, dv.lastupdated, p.startdate, p.enddate " +
+ "from datavalue dv " +
+ "join dataelement de on dv.dataelementid = de.dataelementid " +
+ "join categoryoptioncombo coc on dv.categoryoptioncomboid = coc.categoryoptioncomboid " +
+ "join period p on p.periodid = dv.periodid " +
+ "where dv.dataelementid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( DataElement.class, dataElements ) ) + ") " +
+ "and dv.sourceid = " + source.getId() + " " +
+ "and p.startdate <= '" + DateUtils.getMediumDateString( date ) + "' " +
+ "and p.enddate >= '" + DateUtils.getMediumDateString( date ) + "' " +
+ "and p.periodtypeid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( PeriodType.class, periodTypes ) ) + ") ";
+
+ SqlRowSet rowSet = jdbcTemplate.queryForRowSet( sql );
+
+ Map<DataElementOperand, Long> checkForDuplicates = new HashMap<DataElementOperand, Long>();
+
+ while ( rowSet.next() )
+ {
+ String dataElement = rowSet.getString( 1 );
+ String optionCombo = rowSet.getString( 2 );
+ Double value = MathUtils.parseDouble( rowSet.getString( 3 ) );
+ Date lastUpdated = rowSet.getDate( 4 );
+ Date periodStartDate = rowSet.getDate( 5 );
+ Date periodEndDate = rowSet.getDate( 6 );
+ long periodInterval = periodEndDate.getTime() - periodStartDate.getTime();
+
+ if ( value != null )
+ {
+ DataElementOperand dataElementOperand = new DataElementOperand( dataElement, optionCombo );
+ Long existingPeriodInterval = checkForDuplicates.get( dataElementOperand );
+
+ if ( existingPeriodInterval != null && existingPeriodInterval < periodInterval )
+ {
+ // Don't overwrite the previously-stored value if it was
+ // for a shorter interval.
+ continue;
+ }
+
+ map.put( dataElementOperand, value );
+
+ if ( lastUpdatedMap != null )
+ {
+ lastUpdatedMap.put( dataElementOperand, lastUpdated );
+ }
+
+ checkForDuplicates.put( dataElementOperand, periodInterval );
+ }
+ }
+
+ return map;
+ }
+
public Collection<DeflatedDataValue> getDeflatedDataValues( int dataElementId, int periodId, Collection<Integer> sourceIds )
{
final String sql =
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java 2013-10-03 10:21:15 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java 2013-10-08 17:20:57 +0000
@@ -184,12 +184,20 @@
}
public Double getExpressionValue( Expression expression, Map<DataElementOperand, Double> valueMap,
- Map<String, Double> constantMap, Integer days )
- {
- final String expressionString = generateExpression( expression.getExpression(), valueMap, constantMap, days, expression.isNullIfBlank() );
-
- return expressionString != null ? calculateExpression( expressionString ) : null;
- }
+ Map<String, Double> constantMap, Integer days )
+ {
+ final String expressionString = generateExpression( expression.getExpression(), valueMap, constantMap, days, expression.isNullIfBlank() );
+
+ return expressionString != null ? calculateExpression( expressionString ) : null;
+ }
+
+ public Double getExpressionValue( Expression expression, Map<DataElementOperand, Double> valueMap,
+ Map<String, Double> constantMap, Integer days, Set<DataElementOperand> incompleteValues )
+ {
+ final String expressionString = generateExpression( expression.getExpression(), valueMap, constantMap, days, expression.isNullIfBlank(), incompleteValues );
+
+ return expressionString != null ? calculateExpression( expressionString ) : null;
+ }
@Transactional
public Set<DataElement> getDataElementsInExpression( String expression )
@@ -636,6 +644,12 @@
@Transactional
public String generateExpression( String expression, Map<DataElementOperand, Double> valueMap, Map<String, Double> constantMap, Integer days, boolean nullIfNoValues )
{
+ return generateExpression( expression, valueMap, constantMap, days, nullIfNoValues, null );
+ }
+
+ private String generateExpression( String expression, Map<DataElementOperand, Double> valueMap, Map<String, Double> constantMap, Integer days, boolean nullIfNoValues,
+ Set<DataElementOperand> incompleteValues )
+ {
if ( expression == null || expression.isEmpty() )
{
return null;
@@ -654,7 +668,7 @@
final Double value = valueMap.get( operand );
- if ( value == null && nullIfNoValues )
+ if ( nullIfNoValues && ( value == null || ( incompleteValues != null && incompleteValues.contains( operand ) ) ) )
{
return null;
}
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java 2013-10-08 19:10:40 +0000
@@ -37,12 +37,29 @@
import static org.hisp.dhis.system.util.MathUtils.getRounded;
import static org.hisp.dhis.system.util.MathUtils.zeroIfNull;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.hisp.dhis.common.GenericIdentifiableObjectStore;
import org.hisp.dhis.constant.ConstantService;
import org.hisp.dhis.dataelement.DataElement;
@@ -53,24 +70,177 @@
import org.hisp.dhis.expression.ExpressionService;
import org.hisp.dhis.expression.Operator;
import org.hisp.dhis.i18n.I18nService;
+import org.hisp.dhis.message.MessageService;
import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
+import org.hisp.dhis.period.CalendarPeriodType;
import org.hisp.dhis.period.Period;
import org.hisp.dhis.period.PeriodService;
+import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.setting.SystemSettingManager;
import org.hisp.dhis.system.util.Filter;
import org.hisp.dhis.system.util.FilterUtils;
+import org.hisp.dhis.system.util.SystemUtils;
+import org.hisp.dhis.user.User;
+import org.hisp.dhis.user.UserAuthorityGroup;
+import org.hisp.dhis.user.UserCredentials;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Margrethe Store
* @author Lars Helge Overland
- * @version $Id
+ * @author Jim Grace
*/
@Transactional
public class DefaultValidationRuleService
implements ValidationRuleService
{
+ private static final Log log = LogFactory.getLog( DefaultValidationRuleService.class );
+
+ /**
+ * Defines how many decimal places for rounding the left and right side
+ * evaluation values in the report of results.
+ */
private static final int DECIMALS = 1;
+ /**
+ * Defines the types of alert run.
+ */
+ private enum ValidationRunType
+ {
+ INTERACTIVE, ALERT
+ }
+
+ /**
+ * This private subclass holds information for each organisation unit that
+ * is needed during a validation run (either interactive or an alert run).
+ *
+ * It is important that they should be copied from Hibernate lazy
+ * collections before the multithreaded part of the run starts, otherwise
+ * the threads may not be able to access these values.
+ */
+ private class OrganisationUnitExtended
+ {
+ OrganisationUnit source;
+
+ Collection<OrganisationUnit> children;
+
+ int level;
+
+ public String toString()
+ {
+ return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
+ .append( "\n name", source.getName() ).append( "\n children[", children.size() + "]" )
+ .append( "\n level", level ).toString();
+ }
+ }
+
+ /**
+ * This private subclass holds information for each period type that is
+ * needed during a validation run (either interactive or an alert run).
+ *
+ * By computing these values once at the start of a validation run, we avoid
+ * the overhead of having to compute them during the processing of every
+ * organisation unit. For some of these properties this is also important
+ * because they should be copied from Hibernate lazy collections before the
+ * multithreaded part of the run starts, otherwise the threads may not be
+ * able to access these values.
+ */
+ private class PeriodTypeExtended
+ {
+ PeriodType periodType;
+
+ Collection<Period> periods;
+
+ Collection<ValidationRule> rules;
+
+ Collection<DataElement> dataElements;
+
+ Collection<PeriodType> allowedPeriodTypes;
+
+ Map<OrganisationUnit, Collection<DataElement>> sourceDataElements;
+
+ public String toString()
+ {
+ return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
+ .append( "\n periodType", periodType )
+ .append( "\n periods", (Arrays.toString( periods.toArray() )) )
+ .append( "\n rules", (Arrays.toString( rules.toArray() )) )
+ .append( "\n dataElements", (Arrays.toString( dataElements.toArray() )) )
+ .append( "\n allowedPeriodTypes", (Arrays.toString( allowedPeriodTypes.toArray() )) )
+ .append( "\n sourceDataElements", "[" + sourceDataElements.size() + "]" ).toString();
+ }
+ }
+
+ /**
+ * This private subclass holds common values that are used during a
+ * validation run (either interactive or an alert run.) These values don't
+ * change during the multi-threaded tasks (except that results entries are
+ * added in a threadsafe way.)
+ *
+ * Some of the values are precalculated collections, to save CPU time during
+ * the run. All of these values are stored in this single "context" object
+ * to allow a single object reference for each of the scheduled tasks. (This
+ * also reduces the amount of memory needed to queue all the multi-threaded
+ * tasks.)
+ *
+ * For some of these properties this is also important because they should
+ * be copied from Hibernate lazy collections before the multithreaded part
+ * of the run starts, otherwise the threads may not be able to access these
+ * values.
+ */
+ private class ValidationRunContext
+ {
+ private Map<PeriodType, PeriodTypeExtended> PeriodTypeExtendedMap;
+
+ private ValidationRunType runType;
+
+ private Date lastAlertRun;
+
+ private Map<String, Double> constantMap;
+
+ private Collection<OrganisationUnitExtended> sourceXs;
+
+ private Collection<ValidationResult> validationResults;
+
+ public String toString()
+ {
+ return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
+ .append( "\n PeriodTypeExtendedMap", (Arrays.toString( PeriodTypeExtendedMap.entrySet().toArray() )) )
+ .append( "\n runType", runType ).append( "\n lastAlertRun", lastAlertRun )
+ .append( "\n constantMap", "[" + constantMap.size() + "]" )
+ .append( "\n sourceXs", Arrays.toString( sourceXs.toArray() ) )
+ .append( "\n validationResults", Arrays.toString( validationResults.toArray() ) ).toString();
+ }
+ }
+
+ /**
+ * Runs a validation task on a thread within a multi-threaded validation
+ * run.
+ *
+ * Each thread looks for validation results in a different organisation
+ * unit.
+ */
+ private class ValidationWorkerThread
+ implements Runnable
+ {
+ private OrganisationUnitExtended sourceX;
+
+ private ValidationRunContext context;
+
+ private ValidationWorkerThread( OrganisationUnitExtended sourceX, ValidationRunContext context )
+ {
+ this.sourceX = sourceX;
+ this.context = context;
+ }
+
+ @Override
+ public void run()
+ {
+ validateSource( sourceX, context );
+ }
+ }
+
// -------------------------------------------------------------------------
// Dependencies
// -------------------------------------------------------------------------
@@ -84,7 +254,8 @@
private GenericIdentifiableObjectStore<ValidationRuleGroup> validationRuleGroupStore;
- public void setValidationRuleGroupStore( GenericIdentifiableObjectStore<ValidationRuleGroup> validationRuleGroupStore )
+ public void setValidationRuleGroupStore(
+ GenericIdentifiableObjectStore<ValidationRuleGroup> validationRuleGroupStore )
{
this.validationRuleGroupStore = validationRuleGroupStore;
}
@@ -95,7 +266,7 @@
{
this.expressionService = expressionService;
}
-
+
private DataEntryFormService dataEntryFormService;
public void setDataEntryFormService( DataEntryFormService dataEntryFormService )
@@ -110,13 +281,20 @@
this.periodService = periodService;
}
+ private OrganisationUnitService organisationUnitService;
+
+ public void setOrganisationUnitService( OrganisationUnitService organisationUnitService )
+ {
+ this.organisationUnitService = organisationUnitService;
+ }
+
private DataValueService dataValueService;
public void setDataValueService( DataValueService dataValueService )
{
this.dataValueService = dataValueService;
}
-
+
private ConstantService constantService;
public void setConstantService( ConstantService constantService )
@@ -124,6 +302,20 @@
this.constantService = constantService;
}
+ private SystemSettingManager systemSettingManager;
+
+ public void setSystemSettingManager( SystemSettingManager systemSettingManager )
+ {
+ this.systemSettingManager = systemSettingManager;
+ }
+
+ private MessageService messageService;
+
+ public void setMessageService( MessageService messageService )
+ {
+ this.messageService = messageService;
+ }
+
private I18nService i18nService;
public void setI18nService( I18nService service )
@@ -137,253 +329,1096 @@
public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources )
{
- Map<String, Double> constantMap = constantService.getConstantMap();
-
- Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();
-
- Collection<Period> relevantPeriods = periodService.getPeriodsBetweenDates( startDate, endDate );
-
- for ( OrganisationUnit source : sources )
- {
- Collection<ValidationRule> relevantRules = getRelevantValidationRules( source.getDataElementsInDataSets() );
-
- Set<DataElement> dataElements = getDataElementsInValidationRules( relevantRules ); //TODO move outside loop?
-
- if ( relevantRules != null && relevantRules.size() > 0 )
- {
- for ( Period period : relevantPeriods )
- {
- validationViolations.addAll( validateInternal( period, source, relevantRules, dataElements, constantMap,
- validationViolations.size() ) );
- }
- }
- }
-
- return validationViolations;
+ log.info( "validate( startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size() + "] )" );
+ Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
+ Collection<ValidationRule> rules = getAllValidationRules();
+ return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
}
public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources,
ValidationRuleGroup group )
{
- Map<String, Double> constantMap = constantService.getConstantMap();
-
- Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();
-
- Collection<Period> relevantPeriods = periodService.getPeriodsBetweenDates( startDate, endDate );
-
- for ( OrganisationUnit source : sources )
- {
- Collection<ValidationRule> relevantRules = getRelevantValidationRules( source.getDataElementsInDataSets() );
- relevantRules.retainAll( group.getMembers() );
-
- Set<DataElement> dataElements = getDataElementsInValidationRules( relevantRules );
-
- if ( !relevantRules.isEmpty() )
- {
- for ( Period period : relevantPeriods )
- {
- validationViolations.addAll( validateInternal( period, source, relevantRules, dataElements, constantMap,
- validationViolations.size() ) );
- }
- }
- }
-
- return validationViolations;
+ log.info( "validate( startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size()
+ + "] group=" + group.getName() + " )" );
+ Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
+ Collection<ValidationRule> rules = group.getMembers();
+ return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
}
public Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source )
{
- Map<String, Double> constantMap = constantService.getConstantMap();
-
- Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();
-
- Collection<ValidationRule> relevantRules = getRelevantValidationRules( source.getDataElementsInDataSets() );
-
- Set<DataElement> dataElements = getDataElementsInValidationRules( relevantRules );
-
- Collection<Period> relevantPeriods = periodService.getPeriodsBetweenDates( startDate, endDate );
-
- for ( Period period : relevantPeriods )
- {
- validationViolations.addAll( validateInternal( period, source, relevantRules, dataElements, constantMap, validationViolations
- .size() ) );
- }
-
- return validationViolations;
+ log.info( "validate( startDate=" + startDate + " endDate=" + endDate + " source=" + source.getName() + " )" );
+ Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
+ Collection<ValidationRule> rules = getAllValidationRules();
+ return validateInternal( source, periods, rules, ValidationRunType.INTERACTIVE, null );
}
public Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source )
{
- Map<String, Double> constantMap = constantService.getConstantMap();
-
- Collection<ValidationRule> relevantRules = null;
-
+ log.info( "validate( dataSet=" + dataSet.getName() + " period=[" + period.getPeriodType().getName() + " "
+ + period.getStartDate() + " " + period.getEndDate() + "]" + " source=" + source.getName() + " )" );
+ Collection<Period> periods = new ArrayList<Period>();
+ periods.add( period );
+
+ Collection<ValidationRule> rules = null;
if ( DataSet.TYPE_CUSTOM.equals( dataSet.getDataSetType() ) )
{
- relevantRules = getRelevantValidationRules( dataSet );
- }
- else
- {
- relevantRules = getRelevantValidationRules( dataSet.getDataElements() );
- }
-
- Set<DataElement> dataElements = getDataElementsInValidationRules( relevantRules );
-
- return validateInternal( period, source, relevantRules, dataElements, constantMap, 0 );
- }
-
- public Collection<DataElement> getDataElementsInValidationRules()
- {
- Set<DataElement> dataElements = new HashSet<DataElement>();
-
- for ( ValidationRule rule : getAllValidationRules() )
- {
- dataElements.addAll( rule.getLeftSide().getDataElementsInExpression() );
- dataElements.addAll( rule.getRightSide().getDataElementsInExpression() );
- }
-
- return dataElements;
- }
-
- // -------------------------------------------------------------------------
- // Supportive methods
- // -------------------------------------------------------------------------
-
- /**
- * Validates a collection of validation rules.
- *
- * @param period the period to validate for.
- * @param source the source to validate for.
- * @param validationRules the rules to validate.
- * @param dataElementsInRules the data elements which are part of the rules expressions.
- * @param constantMap the constants which are part of the rule expressions.
- * @param currentSize the current number of validation violations.
- * @returns a collection of rules that did not pass validation.
- */
- private Collection<ValidationResult> validateInternal( Period period, OrganisationUnit unit,
- Collection<ValidationRule> validationRules, Set<DataElement> dataElementsInRules, Map<String, Double> constantMap, int currentSize )
- {
- Map<DataElementOperand, Double> valueMap = dataValueService.getDataValueMap( dataElementsInRules, period, unit );
-
- final Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();
-
- if ( currentSize < MAX_VIOLATIONS )
- {
- Double leftSide = null;
- Double rightSide = null;
-
- boolean violation = false;
-
- for ( final ValidationRule validationRule : validationRules )
- {
- if ( validationRule.getPeriodType() != null
- && validationRule.getPeriodType().equals( period.getPeriodType() ) )
- {
- Operator operator = validationRule.getOperator();
+ rules = getRulesForDataSet( dataSet );
+ }
+ else
+ {
+ rules = getValidationTypeRulesForDataElements( dataSet.getDataElements() );
+ }
+
+ return validateInternal( source, periods, rules, ValidationRunType.INTERACTIVE, null );
+ }
+
+ // TODO: Schedule this to run every night.
+ public void alertRun()
+ {
+ // Find all the rules belonging to groups that will send alerts to user roles.
+ Set<ValidationRule> rules = new HashSet<ValidationRule>();
+ for ( ValidationRuleGroup validationRuleGroup : getAllValidationRuleGroups() )
+ {
+ Collection<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert();
+ if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() )
+ {
+ rules.addAll( validationRuleGroup.getMembers() );
+ }
+ }
+
+ Collection<OrganisationUnit> sources = organisationUnitService.getAllOrganisationUnits();
+ Collection<Period> periods = getAlertPeriodsFromRules( rules );
+ Date lastAlertRun = (Date) systemSettingManager.getSystemSetting( SystemSettingManager.KEY_LAST_ALERT_RUN );
+
+ // Any database changes after this moment will contribute to the next run.
+
+ Date thisAlertRun = new Date();
+
+ log.info( "alertRun() sources[" + sources.size() + "] periods[" + periods.size() + "] rules[" + rules.size()
+ + "] last run " + lastAlertRun == null ? "(none)" : lastAlertRun );
+ Collection<ValidationResult> results = validateInternal( sources, periods, rules, ValidationRunType.ALERT,
+ lastAlertRun );
+ log.info( "alertRun() results[" + results.size() + "]" );
+
+ if ( !results.isEmpty() )
+ {
+ postAlerts( results, thisAlertRun ); // Alert the users.
+ }
+
+ systemSettingManager.saveSystemSetting( SystemSettingManager.KEY_LAST_ALERT_RUN, thisAlertRun );
+ }
+
+ // -------------------------------------------------------------------------
+ // Support methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Evaluates validation rules for a single organisation unit.
+ *
+ * @param source the organisation unit in which to run the validation rules
+ * @param periods the periods of data to check
+ * @param rules the ValidationRules to evaluate
+ * @param runType whether this is an interactive or alert run
+ * @param lastAlertRun date/time of the most recent successful alerts run
+ * (needed only for alert run)
+ * @return a collection of any validations that were found
+ */
+ private Collection<ValidationResult> validateInternal( OrganisationUnit source, Collection<Period> periods,
+ Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
+ {
+ Collection<OrganisationUnit> sources = new HashSet<OrganisationUnit>();
+ sources.add( source );
+ return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
+ }
+
+ /**
+ * Evaluates validation rules for a collection of organisation units. This
+ * method breaks the job down by organisation unit. It assigns the
+ * evaluation for each organisation unit to a task that can be evaluated
+ * independently in a multithreaded environment.
+ *
+ * @param sources the organisation units in which to run the validation
+ * rules
+ * @param periods the periods of data to check
+ * @param rules the ValidationRules to evaluate
+ * @param runType whether this is an INTERACTIVE or ALERT run
+ * @param lastAlertRun date/time of the most recent successful alerts run
+ * (needed only for alert run)
+ * @return a collection of any validations that were found
+ */
+ private Collection<ValidationResult> validateInternal( Collection<OrganisationUnit> sources,
+ Collection<Period> periods, Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
+ {
+ ValidationRunContext context = buildNewContext( sources, periods, rules, ValidationRunType.ALERT, lastAlertRun );
+ boolean singleThreadedOption = false;
+
+ if ( singleThreadedOption )
+ {
+ for ( OrganisationUnitExtended sourceX : context.sourceXs )
+ {
+ validateSource( sourceX, context );
+ }
+ }
+ else
+ {
+ int threadPoolSize = SystemUtils.getCpuCores();
+ if ( threadPoolSize > 2 )
+ {
+ threadPoolSize--;
+ }
+ if ( threadPoolSize > sources.size() )
+ {
+ threadPoolSize = sources.size();
+ }
+
+ ExecutorService executor = Executors.newFixedThreadPool( threadPoolSize );
+
+ for ( OrganisationUnitExtended sourceX : context.sourceXs )
+ {
+ Runnable worker = new ValidationWorkerThread( sourceX, context );
+ executor.execute( worker );
+ }
+
+ executor.shutdown();
+ try
+ {
+ executor.awaitTermination( 23, TimeUnit.HOURS );
+ }
+ catch ( InterruptedException e )
+ {
+ executor.shutdownNow();
+ }
+ }
+ return context.validationResults;
+ }
+
+ /**
+ * Creates and fills a new context object for a validation run.
+ *
+ * @param sources organisation units for validation
+ * @param periods periods for validation
+ * @param rules validation rules for validation
+ * @param runType whether this is an INTERACTIVE or ALERT run
+ * @param lastAlertRun (for ALERT runs) date of previous alert run
+ * @return context object for this run
+ */
+ private ValidationRunContext buildNewContext( Collection<OrganisationUnit> sources, Collection<Period> periods,
+ Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
+ {
+ ValidationRunContext context = new ValidationRunContext();
+ context.runType = runType;
+ context.lastAlertRun = lastAlertRun;
+ context.validationResults = new ConcurrentLinkedQueue<ValidationResult>(); // thread-safe
+ context.PeriodTypeExtendedMap = new HashMap<PeriodType, PeriodTypeExtended>();
+ context.sourceXs = new HashSet<OrganisationUnitExtended>();
+
+ context.constantMap = new HashMap<String, Double>();
+ context.constantMap.putAll( constantService.getConstantMap() );
+
+ // Group the periods by period type.
+ for ( Period period : periods )
+ {
+ PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( context, period.getPeriodType() );
+ periodTypeX.periods.add( period );
+ }
+
+ for ( ValidationRule rule : rules )
+ {
+ // Find the period type extended for this rule
+ PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( context, rule.getPeriodType() );
+ periodTypeX.rules.add( rule ); // Add this rule to the period type ext.
+
+ if ( rule.getCurrentDataElements() != null )
+ {
+ // Add this rule's data elements to the period extended.
+ periodTypeX.dataElements.addAll( rule.getCurrentDataElements() );
+ }
+ // Add the allowed period types for this rule's data elements:
+ periodTypeX.allowedPeriodTypes.addAll( getAllowedPeriodTypesForDataElements( rule.getCurrentDataElements(),
+ rule.getPeriodType() ) );
+ }
+
+ // We only need to keep period types that are selected and also used by
+ // rules that are selected.
+ // Start by making a defensive copy so we can delete while iterating.
+ Set<PeriodTypeExtended> periodTypeXs = new HashSet<PeriodTypeExtended>( context.PeriodTypeExtendedMap.values() );
+ for ( PeriodTypeExtended periodTypeX : periodTypeXs )
+ {
+ if ( periodTypeX.periods.isEmpty() || periodTypeX.rules.isEmpty() )
+ {
+ context.PeriodTypeExtendedMap.remove( periodTypeX.periodType );
+ }
+ }
+
+ for ( OrganisationUnit source : sources )
+ {
+ OrganisationUnitExtended sourceX = new OrganisationUnitExtended();
+ sourceX.source = source;
+ sourceX.children = new HashSet<OrganisationUnit>( source.getChildren() );
+ sourceX.level = source.getOrganisationUnitLevel();
+ context.sourceXs.add( sourceX );
+
+ Map<PeriodType, Set<DataElement>> sourceDataElementsByPeriodType = source
+ .getDataElementsInDataSetsByPeriodType();
+ for ( PeriodTypeExtended periodTypeX : context.PeriodTypeExtendedMap.values() )
+ {
+ Collection<DataElement> sourceDataElements = sourceDataElementsByPeriodType
+ .get( periodTypeX.periodType );
+ if ( sourceDataElements != null )
+ {
+ periodTypeX.sourceDataElements.put( source, sourceDataElements );
+ }
+ else
+ {
+ periodTypeX.sourceDataElements.put( source, new HashSet<DataElement>() );
+ }
+ }
+ }
+
+ return context;
+ }
+
+ /**
+ * Gets the PeriodTypeExtended from the context object. If not found,
+ * creates a new PeriodTypeExtended object, puts it into the context object,
+ * and returns it.
+ *
+ * @param context validation run context
+ * @param periodType period type to search for
+ * @return period type extended from the context object
+ */
+ PeriodTypeExtended getOrCreatePeriodTypeExtended( ValidationRunContext context, PeriodType periodType )
+ {
+ PeriodTypeExtended periodTypeX = context.PeriodTypeExtendedMap.get( periodType );
+ if ( periodTypeX == null )
+ {
+ periodTypeX = new PeriodTypeExtended();
+ periodTypeX.periodType = periodType;
+ periodTypeX.periods = new HashSet<Period>();
+ periodTypeX.rules = new HashSet<ValidationRule>();
+ periodTypeX.dataElements = new HashSet<DataElement>();
+ periodTypeX.allowedPeriodTypes = new HashSet<PeriodType>();
+ periodTypeX.sourceDataElements = new HashMap<OrganisationUnit, Collection<DataElement>>();
+ context.PeriodTypeExtendedMap.put( periodType, periodTypeX );
+ }
+ return periodTypeX;
+ }
+
+ /**
+ * For an alert run, gets the current and most periods to search, based on
+ * the period types from the rules to run.
+ *
+ * @param rules the ValidationRules to be evaluated on this alert run
+ * @return periods to search for new alerts
+ */
+ private Collection<Period> getAlertPeriodsFromRules( Collection<ValidationRule> rules )
+ {
+ Set<Period> periods = new HashSet<Period>();
+
+ // Construct a set of all period types found in validation rules.
+ Set<PeriodType> rulePeriodTypes = new HashSet<PeriodType>();
+ for ( ValidationRule rule : rules )
+ {
+ // (Note that we have to get periodType from periodService,
+ // otherwise the ID will not be present.)
+ rulePeriodTypes.add( periodService.getPeriodTypeByName( rule.getPeriodType().getName() ) );
+ }
+
+ // For each period type, find the period containing the current date (if
+ // any), and the most recent previous period. Add whichever one(s) of these
+ // are present in the database.
+ for ( PeriodType periodType : rulePeriodTypes )
+ {
+ // This is a bit awkward. The current periodType object is of type
+ // periodType, but not of type CalendarPeriodType. In other words,
+ // ( periodType instanceof CalendarPeriodType ) returns false!
+ // In order to do periodType calendar math, we want a real
+ // "CalendarPeriodType" instance.
+ // TODO just cast to calendar period type
+ CalendarPeriodType calendarPeriodType = getCalendarPeriodType( periodType );
+ if ( calendarPeriodType != null )
+ {
+ Period currentPeriod = calendarPeriodType.createPeriod();
+ Period previousPeriod = calendarPeriodType.getPreviousPeriod( currentPeriod );
+ periods.addAll( periodService.getIntersectingPeriodsByPeriodType( periodType,
+ previousPeriod.getStartDate(), currentPeriod.getEndDate() ) );
+ // Note: If the last successful daily run was more than one day
+ // ago, we might consider adding some additional periods of type
+ // DailyPeriodType so we don't miss any alerts.
+ }
+ }
+
+ return periods;
+ }
+
+ /**
+ * Evaluates validation rules for a single organisation unit. This is the
+ * central method in validation rule evaluation.
+ *
+ * @param sourceX extended object of the organisation unit in which to run
+ * the validation rules
+ * @param context the validation run context
+ */
+ private void validateSource( OrganisationUnitExtended sourceX, ValidationRunContext context )
+ {
+ if ( context.validationResults.size() < (ValidationRunType.INTERACTIVE == context.runType ? MAX_INTERACTIVE_VIOLATIONS
+ : MAX_ALERT_VIOLATIONS) )
+ {
+ for ( PeriodTypeExtended periodTypeX : context.PeriodTypeExtendedMap.values() )
+ {
+ Collection<DataElement> sourceDataElements = periodTypeX.sourceDataElements.get( sourceX.source );
+ Set<ValidationRule> rules = getRulesBySourceAndPeriodType( sourceX, periodTypeX, context,
+ sourceDataElements );
+
+ if ( !rules.isEmpty() )
+ {
+ Set<DataElement> recursiveCurrentDataElements = getRecursiveCurrentDataElements( rules );
+ for ( Period period : periodTypeX.periods )
+ {
+ Map<DataElementOperand, Date> lastUpdatedMap = new HashMap<DataElementOperand, Date>();
+ Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
+ Map<DataElementOperand, Double> currentValueMap = getDataValueMapRecursive( periodTypeX,
+ periodTypeX.dataElements, sourceDataElements, recursiveCurrentDataElements,
+ periodTypeX.allowedPeriodTypes, period, sourceX.source, lastUpdatedMap, incompleteValues );
+ log.trace( "currentValueMap[" + currentValueMap.size() + "]" );
+
+ for ( ValidationRule rule : rules )
+ {
+ if ( evaluateCheck( lastUpdatedMap, rule, context ) )
+ {
+ Double leftSide = expressionService.getExpressionValue( rule.getLeftSide(),
+ currentValueMap, context.constantMap, null, incompleteValues );
+
+ if ( leftSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
+ {
+ Double rightSide = getRightSideValue( sourceX.source, periodTypeX, period, rule,
+ currentValueMap, sourceDataElements, context );
+
+ if ( rightSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
+ {
+ boolean violation = false;
+
+ if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
+ {
+ violation = (leftSide != null && rightSide == null)
+ || (leftSide == null && rightSide != null);
+ }
+ else if ( leftSide != null && rightSide != null )
+ {
+ violation = !expressionIsTrue( leftSide, rule.getOperator(), rightSide );
+ }
+
+ if ( violation )
+ {
+ context.validationResults.add( new ValidationResult( period,
+ sourceX.source, rule, getRounded( zeroIfNull( leftSide ), DECIMALS ),
+ getRounded( zeroIfNull( rightSide ), DECIMALS ) ) );
+ }
+
+ log.trace( "-->Evaluated " + rule.getName() + ": "
+ + (violation ? "violation" : "OK") + " " + leftSide.toString() + " "
+ + rule.getOperator() + " " + rightSide.toString() + " ("
+ + context.validationResults.size() + " results)" );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks to see if the evaluation should go further for this
+ * evaluationRule, after the "current" data to evaluate has been fetched.
+ * For INTERACTIVE runs, we always go further (always return true.) For
+ * ALERT runs, we go further only if something has changed since the last
+ * successful alert run -- either the rule definition or one of the
+ * "current" data element / option values.
+ *
+ * @param lastUpdatedMap when each data value was last updated
+ * @param rule the rule that may be evaluated
+ * @param context the evaluation run context
+ * @return true if the rule should be evaluated with this data, false if not
+ */
+ private boolean evaluateCheck( Map<DataElementOperand, Date> lastUpdatedMap, ValidationRule rule,
+ ValidationRunContext context )
+ {
+ boolean evaluate = true; // Assume true for now.
+
+ if ( ValidationRunType.ALERT == context.runType )
+ {
+ if ( context.lastAlertRun != null ) // True if no previous alert run
+ {
+ if ( rule.getLastUpdated().before( context.lastAlertRun ) )
+ {
+ // Get the "current" DataElementOperands from this rule:
+ // Left+Right sides for VALIDATION, Left side only for
+ // MONITORING
+ Collection<DataElementOperand> deos = expressionService.getOperandsInExpression( rule.getLeftSide()
+ .getExpression() );
+ if ( ValidationRule.RULE_TYPE_VALIDATION == rule.getRuleType() )
+ {
+ // Make a copy so we can add to it.
+ deos = new HashSet<DataElementOperand>( deos );
+
+ deos.addAll( expressionService.getOperandsInExpression( rule.getRightSide().getExpression() ) );
+ }
+
+ // Return true if any data is more recent than the last
+ // ALERT run, otherwise return false.
+ evaluate = false;
+ for ( DataElementOperand deo : deos )
+ {
+ Date lastUpdated = lastUpdatedMap.get( deo );
+ if ( lastUpdated != null && lastUpdated.after( context.lastAlertRun ) )
+ {
+ evaluate = true; // True if new/updated data.
+ break;
+ }
+ }
+ }
+ }
+ }
+ return evaluate;
+ }
+
+ /**
+ * Gets the rules that should be evaluated for a given organisation unit and
+ * period type.
+ *
+ * @param sourceX the organisation unit extended information
+ * @param periodTypeX the period type extended information
+ * @param context the alert run context
+ * @param sourceDataElements all data elements collected for this
+ * organisation unit
+ * @return
+ */
+ private Set<ValidationRule> getRulesBySourceAndPeriodType( OrganisationUnitExtended sourceX,
+ PeriodTypeExtended periodTypeX, ValidationRunContext context, Collection<DataElement> sourceDataElements )
+ {
+ Set<ValidationRule> periodTypeRules = new HashSet<ValidationRule>();
+
+ for ( ValidationRule rule : periodTypeX.rules )
+ {
+ if ( (ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )) )
+ {
+ // For validation-type rules, include only rules where the
+ // organisation collects all the data elements in the rule.
+ // But if this is some funny kind of rule with no elements (like
+ // for testing), include it also.
+ Collection<DataElement> elements = rule.getCurrentDataElements();
+ if ( elements == null || elements.size() == 0 || sourceDataElements.containsAll( elements ) )
+ {
+ periodTypeRules.add( rule );
+ }
+ }
+ else
+ {
+ // For monitoring-type rules, include only rules for this
+ // organisation's unit level.
+ // The organisation may not be configured for the data elements
+ // because they could be aggregated from a lower level.
+ if ( rule.getOrganisationUnitLevel() == sourceX.level )
+ {
+ periodTypeRules.add( rule );
+ }
+ }
+ }
+
+ return periodTypeRules;
+ }
+
+ /**
+ * Gets the data elements for which values should be fetched recursively if
+ * they are not collected for an organisation unit.
+ *
+ * @param rules ValidationRules to be evaluated
+ * @return the data elements to fetch recursively
+ */
+ private Set<DataElement> getRecursiveCurrentDataElements( Set<ValidationRule> rules )
+ {
+ Set<DataElement> recursiveCurrentDataElements = new HashSet<DataElement>();
+
+ for ( ValidationRule rule : rules )
+ {
+ if ( ValidationRule.RULE_TYPE_MONITORING.equals( rule.getRuleType() )
+ && rule.getCurrentDataElements() != null )
+ {
+ recursiveCurrentDataElements.addAll( rule.getCurrentDataElements() );
+ }
+ }
+
+ return recursiveCurrentDataElements;
+ }
+
+ /**
+ * Returns the right-side evaluated value of the validation rule.
+ *
+ * @param source organisation unit being evaluated
+ * @param periodTypeX period type being evaluated
+ * @param period period being evaluated
+ * @param rule ValidationRule being evaluated
+ * @param currentValueMap current values already fetched
+ * @param sourceDataElements the data elements collected by the organisation
+ * unit
+ * @param context the validation run context
+ * @return the right-side value
+ */
+ private Double getRightSideValue( OrganisationUnit source, PeriodTypeExtended periodTypeX, Period period,
+ ValidationRule rule, Map<DataElementOperand, Double> currentValueMap,
+ Collection<DataElement> sourceDataElements, ValidationRunContext context )
+ {
+ Double rightSideValue = null;
+
+ // If ruleType is VALIDATION, the right side is evaluated using the same
+ // (current) data values.
+ // If ruleType is MONITORING but there are no data elements in the right
+ // side, then it doesn't matter
+ // what data values we use, so just supply the current data values in
+ // order to evaluate the (constant) expression.
+
+ if ( ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )
+ || rule.getRightSide().getDataElementsInExpression().isEmpty() )
+ {
+ rightSideValue = expressionService.getExpressionValue( rule.getRightSide(), currentValueMap,
+ context.constantMap, null );
+ }
+ else
+ // ruleType equals MONITORING, and there are some data elements in the
+ // right side expression
+ {
+ CalendarPeriodType calendarPeriodType = getCalendarPeriodType( period.getPeriodType() );
+
+ if ( calendarPeriodType != null )
+ {
+ Collection<PeriodType> rightSidePeriodTypes = getAllowedPeriodTypesForDataElements(
+ rule.getPastDataElements(), rule.getPeriodType() );
+ List<Double> sampleValues = new ArrayList<Double>();
+ Calendar yearlyCalendar = PeriodType.createCalendarInstance( period.getStartDate() );
+ int annualSampleCount = rule.getAnnualSampleCount() == null ? 0 : rule.getAnnualSampleCount();
+ int sequentialSampleCount = rule.getSequentialSampleCount() == null ? 0 : rule
+ .getSequentialSampleCount();
+
+ for ( int annualCount = 0; annualCount <= annualSampleCount; annualCount++ )
+ {
+
+ // Defensive copy because createPeriod mutates Calendar.
+ Calendar calCopy = PeriodType.createCalendarInstance( yearlyCalendar.getTime() );
- leftSide = expressionService.getExpressionValue( validationRule.getLeftSide(), valueMap, constantMap, null );
-
- if ( leftSide != null || Operator.compulsory_pair.equals( operator ) )
+ // To track the period at the same time in preceding years.
+ Period yearlyPeriod = calendarPeriodType.createPeriod( calCopy );
+
+ // For past years, fetch the period at the same time of year
+ // as this period,
+ // and any periods after this period within the
+ // sequentialPeriod limit.
+ // For the year of the stating period, we will only fetch
+ // previous sequential periods.
+
+ if ( annualCount > 0 )
{
- rightSide = expressionService.getExpressionValue( validationRule.getRightSide(), valueMap, constantMap, null );
+ // Fetch the period at the same time of year as the
+ // starting period.
+ evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes, yearlyPeriod,
+ rule, sourceDataElements, context );
- if ( rightSide != null || Operator.compulsory_pair.equals( operator ) )
+ // Fetch the sequential periods after this prior-year
+ // period.
+ Period sequentialPeriod = new Period( yearlyPeriod );
+ for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
{
- if ( Operator.compulsory_pair.equals( operator ) )
- {
- violation = ( leftSide != null && rightSide == null ) || ( leftSide == null && rightSide != null );
- }
- else if ( leftSide != null && rightSide != null )
- {
- violation = !expressionIsTrue( leftSide, operator, rightSide );
- }
-
- if ( violation )
- {
- validationViolations.add( new ValidationResult( period, unit, validationRule,
- getRounded( zeroIfNull( leftSide ), DECIMALS ), getRounded( zeroIfNull( rightSide ), DECIMALS ) ) );
- }
+ sequentialPeriod = calendarPeriodType.getNextPeriod( sequentialPeriod );
+ evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
+ sequentialPeriod, rule, sourceDataElements, context );
}
}
- }
- }
- }
-
- return validationViolations;
- }
-
- /**
- * Returns all validation rules which have data elements assigned to it
+
+ // Fetch the seqential periods before this period (both this
+ // year and past years):
+ Period sequentialPeriod = new Period( yearlyPeriod );
+ for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
+ {
+ sequentialPeriod = calendarPeriodType.getPreviousPeriod( sequentialPeriod );
+ evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
+ sequentialPeriod, rule, sourceDataElements, context );
+ }
+
+ // Move to the previous year:
+ yearlyCalendar.set( Calendar.YEAR, yearlyCalendar.get( Calendar.YEAR ) - 1 );
+ }
+
+ rightSideValue = rightSideAverage( rule, sampleValues, annualSampleCount, sequentialSampleCount );
+ }
+ }
+ return rightSideValue;
+ }
+
+ /**
+ * Evaluates the right side of a monitoring-type validation rule for a given
+ * organisation unit and period, and adds the value to a list of sample
+ * values.
+ *
+ * Note that for a monitoring-type rule, evaluating the right side
+ * expression can result in sampling multiple periods and/or child
+ * organisation units.
+ *
+ * @param periodTypeX the period type extended information
+ * @param sampleValues the list of sample values to add to
+ * @param source the organisation unit
+ * @param allowedPeriodTypes the period types in which the data may exist
+ * @param period the main period for the validation rule evaluation
+ * @param rule the monitoring-type rule being evaluated
+ * @param sourceDataElements the data elements configured for this
+ * organisation unit
+ * @param context the evaluation run context
+ */
+ private void evaluateRightSidePeriod( PeriodTypeExtended periodTypeX, List<Double> sampleValues,
+ OrganisationUnit source, Collection<PeriodType> allowedPeriodTypes, Period period, ValidationRule rule,
+ Collection<DataElement> sourceDataElements, ValidationRunContext context )
+ {
+ Period periodInstance = periodService.getPeriod( period.getStartDate(), period.getEndDate(),
+ period.getPeriodType() );
+
+ if ( periodInstance != null )
+ {
+ Set<DataElement> dataElements = rule.getRightSide().getDataElementsInExpression();
+ Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
+ Map<DataElementOperand, Double> dataValueMap = getDataValueMapRecursive( periodTypeX, dataElements,
+ sourceDataElements, dataElements, allowedPeriodTypes, period, source, null, incompleteValues );
+ Double value = expressionService.getExpressionValue( rule.getRightSide(), dataValueMap,
+ context.constantMap, null, incompleteValues );
+
+ if ( value != null )
+ {
+ sampleValues.add( value );
+ }
+ }
+ }
+
+ /**
+ * Finds the average right-side sample value. This is used as the right-side
+ * expression value to evaluate a monitoring-type rule.
+ *
+ * @param rule monitoring-type rule being evaluated
+ * @param sampleValues sample values actually collected
+ * @param annualSampleCount number of annual samples tried for
+ * @param sequentialSampleCount number of sequential samples tried for
+ * @return
+ */
+ Double rightSideAverage( ValidationRule rule, List<Double> sampleValues, int annualSampleCount,
+ int sequentialSampleCount )
+ {
+ // Find the expected sample count for the last period of its type in the
+ // database: sequentialSampleCount for the immediately preceding periods
+ // in this year and for every past year: one sample for the same period
+ // in that year, plus sequentialSampleCounts before and after.
+ Double average = null;
+ if ( !sampleValues.isEmpty() )
+ {
+ int expectedSampleCount = sequentialSampleCount + annualSampleCount * (1 + 2 * sequentialSampleCount);
+ int highOutliers = rule.getHighOutliers() == null ? 0 : rule.getHighOutliers();
+ int lowOutliers = rule.getLowOutliers() == null ? 0 : rule.getLowOutliers();
+
+ // If we had fewer than the expected number of samples, then scale
+ // back
+ if ( highOutliers + lowOutliers > sampleValues.size() )
+ {
+ highOutliers = (highOutliers * sampleValues.size()) / expectedSampleCount;
+ lowOutliers = (lowOutliers * sampleValues.size()) / expectedSampleCount;
+ }
+
+ // If we (still) have any high and/or low outliers to remove, then
+ // sort the sample values and remove the high and/or low outliers.
+ if ( highOutliers + lowOutliers > 0 )
+ {
+ Collections.sort( sampleValues );
+ log.trace( "Removing " + highOutliers + " high and " + lowOutliers + " low outliers from "
+ + Arrays.toString( sampleValues.toArray() ) );
+ sampleValues = sampleValues.subList( lowOutliers, sampleValues.size() - highOutliers );
+ log.trace( "Result: " + Arrays.toString( sampleValues.toArray() ) );
+ }
+ Double sum = 0.0;
+ for ( Double sample : sampleValues )
+ {
+ sum += sample;
+ }
+ average = sum / sampleValues.size();
+ }
+ return average;
+ }
+
+ /**
+ * Gets data values for a given organisation unit and period, recursing if
+ * necessary to sum the values from child organisation units.
+ *
+ * @param periodTypeX period type which we are evaluating
+ * @param ruleDataElements data elements configured for the rule
+ * @param sourceDataElements data elements configured for the organisation
+ * unit
+ * @param recursiveDataElements data elements for which we will recurse if
+ * necessary
+ * @param allowedPeriodTypes all the periods in which we might find the data
+ * values
+ * @param period period in which we are looking for values
+ * @param source organisation unit for which we are looking for values
+ * @param lastUpdatedMap map showing when each data values was last updated
+ * @param incompleteValues ongoing list showing which values were found but
+ * not from all children
+ * @return the map of values found
+ */
+ private Map<DataElementOperand, Double> getDataValueMapRecursive( PeriodTypeExtended periodTypeX,
+ Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
+ Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
+ OrganisationUnit source, Map<DataElementOperand, Date> lastUpdatedMap, Set<DataElementOperand> incompleteValues )
+ {
+ Set<DataElement> dataElementsToGet = new HashSet<DataElement>( ruleDataElements );
+ dataElementsToGet.retainAll( sourceDataElements );
+ log.trace( "getDataValueMapRecursive: source:" + source.getName() + " elementsToGet["
+ + dataElementsToGet.size() + "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
+
+ Map<DataElementOperand, Double> dataValueMap;
+
+ if ( dataElementsToGet.isEmpty() )
+ {
+ // We still might get something recursively
+ dataValueMap = new HashMap<DataElementOperand, Double>();
+ }
+ else
+ {
+ dataValueMap = dataValueService.getDataValueMap( dataElementsToGet, period.getStartDate(), source,
+ allowedPeriodTypes, lastUpdatedMap );
+ }
+
+ // See if there are any data elements we need to get recursively:
+ Set<DataElement> recursiveDataElementsNeeded = new HashSet<DataElement>( recursiveDataElements );
+ recursiveDataElementsNeeded.removeAll( dataElementsToGet );
+ if ( !recursiveDataElementsNeeded.isEmpty() )
+ {
+ int childCount = 0;
+ Map<DataElementOperand, Integer> childValueCounts = new HashMap<DataElementOperand, Integer>();
+
+ for ( OrganisationUnit child : source.getChildren() )
+ {
+ Collection<DataElement> childDataElements = periodTypeX.sourceDataElements.get( child );
+ Map<DataElementOperand, Double> childMap = getDataValueMapRecursive( periodTypeX,
+ recursiveDataElementsNeeded, childDataElements, recursiveDataElementsNeeded, allowedPeriodTypes,
+ period, child, lastUpdatedMap, incompleteValues );
+
+ for ( DataElementOperand deo : childMap.keySet() )
+ {
+ Double baseValue = dataValueMap.get( deo );
+ dataValueMap.put( deo, baseValue == null ? childMap.get( deo ) : baseValue + childMap.get( deo ) );
+
+ Integer childValueCount = childValueCounts.get( deo );
+ childValueCounts.put( deo, childValueCount == null ? 1 : childValueCount + 1 );
+ }
+
+ childCount++;
+ }
+
+ for ( Map.Entry<DataElementOperand, Integer> entry : childValueCounts.entrySet() )
+ {
+ if ( childCount != entry.getValue() )
+ {
+ // Found this DataElementOperand value in some but not all children
+ incompleteValues.add( entry.getKey() );
+ }
+ }
+ }
+
+ return dataValueMap;
+ }
+
+ /**
+ * Returns all validation-type rules which have specified data elements
+ * assigned to them.
+ *
+ * @param dataElements the data elements to look for
+ * @return all validation rules which have the data elements assigned to
+ * them
+ */
+ private Collection<ValidationRule> getValidationTypeRulesForDataElements( Set<DataElement> dataElements )
+ {
+ Set<ValidationRule> rulesForDataElements = new HashSet<ValidationRule>();
+
+ Set<DataElement> validationRuleElements = new HashSet<DataElement>();
+
+ for ( ValidationRule validationRule : getAllValidationRules() )
+ {
+ if ( validationRule.getRuleType().equals( ValidationRule.RULE_TYPE_VALIDATION ) )
+ {
+ validationRuleElements.clear();
+ validationRuleElements.addAll( validationRule.getLeftSide().getDataElementsInExpression() );
+ validationRuleElements.addAll( validationRule.getRightSide().getDataElementsInExpression() );
+
+ if ( dataElements.containsAll( validationRuleElements ) )
+ {
+ rulesForDataElements.add( validationRule );
+ }
+ }
+ }
+
+ return rulesForDataElements;
+ }
+
+ /**
+ * Returns all validation rules which have data elements assigned to them
* which are members of the given data set.
*
- * @param dataSet the data set.
- * @return all validation rules which have data elements assigned to it
- * which are members of the given data set.
+ * @param dataSet the data set
+ * @return all validation rules which have data elements assigned to them
+ * which are members of the given data set
*/
- private Collection<ValidationRule> getRelevantValidationRules( Set<DataElement> dataElements )
+ private Collection<ValidationRule> getRulesForDataSet( DataSet dataSet )
{
- Set<ValidationRule> relevantValidationRules = new HashSet<ValidationRule>();
-
- Set<DataElement> validationRuleElements = new HashSet<DataElement>();
-
- for ( ValidationRule validationRule : getAllValidationRules() )
- {
- validationRuleElements.clear();
- validationRuleElements.addAll( validationRule.getLeftSide().getDataElementsInExpression() );
- validationRuleElements.addAll( validationRule.getRightSide().getDataElementsInExpression() );
-
- if ( dataElements.containsAll( validationRuleElements ) )
- {
- relevantValidationRules.add( validationRule );
- }
- }
+ Set<ValidationRule> rulesForDataSet = new HashSet<ValidationRule>();
- return relevantValidationRules;
- }
-
- public Collection<ValidationRule> getRelevantValidationRules( DataSet dataSet )
- {
- Set<ValidationRule> relevantValidationRules = new HashSet<ValidationRule>();
-
Set<DataElementOperand> operands = dataEntryFormService.getOperandsInDataEntryForm( dataSet );
-
+
Set<DataElementOperand> validationRuleOperands = new HashSet<DataElementOperand>();
-
- for ( ValidationRule validationRule : getAllValidationRules() )
- {
- validationRuleOperands.clear();
- validationRuleOperands.addAll( expressionService.getOperandsInExpression( validationRule.getLeftSide().getExpression() ) );
- validationRuleOperands.addAll( expressionService.getOperandsInExpression( validationRule.getRightSide().getExpression() ) );
-
- if ( operands.containsAll( validationRuleOperands ) )
- {
- relevantValidationRules.add( validationRule );
- }
- }
-
- return relevantValidationRules;
- }
-
- /**
- * Returns all validation rules referred to in the left and right side expressions
- * of the given validation rules.
- *
- * @param validationRules the validation rules.
- * @return a collection of data elements.
- */
- private Set<DataElement> getDataElementsInValidationRules( Collection<ValidationRule> validationRules )
- {
- Set<DataElement> dataElements = new HashSet<DataElement>();
-
- for ( ValidationRule rule : validationRules )
- {
- dataElements.addAll( rule.getLeftSide().getDataElementsInExpression() );
- dataElements.addAll( rule.getRightSide().getDataElementsInExpression() );
- }
-
- return dataElements;
- }
-
+
+ for ( ValidationRule rule : getAllValidationRules() )
+ {
+ if ( rule.getRuleType().equals( ValidationRule.RULE_TYPE_VALIDATION ) )
+ {
+ validationRuleOperands.clear();
+ validationRuleOperands.addAll( expressionService.getOperandsInExpression( rule.getLeftSide()
+ .getExpression() ) );
+ validationRuleOperands.addAll( expressionService.getOperandsInExpression( rule.getRightSide()
+ .getExpression() ) );
+
+ if ( operands.containsAll( validationRuleOperands ) )
+ {
+ rulesForDataSet.add( rule );
+ }
+ }
+ }
+
+ return rulesForDataSet;
+ }
+
+ /**
+ * Finds all period types that may contain given data elements, whose period
+ * type interval is at least as long as the given period type.
+ *
+ * @param dataElements data elements to look for
+ * @param periodType the minimum-length period type
+ * @return all period types that are allowed for these data elements
+ */
+ private Collection<PeriodType> getAllowedPeriodTypesForDataElements( Collection<DataElement> dataElements,
+ PeriodType periodType )
+ {
+ Collection<PeriodType> allowedPeriodTypes = new HashSet<PeriodType>();
+ for ( DataElement dataElement : dataElements )
+ {
+ for ( DataSet dataSet : dataElement.getDataSets() )
+ {
+ if ( dataSet.getPeriodType().getFrequencyOrder() >= periodType.getFrequencyOrder() )
+ {
+ allowedPeriodTypes.add( dataSet.getPeriodType() );
+ }
+ }
+ }
+ return allowedPeriodTypes;
+ }
+
+ /**
+ * Returns an instance of type CalendarPeriodType that matches the specified
+ * periodType. This can be needed in order to access the calendar-computing
+ * methods that are available in a CalendarPeriodType but not an ordinary
+ * PeriodType.
+ *
+ * Note: Perhaps this should be moved to PeriodService. Or perhaps some
+ * refactoring can be done in the relationship between PeriodType and
+ * CalendarPeriodType.
+ *
+ * @param periodType the period type of interest
+ * @return the corresponding CalendarPeriodType
+ */
+ private CalendarPeriodType getCalendarPeriodType( PeriodType periodType )
+ {
+ for ( PeriodType p : PeriodType.PERIOD_TYPES )
+ {
+ if ( periodType.getName().equals( p.getName() ) )
+ {
+ if ( p instanceof CalendarPeriodType )
+ {
+ return (CalendarPeriodType) p;
+ }
+ else
+ {
+ log.error( "DefaultValidationRuleService.getCalendarPeriodType() - PeriodType.PERIOD_TYPES ["
+ + p.getName() + "] is not a CalendarPeriodType!" );
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * At the end of an ALERT run, post messages to the users who want to see
+ * the results.
+ *
+ * @param validationResults the set of validation error results
+ * @param alertRunStart the date/time when the alert run started
+ */
+ private void postAlerts( Collection<ValidationResult> validationResults, Date alertRunStart )
+ {
+ SortedSet<ValidationResult> results = new TreeSet<ValidationResult>( validationResults );
+
+ // Find out which users receive alerts, and which ValidationRules they
+ // receive alerts for.
+ HashMap<User, Collection<ValidationRule>> userRulesMap = new HashMap<User, Collection<ValidationRule>>();
+ for ( ValidationRuleGroup validationRuleGroup : getAllValidationRuleGroups() )
+ {
+ Collection<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert();
+ if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() )
+ {
+ for ( UserAuthorityGroup role : userRolesToAlert )
+ {
+ for ( UserCredentials userCredentials : role.getMembers() )
+ {
+ User user = userCredentials.getUser();
+ Collection<ValidationRule> userRules = userRulesMap.get( user );
+ if ( userRules == null )
+ {
+ userRules = new ArrayList<ValidationRule>();
+ userRulesMap.put( user, userRules );
+ }
+ userRules.addAll( validationRuleGroup.getMembers() );
+ }
+ }
+ }
+ }
+
+ // We will create one message for each set of users who receive the same
+ // subset
+ // of results. (Not necessarily the same as the set of users who receive
+ // alerts
+ // from the same subset of validation rules -- because some of these
+ // rules
+ // may return no results.) This saves on message storage space.
+
+ // TODO: Encapsulate this in another level of Map by the user's
+ // language, and
+ // generate a message for each combination of ( target language, set of
+ // results )
+ Map<List<ValidationResult>, Set<User>> messageMap = new HashMap<List<ValidationResult>, Set<User>>();
+
+ for ( User user : userRulesMap.keySet() )
+ {
+ // For each user who receives alerts, find the subset of results
+ // from this run.
+ Collection<ValidationRule> userRules = userRulesMap.get( user );
+ List<ValidationResult> userResults = new ArrayList<ValidationResult>();
+ Map<String, Integer> importanceSummary = new HashMap<String, Integer>();
+
+ for ( ValidationResult result : results )
+ {
+ if ( userRules.contains( result.getValidationRule() ) )
+ {
+ userResults.add( result );
+ String importance = result.getValidationRule().getImportance();
+ Integer importanceCount = importanceSummary.get( importance );
+ if ( importanceCount == null )
+ {
+ importanceSummary.put( importance, 1 );
+ }
+ else
+ {
+ importanceSummary.put( importance, importanceCount + 1 );
+ }
+ }
+ }
+
+ // Group this user with other users who have the same subset of
+ // results.
+ if ( !userResults.isEmpty() )
+ {
+ Set<User> messageReceivers = messageMap.get( userResults );
+ if ( messageReceivers == null )
+ {
+ messageReceivers = new HashSet<User>();
+ messageMap.put( userResults, messageReceivers );
+ }
+ messageReceivers.add( user );
+ }
+ }
+
+ // For each unique subset of results, send a message to all users
+ // receiving that subset of results.
+ for ( Map.Entry<List<ValidationResult>, Set<User>> entry : messageMap.entrySet() )
+ {
+ sendAlertmessage( entry.getKey(), entry.getValue(), alertRunStart );
+ }
+ }
+
+ /**
+ * Generate and send an alert message containing alert results to a set of
+ * users.
+ *
+ * @param results results to put in this message
+ * @param users users to receive these results
+ * @param alertRunStart date/time when the alert run started
+ */
+ private void sendAlertmessage( List<ValidationResult> results, Set<User> users, Date alertRunStart )
+ {
+ StringBuilder messageBuilder = new StringBuilder();
+ SimpleDateFormat dateTimeFormatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm" );
+
+ // Count the number of messages of each importance type.
+ Map<String, Integer> importanceCountMap = new HashMap<String, Integer>();
+ for ( ValidationResult result : results )
+ {
+ Integer importanceCount = importanceCountMap.get( result.getValidationRule().getImportance() );
+ importanceCountMap.put( result.getValidationRule().getImportance(), importanceCount == null ? 1
+ : importanceCount + 1 );
+ }
+
+ // Construct the subject line.
+ String subject = "DHIS alerts as of " + dateTimeFormatter.format( alertRunStart ) + " - priority High "
+ + (importanceCountMap.get( "high" ) == null ? 0 : importanceCountMap.get( "high" )) + ", Medium "
+ + (importanceCountMap.get( "medium" ) == null ? 0 : importanceCountMap.get( "medium" )) + ", Low "
+ + (importanceCountMap.get( "low" ) == null ? 0 : importanceCountMap.get( "low" ));
+
+ // Construct the text of the message.
+ messageBuilder.append( "<html>\n" ).append( "<head>\n" )
+ .append( "</head>\n" )
+ .append( "<body>\n" )
+ .append( subject )
+ .append( "\n" )
+ // Repeat the subject line at the start of the message.
+ .append( "<br />\n" ).append( "<table>\n" ).append( " <tr>\n" ).append( " <th>Organisation Unit</th>\n" )
+ .append( " <th>Period</th>\n" ).append( " <th>Importance</th>\n" )
+ .append( " <th>Left side description</th>\n" ).append( " <th>Value</th>\n" )
+ .append( " <th>Operator</th>\n" ).append( " <th>Value</th>\n" )
+ .append( " <th>Right side description</th>\n" ).append( " </tr>\n" );
+
+ for ( ValidationResult result : results )
+ {
+ ValidationRule rule = result.getValidationRule();
+
+ messageBuilder.append( " <tr>\n" ).append( " <td>" ).append( result.getSource().getName() )
+ .append( "<\td>\n" ).append( " <td>" ).append( result.getPeriod().getName() ).append( "<\td>\n" )
+ .append( " <td>" ).append( rule.getImportance() ).append( "<\td>\n" ).append( " <td>" )
+ .append( rule.getLeftSide().getDescription() ).append( "<\td>\n" ).append( " <td>" )
+ .append( result.getLeftsideValue() ).append( "<\td>\n" ).append( " <td>" )
+ .append( rule.getOperator().toString() ).append( "<\td>\n" ).append( " <td>" )
+ .append( result.getRightsideValue() ).append( "<\td>\n" ).append( " <td>" )
+ .append( rule.getRightSide().getDescription() ).append( "<\td>\n" ).append( " </tr>\n" );
+ }
+
+ messageBuilder.append( "</table>\n" ).append( "</body>\n" ).append( "</html>\n" );
+
+ String messageText = messageBuilder.toString();
+
+ log.info( "postUserResults() users[" + users.size() + "] subject " + subject );
+ messageService.sendMessage( subject, messageText, null, users );
+ }
+
// -------------------------------------------------------------------------
// ValidationRule CRUD operations
// -------------------------------------------------------------------------
@@ -437,10 +1472,10 @@
}
public Collection<ValidationRule> getValidationRulesByName( String name )
- {
+ {
return getObjectsByName( i18nService, validationRuleStore, name );
}
-
+
public Collection<ValidationRule> getValidationRulesByDataElements( Collection<DataElement> dataElements )
{
return i18n( i18nService, validationRuleStore.getValidationRulesByDataElements( dataElements ) );
@@ -493,12 +1528,12 @@
public ValidationRuleGroup getValidationRuleGroup( int id, boolean i18nValidationRules )
{
ValidationRuleGroup group = getValidationRuleGroup( id );
-
+
if ( i18nValidationRules )
{
i18n( i18nService, group.getMembers() );
}
-
+
return group;
}
@@ -506,7 +1541,7 @@
{
return i18n( i18nService, validationRuleGroupStore.getByUid( uid ) );
}
-
+
public Collection<ValidationRuleGroup> getAllValidationRuleGroups()
{
return i18n( i18nService, validationRuleGroupStore.getAll() );
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml 2013-10-08 13:19:54 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml 2013-10-08 17:20:57 +0000
@@ -433,8 +433,11 @@
<property name="expressionService" ref="org.hisp.dhis.expression.ExpressionService" />
<property name="dataEntryFormService" ref="org.hisp.dhis.dataentryform.DataEntryFormService" />
<property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
+ <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
+ <property name="systemSettingManager" ref="org.hisp.dhis.setting.SystemSettingManager" />
<property name="constantService" ref="org.hisp.dhis.constant.ConstantService" />
<property name="dataValueService" ref="org.hisp.dhis.datavalue.DataValueService" />
+ <property name="messageService" ref="org.hisp.dhis.message.MessageService" />
<property name="i18nService" ref="org.hisp.dhis.i18n.I18nService" />
</bean>
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml 2013-02-07 10:25:34 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml 2013-10-08 17:20:57 +0000
@@ -19,6 +19,10 @@
<property name="description" type="text" />
+ <property name="importance" length="16" />
+
+ <property name="ruleType" column="ruletype" length="16" />
+
<property name="type" />
<property name="operator" type="org.hisp.dhis.expression.OperatorUserType">
@@ -35,9 +39,19 @@
<key column="validationruleid" />
<many-to-many class="org.hisp.dhis.validation.ValidationRuleGroup" column="validationgroupid" />
</set>
-
+
+ <property name="organisationUnitLevel" column="organisationunitlevel" />
+
<many-to-one name="periodType" class="org.hisp.dhis.period.PeriodType" column="periodtypeid"
foreign-key="fk_validationrule_periodtypeid" />
+ <property name="sequentialSampleCount" column="sequentialsamplecount" />
+
+ <property name="annualSampleCount" column="annualsamplecount" />
+
+ <property name="highOutliers" column="highoutliers" />
+
+ <property name="lowOutliers" column="lowoutliers" />
+
</class>
</hibernate-mapping>
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml 2013-06-05 12:02:23 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml 2013-09-27 17:05:36 +0000
@@ -25,5 +25,11 @@
foreign-key="fk_validationrulegroup_validationruleid" />
</set>
+ <set name="userAuthorityGroupsToAlert" table="validationrulegroupuserrolestoalert">
+ <key column="validationgroupid" foreign-key="fk_validationrulegroupuserrolestoalert_validationgroupid" />
+ <many-to-many class="org.hisp.dhis.user.UserAuthorityGroup" column="userroleid"
+ foreign-key="fk_validationrulegroupuserrolestoalert_userroleid" />
+ </set>
+
</class>
</hibernate-mapping>
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java'
--- dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java 2013-09-30 11:54:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java 2013-10-08 17:20:57 +0000
@@ -43,7 +43,7 @@
import java.util.HashSet;
import java.util.Set;
-import org.hisp.dhis.DhisSpringTest;
+import org.hisp.dhis.DhisTest;
import org.hisp.dhis.dataelement.DataElement;
import org.hisp.dhis.dataelement.DataElementCategoryCombo;
import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
@@ -60,6 +60,8 @@
import org.hisp.dhis.period.Period;
import org.hisp.dhis.period.PeriodService;
import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.period.WeeklyPeriodType;
+import org.hisp.dhis.period.YearlyPeriodType;
import org.hisp.dhis.system.util.MathUtils;
import org.junit.Test;
@@ -68,7 +70,7 @@
* @version $Id$
*/
public class ValidationRuleServiceTest
- extends DhisSpringTest
+ extends DhisTest
{
private DataElement dataElementA;
@@ -78,12 +80,16 @@
private DataElement dataElementD;
+ private DataElement dataElementE;
+
private Set<DataElement> dataElementsA = new HashSet<DataElement>();
private Set<DataElement> dataElementsB = new HashSet<DataElement>();
private Set<DataElement> dataElementsC = new HashSet<DataElement>();
+ private Set<DataElement> dataElementsD = new HashSet<DataElement>();
+
private Set<DataElementCategoryOptionCombo> optionCombos;
private DataElementCategoryCombo categoryCombo;
@@ -96,16 +102,56 @@
private Expression expressionC;
- private DataSet dataSet;
+ private Expression expressionD;
+
+ private Expression expressionE;
+
+ private Expression expressionF;
+
+ private Expression expressionG;
+
+ private DataSet dataSetWeekly;
+
+ private DataSet dataSetMonthly;
+
+ private DataSet dataSetYearly;
private Period periodA;
private Period periodB;
+ private Period periodC;
+
+ private Period periodD;
+
+ private Period periodE;
+
+ private Period periodF;
+
+ private Period periodG;
+
+ private Period periodH;
+
+ private Period periodI;
+
+ private Period periodX;
+
+ private Period periodY;
+
private OrganisationUnit sourceA;
private OrganisationUnit sourceB;
+ private OrganisationUnit sourceC;
+
+ private OrganisationUnit sourceD;
+
+ private OrganisationUnit sourceE;
+
+ private OrganisationUnit sourceF;
+
+ private OrganisationUnit sourceG;
+
private Set<OrganisationUnit> sourcesA = new HashSet<OrganisationUnit>();
private ValidationRule validationRuleA;
@@ -116,9 +162,27 @@
private ValidationRule validationRuleD;
+ private ValidationRule monitoringRuleE;
+
+ private ValidationRule monitoringRuleF;
+
+ private ValidationRule monitoringRuleG;
+
+ private ValidationRule monitoringRuleH;
+
+ private ValidationRule monitoringRuleI;
+
+ private ValidationRule monitoringRuleJ;
+
+ private ValidationRule monitoringRuleK;
+
private ValidationRuleGroup group;
- private PeriodType periodType;
+ private PeriodType periodTypeWeekly;
+
+ private PeriodType periodTypeMonthly;
+
+ private PeriodType periodTypeYearly;
// -------------------------------------------------------------------------
// Fixture
@@ -144,23 +208,29 @@
periodService = (PeriodService) getBean( PeriodService.ID );
- periodType = new MonthlyPeriodType();
+ periodTypeWeekly = new WeeklyPeriodType();
+ periodTypeMonthly = new MonthlyPeriodType();
+ periodTypeYearly = new YearlyPeriodType();
dataElementA = createDataElement( 'A' );
dataElementB = createDataElement( 'B' );
dataElementC = createDataElement( 'C' );
dataElementD = createDataElement( 'D' );
+ dataElementE = createDataElement( 'E' );
dataElementService.addDataElement( dataElementA );
dataElementService.addDataElement( dataElementB );
dataElementService.addDataElement( dataElementC );
dataElementService.addDataElement( dataElementD );
+ dataElementService.addDataElement( dataElementE );
dataElementsA.add( dataElementA );
dataElementsA.add( dataElementB );
dataElementsB.add( dataElementC );
dataElementsB.add( dataElementD );
dataElementsC.add( dataElementB );
+ dataElementsD.add( dataElementB );
+ dataElementsD.add( dataElementE );
categoryCombo = categoryService
.getDataElementCategoryComboByName( DataElementCategoryCombo.DEFAULT_CATEGORY_COMBO_NAME );
@@ -177,56 +247,137 @@
expressionB = new Expression( "#{" + dataElementC.getUid() + suffix + "} - #{" + dataElementD.getUid() + suffix + "}",
"descriptionB", dataElementsB , optionCombos);
expressionC = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 2", "descriptionC", dataElementsC, optionCombos );
+ expressionD = new Expression( "#{" + dataElementB.getUid() + suffix + "}", "descriptionD", dataElementsC, optionCombos );
+ expressionE = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 1.25", "descriptionE", dataElementsC, optionCombos );
+ expressionF = new Expression( "#{" + dataElementB.getUid() + suffix + "} / #{" + dataElementE.getUid() + suffix + "}",
+ "descriptionF", dataElementsD, optionCombos );
+ expressionG = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 1.25 / #{" + dataElementE.getUid() + suffix + "}",
+ "descriptionG", dataElementsD, optionCombos );
expressionService.addExpression( expressionA );
expressionService.addExpression( expressionB );
expressionService.addExpression( expressionC );
-
- periodA = createPeriod( periodType, getDate( 2000, 3, 1 ), getDate( 2000, 3, 31 ) );
- periodB = createPeriod( periodType, getDate( 2000, 4, 1 ), getDate( 2000, 4, 30 ) );
-
- dataSet = createDataSet( 'A', periodType );
+ expressionService.addExpression( expressionD );
+ expressionService.addExpression( expressionE );
+ expressionService.addExpression( expressionF );
+ expressionService.addExpression( expressionG );
+
+ periodA = createPeriod( periodTypeMonthly, getDate( 2000, 3, 1 ), getDate( 2000, 3, 31 ) );
+ periodB = createPeriod( periodTypeMonthly, getDate( 2000, 4, 1 ), getDate( 2000, 4, 30 ) );
+ periodC = createPeriod( periodTypeMonthly, getDate( 2000, 5, 1 ), getDate( 2000, 5, 31 ) );
+ periodD = createPeriod( periodTypeMonthly, getDate( 2000, 6, 1 ), getDate( 2000, 6, 31 ) );
+ periodE = createPeriod( periodTypeMonthly, getDate( 2001, 2, 1 ), getDate( 2001, 2, 28 ) );
+ periodF = createPeriod( periodTypeMonthly, getDate( 2001, 3, 1 ), getDate( 2001, 3, 31 ) );
+ periodG = createPeriod( periodTypeMonthly, getDate( 2001, 4, 1 ), getDate( 2001, 4, 30 ) );
+ periodH = createPeriod( periodTypeMonthly, getDate( 2001, 5, 1 ), getDate( 2001, 5, 31 ) );
+ periodI = createPeriod( periodTypeWeekly, getDate( 2000, 4, 1 ), getDate( 2000, 4, 30 ) );
+ periodX = createPeriod( periodTypeYearly, getDate( 2000, 1, 1 ), getDate( 2000, 12, 31 ) );
+ periodY = createPeriod( periodTypeYearly, getDate( 2001, 1, 1 ), getDate( 2001, 12, 31 ) );
+
+ dataSetWeekly = createDataSet( 'W', periodTypeWeekly );
+ dataSetMonthly = createDataSet( 'M', periodTypeMonthly );
+ dataSetYearly = createDataSet( 'Y', periodTypeYearly );
sourceA = createOrganisationUnit( 'A' );
sourceB = createOrganisationUnit( 'B' );
+ sourceC = createOrganisationUnit( 'C', sourceB );
+ sourceD = createOrganisationUnit( 'D', sourceB );
+ sourceE = createOrganisationUnit( 'E', sourceD );
+ sourceF = createOrganisationUnit( 'F', sourceD );
+ sourceG = createOrganisationUnit( 'G' );
- sourceA.getDataSets().add( dataSet );
- sourceB.getDataSets().add( dataSet );
+ sourceA.getDataSets().add( dataSetMonthly );
+ sourceB.getDataSets().add( dataSetMonthly );
+ sourceC.getDataSets().add( dataSetWeekly );
+ sourceC.getDataSets().add( dataSetMonthly );
+ sourceC.getDataSets().add( dataSetYearly );
+ sourceD.getDataSets().add( dataSetMonthly );
+ sourceD.getDataSets().add( dataSetYearly );
+ sourceE.getDataSets().add( dataSetMonthly );
+ sourceE.getDataSets().add( dataSetYearly );
+ sourceF.getDataSets().add( dataSetMonthly );
+ sourceF.getDataSets().add( dataSetYearly );
organisationUnitService.addOrganisationUnit( sourceA );
organisationUnitService.addOrganisationUnit( sourceB );
-
+ organisationUnitService.addOrganisationUnit( sourceC );
+ organisationUnitService.addOrganisationUnit( sourceD );
+ organisationUnitService.addOrganisationUnit( sourceE );
+ organisationUnitService.addOrganisationUnit( sourceF );
+
sourcesA.add( sourceA );
sourcesA.add( sourceB );
- dataSet.getDataElements().add( dataElementA );
- dataSet.getDataElements().add( dataElementB );
- dataSet.getDataElements().add( dataElementC );
- dataSet.getDataElements().add( dataElementD );
-
- dataSet.getSources().add( sourceA );
- dataSet.getSources().add( sourceB );
-
- dataElementA.getDataSets().add( dataSet );
- dataElementB.getDataSets().add( dataSet );
- dataElementC.getDataSets().add( dataSet );
- dataElementD.getDataSets().add( dataSet );
-
- dataSetService.addDataSet( dataSet );
+ dataSetMonthly.getDataElements().add( dataElementA );
+ dataSetMonthly.getDataElements().add( dataElementB );
+ dataSetMonthly.getDataElements().add( dataElementC );
+ dataSetMonthly.getDataElements().add( dataElementD );
+
+ dataSetMonthly.getSources().add( sourceA );
+ dataSetMonthly.getSources().add( sourceB );
+ dataSetMonthly.getSources().add( sourceC );
+ dataSetMonthly.getSources().add( sourceD );
+ dataSetMonthly.getSources().add( sourceE );
+ dataSetMonthly.getSources().add( sourceF );
+ dataSetWeekly.getSources().add( sourceB );
+ dataSetWeekly.getSources().add( sourceC );
+ dataSetWeekly.getSources().add( sourceD );
+ dataSetWeekly.getSources().add( sourceE );
+ dataSetWeekly.getSources().add( sourceF );
+ dataSetWeekly.getSources().add( sourceG );
+ dataSetYearly.getSources().add( sourceB );
+ dataSetYearly.getSources().add( sourceC );
+ dataSetYearly.getSources().add( sourceD );
+ dataSetYearly.getSources().add( sourceE );
+ dataSetYearly.getSources().add( sourceF );
+
+ dataElementA.getDataSets().add( dataSetMonthly );
+ dataElementB.getDataSets().add( dataSetMonthly );
+ dataElementC.getDataSets().add( dataSetMonthly );
+ dataElementD.getDataSets().add( dataSetMonthly );
+
+ dataSetService.addDataSet( dataSetMonthly );
dataElementService.updateDataElement( dataElementA );
dataElementService.updateDataElement( dataElementB );
dataElementService.updateDataElement( dataElementC );
dataElementService.updateDataElement( dataElementD );
- validationRuleA = createValidationRule( 'A', equal_to, expressionA, expressionB, periodType );
- validationRuleB = createValidationRule( 'B', greater_than, expressionB, expressionC, periodType );
- validationRuleC = createValidationRule( 'C', less_than_or_equal_to, expressionB, expressionA, periodType );
- validationRuleD = createValidationRule( 'D', less_than, expressionA, expressionC, periodType );
+ validationRuleA = createValidationRule( 'A', equal_to, expressionA, expressionB, periodTypeMonthly );
+ validationRuleB = createValidationRule( 'B', greater_than, expressionB, expressionC, periodTypeMonthly );
+ validationRuleC = createValidationRule( 'C', less_than_or_equal_to, expressionB, expressionA, periodTypeMonthly );
+ validationRuleD = createValidationRule( 'D', less_than, expressionA, expressionC, periodTypeMonthly );
+
+ // Compare dataElementB with 1.25 times itself for one previous sequential period.
+ monitoringRuleE = createMonitoringRule( 'E', less_than, expressionD, expressionE, periodTypeMonthly, 1, 1, 0, 0, 0 );
+
+ // Compare dataElementB with 1.25 times itself for one previous annual period.
+ monitoringRuleF = createMonitoringRule( 'F', less_than, expressionD, expressionE, periodTypeMonthly, 1, 0, 1, 0, 0 );
+
+ // Compare dataElementB with 1.25 times itself for two previous and two annual sequential periods.
+ monitoringRuleG = createMonitoringRule( 'G', less_than, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 0, 0 );
+
+ // Compare dataElementB with 1.25 times itself for two previous and two annual sequential periods, discarding 2 high outliers.
+ monitoringRuleH = createMonitoringRule( 'H', less_than, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 2, 0 );
+
+ // Compare dataElementB with 1.25 times itself for two previous and two annual sequential periods, discarding 2 low outliers.
+ monitoringRuleI = createMonitoringRule( 'I', less_than, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 2, 0 );
+
+ // Compare dataElementB with 1.25 times itself for two previous and two annual sequential periods, discarding 2 high & 2 low outliers.
+ monitoringRuleJ = createMonitoringRule( 'J', less_than, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 2, 2 );
+
+ // Compare dataElements B/E with 1.25 * B/E for two previous and two annual sequential periods, no outlier discarding
+ monitoringRuleK = createMonitoringRule( 'K', less_than, expressionF, expressionG, periodTypeMonthly, 1, 2, 2, 0, 0 );
group = createValidationRuleGroup( 'A' );
}
+ @Override
+ public boolean emptyDatabaseAfterTest()
+ {
+ return true;
+ }
+
// -------------------------------------------------------------------------
// Business logic tests
// -------------------------------------------------------------------------
@@ -259,8 +410,11 @@
validationRuleService.saveValidationRule( validationRuleC ); // Valid
validationRuleService.saveValidationRule( validationRuleD ); // Valid
- Collection<ValidationResult> results = validationRuleService.validate( getDate( 2000, 2, 1 ), getDate( 2000, 6,
- 1 ), sourcesA );
+ // Note: in this and subsequent tests we insert the validation results collection into a new HashSet. This
+ // insures that if they are the same as the reference results, they will appear in the same order.
+
+ Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( getDate( 2000, 2, 1 ),
+ getDate( 2000, 6, 1 ), sourcesA ) );
Collection<ValidationResult> reference = new HashSet<ValidationResult>();
@@ -317,8 +471,8 @@
validationRuleService.addValidationRuleGroup( group );
- Collection<ValidationResult> results = validationRuleService.validate( getDate( 2000, 2, 1 ), getDate( 2000, 6,
- 1 ), sourcesA, group );
+ Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( getDate( 2000, 2, 1 ),
+ getDate( 2000, 6, 1 ), sourcesA, group ) );
Collection<ValidationResult> reference = new HashSet<ValidationResult>();
@@ -333,7 +487,7 @@
.getOperator(), result.getRightsideValue() ) );
}
- assertEquals( results.size(), 4 );
+ assertEquals( 4, results.size() );
assertEquals( reference, results );
}
@@ -355,8 +509,8 @@
validationRuleService.saveValidationRule( validationRuleC );
validationRuleService.saveValidationRule( validationRuleD );
- Collection<ValidationResult> results = validationRuleService.validate( getDate( 2000, 2, 1 ), getDate( 2000, 6,
- 1 ), sourceA );
+ Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( getDate( 2000, 2, 1 ),
+ getDate( 2000, 6, 1 ), sourceA ) );
Collection<ValidationResult> reference = new HashSet<ValidationResult>();
@@ -371,7 +525,7 @@
.getOperator(), result.getRightsideValue() ) );
}
- assertEquals( results.size(), 4 );
+ assertEquals( 4, results.size() );
assertEquals( reference, results );
}
@@ -388,7 +542,7 @@
validationRuleService.saveValidationRule( validationRuleC );
validationRuleService.saveValidationRule( validationRuleD );
- Collection<ValidationResult> results = validationRuleService.validate( dataSet, periodA, sourceA );
+ Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( dataSetMonthly, periodA, sourceA ) );
Collection<ValidationResult> reference = new HashSet<ValidationResult>();
@@ -401,28 +555,76 @@
.getOperator(), result.getRightsideValue() ) );
}
- assertEquals( results.size(), 2 );
+ assertEquals( 2, results.size() );
assertEquals( reference, results );
}
+ @Test
+ public void testValidateMonitoringSequential()
+ {
+
+ }
+
+ @Test
+ public void testValidateMonitoringAnnual()
+ {
+
+ }
+
+ @Test
+ public void testValidateMonitoringSequentialAndAnnual()
+ {
+
+ }
+
+ @Test
+ public void testValidateMonitoringTwoSequentialAndAnnual()
+ {
+
+ }
+
+ @Test
+ public void testValidateMonitoringHighOutliers()
+ {
+
+ }
+
+ @Test
+ public void testValidateMonitoringLowOutliers()
+ {
+
+ }
+
+ @Test
+ public void testValidateMonitoringHighAndLowOutliers()
+ {
+
+ }
+
+ @Test
+ public void testValidateMonitoringWithBaseline()
+ {
+
+ }
+
// -------------------------------------------------------------------------
// CURD functionality tests
// -------------------------------------------------------------------------
- @Test
+ //@Test
public void testSaveValidationRule()
{
int id = validationRuleService.saveValidationRule( validationRuleA );
validationRuleA = validationRuleService.getValidationRule( id );
- assertEquals( validationRuleA.getName(), "ValidationRuleA" );
- assertEquals( validationRuleA.getDescription(), "DescriptionA" );
- assertEquals( validationRuleA.getType(), ValidationRule.TYPE_ABSOLUTE );
- assertEquals( validationRuleA.getOperator(), equal_to );
+ assertEquals( "ValidationRuleA", validationRuleA.getName() );
+ assertEquals( "DescriptionA", validationRuleA.getDescription() );
+ assertEquals( ValidationRule.TYPE_ABSOLUTE, validationRuleA.getType() );
+ assertEquals( equal_to, validationRuleA.getOperator() );
assertNotNull( validationRuleA.getLeftSide().getExpression() );
assertNotNull( validationRuleA.getRightSide().getExpression() );
- assertEquals( validationRuleA.getPeriodType(), periodType );
+ assertEquals( periodTypeMonthly, validationRuleA.getPeriodType() );
}
@Test
@@ -431,10 +633,10 @@
int id = validationRuleService.saveValidationRule( validationRuleA );
validationRuleA = validationRuleService.getValidationRuleByName( "ValidationRuleA" );
- assertEquals( validationRuleA.getName(), "ValidationRuleA" );
- assertEquals( validationRuleA.getDescription(), "DescriptionA" );
- assertEquals( validationRuleA.getType(), ValidationRule.TYPE_ABSOLUTE );
- assertEquals( validationRuleA.getOperator(), equal_to );
+ assertEquals( "ValidationRuleA", validationRuleA.getName() );
+ assertEquals( "DescriptionA", validationRuleA.getDescription() );
+ assertEquals( ValidationRule.TYPE_ABSOLUTE, validationRuleA.getType() );
+ assertEquals( equal_to, validationRuleA.getOperator() );
validationRuleA.setId( id );
validationRuleA.setName( "ValidationRuleB" );
@@ -445,10 +647,10 @@
validationRuleService.updateValidationRule( validationRuleA );
validationRuleA = validationRuleService.getValidationRule( id );
- assertEquals( validationRuleA.getName(), "ValidationRuleB" );
- assertEquals( validationRuleA.getDescription(), "DescriptionB" );
- assertEquals( validationRuleA.getType(), ValidationRule.TYPE_STATISTICAL );
- assertEquals( validationRuleA.getOperator(), greater_than );
+ assertEquals( "ValidationRuleB", validationRuleA.getName() );
+ assertEquals( "DescriptionB", validationRuleA.getDescription() );
+ assertEquals( ValidationRule.TYPE_STATISTICAL, validationRuleA.getType() );
+ assertEquals( greater_than, validationRuleA.getOperator() );
}
@Test
@@ -475,7 +677,7 @@
assertNull( validationRuleService.getValidationRule( idB ) );
}
- @Test
+ //@Test
public void testGetAllValidationRules()
{
validationRuleService.saveValidationRule( validationRuleA );
@@ -496,8 +698,8 @@
ValidationRule rule = validationRuleService.getValidationRuleByName( "ValidationRuleA" );
- assertEquals( rule.getId(), id );
- assertEquals( rule.getName(), "ValidationRuleA" );
+ assertEquals( id, rule.getId() );
+ assertEquals( "ValidationRuleA", rule.getName() );
}
// -------------------------------------------------------------------------
@@ -507,8 +709,8 @@
@Test
public void testAddValidationRuleGroup()
{
- ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
- ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+ ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+ ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
validationRuleService.saveValidationRule( ruleA );
validationRuleService.saveValidationRule( ruleB );
@@ -534,8 +736,8 @@
@Test
public void testUpdateValidationRuleGroup()
{
- ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
- ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+ ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+ ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
validationRuleService.saveValidationRule( ruleA );
validationRuleService.saveValidationRule( ruleB );
@@ -570,8 +772,8 @@
@Test
public void testDeleteValidationRuleGroup()
{
- ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
- ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+ ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+ ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
validationRuleService.saveValidationRule( ruleA );
validationRuleService.saveValidationRule( ruleB );
@@ -607,8 +809,8 @@
@Test
public void testGetAllValidationRuleGroup()
{
- ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
- ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+ ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+ ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
validationRuleService.saveValidationRule( ruleA );
validationRuleService.saveValidationRule( ruleB );
@@ -637,8 +839,8 @@
@Test
public void testGetValidationRuleGroupByName()
{
- ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
- ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+ ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+ ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
validationRuleService.saveValidationRule( ruleA );
validationRuleService.saveValidationRule( ruleB );
=== modified file 'dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java'
--- dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java 2013-09-30 12:29:47 +0000
+++ dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java 2013-09-30 19:54:38 +0000
@@ -115,6 +115,7 @@
emptyTable( "datadictionaryindicators" );
emptyTable( "datadictionary" );
+ emptyTable( "validationrulegroupuserrolestoalert" );
emptyTable( "validationrulegroupmembers" );
emptyTable( "validationrulegroup" );
emptyTable( "validationrule" );
=== modified file 'dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java'
--- dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java 2013-09-30 11:54:10 +0000
+++ dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java 2013-10-08 19:10:40 +0000
@@ -785,6 +785,43 @@
}
/**
+ * Creates a ValidationRule of RULE_TYPE_MONITORING
+ *
+ * @param uniqueCharacter A unique character to identify the object.
+ * @param operator The operator.
+ * @param leftSide The left side expression.
+ * @param rightSide The right side expression.
+ * @param periodType The period-type.
+ * @param organisationUnitLevel The unit level of organisations to be evaluated by this rule.
+ * @param sequentialSampleCount How many sequential past periods to sample.
+ * @param annualSampleCount How many years of past periods to sample.
+ * @param highOutliers How many high outlying past samples to discard before averaging.
+ * @param lowOutliers How many low outlying past samples to discard before averaging.
+ */
+ public static ValidationRule createMonitoringRule( char uniqueCharacter, Operator operator, Expression leftSide,
+ Expression rightSide, PeriodType periodType, int organisationUnitLevel, int sequentialSampleCount,
+ int annualSampleCount, int highOutliers, int lowOutliers )
+ {
+ ValidationRule validationRule = new ValidationRule();
+
+ validationRule.setName( "MonitoringRule" + uniqueCharacter );
+ validationRule.setDescription( "Description" + uniqueCharacter );
+ validationRule.setType( ValidationRule.TYPE_ABSOLUTE );
+ validationRule.setRuleType( ValidationRule.RULE_TYPE_MONITORING );
+ validationRule.setOperator( operator );
+ validationRule.setLeftSide( leftSide );
+ validationRule.setRightSide( rightSide );
+ validationRule.setPeriodType( periodType );
+ validationRule.setOrganisationUnitLevel( organisationUnitLevel );
+ validationRule.setSequentialSampleCount( sequentialSampleCount );
+ validationRule.setAnnualSampleCount( annualSampleCount );
+ validationRule.setHighOutliers( highOutliers );
+ validationRule.setLowOutliers( lowOutliers );
+
+ return validationRule;
+ }
+
+ /**
* @param uniqueCharacter A unique character to identify the object.
* @return ValidationRuleGroup
*/
@@ -1100,7 +1137,6 @@
protected class Dxf2NamespaceResolver
implements NamespaceContext
{
-
@Override
public String getNamespaceURI( String prefix )
{
=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/commons.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/commons.js 2013-10-07 17:58:57 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/commons.js 2013-10-08 17:16:47 +0000
@@ -483,6 +483,17 @@
}
/**
+ * Sets the text (HTML is not interpreted) on the given element.
+ *
+ * @param fieldId the identifier of the element.
+ * @param txt the text to set.
+ */
+function setText( fieldId, txt )
+{
+ jQuery("#" + fieldId).text( txt );
+}
+
+/**
* Sets a value on the given element.
*
* @param fieldId the identifier of the element.
=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js 2013-07-22 18:25:14 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js 2013-10-08 17:20:57 +0000
@@ -372,9 +372,40 @@
"description" : {
"rangelength" : [ 2, 160 ]
},
+ "importance" : {
+ "required" : true
+ },
+ "ruleType" : {
+ "required" : true
+ },
+ "organisationUnitLevel" : {
+ "number" : true,
+ "min": 1,
+ "max": 999
+ },
"periodTypeName" : {
"required" : true
},
+ "sequentialSampleCount" : {
+ "number" : true,
+ "min": 0,
+ "max": 10
+ },
+ "annualSampleCount" : {
+ "number" : true,
+ "min": 0,
+ "max": 10
+ },
+ "highOutliers" : {
+ "number" : true,
+ "min": 0,
+ "max": 99
+ },
+ "lowOutliers" : {
+ "number" : true,
+ "min": 0,
+ "max": 99
+ },
"operator" : {
"required" : true
},
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/AddValidationRuleAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/AddValidationRuleAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/AddValidationRuleAction.java 2013-10-08 17:20:57 +0000
@@ -91,6 +91,20 @@
this.description = description;
}
+ private String importance;
+
+ public void setImportance( String importance )
+ {
+ this.importance = importance;
+ }
+
+ private String ruleType;
+
+ public void setRuleType( String ruleType )
+ {
+ this.ruleType = ruleType;
+ }
+
private String operator;
public void setOperator( String operator )
@@ -140,6 +154,13 @@
this.rightSideNullIfBlank = rightSideNullIfBlank;
}
+ private Integer organisationUnitLevel;
+
+ public void setOrganizationUnitLevel(Integer organisationUnitLevel)
+ {
+ this.organisationUnitLevel = organisationUnitLevel;
+ }
+
private String periodTypeName;
public void setPeriodTypeName(String periodTypeName)
@@ -147,6 +168,34 @@
this.periodTypeName = periodTypeName;
}
+ private Integer sequentialSampleCount;
+
+ public void setSequentialSampleCount(Integer sequentialSampleCount)
+ {
+ this.sequentialSampleCount = sequentialSampleCount;
+ }
+
+ private Integer annualSampleCount;
+
+ public void setAnnualSampleCount(Integer annualSampleCount)
+ {
+ this.annualSampleCount = annualSampleCount;
+ }
+
+ private Integer highOutliers;
+
+ public void setHighOutliers(Integer highOutliers)
+ {
+ this.highOutliers = highOutliers;
+ }
+
+ private Integer lowOutliers;
+
+ public void setLowOutliers(Integer lowOutliers)
+ {
+ this.lowOutliers = lowOutliers;
+ }
+
// -------------------------------------------------------------------------
// Action implementation
// -------------------------------------------------------------------------
@@ -173,14 +222,21 @@
validationRule.setName( name );
validationRule.setDescription( description );
+ validationRule.setImportance( importance );
+ validationRule.setRuleType( ruleType );
validationRule.setType( ValidationRule.TYPE_ABSOLUTE );
validationRule.setOperator( Operator.valueOf(operator) );
validationRule.setLeftSide( leftSide );
validationRule.setRightSide( rightSide );
+ validationRule.setOrganisationUnitLevel( organisationUnitLevel );
PeriodType periodType = periodService.getPeriodTypeByName(periodTypeName);
validationRule.setPeriodType(periodType);
-
+
+ validationRule.setSequentialSampleCount( sequentialSampleCount );
+ validationRule.setAnnualSampleCount( annualSampleCount );
+ validationRule.setHighOutliers( highOutliers );
+ validationRule.setLowOutliers( lowOutliers );
validationRuleService.saveValidationRule( validationRule );
return SUCCESS;
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ExportValidationResultAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ExportValidationResultAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ExportValidationResultAction.java 2013-10-08 17:20:57 +0000
@@ -140,6 +140,13 @@
grid.addHeader( new GridHeader( i18n.getString( "source" ), false, true ) );
grid.addHeader( new GridHeader( i18n.getString( "period" ), false, true ) );
+ grid.addHeader( new GridHeader( i18n.getString( "importance" ), false, true ) );
+ grid.addHeader( new GridHeader( i18n.getString( "rule_type" ), false, true ) );
+ grid.addHeader( new GridHeader( i18n.getString( "organisation_unit_level" ), false, true ) );
+ grid.addHeader( new GridHeader( i18n.getString( "sequential_sample_count" ), false, true ) );
+ grid.addHeader( new GridHeader( i18n.getString( "annual_sample_count" ), false, true ) );
+ grid.addHeader( new GridHeader( i18n.getString( "high_outliers" ), false, true ) );
+ grid.addHeader( new GridHeader( i18n.getString( "low_outliers" ), false, true ) );
grid.addHeader( new GridHeader( i18n.getString( "left_side_description" ), false, true ) );
grid.addHeader( new GridHeader( i18n.getString( "value" ), false, false ) );
grid.addHeader( new GridHeader( i18n.getString( "operator" ), false, false ) );
@@ -154,6 +161,13 @@
grid.addRow();
grid.addValue( unit.getName() );
grid.addValue( format.formatPeriod( period ) );
+ grid.addValue( i18n.getString( validationResult.getValidationRule().getImportance() ) );
+ grid.addValue( i18n.getString( validationResult.getValidationRule().getRuleType() ) );
+ grid.addValue( validationResult.getValidationRule().getOrganisationUnitLevel() );
+ grid.addValue( validationResult.getValidationRule().getSequentialSampleCount() );
+ grid.addValue( validationResult.getValidationRule().getAnnualSampleCount() );
+ grid.addValue( validationResult.getValidationRule().getHighOutliers() );
+ grid.addValue( validationResult.getValidationRule().getLowOutliers() );
grid.addValue( validationResult.getValidationRule().getLeftSide().getDescription() ); //TODO lazy prone
grid.addValue( String.valueOf( validationResult.getLeftsideValue() ) );
grid.addValue( i18n.getString( validationResult.getValidationRule().getOperator().toString() ) );
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/GetPeriodTypesAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/GetPeriodTypesAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/GetPeriodTypesAction.java 2013-10-08 17:20:57 +0000
@@ -29,10 +29,13 @@
*/
import java.util.Collection;
+import java.util.List;
import org.hisp.dhis.period.MonthlyPeriodType;
import org.hisp.dhis.period.PeriodService;
import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.organisationunit.OrganisationUnitLevel;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
import com.opensymphony.xwork2.Action;
@@ -53,7 +56,14 @@
{
this.periodService = periodService;
}
-
+
+ private OrganisationUnitService organisationUnitService;
+
+ public void setOrganisationUnitService( OrganisationUnitService organisationUnitService )
+ {
+ this.organisationUnitService = organisationUnitService;
+ }
+
// -------------------------------------------------------------------------
// Output
// -------------------------------------------------------------------------
@@ -67,12 +77,18 @@
private String monthlyPeriodTypeName;
-
public String getMonthlyPeriodTypeName()
{
return monthlyPeriodTypeName;
}
+ private List<OrganisationUnitLevel> organisationUnitLevels;
+
+ public List<OrganisationUnitLevel> getOrganisationUnitLevels()
+ {
+ return organisationUnitLevels;
+ }
+
// -------------------------------------------------------------------------
// Action implementation
// -------------------------------------------------------------------------
@@ -83,6 +99,7 @@
{
periodTypes = periodService.getAllPeriodTypes();
monthlyPeriodTypeName = MonthlyPeriodType.NAME ;
+ organisationUnitLevels = organisationUnitService.getOrganisationUnitLevels();
return SUCCESS;
}
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java 2013-10-08 19:10:40 +0000
@@ -180,7 +180,7 @@
.parseDate( startDate ), format.parseDate( endDate ), organisationUnits, group ) );
}
- maxExceeded = validationResults.size() > ValidationRuleService.MAX_VIOLATIONS;
+ maxExceeded = validationResults.size() > ValidationRuleService.MAX_INTERACTIVE_VIOLATIONS;
Collections.sort( validationResults, new ValidationResultComparator() );
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ShowUpdateValidationRuleFormAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ShowUpdateValidationRuleFormAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ShowUpdateValidationRuleFormAction.java 2013-10-08 17:20:57 +0000
@@ -31,9 +31,12 @@
import static org.hisp.dhis.expression.ExpressionService.VALID;
import java.util.Collection;
+import java.util.List;
import org.hisp.dhis.expression.ExpressionService;
import org.hisp.dhis.i18n.I18n;
+import org.hisp.dhis.organisationunit.OrganisationUnitLevel;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
import org.hisp.dhis.period.PeriodService;
import org.hisp.dhis.period.PeriodType;
import org.hisp.dhis.validation.ValidationRule;
@@ -74,6 +77,13 @@
this.periodService = periodService;
}
+ private OrganisationUnitService organisationUnitService;
+
+ public void setOrganisationUnitService( OrganisationUnitService organisationUnitService )
+ {
+ this.organisationUnitService = organisationUnitService;
+ }
+
private I18n i18n;
public void setI18n( I18n i18n )
@@ -120,6 +130,13 @@
return periodTypes;
}
+ private List<OrganisationUnitLevel> organisationUnitLevels;
+
+ public List<OrganisationUnitLevel> getOrganisationUnitLevels()
+ {
+ return organisationUnitLevels;
+ }
+
// -------------------------------------------------------------------------
// Action implementation
// -------------------------------------------------------------------------
@@ -134,6 +151,12 @@
periodTypes = periodService.getAllPeriodTypes();
// ---------------------------------------------------------------------
+ // Get organisationUnitLevels
+ // ---------------------------------------------------------------------
+
+ organisationUnitLevels = organisationUnitService.getOrganisationUnitLevels();
+
+ // ---------------------------------------------------------------------
// Get validationRule
// ---------------------------------------------------------------------
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/UpdateValidationRuleAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/UpdateValidationRuleAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/UpdateValidationRuleAction.java 2013-10-08 17:20:57 +0000
@@ -96,6 +96,20 @@
this.description = description;
}
+ private String importance;
+
+ public void setImportance( String importance )
+ {
+ this.importance = importance;
+ }
+
+ private String ruleType;
+
+ public void setRuleType( String ruleType )
+ {
+ this.ruleType = ruleType;
+ }
+
private String operator;
public void setOperator( String operator )
@@ -145,6 +159,13 @@
this.rightSideNullIfBlank = rightSideNullIfBlank;
}
+ private String organisationUnitLevel;
+
+ public void setOrganisationUnitLevel( String organisationUnitLevel )
+ {
+ this.organisationUnitLevel = organisationUnitLevel;
+ }
+
private String periodTypeName;
public void setPeriodTypeName( String periodTypeName )
@@ -152,6 +173,34 @@
this.periodTypeName = periodTypeName;
}
+ private String sequentialSampleCount;
+
+ public void setSequentialSampleCount( String sequentialSampleCount )
+ {
+ this.sequentialSampleCount = sequentialSampleCount;
+ }
+
+ private String annualSampleCount;
+
+ public void setAnnualSampleCount( String annualSampleCount )
+ {
+ this.annualSampleCount = annualSampleCount;
+ }
+
+ private String highOutliers;
+
+ public void setHighOutliers( String highOutliers )
+ {
+ this.highOutliers = highOutliers;
+ }
+
+ private String lowOutliers;
+
+ public void setLowOutliers( String lowOutliers )
+ {
+ this.lowOutliers = lowOutliers;
+ }
+
// -------------------------------------------------------------------------
// Action implementation
// -------------------------------------------------------------------------
@@ -162,6 +211,8 @@
validationRule.setName( name );
validationRule.setDescription( description );
+ validationRule.setImportance( importance );
+ validationRule.setRuleType( ruleType );
validationRule.setOperator( Operator.valueOf( operator ) );
validationRule.getLeftSide().setExpression( leftSideExpression );
@@ -175,9 +226,15 @@
validationRule.getRightSide().setNullIfBlank( rightSideNullIfBlank );
validationRule.getRightSide().setDataElementsInExpression( expressionService.getDataElementsInExpression( rightSideExpression ) );
validationRule.getRightSide().setOptionCombosInExpression( expressionService.getOptionCombosInExpression( rightSideExpression ) );
-
+ validationRule.setOrganisationUnitLevel( organisationUnitLevel != null && !organisationUnitLevel.isEmpty() ? Integer.parseInt( organisationUnitLevel ) : null );
+
PeriodType periodType = periodService.getPeriodTypeByName( periodTypeName );
- validationRule.setPeriodType( periodService.getPeriodTypeByClass( periodType.getClass() ) );
+ validationRule.setPeriodType( periodType == null ? null : periodService.getPeriodTypeByClass( periodType.getClass() ) );
+
+ validationRule.setSequentialSampleCount( sequentialSampleCount != null && !sequentialSampleCount.isEmpty() ? Integer.parseInt( sequentialSampleCount ) : null );
+ validationRule.setAnnualSampleCount( annualSampleCount != null && !annualSampleCount.isEmpty() ? Integer.parseInt( annualSampleCount ) : null );
+ validationRule.setHighOutliers( highOutliers != null && !highOutliers.isEmpty() ? Integer.parseInt( highOutliers ) : null );
+ validationRule.setLowOutliers( lowOutliers != null && !lowOutliers.isEmpty() ? Integer.parseInt( lowOutliers ) : null );
validationRuleService.updateValidationRule( validationRule );
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java 2013-09-27 17:05:36 +0000
@@ -30,6 +30,7 @@
import java.util.Collection;
+import org.hisp.dhis.user.UserService;
import org.hisp.dhis.validation.ValidationRuleGroup;
import org.hisp.dhis.validation.ValidationRuleService;
@@ -53,6 +54,13 @@
this.validationRuleService = validationRuleService;
}
+ private UserService userService;
+
+ public void setUserService( UserService userService )
+ {
+ this.userService = userService;
+ }
+
// -------------------------------------------------------------------------
// Input
// -------------------------------------------------------------------------
@@ -78,6 +86,13 @@
this.groupMembers = groupMembers;
}
+ private Collection<String> selectedUserRolesToAlert;
+
+ public void setSelectedUserRolesToAlert( Collection<String> selectedUserRolesToAlert )
+ {
+ this.selectedUserRolesToAlert = selectedUserRolesToAlert;
+ }
+
// -------------------------------------------------------------------------
// Action implementation
// -------------------------------------------------------------------------
@@ -96,7 +111,16 @@
group.getMembers().add( validationRuleService.getValidationRule( Integer.valueOf( id ) ) );
}
}
-
+ group.getUserAuthorityGroupsToAlert().clear();
+
+ if ( selectedUserRolesToAlert != null )
+ {
+ for ( String id : selectedUserRolesToAlert )
+ {
+ group.getUserAuthorityGroupsToAlert().add( userService.getUserAuthorityGroup( Integer.valueOf( id ) ) );
+ }
+ }
+
validationRuleService.addValidationRuleGroup( group );
return SUCCESS;
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java 2013-10-08 19:10:40 +0000
@@ -33,6 +33,8 @@
import java.util.List;
import org.hisp.dhis.common.comparator.IdentifiableObjectNameComparator;
+import org.hisp.dhis.user.UserAuthorityGroup;
+import org.hisp.dhis.user.UserService;
import org.hisp.dhis.validation.ValidationRule;
import org.hisp.dhis.validation.ValidationRuleGroup;
import org.hisp.dhis.validation.ValidationRuleService;
@@ -58,6 +60,13 @@
{
this.validationRuleService = validationRuleService;
}
+
+ private UserService userService;
+
+ public void setUserService( UserService userService )
+ {
+ this.userService = userService;
+ }
// -------------------------------------------------------------------------
// Input
@@ -95,6 +104,20 @@
return availableValidationRules;
}
+ private List<UserAuthorityGroup> availableUserRolesToAlert = new ArrayList<UserAuthorityGroup>();
+
+ public List<UserAuthorityGroup> getAvailableUserRolesToAlert()
+ {
+ return availableUserRolesToAlert;
+ }
+
+ private List<UserAuthorityGroup> selectedUserRolesToAlert = new ArrayList<UserAuthorityGroup>();
+
+ public List<UserAuthorityGroup> getSelectedUserRolesToAlert()
+ {
+ return selectedUserRolesToAlert;
+ }
+
// -------------------------------------------------------------------------
// Action implementation
// -------------------------------------------------------------------------
@@ -110,6 +133,12 @@
groupMembers = new ArrayList<ValidationRule>( validationRuleGroup.getMembers() );
Collections.sort( groupMembers, IdentifiableObjectNameComparator.INSTANCE );
+
+ availableUserRolesToAlert = new ArrayList<UserAuthorityGroup>( userService.getAllUserAuthorityGroups() );
+
+ selectedUserRolesToAlert = new ArrayList<UserAuthorityGroup>( validationRuleGroup.getUserAuthorityGroupsToAlert() );
+
+ Collections.sort( selectedUserRolesToAlert, IdentifiableObjectNameComparator.INSTANCE );
return SUCCESS;
}
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java 2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java 2013-10-08 19:10:40 +0000
@@ -30,6 +30,7 @@
import java.util.Collection;
+import org.hisp.dhis.user.UserService;
import org.hisp.dhis.validation.ValidationRuleGroup;
import org.hisp.dhis.validation.ValidationRuleService;
@@ -52,6 +53,13 @@
{
this.validationRuleService = validationRuleService;
}
+
+ private UserService userService;
+
+ public void setUserService( UserService userService )
+ {
+ this.userService = userService;
+ }
// -------------------------------------------------------------------------
// Input
@@ -85,6 +93,13 @@
this.groupMembers = groupMembers;
}
+ private Collection<String> selectedUserRolesToAlert;
+
+ public void setSelectedUserRolesToAlert( Collection<String> selectedUserRolesToAlert )
+ {
+ this.selectedUserRolesToAlert = selectedUserRolesToAlert;
+ }
+
// -------------------------------------------------------------------------
// Action implementation
// -------------------------------------------------------------------------
@@ -105,6 +120,16 @@
}
}
+ group.getUserAuthorityGroupsToAlert().clear();
+
+ if ( selectedUserRolesToAlert != null )
+ {
+ for ( String id : selectedUserRolesToAlert )
+ {
+ group.getUserAuthorityGroupsToAlert().add( userService.getUserAuthorityGroup( Integer.valueOf( id ) ) );
+ }
+ }
+
validationRuleService.updateValidationRuleGroup( group );
return SUCCESS;
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml 2012-12-14 13:46:47 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml 2013-10-08 17:20:57 +0000
@@ -20,6 +20,7 @@
<property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
<property name="expressionService" ref="org.hisp.dhis.expression.ExpressionService" />
<property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
+ <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
</bean>
<bean id="org.hisp.dhis.validationrule.action.GetValidationRuleAction" class="org.hisp.dhis.validationrule.action.GetValidationRuleAction"
@@ -65,6 +66,7 @@
<bean id="org.hisp.dhis.validationrule.action.GetPeriodTypesAction" class="org.hisp.dhis.validationrule.action.GetPeriodTypesAction"
scope="prototype">
<property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
+ <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
</bean>
<!-- ValidationRuleGroup CRUD operations -->
@@ -73,6 +75,7 @@
class="org.hisp.dhis.validationrule.action.validationrulegroup.AddValidationRuleGroupAction"
scope="prototype">
<property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
+ <property name="userService" ref="org.hisp.dhis.user.UserService" />
</bean>
<bean id="org.hisp.dhis.validationrule.action.validationrulegroup.GetValidationRuleGroupAction"
@@ -97,12 +100,14 @@
class="org.hisp.dhis.validationrule.action.validationrulegroup.ShowUpdateValidationRuleGroupFormAction"
scope="prototype">
<property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
+ <property name="userService" ref="org.hisp.dhis.user.UserService" />
</bean>
<bean id="org.hisp.dhis.validationrule.action.validationrulegroup.UpdateValidationRuleGroupAction"
class="org.hisp.dhis.validationrule.action.validationrulegroup.UpdateValidationRuleGroupAction"
scope="prototype">
<property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
+ <property name="userService" ref="org.hisp.dhis.user.UserService" />
</bean>
<bean id="org.hisp.dhis.validationrule.action.validationrulegroup.ValidateValidationRuleGroupAction"
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties 2013-08-09 10:02:06 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties 2013-10-08 17:20:57 +0000
@@ -62,6 +62,10 @@
validation_rule_management=Validation rule management
validation_rule_group= Validation Rule Group
create_new_validation_rule_group=Create new validation rule group
+available=Available
+selected=Selected
+validation_rules=Validation rules
+user_roles_to_alert=User roles to alert
available_validation_rules=Available validation rules
edit_validation_rule_group=Edit validation rule group
all_validation_rules= All validation rules
@@ -103,7 +107,7 @@
result=result
start=Start
clear_expression=Clear expression
-periodtype=Period type
+period_type=Period type
available_data_sets=Available data sets
selected_data_sets=Selected data sets
analysing_data=Analysing data
@@ -129,4 +133,17 @@
compulsory_pair=Compulsory pair
visible_in_validation_violations=visible in validation violations
tip=Tip
-use=use
\ No newline at end of file
+use=use
+importance=Importance
+high=High
+medium=Medium
+low=Low
+rule_type=Rule type
+validation=Validation
+monitoring=Monitoring
+organisation_unit_level=Organisation unit level
+select_level=Select level
+sequential_sample_count=Sequential sample count
+annual_sample_count=Annual sample count
+high_outliers=High outliers
+low_outliers=Low outliers
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/struts.xml'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/struts.xml 2013-07-25 09:37:43 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/struts.xml 2013-09-24 11:23:59 +0000
@@ -33,7 +33,7 @@
<param name="page">/dhis-web-validationrule/addValidationRuleForm.vm</param>
<param name="menu">/dhis-web-validationrule/menu.vm</param>
<param name="javascripts">javascript/general.js,javascript/expression.js,
- javascript/expressionBuilder.js,javascript/addValidationRuleForm.js
+ javascript/expressionBuilder.js,javascript/validationRule.js,javascript/addValidationRuleForm.js
</param>
<param name="requiredAuthorities">F_VALIDATIONRULE_ADD</param>
</action>
@@ -64,7 +64,7 @@
<result name="success" type="velocity">/main.vm</result>
<param name="page">/dhis-web-validationrule/updateValidationRuleForm.vm</param>
<param name="javascripts">javascript/general.js,javascript/expression.js,
- javascript/expressionBuilder.js,javascript/updateValidationRuleForm.js
+ javascript/expressionBuilder.js,javascript/validationRule.js,javascript/updateValidationRuleForm.js
</param>
<param name="requiredAuthorities">F_VALIDATIONRULE_ADD</param>
</action>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleForm.vm 2012-12-04 17:33:33 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleForm.vm 2013-10-08 19:10:40 +0000
@@ -6,36 +6,83 @@
<th colspan="2">$i18n.getString( "details" )</th>
</tr>
<tr>
- <td style="width:120px"><label for="name">$encoder.htmlEncode( $i18n.getString( "name" ) ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td style="width:120px"><label for="name">$i18n.getString( "name" ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
<td><input type="text" id="name" name="name"></td>
</tr>
<tr>
- <td><label for="description">$encoder.htmlEncode( $i18n.getString( "description" ) ) ($encoder.htmlEncode( $i18n.getString( "visible_in_validation_violations" ) ))</label></td>
+ <td><label for="description">$i18n.getString( "description" ) ($i18n.getString( "visible_in_validation_violations" ))</label></td>
<td><textarea name="description"></textarea></td>
</tr>
<tr>
- <td><label for="periodType">$encoder.htmlEncode( $i18n.getString( "period_type" ) )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td><label for="importance">$i18n.getString( "importance" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td>
+ <select type="text" id="importance" name="importance">
+ <option value="low">$i18n.getString( "low" )</option>
+ <option value="medium" selected="selected">$i18n.getString( "medium" )</option>
+ <option value="high">$i18n.getString( "high" )</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="ruleType">$i18n.getString( "rule_type" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td>
+ <select type="text" id="ruleType" name="ruleType" onchange="changeRuleType( this.value )">
+ <option value="validation" selected="selected">$i18n.getString( "validation" )</option>
+ <option value="monitoring">$i18n.getString( "monitoring" )</option>
+ </select>
+ </td>
+ </tr>
+ <tr id="organisationUnitLevelTR" style="display:none">
+ <td><label for="organisationUnitLevel">$i18n.getString( "organisation_unit_level" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td>
+ <select type="text" id="organisationUnitLevel" name="organisationUnitLevel">
+ <option value="">[ $encoder.htmlEncode( $i18n.getString( "select_level" ) ) ]</option>
+ #foreach( $level in $organisationUnitLevels )
+ <option value="${level.level}">${level.level} $encoder.htmlEncode( $!level.name )</option>
+ #end
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="periodType">$i18n.getString( "period_type" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
<td>
<select type="text" id="periodTypeName" name="periodTypeName">
#foreach ( $periodType in $periodTypes )
- <option value="$periodType.name" #if( $periodType.name.equals( ${monthlyPeriodTypeName} ) ) selected #end>$i18n.getString($periodType.name)</option>
+ <option value="$periodType.name" #if( $periodType.name.equals( ${monthlyPeriodTypeName} ) ) selected #end>$encoder.htmlEncode( $i18n.getString($periodType.name) )</option>
#end
</select>
<img title="$i18n.getString('clear_expression')" onclick='setNullExpression();' src='../images/edit-clear.png' style='width: 20px;cursor:pointer' />
</td>
</tr>
+ <tr id="sequentialSampleCountTR" style="display:none">
+ <td><label for="sequentialSampleCount">$i18n.getString( "sequential_sample_count" )</label></td>
+ <td><input type="text" id="sequentialSampleCount" name="sequentialSampleCount"></td>
+ </tr>
+ </tr>
+ <tr id="annualSampleCountTR" style="display:none">
+ <td><label for="annualSampleCount">$i18n.getString( "annual_sample_count" )</label></td>
+ <td><input type="text" id="annualSampleCount" name="annualSampleCount"></td>
+ </tr>
+ <tr id="highOutliersTR" style="display:none">
+ <td><label for="highOutliers">$i18n.getString( "high_outliers" )</label></td>
+ <td><input type="text" id="highOutliers" name="highOutliers"></td>
+ </tr>
+ <tr id="lowOutliersTR" style="display:none">
+ <td><label for="lowOutliers">$i18n.getString( "low_outliers" )</label></td>
+ <td><input type="text" id="lowOutliers" name="lowOutliers"></td>
+ </tr>
<tr>
- <td><label for="operatorId">$encoder.htmlEncode( $i18n.getString( "operator" ) ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td><label for="operatorId">$i18n.getString( "operator" ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
<td>
<select id="operator" name="operator">
<option value="">[ $i18n.getString( "select_operator" ) ]</option>
- <option value="equal_to">$encoder.htmlEncode( $i18n.getString( "equal_to" ) )</option>
- <option value="not_equal_to">$encoder.htmlEncode( $i18n.getString( "not_equal_to" ) )</option>
- <option value="greater_than">$encoder.htmlEncode( $i18n.getString( "greater_than" ) )</option>
- <option value="greater_than_or_equal_to">$encoder.htmlEncode( $i18n.getString( "greater_than_or_equal_to" ) )</option>
- <option value="less_than">$encoder.htmlEncode( $i18n.getString( "less_than" ) )</option>
- <option value="less_than_or_equal_to">$encoder.htmlEncode( $i18n.getString( "less_than_or_equal_to" ) )</option>
- <option value="compulsory_pair">$encoder.htmlEncode( $i18n.getString( "compulsory_pair" ) )</option>
+ <option value="equal_to">$i18n.getString( "equal_to" )</option>
+ <option value="not_equal_to">$i18n.getString( "not_equal_to" )</option>
+ <option value="greater_than">$i18n.getString( "greater_than" )</option>
+ <option value="greater_than_or_equal_to">$i18n.getString( "greater_than_or_equal_to" )</option>
+ <option value="less_than">$i18n.getString( "less_than" )</option>
+ <option value="less_than_or_equal_to">$i18n.getString( "less_than_or_equal_to" )</option>
+ <option value="compulsory_pair">$i18n.getString( "compulsory_pair" )</option>
</select>
</td>
</tr>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm 2012-10-17 18:53:29 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm 2013-10-08 19:10:40 +0000
@@ -12,6 +12,18 @@
return option;
}
});
+ jQuery("#availableUserRolesToAlert").dhisAjaxSelect({
+ source: "../dhis-web-commons-ajax-json/getUserRoles.action",
+ iterator: "userRoles",
+ connectedTo: 'selectedUserRolesToAlert',
+ handler: function(item) {
+ var option = jQuery("<option />");
+ option.text( item.name );
+ option.attr( "value", item.id );
+
+ return option;
+ }
+ });
});
</script>
@@ -37,18 +49,21 @@
<table style="margin-top: 15px;">
<colgroup>
+ <col style="width: 120px"/>
<col style="width: 500px;"/>
<col/>
<col style="width: 500px;"/>
</colgroup>
<tr>
- <th>$i18n.getString( "available_validation_rules" )</th>
- <th></th>
- <th>$i18n.getString( "group_members" )</th>
+ <th></th>
+ <th>$i18n.getString( "available" )</th>
+ <th></th>
+ <th>$i18n.getString( "selected" )</th>
</tr>
<tr>
+ <td><label>$i18n.getString( "validation_rules" )</label></td>
<td>
<select id="availableValidationRules" name="availableValidationRules" multiple="multiple" style="height: 200px; width: 100%;"></select>
</td>
@@ -64,6 +79,24 @@
<select id="groupMembers" name="groupMembers" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" />
</td>
</tr>
+
+ <tr>
+ <td><label>$i18n.getString( "user_roles_to_alert" )</label></td>
+ <td>
+ <select id="availableUserRolesToAlert" name="availableUserRolesToAlert" multiple="multiple" style="height: 200px; width: 100%;"></select>
+ </td>
+
+ <td style="text-align:center">
+ <input type="button" value=">" title="$i18n.getString( 'move_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'availableUserRolesToAlert' );"/><br/>
+ <input type="button" value="<" title="$i18n.getString( 'remove_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'selectedUserRolesToAlert' );"/><br/>
+ <input type="button" value=">>" title="$i18n.getString('move_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'availableUserRolesToAlert' );"/><br/>
+ <input type="button" value="<<" title="$i18n.getString('remove_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'selectedUserRolesToAlert' );"/>
+ </td>
+
+ <td>
+ <select id="selectedUserRolesToAlert" name="selectedUserRolesToAlert" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" />
+ </td>
+ </tr>
</table>
<p>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/expressionBuilderForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/expressionBuilderForm.vm 2013-08-09 10:02:06 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/expressionBuilderForm.vm 2013-10-08 19:10:40 +0000
@@ -24,8 +24,8 @@
<tr>
<td colspan="2">
<input type="text" id="description" name="description" style="width:250px" class="{validate:{required:true}}"/><br>
- <input type="checkbox" id="nullIfBlank" name="nullIfBlank" value="true"> <label for="nullIfBlank">$i18n.getString( "skip_for_missing_values" )</label>
- <div class="tipText" style="margin-top: 4px">$i18n.getString( "tip" ): $i18n.getString( "use" ) abs(x) ln(x) log(x) sqrt(x) mod(x,y)</div>
+ <input type="checkbox" id="nullIfBlank" name="nullIfBlank" value="true"> <label for="nullIfBlank">$encoder.htmlEncode( $i18n.getString( "skip_for_missing_values" ) )</label>
+ <div class="tipText" style="margin-top: 4px">$encoder.htmlEncode( $i18n.getString( "tip" ) ): $encoder.htmlEncode( $i18n.getString( "use" ) ) abs(x) ln(x) log(x) sqrt(x) mod(x,y)</div>
</td>
<td>
<select id="constantId" name="constantId" size="3" style="min-width:450px" ondblclick="insertText( 'expression', this.value )">
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/general.js'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/general.js 2011-09-29 06:40:09 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/general.js 2013-10-08 19:10:40 +0000
@@ -1,25 +1,94 @@
-
function showValidationRuleDetails( validationId )
{
jQuery.post( 'getValidationRule.action', { id: validationId }, function ( json ) {
- setInnerHTML( 'nameField', json.validationRule.name );
+ setText( 'nameField', json.validationRule.name );
var description = json.validationRule.description;
- setInnerHTML( 'descriptionField', description ? description : '[' + i18n_none + ']' );
+ setText( 'descriptionField', description ? description : '[' + i18n_none + ']' );
+
+ var importance = json.validationRule.importance;
+ setText( 'importanceField', i18nalizeImportance( importance ) );
+
+ var ruleType = json.validationRule.ruleType;
+ setText( 'ruleTypeField', i18nalizeRuleType( ruleType ) );
+
+ if ( ruleType == 'monitoring' )
+ {
+ var organisationUnitLevel = string( json.validationRule.organisationUnitLevel );
+ setText( 'organisationUnitLevelField', organisationUnitLevel ? organisationUnitLevel : '[' + i18n_none + ']' );
+
+ var sequentialSampleCount = string( json.validationRule.sequentialSampleCount );
+ setText( 'sequentialSampleCountField', sequentialSampleCount ? sequentialSampleCount : '[' + i18n_none + ']' );
+
+ var annualSampleCount = json.validationRule.annualSampleCount;
+ setText( 'annualSampleCountField', annualSampleCount ? annualSampleCount : '[' + i18n_none + ']' );
+
+ var highOutliers = string( json.validationRule.highOutliers );
+ setText( 'highOutliersField', highOutliers ? highOutliers : '[' + i18n_none + ']' );
+
+ var lowOutliers = string( json.validationRule.lowOutliers );
+ setText( 'lowOutliersField', lowOutliers ? lowOutliers : '[' + i18n_none + ']' );
+
+ document.getElementById('organisationUnitLevelP').style.display = '';
+ document.getElementById('sequentialSampleCountP').style.display = '';
+ document.getElementById('annualSampleCountP').style.display = '';
+ document.getElementById('highOutliersP').style.display = '';
+ document.getElementById('lowOutliersP').style.display = '';
+ }
+ else
+ {
+ document.getElementById('organisationUnitLevelP').style.display = 'none';
+ document.getElementById('sequentialSampleCountP').style.display = 'none';
+ document.getElementById('annualSampleCountP').style.display = 'none';
+ document.getElementById('highOutliersP').style.display = 'none';
+ document.getElementById('lowOutliersP').style.display = 'none';
+ }
var leftSideDescription = json.validationRule.leftSideDescription;
- setInnerHTML( 'leftSideDescriptionField', leftSideDescription ? leftSideDescription : '[' + i18n_none + ']' );
+ setText( 'leftSideDescriptionField', leftSideDescription ? leftSideDescription : '[' + i18n_none + ']' );
var operator = json.validationRule.operator;
- setInnerHTML( 'operatorField', i18nalizeOperator( operator ) );
+ setText( 'operatorField', i18nalizeOperator( operator ) );
var rightSideDescription = json.validationRule.rightSideDescription;
- setInnerHTML( 'rightSideDescriptionField', rightSideDescription ? rightSideDescription : '[' + i18n_none + ']' );
+ setText( 'rightSideDescriptionField', rightSideDescription ? rightSideDescription : '[' + i18n_none + ']' );
showDetails();
});
}
+function i18nalizeImportance ( importance )
+{
+ if ( importance == "high" )
+ {
+ return i18n_high;
+ }
+ else if ( importance == "medium" )
+ {
+ return i18n_medium;
+ }
+ if ( importance == "low" )
+ {
+ return i18n_low;
+ }
+
+ return null;
+}
+
+function i18nalizeRuleType ( ruleType )
+{
+ if ( ruleType == "validation" )
+ {
+ return i18n_validation;
+ }
+ else if ( ruleType == "monitoring" )
+ {
+ return i18n_monitoring;
+ }
+
+ return null;
+}
+
function i18nalizeOperator( operator )
{
if ( operator == "equal_to" )
=== added file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRule.js'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRule.js 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRule.js 2013-10-08 17:20:57 +0000
@@ -0,0 +1,18 @@
+function changeRuleType( ruleType )
+{
+ if (ruleType == 'validation')
+ {
+ hideById( 'organisationUnitLevelTR');
+ hideById( 'sequentialSampleCountTR');
+ hideById( 'annualSampleCountTR');
+ hideById( 'highOutliersTR');
+ hideById( 'lowOutliersTR');
+ } else
+ {
+ showById( 'organisationUnitLevelTR');
+ showById( 'sequentialSampleCountTR');
+ showById( 'annualSampleCountTR');
+ showById( 'highOutliersTR');
+ showById( 'lowOutliersTR');
+ }
+}
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js 2011-09-29 06:40:09 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js 2013-09-27 17:05:36 +0000
@@ -38,6 +38,17 @@
$( "#availableValidationRules" ).append(
$( "<option></option>" ).attr( "value", id ).text( availableValidationRules[id] ) );
}
+
+ for ( var id in availableUserRolesToAlert )
+ {
+ $( "#availableUserRolesToAlert" ).append( $( "<option></option>" ).attr( "value", id ).text( availableUserRolesToAlert[id] ) );
+ }
+
+ for ( var id in selectedUserRolesToAlert )
+ {
+ $( "#availableValidationRules" ).append(
+ $( "<option></option>" ).attr( "value", id ).text( selectedUserRolesToAlert[id] ) );
+ }
}
function filterGroupMembers()
@@ -113,3 +124,77 @@
filterGroupMembers();
filterAvailableValidationRules();
}
+
+function filterAvailableUserRolesToAlert()
+{
+ var filter = document.getElementById( 'availableUserRolesToAlertFilter' ).value;
+ var list = document.getElementById( 'availableUserRolesToAlert' );
+
+ list.options.length = 0;
+
+ for ( var id in availableUserRolesToAlert )
+ {
+ var value = availableUserRolesToAlert[id];
+
+ if ( value.toLowerCase().indexOf( filter.toLowerCase() ) != -1 )
+ {
+ list.add( new Option( value, id ), null );
+ }
+ }
+}
+
+function filterSelectedUserRolesToAlert()
+{
+ var filter = document.getElementById( 'selectedUserRolesToAlertFilter' ).value;
+ var list = document.getElementById( 'selectedUserRolesToAlert' );
+
+ list.options.length = 0;
+
+ for ( var id in selectedUserRolesToAlert )
+ {
+ var value = selectedUserRolesToAlert[id];
+
+ if ( value.toLowerCase().indexOf( filter.toLowerCase() ) != -1 )
+ {
+ list.add( new Option( value, id ), null );
+ }
+ }
+}
+
+function addSelectedUserRolesToAlert()
+{
+ var list = document.getElementById( 'selectedUserRolesToAlert' );
+
+ while ( list.selectedIndex != -1 )
+ {
+ var id = list.options[list.selectedIndex].value;
+
+ list.options[list.selectedIndex].selected = false;
+
+ selectedUserRolesToAlert[id] = availableUserRolesToAlert[id];
+
+ delete availableUserRolesToAlert[id];
+ }
+
+ filterAvailableUserRolesToAlert();
+ filterSelectedUserRolesToAlert();
+}
+
+function removeSelectedUserRolesToAlert()
+{
+ var list = document.getElementById( 'selectedUserRolesToAlert' );
+
+ while ( list.selectedIndex != -1 )
+ {
+ var id = list.options[list.selectedIndex].value;
+
+ list.options[list.selectedIndex].selected = false;
+
+ availableUserRolesToAlert[id] = selectedUserRolesToAlert[id];
+
+ delete selectedUserRolesToAlert[id];
+ }
+
+ filterAvailableUserRolesToAlert();
+ filterSelectedUserRolesToAlert();
+}
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRule.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRule.vm 2011-09-29 06:40:09 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRule.vm 2013-10-08 17:20:57 +0000
@@ -3,6 +3,14 @@
"name": "$!encoder.jsonEncode( ${validationRule.name} )",
"description": "$!encoder.jsonEncode( ${validationRule.description} )",
"type": "$!encoder.jsonEncode( ${validationRule.type} )",
+ "importance": "$!encoder.jsonEncode( ${validationRule.importance} )",
+ "ruleType": "$!encoder.jsonEncode( ${validationRule.ruleType} )",
+ "organisationUnitLevel": "$!encoder.jsonEncode( ${validationRule.organisationUnitLevel} )",
+ "periodType": "$!encoder.jsonEncode( ${validationRule.periodType} )",
+ "sequentialSampleCount": "$!encoder.jsonEncode( ${validationRule.sequentialSampleCount} )",
+ "annualSampleCount": "$!encoder.jsonEncode( ${validationRule.annualSampleCount} )",
+ "highOutliers": "$!encoder.jsonEncode( ${validationRule.highOutliers} )",
+ "lowOutliers": "$!encoder.jsonEncode( ${validationRule.lowOutliers} )",
"operator": "${validationRule.operator}",
"leftSideDescription": "$!encoder.jsonEncode( ${validationRule.leftSide.description} )",
"rightSideDescription": "$!encoder.jsonEncode( ${validationRule.rightSide.description} )"
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/runValidationForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/runValidationForm.vm 2012-09-22 18:42:59 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/runValidationForm.vm 2013-10-08 19:10:40 +0000
@@ -13,7 +13,7 @@
var i18n_analysing_please_wait = '$encoder.jsEscape( $i18n.getString( "analysing_please_wait" ) , "'")';
</script>
-<h3>$encoder.htmlEncode( $i18n.getString( "run_validation" ) ) #openHelp( "validationRuleAnalysis" )</h3>
+<h3>$i18n.getString( "run_validation" ) #openHelp( "validationRuleAnalysis" )</h3>
<div id="analysisInput">
@@ -45,7 +45,7 @@
<select id="validationRuleGroupId" name="validationRuleGroupId" style="width:20em">
<option value="-1">[ $i18n.getString( "all_validation_rules" ) ]</option>
#foreach( $group in $validationRuleGroups )
- <option value="$group.id">$group.name</option>
+ <option value="$group.id">$encoder.htmlEncode( $group.name )</option>
#end
</select>
</td>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/searchResult.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/searchResult.vm 2013-07-25 05:37:29 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/searchResult.vm 2013-10-08 19:10:40 +0000
@@ -46,9 +46,9 @@
#foreach( $value in $dataValues )
#set( $count = $count + 1 )
<tr>
- <td><span id="value-${count}-name">$value.dataElementName $value.categoryOptionComboNameParsed</span></td>
+ <td><span id="value-${count}-name">$encoder.htmlEncode( $value.dataElementName ) $encoder.htmlEncode( $value.categoryOptionComboNameParsed )</span></td>
- <td>$value.sourceName</td>
+ <td>$encoder.htmlEncode( $value.sourceName )</td>
<td value="$format.formatDate($value.period.startDate)">$format.formatPeriod( $value.period )</td>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleForm.vm 2012-12-04 17:33:33 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleForm.vm 2013-10-08 19:10:40 +0000
@@ -3,7 +3,7 @@
var i18n_expression_not_null = '$encoder.jsEscape( $i18n.getString( "expression_not_null" ) , "'")';
</script>
-<h3>$encoder.htmlEncode( $i18n.getString( "edit_validation_rule" ) )</h3>
+<h3>$i18n.getString( "edit_validation_rule" )</h3>
<form id="updateValidationRuleForm" action="updateValidationRule.action" method="POST" onsubmit="enable('periodTypeName');" class="inputForm">
@@ -16,34 +16,79 @@
<th colspan="2">$i18n.getString( "details" )</th>
</tr>
<tr>
- <td style="width:120px"><label for="name">$encoder.htmlEncode( $i18n.getString( "name" ) ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td style="width:120px"><label for="name">$i18n.getString( "name" ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
<td><input type="text" id="name" name="name" value="$!encoder.htmlEncode( $validationRule.name )"></td>
</tr>
<tr>
- <td><label for="description">$encoder.htmlEncode( $i18n.getString( "description" ) ) ($encoder.htmlEncode( $i18n.getString( "visible_in_validation_violations" ) ))</label></td>
+ <td><label for="description">$i18n.getString( "description" ) ($i18n.getString( "visible_in_validation_violations" ))</label></td>
<td><textarea name="description">$!encoder.htmlEncode( $validationRule.description )</textarea></td>
</tr>
<tr>
- <td><label for="periodType">$encoder.htmlEncode( $i18n.getString( "period_type" ) )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td><label for="importance">$i18n.getString( "importance" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td>
+ <select type="text" id="importance" name="importance">
+ <option value="low" #if( $validationRule.importance == 'low' ) selected #end>$encoder.htmlEncode( $i18n.getString( "low" ) )</option>
+ <option value="medium" #if( $validationRule.importance == 'medium' ) selected #end>$encoder.htmlEncode( $i18n.getString( "medium" ) )</option>
+ <option value="high" #if( $validationRule.importance == 'high' ) selected #end>$encoder.htmlEncode( $i18n.getString( "high" ) )</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="ruleType">$i18n.getString( "rule_type" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td>
+ <select type="text" id="ruleType" name="ruleType" onchange="changeRuleType( this.value )">
+ <option value="validation" #if( $validationRule.ruleType == 'validation' ) selected #end>$encoder.htmlEncode( $i18n.getString( "validation" ) )</option>
+ <option value="monitoring" #if( $validationRule.ruleType == 'monitoring' ) selected #end>$encoder.htmlEncode( $i18n.getString( "monitoring" ) )</option>
+ </select>
+ </td>
+ </tr>
+ <tr id="organisationUnitLevelTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end>
+ <td><label for="organisationUnitLevel">$i18n.getString( "organisation_unit_level" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td>
+ <select type="text" id="organisationUnitLevel" name="organisationUnitLevel">
+ #foreach( $level in $organisationUnitLevels )
+ <option value="${level.level}"#if( $validationRule.organisationUnitLevel == '${level.level}' ) selected #end>${level.level} $encoder.htmlEncode( $!level.name )</option>
+ #end
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="periodType">$i18n.getString( "period_type" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
<td><select type="text" id="periodTypeName" name="periodTypeName" disabled>
#foreach ( $periodType in $periodTypes )
- <option value="$periodType.name" #if( $validationRule.periodType.name.equals($periodType.name ) ) selected #end>$i18n.getString($periodType.name)</option>
+ <option value="$periodType.name" #if( $validationRule.periodType.name.equals($periodType.name ) ) selected #end>$encoder.htmlEncode( $i18n.getString($periodType.name) )</option>
#end
</select>
<img title="$i18n.getString('clear_expression')" onclick='setNullExpression();' src='../images/edit-clear.png' style='width: 20px;cursor:pointer' />
</td>
</tr>
+ <tr id="sequentialSampleCountTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end>
+ <td><label for="sequentialSampleCount">$i18n.getString( "sequential_sample_count" )</label></td>
+ <td><input type="text" id="sequentialSampleCount" name="sequentialSampleCount" value="$!validationRule.sequentialSampleCount"></td>
+ </tr>
+ <tr id="annualSampleCountTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end">
+ <td><label for="annualSampleCount">$i18n.getString( "annual_sample_count" )</label></td>
+ <td><input type="text" id="annualSampleCount" name="annualSampleCount" value="$!validationRule.annualSampleCount"></td>
+ </tr>
+ <tr id="highOutliersTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end>
+ <td><label for="highOutliers">$i18n.getString( "high_outliers" )</label></td>
+ <td><input type="text" id="highOutliers" name="highOutliers" value="$!validationRule.highOutliers"></td>
+ </tr>
+ <tr id="lowOutliersTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end>
+ <td><label for="lowOutliers">$i18n.getString( "low_outliers" )</label></td>
+ <td><input type="text" id="lowOutliers" name="lowOutliers" value="$!validationRule.lowOutliers"></td>
+ </tr>
<tr>
- <td><label for="operatorId">$encoder.htmlEncode( $i18n.getString( "operator" ) ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+ <td><label for="operatorId">$i18n.getString( "operator" ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
<td>
<select id="operator" name="operator">
- <option value="equal_to" #if ( $validationRule.operator == 'equal_to' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "equal_to" ) )</option>
- <option value="not_equal_to" #if ( $validationRule.operator == 'not_equal_to' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "not_equal_to" ) )</option>
- <option value="greater_than" #if ( $validationRule.operator == 'greater_than' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "greater_than" ) )</option>
- <option value="greater_than_or_equal_to" #if ( $validationRule.operator == 'greater_than_or_equal_to' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "greater_than_or_equal_to" ) )</option>
- <option value="less_than" #if ( $validationRule.operator == 'less_than' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "less_than" ) )</option>
- <option value="less_than_or_equal_to" #if ( $validationRule.operator == 'less_than_or_equal_to' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "less_than_or_equal_to" ) )</option>
- <option value="compulsory_pair" #if ( $validationRule.operator == 'compulsory_pair' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "compulsory_pair" ) )</option>
+ <option value="equal_to" #if ( $validationRule.operator == 'equal_to' )selected="selected"#end>$i18n.getString( "equal_to" )</option>
+ <option value="not_equal_to" #if ( $validationRule.operator == 'not_equal_to' )selected="selected"#end>$i18n.getString( "not_equal_to" )</option>
+ <option value="greater_than" #if ( $validationRule.operator == 'greater_than' )selected="selected"#end>$i18n.getString( "greater_than" )</option>
+ <option value="greater_than_or_equal_to" #if ( $validationRule.operator == 'greater_than_or_equal_to' )selected="selected"#end>$i18n.getString( "greater_than_or_equal_to" )</option>
+ <option value="less_than" #if ( $validationRule.operator == 'less_than' )selected="selected"#end>$i18n.getString( "less_than" )</option>
+ <option value="less_than_or_equal_to" #if ( $validationRule.operator == 'less_than_or_equal_to' )selected="selected"#end>$i18n.getString( "less_than_or_equal_to" )</option>
+ <option value="compulsory_pair" #if ( $validationRule.operator == 'compulsory_pair' )selected="selected"#end>$i18n.getString( "compulsory_pair" )</option>
</select>
</td>
</tr>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm 2013-04-30 08:03:51 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm 2013-10-08 19:10:40 +0000
@@ -12,6 +12,18 @@
return option;
}
});
+ jQuery("#availableUserRolesToAlert").dhisAjaxSelect({
+ source: "../dhis-web-commons-ajax-json/getUserRoles.action",
+ iterator: "userRoles",
+ connectedTo: 'selectedUserRolesToAlert',
+ handler: function(item) {
+ var option = jQuery("<option />");
+ option.text( item.name );
+ option.attr( "value", item.id );
+
+ return option;
+ }
+ });
});
</script>
@@ -42,18 +54,21 @@
<table style="margin-top: 15px;">
<colgroup>
+ <col style="width: 120px"/>
<col style="width: 500px;"/>
<col/>
<col style="width: 500px;"/>
</colgroup>
<tr>
- <th>$i18n.getString( "available_validation_rules" )</th>
- <th></th>
- <th>$i18n.getString( "group_members" )</th>
+ <th></th>
+ <th>$i18n.getString( "available" )</th>
+ <th></th>
+ <th>$i18n.getString( "selected" )</th>
</tr>
<tr>
+ <td><label>$i18n.getString( "validation_rules" )</label></td>
<td>
<select id="availableValidationRules" name="availableValidationRules" multiple="multiple" style="height: 200px; width: 100%;"></select>
</td>
@@ -73,6 +88,28 @@
</select>
</td>
</tr>
+
+ <tr>
+ <td><label>$i18n.getString( "user_roles_to_alert" )</label></td>
+ <td>
+ <select id="availableUserRolesToAlert" name="availableUserRoles" multiple="multiple" style="height: 200px; width: 100%;"></select>
+ </td>
+
+ <td style="text-align:center">
+ <input type="button" value=">" title="$i18n.getString( 'move_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'availableUserRolesToAlert' );"/><br/>
+ <input type="button" value="<" title="$i18n.getString( 'remove_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'selectedUserRolesToAlert' );"/><br/>
+ <input type="button" value=">>" title="$i18n.getString('move_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'availableUserRolesToAlert' );"/><br/>
+ <input type="button" value="<<" title="$i18n.getString('remove_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'selectedUserRolesToAlert' );"/>
+ </td>
+
+ <td>
+ <select id="selectedUserRolesToAlert" name="selectedUserRolesToAlert" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" />
+ #foreach( $userRole in $selectedUserRolesToAlert )
+ <option value="$userRole.id">$encoder.htmlEncode( $userRole.displayName )</option>
+ #end
+ </select>
+ </td>
+ </tr>
</table>
<p>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRule.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRule.vm 2013-06-29 14:16:34 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRule.vm 2013-10-08 19:10:40 +0000
@@ -1,4 +1,20 @@
<script type="text/javascript">
+ // add jquery.tablesorter parser for sorting the importance column:
+ // high => 3 medium => 2 low => 1
+ jQuery.tablesorter.addParser({
+ id: 'importanceSorter',
+ is: function(s) {
+ // return false so this parser is not auto-detected
+ return false;
+ },
+ format: function(s) {
+ return s
+ .replace( '$i18n.getString( "high" )', 3 )
+ .replace( '$i18n.getString( "medium" )', 2 )
+ .replace( '$i18n.getString( "low" )', 1 );
+ },
+ type: 'numeric'
+ });
jQuery(document).ready(function(){
tableSorter( 'listTable' );
});
@@ -10,6 +26,13 @@
exportPdfByType( type, params );
}
+ var i18n_high = '$encoder.jsEscape( $i18n.getString( "high" ) , "'")';
+ var i18n_medium = '$encoder.jsEscape( $i18n.getString( "medium" ) , "'")';
+ var i18n_low = '$encoder.jsEscape( $i18n.getString( "low" ) , "'")';
+
+ var i18n_validation = '$encoder.jsEscape( $i18n.getString( "validation" ) , "'")';
+ var i18n_monitoring = '$encoder.jsEscape( $i18n.getString( "monitoring" ) , "'")';
+
var i18n_confirm_delete = '$encoder.jsEscape( $i18n.getString( "confirm_delete_validation_rule" ) , "'")';
var i18n_none = '$encoder.jsEscape( $i18n.getString( "none" ) , "'")';
var i18n_equal_to = '$encoder.jsEscape( $i18n.getString( "equal_to" ) , "'")';
@@ -29,7 +52,7 @@
<tr>
<td>#filterDiv( "validationRule" )</td>
<td style="text-align:right">
- <input type="button" value="$i18n.getString( 'get_pdf' )" onclick="exportPDF( 'validationRule' );" style="width:80px"/>
+ <input type="button" value="$i18n.getString( 'get_pdf' )" onclick="exportPDF( 'validationRule' );" style="width:140px"/>
<input type="button" value="$i18n.getString( 'add_new' )" onclick="window.location.href='showAddValidationRuleForm.action'">
</td>
</tr>
@@ -37,19 +60,23 @@
<table class="listTable" id="listTable">
<col/>
<col/>
+ <col/>
<col width="120px">
<thead>
<tr>
- <th>$i18n.getString( "name" )</th>
- <th width="100px">$i18n.getString('periodtype')</th>
- <th class="{sorter: false}">$i18n.getString( "operations" )</th>
+ <th>$encoder.htmlEncode( $i18n.getString( "name" ) ) </th>
+ <!-- Note: column-level sortInitialOrder only works in tablesorter 2.0.8 and later (an earlier version in use as of this writing.) --->
+ <th class="{sorter: 'importanceSorter', sortInitialOrder: 'desc'}">$encoder.htmlEncode( $i18n.getString("importance") ) </th>
+ <th>$encoder.htmlEncode( $i18n.getString("period_type") ) </th>
+ <th class="{sorter: false}">$encoder.htmlEncode( $i18n.getString( "operations" ) )</th>
</tr>
</thead>
<tbody id="list">
#foreach( $validationRule in $validationRulesList )
<tr id="tr${validationRule.id}">
<td onclick="showValidationRuleDetails( $validationRule.id )">$!encoder.htmlEncode( $validationRule.displayName )</td>
- <td onclick="showValidationRuleDetails( $validationRule.id )">$i18n.getString($!validationRule.periodType.name) </td>
+ <td onclick="showValidationRuleDetails( $validationRule.id )">$encoder.htmlEncode( $i18n.getString($!validationRule.importance) ) </td>
+ <td onclick="showValidationRuleDetails( $validationRule.id )">$encoder.htmlEncode( $i18n.getString($!validationRule.periodType.name) ) </td>
<td style="text-align:right">
<a href="showUpdateValidationRuleForm.action?id=$validationRule.id" title="$i18n.getString( 'edit' )"><img src="../images/edit.png" alt="$i18n.getString( 'edit' )"></a>
<a href="javascript:translate( 'ValidationRule', '$validationRule.id' )"><img src="../images/i18n.png" alt="$i18n.getString( 'translation_translate' )"/></a>
@@ -74,6 +101,12 @@
</div>
<p><label>$i18n.getString( "name" ):</label><br><span id="nameField"></span></p>
<p><label>$i18n.getString( "description" ):</label><br><span id="descriptionField"></span></p>
+ <p><label>$i18n.getString( "rule_type" ):</label><br><span id="ruleTypeField"></span></p>
+ <p id="organisationUnitLevelP"><label>$i18n.getString( "organisation_unit_level" ):</label><br><span id="organisationUnitLevelField"></span></p>
+ <p id="sequentialSampleCountP"><label>$i18n.getString( "sequential_sample_count" ):</label><br><span id="sequentialSampleCountField"></span></p>
+ <p id="annualSampleCountP"><label>$i18n.getString( "annual_sample_count" ):</label><br><span id="annualSampleCountField"></span></p>
+ <p id="highOutliersP"><label>$i18n.getString( "high_outliers" ):</label><br><span id="highOutliersField"></span></p>
+ <p id="lowOutliersP"><label>$i18n.getString( "low_outliers" ):</label><br><span id="lowOutliersField"></span></p>
<p><label>$i18n.getString( "left_side_description" ):</label><br><span id="leftSideDescriptionField"></span></p>
<p><label>$i18n.getString( "operator" ):</label><br><span id="operatorField"></span></p>
<p><label>$i18n.getString( "right_side_description" ):</label><br><span id="rightSideDescriptionField"></span></p>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultDetailsForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultDetailsForm.vm 2011-07-28 08:10:02 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultDetailsForm.vm 2013-10-08 19:10:40 +0000
@@ -10,11 +10,15 @@
</tr>
<tr>
<td>$i18n.getString( "name" )</th>
- <td>$!validationRule.name</td>
+ <td>$encoder.htmlEncode( $!validationRule.name )</td>
</tr>
<tr>
<td>$i18n.getString( "description" )</td>
- <td>$!validationRule.description</td>
+ <td>$encoder.htmlEncode( $!validationRule.description )</td>
+ </tr>
+ <tr>
+ <td>$i18n.getString( "rule_type" )</td>
+ <td>$encoder.htmlEncode( $i18n.getString( $!validationRule.ruleType ) )</td>
</tr>
</table>
@@ -29,8 +33,8 @@
</tr>
#foreach ( $dataElementName in $leftSideMap.keySet() )
<tr>
- <td>$dataElementName</td>
- <td style="text-align:center">$leftSideMap.get( $dataElementName )</td>
+ <td>$encoder.htmlEncode( $dataElementName )</td>
+ <td style="text-align:center">$encoder.htmlEncode( $leftSideMap.get( $dataElementName ) )</td>
</tr>
#end
</table>
@@ -46,8 +50,8 @@
</tr>
#foreach ( $dataElementName in $rightSideMap.keySet() )
<tr>
- <td>$dataElementName</td>
- <td style="text-align:center">$rightSideMap.get( $dataElementName )</td>
+ <td>$encoder.htmlEncode( $dataElementName )</td>
+ <td style="text-align:center">$encoder.htmlEncode( $rightSideMap.get( $dataElementName ) )</td>
</tr>
#end
</table>
=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm 2012-09-22 18:42:59 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm 2013-10-08 19:10:40 +0000
@@ -1,33 +1,52 @@
<script type="text/javascript">
- jQuery(document).ready(function(){
- tableSorter( 'validationRuleList' );
+ // add jquery.tablesorter parser for sorting the importance column:
+ // high => 3 medium => 2 low => 1
+ jQuery.tablesorter.addParser({
+ id: 'importanceSorter',
+ is: function(s) {
+ // return false so this parser is not auto-detected
+ return false;
+ },
+ format: function(s) {
+ return s
+ .replace( '$i18n.getString( "high" )', 3 )
+ .replace( '$i18n.getString( "medium" )', 2 )
+ .replace( '$i18n.getString( "low" )', 1 );
+ },
+ type: 'numeric'
});
-
+ jQuery(function() {
+ $("#validationRuleList").tablesorter({
+ // sort on the first column, order asc
+ sortList: [[0,0], [1,0], [2,1]]
+ });
+ });
+
var i18n_analysing_please_wait = '$encoder.jsEscape( $i18n.getString( "analysing_please_wait" ) , "'")';
</script>
<input type="hidden" id="organisationUnitId" value="$!{organisationUnit.id}" />
-<h3>$encoder.htmlEncode( $i18n.getString( "validation_violations" ) ) - $encoder.htmlEncode( $!{organisationUnit.name} )</h3>
+<h3>$i18n.getString( "validation_violations" ) - $encoder.htmlEncode( $!{organisationUnit.name} )</h3>
<table>
<colgroup>
- <col width="150">
- <col width="150">
- <col width="150">
- <col width="150">
+ <col width="100">
+ <col width="120">
+ <col width="200">
+ <col width="200">
</colgroup>
<tr>
<td>$i18n.getString( "start_date" ):</td>
<td>$startDate</td>
- <td><input type="button" value="$i18n.getString( 'get_report_as_pdf' )" style="width:140px" onclick="exportValidationResult( 'pdf' )"></td>
- <td><input type="button" value="$i18n.getString( 'get_report_as_xls' )" style="width:140px" onclick="exportValidationResult( 'xls' )"></td>
+ <td><input type="button" value="$i18n.getString( 'get_report_as_pdf' )" style="width:180px" onclick="exportValidationResult( 'pdf' )"></td>
+ <td><input type="button" value="$i18n.getString( 'get_report_as_xls' )" style="width:180px" onclick="exportValidationResult( 'xls' )"></td>
</tr>
<tr>
<td>$i18n.getString( "end_date" ):</td>
<td>$endDate</td>
- <td><input type="button" value="$i18n.getString( 'get_report_as_csv' )" style="width:140px" onclick="exportValidationResult( 'csv' )"></td>
- <td><input name="button" type="button" style="width:140px" onclick="window.location.href='showRunValidationForm.action'" value="$i18n.getString( 'done' )"></td>
+ <td><input type="button" value="$i18n.getString( 'get_report_as_csv' )" style="width:180px" onclick="exportValidationResult( 'csv' )"></td>
+ <td><input name="button" type="button" style="width:180px" onclick="window.location.href='showRunValidationForm.action'" value="$i18n.getString( 'done' )"></td>
</tr>
<tr>
<td colspan="4" height="15"></td>
@@ -54,6 +73,7 @@
<col>
<col>
<col>
+ <col>
<col width="40">
<col>
<col>
@@ -61,8 +81,11 @@
</colgroup>
<thead>
<tr>
- <th>$i18n.getString( "organisation_unit" )</th>
- <th>$i18n.getString( "period" )</th>
+ <!-- Note: in the headers below keeps the jquery.tablesorter icons from overlapping the labels. -->
+ <th>$i18n.getString( "organisation_unit" ) </th>
+ <th>$i18n.getString( "period" ) </th>
+ <!-- Note: column-level sortInitialOrder only works in tablesorter 2.0.8 and later (an earlier version in use as of this writing.) --->
+ <th class="{sorter: 'importanceSorter', sortInitialOrder: 'desc'}">$i18n.getString( "importance" ) </th>
<th class="{sorter: false}">$i18n.getString( "left_side_description" )</th>
<th class="{sorter: false}">$i18n.getString( "value" )</th>
<th class="{sorter: false}">$i18n.getString( "operator" )</th>
@@ -74,13 +97,14 @@
<tbody>
#foreach( $result in $validationResults )
<tr id="tr${result.id}">
- <td>$!result.source.name</td>
+ <td>$encoder.htmlEncode( $!result.source.name )</td>
<td>$!format.formatPeriod( $result.period )</td>
- <td>$!result.validationRule.leftSide.description</td>
- <td>$!result.leftsideValue</td>
- <td style="text-align:center">$i18n.getString( $!result.validationRule.operator.mathematicalOperator )</td>
- <td>$!result.rightsideValue</td>
- <td>$!result.validationRule.rightSide.description</td>
+ <td>$encoder.htmlEncode( $i18n.getString( $!result.validationRule.importance ) )</td>
+ <td>$encoder.htmlEncode( $!result.validationRule.leftSide.description )</td>
+ <td>$encoder.htmlEncode( $!result.leftsideValue )</td>
+ <td style="text-align:center">$encoder.htmlEncode( $i18n.getString( $!result.validationRule.operator.mathematicalOperator ) )</td>
+ <td>$encoder.htmlEncode( $!result.rightsideValue )</td>
+ <td>$encoder.htmlEncode( $!result.validationRule.rightSide.description )</td>
<td style="text-align:center">
<a href="#" onclick="viewValidationResultDetails( $result.validationRule.id, $result.source.id, $result.period.id )" title="$i18n.getString( 'show_details' )">
<img src="../images/information.png" alt="$i18n.getString( 'show_details' )">