This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26864-tui-walk-bugs in repository https://gitbox.apache.org/repos/asf/camel.git
commit 99d1391bb7f44c4aec488d7bcc811ee9ca7d2492 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Nov 11 17:46:15 2026 +0100 CAMEL-26864: camel-jbang - Diagram drill-down from the architecture, Inspect diagram follows the history, avg body without hand-offs, MCP keys go to the tab - Diagram: drilling into a route (or selecting one) closes the architecture view, which kept being drawn while the state said route, and left the node boxes of the route drawn before; diagramMode says architecture. - Inspect: the Diagram view draws the exchange of the history when it is read again (tab opened again, F5), so the screen and the table agree. - Endpoints: the avg body chart leaves out the hand-offs between the routes, as the counts do. - tui_send_keys: the keys go to the active tab, not the AI panel's input, and the result says which tab. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 2 +- .../dsl/jbang/core/commands/tui/Confirmations.java | 8 +++ .../dsl/jbang/core/commands/tui/DiagramTab.java | 9 ++- .../dsl/jbang/core/commands/tui/EndpointFlow.java | 19 +++++- .../dsl/jbang/core/commands/tui/HistoryTab.java | 18 ++++++ .../dsl/jbang/core/commands/tui/McpFacade.java | 12 +++- .../jbang/core/commands/tui/MetricsCollector.java | 10 +-- .../core/commands/tui/TuiToolDefinitions.java | 10 +-- .../jbang/core/commands/tui/TuiToolRegistry.java | 4 +- .../src/main/resources/tui/help/endpoints.md | 4 +- .../jbang/core/commands/tui/ConfirmationsTest.java | 33 +++++++++- .../tui/DiagramTabArchitectureNavigationTest.java | 75 ++++++++++++++++++++++ .../jbang/core/commands/tui/EndpointFlowTest.java | 19 +++++- 13 files changed, 197 insertions(+), 26 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java index 204eb57dac71..ca05f124963a 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java @@ -1238,7 +1238,7 @@ public class CamelMonitor extends CamelCommand { if (popupManager.handleKeyEvent(ke, tabRegistry.selectedTabIndex(), TAB_LOG)) { return true; } - if (aiPanel.isOpen()) { + if (aiPanel.isOpen() && !confirmations.isDeliveringMcpKey()) { if (ke.isKey(KeyCode.F8) && ke.hasShift()) { if (lastContentArea != null) { aiPanel.cycleHeight(lastContentArea.height()); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Confirmations.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Confirmations.java index 9076cd41f9bb..c9dde27d35c9 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Confirmations.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/Confirmations.java @@ -44,6 +44,7 @@ final class Confirmations { private final List<Dialog> dialogs = new ArrayList<>(); private final Queue<McpFacade.PendingKey> pendingKeys; private boolean delivering; + private boolean deliveringFromMcp; Confirmations(Queue<McpFacade.PendingKey> pendingKeys) { this.pendingKeys = pendingKeys; @@ -118,10 +119,12 @@ final class Confirmations { continue; } delivering = true; + deliveringFromMcp = key.fromMcp(); try { handler.accept(key.event()); } finally { delivering = false; + deliveringFromMcp = false; } } return taken; @@ -135,4 +138,9 @@ final class Confirmations { boolean isDelivering() { return delivering; } + + /** Whether the key being handled was sent by an MCP client: it is for the active tab, not the AI panel's input. */ + boolean isDeliveringMcpKey() { + return deliveringFromMcp; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java index 44200a466296..5512f2ba7042 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java @@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -799,6 +800,8 @@ class DiagramTab extends AbstractDiagramTab { if (idx < 0) { return false; } + // the route is selected in the topology, so that is the level shown, not the architecture over it + architecture.close(); diagram.setSelectedNodeIndex(idx); diagram.scrollToSelectedNode(); return true; @@ -816,6 +819,9 @@ class DiagramTab extends AbstractDiagramTab { if (info == null || info.routes.stream().noneMatch(r -> routeId.equals(r.routeId))) { return false; } + // the route is shown, not the architecture: while that is active the route is not drawn, and its node + // boxes stay those of the route drawn before (CAMEL-26864) + architecture.close(); routeNavigationStack.clear(); drillDownRouteId = routeId; topologyMode = false; @@ -943,7 +949,8 @@ class DiagramTab extends AbstractDiagramTab { return null; } JsonObject result = new JsonObject(); - result.put("diagramMode", topologyMode ? "topology" : "route"); + // the level the screen shows: the architecture is drawn over the topology or route + result.put("diagramMode", level().name().toLowerCase(Locale.ROOT)); result.put("zoom", zoomStateAsJson()); if (!topologyMode && drillDownRouteId != null) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlow.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlow.java index 486d0fefadc0..3928e7fe11f2 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlow.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlow.java @@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui; import java.util.HashSet; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Stream; /** * The messages that come into an integration and go out of it, for the flow of the Endpoints tab and its rate chart. An @@ -50,10 +51,22 @@ final class EndpointFlow { /** The hits of the endpoints of the direction the filter takes, without the hand-offs between its routes. */ static long total(IntegrationInfo info, String direction, Predicate<EndpointInfo> filter) { + return flow(info, direction).filter(filter).mapToLong(ep -> ep.hits).sum(); + } + + /** + * The largest mean body size of the endpoints of the direction, without the hand-offs between its routes, as the + * counts of the flow (CAMEL-26864: the avg body chart said out 7 B where the flow said 0 out). + */ + static long meanBodySize(IntegrationInfo info, String direction) { + return flow(info, direction).filter(ep -> ep.meanBodySize >= 0).mapToLong(ep -> ep.meanBodySize).max() + .orElse(0); + } + + private static Stream<EndpointInfo> flow(IntegrationInfo info, String direction) { Set<String> handOffs = handOffs(info); return info.endpoints.stream() - .filter(ep -> direction.equals(ep.direction) && filter.test(ep)) - .filter(ep -> ep.uri == null || !handOffs.contains(DiagramTopology.endpointKey(ep.uri))) - .mapToLong(ep -> ep.hits).sum(); + .filter(ep -> direction.equals(ep.direction)) + .filter(ep -> ep.uri == null || !handOffs.contains(DiagramTopology.endpointKey(ep.uri))); } } 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 6345bae4b9dd..6fb7c97f113a 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 @@ -723,6 +723,7 @@ class HistoryTab extends AbstractTab { if (!historyEntries.isEmpty()) { historyTableState.select(0); } + redrawHistoryDiagram(); traceDetailView = false; traceSelectedExchangeId = null; if (!traces.get().isEmpty()) { @@ -740,10 +741,27 @@ class HistoryTab extends AbstractTab { if (historyRefreshRequested || historyFollow) { historyRefreshRequested = false; historyEntries = data.loadHistoryData(pids); + redrawHistoryDiagram(); } data.refreshTraceData(pids); } + /** + * The Diagram view draws the exchange of the history as it was when the view opened: when the history was read + * again and is another exchange now, the diagram is drawn for that one, so the screen and the table agree + * (CAMEL-26864). + */ + private void redrawHistoryDiagram() { + if (!diagram.isShowDiagram() || !traces.get().isEmpty() || historyEntries.isEmpty() + || diagramHistorySteps.isEmpty()) { + return; + } + String shown = diagramHistorySteps.get(0).exchangeId; + if (!Objects.equals(shown, reorderHistoryDepthFirst(historyEntries).get(0).exchangeId)) { + loadDiagramForCurrentView(); + } + } + boolean isTraceDetailView() { return traceDetailView; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java index 486c1750122e..e7b8cb52f2b9 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java @@ -65,7 +65,15 @@ class McpFacade { * Pending key event queued for injection into the TUI event loop, by an MCP client or a tape. It fires on the first * tick at or after fireAt, a {@link MonotonicClock} time. */ - record PendingKey(KeyEvent event, long fireAt) { + /** + * A key sent to the monitor. One from an MCP client ({@code fromMcp}) is for the active tab: the AI panel's input + * takes only what the user types (CAMEL-26864). One from a tape is as the user typed it. + */ + record PendingKey(KeyEvent event, long fireAt, boolean fromMcp) { + + PendingKey(KeyEvent event, long fireAt) { + this(event, fireAt, false); + } /** Whether the key fires at the given {@link MonotonicClock} time. */ boolean isDue(long now) { @@ -662,7 +670,7 @@ class McpFacade { for (String key : keys) { KeyEvent ke = parseKey(key); if (ke != null) { - pendingKeys.add(new PendingKey(ke, fireAt)); + pendingKeys.add(new PendingKey(ke, fireAt, true)); fireAt += delayMs; count++; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsCollector.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsCollector.java index e6838df8cd42..a050ef0cb194 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsCollector.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsCollector.java @@ -268,13 +268,9 @@ class MetricsCollector { endpointRemoteStubSamples, previousEndpointRemoteStubTime, endpointRemoteStubInHistory, endpointRemoteStubOutHistory); - // Record payload size snapshots (mean body size per direction) - long inMeanSize = info.endpoints.stream() - .filter(ep -> "in".equals(ep.direction) && ep.meanBodySize >= 0) - .mapToLong(ep -> ep.meanBodySize).max().orElse(0); - long outMeanSize = info.endpoints.stream() - .filter(ep -> "out".equals(ep.direction) && ep.meanBodySize >= 0) - .mapToLong(ep -> ep.meanBodySize).max().orElse(0); + // Record payload size snapshots (mean body size per direction), of what comes in and goes out as above + long inMeanSize = EndpointFlow.meanBodySize(info, "in"); + long outMeanSize = EndpointFlow.meanBodySize(info, "out"); Long lastSizeTime = previousEndpointSizeTime.get(pid); if (lastSizeTime == null || now - lastSizeTime >= 1000) { previousEndpointSizeTime.put(pid, now); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolDefinitions.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolDefinitions.java index 965dc9246727..a61e49fb9b7e 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolDefinitions.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolDefinitions.java @@ -140,11 +140,11 @@ final class TuiToolDefinitions { tools.add(tool(toolDef( "tui_send_keys", - "Sends key presses to the TUI. A human is watching the screen, so keys should be paced naturally " - + "like a skilled user would type. Key names: Enter, Esc, Tab, Backspace, Delete, Up, " - + "Down, Left, Right, Home, End, PgUp, PgDn, Space, F1-F12, or any single character. " - + "Modifiers: Ctrl+x, Shift+x, Ctrl+Shift+x. Keys never answer a dialog that asks the " - + "user to confirm an action (quit, a file write): only the user does.", + "Sends key presses to the active tab (not the AI panel's input). A human is watching, so pace " + + "them as a skilled user types. Key names: Enter, Esc, Tab, Backspace, Delete, Up, Down, " + + "Left, Right, Home, End, PgUp, PgDn, Space, F1-F12, or any single character. Modifiers: " + + "Ctrl+x, Shift+x, Ctrl+Shift+x. Keys never answer a dialog that asks the user to " + + "confirm an action (quit, a file write): only the user does.", Map.of("keys", propDef("array", "Array of key name strings to send"), "delay", propDef("integer", "Delay in milliseconds between keys (default 150, minimum 80)"), diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java index 2761f41ef244..adb18f905e87 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java @@ -537,7 +537,8 @@ class TuiToolRegistry { int sent = facade.injectKeys(keys, delay); if (!wait) { - return "Queued " + sent + " key(s) with " + delay + "ms delay"; + // for the active tab: the AI panel's input, when it is open, takes only what the user types + return "Queued " + sent + " key(s) with " + delay + "ms delay for the " + facade.getActiveTabName() + " tab"; } long lastKeyFireAt = MonotonicClock.millis() + (long) (sent - 1) * delay; @@ -563,6 +564,7 @@ class TuiToolRegistry { Buffer buf = facade.getLastBuffer(); JsonObject result = new JsonObject(); result.put("sent", sent); + result.put("activeTab", facade.getActiveTabName()); result.put("delay", delay); result.put("waitedMs", MonotonicClock.millis() - start); if (buf != null) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md index 430a77ef4600..b9d9d7f390f6 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md @@ -49,8 +49,8 @@ The left box lists all inbound components (consumers), the right box lists all outbound components (producers). The integration name sits in the middle, showing the overall message flow direction. -The numbers, and the in and out of the throughput chart, are the -messages that come into the integration and go out of it. An endpoint +The numbers, the in and out of the throughput chart, and the avg body +chart are of the messages that come into the integration and go out of it. An endpoint one of its routes sends to and another consumes (`direct`, `seda`) is a hand-off between its routes and is left out: it would count each message twice, as an out and an in, and say it left. The table still diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfirmationsTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfirmationsTest.java index 6b81a39fd80f..ad0c58fd8edf 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfirmationsTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfirmationsTest.java @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicReference; import dev.tamboui.tui.event.KeyCode; import dev.tamboui.tui.event.KeyEvent; import dev.tamboui.tui.event.KeyModifiers; +import dev.tamboui.widgets.tabs.TabsState; import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -63,6 +64,31 @@ class ConfirmationsTest { assertThat(confirmations.isDelivering()).isFalse(); } + @Test + void aKeyOfAnMcpClientIsForTheActiveTabNotTheAiPanelInput() { + // CAMEL-26864: tui_send_keys sent Enter while the AI panel was open, and the panel's input took it + pending.add(new McpFacade.PendingKey(KeyEvent.ofChar('a'), 0, true)); + pending.add(due(KeyEvent.ofChar('b'))); + List<String> fromMcp = new ArrayList<>(); + + confirmations.deliverDue(0, key -> { + if (confirmations.isDeliveringMcpKey()) { + fromMcp.add(key.string()); + } + }); + + // a tape plays the keys as the user typed them, into the panel too + assertThat(fromMcp).containsExactly("a"); + assertThat(confirmations.isDeliveringMcpKey()).isFalse(); + + // tui_send_keys marks its keys + McpFacade facade = new McpFacade( + new MonitorContext(new AtomicReference<>(List.of()), new AtomicReference<>(List.of())), + new AtomicReference<>(List.of()), null, null, null, null, null, null, null, null, null, pending, null); + assertThat(facade.injectKeys(List.of("Enter"), 100)).isEqualTo(1); + assertThat(pending.poll().fromMcp()).isTrue(); + } + @Test void aKeyThatComesWhileADialogAsksToConfirmIsDropped() { // q opens the quit confirmation; the Enter queued right behind it must not answer it @@ -128,8 +154,8 @@ class ConfirmationsTest { AtomicBoolean chaosConfirm = new AtomicBoolean(true); Confirmations chaos = new Confirmations(pending).add("a confirm dialog of the tab", chaosConfirm::get, () -> true); McpFacade facade = new McpFacade( - null, new AtomicReference<>(List.of()), null, new RecordingManager(null), null, null, null, null, null, - null, null, pending, null); + null, new AtomicReference<>(List.of()), new TabsState(0), new RecordingManager(null), null, null, null, + null, null, null, null, pending, null); facade.setConfirmations(chaos); TuiToolRegistry tools = new TuiToolRegistry(facade); @@ -137,7 +163,8 @@ class ConfirmationsTest { .startsWith("Not sent: a confirm dialog of the tab is open").endsWith("(a lone Esc cancels it)."); assertThat(tools.callSendKeys(Map.of("keys", List.of("Esc", "y")))).startsWith("Not sent"); assertThat(pending).isEmpty(); - assertThat(tools.callSendKeys(Map.of("keys", List.of("Esc")))).startsWith("Queued 1 key"); + assertThat(tools.callSendKeys(Map.of("keys", List.of("Esc")))).startsWith("Queued 1 key") + .endsWith("for the Overview tab"); assertThat(pending).hasSize(1); } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTabArchitectureNavigationTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTabArchitectureNavigationTest.java new file mode 100644 index 000000000000..1eb33c99bfa2 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTabArchitectureNavigationTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.commands.tui; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.camel.diagram.TopologyLayoutEngine.TopologyNodeInfo; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * CAMEL-26864: tui_navigate drilled into a route while the Diagram tab showed the architecture. The state said route, + * the screen still drew the architecture, and the route's node boxes stayed those of the route drawn before. + */ +class DiagramTabArchitectureNavigationTest { + + @Test + void goingToARouteOrSelectingOneLeavesTheArchitecture() { + IntegrationInfo info = new IntegrationInfo(); + info.pid = "1234"; + info.name = "hello"; + info.routes.add(route("hello")); + info.routes.add(route("seven")); + MonitorContext ctx = new MonitorContext(new AtomicReference<>(List.of(info)), new AtomicReference<>(List.of())); + ctx.selectedPid = "1234"; + DiagramTab tab = new DiagramTab(ctx); + // the topology lists the routes in another order than the integration + tab.diagram.showCustomTopology(List.of(node("seven"), node("hello")), List.of(), Map.of(), null); + + tab.goToLevel(DiagramTab.Level.ARCHITECTURE); + assertThat(tab.level()).isEqualTo(DiagramTab.Level.ARCHITECTURE); + assertThat(tab.getDiagramStateAsJson().getString("diagramMode")).as("what the screen shows") + .isEqualTo("architecture"); + + assertThat(tab.selectNode("hello", null)).isTrue(); + assertThat(tab.level()).isEqualTo(DiagramTab.Level.ROUTE); + assertThat(tab.getDiagramStateAsJson().getString("diagramMode")).isEqualTo("route"); + assertThat(tab.getDiagramStateAsJson().getString("routeId")).isEqualTo("hello"); + + tab.goToLevel(DiagramTab.Level.ARCHITECTURE); + assertThat(tab.selectRoute("seven")).isTrue(); + assertThat(tab.level()).isEqualTo(DiagramTab.Level.TOPOLOGY); + } + + private static RouteInfo route(String id) { + RouteInfo r = new RouteInfo(); + r.routeId = id; + return r; + } + + private static TopologyNodeInfo node(String id) { + TopologyNodeInfo n = new TopologyNodeInfo(); + n.routeId = id; + n.from = ""; + n.nodeType = "route"; + return n; + } +} diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlowTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlowTest.java index 354be3b0e870..84bfe9fcafa5 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlowTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointFlowTest.java @@ -43,12 +43,29 @@ class EndpointFlowTest { assertThat(EndpointFlow.total(info, "out", ep -> true)).isEqualTo(5); } - private static void add(IntegrationInfo info, String routeId, String direction, String uri, long hits) { + @Test + void theAvgBodyIsOfWhatComesInAndGoesOutAsTheCounts() { + // CAMEL-26864: hello sends to direct:seven, which route seven consumes; the flow said 0 out, the avg body 7 B + IntegrationInfo info = new IntegrationInfo(); + add(info, "hello", "in", "timer://tick?period=2000", 123).meanBodySize = 0; + add(info, "hello", "out", "direct://seven", 26).meanBodySize = 7; + add(info, "seven", "in", "direct://seven", 26).meanBodySize = 7; + + assertThat(EndpointFlow.total(info, "out", ep -> true)).isZero(); + assertThat(EndpointFlow.meanBodySize(info, "in")).isZero(); + assertThat(EndpointFlow.meanBodySize(info, "out")).isZero(); + + add(info, "seven", "out", "kafka:lucky", 26).meanBodySize = 8; + assertThat(EndpointFlow.meanBodySize(info, "out")).isEqualTo(8); + } + + private static EndpointInfo add(IntegrationInfo info, String routeId, String direction, String uri, long hits) { EndpointInfo ep = new EndpointInfo(); ep.routeId = routeId; ep.direction = direction; ep.uri = uri; ep.hits = hits; info.endpoints.add(ep); + return ep; } }
