This is a more in-depth look at how to use REST APIs to send emails from Calculations Manager. Please go to the send email post before reading any further if you are not sure how its done. I experienced an issue while sending an email from Calculation Manager; whenever a special character or multi-lines appear in the body of the email, the system returns a ‘NOT FOUND’ error, preventing the email from being sent. Following some trial and error, I noticed that replacing next line characters with <br> worked perfectly. So I attempted to learn more about HTML (I had no prior knowledge with the language) by creating a simple summary email with an html table.
In this post, I’ll explain how to run multiple business rules in a sequence, retrieve the status and information for each rule, and communicate them to users in a summary table.
EXECUTE MULTIPLE BUSINESS RULES
We’ll utilize job factory to execute numerous business rules in a sequence. I prefer to write a function so that I may reuse it without having to write the code over and over again. Business rule name and RTPs would be the function’s inputs, and the function’s outputs would be status, Start Time, End Time, and Total Time Elapsed, which we’d utilize in the summary table for each business rule. I use static variables as I am using multiple small functions and wanted the variable values accessible at all times.
//business rules & statuses
class BusinessRule {
static Map<String, Map<String, String>> BUSINESS_RULES = [:]
static Map<String, Map<String, String>> BUSINESS_RULES_STATUS = [:]
static String CURRENT_BUSINESS_RULE = ""
}
Map<String, String> executeBusinessRule(String ruleName, Map runTimePrompts) {
//capture the start time
Date startDate = new Date()
//define the job factory
JobFactory jf = operation.application.jobFactory
JobDefinition jobDef = jf.job(ruleName, "Rules", runTimePrompts)
Job job = executeJob(jobDef)
//capture the end time
Date endDate = new Date()
//return rule execution stats
return ['status': job.status.toString(), 'startTime': startDate.toString(), 'endTime': endDate.toString(), 'timeElapsed': { (endDate.time - startDate.time) / 1000 }().toString()]
}
define and execute the business rules. Lets ignore the sendEmail function for now. Once we finalize the send email function, this is the main code which executes each rule and send
//Specify the business rules for executions
BusinessRule.BUSINESS_RULES = [ 'Groovy - Actuals data calculations': [:] , 'Groovy - Copy data to closed Months' : [:], 'Groovy - Recalculate Forecast': [:] ] as Map<String, Map<String, String> >
//execute business rules serially..
BusinessRule.BUSINESS_RULES.each { String curRule, Map<String, String> rtp ->
//execute business rule and fetch the rule stats
Map<String, String> curRuleExecStatus = executeBusinessRule(curRule, rtp)
//get status of each business rule
BusinessRule.BUSINESS_RULES_STATUS[curRule] = curRuleExecStatus
BusinessRule.CURRENT_BUSINESS_RULE = curRule
if(curRuleExecStatus.status != 'Completed'){
throwVetoException("$curRule is failed, please check the logs")
}
}
DEFINE ‘SEND EMAIL’ FUNCTION
GENERATE EMAIL BODY
We can define custom messages, and other information that we want to put in a div bucket. However, I’m converting the status map to an HTML table using a separate function.
String generateEmailBody() {
String subJobsStatus = BusinessRule.BUSINESS_RULES_STATUS.find { it.key == BusinessRule.CURRENT_BUSINESS_RULE }?.value.status == 'Completed' ? "The execution of sub business rules is ${BusinessRule.BUSINESS_RULES.keySet().last() == BusinessRule.CURRENT_BUSINESS_RULE ? 'Completed' : 'currently in Progress'}" : "Error Occured while executing ${BusinessRule.CURRENT_BUSINESS_RULE}"
String nextSteps = BusinessRule.BUSINESS_RULES_STATUS.find { it.key == BusinessRule.CURRENT_BUSINESS_RULE }?.value.status == 'Completed' ? BusinessRule.BUSINESS_RULES.keySet().last() == BusinessRule.CURRENT_BUSINESS_RULE ? "All Sub Business rules completed successfully" : "" : "System Administrators are currently looking into the problem"
println BusinessRule.BUSINESS_RULES_STATUS.find { it.key == BusinessRule.CURRENT_BUSINESS_RULE }?.value.status
String htmlBody = """
<html>
<head>
<style>
.outerDiv { border-style: none! important; background-color: #AAAAAA; text-align: center; overflow:auto; outline: none;}
.innerDiv {border: 3px outset #1E626B; background-color: #AAAAAA; text-align: center; overflow:auto; height: 40px;}
.header { border: 4px outset #1E626B; background-color: #1E626B; text-align: center; }
.tg { border-collapse:collapse; border-spacing:0; margin-left: auto; margin-right: auto; }
.tg td { border-color:black; border-style:solid; border-width:1px; overflow:hidden; padding:10px 5px; word-break:normal; }
.tg th { border-color:black; border-style:solid; border-width:1px; font-weight:normal; overflow:hidden; padding:10px 5px; word-break:normal;}
.tg .tg-l8kr { border-color:#013300; text-align:left; vertical-align:top;}
</style>
</head>
<body>
<div class = "outerDiv">
<div class ="innerDiv">
<h1 class ="header">EPBCS - Business Rules Execution Status</h1>
<h2>${subJobsStatus}</h2>
<h2>${nextSteps}</h2>
<h3> Executed by ${operation.user.fullName}</h2>
</div>
<h3> Summary of Sub-Rules </h3>
${convertStatusMapToHtmlTable()}
<p><br><br>This is a system generated message. Do not reply to this message.</p>
</div>
</body>
</html>
"""
return htmlBody
}
Now lets define the function which converts the status map to an HTML Table. I have not used html class, I am not sure it is supported in calculation manager.
String convertStatusMapToHtmlTable() {
String tableHeaders = """
<thead>
<tr>
<th class="tg-l8kr">#</th>
<th class="tg-l8kr">Rule Name</th>
<th class="tg-l8kr">Status</th>
<th class="tg-l8kr">Start Time</th>
<th class="tg-l8kr">End Time</th>
<th class="tg-l8kr">Total Time Elapsed</th>
</tr>
</thead>
"""
String tableBody = ""
BusinessRule.BUSINESS_RULES.keySet().eachWithIndex { curRule, idx ->
tableBody += "<tbody>\n<tr>\n"
tableBody += """<td class="tg-l8kr">${idx + 1}</td> \n"""
tableBody += """<td class="tg-l8kr">${curRule}</td> \n"""
if (BusinessRule.BUSINESS_RULES_STATUS.keySet().contains(curRule)) {
Map properties = BusinessRule.BUSINESS_RULES_STATUS[curRule] as Map<String, String>
properties.each { key, value ->
tableBody += """<td class="tg-l8kr">$value</td> \n"""
}
} else {
4.times {
if (it == 0) {
tableBody += """<td class="tg-l8kr">Not Started</td> \n"""
} else {
tableBody += """<td class="tg-l8kr"></td> \n"""
}
}
}
tableBody += "</tr></tbody>\n"
}
String table = """
<table class="tg">
${tableHeaders}
${tableBody}
</table>
"""
return table
}
DEFINE ‘SEND EMAIL’ FUNCTION
In the previous steps, we defined the generateEmailBody function. It’s time to specify the function for sending emails. It’s critical to replace the characters on the next line with those from the body string. Failure to do so will result in the email not being sent.
class EmailProperties {
//email properties
static String SEND_TO = '[email protected]'
static String SUBJECT = 'EPBCS Business rule Status'
//connection details
static String CONNECTION_NAME = 'restConnection'
static String REST_URL = '/interop/rest/v1/services/sendmail'
static String CONTENT_TYPE = 'application/x-www-form-urlencoded'
}
Integer sendEmail() {
//define body of the email
String body = generateEmailBody().replaceAll("[\n\r]", "")
String emailContents = "to=${EmailProperties.SEND_TO}&subject=${EmailProperties.SUBJECT}&body=${body}"
//Initiate email
Connection connection = operation.application.getConnection(EmailProperties.CONNECTION_NAME)
HttpResponse<String> jsonResponse = connection.post(EmailProperties.REST_URL).header("Content-type", EmailProperties.CONTENT_TYPE).body(emailContents).asString()
//capture error and throw error
if (!(200..299).contains(jsonResponse.status)) {
throwVetoException("Error occured: $jsonResponse.statusText")
}
//check until the email is triggerred
final int IN_PROGRESS = -1
ReadContext ctx = JsonPath.parse(jsonResponse.body)
String jobId = ctx.read('$.links[1].href').toString().split("/").last() as String
int status = ctx.read('$.status')
for (long delay = 50; status == IN_PROGRESS; delay = Math.min(1000, delay * 2)) {
sleep(delay)
HttpResponse<String> pingResponse = connection.get(EmailProperties.REST_URL.replaceAll('sendmail', '/jobs/') + jobId).asString()
status = JsonPath.parse(pingResponse.body).read('$.status')
}
return status.toInteger()
}
ADD SEND EMAIL FUNCTION IN THE MAIN CODE
I am adding the condition to shoot email only if the system encounters any errors or the last rule is completed without any errors.
//execute business rules serially..
BusinessRule.BUSINESS_RULES.each { String curRule, Map<String, String> rtp ->
//execute business rule and fetch the rule stats
Map<String, String> curRuleExecStatus = executeBusinessRule(curRule, rtp)
//get status of each business rule
BusinessRule.BUSINESS_RULES_STATUS[curRule] = curRuleExecStatus
BusinessRule.CURRENT_BUSINESS_RULE = curRule
//sent email if errored out or last rule
if (curRule == BusinessRule.BUSINESS_RULES.keySet().last() || curRuleExecStatus.status != 'Completed') {
Integer emailStatus = sendEmail()
println "Email status ${emailStatus == 0 ? "-success" : "-failed"}"
}
if (curRuleExecStatus.status != 'Completed') {
throwVetoException("$curRule is failed, please check the logs")
}
}
LET’S TAKE A LOOK AT THE OUTCOMES.
CASE 1: IF ONE OF THE BUSINESS RULE FAILED
CASE 2 : IF ALL THE RULES ARE EXECUTED SUCCESSFULLY
It is possible to use multiple ways to code and achieve the same using groovy. I always believe summary of set of rules in a table format will be more useful. Hope this is helpful.
Comments
2 responses to “EPM GROOVY – Run Multiple Calculation Manager Rules and Send HTML Email Summaries”
Hi, very helpfull. Thanks.
I have a question: Do you know if it is possible to run the BR in sequence and start the BR only if the previous is completed?
Yes, Currently the job factory execute the rules sequentially.