diff options
author | Eirik Nygaard <eirik.nygaard@yahooinc.com> | 2022-11-24 13:01:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-24 13:01:43 +0100 |
commit | 2edd631a79e96791d9638f7b3a80b15c9f94d1f7 (patch) | |
tree | 32a42254d55bbf91fb0274b23dec084d2a489084 | |
parent | 10c2f711e234065c193aee3b413081ac7f86290e (diff) | |
parent | 5a6b8f759f5d41ee9c7801f57e205d1499df84fd (diff) |
Merge pull request #24971 from vespa-engine/ogronnesby/notification-format
Ogronnesby/notification format
10 files changed, 1844 insertions, 45 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mailer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mailer.java index 27eec34cf4e..187f140f646 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mailer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/Mailer.java @@ -19,6 +19,4 @@ public interface Mailer { /** Returns the domain this is configured to use. */ String domain(); - void sendVerificationMail(PendingMailVerification pendingMailVerification); - } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java index dcfa9b7ea07..c2f53e3457d 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/MockMailer.java @@ -48,11 +48,6 @@ public class MockMailer implements Mailer { return "domain"; } - @Override - public void sendVerificationMail(PendingMailVerification pendingMailVerification) { - send(new Mail(List.of(pendingMailVerification.getMailAddress()), "subject", "message")); - } - /** Returns the list of mails sent to the given recipient. Modifications affect the set of mails stored in this. */ public List<Mail> inbox(String recipient) { return mails.getOrDefault(recipient, List.of()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java index b40db745fd7..9a7fffd2a9e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java @@ -126,7 +126,7 @@ public class Controller extends AbstractComponent { notifier = new Notifier(curator, serviceRegistry.zoneRegistry(), serviceRegistry.mailer(), flagSource); notificationsDb = new NotificationsDb(this); supportAccessControl = new SupportAccessControl(this); - mailVerifier = new MailVerifier(tenantController, serviceRegistry.mailer(), curator, clock); + mailVerifier = new MailVerifier(serviceRegistry.zoneRegistry().dashboardUrl(), tenantController, serviceRegistry.mailer(), curator, clock); // Record the version of this controller curator().writeControllerVersion(this.hostname(), serviceRegistry.controllerVersion()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/MailVerifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/MailVerifier.java index a7f3a3ca3b2..902343d5acf 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/MailVerifier.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/MailVerifier.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller; import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Mail; import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; @@ -10,11 +11,15 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantContacts; import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification; +import java.net.URI; import java.time.Clock; import java.time.Duration; +import java.util.List; import java.util.Optional; import java.util.UUID; +import static com.yahoo.yolean.Exceptions.uncheck; + /** * @author olaa @@ -25,13 +30,16 @@ public class MailVerifier { private final Mailer mailer; private final CuratorDb curatorDb; private final Clock clock; + private final URI dashboardUri; private static final Duration VERIFICATION_DEADLINE = Duration.ofDays(7); - public MailVerifier(TenantController tenantController, Mailer mailer, CuratorDb curatorDb, Clock clock) { + + public MailVerifier(URI dashboardUri, TenantController tenantController, Mailer mailer, CuratorDb curatorDb, Clock clock) { this.tenantController = tenantController; this.mailer = mailer; this.curatorDb = curatorDb; this.clock = clock; + this.dashboardUri = dashboardUri; } public PendingMailVerification sendMailVerification(TenantName tenantName, String email, PendingMailVerification.MailType mailType) { @@ -43,7 +51,7 @@ public class MailVerifier { var verificationDeadline = clock.instant().plus(VERIFICATION_DEADLINE); var pendingMailVerification = new PendingMailVerification(tenantName, email, verificationCode, verificationDeadline, mailType); writePendingVerification(pendingMailVerification); - mailer.sendVerificationMail(pendingMailVerification); + mailer.send(mailOf(pendingMailVerification)); return pendingMailVerification; } @@ -113,4 +121,15 @@ public class MailVerifier { .map(CloudTenant.class::cast) .orElseThrow(() -> new IllegalStateException("Mail verification is only applicable for cloud tenants")); } + + private Mail mailOf(PendingMailVerification pendingMailVerification) { + var classLoader = this.getClass().getClassLoader(); + var template = uncheck(() -> classLoader.getResourceAsStream("mail/mail-verification.tmpl").readAllBytes()); + var message = new String(template) + .replaceAll("%\\{consoleUrl}", dashboardUri.getHost()) + .replaceAll("%\\{email}", pendingMailVerification.getMailAddress()) + .replaceAll("%\\{code}", pendingMailVerification.getVerificationCode()); + return new Mail(List.of(pendingMailVerification.getMailAddress()), "Please verify your email", "", message); + } + } 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 1074649ca7a..526bf0c53b3 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 @@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.controller.notification; import com.google.common.annotations.VisibleForTesting; import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.TenantName; +import com.yahoo.restapi.UriBuilder; import com.yahoo.text.Text; import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.FlagSource; @@ -15,6 +17,7 @@ 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 java.net.URI; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -23,6 +26,8 @@ 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. * @@ -43,6 +48,7 @@ public class Notifier { private final Mailer mailer; private final FlagSource flagSource; private final NotificationFormatter formatter; + private final URI dashboardUri; private static final Logger log = Logger.getLogger(Notifier.class.getName()); @@ -54,6 +60,7 @@ public class Notifier { this.mailer = Objects.requireNonNull(mailer); this.flagSource = Objects.requireNonNull(flagSource); this.formatter = new NotificationFormatter(zoneRegistry); + this.dashboardUri = zoneRegistry.dashboardUrl(); } public void dispatch(List<Notification> notifications, NotificationSource source) { @@ -124,24 +131,18 @@ 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())); - String body = new StringBuilder() - .append(content.messagePrefix()).append("\n") - .append(notification.messages().stream().map(m -> " * " + m).collect(Collectors.joining("\n"))).append("\n") - .append("\n") - .append("Vespa Console link:\n") - .append(content.uri().toString()).toString(); - String html = new StringBuilder() - .append(header) - .append(content.messagePrefix()).append("<br>\n") - .append("<ul>\n") - .append(notification.messages().stream() + 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(m -> "<li>" + m + "</li>") - .collect(Collectors.joining("<br>\n"))) - .append("</ul>\n") - .append("<br>\n") - .append("<a href=\"" + content.uri() + "\">Vespa Console</a>").toString(); - return new Mail(recipients, subject, body, html); + .collect(Collectors.joining())) + .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")); + return new Mail(recipients, subject, "", html); } @VisibleForTesting @@ -157,5 +158,16 @@ public class Notifier { return sb.toString(); } + private String accountNotificationsUri(TenantName tenant) { + return new UriBuilder(dashboardUri) + .append("tenant/") + .append(tenant.value()) + .append("account/notifications") + .toString(); + } + + private String consoleUri(String path) { + return new UriBuilder(dashboardUri).append(path).toString(); + } } diff --git a/controller-server/src/main/resources/mail/mail-notification.tmpl b/controller-server/src/main/resources/mail/mail-notification.tmpl new file mode 100644 index 00000000000..868a3a76bab --- /dev/null +++ b/controller-server/src/main/resources/mail/mail-notification.tmpl @@ -0,0 +1,647 @@ +<!DOCTYPE html> +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:o="urn:schemas-microsoft-com:office:office" +> + <head> + <title></title> + <!--[if !mso]><!--> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <!--<![endif]--> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <style type="text/css"> + #outlook a { + padding: 0; + } + + body { + margin: 0; + padding: 0; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + table, + td { + border-collapse: collapse; + mso-table-lspace: 0pt; + mso-table-rspace: 0pt; + } + + img { + border: 0; + height: auto; + line-height: 100%; + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; + } + + p { + display: block; + margin: 13px 0; + } + </style> + <!--[if mso]> + <noscript> + <xml> + <o:OfficeDocumentSettings> + <o:AllowPNG /> + <o:PixelsPerInch>96</o:PixelsPerInch> + </o:OfficeDocumentSettings> + </xml> + </noscript> + <![endif]--> + <!--[if lte mso 11]> + <style type="text/css"> + .mj-outlook-group-fix { + width: 100% !important; + } + </style> + <![endif]--> + <!--[if !mso]><!--> + <link + href="https://fonts.googleapis.com/css?family=Open Sans" + rel="stylesheet" + type="text/css" + /> + <style type="text/css"> + @import url(https://fonts.googleapis.com/css?family=Open Sans); + </style> + <!--<![endif]--> + <style type="text/css"> + @media only screen and (min-width: 480px) { + .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + } + </style> + <style media="screen and (min-width:480px)"> + .moz-text-html .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + </style> + <style type="text/css"> + [owa] .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + </style> + <style type="text/css"> + @media only screen and (max-width: 480px) { + table.mj-full-width-mobile { + width: 100% !important; + } + + td.mj-full-width-mobile { + width: auto !important; + } + } + </style> + </head> + + <body style="word-spacing: normal; background-color: #f2f7fa"> + <div style="background-color: #f2f7fa"> + <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div style="margin: 0px auto; max-width: 600px"> + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0px 20px 0px; + padding-bottom: 0px; + padding-top: 0px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 0px 0px 25px; + padding-top: 0px; + padding-bottom: 0px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 11px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <br /> + </div> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div + style=" + background: #ffffff; + background-color: #ffffff; + margin: 0px auto; + max-width: 600px; + " + > + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="background: #ffffff; background-color: #ffffff; width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0; + padding-bottom: 0px; + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="center" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 0px; + padding-right: 0px; + padding-bottom: 40px; + padding-left: 0px; + word-break: break-word; + " + > + <p + style=" + border-top: solid 8px #005a8e; + font-size: 1px; + margin: 0px auto; + width: 100%; + " + ></p> + <!--[if mso | IE + ]><table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + style=" + border-top: solid 8px #005a8e; + font-size: 1px; + margin: 0px auto; + width: 600px; + " + role="presentation" + width="600px" + > + <tr> + <td style="height: 0; line-height: 0"> + + </td> + </tr> + </table><! + [endif]--> + </td> + </tr> + <tr> + <td + align="center" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 0px; + padding-bottom: 0px; + word-break: break-word; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style=" + border-collapse: collapse; + border-spacing: 0px; + " + > + <tbody> + <tr> + <td style="width: 121px"> + <img + alt="" + height="auto" + src="https://data.vespa.oath.cloud/assets/vespa-cloud-logo.png" + style=" + border: none; + display: block; + outline: none; + text-decoration: none; + height: auto; + width: 100%; + font-size: 13px; + " + width="121" + /> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div + style=" + background: #ffffff; + background-color: #ffffff; + margin: 0px auto; + max-width: 600px; + " + > + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="background: #ffffff; background-color: #ffffff; width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0px 20px 0px; + padding-bottom: 70px; + padding-top: 30px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 25px 0px 25px; + padding-top: 0px; + padding-right: 50px; + padding-bottom: 0px; + padding-left: 50px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <h1 + style=" + text-align: center; + color: #000000; + line-height: 32px; + " + > + Vespa cloud notifications + </h1> + </div> + </td> + </tr> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 25px 0px 25px; + padding-top: 0px; + padding-right: 50px; + padding-bottom: 0px; + padding-left: 50px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <p> + [[NOTIFICATION_HEADER]] + </p> + <ul> + [[NOTIFICATION_ITEMS]] + </ul> + </div> + </td> + </tr> + <tr> + <td + align="center" + vertical-align="middle" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 20px; + padding-bottom: 20px; + word-break: break-word; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="border-collapse: separate; line-height: 100%" + > + <tbody> + <tr> + <td + align="center" + bgcolor="#005A8E" + role="presentation" + style=" + border: none; + border-radius: 100px; + cursor: auto; + mso-padding-alt: 15px 25px 15px 25px; + background: #005a8e; + " + valign="middle" + > + <a + href="#" + style=" + display: inline-block; + background: #005a8e; + color: #ffffff; + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 120%; + margin: 0; + text-decoration: none; + text-transform: none; + padding: 15px 25px 15px 25px; + mso-padding-alt: 0px; + border-radius: 100px; + " + target="_blank" + ><b style="font-weight: 700" + ><b style="font-weight: 700" + >Go to Console</b + ></b + ></a + > + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div style="margin: 0px auto; max-width: 600px"> + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0px 20px 0px; + padding-bottom: 0px; + padding-top: 20px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="center" + style=" + font-size: 0px; + padding: 0px 20px 0px 20px; + padding-top: 0px; + padding-bottom: 0px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 11px; + line-height: 22px; + text-align: center; + color: #797e82; + " + > + <p style="margin: 10px 0"> + <a + target="_blank" + rel="noopener noreferrer" + style="color: #005a8e" + href="[[LINK_TO_PRIVACY_POLICY]]" + ><span style="color: #005a8e" + >Yahoo Privacy Policy</span + ></a + ><span style="color: #797e82" + > | </span + ><a + target="_blank" + rel="noopener noreferrer" + style="color: #005a8e" + href="[[LINK_TO_TERMS_OF_SERVICE]]" + ><span style="color: #005a8e" + >Terms of Service</span + ></a + ><span style="color: #797e82" + > | </span + ><a + target="_blank" + rel="noopener noreferrer" + style="color: #005a8e" + href="[[LINK_TO_SUPPORT]]" + ><span style="color: #005a8e">Support</span></a + > + </p> + <p style="margin: 10px 0"> + <a + target="_blank" + rel="noopener noreferrer" + style="color: inherit; text-decoration: none" + href="[[LINK_TO_ACCOUNT_NOTIFICATIONS]]" + >Click + <span style="color: #005a8e"><u>here</u></span> + to manage your notifications setting.</a + ><br /> + </p> + </div> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </div> + </body> +</html> diff --git a/controller-server/src/main/resources/mail/mail-verification.tmpl b/controller-server/src/main/resources/mail/mail-verification.tmpl new file mode 100644 index 00000000000..8a473e74755 --- /dev/null +++ b/controller-server/src/main/resources/mail/mail-verification.tmpl @@ -0,0 +1,494 @@ +<!DOCTYPE html> +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:o="urn:schemas-microsoft-com:office:office" +> + <head> + <title></title> + <!--[if !mso]><!--> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <!--<![endif]--> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <style type="text/css"> + #outlook a { + padding: 0; + } + + body { + margin: 0; + padding: 0; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + table, + td { + border-collapse: collapse; + mso-table-lspace: 0pt; + mso-table-rspace: 0pt; + } + + img { + border: 0; + height: auto; + line-height: 100%; + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; + } + + p { + display: block; + margin: 13px 0; + } + </style> + <!--[if mso]> + <noscript> + <xml> + <o:OfficeDocumentSettings> + <o:AllowPNG /> + <o:PixelsPerInch>96</o:PixelsPerInch> + </o:OfficeDocumentSettings> + </xml> + </noscript> + <![endif]--> + <!--[if lte mso 11]> + <style type="text/css"> + .mj-outlook-group-fix { + width: 100% !important; + } + </style> + <![endif]--> + <!--[if !mso]><!--> + <link + href="https://fonts.googleapis.com/css?family=Open Sans" + rel="stylesheet" + type="text/css" + /> + <style type="text/css"> + @import url(https://fonts.googleapis.com/css?family=Open Sans); + </style> + <!--<![endif]--> + <style type="text/css"> + @media only screen and (min-width: 480px) { + .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + } + </style> + <style media="screen and (min-width:480px)"> + .moz-text-html .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + </style> + <style type="text/css"> + [owa] .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + </style> + <style type="text/css"> + @media only screen and (max-width: 480px) { + table.mj-full-width-mobile { + width: 100% !important; + } + + td.mj-full-width-mobile { + width: auto !important; + } + } + </style> + </head> + + <body style="word-spacing: normal; background-color: #f8f8f8"> + <div style="background-color: #f8f8f8"> + <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div + style=" + background: #ffffff; + background-color: #ffffff; + margin: 0px auto; + max-width: 600px; + " + > + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="background: #ffffff; background-color: #ffffff; width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0; + padding-bottom: 0px; + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="center" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 0px; + padding-right: 0px; + padding-bottom: 40px; + padding-left: 0px; + word-break: break-word; + " + > + <p + style=" + border-top: solid 7px #3b9fde; + font-size: 1px; + margin: 0px auto; + width: 100%; + " + ></p> + <!--[if mso | IE + ]><table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + style=" + border-top: solid 7px #3b9fde; + font-size: 1px; + margin: 0px auto; + width: 600px; + " + role="presentation" + width="600px" + > + <tr> + <td style="height: 0; line-height: 0"> + + </td> + </tr> + </table><! + [endif]--> + </td> + </tr> + <tr> + <td + align="center" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 0px; + padding-bottom: 0px; + word-break: break-word; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style=" + border-collapse: collapse; + border-spacing: 0px; + " + > + <tbody> + <tr> + <td style="width: 110px"> + <img + alt="" + height="auto" + src="https://data.vespa.oath.cloud/assets/vespa-icon.png" + style=" + border: none; + display: block; + outline: none; + text-decoration: none; + height: auto; + width: 100%; + font-size: 13px; + " + width="110" + /> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div + style=" + background: #ffffff; + background-color: #ffffff; + margin: 0px auto; + max-width: 600px; + " + > + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="background: #ffffff; background-color: #ffffff; width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0px 20px 0px; + padding-bottom: 70px; + padding-top: 30px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 25px 0px 25px; + padding-top: 0px; + padding-right: 50px; + padding-bottom: 0px; + padding-left: 50px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <h1 + style=" + text-align: center; + color: #000000; + line-height: 32px; + " + > + Verify your email address + </h1> + </div> + </td> + </tr> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 25px 0px 25px; + padding-top: 0px; + padding-right: 50px; + padding-bottom: 0px; + padding-left: 50px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <p style="margin: 10px 0; text-align: center"> + You have entered the email address <b>%{email}</b> in + Vespa Cloud. + </p> + <p style="margin: 10px 0; text-align: center"> + Please verify this email address by clicking the + button below. + </p> + </div> + </td> + </tr> + <tr> + <td + align="center" + vertical-align="middle" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 20px; + padding-bottom: 20px; + word-break: break-word; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="border-collapse: separate; line-height: 100%" + > + <tbody> + <tr> + <td + align="center" + bgcolor="#3B9FDE" + role="presentation" + style=" + border: none; + border-radius: 100px; + cursor: auto; + mso-padding-alt: 15px 25px 15px 25px; + background: #3b9fde; + " + valign="middle" + > + <a + href="https://%{consoleUrl}/verify?code=%{code}" + style=" + display: inline-block; + background: #3b9fde; + color: #ffffff; + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 120%; + margin: 0; + text-decoration: none; + text-transform: none; + padding: 15px 25px 15px 25px; + mso-padding-alt: 0px; + border-radius: 100px; + " + target="_blank" + ><b style="font-weight: 700" + ><b style="font-weight: 700" + >Verify your email</b + ></b + ></a + > + </td> + </tr> + </tbody> + </table> + </td> + </tr> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 25px 0px 25px; + padding-top: 0px; + padding-right: 50px; + padding-bottom: 0px; + padding-left: 50px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <p style="margin: 10px 0; text-align: center"> + Or copy and paste this link into your browser + </p> + <p style="margin: 10px 0; text-align: center"> + <a + target="_blank" + rel="noopener noreferrer" + href="https://%{consoleUrl}/verify?code=%{code}" + style="color: #3b9fde" + >https://%{consoleUrl}/verify?code=%{code}</a + > + </p> + </div> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </div> + </body> +</html> diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java index 873ab435444..edea07e205c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java @@ -12,9 +12,11 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantContacts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.net.URI; import java.time.Duration; import java.util.List; +import static com.yahoo.yolean.Exceptions.uncheck; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -27,7 +29,7 @@ class MailVerifierTest { private final ControllerTester tester = new ControllerTester(SystemName.Public); private final MockMailer mailer = tester.serviceRegistry().mailer(); - private final MailVerifier mailVerifier = new MailVerifier(tester.controller().tenants(), mailer, tester.curator(), tester.clock()); + private final MailVerifier mailVerifier = new MailVerifier(URI.create("https://dashboard.uri.example.com"), tester.controller().tenants(), mailer, tester.curator(), tester.clock()); private static final TenantName tenantName = TenantName.from("scoober"); private static final String mail = "unverified@bar.com"; @@ -53,9 +55,7 @@ class MailVerifierTest { mailVerifier.sendMailVerification(tenantName, mail, PendingMailVerification.MailType.NOTIFICATIONS); // Verify mail is sent - var expectedMail = "message"; assertEquals(1, mailer.inbox(mail).size()); - assertEquals(expectedMail, mailer.inbox(mail).get(0).message()); // Verify ZK data is updated var writtenMailVerification = tester.curator().listPendingMailVerifications().get(0); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java index c0501206494..ecf42979b40 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java @@ -20,6 +20,7 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.time.Instant; import java.util.List; import java.util.Map; @@ -54,7 +55,7 @@ public class NotifierTest { } @Test - void dispatch() { + void dispatch() throws IOException { var mailer = new MockMailer(); var flagSource = new InMemoryFlagSource().withBooleanFlag(Flags.NOTIFICATION_DISPATCH_FLAG.id(), true); var notifier = new Notifier(curatorDb, new ZoneRegistryMock(SystemName.cd), mailer, flagSource); @@ -68,21 +69,7 @@ public class NotifierTest { var mail = mailer.inbox(email.getEmailAddress()).get(0); assertEquals("[WARNING] Test package Vespa Notification for tenant1.default.default", mail.subject()); - assertEquals(""" - <div style="background: #00598c; height: 55px; width: 100%"> - <img - src="https://vespa.ai/assets/vespa-logo.png" - style="width: auto; height: 34px; margin: 10px" - /> - </div> - <br> - There are problems with tests for default.default<br> - <ul> - <li>test package has production tests, but no production tests are declared in deployment.xml</li><br> - <li>see <a href="https://docs.vespa.ai/en/testing.html">https://docs.vespa.ai/en/testing.html</a> for details on how to write system tests for Vespa</li></ul> - <br> - <a href="https://dashboard.tld/tenant1/default">Vespa Console</a>""", - mail.htmlMessage().get()); + assertEquals(new String(NotifierTest.class.getResourceAsStream("/mail/notification.txt").readAllBytes()), mail.htmlMessage().get()); } @Test diff --git a/controller-server/src/test/resources/mail/notification.txt b/controller-server/src/test/resources/mail/notification.txt new file mode 100644 index 00000000000..5ca26cb4753 --- /dev/null +++ b/controller-server/src/test/resources/mail/notification.txt @@ -0,0 +1,647 @@ +<!DOCTYPE html> +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:o="urn:schemas-microsoft-com:office:office" +> + <head> + <title></title> + <!--[if !mso]><!--> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <!--<![endif]--> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <style type="text/css"> + #outlook a { + padding: 0; + } + + body { + margin: 0; + padding: 0; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + table, + td { + border-collapse: collapse; + mso-table-lspace: 0pt; + mso-table-rspace: 0pt; + } + + img { + border: 0; + height: auto; + line-height: 100%; + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; + } + + p { + display: block; + margin: 13px 0; + } + </style> + <!--[if mso]> + <noscript> + <xml> + <o:OfficeDocumentSettings> + <o:AllowPNG /> + <o:PixelsPerInch>96</o:PixelsPerInch> + </o:OfficeDocumentSettings> + </xml> + </noscript> + <![endif]--> + <!--[if lte mso 11]> + <style type="text/css"> + .mj-outlook-group-fix { + width: 100% !important; + } + </style> + <![endif]--> + <!--[if !mso]><!--> + <link + href="https://fonts.googleapis.com/css?family=Open Sans" + rel="stylesheet" + type="text/css" + /> + <style type="text/css"> + @import url(https://fonts.googleapis.com/css?family=Open Sans); + </style> + <!--<![endif]--> + <style type="text/css"> + @media only screen and (min-width: 480px) { + .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + } + </style> + <style media="screen and (min-width:480px)"> + .moz-text-html .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + </style> + <style type="text/css"> + [owa] .mj-column-per-100 { + width: 100% !important; + max-width: 100%; + } + </style> + <style type="text/css"> + @media only screen and (max-width: 480px) { + table.mj-full-width-mobile { + width: 100% !important; + } + + td.mj-full-width-mobile { + width: auto !important; + } + } + </style> + </head> + + <body style="word-spacing: normal; background-color: #f2f7fa"> + <div style="background-color: #f2f7fa"> + <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div style="margin: 0px auto; max-width: 600px"> + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0px 20px 0px; + padding-bottom: 0px; + padding-top: 0px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 0px 0px 25px; + padding-top: 0px; + padding-bottom: 0px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 11px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <br /> + </div> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div + style=" + background: #ffffff; + background-color: #ffffff; + margin: 0px auto; + max-width: 600px; + " + > + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="background: #ffffff; background-color: #ffffff; width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0; + padding-bottom: 0px; + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="center" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 0px; + padding-right: 0px; + padding-bottom: 40px; + padding-left: 0px; + word-break: break-word; + " + > + <p + style=" + border-top: solid 8px #005a8e; + font-size: 1px; + margin: 0px auto; + width: 100%; + " + ></p> + <!--[if mso | IE + ]><table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + style=" + border-top: solid 8px #005a8e; + font-size: 1px; + margin: 0px auto; + width: 600px; + " + role="presentation" + width="600px" + > + <tr> + <td style="height: 0; line-height: 0"> + + </td> + </tr> + </table><! + [endif]--> + </td> + </tr> + <tr> + <td + align="center" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 0px; + padding-bottom: 0px; + word-break: break-word; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style=" + border-collapse: collapse; + border-spacing: 0px; + " + > + <tbody> + <tr> + <td style="width: 121px"> + <img + alt="" + height="auto" + src="https://data.vespa.oath.cloud/assets/vespa-cloud-logo.png" + style=" + border: none; + display: block; + outline: none; + text-decoration: none; + height: auto; + width: 100%; + font-size: 13px; + " + width="121" + /> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div + style=" + background: #ffffff; + background-color: #ffffff; + margin: 0px auto; + max-width: 600px; + " + > + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="background: #ffffff; background-color: #ffffff; width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0px 20px 0px; + padding-bottom: 70px; + padding-top: 30px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 25px 0px 25px; + padding-top: 0px; + padding-right: 50px; + padding-bottom: 0px; + padding-left: 50px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <h1 + style=" + text-align: center; + color: #000000; + line-height: 32px; + " + > + Vespa cloud notifications + </h1> + </div> + </td> + </tr> + <tr> + <td + align="left" + style=" + font-size: 0px; + padding: 0px 25px 0px 25px; + padding-top: 0px; + padding-right: 50px; + padding-bottom: 0px; + padding-left: 50px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + line-height: 22px; + text-align: left; + color: #797e82; + " + > + <p> + There are problems with tests for default.default + </p> + <ul> + <li>test package has production tests, but no production tests are declared in deployment.xml</li><li>see <a href="https://docs.vespa.ai/en/testing.html">https://docs.vespa.ai/en/testing.html</a> for details on how to write system tests for Vespa</li> + </ul> + </div> + </td> + </tr> + <tr> + <td + align="center" + vertical-align="middle" + style=" + font-size: 0px; + padding: 10px 25px; + padding-top: 20px; + padding-bottom: 20px; + word-break: break-word; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="border-collapse: separate; line-height: 100%" + > + <tbody> + <tr> + <td + align="center" + bgcolor="#005A8E" + role="presentation" + style=" + border: none; + border-radius: 100px; + cursor: auto; + mso-padding-alt: 15px 25px 15px 25px; + background: #005a8e; + " + valign="middle" + > + <a + href="#" + style=" + display: inline-block; + background: #005a8e; + color: #ffffff; + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 120%; + margin: 0; + text-decoration: none; + text-transform: none; + padding: 15px 25px 15px 25px; + mso-padding-alt: 0px; + border-radius: 100px; + " + target="_blank" + ><b style="font-weight: 700" + ><b style="font-weight: 700" + >Go to Console</b + ></b + ></a + > + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--> + <div style="margin: 0px auto; max-width: 600px"> + <table + align="center" + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="width: 100%" + > + <tbody> + <tr> + <td + style=" + direction: ltr; + font-size: 0px; + padding: 20px 0px 20px 0px; + padding-bottom: 0px; + padding-top: 20px; + text-align: center; + " + > + <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--> + <div + class="mj-column-per-100 mj-outlook-group-fix" + style=" + font-size: 0px; + text-align: left; + direction: ltr; + display: inline-block; + vertical-align: top; + width: 100%; + " + > + <table + border="0" + cellpadding="0" + cellspacing="0" + role="presentation" + style="vertical-align: top" + width="100%" + > + <tbody> + <tr> + <td + align="center" + style=" + font-size: 0px; + padding: 0px 20px 0px 20px; + padding-top: 0px; + padding-bottom: 0px; + word-break: break-word; + " + > + <div + style=" + font-family: Open Sans, Helvetica, Arial, + sans-serif; + font-size: 11px; + line-height: 22px; + text-align: center; + color: #797e82; + " + > + <p style="margin: 10px 0"> + <a + target="_blank" + rel="noopener noreferrer" + style="color: #005a8e" + href="https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html" + ><span style="color: #005a8e" + >Yahoo Privacy Policy</span + ></a + ><span style="color: #797e82" + > | </span + ><a + target="_blank" + rel="noopener noreferrer" + style="color: #005a8e" + href="https://dashboard.tld/terms-of-service-trial.html" + ><span style="color: #005a8e" + >Terms of Service</span + ></a + ><span style="color: #797e82" + > | </span + ><a + target="_blank" + rel="noopener noreferrer" + style="color: #005a8e" + href="https://dashboard.tld/support" + ><span style="color: #005a8e">Support</span></a + > + </p> + <p style="margin: 10px 0"> + <a + target="_blank" + rel="noopener noreferrer" + style="color: inherit; text-decoration: none" + href="https://dashboard.tld/tenant/tenant1/account/notifications" + >Click + <span style="color: #005a8e"><u>here</u></span> + to manage your notifications setting.</a + ><br /> + </p> + </div> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + </div> + <!--[if mso | IE]></td></tr></table><![endif]--> + </div> + </body> +</html> |