This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-26700-about
in repository https://gitbox.apache.org/repos/asf/camel.git

commit dfc49bb1c38e1e5cde1478866a8be16fcd46515c
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 7 16:09:58 2026 +0100

    CAMEL-26700: About - when the worst of a route last happened, and the 
failure a recovery recovered from
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/AboutView.java     | 59 +++++++++++++++++++---
 .../dsl/jbang/core/commands/tui/AboutViewTest.java | 22 ++++++++
 2 files changed, 75 insertions(+), 6 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
index 5eeab4899e27..6609884bb175 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
@@ -318,12 +318,26 @@ final class AboutView {
             }
             long done = r.total(r.completed()) + r.total(r.recovered()) + 
r.total(r.handled()) + r.total(r.failed());
             spans.add(Span.styled("  " + done, Theme.success()));
-            if (r.total(r.failed()) > 0) {
-                spans.add(Span.styled("  " + r.total(r.failed()) + " failed", 
Theme.error()));
-            } else if (r.total(r.handled()) > 0) {
-                spans.add(Span.styled("  " + r.total(r.handled()) + " 
handled", Style.EMPTY.fg(Theme.ACCENT)));
-            } else if (r.total(r.recovered()) > 0) {
-                spans.add(Span.styled("  " + r.total(r.recovered()) + " 
recovered", Theme.warning()));
+            // the worst that happened, when it last did, and for a recovery 
the failure it recovered from (a failed or
+            // handled one has its cause on the Last 5m line and on the 
Timeline)
+            long[] worst = r.total(r.failed()) > 0 ? r.failed() : 
r.total(r.handled()) > 0 ? r.handled()
+                    : r.total(r.recovered()) > 0 ? r.recovered() : null;
+            if (worst != null) {
+                String what = worst == r.failed() ? " failed" : worst == 
r.handled() ? " handled" : " recovered";
+                Style style = worst == r.failed() ? Theme.error()
+                        : worst == r.handled() ? Style.EMPTY.fg(Theme.ACCENT) 
: Theme.warning();
+                long at = lastAt(worst, end, seconds);
+                spans.add(Span.styled("  " + r.total(worst) + what, style));
+                spans.add(Span.styled(" " + ago(last - at), Theme.muted()));
+                String cause = worst == r.recovered() ? recoveredFrom(info, 
r.route(), end - seconds + 1, end) : null;
+                if (cause != null) {
+                    int room = width - label - columns - 40;
+                    if (room > 10) {
+                        spans.add(Span.styled(" · ", Theme.muted()));
+                        spans.add(Span.styled(cause.length() > room ? 
cause.substring(0, room - 1) + "…" : cause,
+                                Theme.warning()));
+                    }
+                }
             }
             lines.add(Line.from(spans));
         }
@@ -336,6 +350,39 @@ final class AboutView {
         return lines;
     }
 
+    /** The last second of the window with a value, or the end. */
+    private static long lastAt(long[] values, long end, int seconds) {
+        for (int i = values.length - 1; i >= 0; i--) {
+            if (values[i] > 0) {
+                return end - seconds + 1 + i;
+            }
+        }
+        return end;
+    }
+
+    private static String ago(long seconds) {
+        return seconds > 0 ? ChaosTab.shortTime(seconds * 1000) + " ago" : 
"now";
+    }
+
+    /**
+     * The failure the last recovered exchange of the route was redelivered 
for, from the exchanges the integration
+     * kept; as a route called by another keeps its exchanges under the 
caller, any recovered exchange of the window
+     * will do.
+     */
+    private String recoveredFrom(IntegrationInfo info, String routeId, long 
from, long to) {
+        String any = null;
+        for (Timeline.Kept k : ctx.timeline.exchanges(info.pid, from, to)) {
+            if (k.redelivered() && !k.failed() && !k.handled() && 
k.exception() != null) {
+                if (routeId.equals(k.routeId())) {
+                    any = k.exception();
+                } else if (any == null) {
+                    any = k.exception();
+                }
+            }
+        }
+        return any;
+    }
+
     private static String meta(IntegrationInfo info, ProjectOverview.Overview 
o) {
         StringBuilder sb = new StringBuilder();
         if (info.camelVersion != null) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
index 7dcf5d658814..c2352c5173c4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
@@ -120,6 +120,28 @@ class AboutViewTest {
         assertThat(about.handleKeyEvent(KeyEvent.ofChar('x'), info)).isFalse();
     }
 
+    @Test
+    void aRecoveryShowsWhenAndTheFailureItRecoveredFrom() {
+        IntegrationInfo info = new IntegrationInfo();
+        info.pid = "1234";
+        for (String id : List.of("orders", "stock")) {
+            RouteInfo r = new RouteInfo();
+            r.routeId = id;
+            info.routes.add(r);
+        }
+        MonitorContext ctx = new MonitorContext(new 
AtomicReference<>(List.of(info)), new AtomicReference<>(List.of()));
+        TimelineJson t = new TimelineJson(T0 / 1000 + 299, 300);
+        for (int i = 0; i < 300; i++) {
+            t.route("orders", "completed", i, 1);
+        }
+        // redelivered in stock 2 minutes ago, kept under orders where it came 
in
+        t.route("stock", "recovered", 179, 1).route("stock", 
"redeliveryAttempts", 179, 1);
+        t.exchange("R1", "orders", 179, 40, "recovered", "Connection refused", 
null);
+        ctx.timeline.merge("1234", t.json(), true);
+        AboutView about = new AboutView(ctx);
+        assertThat(render(about, info)).contains("1 recovered 2m ago · 
Connection refused");
+    }
+
     @Test
     void theSendsOfARouteToAnEndpoint() {
         IntegrationInfo info = new IntegrationInfo();

Reply via email to