feat(trace-charts): serve persisted traces as a web chart (iter 2)
Iteration 2 (serve half) of the visual face — spec 0056 / plan 0056:
read a persisted run's traces and serve them as a self-contained uPlot
HTML chart. Completes the goal "tap data from a graph, serve it as a
chart in the browser".
- chart-viewer.js (first-party): a pure buildCharts(traces, mode) that
maps the injected window.AURA_TRACES to uPlot configs (no DOM) — overlay
= one plot, every series on its own y-scale over the shared xs; panels =
one plot per series, all on the same timestamp x. A browser-only mount()
instantiates the vendored global uPlot. Guarded by a node .mjs DOM test
(+ a sparse/gaps variant) shelling out exactly like viewer_dot.rs.
- render_chart_html + ChartMode/ChartData/Series (aura-cli/render.rs):
mirrors render_html — self-contained page, uPlot (1.6.32, vendored
d534f10) + chart-viewer.js inlined via include_str!, data injected as
window.AURA_TRACES.
- emit_chart + build_chart_data (aura-cli/main.rs): the spec-§6 3-step
union-spine alignment — xs = sorted-dedup union of every tap's ts; a
synthetic empty-payload spine + ALL taps as symmetric join_on_ts sides
(no tap privileged, no point dropped); flatten each (tap,column) to a
Series of Option<f64>. New arms `aura chart <name> [--panels]`; a
missing run is stderr + exit 2 (TraceStoreError::NotFound), never a
panic. USAGE advertises the verb in lockstep.
Verified by the orchestrator: `cargo test --workspace` green (incl. the
node guards, render_chart_html, and the chart e2e); `cargo clippy
--workspace --all-targets -D warnings` exit 0; and a real end-to-end —
`aura run --trace demo && aura chart demo` emits a 57KB self-contained
page (doctype + inlined uPlot + AURA_TRACES carrying the equity/exposure
series), `--panels` reflects the panels mode, `aura chart nope` exits 2.
Implementer deviation folded in (compiler-prescribed): serde added to
aura-cli/Cargo.toml for the Series derive (Cargo.lock updated).
refs #101
This commit is contained in:
@@ -413,6 +413,60 @@ fn json_array_body<'a>(json: &'a str, marker: &str) -> &'a str {
|
||||
&rest[..end]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chart_emits_self_contained_html_for_a_persisted_run() {
|
||||
let cwd = temp_cwd("chart-serve");
|
||||
|
||||
// persist a run first (iteration 1), then chart it.
|
||||
let traced = Command::new(BIN).args(["run", "--trace", "demo"]).current_dir(&cwd).output().expect("spawn run --trace");
|
||||
assert!(traced.status.success(), "run --trace exit: {:?}", traced.status);
|
||||
|
||||
let chart = Command::new(BIN).args(["chart", "demo"]).current_dir(&cwd).output().expect("spawn chart");
|
||||
assert!(chart.status.success(), "chart exit: {:?}", chart.status);
|
||||
let html = String::from_utf8(chart.stdout).expect("utf-8 html");
|
||||
assert!(html.starts_with("<!doctype html>"), "not an HTML doc: {:?}", &html[..html.len().min(40)]);
|
||||
assert!(html.contains("window.AURA_TRACES = {"), "AURA_TRACES not injected");
|
||||
assert!(html.contains("\"equity\""), "equity series missing from the chart");
|
||||
assert!(html.contains("uPlot=function"), "vendored uPlot not inlined");
|
||||
|
||||
// a missing run is a usage error (stderr + exit 2), not a panic.
|
||||
let missing = Command::new(BIN).args(["chart", "nope"]).current_dir(&cwd).output().expect("spawn chart nope");
|
||||
assert_eq!(missing.status.code(), Some(2), "missing run must exit 2");
|
||||
assert!(!missing.status.success());
|
||||
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// Property: the `--panels` flag is an honest CLI dispatch axis — `chart <name>`
|
||||
/// serves the overlay page and `chart <name> --panels` serves the panels page over
|
||||
/// the SAME persisted run. The mode the viewer reads (`AURA_TRACES.mode`) reflects
|
||||
/// which arm dispatched: a `--panels`→Overlay miswiring would leave every shape and
|
||||
/// render test green while silently ignoring the flag, so this pins it at the
|
||||
/// binary's observable stdout.
|
||||
#[test]
|
||||
fn chart_panels_flag_selects_panels_mode() {
|
||||
let cwd = temp_cwd("chart-panels");
|
||||
|
||||
let traced = Command::new(BIN).args(["run", "--trace", "demo"]).current_dir(&cwd).output().expect("spawn run --trace");
|
||||
assert!(traced.status.success(), "run --trace exit: {:?}", traced.status);
|
||||
|
||||
// default (no flag) -> overlay.
|
||||
let overlay = Command::new(BIN).args(["chart", "demo"]).current_dir(&cwd).output().expect("spawn chart");
|
||||
assert!(overlay.status.success(), "chart exit: {:?}", overlay.status);
|
||||
let overlay_html = String::from_utf8(overlay.stdout).expect("utf-8 html");
|
||||
assert!(overlay_html.contains("\"mode\":\"overlay\""), "default chart must serve overlay mode");
|
||||
assert!(!overlay_html.contains("\"mode\":\"panels\""), "default chart must not serve panels mode");
|
||||
|
||||
// --panels -> panels, over the very same persisted run.
|
||||
let panels = Command::new(BIN).args(["chart", "demo", "--panels"]).current_dir(&cwd).output().expect("spawn chart --panels");
|
||||
assert!(panels.status.success(), "chart --panels exit: {:?}", panels.status);
|
||||
let panels_html = String::from_utf8(panels.stdout).expect("utf-8 html");
|
||||
assert!(panels_html.contains("\"mode\":\"panels\""), "--panels must serve panels mode");
|
||||
assert!(!panels_html.contains("\"mode\":\"overlay\""), "--panels must not fall back to overlay");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_args_prints_usage_and_exits_two() {
|
||||
let out = Command::new(BIN).output().expect("spawn aura");
|
||||
|
||||
Reference in New Issue
Block a user