Skip to main content

Notification Service

Notification Service

Overview

NotificationService interface provides access to Notification actions such as sending messages, attachments and templates via channel or just template evaluation.

Interface is located in eu.ibagroup.easyrpa.engine.service package.

Channel

Channels are used to determine the recipients of the notification and delivery type.

There are two channel types are supported by current version - Email and Slack. Their config is stored in CS configuration section under notification.channels.config key. 

{
	"email": {
		"default-encoding": "utf-8",
		"host": "mailin.iba",
		"port": "25",
		"protocol": "smtp",
		"mail.smtp.auth": "false",
		"mail.smtp.starttls.enable": "true",
		"mail.smtp.ssl.trust": "mailin.iba",
		"from": "easyrpatest@ibagroup.eu",
		"subject": "EasyRPA Notification", // optional
		"replyTo": "easyrpatest@ibagroup.eu", // optional
		"cc": "easyrpaadmin@ibagroup.eu", // optional
		"bcc": "easyrpadevops@ibagroup.eu", // optional
		"outputContentType": "text" // optional
	},
	"slack": {
		"token": "token-example-2000026465459-2Ksi6raocFxRtqJd6c5H7AF1"
	}
}

Email channel configuration

All config parameters are shown above. For example Gmail configuration is described here

The "outputContentType" parameter allows to select the message output format between text and html. By default, the type is determined automatically.

Setup slack channel configuration

  1. Create Slack App and add necessary Bot OAuth Scopes such as chat:write. Slack app configuration link - https://api.slack.com/apps
  2. Go to CS Configurationnotification.channels.config and add new config in format - "<config_name>" : {"token" : "<Bot User OAuth Token>"}
  3. Create Slack channel and add your App in it. Copy Channel ID as it will be used further (ID starts from xoxb-...).
  4. Create notification. Use your config_name as Config Name and Channel ID as recipient.
  5. Invite Application Bot into channel you are going to use as recipient. In slack channel send the following

    /invite @AppName

Template

Templates could be used if it is needed to insert some data into message dynamically. Control server support two template engines:

  • Thymeleaf
    Currently implemented ThymeleafHtml and ThymeleafText handlers. The key difference between textual template mode and the markup one is that in a textual template there are no tags into which to insert logic in the form of attributes, so we have to rely on other mechanisms. Read more about template modes here. Also useful documentation about Thymeleaf dialect is here.

  • FreeMarker
    Apache FreeMarker is a template engine: a Java library to generate text output (HTML web pages, e-mails, configuration files, source code, etc.) based on templates and changing data. Templates are written in the FreeMarker Template Language (FTL), which is a simple, specialized language
    Getting started manual is here.

All parameters are passed to the template engine as a Map<String, ?> params. 

There is an example of FreeMarker template for Debtors sample.

