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 c9991d6600434b647e83af91a574135e1ec00000 Author: Claus Ibsen <[email protected]> AuthorDate: Sat Nov 7 11:01:52 2026 +0100 CAMEL-26700: About - what happened in the last 5 minutes, a line per route coloured as on the Timeline Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../dsl/jbang/core/commands/tui/AboutView.java | 70 +++++++++++++++++++++- .../dsl/jbang/core/commands/tui/AboutViewTest.java | 3 +- 2 files changed, 71 insertions(+), 2 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 62b9089acde6..c4d7788cc236 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 @@ -146,9 +146,18 @@ final class AboutView { int available = Math.max(3, area.height() - headHeight - strips.size()); int columnHeight = Math.min(columnRows + 2, available); + List<Line> routeStrips = routeStrips(info, width); + int stripsHeight = strips.size(); + int happenedHeight = routeStrips.isEmpty() + ? 0 : Math.max(0, Math.min(routeStrips.size() + 2, area.height() - headHeight - columnHeight - stripsHeight)); List<Rect> rows = Layout.vertical() - .constraints(Constraint.length(headHeight), Constraint.length(columnHeight), Constraint.fill()) + .constraints(Constraint.length(headHeight), Constraint.length(columnHeight), Constraint.length(stripsHeight), + Constraint.length(happenedHeight), Constraint.fill()) .split(area); + if (happenedHeight > 2) { + frame.renderWidget(Paragraph.builder().text(Text.from(routeStrips)) + .block(block(" What happened ─ last 5m, a column is 5s ─ 4 for each second ")).build(), rows.get(3)); + } frame.renderWidget(Paragraph.builder().text(Text.from(head)) .block(block(" " + (info.name != null ? info.name : info.pid) + " ")).build(), rows.get(0)); List<Rect> cols = Layout.horizontal() @@ -160,6 +169,65 @@ final class AboutView { frame.renderWidget(Paragraph.builder().text(Text.from(strips)).build(), rows.get(2)); } + /** + * A line per route over the last 5 minutes, a column of 5 seconds coloured by the worst that happened to its + * exchanges then (failed, handled, recovered, completed), as the Timeline tab colours its seconds. + */ + private List<Line> routeStrips(IntegrationInfo info, int width) { + List<Line> lines = new ArrayList<>(); + long end = ctx.timeline.endSecond(info.pid); + if (end < 0) { + return lines; + } + List<String> order = info.routes.stream().map(r -> r.routeId).filter(id -> id != null).toList(); + // the names, then one space: every line starts its columns at the same place + int label = Math.min(18, Math.max(8, order.stream().mapToInt(String::length).max().orElse(8))) + 4; + int per = 5; + int columns = HAPPENED_SECONDS / per; + for (Timeline.RouteSeries r : ctx.timeline.routes(info.pid, order, end, HAPPENED_SECONDS)) { + List<Span> spans = new ArrayList<>(); + String name = r.route().length() > label - 3 ? r.route().substring(0, label - 4) + "…" : r.route(); + spans.add(Span.raw(" " + name + " ".repeat(Math.max(1, label - 3 - name.length())))); + long max = 1; + long[][] col = new long[columns][5]; + for (int i = 0; i < HAPPENED_SECONDS; i++) { + long[] c = col[i / per]; + c[0] += Math.max(0, r.completed()[i]); + c[1] += Math.max(0, r.recovered()[i]) + Math.max(0, r.attempts()[i]); + c[2] += Math.max(0, r.handled()[i]); + c[3] += Math.max(0, r.failed()[i]); + c[4] += Math.max(0, r.down()[i]); + } + for (long[] c : col) { + max = Math.max(max, c[0] + c[1] + c[2] + c[3]); + } + for (long[] c : col) { + long t = c[0] + c[1] + c[2] + c[3]; + if (c[4] > 0) { + spans.add(Span.styled("░", Theme.error())); + } else if (t == 0) { + spans.add(Span.styled("·", Theme.muted())); + } else { + int level = (int) Math.max(1, Math.min(8, Math.ceil(t * 8.0 / max))); + Style style = c[3] > 0 ? Theme.error() + : c[2] > 0 ? Style.EMPTY.fg(Theme.ACCENT) : c[1] > 0 ? Theme.warning() : Theme.success(); + spans.add(Span.styled(String.valueOf(BLOCKS.charAt(level)), style)); + } + } + long done = r.total(r.completed()) + r.total(r.recovered()) + r.total(r.handled()) + r.total(r.failed()); + spans.add(Span.raw(" " + done)); + 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())); + } + lines.add(Line.from(spans)); + } + return lines; + } + 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 83fa15bbdaa2..044cebd06308 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 @@ -42,7 +42,7 @@ class AboutViewTest { Path dir; private static String render(AboutView about, IntegrationInfo info) { - Rect area = new Rect(0, 0, 160, 30); + Rect area = new Rect(0, 0, 160, 34); Buffer buffer = Buffer.empty(area); about.render(Frame.forTesting(buffer), area, info); return TuiTestHelper.bufferToString(buffer); @@ -131,6 +131,7 @@ class AboutViewTest { .contains("↳ stock").contains(" Out ").contains("localhost:8765/ship").contains("from orders"); assertThat(screen).contains("Last 5m").contains("30 handled") .contains("HttpOperationFailedException 503 ×30").contains("Watch").contains("Next"); + assertThat(screen).contains("What happened").contains("30 handled"); }); } }
