aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java84
1 files changed, 67 insertions, 17 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java
index afb260bf765..c1e1f075552 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java
@@ -16,8 +16,17 @@ import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
-
+import com.yahoo.yolean.Exceptions;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.runtime.resource.util.StringResourceRepository;
+import org.apache.velocity.tools.generic.EscapeTool;
+
+import java.io.StringWriter;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -26,8 +35,6 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import static com.yahoo.yolean.Exceptions.uncheck;
-
/**
* Notifier is responsible for dispatching user notifications to their chosen Contact points.
*
@@ -39,6 +46,7 @@ public class Notifier {
private final FlagSource flagSource;
private final NotificationFormatter formatter;
private final URI dashboardUri;
+ private final VelocityEngine velocity;
private static final Logger log = Logger.getLogger(Notifier.class.getName());
@@ -51,6 +59,28 @@ public class Notifier {
this.flagSource = Objects.requireNonNull(flagSource);
this.formatter = new NotificationFormatter(zoneRegistry);
this.dashboardUri = zoneRegistry.dashboardUrl();
+ this.velocity = createTemplateEngine();
+ }
+
+ private static VelocityEngine createTemplateEngine() {
+ var v = new VelocityEngine();
+ v.setProperty(Velocity.RESOURCE_LOADERS, "string");
+ v.setProperty(Velocity.RESOURCE_LOADER + ".string.class", StringResourceLoader.class.getName());
+ v.setProperty(Velocity.RESOURCE_LOADER + ".string.repository.static", "false");
+ v.init();
+ var repo = (StringResourceRepository) v.getApplicationAttribute(StringResourceLoader.REPOSITORY_NAME_DEFAULT);
+ registerTemplate(repo, "mail");
+ registerTemplate(repo, "default-mail-content");
+ registerTemplate(repo, "notification-message");
+ return v;
+ }
+
+ private static void registerTemplate(StringResourceRepository repo, String name) {
+ var templateStr = Exceptions.uncheck(() -> {
+ var in = Notifier.class.getResourceAsStream("/mail/%s.vm".formatted(name));
+ return new String(in.readAllBytes());
+ });
+ repo.putStringResource(name, templateStr);
}
public void dispatch(List<Notification> notifications, NotificationSource source) {
@@ -114,23 +144,43 @@ public class Notifier {
public Mail mailOf(FormattedNotification content, Collection<String> recipients) {
var notification = content.notification();
- var subject = Text.format("[%s] %s Vespa Notification for %s", notification.level().toString().toUpperCase(), content.prettyType(), applicationIdSource(notification.source()));
- var template = uncheck(() -> Notifier.class.getResourceAsStream("/mail/mail-notification.tmpl").readAllBytes());
- var html = new String(template)
- .replace("[[NOTIFICATION_HEADER]]", content.messagePrefix())
- .replace("[[NOTIFICATION_ITEMS]]", notification.messages().stream()
- .map(Notifier::linkify)
- .map(Notifier::capitalise)
- .map(m -> "<p>" + m + "</p>")
- .collect(Collectors.joining()))
- .replace("[[LINK_TO_NOTIFICATION]]", notificationLink(notification.source()))
- .replace("[[LINK_TO_ACCOUNT_NOTIFICATIONS]]", accountNotificationsUri(content.notification().source().tenant()))
- .replace("[[LINK_TO_PRIVACY_POLICY]]", "https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html")
- .replace("[[LINK_TO_TERMS_OF_SERVICE]]", consoleUri("terms-of-service-trial.html"))
- .replace("[[LINK_TO_SUPPORT]]", consoleUri("support"));
+ var subject = content.notification().mailContent().flatMap(Notification.MailContent::subject)
+ .orElseGet(() -> Text.format(
+ "[%s] %s Vespa Notification for %s", notification.level().toString().toUpperCase(),
+ content.prettyType(), applicationIdSource(notification.source())));
+ var html = generateHtml(content);
return new Mail(recipients, subject, "", html);
}
+ private String generateHtml(FormattedNotification content) {
+ var esc = new EscapeTool();
+ var mailContent = content.notification().mailContent().orElseGet(() -> generateContentFromMessages(content, esc));
+ var ctx = new VelocityContext();
+ ctx.put("esc", esc);
+ ctx.put("accountNotificationLink", accountNotificationsUri(content.notification().source().tenant()));
+ ctx.put("privacyPolicyLink", "https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html");
+ ctx.put("termsOfServiceLink", consoleUri("terms-of-service-trial.html"));
+ ctx.put("supportLink", consoleUri("support"));
+ ctx.put("mailBodyTemplate", mailContent.template());
+ mailContent.values().forEach(ctx::put);
+
+ var writer = new StringWriter();
+ // Ignoring return value - implementation either returns 'true' or throws, never 'false'
+ velocity.mergeTemplate("mail", StandardCharsets.UTF_8.name(), ctx, writer);
+ return writer.toString();
+ }
+
+ private Notification.MailContent generateContentFromMessages(FormattedNotification f, EscapeTool esc) {
+ var items = f.notification().messages().stream().map(m -> capitalise(linkify(esc.html(m)))).toList();
+ return Notification.MailContent.fromTemplate("default-mail-content")
+ .with("mailMessageTemplate", "notification-message")
+ .with("mailTitle", "Vespa Cloud Notifications")
+ .with("notificationHeader", f.messagePrefix())
+ .with("notificationItems", items)
+ .with("consoleLink", notificationLink(f.notification().source()))
+ .build();
+ }
+
@VisibleForTesting
static String linkify(String text) {
return urlPattern.matcher(text).replaceAll((res) -> String.format("<a href=\"%s\">%s</a>", res.group(), res.group()));