This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new dbbbde21ac42 CAMEL-26864: camel-jbang - Consumers has the total of an
event-driven consumer, and the Info panel of Inspect says what a step is and
changed (#29658)
dbbbde21ac42 is described below
commit dbbbde21ac4234e94d25aa71dcebdccb5dcecdb0
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Nov 12 10:03:02 2026 +0100
CAMEL-26864: camel-jbang - Consumers has the total of an event-driven
consumer, and the Info panel of Inspect says what a step is and changed (#29658)
* CAMEL-26864: camel-jbang - Consumers has the total of an event-driven
consumer, and the Info panel of Inspect says what a step is and changed
- Consumers: a TOTAL column with the messages each consumer received (the
exchanges of its route), also for direct and seda, which have no POLLS
- Inspect diagram Info panel: message in / message out / to a remote
endpoint instead of the marker *->, and Changed in words: (first step),
nothing, or body, headers
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* CAMEL-26864: camel-jbang - stop at the route of the consumer once found
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---------
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../dsl/jbang/core/commands/tui/ConsumerInfo.java | 2 ++
.../dsl/jbang/core/commands/tui/ConsumersTab.java | 11 ++++--
.../dsl/jbang/core/commands/tui/HistoryTab.java | 41 +++++++++++++++++-----
.../dsl/jbang/core/commands/tui/StatusParser.java | 6 ++++
.../src/main/resources/tui/help/consumers.md | 11 +++---
.../core/commands/tui/ConsumersTabRenderTest.java | 28 +++++++++++++++
.../core/commands/tui/HistoryTabRenderTest.java | 15 ++++++++
7 files changed, 99 insertions(+), 15 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumerInfo.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumerInfo.java
index 403c83db4ed8..89290f9374ca 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumerInfo.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumerInfo.java
@@ -28,6 +28,8 @@ class ConsumerInfo {
int inflight;
Boolean polling;
Long totalCounter;
+ // the messages its route received (the exchanges of the route), also for
an event-driven consumer (CAMEL-26864)
+ long total = -1;
Long delay;
Long period;
String schedule;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
index ab0e5a153642..d7985be91af8 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
@@ -42,7 +42,7 @@ import static
org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.*;
class ConsumersTab extends AbstractTableTab {
ConsumersTab(MonitorContext ctx) {
- super(ctx, "id", "status", "type", "remote", "inflight", "polls",
"uri");
+ super(ctx, "id", "status", "type", "remote", "inflight", "polls",
"total", "uri");
}
@Override
@@ -82,7 +82,7 @@ class ConsumersTab extends AbstractTableTab {
// the URI gets the rest of the width: the borders, ">> ", 8 gaps
and the other columns; a long id or URI
// ends with an ellipsis where it is cut
- int uriWidth = Math.max(0, area.width() - 2 - 3 - 9 - (20 + 10 +
16 + 8 + 8 + 22 + 8 + 10 + 10));
+ int uriWidth = Math.max(0, area.width() - 2 - 3 - 10 - (20 + 10 +
16 + 8 + 8 + 22 + 8 + 8 + 10 + 10));
rows.add(Row.from(
Cell.from(Span.styled(" " + TuiHelper.truncate(ci.id, 19),
Style.EMPTY.fg(Theme.accent()))),
Cell.from(Span.styled(statusText, statusStyle)),
@@ -91,6 +91,7 @@ class ConsumersTab extends AbstractTableTab {
rightCell(String.valueOf(ci.inflight), 8),
Cell.from(schedule),
rightCell(ci.totalCounter != null ?
String.valueOf(ci.totalCounter) : "", 8),
+ rightCell(ci.total >= 0 ? String.valueOf(ci.total) : "",
8),
rightCell(last, 10),
lastFail.isEmpty() ? rightCell("", 10) :
rightCell(lastFail, 10, Theme.error()),
Cell.from(Span.styled(TuiHelper.truncate(uri, uriWidth),
healthDown ? Theme.error() : Style.EMPTY))));
@@ -110,6 +111,7 @@ class ConsumersTab extends AbstractTableTab {
rightCell(sortLabel("INFLIGHT", "inflight"), 8,
sortStyle("inflight")),
Cell.from(Span.styled("SCHEDULE", Style.EMPTY.bold())),
rightCell(sortLabel("POLLS", "polls"), 8,
sortStyle("polls")),
+ rightCell(sortLabel("TOTAL", "total"), 8,
sortStyle("total")),
rightCell("LAST", 10, Style.EMPTY.bold()),
rightCell("LAST FAIL", 10, Style.EMPTY.bold()),
Cell.from(Span.styled(sortLabel("URI", "uri"),
sortStyle("uri")))))
@@ -121,6 +123,7 @@ class ConsumersTab extends AbstractTableTab {
Constraint.length(8),
Constraint.length(22),
Constraint.length(8),
+ Constraint.length(8),
Constraint.length(10),
Constraint.length(10),
Constraint.fill())
@@ -154,6 +157,7 @@ class ConsumersTab extends AbstractTableTab {
long lb = b.totalCounter != null ? b.totalCounter : 0;
yield Long.compare(lb, la);
}
+ case "total" -> Long.compare(b.total, a.total);
case "uri" -> {
String ua = a.uri != null ? a.uri : "";
String ub = b.uri != null ? b.uri : "";
@@ -356,6 +360,9 @@ class ConsumersTab extends AbstractTableTab {
if (ci.totalCounter != null) {
row.put("totalCounter", ci.totalCounter);
}
+ if (ci.total >= 0) {
+ row.put("total", ci.total);
+ }
if (ci.polling != null) {
row.put("polling", ci.polling);
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
index 829332824858..c19fa55c5ad7 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicReference;
import dev.tamboui.layout.Alignment;
@@ -1008,7 +1009,7 @@ class HistoryTab extends AbstractTab {
routeId = e.routeId;
nodeId = e.nodeId;
processor = e.processor;
- direction = e.direction;
+ direction = stepKind(e.first, e.last, e.remoteEndpoint,
e.stubEndpoint);
timestamp = e.timestamp;
threadName = e.threadName;
elapsed = e.elapsed;
@@ -1032,7 +1033,7 @@ class HistoryTab extends AbstractTab {
routeId = e.routeId;
nodeId = e.nodeId;
processor = e.processor;
- direction = e.direction;
+ direction = stepKind(e.first, e.last, e.remoteEndpoint,
e.stubEndpoint);
timestamp = e.timestamp;
threadName = e.threadName;
elapsed = e.elapsed;
@@ -1118,12 +1119,7 @@ class HistoryTab extends AbstractTab {
boolean propsChanged = changes.length() > 2 && changes.charAt(2) ==
'P';
boolean varsChanged = changes.length() > 3 && changes.charAt(3) == 'V';
- List<Span> changeSpans = new ArrayList<>();
- changeSpans.add(Span.styled(" Changed: ", Theme.muted()));
- if (!changes.isBlank()) {
- changeSpans.addAll(buildChangeSpans(changes));
- }
- lines.add(Line.from(changeSpans));
+ lines.add(Line.from(Span.styled(" Changed: ", Theme.muted()),
changedWords(changes, stepIdx == 0)));
boolean isTraceMode = !diagramTraceSteps.isEmpty();
boolean showBody = isTraceMode ? showTraceBody : showHistoryBody;
@@ -1958,6 +1954,35 @@ class HistoryTab extends AbstractTab {
rightCell(elapsedStr, 10));
}
+ /**
+ * What a step is, in words, for the Info panel; the table has the markers
of camel get history ({@code *->},
+ * {@code <-*}, {@code --->}, {@code ~-->}) in a column of its own. Empty
for a step inside the route.
+ */
+ static String stepKind(boolean first, boolean last, boolean remote,
boolean stub) {
+ String where = stub ? " (stub)" : remote ? " (remote)" : "";
+ if (first) {
+ return "message in" + where;
+ } else if (last) {
+ return "message out" + where;
+ }
+ return stub ? "to a stub" : remote ? "to a remote endpoint" : "";
+ }
+
+ /** What the step changed, in words (the table has the letters B, H, P and
V): "body, headers", or nothing. */
+ static Span changedWords(String changes, boolean firstStep) {
+ if (firstStep) {
+ return Span.styled("(first step)", Style.EMPTY.dim());
+ }
+ String[] words = { "body", "headers", "properties", "variables" };
+ StringJoiner sj = new StringJoiner(", ");
+ for (int i = 0; i < Math.min(changes.length(), words.length); i++) {
+ if (changes.charAt(i) != ' ') {
+ sj.add(words[i]);
+ }
+ }
+ return sj.length() > 0 ? Span.styled(sj.toString(), Theme.change()) :
Span.styled("nothing", Style.EMPTY.dim());
+ }
+
private static List<Span> buildChangeSpans(String changes) {
if (changes == null || changes.isEmpty()) {
return List.of(Span.raw(""));
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index b549c4d05c8b..a306928a7679 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -478,6 +478,12 @@ final class StatusParser {
ci.inflight = cj.getIntegerOrDefault("inflight", 0);
ci.polling = Boolean.TRUE.equals(cj.get("polling"));
ci.totalCounter = cj.getLong("totalCounter");
+ for (RouteInfo r : info.routes) {
+ if (r.routeId != null && r.routeId.equals(ci.id)) {
+ ci.total = r.total;
+ break;
+ }
+ }
ci.delay = cj.getLong("delay");
ci.period = cj.getLong("period");
JsonObject cStats = (JsonObject) cj.get("statistics");
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/consumers.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/consumers.md
index 34bf23e72b4c..e59e3c30dee3 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/consumers.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/consumers.md
@@ -17,6 +17,7 @@ There are two types of consumers:
- **STATUS** — Consumer state: `Started` (running normally), `Polling`
(currently in a poll cycle). Shows a health warning icon if the consumer health
check is `DOWN`
- **TYPE** — The Camel component type (e.g., `kafka`, `file`, `timer`, `cron`,
`seda`)
- **POLLS** — Total number of poll cycles the scheduler has executed. This
counts poll **attempts**, not messages received — a poll may check for new
files and find none. For event-driven consumers (like `seda`), this column is
empty
+- **TOTAL** — The messages the consumer received: the exchanges of its route.
It is there for every consumer, also an event-driven one (`direct`, `seda`)
that has no POLLS
- **SCHEDULE** — How often the consumer polls: a fixed interval like `1s` or
`5000ms`, or a cron expression like `0 0/5 * * *`. Only shown for polling
consumers
- **LAST** — How long ago the consumer's last exchange completed (or started,
when none has completed yet). A consumer that should be busy but has a long
LAST is stale
- **LAST FAIL** — How long ago its last exchange failed, in red; blank when
none did
@@ -25,13 +26,13 @@ There are two types of consumers:
## Example Screen
```
- ROUTE STATUS TYPE POLLS SCHEDULE LAST LAST FAIL URI
- timer-to-log Started timer 17 2s 1s ago
timer://hello?period=2000
- timer-to-seda Started timer 12 3s 0s ago 5m ago
timer://pump?period=3000
- seda-consumer Started seda 0s ago
seda://queue
+ ROUTE STATUS TYPE REMOTE INFLIGHT SCHEDULE POLLS TOTAL
LAST LAST FAIL URI
+ timer-to-log Started timer 0 2s 17 17 1s
ago timer://hello?period=2000
+ timer-to-seda Started timer 0 3s 12 12 0s
ago 5m ago timer://pump?period=3000
+ seda-consumer Started seda 0 12 0s
ago seda://queue
```
-Notice that `seda-consumer` has no POLLS or SCHEDULE because it is
+Notice that `seda-consumer` has no POLLS or SCHEDULE, only a TOTAL, because it
is
event-driven — it reacts immediately when a message is put into the
SEDA queue by another route.
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTabRenderTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTabRenderTest.java
index 5f1273f0c2e2..68d492f63336 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTabRenderTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTabRenderTest.java
@@ -27,6 +27,9 @@ import dev.tamboui.terminal.Frame;
import dev.tamboui.text.Span;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.tui.event.KeyModifiers;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -243,6 +246,31 @@ class ConsumersTabRenderTest {
assertThat(ConsumersTab.ago("-")).isEmpty();
}
+ @Test
+ void anEventDrivenConsumerHasTheTotalOfItsRoute() throws Exception {
+ // CAMEL-26864: the direct consumer of route seven received 200
messages, but had no POLLS and no count at all
+ JsonObject root = (JsonObject) Jsoner.deserialize("""
+ {"context": {"name": "hello"},
+ "routes": [{"routeId": "seven", "statistics":
{"exchangesTotal": 200}},
+ {"routeId": "hello", "statistics":
{"exchangesTotal": 1131}}],
+ "consumers": {"consumers": [
+ {"id": "seven", "uri": "direct://seven", "state":
"Started"},
+ {"id": "hello", "uri": "timer://tick", "state": "Started",
"totalCounter": 1131}]}}
+ """);
+ IntegrationInfo parsed =
StatusParser.parseIntegration(ProcessHandle.current(), root);
+ info.consumers.addAll(parsed.consumers);
+
+ ConsumersTab tab = new ConsumersTab(ctx);
+ String rendered = TuiTestHelper.renderToString(tab, 200, 10);
+ assertThat(rendered).contains("TOTAL");
+ String seven = rendered.lines().filter(l ->
l.contains("direct://seven")).findFirst().orElseThrow();
+ assertThat(seven).contains(" 200 ");
+ JsonObject row = (JsonObject) ((JsonArray)
tab.getTableDataAsJson().get("rows")).get(1);
+ assertThat(row.getString("id")).isEqualTo("seven");
+ assertThat(row.getLong("total")).isEqualTo(200L);
+ assertThat(row.containsKey("totalCounter")).as("a direct consumer does
not poll").isFalse();
+ }
+
private ConsumerInfo addConsumer(String id, String uri, String state,
String className) {
ConsumerInfo ci = new ConsumerInfo();
ci.id = id;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTabRenderTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTabRenderTest.java
index a034808f2ec0..908937d8ff35 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTabRenderTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTabRenderTest.java
@@ -328,4 +328,19 @@ class HistoryTabRenderTest {
private static HistoryTab.WaterfallStep step(String id, boolean first,
boolean last, long elapsed, long start) {
return new HistoryTab.WaterfallStep(id, id, "-->", first, last, 0,
elapsed, 0, start);
}
+
+ @Test
+ void theInfoPanelSaysWhatAStepIsAndWhatItChangedInWords() {
+ // CAMEL-26864: it showed the marker of the table ("Step: 1/6 *->")
and an empty "Changed:"
+ assertEquals("message in", HistoryTab.stepKind(true, false, false,
false));
+ assertEquals("message in (remote)", HistoryTab.stepKind(true, false,
true, false));
+ assertEquals("message out", HistoryTab.stepKind(false, true, false,
false));
+ assertEquals("to a remote endpoint", HistoryTab.stepKind(false, false,
true, false));
+ assertEquals("to a stub", HistoryTab.stepKind(false, false, true,
true));
+ assertEquals("", HistoryTab.stepKind(false, false, false, false));
+
+ assertEquals("(first step)", HistoryTab.changedWords("",
true).content());
+ assertEquals("nothing", HistoryTab.changedWords(" ",
false).content());
+ assertEquals("body, properties", HistoryTab.changedWords("B P ",
false).content());
+ }
}