This is an automated email from the ASF dual-hosted git repository. dominikriemer pushed a commit to branch extend-email-error-hints in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit a3e613f4aac481089917c47a01df369649a9ebba Author: Dominik Riemer <[email protected]> AuthorDate: Thu Jul 2 13:37:03 2026 +0200 feat: Show more meaningful error messages when sending test emails --- .../impl/admin/EmailConfigurationResource.java | 58 +++++++++++++++++++++- .../email-configuration.component.ts | 34 ++++++++++--- ui/src/scss/sp/sp-theme.scss | 6 +++ 3 files changed, 90 insertions(+), 8 deletions(-) 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 4bd6cf8a63..263307f255 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 @@ -28,6 +28,8 @@ import org.apache.streampipes.storage.api.system.ISpCoreConfigurationStorage; import org.apache.streampipes.user.management.encryption.SecretEncryptionManager; import org.simplejavamail.MailException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -39,12 +41,21 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import jakarta.mail.MessagingException; + import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Set; @RestController @RequestMapping("/api/v2/admin/mail-config") public class EmailConfigurationResource extends AbstractAuthGuardedRestResource { + private static final Logger LOG = LoggerFactory.getLogger(EmailConfigurationResource.class); + private final ISpCoreConfigurationStorage configurationStorage; public EmailConfigurationResource(ISpCoreConfigurationStorage coreConfigurationStorage) { @@ -100,7 +111,52 @@ public class EmailConfigurationResource extends AbstractAuthGuardedRestResource new MailTester(coreConfiguration).sendTestMail(config); return ok(); } catch (MailException | IllegalArgumentException | IOException e) { - throw new SpMessageException(HttpStatus.BAD_REQUEST, Notifications.error(e.getMessage())); + var mailExceptionDetails = getMailExceptionDetails(e); + LOG.warn("Unable to send test email. {}", mailExceptionDetails, e); + throw new SpMessageException( + HttpStatus.BAD_REQUEST, + Notifications.error( + "Unable to send test email", + "Please verify the SMTP server, port, transport strategy, credentials, proxy settings, " + + "and recipient address. Server response: " + mailExceptionDetails)); + } + } + + private String getMailExceptionDetails(Exception e) { + return String.join(" Cause: ", extractExceptionMessages(e)); + } + + private List<String> extractExceptionMessages(Throwable throwable) { + var messages = new ArrayList<String>(); + Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<>()); + collectExceptionMessages(throwable, messages, visited); + return messages; + } + + private void collectExceptionMessages(Throwable throwable, + List<String> messages, + Set<Throwable> visited) { + if (throwable == null || !visited.add(throwable)) { + return; + } + + addExceptionMessage(throwable, messages); + collectExceptionMessages(throwable.getCause(), messages, visited); + + if (throwable instanceof MessagingException messagingException) { + collectExceptionMessages(messagingException.getNextException(), messages, visited); + } + } + + private void addExceptionMessage(Throwable throwable, + List<String> messages) { + var message = throwable.getMessage(); + if (message == null || message.isBlank()) { + message = throwable.getClass().getSimpleName(); + } + + if (!messages.contains(message)) { + messages.add(message); } } } diff --git a/ui/src/app/configuration/email-configuration/email-configuration.component.ts b/ui/src/app/configuration/email-configuration/email-configuration.component.ts index c84b3d009c..d4d0a0efa4 100644 --- a/ui/src/app/configuration/email-configuration/email-configuration.component.ts +++ b/ui/src/app/configuration/email-configuration/email-configuration.component.ts @@ -16,6 +16,7 @@ * */ +import { HttpErrorResponse } from '@angular/common/http'; import { Component, OnInit, inject } from '@angular/core'; import { FormsModule, @@ -244,14 +245,33 @@ export class EmailConfigurationComponent implements OnInit { error => { this.sendingTestMailInProgress = false; this.sendingTestMailSuccess = false; - this.sendingEmailErrorMessage = this.translateService.instant( - `Error: {{message}} with cause {{cause}}`, - { - message: error.error.localizedMessage, - cause: error.error.cause.localizedMessage, - }, - ); + this.sendingEmailErrorMessage = + this.extractTestMailErrorMessage(error); }, ); } + + private extractTestMailErrorMessage(error: HttpErrorResponse): string { + const notification = error.error?.notifications?.[0]; + const message = [notification?.title, notification?.description] + .filter( + (messagePart, index, messageParts) => + messagePart && messageParts.indexOf(messagePart) === index, + ) + .join('. '); + const cause = + notification?.additionalInformation || + error.error?.localizedMessage || + error.error?.cause?.localizedMessage; + + if (message && cause && message !== cause) { + return `${message} ${cause}`; + } + + return ( + message || + cause || + this.translateService.instant('Could not send test email.') + ); + } } diff --git a/ui/src/scss/sp/sp-theme.scss b/ui/src/scss/sp/sp-theme.scss index 7406dad58f..e2cff563f6 100644 --- a/ui/src/scss/sp/sp-theme.scss +++ b/ui/src/scss/sp/sp-theme.scss @@ -141,6 +141,8 @@ html { .mat-mdc-menu-panel { min-width: 10rem; + width: max-content; + max-width: min(22rem, calc(100vw - 2rem)); border: 1px solid var(--color-border-subtle); overflow: hidden; } @@ -155,6 +157,7 @@ html { box-sizing: border-box; border-radius: var(--radius-xs); margin: 0 var(--space-xs); + white-space: nowrap; transition: background-color var(--motion-duration-fast) var(--motion-easing-standard), @@ -170,6 +173,9 @@ html { } .mat-mdc-menu-item-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; line-height: 1.25rem; }
