From 7ca3a885c7c98104ca6e1bcb49439c48a3159b84 Mon Sep 17 00:00:00 2001 From: Dominik Riemer Date: Fri, 20 Oct 2023 14:42:12 +0200 Subject: [PATCH] feat(#2056): Make email templates configurable in UI (#2057) * feat(#2056): Make email templates configurable in UI * Suppress checkstyle warning for HTML templates * Remove obsolete code * Fix content type in rest interface * Move email default template to resources, use syntax highlighting in UI * Fix misleading comment --- .../apache/streampipes/mail/MailSender.java | 8 +- .../mail/template/AbstractMailTemplate.java | 23 ++- .../AccountActiviationMailTemplate.java | 5 +- .../mail/template/CustomMailTemplate.java | 57 +++++++ .../template/InitialPasswordMailTemplate.java | 5 +- .../PasswordRecoveryMailTemplate.java | 6 +- .../mail/template/TestMailTemplate.java | 5 +- .../generation/DefaultPlaceholders.java | 2 - .../generation/MailTemplateBuilder.java | 59 +++---- .../mail/template/part/MailTemplatePart.java | 14 +- .../main/resources/mail-template-footer.html | 39 ----- .../resources/mail-template-inner-button.html | 70 ++++---- .../resources/mail-template-inner-plain.html | 29 ++-- .../main/resources/mail-template-outer.html | 154 ------------------ .../DefaultEmailTemplateConfiguration.java | 58 +++++++ .../DefaultSpCoreConfiguration.java | 1 + .../configuration/EmailTemplateConfig.java | 41 +++++ .../configuration/SpCoreConfiguration.java | 9 + .../streampipes/model/mail/SpEmail.java | 20 ++- .../main/resources/default-mail-template.html | 150 +++++++++++++++++ .../admin/EmailConfigurationResource.java | 22 +++ .../core/migrations/AvailableMigrations.java | 4 +- .../v093/StoreEmailTemplatesMigration.java | 58 +++++++ .../src/lib/apis/mail-config.service.ts | 12 +- .../src/lib/model/email-config.model.ts | 4 + .../src/lib/model/gen/streampipes-model.ts | 6 +- .../app/configuration/configuration.module.ts | 4 + .../email-configuration.component.html | 6 + ...mail-template-configuration.component.html | 87 ++++++++++ ...mail-template-configuration.component.scss | 40 +++++ .../email-template-configuration.component.ts | 92 +++++++++++ 31 files changed, 774 insertions(+), 316 deletions(-) create mode 100644 streampipes-mail/src/main/java/org/apache/streampipes/mail/template/CustomMailTemplate.java delete mode 100644 streampipes-mail/src/main/resources/mail-template-footer.html delete mode 100644 streampipes-mail/src/main/resources/mail-template-outer.html create mode 100644 streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultEmailTemplateConfiguration.java create mode 100644 streampipes-model/src/main/java/org/apache/streampipes/model/configuration/EmailTemplateConfig.java create mode 100644 streampipes-model/src/main/resources/default-mail-template.html create mode 100644 streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/StoreEmailTemplatesMigration.java create mode 100644 ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.html create mode 100644 ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.scss create mode 100644 ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.ts diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/MailSender.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/MailSender.java index c34f8f6e65..43f9d57803 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/MailSender.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/MailSender.java @@ -18,6 +18,7 @@ package org.apache.streampipes.mail; import org.apache.streampipes.mail.template.AccountActiviationMailTemplate; +import org.apache.streampipes.mail.template.CustomMailTemplate; import org.apache.streampipes.mail.template.InitialPasswordMailTemplate; import org.apache.streampipes.mail.template.PasswordRecoveryMailTemplate; import org.apache.streampipes.mail.utils.MailUtils; @@ -29,11 +30,14 @@ public class MailSender extends AbstractMailer { - public void sendEmail(SpEmail mail) { + public void sendEmail(SpEmail mail) throws IOException { Email email = baseEmail() .withRecipients(toSimpleRecipientList(mail.getRecipients())) .withSubject(mail.getSubject()) - .appendText(mail.getMessage()) + .appendTextHTML(new CustomMailTemplate( + mail.getSubject(), + mail.getPreheader(), + mail.getMessage()).generateTemplate()) .buildEmail(); deliverMail(email); diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AbstractMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AbstractMailTemplate.java index ec05ada2e8..d9f58e00f7 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AbstractMailTemplate.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AbstractMailTemplate.java @@ -21,7 +21,7 @@ import org.apache.streampipes.mail.template.generation.MailTemplateBuilder; import org.apache.streampipes.mail.template.part.BaseUrlPart; import org.apache.streampipes.mail.template.part.LogoPart; -import org.apache.streampipes.mail.template.part.MailTemplatePart; +import org.apache.streampipes.storage.management.StorageDispatcher; import java.io.IOException; import java.util.HashMap; @@ -35,22 +35,27 @@ public abstract class AbstractMailTemplate { protected abstract void addPlaceholders(Map placeholders); - protected abstract void addTemplateParts(Map templateParts); + protected abstract void configureTemplate(MailTemplateBuilder builder); public String generateTemplate() throws IOException { - Map templateParts = new HashMap<>(); Map placeholders = new HashMap<>(); - addTemplateParts(templateParts); addPlaceholders(placeholders); - return MailTemplateBuilder.create(MailTemplatePart.MAIL_TEMPLATE_OUTER) - .addSubpart(DefaultPlaceholders.FOOTER, MailTemplatePart.MAIL_TEMPLATE_FOOTER) - .addSubparts(templateParts) + var template = StorageDispatcher.INSTANCE + .getNoSqlStore() + .getSpCoreConfigurationStorage() + .get() + .getEmailTemplateConfig() + .getTemplate(); + + var builder = MailTemplateBuilder.create(template) .withPlaceholder(DefaultPlaceholders.TITLE, getTitle()) .withPlaceholder(DefaultPlaceholders.PREHEADER, getPreHeader()) .withPlaceholder(DefaultPlaceholders.LOGO, new LogoPart().generate()) .withPlaceholder(DefaultPlaceholders.BASE_URL, new BaseUrlPart().generate()) - .withPlaceholders(placeholders) - .generateHtmlTemplate(); + .withPlaceholders(placeholders); + + configureTemplate(builder); + return builder.generateHtmlTemplate(); } } diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AccountActiviationMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AccountActiviationMailTemplate.java index 7bd0a7974d..c9c204559e 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AccountActiviationMailTemplate.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/AccountActiviationMailTemplate.java @@ -18,6 +18,7 @@ package org.apache.streampipes.mail.template; import org.apache.streampipes.mail.template.generation.DefaultPlaceholders; +import org.apache.streampipes.mail.template.generation.MailTemplateBuilder; import org.apache.streampipes.mail.template.part.LinkPart; import org.apache.streampipes.mail.template.part.MailTemplatePart; import org.apache.streampipes.mail.utils.MailUtils; @@ -54,8 +55,8 @@ protected void addPlaceholders(Map placeholders) { } @Override - protected void addTemplateParts(Map templateParts) { - templateParts.put(DefaultPlaceholders.INNER.key(), MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON); + protected void configureTemplate(MailTemplateBuilder builder) { + builder.withInnerPart(MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON); } private String makeLink() { diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/CustomMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/CustomMailTemplate.java new file mode 100644 index 0000000000..1ea53286d8 --- /dev/null +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/CustomMailTemplate.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.mail.template; + +import org.apache.streampipes.mail.template.generation.MailTemplateBuilder; + +import java.util.Map; + +public class CustomMailTemplate extends AbstractMailTemplate { + + private final String title; + private final String preheader; + private final String content; + + public CustomMailTemplate(String title, + String preheader, + String content) { + this.title = title; + this.preheader = preheader; + this.content = content; + } + + @Override + protected String getTitle() { + return title; + } + + @Override + protected String getPreHeader() { + return preheader; + } + + @Override + protected void addPlaceholders(Map placeholders) { + } + + @Override + protected void configureTemplate(MailTemplateBuilder builder) { + builder.withInnerPart(content); + } +} diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/InitialPasswordMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/InitialPasswordMailTemplate.java index 6e8a7e847c..f74f279f1f 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/InitialPasswordMailTemplate.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/InitialPasswordMailTemplate.java @@ -18,6 +18,7 @@ package org.apache.streampipes.mail.template; import org.apache.streampipes.mail.template.generation.DefaultPlaceholders; +import org.apache.streampipes.mail.template.generation.MailTemplateBuilder; import org.apache.streampipes.mail.template.part.LinkPart; import org.apache.streampipes.mail.template.part.MailTemplatePart; import org.apache.streampipes.mail.utils.MailUtils; @@ -54,8 +55,8 @@ protected void addPlaceholders(Map placeholders) { } @Override - protected void addTemplateParts(Map templateParts) { - templateParts.put(DefaultPlaceholders.INNER.key(), MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON); + protected void configureTemplate(MailTemplateBuilder builder) { + builder.withInnerPart(MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON); } private String makeLink() { diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/PasswordRecoveryMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/PasswordRecoveryMailTemplate.java index 2cfa43baa0..da1d0aa093 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/PasswordRecoveryMailTemplate.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/PasswordRecoveryMailTemplate.java @@ -18,6 +18,7 @@ package org.apache.streampipes.mail.template; import org.apache.streampipes.mail.template.generation.DefaultPlaceholders; +import org.apache.streampipes.mail.template.generation.MailTemplateBuilder; import org.apache.streampipes.mail.template.part.LinkPart; import org.apache.streampipes.mail.template.part.MailTemplatePart; import org.apache.streampipes.mail.utils.MailUtils; @@ -54,9 +55,8 @@ protected void addPlaceholders(Map placeholders) { } @Override - protected void addTemplateParts(Map templateParts) { - templateParts.put(DefaultPlaceholders.INNER.key(), MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON); - templateParts.put(DefaultPlaceholders.FOOTER.key(), MailTemplatePart.MAIL_TEMPLATE_FOOTER); + protected void configureTemplate(MailTemplateBuilder builder) { + builder.withInnerPart(MailTemplatePart.MAIL_TEMPLATE_INNER_BUTTON); } private String makeLink() { diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/TestMailTemplate.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/TestMailTemplate.java index dc02613a5d..d6f913f4cf 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/TestMailTemplate.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/TestMailTemplate.java @@ -18,6 +18,7 @@ package org.apache.streampipes.mail.template; import org.apache.streampipes.mail.template.generation.DefaultPlaceholders; +import org.apache.streampipes.mail.template.generation.MailTemplateBuilder; import org.apache.streampipes.mail.template.part.MailTemplatePart; import org.apache.streampipes.mail.utils.MailUtils; @@ -41,8 +42,8 @@ protected void addPlaceholders(Map placeholders) { } @Override - protected void addTemplateParts(Map templateParts) { - templateParts.put(DefaultPlaceholders.INNER.key(), MailTemplatePart.MAIL_TEMPLATE_INNER_PLAIN); + protected void configureTemplate(MailTemplateBuilder builder) { + builder.withInnerPart(MailTemplatePart.MAIL_TEMPLATE_INNER_PLAIN); } private String makeText() { diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/DefaultPlaceholders.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/DefaultPlaceholders.java index 757c8571ea..3f80e578f0 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/DefaultPlaceholders.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/DefaultPlaceholders.java @@ -21,8 +21,6 @@ public enum DefaultPlaceholders { TITLE("TITLE"), PREHEADER("PREHEADER"), - FOOTER("FOOTER"), - FOOTER_TEXT("FOOTER_TEXT"), INNER("INNER"), BUTTON_TEXT("BUTTON_TEXT"), diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/MailTemplateBuilder.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/MailTemplateBuilder.java index 4109c1dc33..c16cc8b261 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/MailTemplateBuilder.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/generation/MailTemplateBuilder.java @@ -18,7 +18,6 @@ package org.apache.streampipes.mail.template.generation; import org.apache.streampipes.mail.template.part.MailTemplatePart; -import org.apache.streampipes.mail.utils.MailUtils; import java.io.IOException; import java.util.HashMap; @@ -26,33 +25,30 @@ public class MailTemplateBuilder { - private final MailTemplatePart outerPart; - private final Map innerParts; + private final String outerTemplate; + private String innerPart; private final Map placeholders; - private MailTemplateBuilder(MailTemplatePart outerPart) { - this.outerPart = outerPart; - this.innerParts = new HashMap<>(); + private MailTemplateBuilder(String outerTemplate) { + this.outerTemplate = outerTemplate; this.placeholders = new HashMap<>(); } - public static MailTemplateBuilder create(MailTemplatePart outerPart) { - return new MailTemplateBuilder(outerPart); + public static MailTemplateBuilder create(String outerTemplate) { + return new MailTemplateBuilder(outerTemplate); } - public MailTemplateBuilder addSubpart(String placeholder, MailTemplatePart templatePart) { - this.innerParts.put(placeholder, templatePart); - + public MailTemplateBuilder withInnerPart(MailTemplatePart innerPart) { + try { + this.innerPart = innerPart.getContent(); + } catch (IOException e) { + throw new RuntimeException(e); + } return this; } - public MailTemplateBuilder addSubpart(DefaultPlaceholders placeholder, MailTemplatePart templatePart) { - return addSubpart(placeholder.key(), templatePart); - - } - - public MailTemplateBuilder addSubparts(Map templateParts) { - this.innerParts.putAll(templateParts); + public MailTemplateBuilder withInnerPart(String content) { + this.innerPart = content; return this; } @@ -72,8 +68,8 @@ public MailTemplateBuilder withPlaceholder(DefaultPlaceholders placeholder, Stri return withPlaceholder(placeholder.key(), content); } - public String generateHtmlTemplate() throws IOException { - String fullTemplate = getAndApplyPlaceholders(outerPart); + public String generateHtmlTemplate() { + String fullTemplate = getAndApplyPlaceholders(outerTemplate); for (String key : placeholders.keySet()) { String placeholder = makeKey(key); @@ -83,30 +79,15 @@ public String generateHtmlTemplate() throws IOException { return fullTemplate; } - private String readFileContentsToString(MailTemplatePart part) throws IOException { - return MailUtils.readResourceFileToString(part.getTemplateFilename()); + private String getAndApplyPlaceholders(String outerTemplate) { + return applyInnerTemplate(outerTemplate); } - private String getAndApplyPlaceholders(MailTemplatePart partContent) throws IOException { - String partContentAsString = readFileContentsToString(partContent); - for (String innerPartKey : innerParts.keySet()) { - String templateKey = makeKey(innerPartKey); - if (hasPlaceholder(partContentAsString, templateKey)) { - partContentAsString = - partContentAsString.replaceAll(templateKey, getAndApplyPlaceholders(innerParts.get(innerPartKey))); - } - } - - return partContentAsString; - } - - private boolean hasPlaceholder(String content, String placeholder) { - return content.contains(placeholder); + private String applyInnerTemplate(String content) { + return content.replaceAll(makeKey(DefaultPlaceholders.INNER.key()), innerPart); } private String makeKey(String key) { return "###" + key + "###"; } - - } diff --git a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/part/MailTemplatePart.java b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/part/MailTemplatePart.java index a3526fd13b..addd162857 100644 --- a/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/part/MailTemplatePart.java +++ b/streampipes-mail/src/main/java/org/apache/streampipes/mail/template/part/MailTemplatePart.java @@ -17,20 +17,22 @@ */ package org.apache.streampipes.mail.template.part; +import org.apache.streampipes.mail.utils.MailUtils; + +import java.io.IOException; + public enum MailTemplatePart { - MAIL_TEMPLATE_OUTER("mail-template-outer.html"), MAIL_TEMPLATE_INNER_BUTTON("mail-template-inner-button.html"), - MAIL_TEMPLATE_INNER_PLAIN("mail-template-inner-plain.html"), - MAIL_TEMPLATE_FOOTER("mail-template-footer.html"); + MAIL_TEMPLATE_INNER_PLAIN("mail-template-inner-plain.html"); - private String templateFilename; + private final String templateFilename; MailTemplatePart(String templateFilename) { this.templateFilename = templateFilename; } - public String getTemplateFilename() { - return this.templateFilename; + public String getContent() throws IOException { + return MailUtils.readResourceFileToString(templateFilename); } } diff --git a/streampipes-mail/src/main/resources/mail-template-footer.html b/streampipes-mail/src/main/resources/mail-template-footer.html deleted file mode 100644 index 3c0aaca246..0000000000 --- a/streampipes-mail/src/main/resources/mail-template-footer.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - -
-

This mail was automatically sent from a local installation of Apache - StreamPipes - contact your administrator if you think you shouldn't receive this mail.

-

This mail is not sent by the Apache StreamPipes team.

-

-
- - diff --git a/streampipes-mail/src/main/resources/mail-template-inner-button.html b/streampipes-mail/src/main/resources/mail-template-inner-button.html index f9bc7aecd7..98878a0358 100644 --- a/streampipes-mail/src/main/resources/mail-template-inner-button.html +++ b/streampipes-mail/src/main/resources/mail-template-inner-button.html @@ -22,42 +22,38 @@ ~ SOFTWARE. --> - - - - - - - - - - - + + + - + +
-

###MANUAL###

-
- - - - -
- - - - -
- ###BUTTON_TEXT### -
-
-
-

###LINK_DESCRIPTION###

-

###LINK### -

+ + + + + + + +
+

###MANUAL###

+
+ + + +
+ + + - -
+ ###BUTTON_TEXT###
+
+
+
+

###LINK_DESCRIPTION###

+

###LINK### +

diff --git a/streampipes-mail/src/main/resources/mail-template-inner-plain.html b/streampipes-mail/src/main/resources/mail-template-inner-plain.html index 90ef04ec93..6974a6d124 100644 --- a/streampipes-mail/src/main/resources/mail-template-inner-plain.html +++ b/streampipes-mail/src/main/resources/mail-template-inner-plain.html @@ -22,18 +22,17 @@ ~ SOFTWARE. --> - - - - - - +
- - - - -
-

###TEXT###

-
-
+ + + +
+ + + + +
+

###TEXT###

+
+
diff --git a/streampipes-mail/src/main/resources/mail-template-outer.html b/streampipes-mail/src/main/resources/mail-template-outer.html deleted file mode 100644 index 30eceded55..0000000000 --- a/streampipes-mail/src/main/resources/mail-template-outer.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - - - ###TITLE### - - - - - - - - - - - - - - - - - - - ###INNER### - - ###FOOTER### -
- - - - -
- - Logo - -
-
- - - - -
-

- ###TITLE###

-
-
- - diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultEmailTemplateConfiguration.java b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultEmailTemplateConfiguration.java new file mode 100644 index 0000000000..2f1e37dd2d --- /dev/null +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultEmailTemplateConfiguration.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.model.configuration; + +import org.apache.streampipes.commons.resources.Resources; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class DefaultEmailTemplateConfiguration { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultEmailTemplateConfiguration.class); + + private static final String DefaultTemplateFilename = "default-mail-template.html"; + + public EmailTemplateConfig getDefaultTemplates() { + String template = ""; + try { + template = removeHeader(getTemplate()); + } catch (IOException e) { + LOG.warn( + "Default email template could not be loaded - set this manually in the UI under Configuration->Mail" + ); + } + return new EmailTemplateConfig(template); + } + + private String getTemplate() throws IOException { + return Resources.asString(DefaultTemplateFilename, StandardCharsets.UTF_8); + } + + private String removeHeader(String template) { + return Arrays.stream(template.split("\n")) + .skip(22) + .collect(Collectors.joining("\n")); + } +} diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java index 044d1f5c42..4f654bd992 100644 --- a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/DefaultSpCoreConfiguration.java @@ -32,6 +32,7 @@ public SpCoreConfiguration make() { coreCfg.setGeneralConfig(new GeneralConfig()); coreCfg.setMessagingSettings(new DefaultMessagingSettings().make()); coreCfg.setEmailConfig(EmailConfig.fromDefaults()); + coreCfg.setEmailTemplateConfig(new DefaultEmailTemplateConfiguration().getDefaultTemplates()); coreCfg.setFilesDir(makeFileLocation()); coreCfg.setAssetDir(makeAssetLocation()); coreCfg.setLocalAuthConfig(LocalAuthConfig.fromDefaults(getJwtSecret())); diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/EmailTemplateConfig.java b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/EmailTemplateConfig.java new file mode 100644 index 0000000000..2e8d2df6e6 --- /dev/null +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/EmailTemplateConfig.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.model.configuration; + +public class EmailTemplateConfig { + + private String template; + + public EmailTemplateConfig() { + } + + public EmailTemplateConfig(String template) { + this.template = template; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } +} + + diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java index 32ea21d4ee..3e3bd251d0 100644 --- a/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/configuration/SpCoreConfiguration.java @@ -30,6 +30,7 @@ public class SpCoreConfiguration { private MessagingSettings messagingSettings; private LocalAuthConfig localAuthConfig; private EmailConfig emailConfig; + private EmailTemplateConfig emailTemplateConfig; private GeneralConfig generalConfig; private boolean isConfigured; @@ -111,4 +112,12 @@ public String getFilesDir() { public void setFilesDir(String filesDir) { this.filesDir = filesDir; } + + public EmailTemplateConfig getEmailTemplateConfig() { + return this.emailTemplateConfig; + } + + public void setEmailTemplateConfig(EmailTemplateConfig emailTemplateConfig) { + this.emailTemplateConfig = emailTemplateConfig; + } } diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/mail/SpEmail.java b/streampipes-model/src/main/java/org/apache/streampipes/model/mail/SpEmail.java index a1a1efd614..3fcf592cdf 100644 --- a/streampipes-model/src/main/java/org/apache/streampipes/model/mail/SpEmail.java +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/mail/SpEmail.java @@ -22,6 +22,7 @@ public class SpEmail { private List recipients; + private String preheader; private String subject; private String message; @@ -30,16 +31,25 @@ public SpEmail() { public SpEmail(List recipients, String subject, + String preheader, String message) { this.recipients = recipients; this.subject = subject; + this.preheader = preheader; this.message = message; } public static SpEmail from(List recipients, String subject, String message) { - return new SpEmail(recipients, subject, message); + return new SpEmail(recipients, subject, "", message); + } + + public static SpEmail from(List recipients, + String subject, + String preheader, + String message) { + return new SpEmail(recipients, subject, preheader, message); } public List getRecipients() { @@ -58,6 +68,14 @@ public void setSubject(String subject) { this.subject = subject; } + public String getPreheader() { + return preheader; + } + + public void setPreheader(String preheader) { + this.preheader = preheader; + } + public String getMessage() { return message; } diff --git a/streampipes-model/src/main/resources/default-mail-template.html b/streampipes-model/src/main/resources/default-mail-template.html new file mode 100644 index 0000000000..956658011b --- /dev/null +++ b/streampipes-model/src/main/resources/default-mail-template.html @@ -0,0 +1,150 @@ + + + + + + + + + + ###TITLE### + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + Logo + +
+
+ + + + +
+

+ ###TITLE###

+
+
+ ###INNER### +
+ + + + +
+

This mail was automatically sent from a local installation of Apache + StreamPipes - contact your administrator if you think you shouldn't receive this mail.

+

This mail is not sent by the Apache StreamPipes team.

+

+
+
+ + diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/EmailConfigurationResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/EmailConfigurationResource.java index a14e8f7bcf..6b6f646975 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/EmailConfigurationResource.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/EmailConfigurationResource.java @@ -19,6 +19,7 @@ import org.apache.streampipes.mail.MailTester; import org.apache.streampipes.model.configuration.EmailConfig; +import org.apache.streampipes.model.configuration.EmailTemplateConfig; import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource; import org.apache.streampipes.rest.security.AuthConstants; import org.apache.streampipes.rest.shared.annotation.JacksonSerialized; @@ -51,6 +52,27 @@ public Response getMailConfiguration() { return ok(getSpCoreConfigurationStorage().get().getEmailConfig()); } + @GET + @Path("templates") + @JacksonSerialized + @Produces(MediaType.APPLICATION_JSON) + @PreAuthorize(AuthConstants.IS_ADMIN_ROLE) + public Response getMailTemplates() { + return ok(getSpCoreConfigurationStorage().get().getEmailTemplateConfig()); + } + + @PUT + @Path("templates") + @JacksonSerialized + @Consumes(MediaType.APPLICATION_JSON) + @PreAuthorize(AuthConstants.IS_ADMIN_ROLE) + public Response updateMailTemplate(EmailTemplateConfig templateConfig) { + var config = getSpCoreConfigurationStorage().get(); + config.setEmailTemplateConfig(templateConfig); + getSpCoreConfigurationStorage().updateElement(config); + return ok(); + } + @PUT @JacksonSerialized @Consumes(MediaType.APPLICATION_JSON) diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java index 3336f462de..ce673b5e5c 100644 --- a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java +++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java @@ -25,6 +25,7 @@ import org.apache.streampipes.service.core.migrations.v090.UpdateUsernameViewMigration; import org.apache.streampipes.service.core.migrations.v093.AdapterMigration; import org.apache.streampipes.service.core.migrations.v093.ConsulConfigMigration; +import org.apache.streampipes.service.core.migrations.v093.StoreEmailTemplatesMigration; import java.util.Arrays; import java.util.List; @@ -38,7 +39,8 @@ public List getAvailableMigrations() { new CreateFileAssetTypeMigration(), new UpdateUsernameViewMigration(), new AdapterMigration(), - new ConsulConfigMigration() + new ConsulConfigMigration(), + new StoreEmailTemplatesMigration() ); } } diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/StoreEmailTemplatesMigration.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/StoreEmailTemplatesMigration.java new file mode 100644 index 0000000000..534a070b48 --- /dev/null +++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v093/StoreEmailTemplatesMigration.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.streampipes.service.core.migrations.v093; + +import org.apache.streampipes.model.configuration.DefaultEmailTemplateConfiguration; +import org.apache.streampipes.service.core.migrations.Migration; +import org.apache.streampipes.storage.api.ISpCoreConfigurationStorage; +import org.apache.streampipes.storage.management.StorageDispatcher; + +import java.io.IOException; + +public class StoreEmailTemplatesMigration implements Migration { + + private final ISpCoreConfigurationStorage storage; + + public StoreEmailTemplatesMigration() { + this.storage = StorageDispatcher.INSTANCE.getNoSqlStore().getSpCoreConfigurationStorage(); + } + + @Override + public boolean shouldExecute() { + var config = storage.get(); + if (config == null) { + return false; + } else { + return config.getEmailTemplateConfig() == null; + } + } + + @Override + public void executeMigration() throws IOException { + var config = storage.get(); + config.setEmailTemplateConfig(new DefaultEmailTemplateConfiguration().getDefaultTemplates()); + + storage.updateElement(config); + } + + @Override + public String getDescription() { + return "Moving email templates to configuration storage"; + } +} diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/mail-config.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/mail-config.service.ts index afc270735d..a90e98897b 100644 --- a/ui/projects/streampipes/platform-services/src/lib/apis/mail-config.service.ts +++ b/ui/projects/streampipes/platform-services/src/lib/apis/mail-config.service.ts @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http'; import { map } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { PlatformServicesCommons } from './commons.service'; -import { EmailConfig } from '../model/email-config.model'; +import { EmailConfig, EmailTemplate } from '../model/email-config.model'; @Injectable({ providedIn: 'root', @@ -38,10 +38,20 @@ export class MailConfigService { .pipe(map(response => response as EmailConfig)); } + getMailTemplate(): Observable { + return this.http + .get(`${this.mailConfigPath}/templates`) + .pipe(map(response => response as EmailTemplate)); + } + updateMailConfig(config: EmailConfig): Observable { return this.http.put(this.mailConfigPath, config); } + updateMailTemplate(template: EmailTemplate): Observable { + return this.http.put(`${this.mailConfigPath}/templates`, template); + } + sendTestMail(config: EmailConfig) { return this.http.post(`${this.mailConfigPath}/test`, config); } diff --git a/ui/projects/streampipes/platform-services/src/lib/model/email-config.model.ts b/ui/projects/streampipes/platform-services/src/lib/model/email-config.model.ts index 638b61a778..fea8fefb68 100644 --- a/ui/projects/streampipes/platform-services/src/lib/model/email-config.model.ts +++ b/ui/projects/streampipes/platform-services/src/lib/model/email-config.model.ts @@ -44,3 +44,7 @@ export interface EmailConfig { smtpPassEncrypted: boolean; proxyPassEncrypted: boolean; } + +export interface EmailTemplate { + template: string; +} diff --git a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts index 36a810b513..04a5fb54cf 100644 --- a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts +++ b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts @@ -20,7 +20,7 @@ /* tslint:disable */ /* eslint-disable */ // @ts-nocheck -// Generated using typescript-generator version 3.1.1185 on 2023-08-06 11:37:37. +// Generated using typescript-generator version 3.2.1263 on 2023-10-19 12:31:37. export class NamedStreamPipesEntity { '@class': @@ -198,6 +198,7 @@ export class TransformationRuleDescription { | 'org.apache.streampipes.model.connect.rules.schema.DeleteRuleDescription' | 'org.apache.streampipes.model.connect.rules.schema.RenameRuleDescription' | 'org.apache.streampipes.model.connect.rules.schema.MoveRuleDescription'; + 'rulePriority': number; static 'fromData'( data: TransformationRuleDescription, @@ -208,6 +209,7 @@ export class TransformationRuleDescription { } const instance = target || new TransformationRuleDescription(); instance['@class'] = data['@class']; + instance.rulePriority = data.rulePriority; return instance; } @@ -3521,6 +3523,7 @@ export class SpServiceRegistration { scheme: string; svcGroup: string; svcId: string; + svcType: string; tags: SpServiceTag[]; static fromData( @@ -3540,6 +3543,7 @@ export class SpServiceRegistration { instance.scheme = data.scheme; instance.svcGroup = data.svcGroup; instance.svcId = data.svcId; + instance.svcType = data.svcType; instance.tags = __getCopyArrayFn(SpServiceTag.fromData)(data.tags); return instance; } diff --git a/ui/src/app/configuration/configuration.module.ts b/ui/src/app/configuration/configuration.module.ts index f0cc6b5915..05efe24c59 100644 --- a/ui/src/app/configuration/configuration.module.ts +++ b/ui/src/app/configuration/configuration.module.ts @@ -65,6 +65,8 @@ import { SpRegisteredExtensionsServiceComponent } from './extensions-service-man import { SpExtensionsServiceConfigurationComponent } from './extensions-service-management/extensions-service-configuration/extensions-service-configuration.component'; import { SpMessagingBrokerConfigComponent } from './messaging-configuration/broker-config/broker-config.component'; import { SpExtensionsServiceDetailsDialogComponent } from './dialog/extensions-service-details/extensions-service-details-dialog.component'; +import { SpEmailTemplateConfigurationComponent } from './email-configuration/email-template-configuration/email-template-configuration.component'; +import { CodemirrorModule } from '@ctrl/ngx-codemirror'; @NgModule({ imports: [ @@ -130,6 +132,7 @@ import { SpExtensionsServiceDetailsDialogComponent } from './dialog/extensions-s ]), SharedUiModule, ColorPickerModule, + CodemirrorModule, ], declarations: [ ServiceConfigsComponent, @@ -155,6 +158,7 @@ import { SpExtensionsServiceDetailsDialogComponent } from './dialog/extensions-s SpDataExportItemComponent, SpDataImportDialogComponent, SpEditLabelComponent, + SpEmailTemplateConfigurationComponent, SpExtensionsServiceDetailsDialogComponent, SpLabelConfigurationComponent, SpMessagingBrokerConfigComponent, diff --git a/ui/src/app/configuration/email-configuration/email-configuration.component.html b/ui/src/app/configuration/email-configuration/email-configuration.component.html index fd2d87cb96..c941aeb583 100644 --- a/ui/src/app/configuration/email-configuration/email-configuration.component.html +++ b/ui/src/app/configuration/email-configuration/email-configuration.component.html @@ -216,6 +216,12 @@
{{ sendingEmailErrorMessage }}
+
+ +
diff --git a/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.html b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.html new file mode 100644 index 0000000000..9ddfe8ca7c --- /dev/null +++ b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.html @@ -0,0 +1,87 @@ + + +
+ +
+
+ +
+
+
+
+
+ You can set various placeholder variables that will + be replaced with the actual values when sending an + email: +
+
+
+ {{ + placeholder.placeholder + }} + {{ + placeholder.description + }} +
+
+
+
+
+
+ + +
+
+ +
+
+
+
diff --git a/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.scss b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.scss new file mode 100644 index 0000000000..65b0f3963b --- /dev/null +++ b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.scss @@ -0,0 +1,40 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +.placeholder-explanation { + padding: 8px; + border: 1px solid var(--color-accent); + background: var(--color-bg-1); + margin-bottom: 10px; + width: 100%; +} + +.placeholder-item { + padding: 3px; + border-radius: 5px; + border: 1px solid var(--color-default-text); + margin: 3px 5px; +} + +.placeholder-variable { + margin-right: 5px; + font-weight: bold; +} + +.placeholder-description { +} diff --git a/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.ts b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.ts new file mode 100644 index 0000000000..e138a5538c --- /dev/null +++ b/ui/src/app/configuration/email-configuration/email-template-configuration/email-template-configuration.component.ts @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Component, OnInit } from '@angular/core'; +import { UntypedFormBuilder } from '@angular/forms'; +import { + EmailTemplate, + MailConfigService, +} from '@streampipes/platform-services'; + +import 'codemirror/mode/htmlembedded/htmlembedded'; + +@Component({ + selector: 'sp-email-template-configuration', + templateUrl: './email-template-configuration.component.html', + styleUrls: ['./email-template-configuration.component.scss'], +}) +export class SpEmailTemplateConfigurationComponent implements OnInit { + template: EmailTemplate; + originalTemplate: string; + templateLoaded = false; + templateStored = false; + + editorOptions = { + mode: 'htmlembedded', + autoRefresh: true, + theme: 'dracula', + autoCloseBrackets: true, + lineNumbers: true, + lineWrapping: true, + gutters: ['CodeMirror-lint-markers'], + lint: true, + }; + + allowedPlaceholders: { placeholder: string; description: string }[] = [ + { placeholder: '###LOGO###', description: 'The default logo' }, + { placeholder: '###BASE_URL###', description: 'The base URL' }, + { placeholder: '###TITLE###', description: 'Email title' }, + { placeholder: '###PREHEADER###', description: 'Email preheader' }, + { + placeholder: '###INNER###', + description: 'Email custom inner content (mandatory)', + }, + ]; + + constructor( + private fb: UntypedFormBuilder, + private mailConfigService: MailConfigService, + ) {} + + ngOnInit(): void { + this.loadTemplate(); + } + + loadTemplate(): void { + this.templateLoaded = false; + this.mailConfigService.getMailTemplate().subscribe(template => { + this.originalTemplate = template.template; + this.template = template; + this.templateLoaded = true; + }); + } + + restoreTemplate(): void { + this.template.template = this.originalTemplate; + } + + saveTemplate(): void { + this.templateStored = false; + this.mailConfigService + .updateMailTemplate(this.template) + .subscribe(() => { + this.templateStored = true; + this.loadTemplate(); + }); + } +}