------------------------------------------------------------ revno: 14504 committer: Jim Grace <jimgr...@gmail.com> branch nick: dhis2 timestamp: Thu 2014-03-27 21:51:34 -0400 message: Change validation notifications from roles to user groups. modified: 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-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableAlteror.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/java/org/hisp/dhis/validation/ValidationResultToAlertFilter.java dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.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/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/addValidationRuleGroupForm.js dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/updateValidationRuleGroupForm.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/jsonValidationRuleGroup.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/validationRuleGroup.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/validation/ValidationRule.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java 2014-03-18 08:10:10 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java 2014-03-28 01:51:34 +0000 @@ -253,13 +253,13 @@ } /** - * Indicates whether this validation rule has user roles to alert. + * Indicates whether this validation rule has user groups to alert. */ - public boolean hasUserRolesToAlert() + public boolean hasUserGroupsToAlert() { for ( ValidationRuleGroup group : groups ) { - if ( group.hasUserRolesToAlert() ) + if ( group.hasUserGroupsToAlert() ) { return true; } === 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 2014-03-18 08:10:10 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java 2014-03-28 01:51:34 +0000 @@ -45,6 +45,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.hisp.dhis.user.UserGroup; /** * @author Lars Helge Overland @@ -63,8 +64,10 @@ @Scanned private Set<ValidationRule> members = new HashSet<ValidationRule>(); - private Set<UserAuthorityGroup> userAuthorityGroupsToAlert = new HashSet<UserAuthorityGroup>(); - + private Set<UserGroup> userGroupsToAlert = new HashSet<UserGroup>(); + + private boolean alertByOrgUnits; + // ------------------------------------------------------------------------- // Constructors // ------------------------------------------------------------------------- @@ -104,9 +107,9 @@ /** * Indicates whether this group has user roles to alert. */ - public boolean hasUserRolesToAlert() + public boolean hasUserGroupsToAlert() { - return userAuthorityGroupsToAlert != null && !userAuthorityGroupsToAlert.isEmpty(); + return userGroupsToAlert != null && !userGroupsToAlert.isEmpty(); } // ------------------------------------------------------------------------- @@ -144,16 +147,32 @@ @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; + @JacksonXmlElementWrapper( localName = "userGroupsToAlert", namespace = DxfNamespaces.DXF_2_0) + @JacksonXmlProperty( localName = "userGroupToAlert", namespace = DxfNamespaces.DXF_2_0) + public Set<UserGroup> getUserGroupsToAlert() + { + return userGroupsToAlert; + } + + @JsonProperty + @JsonView( {DetailedView.class, ExportView.class} ) + @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0) + public void setUserGroupsToAlert( Set<UserGroup> userGroupsToAlert ) + { + this.userGroupsToAlert = userGroupsToAlert; + } + + @JsonProperty + @JsonView( {DetailedView.class, ExportView.class} ) + @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0) + public boolean isAlertByOrgUnits() + { + return alertByOrgUnits; + } + + public void setAlertByOrgUnits( boolean alertByOrgUnits ) + { + this.alertByOrgUnits = alertByOrgUnits; } @Override === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableAlteror.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableAlteror.java 2014-03-25 14:01:51 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableAlteror.java 2014-03-28 01:51:34 +0000 @@ -122,6 +122,7 @@ executeSql( "DROP TABLE loginfailure" ); executeSql( "DROP TABLE dashboarditem_trackedentitytabularreports" ); executeSql( "DROP TABLE categoryoptioncombousergroupaccesses" ); + executeSql( "DROP TABLE validationrulegroupuserrolestoalert" ); executeSql( "ALTER TABLE categoryoptioncombo drop column userid" ); executeSql( "ALTER TABLE categoryoptioncombo drop column publicaccess" ); executeSql( "ALTER TABLE dataelementcategoryoption drop column categoryid" ); @@ -703,6 +704,9 @@ // update attribute.code, set to null if code='' executeSql( "UPDATE attribute SET code=NULL WHERE code=''" ); + // validation rule group, new column alertbyorgunits needs values + executeSql( "UPDATE validationrulegroup SET alertbyorgunits=false WHERE alertbyorgunits IS NULL" ); + upgradeDataValuesWithAttributeOptionCombo(); upgradeMapViewsToAnalyticalObject(); === 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 2014-03-18 08:10:10 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java 2014-03-28 01:51:34 +0000 @@ -71,8 +71,8 @@ import org.hisp.dhis.system.util.Filter; import org.hisp.dhis.system.util.FilterUtils; import org.hisp.dhis.user.User; -import org.hisp.dhis.user.UserAuthorityGroup; import org.hisp.dhis.user.UserCredentials; +import org.hisp.dhis.user.UserGroup; import org.springframework.transaction.annotation.Transactional; /** @@ -269,7 +269,7 @@ Collection<ValidationResult> results = Validator.validate( sources, periods, rules, lastScheduledRun, constantService, expressionService, periodService, dataValueService ); - log.info( "Run results: " + results.size() ); + log.info( "Validation run result count: " + results.size() ); if ( !results.isEmpty() ) { @@ -296,7 +296,7 @@ for ( ValidationRuleGroup validationRuleGroup : getAllValidationRuleGroups() ) { - if ( validationRuleGroup.hasUserRolesToAlert() ) + if ( validationRuleGroup.hasUserGroupsToAlert() ) { rules.addAll( validationRuleGroup.getMembers() ); } @@ -361,11 +361,10 @@ { SortedSet<ValidationResult> results = new TreeSet<ValidationResult>( validationResults ); - Map<List<ValidationResult>, Set<User>> messageMap = getMessageMap( results ); - - for ( Map.Entry<List<ValidationResult>, Set<User>> entry : messageMap.entrySet() ) + Map<SortedSet<ValidationResult>, Set<User>> messageMap = getMessageMap( results ); + + for ( Map.Entry<SortedSet<ValidationResult>, Set<User>> entry : messageMap.entrySet() ) { - Collections.sort( entry.getKey() ); sendAlertmessage( entry.getKey(), entry.getValue(), scheduledRunStart ); } } @@ -396,82 +395,90 @@ * to assemble into a message, and the value is the set of users who * should receive this message. * - * @param results all the validation run results + * @param results all the validation run results, in a sorted set * @return map of result sets to users */ - private Map<List<ValidationResult>, Set<User>> getMessageMap( Set<ValidationResult> results ) + private Map<SortedSet<ValidationResult>, Set<User>> getMessageMap( SortedSet<ValidationResult> results ) { - Map<User, Set<ValidationRule>> userRulesMap = getUserRulesMap(); - - Map<List<ValidationResult>, Set<User>> messageMap = new HashMap<List<ValidationResult>, Set<User>>(); - - for ( User user : userRulesMap.keySet() ) + Map<User, SortedSet<ValidationResult>> userResults = getUserResults( results ); + + Map<SortedSet<ValidationResult>, Set<User>> messageMap = new HashMap<SortedSet<ValidationResult>, Set<User>>(); + + for (Map.Entry<User, SortedSet<ValidationResult>> userResultEntry : userResults.entrySet() ) { - // For users receiving alerts, find the subset of results from run. - - Collection<ValidationRule> userRules = userRulesMap.get( user ); - List<ValidationResult> userResults = new ArrayList<ValidationResult>(); - - for ( ValidationResult result : results ) - { - if ( userRules.contains( result.getValidationRule() ) ) - { - userResults.add( result ); - } - } - - // Group this user with other users having the same result subset. - - if ( !userResults.isEmpty() ) - { - Set<User> messageReceivers = messageMap.get( userResults ); - if ( messageReceivers == null ) - { - messageReceivers = new HashSet<User>(); - messageMap.put( userResults, messageReceivers ); - } - messageReceivers.add( user ); - } + Set<User> users = messageMap.get( userResultEntry.getValue() ); + + if ( users == null ) + { + users = new HashSet<User>(); + + messageMap.put( userResultEntry.getValue(), users ); + } + users.add( userResultEntry.getKey() ); } - + return messageMap; } /** - * Constructs a Map where the key is each user who is configured to - * receive alerts, and the value is a list of rules they should receive - * results for. - * - * @return Map from users to sets of rules + * Returns a map where the key is a user and the value is a naturally-sorted + * list of results they should receive. + * + * @param results all the validation run results, in a sorted set + * @return map of users to results */ - private Map<User, Set<ValidationRule>> getUserRulesMap() + private Map<User, SortedSet<ValidationResult>> getUserResults( SortedSet<ValidationResult> results ) { - Map<User, Set<ValidationRule>> userRulesMap = new HashMap<User, Set<ValidationRule>>(); + Map<User, SortedSet<ValidationResult>> userResults = new HashMap<User, SortedSet<ValidationResult>>(); - for ( ValidationRuleGroup validationRuleGroup : getAllValidationRuleGroups() ) + for ( ValidationResult result : results ) { - Collection<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert(); - - if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() ) + for ( ValidationRuleGroup ruleGroup : result.getValidationRule().getGroups() ) { - for ( UserAuthorityGroup role : userRolesToAlert ) + if ( ruleGroup.hasUserGroupsToAlert() ) { - for ( UserCredentials userCredentials : role.getMembers() ) + for ( UserGroup userGroup : ruleGroup.getUserGroupsToAlert() ) { - User user = userCredentials.getUser(); - Set<ValidationRule> userRules = userRulesMap.get( user ); - if ( userRules == null ) + for ( User user : userGroup.getMembers() ) { - userRules = new HashSet<ValidationRule>(); - userRulesMap.put( user, userRules ); + if ( !ruleGroup.isAlertByOrgUnits() || canUserAccessSource( user, result.getSource() ) ) + { + SortedSet<ValidationResult> resultSet = userResults.get ( user ); + + if ( resultSet == null ) + { + resultSet = new TreeSet<ValidationResult>(); + + userResults.put( user, resultSet ); + } + resultSet.add( result ); + } } - userRules.addAll( validationRuleGroup.getMembers() ); } } } } - - return userRulesMap; + return userResults; + } + + /** + * Determines whether a user can access an organisation unit, + * based on the organisation units to which the user has been assigned. + * + * @param user user to test + * @param source organisation unit to which the user may have access + * @return whether the user has acceess to the organisation unit + */ + private boolean canUserAccessSource( User user, OrganisationUnit source ) + { + for ( OrganisationUnit o : user.getOrganisationUnits() ) + { + if ( source == o || source.getAncestors().contains( o ) ) + { + return true; + } + } + return false; } /** @@ -482,7 +489,7 @@ * @param users users to receive these results * @param scheduledRunStart date/time when the scheduled run started */ - private void sendAlertmessage( List<ValidationResult> results, Set<User> users, Date scheduledRunStart ) + private void sendAlertmessage( SortedSet<ValidationResult> results, Set<User> users, Date scheduledRunStart ) { StringBuilder builder = new StringBuilder(); @@ -522,7 +529,7 @@ * @param results results to analyze * @return Mapping between importance type and result counts. */ - private Map<String, Integer> countResultsByImportanceType ( List<ValidationResult> results ) + private Map<String, Integer> countResultsByImportanceType ( Set<ValidationResult> results ) { Map<String, Integer> importanceCountMap = new HashMap<String, Integer>(); === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationResultToAlertFilter.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationResultToAlertFilter.java 2014-03-18 08:10:10 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationResultToAlertFilter.java 2014-03-28 01:51:34 +0000 @@ -36,6 +36,6 @@ @Override public boolean retain( ValidationResult result ) { - return result != null && result.getValidationRule() != null && result.getValidationRule().hasUserRolesToAlert(); + return result != null && result.getValidationRule() != null && result.getValidationRule().hasUserGroupsToAlert(); } } === 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 2014-03-24 18:52:45 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml 2014-03-28 01:51:34 +0000 @@ -19,16 +19,18 @@ <property name="description" type="text" /> + <property name="alertByOrgUnits" column="alertbyorgunits" /> + <set name="members" table="validationrulegroupmembers"> <key column="validationgroupid" foreign-key="fk_validationrulegroupmembers_validationrulegroupid" /> <many-to-many class="org.hisp.dhis.validation.ValidationRule" column="validationruleid" 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 name="userGroupsToAlert" table="validationrulegroupusergroupstoalert"> + <key column="validationgroupid" foreign-key="fk_validationrulegroupusergroupstoalert_validationgroupid" /> + <many-to-many class="org.hisp.dhis.user.UserGroup" column="usergroupid" + foreign-key="fk_validationrulegroupusergroupstoalert_usergroupid" /> </set> <!-- Access properties --> === 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 2014-03-18 08:10:10 +0000 +++ dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java 2014-03-28 01:51:34 +0000 @@ -115,7 +115,7 @@ emptyTable( "datadictionaryindicators" ); emptyTable( "datadictionary" ); - emptyTable( "validationrulegroupuserrolestoalert" ); + emptyTable( "validationrulegroupusergroupstoalert" ); emptyTable( "validationrulegroupmembers" ); emptyTable( "validationrulegroup" ); emptyTable( "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 2014-03-18 08:10:10 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java 2014-03-28 01:51:34 +0000 @@ -30,7 +30,7 @@ import java.util.Set; -import org.hisp.dhis.user.UserService; +import org.hisp.dhis.user.UserGroupService; import org.hisp.dhis.validation.ValidationRuleGroup; import org.hisp.dhis.validation.ValidationRuleService; @@ -54,11 +54,11 @@ this.validationRuleService = validationRuleService; } - private UserService userService; + private UserGroupService userGroupService; - public void setUserService( UserService userService ) + public void setUserService( UserGroupService userGroupService ) { - this.userService = userService; + this.userGroupService = userGroupService; } // ------------------------------------------------------------------------- @@ -86,13 +86,20 @@ this.groupMembers = groupMembers; } - private Set<String> userRolesToAlert; - - public void setUserRolesToAlert( Set<String> userRolesToAlert ) - { - this.userRolesToAlert = userRolesToAlert; - } - + private Set<String> userGroupsToAlert; + + public void setUserGroupsToAlert( Set<String> userGroupsToAlert ) + { + this.userGroupsToAlert = userGroupsToAlert; + } + + private boolean alertByOrgUnits; + + public void setAlertByOrgUnits( boolean alertByOrgUnits ) + { + this.alertByOrgUnits = alertByOrgUnits; + } + // ------------------------------------------------------------------------- // Action implementation // ------------------------------------------------------------------------- @@ -111,15 +118,17 @@ group.getMembers().add( validationRuleService.getValidationRule( Integer.valueOf( id ) ) ); } } - group.getUserAuthorityGroupsToAlert().clear(); + group.getUserGroupsToAlert().clear(); - if ( userRolesToAlert != null ) + if ( userGroupsToAlert != null ) { - for ( String id : userRolesToAlert ) + for ( String id : userGroupsToAlert ) { - group.getUserAuthorityGroupsToAlert().add( userService.getUserAuthorityGroup( Integer.valueOf( id ) ) ); + group.getUserGroupsToAlert().add( userGroupService.getUserGroup( Integer.valueOf( id ) ) ); } } + + group.setAlertByOrgUnits( alertByOrgUnits ); validationRuleService.addValidationRuleGroup( group ); === 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 2014-03-18 08:10:10 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java 2014-03-28 01:51:34 +0000 @@ -34,6 +34,8 @@ import org.hisp.dhis.common.comparator.IdentifiableObjectNameComparator; import org.hisp.dhis.user.UserAuthorityGroup; +import org.hisp.dhis.user.UserGroup; +import org.hisp.dhis.user.UserGroupService; import org.hisp.dhis.user.UserService; import org.hisp.dhis.validation.ValidationRule; import org.hisp.dhis.validation.ValidationRuleGroup; @@ -61,11 +63,11 @@ this.validationRuleService = validationRuleService; } - private UserService userService; + private UserGroupService userGroupService; - public void setUserService( UserService userService ) + public void setUserGroupService( UserGroupService userGroupService ) { - this.userService = userService; + this.userGroupService = userGroupService; } // ------------------------------------------------------------------------- @@ -104,18 +106,25 @@ return groupMembers; } - private List<UserAuthorityGroup> availableUserRolesToAlert = new ArrayList<UserAuthorityGroup>(); + private List<UserGroup> availableUserGroupsToAlert = new ArrayList<UserGroup>(); - public List<UserAuthorityGroup> getAvailableUserRolesToAlert() - { - return availableUserRolesToAlert; - } - - private List<UserAuthorityGroup> userRolesToAlert = new ArrayList<UserAuthorityGroup>(); - - public List<UserAuthorityGroup> getUserRolesToAlert() - { - return userRolesToAlert; + public List<UserGroup> getAvailableUserGroupsToAlert() + { + return availableUserGroupsToAlert; + } + + private List<UserGroup> userGroupsToAlert = new ArrayList<UserGroup>(); + + public List<UserGroup> getUserGroupsToAlert() + { + return userGroupsToAlert; + } + + private boolean alertByOrgUnits; + + public boolean getAlertByOrgUnits() + { + return alertByOrgUnits; } // ------------------------------------------------------------------------- @@ -130,11 +139,13 @@ Collections.sort( groupMembers, IdentifiableObjectNameComparator.INSTANCE ); - availableUserRolesToAlert = new ArrayList<UserAuthorityGroup>( userService.getAllUserAuthorityGroups() ); + availableUserGroupsToAlert = new ArrayList<UserGroup>( userGroupService.getAllUserGroups() ); - userRolesToAlert = new ArrayList<UserAuthorityGroup>( validationRuleGroup.getUserAuthorityGroupsToAlert() ); + userGroupsToAlert = new ArrayList<UserGroup>( validationRuleGroup.getUserGroupsToAlert() ); - Collections.sort( userRolesToAlert, IdentifiableObjectNameComparator.INSTANCE ); + Collections.sort( userGroupsToAlert, IdentifiableObjectNameComparator.INSTANCE ); + + alertByOrgUnits = validationRuleGroup.isAlertByOrgUnits(); 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 2014-03-18 08:10:10 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java 2014-03-28 01:51:34 +0000 @@ -30,7 +30,7 @@ import java.util.Set; -import org.hisp.dhis.user.UserService; +import org.hisp.dhis.user.UserGroupService; import org.hisp.dhis.validation.ValidationRuleGroup; import org.hisp.dhis.validation.ValidationRuleService; @@ -54,11 +54,11 @@ this.validationRuleService = validationRuleService; } - private UserService userService; + private UserGroupService userGroupService; - public void setUserService( UserService userService ) + public void setUserGroupService( UserGroupService userGroupService ) { - this.userService = userService; + this.userGroupService = userGroupService; } // ------------------------------------------------------------------------- @@ -93,13 +93,20 @@ this.groupMembers = groupMembers; } - private Set<String> userRolesToAlert; - - public void setUserRolesToAlert( Set<String> userRolesToAlert ) - { - this.userRolesToAlert = userRolesToAlert; - } - + private Set<String> userGroupsToAlert; + + public void setUserGroupsToAlert( Set<String> userGroupsToAlert ) + { + this.userGroupsToAlert = userGroupsToAlert; + } + + private boolean alertByOrgUnits; + + public void setAlertByOrgUnits( boolean alertByOrgUnits ) + { + this.alertByOrgUnits = alertByOrgUnits; + } + // ------------------------------------------------------------------------- // Action implementation // ------------------------------------------------------------------------- @@ -120,16 +127,18 @@ } } - group.getUserAuthorityGroupsToAlert().clear(); + group.getUserGroupsToAlert().clear(); - if ( userRolesToAlert != null ) + if ( userGroupsToAlert != null ) { - for ( String id : userRolesToAlert ) + for ( String id : userGroupsToAlert ) { - group.getUserAuthorityGroupsToAlert().add( userService.getUserAuthorityGroup( Integer.valueOf( id ) ) ); + group.getUserGroupsToAlert().add( userGroupService.getUserGroup( Integer.valueOf( id ) ) ); } } - + + group.setAlertByOrgUnits( alertByOrgUnits ); + 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 2013-12-17 09:33:58 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml 2014-03-28 01:51:34 +0000 @@ -77,7 +77,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" /> + <property name="userGroupService" ref="org.hisp.dhis.user.UserGroupService" /> </bean> <bean id="org.hisp.dhis.validationrule.action.validationrulegroup.GetValidationRuleGroupAction" @@ -102,14 +102,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" /> + <property name="userGroupService" ref="org.hisp.dhis.user.UserGroupService" /> </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" /> + <property name="userGroupService" ref="org.hisp.dhis.user.UserGroupService" /> </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 2014-03-04 00:22:37 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties 2014-03-28 01:51:34 +0000 @@ -64,7 +64,10 @@ available=Available selected=Selected validation_rules=Validation rules -user_roles_to_alert=User roles to alert +user_groups_to_alert=User groups to alert +alert_by_org_units=Only organisation unit related users are alerted +yes=Yes +no=No available_validation_rules=Available validation rules edit_validation_rule_group=Edit validation rule group all_validation_rules= All validation rules @@ -146,7 +149,7 @@ annual_sample_count=Annual sample count high_outliers=High outliers low_outliers=Low outliers -number_of_user_roles_to_alert=Number of user roles to alert +number_of_user_groups_to_alert=Number of user groups to alert send_alerts=Send alerts visible_when_rule_is_violated=visible when rule is violated instruction=Instruction \ No newline at end of file === 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 2014-03-25 08:05:13 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm 2014-03-28 01:51:34 +0000 @@ -12,10 +12,10 @@ return option; } }); - jQuery("#availableUserRolesToAlert").dhisAjaxSelect({ - source: "../dhis-web-commons-ajax-json/getUserRoles.action", - iterator: "userRoles", - connectedTo: 'userRolesToAlert', + jQuery("#availableUserGroupsToAlert").dhisAjaxSelect({ + source: "../dhis-web-commons-ajax-json/getUserGroups.action", + iterator: "userGroups", + connectedTo: 'userGroupsToAlert', handler: function(item) { var option = jQuery("<option />"); option.text( item.name ); @@ -83,25 +83,37 @@ </tr> <tr> - <td><label>$i18n.getString( "user_roles_to_alert" )</label></td> + <td><label>$i18n.getString( "user_groups_to_alert" )</label></td> <td> - <select id="availableUserRolesToAlert" name="availableUserRolesToAlert" multiple="multiple" style="height: 200px; width: 100%;"></select> + <select id="availableUserGroupsToAlert" name="availableUserGroupsToAlert" 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( 'userRolesToAlert' );"/><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( 'userRolesToAlert' );"/> + <input type="button" value=">" title="$i18n.getString( 'move_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'availableUserGroupsToAlert' );"/><br/> + <input type="button" value="<" title="$i18n.getString( 'remove_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'userGroupsToAlert' );"/><br/> + <input type="button" value=">>" title="$i18n.getString('move_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'availableUserGroupsToAlert' );"/><br/> + <input type="button" value="<<" title="$i18n.getString('remove_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'userGroupsToAlert' );"/> </td> <td> - <select id="userRolesToAlert" name="userRolesToAlert" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" /> + <select id="userGroupsToAlert" name="userGroupsToAlert" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" /> </td> </tr> </table> -<p> +<table> + <tr> + <td><label for="alertByOrgUnits">$i18n.getString( "alert_by_org_units" )</label></td> + <td> + <select type="text" id="alertByOrgUnits" name="alertByOrgUnits"> + <option value="false">$i18n.getString( "no" )</option> + <option value="true">$i18n.getString( "yes" )</option> + </select> + </td> + </tr> +</table> + + <p> <input type="submit" value="$i18n.getString( "add" )" style="width:10em" /> <input type="button" value="$i18n.getString( "cancel" )" onclick="window.location.href='showValidationRuleGroupForm.action'" style="width:10em" /> </p> === modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/addValidationRuleGroupForm.js' --- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/addValidationRuleGroupForm.js 2013-10-13 18:23:51 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/addValidationRuleGroupForm.js 2014-03-28 01:51:34 +0000 @@ -9,7 +9,7 @@ 'beforeValidateHandler' : function() { selectAllById( 'groupMembers' ); - selectAllById( 'userRolesToAlert' ); + selectAllById( 'userGroupsToAlert' ); }, 'rules' : getValidationRules( "validationRuleGroup" ) } ); === modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/updateValidationRuleGroupForm.js' --- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/updateValidationRuleGroupForm.js 2013-10-16 20:03:41 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/updateValidationRuleGroupForm.js 2014-03-28 01:51:34 +0000 @@ -9,7 +9,7 @@ 'beforeValidateHandler' : function() { selectAllById( 'groupMembers' ); - selectAllById( 'userRolesToAlert' ); + selectAllById( 'userGroupsToAlert' ); }, 'rules' : getValidationRules( "validationRuleGroup" ) } ); === 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 2013-12-06 13:37:28 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js 2014-03-28 01:51:34 +0000 @@ -12,7 +12,7 @@ setInnerHTML('nameField', json.validationRuleGroup.name); setInnerHTML('descriptionField', json.validationRuleGroup.description); setInnerHTML('memberCountField', json.validationRuleGroup.memberCount); - setInnerHTML('userRolesToAlertCountField', json.validationRuleGroup.userRolesToAlertCount); + setInnerHTML('userGroupsToAlertCountField', json.validationRuleGroup.userGroupsToAlertCount); showDetails(); }); @@ -40,13 +40,13 @@ $("<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 availableUserGroupsToAlert ) { + $("#availableUserGroupsToAlert").append($("<option></option>").attr("value", id).text(availableUserGroupsToAlert[id])); } - for( var id in selectedUserRolesToAlert ) { + for( var id in selectedUserGroupsToAlert ) { $("#availableValidationRules").append( - $("<option></option>").attr("value", id).text(selectedUserRolesToAlert[id])); + $("<option></option>").attr("value", id).text(selectedUserGroupsToAlert[id])); } } @@ -114,66 +114,66 @@ 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(); +function filterAvailableUserGroupsToAlert() { + var filter = document.getElementById('availableUserGroupsToAlertFilter').value; + var list = document.getElementById('availableUserGroupsToAlert'); + + list.options.length = 0; + + for( var id in availableUserGroupsToAlert ) { + var value = availableUserGroupsToAlert[id]; + + if( value.toLowerCase().indexOf(filter.toLowerCase()) != -1 ) { + list.add(new Option(value, id), null); + } + } +} + +function filterSelectedUserGroupsToAlert() { + var filter = document.getElementById('selectedUserGroupsToAlertFilter').value; + var list = document.getElementById('selectedUserGroupsToAlert'); + + list.options.length = 0; + + for( var id in selectedUserGroupsToAlert ) { + var value = selectedUserGroupsToAlert[id]; + + if( value.toLowerCase().indexOf(filter.toLowerCase()) != -1 ) { + list.add(new Option(value, id), null); + } + } +} + +function addSelectedUserGroupsToAlert() { + var list = document.getElementById('selectedUserGroupsToAlert'); + + while( list.selectedIndex != -1 ) { + var id = list.options[list.selectedIndex].value; + + list.options[list.selectedIndex].selected = false; + + selectedUserGroupsToAlert[id] = availableUserGroupsToAlert[id]; + + delete availableUserGroupsToAlert[id]; + } + + filterAvailableUserGroupsToAlert(); + filterSelectedUserGroupsToAlert(); +} + +function removeSelectedUserGroupsToAlert() { + var list = document.getElementById('selectedUserGroupsToAlert'); + + while( list.selectedIndex != -1 ) { + var id = list.options[list.selectedIndex].value; + + list.options[list.selectedIndex].selected = false; + + availableUserGroupsToAlert[id] = selectedUserGroupsToAlert[id]; + + delete selectedUserGroupsToAlert[id]; + } + + filterAvailableUserGroupsToAlert(); + filterSelectedUserGroupsToAlert(); } === modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRuleGroup.vm' --- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRuleGroup.vm 2013-10-13 18:35:46 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRuleGroup.vm 2014-03-28 01:51:34 +0000 @@ -4,6 +4,7 @@ "name": "$!encoder.jsonEncode( ${validationRuleGroup.name} )", "description": "$!encoder.jsonEncode( ${validationRuleGroup.description} )", "memberCount": "${validationRuleGroup.members.size()}", - "userRolesToAlertCount": "$!{validationRuleGroup.userAuthorityGroupsToAlert.size()}" + "userGroupsToAlertCount": "$!{validationRuleGroup.userGroupsToAlert.size()}", + "alertByOrgUnits": $!{validationRuleGroup.alertByOrgUnits}" } } === 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-10-13 18:20:54 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm 2014-03-28 01:51:34 +0000 @@ -12,10 +12,10 @@ return option; } }); - jQuery("#availableUserRolesToAlert").dhisAjaxSelect({ - source: "../dhis-web-commons-ajax-json/getUserRoles.action", - iterator: "userRoles", - connectedTo: 'userRolesToAlert', + jQuery("#availableUserGroupsToAlert").dhisAjaxSelect({ + source: "../dhis-web-commons-ajax-json/getUserGroups.action", + iterator: "userGroups", + connectedTo: 'userGroupsToAlert', handler: function(item) { var option = jQuery("<option />"); option.text( item.name ); @@ -90,28 +90,40 @@ </tr> <tr> - <td><label>$i18n.getString( "user_roles_to_alert" )</label></td> + <td><label>$i18n.getString( "user_groups_to_alert" )</label></td> <td> - <select id="availableUserRolesToAlert" name="availableUserRoles" multiple="multiple" style="height: 200px; width: 100%;"></select> + <select id="availableUserGroupsToAlert" name="availableUserGroups" 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( 'userRolesToAlert' );"/><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( 'userRolesToAlert' );"/> + <input type="button" value=">" title="$i18n.getString( 'move_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'availableUserGroupsToAlert' );"/><br/> + <input type="button" value="<" title="$i18n.getString( 'remove_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'userGroupsToAlert' );"/><br/> + <input type="button" value=">>" title="$i18n.getString('move_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'availableUserGroupsToAlert' );"/><br/> + <input type="button" value="<<" title="$i18n.getString('remove_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'userGroupsToAlert' );"/> </td> <td> - <select id="userRolesToAlert" name="userRolesToAlert" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" /> - #foreach( $userRole in $userRolesToAlert ) - <option value="$userRole.id">$encoder.htmlEncode( $userRole.displayName )</option> + <select id="userGroupsToAlert" name="userGroupsToAlert" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" /> + #foreach( $userGroup in $userGroupsToAlert ) + <option value="$userGroup.id">$encoder.htmlEncode( $userGroup.displayName )</option> #end </select> </td> </tr> </table> +<table> + <tr> + <td><label for="alertByOrgUnits">$i18n.getString( "alert_by_org_units" )</label></td> + <td> + <select type="text" id="alertByOrgUnits" name="alertByOrgUnits"> + <option value="false">$i18n.getString( "no" )</option> + <option value="true"#if( $validationRuleGroup.alertByOrgUnits == true ) selected="selected"#end>$i18n.getString( "yes" )</option> + </select> + </td> + </tr> +</table> + <p> <input type="submit" value="$i18n.getString( "save" )" style="width:10em" /> <input type="button" value="$i18n.getString( "cancel" )" onclick="window.location.href='showValidationRuleGroupForm.action'" style="width:10em" /> === modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRuleGroup.vm' --- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRuleGroup.vm 2014-03-25 08:05:13 +0000 +++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRuleGroup.vm 2014-03-28 01:51:34 +0000 @@ -66,7 +66,7 @@ <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( "number_of_members" ):</label><br><span id="memberCountField"></span></p> - <p><label>$i18n.getString( "number_of_user_roles_to_alert" ):</label><br><span id="userRolesToAlertCountField"></span></p> + <p><label>$i18n.getString( "number_of_user_groups_to_alert" ):</label><br><span id="userGroupsToAlertCountField"></span></p> </div> <div id="warningArea">
_______________________________________________ Mailing list: https://launchpad.net/~dhis2-devs Post to : dhis2-devs@lists.launchpad.net Unsubscribe : https://launchpad.net/~dhis2-devs More help : https://help.launchpad.net/ListHelp