------------------------------------------------------------ revno: 19419 committer: Abyot Asalefew Gizaw <aby...@gmail.com> branch nick: dhis2 timestamp: Tue 2015-06-16 13:22:51 +0200 message: moved services related to rules engine to dhis-web-commons-resources modified: dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js
-- 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-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js 2015-06-14 13:07:10 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js 2015-06-16 11:22:51 +0000 @@ -1773,469 +1773,4 @@ return event; } }; -}) - -/* service for building variables based on the data in users fields */ -.service('VariableService', function($rootScope,$q,TrackerRuleVariableFactory,$filter,orderByFilter,$log){ - return { - getVariables: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) { - var thePromisedVariables = $q.defer(); - var variables = {}; - - var pushVariable = function(variablename, variableValue, variableType, variablefound) { - //First clean away single or double quotation marks at the start and end of the variable name. - variableValue = $filter('trimquotes')(variableValue); - - //Append single quotation marks in case the variable is of text type: - if(variableType === 'string') { - variableValue = "'" + variableValue + "'"; - } - else if(variableType === 'date') { - variableValue = "'" + variableValue + "'"; - } - else if(variableType === 'bool' || variableType === 'trueOnly') { - if(eval(variableValue)) { - variableValue = true; - } - else { - variableValue = false; - } - } - else if(variableType === "int" || variableType === "number") { - variableValue = Number(variableValue); - } - else{ - $log.warn("unknown datatype:" + variableType); - } - - - //Make sure that the variableValue does not contain a dollar sign anywhere - //- this would potentially mess up later use of the variable: -// if(angular.isDefined(variableValue) -// && variableValue !== null -// && variableValue.indexOf("$") !== -1 ) { -// variableValue = variableValue.replace(/\\$/,""); -// } - - //TODO: - //Also clean away instructions that might be erroneusly evalutated in javascript - - variables[variablename] = { - variableValue:variableValue, - variableType:variableType, - hasValue:variablefound - }; - }; - - TrackerRuleVariableFactory.getProgramRuleVariables(programid).then(function(programVariables){ - - // The following section will need a different implementation for event capture: - var allEventsSorted = []; - var currentEvent = executingEvent; - var eventsSortedPerProgramStage = []; - - for(var key in allEventsByStage){ - if(allEventsByStage.hasOwnProperty(key)){ - eventsSortedPerProgramStage[key] = []; - angular.forEach(allEventsByStage[key], function(event){ - allEventsSorted.push(event); - eventsSortedPerProgramStage[key].push(event); - }); - eventsSortedPerProgramStage[key] = orderByFilter(eventsSortedPerProgramStage[key], '-sortingDate').reverse(); - } - } - allEventsSorted = orderByFilter(allEventsSorted, '-sortingDate').reverse(); - - var allDes = allDataElements; -// angular.forEach($scope.programStages, function(programStage){ -// angular.forEach(programStage.programStageDataElements, function(dataElement) { -// allDes[dataElement.dataElement.id] = dataElement; -// }); -// }); - //End of region that neeeds specific implementation for event capture - - angular.forEach(programVariables, function(programVariable) { - var valueFound = false; - if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE"){ - if(programVariable.programStage) { - angular.forEach(eventsSortedPerProgramStage[programVariable.programStage.id], function(event) { - if(angular.isDefined(event[programVariable.dataElement.id]) - && event[programVariable.dataElement.id] !== null ){ - valueFound = true; - pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); - } - }); - } else { - $log.warn("Variable id:'" + programVariable.id + "' name:'" + programVariable.name - + "' does not have a programstage defined," - + " despite that the variable has sourcetype DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE" ); - } - - } - else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM"){ - angular.forEach(allEventsSorted, function(event) { - if(angular.isDefined(event[programVariable.dataElement.id]) - && event[programVariable.dataElement.id] !== null ){ - valueFound = true; - pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); - } - }); - } - else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_CURRENT_EVENT"){ - if(angular.isDefined(currentEvent[programVariable.dataElement.id]) - && currentEvent[programVariable.dataElement.id] !== null ){ - valueFound = true; - pushVariable(programVariable.name, currentEvent[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); - } - } - else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_PREVIOUS_EVENT"){ - //Only continue checking for a value if there is more than one event. - if(allEventsSorted && allEventsSorted.length > 1) { - var previousvalue = null; - var currentEventPassed = false; - for(var i = 0; i < allEventsSorted.length; i++) { - //Store the values as we iterate through the stages - //If the event[i] is not the current event, it is older(previous). Store the previous value if it exists - if(!currentEventPassed && allEventsSorted[i] !== currentEvent && - angular.isDefined(allEventsSorted[i][programVariable.dataElement.id])) { - previousvalue = allEventsSorted[i][programVariable.dataElement.id]; - valueFound = true; - } - else if(allEventsSorted[i] === currentEvent) { - //We have iterated to the newest event - store the last collected variable value - if any is found: - if(valueFound) { - pushVariable(programVariable.name, previousvalue, allDes[programVariable.dataElement.id].dataElement.type, valueFound ); - } - //Set currentEventPassed, ending the iteration: - currentEventPassed = true; - } - } - } - } - else if(programVariable.programRuleVariableSourceType === "TEI_ATTRIBUTE"){ - angular.forEach(selectedEntity.attributes , function(attribute) { - if(!valueFound) { - if(attribute.attribute === programVariable.trackedEntityAttribute.id) { - valueFound = true; - pushVariable(programVariable.name, attribute.value, attribute.type, valueFound ); - } - } - }); - } - else if(programVariable.programRuleVariableSourceType === "CALCULATED_VALUE"){ - //We won't assign the calculated variables at this step. The rules execution will calculate and assign the variable. - } - else if(programVariable.programRuleVariableSourceType === "NUMBEROFEVENTS_PROGRAMSTAGE"){ - var numberOfEvents = 0; - if( programVariable.programStage && eventsSortedPerProgramStage[programVariable.programStage.id] ) { - numberOfEvents = eventsSortedPerProgramStage[programVariable.programStage.id].length; - } - valueFound = true; - pushVariable(programVariable.name, numberOfEvents, 'int', valueFound ); - } - else { - //Missing handing of ruletype - $log.warn("Unknown programRuleVariableSourceType:" + programVariable.programRuleVariableSourceType); - } - - - if(!valueFound){ - //If there is still no value found, assign default value: - if(programVariable.dataElement) { - var dataElement = allDes[programVariable.dataElement.id]; - if( dataElement ) { - pushVariable(programVariable.name, "", dataElement.dataElement.type ); - } - else { - $log.warn("Variable #{" + programVariable.name + "} is linked to a dataelement that is not part of the program"); - pushVariable(programVariable.name, "", "string" ); - } - } - else { - pushVariable(programVariable.name, "", "string" ); - } - } - }); - - //add context variables: - //last parameter "valuefound" is always true for event date - pushVariable('eventdate', executingEvent.eventDate, 'date', true ); - - thePromisedVariables.resolve(variables); - }); - - return thePromisedVariables.promise; - } - }; -}) - - - -/* service for executing tracker rules and broadcasting results */ -.service('TrackerRulesExecutionService', function(TrackerRulesFactory,VariableService, $rootScope, $log, $filter, orderByFilter){ - return { - executeRules: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) { - //When debugging rules, the caller should provide a variable for wether or not the rules is being debugged. - //hard coding this for now: - var debug = true; - var verbose = true; - - var variablesHash = {}; - - var replaceVariables = function(expression) { - //replaces the variables in an expression with actual variable values. - //First check if the expression contains variables at all(any dollar signs): - if(expression.indexOf('#') !== -1) { - //Find every variable name in the expression; - var variablespresent = expression.match(/#{\w+}/g); - //Replace each matched variable: - angular.forEach(variablespresent, function(variablepresent) { - //First strip away any dollar signs from the variable name: - variablepresent = variablepresent.replace("#{","").replace("}",""); - - if(angular.isDefined(variablesHash[variablepresent])) { - //Replace all occurrences of the variable name(hence using regex replacement): - expression = expression.replace(new RegExp("#{" + variablepresent + "}", 'g'), - variablesHash[variablepresent].variableValue); - } - else { - $log.warn("Expression " + expression + " conains variable " + variablepresent - + " - but this variable is not defined." ); - } - - }); - } - return expression; - }; - - var runDhisFunctions = function(expression) { - //Called from "runExpression". Only proceed with this logic in case there seems to be dhis function calls: "dhis." is present. - if(angular.isDefined(expression) && expression.indexOf("dhis.") !== -1){ - var dhisFunctions = [{name:"dhis.daysbetween",parameters:2}, - {name:"dhis.floor",parameters:1}, - {name:"dhis.modulus",parameters:2}, - {name:"dhis.hasValue",parameters:1}, - {name:"dhis.concatenate"}]; - - angular.forEach(dhisFunctions, function(dhisFunction){ - //Replace each * with a regex that matches each parameter, allowing commas only inside single quotation marks. - var regularExFunctionCall = new RegExp(dhisFunction.name.replace(".","\\.") + "\\([^\\)]*\\)",'g'); - var callsToThisFunction = expression.match(regularExFunctionCall); - angular.forEach(callsToThisFunction, function(callToThisFunction){ - //Remove the function name and paranthesis: - var justparameters = callToThisFunction.replace(/(^[^\(]+\()|\)$/g,""); - //Then split into single parameters: - var parameters = justparameters.match(/(('[^']+')|([^,]+))/g); - - //Show error if no parameters is given and the function requires parameters, - //or if the number of parameters is wrong. - if(angular.isDefined(dhisFunction.parameters)){ - //But we are only checking parameters where the dhisFunction actually has a defined set of parameters(concatenate, for example, does not have a fixed number); - if((!angular.isDefined(parameters) && dhisFunction.parameters > 0) - || parameters.length !== dhisFunction.parameters){ - $log.warn(dhisFunction.name + " was called with the incorrect number of parameters"); - } - } - - //In case the function call is nested, the parameter itself contains an expression, run the expression. - if(angular.isDefined(parameters)) { - for (var i = 0; i < parameters.length; i++) { - parameters[i] = runExpression(parameters[i],dhisFunction.name,"parameter:" + i); - } - } - - //Special block for dhis.weeksBetween(*,*) - add such a block for all other dhis functions. - if(dhisFunction.name === "dhis.daysbetween") - { - var firstdate = $filter('trimquotes')(parameters[0]); - var seconddate = $filter('trimquotes')(parameters[1]); - firstdate = moment(firstdate); - seconddate = moment(seconddate); - //Replace the end evaluation of the dhis function: - expression = expression.replace(callToThisFunction, seconddate.diff(firstdate,'days')); - } - else if(dhisFunction.name === "dhis.floor") - { - var floored = Math.floor(parameters[0]); - //Replace the end evaluation of the dhis function: - expression = expression.replace(callToThisFunction, floored); - } - else if(dhisFunction.name === "dhis.modulus") - { - var dividend = Number(parameters[0]); - var divisor = Number(parameters[1]); - var rest = dividend % divisor; - //Replace the end evaluation of the dhis function: - expression = expression.replace(callToThisFunction, rest); - } - else if(dhisFunction.name === "dhis.hasValue") - { - //"evaluate" hasvalue to true or false: - if(variablesHash[parameters[0]].hasValue){ - expression = expression.replace(callToThisFunction, 'true'); - } else { - expression = expression.replace(callToThisFunction, 'false'); - } - } - else if(dhisFunction.name === "dhis.concatenate") - { - var returnString = "'"; - for (var i = 0; i < parameters.length; i++) { - returnString += parameters[i]; - } - returnString += "'"; - expression = expression.replace(callToThisFunction, returnString); - } - }); - }); - } - - return expression; - }; - - var runExpression = function(expression, beforereplacement, identifier ){ - //determine if expression is true, and actions should be effectuated - //If DEBUG mode, use try catch and report errors. If not, omit the heavy try-catch loop.: - var answer = false; - if(debug) { - try{ - - var dhisfunctionsevaluated = runDhisFunctions(expression); - answer = eval(dhisfunctionsevaluated); - - if(verbose) - { - $log.info("Expression with id " + identifier + " was successfully run. Original condition was: " + beforereplacement + " - Evaluation ended up as:" + expression + " - Result of evaluation was:" + answer); - } - } - catch(e) - { - $log.warn("Expression with id " + identifier + " could not be run. Original condition was: " + beforereplacement + " - Evaluation ended up as:" + expression + " - error message:" + e); - } - } - else { - //Just run the expression. This is much faster than the debug route: http://jsperf.com/try-catch-block-loop-performance-comparison - var dhisfunctionsevaluated = runDhisFunctions(expression); - answer = eval(dhisfunctionsevaluated); - } - return answer; - }; - - - VariableService.getVariables(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity).then(function(variablesReceived){ - TrackerRulesFactory.getProgramStageRules(programid, executingEvent.programStage).then(function(rules){ - //But run rules in priority - lowest number first(priority null is last) - rules = orderByFilter(rules, 'priority'); - - variablesHash = variablesReceived; - - if(angular.isObject(rules) && angular.isArray(rules)){ - //The program has rules, and we want to run them. - //Prepare repository unless it is already prepared: - if(angular.isUndefined( $rootScope.ruleeffects ) ) { - $rootScope.ruleeffects = {}; - } - - if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event] )){ - $rootScope.ruleeffects[executingEvent.event] = {}; - } - - var updatedEffectsExits = false; - - angular.forEach(rules, function(rule) { - var ruleEffective = false; - - var expression = rule.condition; - //Go through and populate variables with actual values, but only if there actually is any replacements to be made(one or more "$" is present) - if(expression) { - if(expression.indexOf('#') !== -1) { - expression = replaceVariables(expression); - } - //run expression: - ruleEffective = runExpression(expression, rule.condition, "rule:" + rule.id); - } else { - $log.warn("Rule id:'" + rule.id + "'' and name:'" + rule.name + "' had no condition specified. Please check rule configuration."); - } - - angular.forEach(rule.actions, function(action){ - //In case the effect-hash is not populated, add entries - if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event][action.id] )){ - $rootScope.ruleeffects[executingEvent.event][action.id] = { - id:action.id, - location:action.location, - action:action.programRuleActionType, - dataElement:action.dataElement, - content:action.content, - data:action.data, - ineffect:false - }; - } - - //In case the rule is effective and contains specific data, - //the effect be refreshed from the variables list. - //If the rule is not effective we can skip this step - if(ruleEffective && action.data) - { - //The key data might be containing a dollar sign denoting that the key data is a variable. - //To make a lookup in variables hash, we must make a lookup without the dollar sign in the variable name - //The first strategy is to make a direct lookup. In case the "data" expression is more complex, we have to do more replacement and evaluation. - - var nameWithoutBrackets = action.data.replace('#{','').replace('}',''); - if(angular.isDefined(variablesHash[nameWithoutBrackets])) - { - //The variable exists, and is replaced with its corresponding value - $rootScope.ruleeffects[executingEvent.event][action.id].data = - variablesHash[nameWithoutBrackets].variableValue; - } - else if(action.data.indexOf('#') !== -1) - { - //Since the value couldnt be looked up directly, and contains a dollar sign, the expression was more complex - //Now we will have to make a thorough replacement and separate evaluation to find the correct value: - $rootScope.ruleeffects[executingEvent.event][action.id].data = replaceVariables(action.data); - //In a scenario where the data contains a complex expression, evaluate the expression to compile(calculate) the result: - $rootScope.ruleeffects[executingEvent.event][action.id].data = runExpression($rootScope.ruleeffects[executingEvent.event][action.id].data, action.data, "action:" + action.id); - } - } - - //Update the rule effectiveness if it changed in this evaluation; - if($rootScope.ruleeffects[executingEvent.event][action.id].ineffect !== ruleEffective) - { - //There is a change in the rule outcome, we need to update the effect object. - updatedEffectsExits = true; - $rootScope.ruleeffects[executingEvent.event][action.id].ineffect = ruleEffective; - } - - //In case the rule is of type "assign variable" and the rule is effective, - //the variable data result needs to be applied to the correct variable: - if($rootScope.ruleeffects[executingEvent.event][action.id].action === "ASSIGNVARIABLE" && $rootScope.ruleeffects[executingEvent.event][action.id].ineffect){ - //from earlier evaluation, the data portion of the ruleeffect now contains the value of the variable to be assign. - //the content portion of the ruleeffect defines the name for the variable, when dollar is removed: - var variabletoassign = $rootScope.ruleeffects[executingEvent.event][action.id].content.replace("#{","").replace("}",""); - - if(!angular.isDefined(variablesHash[variabletoassign])){ - $log.warn("Variable " + variabletoassign + " was not defined."); - } - - //Even if the variable is not defined: we assign it: - if(variablesHash[variabletoassign].variableValue !== $rootScope.ruleeffects[executingEvent.event][action.id].data){ - //If the variable was actually updated, we assume that there is an updated ruleeffect somewhere: - updatedEffectsExits = true; - //Then we assign the new value: - variablesHash[variabletoassign].variableValue = $rootScope.ruleeffects[executingEvent.event][action.id].data; - } - } - }); - }); - - //Broadcast rules finished if there was any actual changes to the event. - if(updatedEffectsExits){ - $rootScope.$broadcast("ruleeffectsupdated", { event: executingEvent.event }); - } - } - - return true; - }); - }); - } - }; }); === modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js 2015-06-15 15:29:59 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js 2015-06-16 11:22:51 +0000 @@ -707,4 +707,467 @@ return colExists; } }; +}) + +/* service for building variables based on the data in users fields */ +.service('VariableService', function($rootScope,$q,TrackerRuleVariableFactory,$filter,orderByFilter,$log){ + return { + getVariables: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) { + var thePromisedVariables = $q.defer(); + var variables = {}; + + var pushVariable = function(variablename, variableValue, variableType, variablefound) { + //First clean away single or double quotation marks at the start and end of the variable name. + variableValue = $filter('trimquotes')(variableValue); + + //Append single quotation marks in case the variable is of text type: + if(variableType === 'string') { + variableValue = "'" + variableValue + "'"; + } + else if(variableType === 'date') { + variableValue = "'" + variableValue + "'"; + } + else if(variableType === 'bool' || variableType === 'trueOnly') { + if(eval(variableValue)) { + variableValue = true; + } + else { + variableValue = false; + } + } + else if(variableType === "int" || variableType === "number") { + variableValue = Number(variableValue); + } + else{ + $log.warn("unknown datatype:" + variableType); + } + + + //Make sure that the variableValue does not contain a dollar sign anywhere + //- this would potentially mess up later use of the variable: +// if(angular.isDefined(variableValue) +// && variableValue !== null +// && variableValue.indexOf("$") !== -1 ) { +// variableValue = variableValue.replace(/\\$/,""); +// } + + //TODO: + //Also clean away instructions that might be erroneusly evalutated in javascript + + variables[variablename] = { + variableValue:variableValue, + variableType:variableType, + hasValue:variablefound + }; + }; + + TrackerRuleVariableFactory.getProgramRuleVariables(programid).then(function(programVariables){ + + // The following section will need a different implementation for event capture: + var allEventsSorted = []; + var currentEvent = executingEvent; + var eventsSortedPerProgramStage = []; + + for(var key in allEventsByStage){ + if(allEventsByStage.hasOwnProperty(key)){ + eventsSortedPerProgramStage[key] = []; + angular.forEach(allEventsByStage[key], function(event){ + allEventsSorted.push(event); + eventsSortedPerProgramStage[key].push(event); + }); + eventsSortedPerProgramStage[key] = orderByFilter(eventsSortedPerProgramStage[key], '-sortingDate').reverse(); + } + } + allEventsSorted = orderByFilter(allEventsSorted, '-sortingDate').reverse(); + + var allDes = allDataElements; +// angular.forEach($scope.programStages, function(programStage){ +// angular.forEach(programStage.programStageDataElements, function(dataElement) { +// allDes[dataElement.dataElement.id] = dataElement; +// }); +// }); + //End of region that neeeds specific implementation for event capture + + angular.forEach(programVariables, function(programVariable) { + var valueFound = false; + if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE"){ + if(programVariable.programStage) { + angular.forEach(eventsSortedPerProgramStage[programVariable.programStage.id], function(event) { + if(angular.isDefined(event[programVariable.dataElement.id]) + && event[programVariable.dataElement.id] !== null ){ + valueFound = true; + pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); + } + }); + } else { + $log.warn("Variable id:'" + programVariable.id + "' name:'" + programVariable.name + + "' does not have a programstage defined," + + " despite that the variable has sourcetype DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE" ); + } + + } + else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_NEWEST_EVENT_PROGRAM"){ + angular.forEach(allEventsSorted, function(event) { + if(angular.isDefined(event[programVariable.dataElement.id]) + && event[programVariable.dataElement.id] !== null ){ + valueFound = true; + pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); + } + }); + } + else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_CURRENT_EVENT"){ + if(angular.isDefined(currentEvent[programVariable.dataElement.id]) + && currentEvent[programVariable.dataElement.id] !== null ){ + valueFound = true; + pushVariable(programVariable.name, currentEvent[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); + } + } + else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_PREVIOUS_EVENT"){ + //Only continue checking for a value if there is more than one event. + if(allEventsSorted && allEventsSorted.length > 1) { + var previousvalue = null; + var currentEventPassed = false; + for(var i = 0; i < allEventsSorted.length; i++) { + //Store the values as we iterate through the stages + //If the event[i] is not the current event, it is older(previous). Store the previous value if it exists + if(!currentEventPassed && allEventsSorted[i] !== currentEvent && + angular.isDefined(allEventsSorted[i][programVariable.dataElement.id])) { + previousvalue = allEventsSorted[i][programVariable.dataElement.id]; + valueFound = true; + } + else if(allEventsSorted[i] === currentEvent) { + //We have iterated to the newest event - store the last collected variable value - if any is found: + if(valueFound) { + pushVariable(programVariable.name, previousvalue, allDes[programVariable.dataElement.id].dataElement.type, valueFound ); + } + //Set currentEventPassed, ending the iteration: + currentEventPassed = true; + } + } + } + } + else if(programVariable.programRuleVariableSourceType === "TEI_ATTRIBUTE"){ + angular.forEach(selectedEntity.attributes , function(attribute) { + if(!valueFound) { + if(attribute.attribute === programVariable.trackedEntityAttribute.id) { + valueFound = true; + pushVariable(programVariable.name, attribute.value, attribute.type, valueFound ); + } + } + }); + } + else if(programVariable.programRuleVariableSourceType === "CALCULATED_VALUE"){ + //We won't assign the calculated variables at this step. The rules execution will calculate and assign the variable. + } + else if(programVariable.programRuleVariableSourceType === "NUMBEROFEVENTS_PROGRAMSTAGE"){ + var numberOfEvents = 0; + if( programVariable.programStage && eventsSortedPerProgramStage[programVariable.programStage.id] ) { + numberOfEvents = eventsSortedPerProgramStage[programVariable.programStage.id].length; + } + valueFound = true; + pushVariable(programVariable.name, numberOfEvents, 'int', valueFound ); + } + else { + //Missing handing of ruletype + $log.warn("Unknown programRuleVariableSourceType:" + programVariable.programRuleVariableSourceType); + } + + + if(!valueFound){ + //If there is still no value found, assign default value: + if(programVariable.dataElement) { + var dataElement = allDes[programVariable.dataElement.id]; + if( dataElement ) { + pushVariable(programVariable.name, "", dataElement.dataElement.type ); + } + else { + $log.warn("Variable #{" + programVariable.name + "} is linked to a dataelement that is not part of the program"); + pushVariable(programVariable.name, "", "string" ); + } + } + else { + pushVariable(programVariable.name, "", "string" ); + } + } + }); + + //add context variables: + //last parameter "valuefound" is always true for event date + pushVariable('eventdate', executingEvent.eventDate, 'date', true ); + + thePromisedVariables.resolve(variables); + }); + + return thePromisedVariables.promise; + } + }; +}) + +/* service for executing tracker rules and broadcasting results */ +.service('TrackerRulesExecutionService', function(TrackerRulesFactory,VariableService, $rootScope, $log, $filter, orderByFilter){ + return { + executeRules: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) { + //When debugging rules, the caller should provide a variable for wether or not the rules is being debugged. + //hard coding this for now: + var debug = true; + var verbose = true; + + var variablesHash = {}; + + var replaceVariables = function(expression) { + //replaces the variables in an expression with actual variable values. + //First check if the expression contains variables at all(any dollar signs): + if(expression.indexOf('#') !== -1) { + //Find every variable name in the expression; + var variablespresent = expression.match(/#{\w+}/g); + //Replace each matched variable: + angular.forEach(variablespresent, function(variablepresent) { + //First strip away any dollar signs from the variable name: + variablepresent = variablepresent.replace("#{","").replace("}",""); + + if(angular.isDefined(variablesHash[variablepresent])) { + //Replace all occurrences of the variable name(hence using regex replacement): + expression = expression.replace(new RegExp("#{" + variablepresent + "}", 'g'), + variablesHash[variablepresent].variableValue); + } + else { + $log.warn("Expression " + expression + " conains variable " + variablepresent + + " - but this variable is not defined." ); + } + + }); + } + return expression; + }; + + var runDhisFunctions = function(expression) { + //Called from "runExpression". Only proceed with this logic in case there seems to be dhis function calls: "dhis." is present. + if(angular.isDefined(expression) && expression.indexOf("dhis.") !== -1){ + var dhisFunctions = [{name:"dhis.daysbetween",parameters:2}, + {name:"dhis.floor",parameters:1}, + {name:"dhis.modulus",parameters:2}, + {name:"dhis.hasValue",parameters:1}, + {name:"dhis.concatenate"}]; + + angular.forEach(dhisFunctions, function(dhisFunction){ + //Replace each * with a regex that matches each parameter, allowing commas only inside single quotation marks. + var regularExFunctionCall = new RegExp(dhisFunction.name.replace(".","\\.") + "\\([^\\)]*\\)",'g'); + var callsToThisFunction = expression.match(regularExFunctionCall); + angular.forEach(callsToThisFunction, function(callToThisFunction){ + //Remove the function name and paranthesis: + var justparameters = callToThisFunction.replace(/(^[^\(]+\()|\)$/g,""); + //Then split into single parameters: + var parameters = justparameters.match(/(('[^']+')|([^,]+))/g); + + //Show error if no parameters is given and the function requires parameters, + //or if the number of parameters is wrong. + if(angular.isDefined(dhisFunction.parameters)){ + //But we are only checking parameters where the dhisFunction actually has a defined set of parameters(concatenate, for example, does not have a fixed number); + if((!angular.isDefined(parameters) && dhisFunction.parameters > 0) + || parameters.length !== dhisFunction.parameters){ + $log.warn(dhisFunction.name + " was called with the incorrect number of parameters"); + } + } + + //In case the function call is nested, the parameter itself contains an expression, run the expression. + if(angular.isDefined(parameters)) { + for (var i = 0; i < parameters.length; i++) { + parameters[i] = runExpression(parameters[i],dhisFunction.name,"parameter:" + i); + } + } + + //Special block for dhis.weeksBetween(*,*) - add such a block for all other dhis functions. + if(dhisFunction.name === "dhis.daysbetween") + { + var firstdate = $filter('trimquotes')(parameters[0]); + var seconddate = $filter('trimquotes')(parameters[1]); + firstdate = moment(firstdate); + seconddate = moment(seconddate); + //Replace the end evaluation of the dhis function: + expression = expression.replace(callToThisFunction, seconddate.diff(firstdate,'days')); + } + else if(dhisFunction.name === "dhis.floor") + { + var floored = Math.floor(parameters[0]); + //Replace the end evaluation of the dhis function: + expression = expression.replace(callToThisFunction, floored); + } + else if(dhisFunction.name === "dhis.modulus") + { + var dividend = Number(parameters[0]); + var divisor = Number(parameters[1]); + var rest = dividend % divisor; + //Replace the end evaluation of the dhis function: + expression = expression.replace(callToThisFunction, rest); + } + else if(dhisFunction.name === "dhis.hasValue") + { + //"evaluate" hasvalue to true or false: + if(variablesHash[parameters[0]].hasValue){ + expression = expression.replace(callToThisFunction, 'true'); + } else { + expression = expression.replace(callToThisFunction, 'false'); + } + } + else if(dhisFunction.name === "dhis.concatenate") + { + var returnString = "'"; + for (var i = 0; i < parameters.length; i++) { + returnString += parameters[i]; + } + returnString += "'"; + expression = expression.replace(callToThisFunction, returnString); + } + }); + }); + } + + return expression; + }; + + var runExpression = function(expression, beforereplacement, identifier ){ + //determine if expression is true, and actions should be effectuated + //If DEBUG mode, use try catch and report errors. If not, omit the heavy try-catch loop.: + var answer = false; + if(debug) { + try{ + + var dhisfunctionsevaluated = runDhisFunctions(expression); + answer = eval(dhisfunctionsevaluated); + + if(verbose) + { + $log.info("Expression with id " + identifier + " was successfully run. Original condition was: " + beforereplacement + " - Evaluation ended up as:" + expression + " - Result of evaluation was:" + answer); + } + } + catch(e) + { + $log.warn("Expression with id " + identifier + " could not be run. Original condition was: " + beforereplacement + " - Evaluation ended up as:" + expression + " - error message:" + e); + } + } + else { + //Just run the expression. This is much faster than the debug route: http://jsperf.com/try-catch-block-loop-performance-comparison + var dhisfunctionsevaluated = runDhisFunctions(expression); + answer = eval(dhisfunctionsevaluated); + } + return answer; + }; + + + VariableService.getVariables(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity).then(function(variablesReceived){ + TrackerRulesFactory.getProgramStageRules(programid, executingEvent.programStage).then(function(rules){ + //But run rules in priority - lowest number first(priority null is last) + rules = orderByFilter(rules, 'priority'); + + variablesHash = variablesReceived; + + if(angular.isObject(rules) && angular.isArray(rules)){ + //The program has rules, and we want to run them. + //Prepare repository unless it is already prepared: + if(angular.isUndefined( $rootScope.ruleeffects ) ) { + $rootScope.ruleeffects = {}; + } + + if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event] )){ + $rootScope.ruleeffects[executingEvent.event] = {}; + } + + var updatedEffectsExits = false; + + angular.forEach(rules, function(rule) { + var ruleEffective = false; + + var expression = rule.condition; + //Go through and populate variables with actual values, but only if there actually is any replacements to be made(one or more "$" is present) + if(expression) { + if(expression.indexOf('#') !== -1) { + expression = replaceVariables(expression); + } + //run expression: + ruleEffective = runExpression(expression, rule.condition, "rule:" + rule.id); + } else { + $log.warn("Rule id:'" + rule.id + "'' and name:'" + rule.name + "' had no condition specified. Please check rule configuration."); + } + + angular.forEach(rule.actions, function(action){ + //In case the effect-hash is not populated, add entries + if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event][action.id] )){ + $rootScope.ruleeffects[executingEvent.event][action.id] = { + id:action.id, + location:action.location, + action:action.programRuleActionType, + dataElement:action.dataElement, + content:action.content, + data:action.data, + ineffect:false + }; + } + + //In case the rule is effective and contains specific data, + //the effect be refreshed from the variables list. + //If the rule is not effective we can skip this step + if(ruleEffective && action.data) + { + //The key data might be containing a dollar sign denoting that the key data is a variable. + //To make a lookup in variables hash, we must make a lookup without the dollar sign in the variable name + //The first strategy is to make a direct lookup. In case the "data" expression is more complex, we have to do more replacement and evaluation. + + var nameWithoutBrackets = action.data.replace('#{','').replace('}',''); + if(angular.isDefined(variablesHash[nameWithoutBrackets])) + { + //The variable exists, and is replaced with its corresponding value + $rootScope.ruleeffects[executingEvent.event][action.id].data = + variablesHash[nameWithoutBrackets].variableValue; + } + else if(action.data.indexOf('#') !== -1) + { + //Since the value couldnt be looked up directly, and contains a dollar sign, the expression was more complex + //Now we will have to make a thorough replacement and separate evaluation to find the correct value: + $rootScope.ruleeffects[executingEvent.event][action.id].data = replaceVariables(action.data); + //In a scenario where the data contains a complex expression, evaluate the expression to compile(calculate) the result: + $rootScope.ruleeffects[executingEvent.event][action.id].data = runExpression($rootScope.ruleeffects[executingEvent.event][action.id].data, action.data, "action:" + action.id); + } + } + + //Update the rule effectiveness if it changed in this evaluation; + if($rootScope.ruleeffects[executingEvent.event][action.id].ineffect !== ruleEffective) + { + //There is a change in the rule outcome, we need to update the effect object. + updatedEffectsExits = true; + $rootScope.ruleeffects[executingEvent.event][action.id].ineffect = ruleEffective; + } + + //In case the rule is of type "assign variable" and the rule is effective, + //the variable data result needs to be applied to the correct variable: + if($rootScope.ruleeffects[executingEvent.event][action.id].action === "ASSIGNVARIABLE" && $rootScope.ruleeffects[executingEvent.event][action.id].ineffect){ + //from earlier evaluation, the data portion of the ruleeffect now contains the value of the variable to be assign. + //the content portion of the ruleeffect defines the name for the variable, when dollar is removed: + var variabletoassign = $rootScope.ruleeffects[executingEvent.event][action.id].content.replace("#{","").replace("}",""); + + if(!angular.isDefined(variablesHash[variabletoassign])){ + $log.warn("Variable " + variabletoassign + " was not defined."); + } + + //Even if the variable is not defined: we assign it: + if(variablesHash[variabletoassign].variableValue !== $rootScope.ruleeffects[executingEvent.event][action.id].data){ + //If the variable was actually updated, we assume that there is an updated ruleeffect somewhere: + updatedEffectsExits = true; + //Then we assign the new value: + variablesHash[variabletoassign].variableValue = $rootScope.ruleeffects[executingEvent.event][action.id].data; + } + } + }); + }); + + //Broadcast rules finished if there was any actual changes to the event. + if(updatedEffectsExits){ + $rootScope.$broadcast("ruleeffectsupdated", { event: executingEvent.event }); + } + } + + return true; + }); + }); + } + }; });
_______________________________________________ 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