gnodet-bot commented on code in PR #29947: URL: https://github.com/apache/camel/pull/29947#discussion_r4575387364
########## dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/FlowDots.java: ########## @@ -0,0 +1,175 @@ +/* + * 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.diagram; + +import java.util.ArrayList; +import java.util.List; + +import dev.tamboui.buffer.Buffer; +import dev.tamboui.layout.Rect; +import dev.tamboui.style.Color; +import dev.tamboui.style.Style; +import org.apache.camel.dsl.jbang.core.commands.tui.Theme; + +import static org.apache.camel.dsl.jbang.core.commands.tui.diagram.DiagramColors.failColor; + +/** + * The dots of the messages on the arrows of a diagram (a route or the topology), drawn over the lines the arrows are + * drawn with, at the pace of a {@link FlowAnimation.Track}. + */ +final class FlowDots { + + /** The cell of a line lit up as it is: no dot on it. */ + static final char LIT = 0; + + /** + * The cells of an arrow in the order a message goes along them, and its head (a char of 0 when the line goes on to + * the mark of a step, with no head). + */ + record Path(String key, int fromRow, List<int[]> cells, int[] head, char headChar) { + } + + private FlowDots() { + } + + /** + * The path of an arrow as the diagrams draw one: down from the source, along a row half way when the target is in + * another column, and down to the target; with a head in the row above the target, or none. + */ + static Path path(String key, int fromCx, int fromRow, int toCx, int toRow, char headChar) { + int end = headChar != 0 ? toRow - 1 : toRow; + List<int[]> cells = new ArrayList<>(); + if (fromCx == toCx) { + for (int r = fromRow; r < end; r++) { + cells.add(new int[] { r, fromCx }); + } + } else { + int midRow = fromRow + (toRow - fromRow) / 2; + for (int r = fromRow; r < midRow; r++) { + cells.add(new int[] { r, fromCx }); + } + int step = toCx > fromCx ? 1 : -1; + for (int c = fromCx; c != toCx + step; c += step) { + cells.add(new int[] { midRow, c }); + } + for (int r = midRow + 1; r < end; r++) { + cells.add(new int[] { r, toCx }); + } + } + return new Path(key, fromRow, cells, headChar != 0 ? new int[] { toRow - 1, toCx } : null, headChar); + } + + /** + * The dots on an arrow that messages go along, red when most of them fail; or with {@code dots} off its line lit + * up, where a dot would look like the mark of a step. A letter, a digit or a char of {@code keep} on the way stays. + */ + static void draw( + Buffer buffer, Rect area, int scrollX, int scrollY, Path path, FlowAnimation.Track t, boolean dots, + String keep) { + if (t == null || t.rate() <= 0) { + return; + } + int spacing = FlowAnimation.spacing(t.rate()); + long moved = (long) Math.floor(t.phase()); + // the foreground of the theme: the dots are lights on the lines, whatever the colours of the steps are + Color head = light(t); + Color tail = mix(head, Theme.muted().fg().orElse(Color.GRAY), 0.5); + Style dotStyle = dots ? Style.EMPTY.fg(head) : Style.EMPTY.fg(head).bold(); + List<int[]> cells = path.cells(); + // the head last: an arrow of a compact diagram is only its head, which then lights up as the dots come by + int last = path.head() != null ? cells.size() : cells.size() - 1; + for (int i = 0; i <= last; i++) { + // counted from the top of the diagram: below a box the dots go on at the same pace + if (Math.floorMod(path.fromRow() + i - moved, spacing) == 0) { + if (i == cells.size()) { + dot(buffer, area, scrollX, scrollY, path.head(), path.headChar(), Style.EMPTY.fg(head).bold(), + path.headChar(), keep); + } else { + dot(buffer, area, scrollX, scrollY, cells.get(i), dots ? '●' : LIT, dotStyle, (char) 0, keep); + } + if (i > 0) { + dot(buffer, area, scrollX, scrollY, cells.get(i - 1), dots ? '•' : LIT, Style.EMPTY.fg(tail), + (char) 0, keep); + } + } + } + } + + /** A dot on a line of an arrow, or its head lit up (only where the head is). */ + private static void dot( + Buffer buffer, Rect area, int scrollX, int scrollY, int[] cell, char ch, Style style, char onHead, + String keep) { + int x = area.x() + cell[1] - scrollX; + int y = area.y() + cell[0] - scrollY; + if (x < area.left() || x >= area.right() || y < area.top() || y >= area.bottom()) { + return; + } + String existing = buffer.get(x, y).symbol(); + if (onHead != 0) { + // a head on the line of a branch is the head of that arrow, not a cell of its line + if (!existing.equals(String.valueOf(onHead))) { + return; + } + } else if (!existing.isEmpty() && (Character.isLetterOrDigit(existing.codePointAt(0)) + || keep.indexOf(existing.charAt(0)) >= 0)) { + return; + } + if (ch == LIT && existing.isBlank()) { + return; + } + buffer.setString(x, y, ch == LIT ? existing : String.valueOf(ch), style); + } + + /** The colour of the dots of a track: the foreground of the theme, or red when most of the messages fail. */ + static Color light(FlowAnimation.Track t) { + return t.failRate() * 2 > t.rate() ? failColor() : Theme.baseFg(); + } + + /** + * The colour of a mark in a row as the light of the messages passes over it, at the pace of the dots on the lines: + * the marks of a busy path light up one after another. + */ + static Color lit(Color base, FlowAnimation.Track t, int row) { + if (t == null || t.rate() <= 0) { + return base; + } + int spacing = FlowAnimation.spacing(t.rate()); + int at = Math.floorMod(row - (long) Math.floor(t.phase()), spacing); + if (at == 0) { + return mix(base, light(t), 0.7); + } + return at == spacing - 1 ? mix(base, light(t), 0.35) : base; + } + + /** The colour between two colours, {@code t} of the way from the first. */ + static Color mix(Color from, Color to, double t) { + if (t <= 0) { + return from; + } + try { + Color.Rgb a = from.toRgb(); + Color.Rgb b = to.toRgb(); + return Color.rgb( + a.r() + (int) Math.round((b.r() - a.r()) * t), + a.g() + (int) Math.round((b.g() - a.g()) * t), + a.b() + (int) Math.round((b.b() - a.b()) * t)); + } catch (RuntimeException e) { + // a colour without RGB (the default of the terminal) + return t < 0.5 ? from : to; + } Review Comment: ⚠️ **Too broad catch:** `catch (RuntimeException e)` can mask unrelated exceptions (NPE, arithmetic errors) that have nothing to do with missing RGB. `Color.toRgb()` throws `UnsupportedOperationException` when the colour is not an RGB colour — guard with `instanceof` instead: ```suggestion if (from instanceof Color.Rgb a && to instanceof Color.Rgb b) { return Color.rgb( a.r() + (int) Math.round((b.r() - a.r()) * t), a.g() + (int) Math.round((b.g() - a.g()) * t), a.b() + (int) Math.round((b.b() - a.b()) * t)); } // a colour without RGB (the default of the terminal) return t < 0.5 ? from : to; ``` ########## docs/user-manual/modules/ROOT/pages/camel-jbang-tui-diagram.adoc: ########## @@ -241,6 +243,41 @@ Press *e* to cycle through three modes for external endpoints: * Press *c* to view the source code of the selected route, with syntax highlighting * Press *b* (or *n*) to switch between the business view (labels) and the technical view (route ids and endpoints) +=== Live Flow + +With the metrics on, a route diagram and the topology show where the messages go while the integration runs, so the +busy part stands out at a glance, not only as counts that go up. The architecture shows it between its groups. Press *w* to turn it off and on; the choice is kept +for the next start. + +* *Dots on the arrows* move along an arrow while messages reach the step below it: the more messages a second, the + faster and closer together they are. They are red when most of the messages fail there. +* At the compact level the head of an arrow lights up as a dot comes in. At the bare and overview levels the lines light + up instead, as a dot would look like the mark of a step there, and in the overview, which has hardly any lines between + its steps, the light passes over the steps themselves. The steps keep the colours of their kind at every level. Review Comment: 📝 **Run-on sentence:** This bullet chains four independent clauses (~55 words) through commas and conjunctions, making it hard to parse at a glance. Consider splitting into shorter sentences, e.g. one sentence per display level. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
