Commit 1283b1c1 by kevin peters Committed by Johannes Edmeier

Generate mails using thymeleaf templates

parent 9ed8ec4e
......@@ -131,6 +131,24 @@ spring.boot.admin.notify.mail.to=admin@example.com
| `+++"#{application.name} (#{application.id})\nstatus changed from #{from.status} to #{to.status}\n\n#{application.healthUrl}"+++`
|===
Mail notifications will be delivered as HTML emails composed of multiple https://www.thymeleaf.org/[Thymeleaf] templates.
The default body looks like this:
....
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<span>some-service-id</span> (<span>a16d130feb17</span>)
status changed from <span>UP</span> to <span>DOWN</span>
<br />
<span>http://some.domain:8081/actuator/health</span>
</body>
</html>
....
[[pagerduty-notifications]]
==== PagerDuty notifications ====
......
......@@ -32,7 +32,11 @@ import de.codecentric.boot.admin.server.notify.TelegramNotifier;
import de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;
import de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.CharEncoding;
import org.reactivestreams.Publisher;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
......@@ -48,6 +52,10 @@ import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.mail.MailSender;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
@Configuration
@AutoConfigureAfter({MailSenderAutoConfiguration.class})
......@@ -109,7 +117,26 @@ public class AdminServerNotifierAutoConfiguration {
@ConditionalOnMissingBean
@ConfigurationProperties("spring.boot.admin.notify.mail")
public MailNotifier mailNotifier(MailSender mailSender, InstanceRepository repository) {
return new MailNotifier(mailSender, repository);
return new MailNotifier(mailSender, repository, mailNotifierTemplateEngine());
}
@Bean
public TemplateEngine mailNotifierTemplateEngine() {
final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setOrder(Integer.valueOf(1));
templateResolver.setPrefix("/templates/");
final Set<String> notificationTemplateNames = new HashSet<>();
notificationTemplateNames.add("notification-template-subject.*");
notificationTemplateNames.add("notification-template-body.*");
templateResolver.setResolvablePatterns(notificationTemplateNames);
templateResolver.setTemplateMode(TemplateMode.TEXT);
templateResolver.setCharacterEncoding(CharEncoding.UTF_8);
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(templateResolver);
return templateEngine;
}
}
......
......@@ -19,18 +19,13 @@ package de.codecentric.boot.admin.server.notify;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
/**
* Notifier sending emails.
......@@ -38,10 +33,6 @@ import org.springframework.mail.SimpleMailMessage;
* @author Johannes Edmeier
*/
public class MailNotifier extends AbstractStatusChangeNotifier {
private static final String DEFAULT_SUBJECT = "#{instance.registration.name} (#{instance.id}) is #{event.statusInfo.status}";
private static final String DEFAULT_TEXT = "#{instance.registration.name} (#{instance.id})\nstatus changed from #{lastStatus} to #{event.statusInfo.status}\n\n#{instance.registration.healthUrl}";
private final SpelExpressionParser parser = new SpelExpressionParser();
private final MailSender sender;
/**
......@@ -60,36 +51,33 @@ public class MailNotifier extends AbstractStatusChangeNotifier {
private String from = null;
/**
* Mail Text. SpEL template using event as root;
* Mail Subject. SpEL template using event as root;
*/
private Expression text;
private String subject;
/**
* Mail Subject. SpEL template using event as root;
* Mail Text. Is prepared via thymeleaf template;
*/
private Expression subject;
private TemplateEngine templateEngine;
public MailNotifier(MailSender sender, InstanceRepository repository) {
public MailNotifier(MailSender sender, InstanceRepository repository, TemplateEngine templateEngine) {
super(repository);
this.sender = sender;
this.subject = parser.parseExpression(DEFAULT_SUBJECT, ParserContext.TEMPLATE_EXPRESSION);
this.text = parser.parseExpression(DEFAULT_TEXT, ParserContext.TEMPLATE_EXPRESSION);
this.templateEngine = templateEngine;
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
Map<String, Object> root = new HashMap<>();
root.put("event", event);
root.put("instance", instance);
root.put("lastStatus", getLastStatus(event.getInstance()));
StandardEvaluationContext context = new StandardEvaluationContext(root);
context.addPropertyAccessor(new MapAccessor());
SimpleMailMessage message = new SimpleMailMessage();
final Context ctx = new Context();
ctx.setVariable("event", event);
ctx.setVariable("instance", instance);
ctx.setVariable("lastStatus", getLastStatus(event.getInstance()));
final SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setFrom(from);
message.setSubject(subject.getValue(context, String.class));
message.setText(text.getValue(context, String.class));
message.setSubject(templateEngine.process("notification-template-subject.txt", ctx));
message.setText(templateEngine.process("notification-template-body.html", ctx));
message.setCc(cc);
return Mono.fromRunnable(() -> sender.send(message));
......@@ -120,19 +108,11 @@ public class MailNotifier extends AbstractStatusChangeNotifier {
}
public void setSubject(String subject) {
this.subject = parser.parseExpression(subject, ParserContext.TEMPLATE_EXPRESSION);
this.subject = subject;
}
public String getSubject() {
return subject.getExpressionString();
}
public void setText(String text) {
this.text = parser.parseExpression(text, ParserContext.TEMPLATE_EXPRESSION);
}
public String getText() {
return text.getExpressionString();
return subject;
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<span th:text="${instance.registration.name}"/> (<span th:text="${instance.id}"/>)
status changed from <span th:text="${lastStatus}"/> to <span th:text="${event.statusInfo.status}"/>
<br />
<span th:text="${instance.registration.healthUrl}"/>
</body>
</html>
[(${instance.registration.name})] ([(${instance.id})]) is [(${event.statusInfo.status})]
......@@ -16,6 +16,7 @@
package de.codecentric.boot.admin.server.notify;
import de.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
......@@ -23,6 +24,7 @@ import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent
import de.codecentric.boot.admin.server.domain.values.InstanceId;
import de.codecentric.boot.admin.server.domain.values.Registration;
import de.codecentric.boot.admin.server.domain.values.StatusInfo;
import org.thymeleaf.TemplateEngine;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
......@@ -39,19 +41,34 @@ import static org.mockito.Mockito.when;
public class MailNotifierTest {
private final Instance instance = Instance.create(InstanceId.of("-id-"))
.register(Registration.create("App", "http://health").build());
.register(Registration.create("App", "http://health").build());
private MailSender sender;
private MailNotifier notifier;
private InstanceRepository repository;
private static final String EXPECTED_SUBJECT = "App (-id-) is DOWN\n";
private static final String EXPECTED_BODY = "<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" +
"</head>\n" +
"<body>\n" +
"<span>App</span> (<span>-id-</span>)\n" +
"status changed from <span>UNKNOWN</span> to <span>DOWN</span>\n" +
"<br />\n" +
"<span>http://health</span>\n" +
"</body>\n" +
"</html>\n";
@Before
public void setup() {
final TemplateEngine templateEngine = new AdminServerNotifierAutoConfiguration.MailNotifierConfiguration().mailNotifierTemplateEngine();
repository = mock(InstanceRepository.class);
when(repository.find(instance.getId())).thenReturn(Mono.just(instance));
sender = mock(MailSender.class);
notifier = new MailNotifier(sender, repository);
notifier = new MailNotifier(sender, repository, templateEngine);
notifier.setTo(new String[]{"foo@bar.com"});
notifier.setCc(new String[]{"bar@foo.com"});
notifier.setFrom("SBA <no-reply@example.com>");
......@@ -59,20 +76,18 @@ public class MailNotifierTest {
}
@Test
public void test_onApplicationEvent() {
public void test_onApplicationEvent_with_Thymeleaf_template() {
StepVerifier.create(notifier.notify(
new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofDown())))
.verifyComplete();
StepVerifier.create(
notifier.notify(new InstanceStatusChangedEvent(instance.getId(), instance.getVersion(), StatusInfo.ofUp())))
.verifyComplete();
.verifyComplete();
SimpleMailMessage expected = new SimpleMailMessage();
final SimpleMailMessage expected = new SimpleMailMessage();
expected.setTo(new String[]{"foo@bar.com"});
expected.setCc(new String[]{"bar@foo.com"});
expected.setFrom("SBA <no-reply@example.com>");
expected.setText("App (-id-)\nstatus changed from DOWN to UP\n\nhttp://health");
expected.setSubject("-id- is UP");
expected.setSubject(EXPECTED_SUBJECT);
expected.setText(EXPECTED_BODY);
verify(sender).send(eq(expected));
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment