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 be552fe5ad11523c5ec7c89998dec2150c746b2a Author: Claus Ibsen <[email protected]> AuthorDate: Sat Nov 7 16:15:50 2026 +0100 CAMEL-26700: About - a cursor on In, Routes and Out, and Enter opens a route on the Diagram or an endpoint on the Endpoint tab Up/down move over the lines of In, Routes and Out that are about a route or an endpoint. Enter on a route opens it drilled down on the Diagram (once the diagram has loaded); on an endpoint a route sends to, the Endpoint tab with it selected and its details. Without a cursor, Enter does as before. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../dsl/jbang/core/commands/tui/AboutView.java | 105 +++++++++++++++++++-- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 3 + .../dsl/jbang/core/commands/tui/DiagramTab.java | 23 +++++ .../dsl/jbang/core/commands/tui/EndpointsTab.java | 28 ++++++ .../jbang/core/commands/tui/MonitorContext.java | 2 + .../dsl/jbang/core/commands/tui/OverviewTab.java | 15 +++ .../dsl/jbang/core/commands/tui/TabRegistry.java | 9 ++ .../src/main/resources/tui/help/overview.md | 5 +- .../dsl/jbang/core/commands/tui/AboutViewTest.java | 18 ++++ .../core/commands/tui/EndpointsTabRenderTest.java | 14 +++ 10 files changed, 214 insertions(+), 8 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 6609884bb175..cd90522c73bd 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 @@ -78,6 +78,19 @@ final class AboutView { // -1 follows the last second recorded private int zoom = TimelineTab.WINDOWS.length - 1; private long stripsEnd = -1; + // the line of In, Routes or Out the cursor is on (↑ ↓), in the order of the columns; -1 when none is + private int cursor = -1; + private volatile List<Target> targets = List.of(); + + /** + * What a line of In, Routes or Out is about, for Enter: a route (opened on the Diagram) or an endpoint a route + * sends to (opened on the Endpoint tab). + * + * @param routeId the route + * @param endpoint the URI of the endpoint as the sources have it, or null for a route + */ + record Target(String routeId, String endpoint) { + } AboutView(MonitorContext ctx) { this.ctx = ctx; @@ -132,6 +145,9 @@ final class AboutView { if (info == null) { return false; } + if (ke.isConfirm()) { + return open(); + } long end = ctx.timeline.endSecond(info.pid); if (ke.isChar('+') || ke.isChar('=')) { if (zoom > 0 && end >= 0) { @@ -161,6 +177,34 @@ final class AboutView { return false; } + /** ↑ ↓: the cursor moves over the lines of In, Routes and Out that are about a route or an endpoint. */ + void moveCursor(int delta) { + int n = targets.size(); + if (n == 0) { + cursor = -1; + } else if (cursor < 0) { + cursor = delta > 0 ? 0 : n - 1; + } else { + cursor = Math.max(0, Math.min(n - 1, cursor + delta)); + } + } + + /** The line the cursor is on, or null. */ + Target selected() { + List<Target> t = targets; + return cursor >= 0 && cursor < t.size() ? t.get(cursor) : null; + } + + /** Enter: opens what the cursor is on; false when it is on nothing. */ + boolean open() { + Target t = selected(); + if (t == null || ctx.openFromAbout == null) { + return false; + } + ctx.openFromAbout.accept(t); + return true; + } + private int seconds() { return TimelineTab.WINDOWS[zoom]; } @@ -219,9 +263,26 @@ final class AboutView { List<Line> in = new ArrayList<>(); List<Line> routes = new ArrayList<>(); List<Line> out = new ArrayList<>(); + List<Target> inTargets = new ArrayList<>(); + List<Target> routeTargets = new ArrayList<>(); + List<Target> outTargets = new ArrayList<>(); if (o != null) { - columns(o, info, in, routes, out); + columns(o, info, in, routes, out, inTargets, routeTargets, outTargets); + } + // the cursor goes down In, then Routes, then Out + List<Target> all = new ArrayList<>(); + for (List<Target> column : List.of(inTargets, routeTargets, outTargets)) { + for (Target t : column) { + if (t != null) { + all.add(t); + } + } + } + targets = all; + if (cursor >= all.size()) { + cursor = all.size() - 1; } + highlight(in, inTargets, routes, routeTargets, out, outTargets); int columnRows = Math.max(1, Math.max(in.size(), Math.max(routes.size(), out.size()))); List<Line> strips = strips(info, width); int headHeight = head.size() + 2; @@ -414,8 +475,31 @@ final class AboutView { // ---- In, Routes, Out ---- + /** The line the cursor is on, in the selection colour. */ + private void highlight( + List<Line> in, List<Target> inTargets, List<Line> routes, List<Target> routeTargets, List<Line> out, + List<Target> outTargets) { + int n = 0; + List<List<Line>> columns = List.of(in, routes, out); + List<List<Target>> columnTargets = List.of(inTargets, routeTargets, outTargets); + for (int c = 0; c < columns.size(); c++) { + List<Target> t = columnTargets.get(c); + for (int i = 0; i < t.size(); i++) { + if (t.get(i) != null && n++ == cursor) { + List<Span> spans = new ArrayList<>(); + for (Span s : columns.get(c).get(i).spans()) { + spans.add(Span.styled(s.content(), s.style().patch(Theme.selectionBg()))); + } + columns.get(c).set(i, Line.from(spans)); + return; + } + } + } + } + private static void columns( - ProjectOverview.Overview o, IntegrationInfo info, List<Line> in, List<Line> routes, List<Line> out) { + ProjectOverview.Overview o, IntegrationInfo info, List<Line> in, List<Line> routes, List<Line> out, + List<Target> inTargets, List<Target> routeTargets, List<Target> outTargets) { Map<String, Long> totals = new LinkedHashMap<>(); for (RouteInfo r : info.routes) { if (r.routeId != null) { @@ -429,9 +513,11 @@ final class AboutView { ? e.label().substring(0, e.label().indexOf(':')) : e.kind(); in.add(Line.from(Span.raw(" " + icon(kind) + " "), Span.styled(e.label(), Style.EMPTY.bold()), Span.styled(total != null ? " " + total : "", Theme.success()))); + inTargets.add(e.route() != null ? new Target(e.route(), null) : null); } if (in.isEmpty()) { in.add(Line.from(Span.styled(" nothing from outside", Theme.muted()))); + inTargets.add(null); } // the routes that something else in the integration calls go under their caller @@ -446,13 +532,13 @@ final class AboutView { Set<String> shown = new LinkedHashSet<>(); for (ProjectRoutes.Route r : o.flows()) { if (!called.contains(r.key())) { - routeLines(o, r, 0, null, calledBy, totals, shown, routes); + routeLines(o, r, 0, null, calledBy, totals, shown, routes, routeTargets); } } // what is left: routes called only by routes in a cycle for (ProjectRoutes.Route r : o.flows()) { if (!shown.contains(r.key())) { - routeLines(o, r, 0, null, calledBy, totals, shown, routes); + routeLines(o, r, 0, null, calledBy, totals, shown, routes, routeTargets); } } @@ -467,11 +553,14 @@ final class AboutView { out.add(Line.from(Span.raw(" " + icon(s.category()) + " "), Span.styled(shortUri(s.uri()), Style.EMPTY.bold()), Span.styled(" " + s.direction(), Theme.muted()), Span.styled(hits != null ? " " + hits : "", Theme.success()))); + outTargets.add(new Target(s.route(), s.uri())); out.add(Line.from(Span.styled(" from " + s.route(), Theme.muted()))); + outTargets.add(null); } } if (out.isEmpty()) { out.add(Line.from(Span.styled(" nothing outside the integration", Theme.muted()))); + outTargets.add(null); } } @@ -492,7 +581,7 @@ final class AboutView { } /** The URI without its options, and the same with or without the // after the scheme. */ - private static String baseUri(String uri) { + static String baseUri(String uri) { if (uri == null) { return ""; } @@ -505,7 +594,7 @@ final class AboutView { private static void routeLines( ProjectOverview.Overview o, ProjectRoutes.Route r, int depth, String how, Map<String, List<ProjectOverview.Link>> calledBy, Map<String, Long> totals, Set<String> shown, - List<Line> lines) { + List<Line> lines, List<Target> targets) { if (!shown.add(r.key()) || depth > 6) { return; } @@ -519,11 +608,13 @@ final class AboutView { spans.add(Span.styled(" " + total, Theme.success())); } lines.add(Line.from(spans)); + targets.add(new Target(r.id() != null ? r.id() : r.key(), null)); for (ProjectOverview.Link l : calledBy.getOrDefault(r.key(), List.of())) { ProjectRoutes.Route target = o.route(l.to()); if (target != null) { // ↳ a call that waits for the route, ⇢ a hand-off that does not - routeLines(o, target, depth + 1, "call".equals(l.kind()) ? "↳" : "⇢", calledBy, totals, shown, lines); + routeLines(o, target, depth + 1, "call".equals(l.kind()) ? "↳" : "⇢", calledBy, totals, shown, lines, + targets); } } } 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 b2236102e287..dc41edae958f 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 @@ -1505,6 +1505,9 @@ public class CamelMonitor extends CamelCommand { int tab = tabRegistry.selectedTabIndex(); if (ke.isConfirm() && tab == TAB_OVERVIEW) { + if (tabRegistry.overviewTab().openFromAbout()) { + return true; + } tabRegistry.overviewTab().selectCurrentIntegration(); if (ctx.selectedPid != null) { if (isPhantomSelected()) { 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 535779af9f0e..90f6877aa898 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 @@ -54,6 +54,9 @@ class DiagramTab extends AbstractDiagramTab { } private int infoPanelWidth = 30; + // a route to open drilled down once the diagram has loaded (Enter on About), and until when to wait for it + private volatile String pendingRoute; + private long pendingRouteUntil; private final DragSplit hSplit = new DragSplit(); private final ArchitectureView architecture @@ -578,6 +581,9 @@ class DiagramTab extends AbstractDiagramTab { @Override public void render(Frame frame, Rect area) { + if (pendingRoute != null && isDiagramShown()) { + openPendingRoute(); + } IntegrationInfo info = ctx.findSelectedIntegration(); if (info == null) { renderNoSelection(frame, area); @@ -770,6 +776,23 @@ class DiagramTab extends AbstractDiagramTab { return !diagram.isLoading() && !diagram.getNodeBoxes().isEmpty(); } + /** + * Opens the route drilled down, as Enter on it does (from About): now, or once the diagram has loaded, for up to 10 + * seconds. + */ + void openRoute(String routeId) { + pendingRoute = routeId; + pendingRouteUntil = System.currentTimeMillis() + 10_000; + openPendingRoute(); + } + + private void openPendingRoute() { + String routeId = pendingRoute; + if (routeId != null && (selectNode(routeId, null) || System.currentTimeMillis() > pendingRouteUntil)) { + pendingRoute = null; + } + } + boolean selectRoute(String routeId) { int idx = diagram.findNodeIndexByRouteId(routeId); if (idx < 0) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java index b8b8135cc2b8..71672ff5813c 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java @@ -425,6 +425,34 @@ class EndpointsTab extends AbstractTableTab { ChaosHere.hint(spans, ctx.findSelectedIntegration()); } + /** + * Selects the endpoint a route sends to (from About, where the URI is as the sources have it), with its details; + * the filter is cleared when it hides the endpoint. + */ + void selectEndpoint(String uri, String routeId) { + IntegrationInfo info = ctx.findSelectedIntegration(); + if (info == null) { + return; + } + String base = AboutView.baseUri(uri); + for (int attempt = 0; attempt < 2; attempt++) { + List<EndpointInfo> sorted = new ArrayList<>(info.endpoints); + sorted.removeIf(ep -> !matchesFilter(ep)); + sorted.sort(this::sortEndpoint); + for (int i = 0; i < sorted.size(); i++) { + EndpointInfo ep = sorted.get(i); + if ("out".equals(ep.direction) && (routeId == null || routeId.equals(ep.routeId)) + && base.equals(AboutView.baseUri(ep.uri))) { + tableState.select(i); + panelMode = PANEL_DETAIL; + detailScroll = 0; + return; + } + } + filter = 0; + } + } + /** The endpoint selected in the table, as filtered and sorted. */ private EndpointInfo selectedEndpoint(IntegrationInfo info) { Integer sel = tableState.selected(); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java index 4de39d756175..e993f61030b6 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java @@ -94,6 +94,8 @@ class MonitorContext { BiConsumer<String, Boolean> notificationCallback; // opens the Chaos screen with the known rules for a target ({@code !} on an endpoint, a consumer or a diagram node) Consumer<ChaosHere.Target> chaosHere; + // opens what a line of About is about (Enter): a route on the Diagram, an endpoint on the Endpoint tab + Consumer<AboutView.Target> openFromAbout; // the exchanges chaos rules faulted, for the marks on the Activity, Errors and Inspect tabs final ChaosMarks chaosMarks = new ChaosMarks(); // what happened second by second, for the Timeline screen (CAMEL-26691) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java index b84a8cede24c..4c9304c88ff2 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java @@ -154,6 +154,11 @@ class OverviewTab extends AbstractTab { return infos.size() == 1 ? infos.get(0) : null; } + /** Enter on About: opens the route or endpoint the cursor is on; false when it is on nothing. */ + boolean openFromAbout() { + return showsAbout() && about.open(); + } + /** Whether About shows: the view the user picked, else About when one integration runs. */ boolean showsAbout() { if (ctx.isInfraSelected()) { @@ -306,6 +311,10 @@ class OverviewTab extends AbstractTab { @Override public void navigateUp() { + if (showsAbout()) { + about.moveCursor(-1); + return; + } if (infraFocused) { infraTableState.selectPrevious(); } else { @@ -316,6 +325,10 @@ class OverviewTab extends AbstractTab { @Override public void navigateDown() { + if (showsAbout()) { + about.moveCursor(1); + return; + } if (infraFocused) { infraTableState.selectNext(ctx.infraData.get().size()); } else { @@ -1315,6 +1328,8 @@ class OverviewTab extends AbstractTab { hint(spans, "q", "quit"); if (showsAbout()) { // the keys of the table do not apply to About + hint(spans, "↑↓", "select"); + hint(spans, "Enter", "open"); hint(spans, "+/-", "zoom"); hint(spans, "←→", "move"); hint(spans, "4", "timeline"); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java index 9c429f2b302d..7484b3d5d2f9 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java @@ -335,6 +335,15 @@ class TabRegistry { selectMoreTab(moreTabIndex("Chaos")); chaosTab.openAt(target); }; + ctx.openFromAbout = target -> { + if (target.endpoint() != null) { + handleTabKey(TAB_ENDPOINTS); + endpointsTab.selectEndpoint(target.endpoint(), target.routeId()); + } else { + handleTabKey(TAB_DIAGRAM); + diagramTab.openRoute(target.routeId()); + } + }; sqlTraceTab.setEditSqlAction(sql -> { selectMoreTab(moreTabIndex("SQL Query")); sqlQueryTab.setInputValue("sql", sql); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md index b844355ac634..09e736b046c4 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md @@ -20,7 +20,10 @@ The Overview has two views, switched with **v**: **What happened** has a line per route, coloured as the Timeline: **+/-** zooms between 5, 2 and 1 minutes (zooming in centres on the last trouble, so you see what the other routes did at that moment) - and **←→** moves a zoomed window back and forward. + and **←→** moves a zoomed window back and forward. **↑↓** select a + line of In, Routes or Out, and **Enter** opens it: a route drilled + down on the Diagram, an endpoint a route sends to with its details on + the Endpoint tab. - **Integrations** — the table of all running integrations below, shown when several run. 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 c2352c5173c4..4701e5c185fc 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 @@ -21,6 +21,7 @@ import java.nio.file.Path; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -28,6 +29,7 @@ import java.util.concurrent.atomic.AtomicReference; import dev.tamboui.buffer.Buffer; import dev.tamboui.layout.Rect; import dev.tamboui.terminal.Frame; +import dev.tamboui.tui.event.KeyCode; import dev.tamboui.tui.event.KeyEvent; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -210,5 +212,21 @@ class AboutViewTest { .contains("HttpOperationFailedException 503 ×30").contains("Watch").contains("Next"); assertThat(screen).contains("What happened").contains("30 handled"); }); + + // the cursor goes down In, Routes and Out; Enter opens what it is on + List<AboutView.Target> opened = new ArrayList<>(); + ctx.openFromAbout = opened::add; + assertThat(about.open()).isFalse(); + about.moveCursor(1); + render(about, info); + assertThat(about.selected()).isEqualTo(new AboutView.Target("orders", null)); + about.moveCursor(2); + render(about, info); + assertThat(about.selected()).isEqualTo(new AboutView.Target("stock", null)); + about.moveCursor(5); + render(about, info); + assertThat(about.selected()).isEqualTo(new AboutView.Target("orders", "http:localhost:8765/ship")); + assertThat(about.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER), info)).isTrue(); + assertThat(opened).containsExactly(new AboutView.Target("orders", "http:localhost:8765/ship")); } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTabRenderTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTabRenderTest.java index 5355400b4c7c..04e8a93be721 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTabRenderTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTabRenderTest.java @@ -232,6 +232,20 @@ class EndpointsTabRenderTest { // ---- Helper methods ---- + @Test + void anEndpointOfARouteIsSelectedWithItsDetails() { + addEndpoint("timer", "timer://tick", "in", "a", 10); + addEndpoint("http", "http://example.com/api", "out", "a", 5); + addEndpoint("http", "http://example.com/api", "out", "b", 5); + EndpointsTab tab = new EndpointsTab(ctx, new MetricsCollector()); + TuiTestHelper.renderToString(tab, 140, 30); + + // as About has it: the URI as the sources write it + tab.selectEndpoint("http:example.com/api", "b"); + assertThat(tab.tableState.selected()).isEqualTo(2); + assertThat(TuiTestHelper.renderToString(tab, 140, 30)).contains("http://example.com/api"); + } + private EndpointInfo addEndpoint(String component, String uri, String direction, String routeId, long hits) { EndpointInfo ep = new EndpointInfo(); ep.component = component;
