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;
     }
 

Reply via email to