From 43e87d458887b85c670ddb490900c823fd5015fc Mon Sep 17 00:00:00 2001 From: Oleksii Kuripko Date: Tue, 21 Apr 2026 18:45:44 +0200 Subject: [PATCH] preserve failure count on service recovery in incident header Recovered services are now shown as ':large_green_circle: ()' with the last failure count that was observed before the service recovered, matching the red-circle format. --- .../notification/incident/IncidentManager.java | 13 +++++++------ .../notification/incident/IncidentManagerTest.java | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/notification/incident/IncidentManager.java b/monitoring/src/main/java/org/thingsboard/monitoring/notification/incident/IncidentManager.java index 133d665f4c..1e52c37634 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/notification/incident/IncidentManager.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/notification/incident/IncidentManager.java @@ -51,7 +51,7 @@ public class IncidentManager { private Instant incidentStartTime; private Instant lastAlertTime; private final Map failingServices = new LinkedHashMap<>(); - private final Set recoveredServices = new LinkedHashSet<>(); + private final Map recoveredServices = new LinkedHashMap<>(); private final Set highLatencyServices = new LinkedHashSet<>(); public IncidentManager(IncidentTransport transport, long resolutionTimeoutSeconds, @@ -110,13 +110,14 @@ public class IncidentManager { if (prev == null || prev.intValue() != service.failureCount()) { changed = true; } - if (recoveredServices.remove(name)) { + if (recoveredServices.remove(name) != null) { changed = true; } } case RECOVERED -> { - if (failingServices.remove(name) != null) { - recoveredServices.add(name); + Integer lastFailureCount = failingServices.remove(name); + if (lastFailureCount != null) { + recoveredServices.put(name, lastFailureCount); changed = true; } } @@ -257,9 +258,9 @@ public class IncidentManager { sb.append(":large_yellow_circle: ").append(name); first = false; } - for (String name : recoveredServices) { + for (Map.Entry entry : recoveredServices.entrySet()) { if (!first) sb.append(", "); - sb.append(":large_green_circle: ").append(name); + sb.append(":large_green_circle: ").append(entry.getKey()).append(" (").append(entry.getValue()).append(")"); first = false; } return sb.toString(); diff --git a/monitoring/src/test/java/org/thingsboard/monitoring/notification/incident/IncidentManagerTest.java b/monitoring/src/test/java/org/thingsboard/monitoring/notification/incident/IncidentManagerTest.java index 81202efe25..afcd20adf3 100644 --- a/monitoring/src/test/java/org/thingsboard/monitoring/notification/incident/IncidentManagerTest.java +++ b/monitoring/src/test/java/org/thingsboard/monitoring/notification/incident/IncidentManagerTest.java @@ -85,13 +85,13 @@ class IncidentManagerTest { } @Test - void recoveryAfterFailureMovesServiceToGreenAndUpdatesHeader() { - manager.sendAlert("CoAP failure", List.of(AffectedService.failing("CoAP", 1))); + void recoveryAfterFailureMovesServiceToGreenAndKeepsFailureCount() { + manager.sendAlert("CoAP failure", List.of(AffectedService.failing("CoAP", 4))); manager.sendAlert("CoAP is OK", List.of(AffectedService.recovered("CoAP"))); assertThat(transport.updates).hasSize(1); String updated = transport.updates.get(0).text(); - assertThat(updated).contains(":large_green_circle: CoAP").doesNotContain(":red_circle:"); + assertThat(updated).contains(":large_green_circle: CoAP (4)").doesNotContain(":red_circle:"); } @Test @@ -126,7 +126,7 @@ class IncidentManagerTest { .asString() .contains(":white_check_mark:") .contains(":red_circle: WS Connect") - .contains(":large_green_circle: Login"); + .contains(":large_green_circle: Login (1)"); } @Test