From c87b20e0c269d6f0c94b396ea371b045e17fa378 Mon Sep 17 00:00:00 2001 From: Oleksii Kuripko Date: Tue, 21 Apr 2026 18:41:46 +0200 Subject: [PATCH] linkify the URL in HTTP failure messages as a Slack hyperlink Transforms 'request for ""' into Slack mrkdwn , so the failure line renders the word 'request' as a clickable link instead of showing the raw URL in quotes. --- .../ServiceFailureNotification.java | 17 +++++++++++++++++ .../ServiceFailureNotificationTest.java | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java index 47daf043ab..5fcc953cce 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java @@ -19,6 +19,8 @@ import lombok.Getter; import org.apache.commons.lang3.exception.ExceptionUtils; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Getter public class ServiceFailureNotification implements Notification { @@ -46,9 +48,24 @@ public class ServiceFailureNotification implements Notification { errorMsg = error.getClass().getSimpleName(); } errorMsg = stripResponseBody(errorMsg); + errorMsg = linkifyRequestUrl(errorMsg); return String.format("%s - Failure: %s (number of subsequent failures: %s)", serviceKey, errorMsg, failuresCount); } + private static final Pattern REQUEST_URL_PATTERN = Pattern.compile("request for \"(https?://[^\"\\s]+)\""); + + static String linkifyRequestUrl(String msg) { + if (msg == null) { + return null; + } + Matcher m = REQUEST_URL_PATTERN.matcher(msg); + if (!m.find()) { + return msg; + } + // Slack mrkdwn link: + return m.replaceAll("<$1|request>"); + } + static String stripResponseBody(String msg) { if (msg == null) { return null; diff --git a/monitoring/src/test/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotificationTest.java b/monitoring/src/test/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotificationTest.java index 92c6996d1d..7f66a456fe 100644 --- a/monitoring/src/test/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotificationTest.java +++ b/monitoring/src/test/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotificationTest.java @@ -53,6 +53,25 @@ class ServiceFailureNotificationTest { assertThat(ServiceFailureNotification.stripResponseBody(null)).isNull(); } + @Test + void linkifyReplacesRequestForUrlWithSlackMrkdwnLink() { + String msg = "503 Service Temporarily Unavailable on POST request for \"https://qa-tb-pe-lts43.iot-private.cloud/api/auth/login\""; + + assertThat(ServiceFailureNotification.linkifyRequestUrl(msg)) + .isEqualTo("503 Service Temporarily Unavailable on POST "); + } + + @Test + void linkifyLeavesMessagesWithoutRequestUrlUntouched() { + String msg = "Connection refused"; + assertThat(ServiceFailureNotification.linkifyRequestUrl(msg)).isEqualTo(msg); + } + + @Test + void linkifyHandlesNull() { + assertThat(ServiceFailureNotification.linkifyRequestUrl(null)).isNull(); + } + @Test void shortNameUsesShortNameProviderWhenAvailable() { ShortNameProvider provider = () -> "MQTT";