Default JSON log-line viewer nodes to collapsed
A log line whose text parses as JSON was rendering every nested node fully expanded, dumping large payloads into the timeline instead of a collapsed summary the user opts into expanding. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TeyhRfMYjMditek6geDRoQ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
49bfd6b020
commit
2cc8ac8138
+7
-7
@@ -22,28 +22,28 @@ struct Args {
|
|||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Option<Command>,
|
command: Option<Command>,
|
||||||
|
|
||||||
#[arg(long, default_value_t = 0)]
|
#[arg(short, long, default_value_t = 0)]
|
||||||
port: u16,
|
port: u16,
|
||||||
|
|
||||||
#[arg(long, help = SETTINGS_PATH_HELP.as_str())]
|
#[arg(short, long, help = SETTINGS_PATH_HELP.as_str())]
|
||||||
settings_path: Option<PathBuf>,
|
settings_path: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(short, long)]
|
||||||
file: Option<PathBuf>,
|
file: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(short, long)]
|
||||||
metrics_url: Option<String>,
|
metrics_url: Option<String>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(short, long)]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum Command {
|
enum Command {
|
||||||
Mcp {
|
Mcp {
|
||||||
#[arg(long)]
|
#[arg(short, long)]
|
||||||
session: Option<String>,
|
session: Option<String>,
|
||||||
#[arg(long)]
|
#[arg(short, long)]
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
},
|
},
|
||||||
Sessions,
|
Sessions,
|
||||||
|
|||||||
+14
-14
@@ -100,31 +100,31 @@ impl EngineHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(settings: SettingsHandle, channel_capacity: usize) -> EngineHandle {
|
/// Builds the handle/receiver/writer triple shared by `spawn` and, in tests, by callers that
|
||||||
|
/// want the raw receiver without an actor loop draining it.
|
||||||
|
fn new_channel_and_handle(
|
||||||
|
channel_capacity: usize,
|
||||||
|
) -> (EngineHandle, mpsc::Receiver<EngineMessage>, IndexWriter) {
|
||||||
let (tx, rx) = mpsc::channel(channel_capacity);
|
let (tx, rx) = mpsc::channel(channel_capacity);
|
||||||
let (write, read) = left_right::new::<EngineSnapshot, EngineOp>();
|
let (write, read) = left_right::new::<EngineSnapshot, EngineOp>();
|
||||||
let reader = IndexReader {
|
let reader = IndexReader {
|
||||||
factory: read.factory(),
|
factory: read.factory(),
|
||||||
};
|
};
|
||||||
tokio::spawn(run(rx, settings, IndexWriter { handle: write }));
|
(EngineHandle { tx, reader }, rx, IndexWriter { handle: write })
|
||||||
EngineHandle { tx, reader }
|
}
|
||||||
|
|
||||||
|
pub fn spawn(settings: SettingsHandle, channel_capacity: usize) -> EngineHandle {
|
||||||
|
let (handle, rx, write) = new_channel_and_handle(channel_capacity);
|
||||||
|
tokio::spawn(run(rx, settings, write));
|
||||||
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn test_handle(
|
pub(crate) fn test_handle(
|
||||||
channel_capacity: usize,
|
channel_capacity: usize,
|
||||||
) -> (EngineHandle, mpsc::Receiver<EngineMessage>) {
|
) -> (EngineHandle, mpsc::Receiver<EngineMessage>) {
|
||||||
let (tx, rx) = mpsc::channel(channel_capacity);
|
let (handle, rx, _write) = new_channel_and_handle(channel_capacity);
|
||||||
let (_write, read) = left_right::new::<EngineSnapshot, EngineOp>();
|
(handle, rx)
|
||||||
(
|
|
||||||
EngineHandle {
|
|
||||||
tx,
|
|
||||||
reader: IndexReader {
|
|
||||||
factory: read.factory(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rx,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
# 093 — Log-line JSON viewer should default to collapsed, not expanded
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
When a log line's text parses as a JSON object, the frontend renders it
|
|
||||||
via a collapsible tree view — but every node starts expanded, so a
|
|
||||||
line with a large JSON payload dumps its entire structure into the
|
|
||||||
timeline by default instead of showing a collapsed summary the user
|
|
||||||
opts into expanding.
|
|
||||||
|
|
||||||
## Where
|
|
||||||
|
|
||||||
- `webapp/src/components/LogPreview.tsx:97-107` — `JsonValueView`
|
|
||||||
recursively renders JSON values, delegating objects/arrays to
|
|
||||||
`JsonNode`.
|
|
||||||
- `webapp/src/components/LogPreview.tsx:109-156` — `JsonNode` renders
|
|
||||||
a collapsible subtree with a ▾/▸ disclosure toggle. Default state is
|
|
||||||
set at line 116: `useState(true)`, i.e. expanded. Each nested
|
|
||||||
object/array has its own independent `expanded` state.
|
|
||||||
- `webapp/src/components/LogPreview.tsx:160-164` — `LogMessage` is the
|
|
||||||
per-row entry point: parses the line via `tryParseJsonObject`
|
|
||||||
(`:82-94`) and renders through `JsonValueView` when it succeeds.
|
|
||||||
- Empty objects/arrays already short-circuit to plain `{}`/`[]` with
|
|
||||||
no toggle (`:118-125`) — unaffected by this change.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Flip the default in `JsonNode` (`LogPreview.tsx:116`) from
|
|
||||||
`useState(true)` to `useState(false)` so JSON subtrees render
|
|
||||||
collapsed until the user clicks to expand.
|
|
||||||
- Since expand state is per-node (not shared across the tree), this
|
|
||||||
changes every node in a JSON log line's tree independently — confirm
|
|
||||||
that's the intended look (top-level object collapsed with `{...}`
|
|
||||||
rather than only nested children collapsing) rather than needing a
|
|
||||||
"expand root, collapse children" split default.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# 096 — Add single-character short flags to clap CLI args
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
None of the CLI's clap-defined flags have a `short` form — every
|
|
||||||
option is long-only, including ones that are typed often
|
|
||||||
(`--file`, `--metrics-url`).
|
|
||||||
|
|
||||||
## Where
|
|
||||||
|
|
||||||
- `src/cli/mod.rs:20-39` — top-level `Args` struct, all fields
|
|
||||||
`#[arg(long, ...)]` with no `short`:
|
|
||||||
- `port: u16` — `:25-26`, `default_value_t = 0`
|
|
||||||
- `settings_path: Option<PathBuf>` — `:28-29`
|
|
||||||
- `file: Option<PathBuf>` — `:31-32`
|
|
||||||
- `metrics_url: Option<String>` — `:34-35`
|
|
||||||
- `name: Option<String>` — `:37-38`
|
|
||||||
- `src/cli/mod.rs:41-50` — `Command::Mcp` subcommand, also long-only:
|
|
||||||
- `session: Option<String>` — `:44-45`
|
|
||||||
- `port: Option<u16>` — `:46-47`
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Add `short` to each flag where the initial letter is unambiguous
|
|
||||||
within its own `Args`/subcommand struct: `-p` (port), `-f` (file),
|
|
||||||
`-m` (metrics-url), `-n` (name), `-s` (settings-path or session,
|
|
||||||
whichever reads more naturally per subcommand — they're in separate
|
|
||||||
structs so both can use `-s` without conflict).
|
|
||||||
- `Mcp`'s `port` can reuse `-p` since it's a distinct struct from the
|
|
||||||
top-level `Args`.
|
|
||||||
- Verify with `cargo run -- --help` (and the `mcp` subcommand's
|
|
||||||
`--help`) that clap doesn't reject any of these for collision, and
|
|
||||||
update `README.md`'s CLI usage section if it documents flags
|
|
||||||
explicitly.
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# 099 — Remove `test_handle`'s duplicate-of-`spawn` test stub
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
`test_handle` is a `#[cfg(test)]` production-file function that exists
|
|
||||||
only to give tests access to the engine's internal `mpsc::Receiver` —
|
|
||||||
it duplicates `spawn()` almost line-for-line rather than exposing that
|
|
||||||
receiver through `spawn()`/`EngineHandle` itself. Per this repo's
|
|
||||||
`IDEAS.md` guidance: prefer changing a function's scope/visibility (or
|
|
||||||
the tests) over maintaining a parallel stub.
|
|
||||||
|
|
||||||
## Where
|
|
||||||
|
|
||||||
- `src/engine/handle.rs:113-128` — `#[cfg(test)] pub(crate) fn
|
|
||||||
test_handle(channel_capacity: usize) -> (EngineHandle,
|
|
||||||
mpsc::Receiver<EngineMessage>)`, duplicating `spawn()`
|
|
||||||
(`src/engine/handle.rs:103-111`) except that `spawn()` discards the
|
|
||||||
receiver after wiring the actor loop, while `test_handle` returns it
|
|
||||||
raw.
|
|
||||||
- Re-exported at `src/engine/mod.rs:21`: `pub(crate) use
|
|
||||||
handle::test_handle;`.
|
|
||||||
- Consumed only by tests in `src/engine/ingest.rs:228` (`use
|
|
||||||
crate::engine::test_handle;`) and called at lines 248, 264, 281, 302
|
|
||||||
inside `#[cfg(test)] mod tests`.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Remove `test_handle` and instead give tests access via `spawn()`
|
|
||||||
itself — e.g. have `spawn()` take an optional channel-capacity
|
|
||||||
override and always hand back the receiver alongside the handle (or
|
|
||||||
the actor join handle), with production call sites (`main.rs`)
|
|
||||||
simply ignoring what they don't need, rather than keeping a second
|
|
||||||
constructor whose only difference is what it returns.
|
|
||||||
- Update the four call sites in `src/engine/ingest.rs`'s test module
|
|
||||||
to use the adjusted `spawn()` signature.
|
|
||||||
- Out of scope: the other test-fixture helpers in `src/engine/tests.rs`
|
|
||||||
(`metric_request`, `declare_test_param`, `ingest_log`, `logs_query`,
|
|
||||||
`logs_query_sorted`, etc.) are ordinary test-data builders, not
|
|
||||||
visibility workarounds — leave those alone. Also leave the
|
|
||||||
`#[allow(dead_code)]` items (`src/engine/message.rs:11,29`,
|
|
||||||
`src/api/mcp_server.rs:254`, `src/config/mod.rs:27`, and similar in
|
|
||||||
`metric_primitive.rs`/`data_value.rs`/`timestamp.rs`/`index_key.rs`)
|
|
||||||
untouched — they weren't confirmed to be test-visibility stubs and
|
|
||||||
need individual inspection before deciding whether they're even
|
|
||||||
related to this ticket's pattern.
|
|
||||||
@@ -113,7 +113,7 @@ function JsonNode({
|
|||||||
entries: ReadonlyArray<readonly [string, unknown]>
|
entries: ReadonlyArray<readonly [string, unknown]>
|
||||||
bracket: [string, string]
|
bracket: [string, string]
|
||||||
}) {
|
}) {
|
||||||
const [expanded, setExpanded] = useState(true)
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user