debtors_template.txt
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<style>
	body {
		background: #fafafa url(https://jackrugile.com/images/misc/noise-diagonal.png);
		color: #444;
		font: 100%/30px 'Helvetica Neue', helvetica, arial, sans-serif;
		text-shadow: 0 1px 0 #fff;
	}

	strong {
		font-weight: bold;
	}

	em {
		font-style: italic;
	}

	table {
		background: #f5f5f5;
		border-collapse: separate;
		box-shadow: inset 0 1px 0 #fff;
		font-size: 14px;
		line-height: 24px;
		margin: 30px auto;
		text-align: center;
		width: 800px;
	}

	caption {
		font-size: 18px;
	font-weight: bold;
	}

	th {
		background: url(https://jackrugile.com/images/misc/noise-diagonal.png), linear-gradient(#777, #444);
		border-left: 1px solid #555;
		border-right: 1px solid #777;
		border-top: 1px solid #555;
		border-bottom: 1px solid #333;
		box-shadow: inset 0 1px 0 #999;
		color: #fff;
		font-weight: bold;
		padding: 10px 15px;
		position: relative;
		text-shadow: 0 1px 0 #000;
	}

	th:after {
		background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, .08));
		content: '';
		display: block;
		height: 25%;
		left: 0;
		margin: 1px 0 0 0;
		position: absolute;
		top: 25%;
		width: 100%;
	}

	th:first-child {
		border-left: 1px solid #777;
		box-shadow: inset 1px 1px 0 #999;
	}

	th:last-child {
		box-shadow: inset -1px 1px 0 #999;
	}

	td {
		border-right: 1px solid #fff;
		border-left: 1px solid #e8e8e8;
		border-top: 1px solid #fff;
		border-bottom: 1px solid #e8e8e8;
		padding: 10px 15px;
		position: relative;
		transition: all 300ms;
	}

	td:first-child {
		box-shadow: inset 1px 0 0 #fff;
	}

	td:last-child {
		border-right: 1px solid #e8e8e8;
		box-shadow: inset -1px 0 0 #fff;
	}

	tr {
		background: url(https://jackrugile.com/images/misc/noise-diagonal.png);
	}

	tr:nth-child(odd) td {
		background: #f1f1f1 url(https://jackrugile.com/images/misc/noise-diagonal.png);
	}

	tr:last-of-type td {
		box-shadow: inset 0 -1px 0 #fff;
	}

	tr:last-of-type td:first-child {
		box-shadow: inset 1px -1px 0 #fff;
	}

	tr:last-of-type td:last-child {
		box-shadow: inset -1px -1px 0 #fff;
	}

	tbody:hover td {
		color: transparent;
		text-shadow: 0 0 3px #aaa;
	}

	tbody:hover tr:hover td {
		color: #444;
		text-shadow: 0 1px 0 #fff;
	}

	</style>
</head>
<body>

<div class="container">
	<table class="responsive-table">
		<caption>Отчет по списку должников</caption>
		<thead>
		<tr>
			<th scope="col">УНП</th>
			<th scope="col">Покупатель</th>
			<th scope="col">ГТК</th>
			<th scope="col">НБРБ</th>
			<th scope="col">МНС</th>
			<th scope="col">ФСЗН</th>
		</tr>
		</thead>
		<tfoot>
		<tr>
			<td colspan="7" align="left">
				Sources:
				<#list links?keys as key>
					<a href="${links[key]}" rel="external">${key}</a>.
				</#list>
				Data is current as of ${date}.
			</td>
		</tr>
		</tfoot>
		<tbody>
		<#list debtors as debtor>
			<#if debtor??>
				<tr>
					<td data-title="УНП">${debtor.companyID}</td>
					<td data-title="Покупатель">${debtor.companyName}</td>
					<#if debtor.customsFound??>
						<td data-title="ГТК" data-type="currency">${(debtor.customsFound=="true") ? then("✓", "")}</td>
					</#if>
					<#if debtor.nationalBankFound??>
						<td data-title="НБРБ" data-type="currency">${(debtor.nationalBankFound=="true") ? then("✓", "")}</td>
					</#if>
					<#if debtor.taxesFound??>
						<td data-title="МНС" data-type="currency">${(debtor.taxesFound=="true") ? then("✓", "")}</td>
					</#if>	
					<#if debtor.socialfundFound??>
						<td data-title="ФСЗН" data-type="currency">${(debtor.socialfundFound=="true") ? then("✓", "")}</td>
					</#if>
				</tr>
			</#if>
		</#list>
		</tbody>
	</table>
</div>
</body>
</html>

Usage

  1. Create Channel in Notification Management section. Config Name should be one of notification.channels.config keys.


  2. Create Template in Notification Management section. It could be validated by one of the entities - AutomationProcess, DocumentType, Node, Schedule.


  3. Inject NotificationService in ApTask as well as notification channel and template names.

    ApTask configuration
    @Inject
    private NotificationService notificationService;
    
    @Configuration(value = "notificationChannel", defaultValue = "Default Notification Channel")
    private String notificationChannel;
    
    @Configuration(value = "notificationTemplate", defaultValue = "Default Email Template")
    private String notificationTemplate;
  4. Use one of the provided interface methods:

    NotificationService.java
    /**
    * Evaluates template with provided parameters.
    * @param templateName template name
    * @param params params for template
    * @return the array of byte for processed template
    */
    byte[] evaluateTemplate(String templateName, Map<String, ?> params);
    
    /**
    * Evaluates template with provided parameters and sends result into specified channel.
    * @param templateName template name
    * @param params params for template
    * @param channelName name of channel for sending
    * @param attachments list of attachments for channel if needed
    */
    void evaluateTemplateAndSend(String templateName, Map<String, ?> params, String channelName, List<File> attachments);
    
    /**
    * Sends notification into specified channel.
    * @param channelName name of channel for sending
    * @param text body text to send
    * @param attachments list of attachments for channel if needed
    */
    void sendNotification(String channelName, String text, List<File> attachments);

Examples

  • Evaluating and sending template in the Debtors sample.

    GenerateEmailReport.java
    package eu.ibagroup.easyrpa.debtors.task;
    
    import eu.ibagroup.easyrpa.debtors.domain.Debtor;
    import eu.ibagroup.easyrpa.debtors.repository.DebtorRepository;
    import eu.ibagroup.easyrpa.debtors.to.DebtorsResultTo;
    import eu.ibagroup.easyrpa.engine.annotation.ApTaskEntry;
    import eu.ibagroup.easyrpa.engine.annotation.Configuration;
    import eu.ibagroup.easyrpa.engine.annotation.Input;
    import eu.ibagroup.easyrpa.engine.annotation.Output;
    import eu.ibagroup.easyrpa.engine.apflow.ApTask;
    import eu.ibagroup.easyrpa.engine.service.ConfigurationService;
    import eu.ibagroup.easyrpa.engine.service.NotificationService;
    
    import javax.inject.Inject;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @ApTaskEntry(name = "Generate Email Report")
    public class GenerateEmailReport extends ApTask {
    
    	 @Input(ExtractDebtors.DEBTOR_KEY)
    	 @Output(ExtractDebtors.DEBTOR_KEY)
    	 private DebtorsResultTo debtorsResult;
    
    	 @Configuration(value = "notificationChannel", defaultValue = "Debtor Notification Channel")
    	 private String notificationChannel;
    
    	 @Configuration(value = "notificationTemplate", defaultValue = "Debtor Email Template")
    	 private String notificationTemplate;
    
    	 @Inject
    	 private DebtorRepository repo;
    
    	 @Inject
    	 private NotificationService notificationService;
    
    	 @Override
    	 public void execute() {
    		try {
    		 List<Debtor> debtors = repo.fetchDebtors(debtorsResult.getDebtorIds());
    
    		 Map data = new HashMap<>();
    		 data.put("debtors", debtors);
    		 data.put("date", new SimpleDateFormat("MMMM dd, yyyy").format(new Date()));
    		 data.put("links", getLinks());
    
    		 notificationService.evaluateTemplateAndSend(notificationTemplate, data, notificationChannel, new ArrayList<>());
    		 debtorsResult.setGenerateEmailReportComplete(true);
    		} catch (Exception e) {
    		 handleStepError(e);
    		}
    	 }
    
    	 @Inject
    	 private ConfigurationService cfg;
    
    	 private Map getLinks() {
    		HashMap<String, String> links = new HashMap<>();
    		links.put("ГТК", cfg.get("gtk.client.url", "http://www.gtk.gov.by/ru/dolg-test-ru/"));
    		links.put("НБРБ", cfg.get("nbrb.client.url", "http://www.nbrb.by/system/banks/guaranteesregister/"));
    		links.put("Суд", cfg.get("court.client.url", "http://service.court.by/ru/juridical/judgmentresults/"));
    		links.put("МНС", cfg.get("nalog.client.url", "http://www.portal.nalog.gov.by/debtor/"));
    		links.put("ФСЗН", cfg.get("ssf.client.url", "https://ssf.gov.by/ru/debtors-ru/"));
    		return links;
    	 }
    }
  • Sending notification with attachment in the SAP sample.

    GenerateAndSendReportTask.java
    package eu.ibagroup.easyrpa.ap.creatematerial.task;
    
    import eu.ibagroup.easyrpa.ap.creatematerial.ApConstants;
    import eu.ibagroup.easyrpa.engine.annotation.ApTaskEntry;
    import eu.ibagroup.easyrpa.engine.annotation.Configuration;
    import eu.ibagroup.easyrpa.engine.annotation.Input;
    import eu.ibagroup.easyrpa.engine.apflow.ApTask;
    import eu.ibagroup.easyrpa.engine.service.NotificationService;
    import eu.ibagroup.easyrpa.excel.SpreadsheetDocument;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Sheet;
    
    import javax.inject.Inject;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.file.Files;
    import java.nio.file.StandardCopyOption;
    import java.util.*;
    
    import static eu.ibagroup.easyrpa.ap.creatematerial.ApConstants.TEMPLATE_NAME;
    
    @ApTaskEntry(name = "Generate and send report")
    @Slf4j
    public class GenerateAndSendReportTask extends ApTask {
    
    	@Input
    	private String materialCode;
    
    	@Inject
    	private NotificationService notificationService;
    
    	@Configuration(value = "notificationTemplate", defaultValue = "SAP Email Template")
    	private String notificationTemplate;
    
    	@Configuration(value = "notificationChannel", defaultValue = "SAP Notification Channel")
    	private String notificationChannel;
    
    	@Override
    	public void execute() {
    		try {
    			File attachment = getAttachment();
    			notificationService.evaluateTemplateAndSend(notificationTemplate, new HashMap<>(),
    														notificationChannel, Collections.singletonList(attachment));
    			attachment.delete();
    		} catch (Exception e) {
    			handleStepError(e);
    		}
    	}
    
    	private File getAttachment() throws IOException {
    		SpreadsheetDocument doc = createSpreadsheet();
    		InputStream initialStream = doc.getInputStream();
    		File resultFile = new File("execution_results.xlsx");
    		Files.copy(
    			initialStream,
    			resultFile.toPath(),
    			StandardCopyOption.REPLACE_EXISTING);
    		return resultFile;
    	}
    
    	private SpreadsheetDocument createSpreadsheet() {
    		SpreadsheetDocument doc = new SpreadsheetDocument(getClass().getResourceAsStream(TEMPLATE_NAME));
    		Sheet sheet = doc.getActiveSheet();
    		Row row = sheet.createRow(1);
    		row.createCell(0).setCellValue(this.materialCode);
    		row.createCell(1).setCellValue(ApConstants.EASYRPA_BASE_UNIT);
    		row.createCell(2).setCellValue(String.format(ApConstants.EASYRPA_DESCR_STRING_TEMPL, this.materialCode));
    		return doc;
    	}
    }