Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion artifacts/requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7568,7 +7568,7 @@ artifacts:
- id: REQ-238
type: requirement
title: graphical req->test-result trace view
status: implemented
status: verified
description: "A graphical/visual way to trace a requirement to its test results. #547. The forward-trace MECHANISM shipped (rivet-core result_trace + `rivet trace-results`, the JSON the view consumes); the graphical dashboard rendering is the remaining half and needs the serve/frontend work + Playwright E2E gate, so it is scoped to v0.24. Moved from v0.23 so v0.23 cuts on its 6 verified evidence REQs."
provenance:
created-by: ai-assisted
Expand Down
48 changes: 48 additions & 0 deletions rivet-cli/src/render/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,54 @@ pub(crate) fn render_artifact_detail(ctx: &RenderContext, id: &str) -> RenderRes
html.push_str("</tbody></table></div>");
}

// Test-result trace (REQ-238, #547): walk forward from this artifact to the
// verifications/tests that trace back to it and roll the reached test
// results into a one-line verdict. This is the dashboard counterpart of
// `rivet trace-results` — previously the only way to see this was the CLI.
// Rendered only when something traces back (leaf artifacts stay clean).
// Depth 4 mirrors the CLI default (the ASPICE V depth).
let trace = rivet_core::result_trace::trace_test_results(id, graph, ctx.result_store, 4);
if !trace.is_empty() {
let (verdict_class, verdict_label) = match rivet_core::result_trace::verdict(&trace) {
rivet_core::result_trace::TraceVerdict::Passing => ("badge-ok", "passing"),
rivet_core::result_trace::TraceVerdict::Failing => ("badge-error", "failing"),
rivet_core::result_trace::TraceVerdict::NoEvidence => {
("badge-warn", "no test evidence")
}
};
html.push_str(&format!(
"<div class=\"card\"><h3>Test Result Trace \
<span class=\"badge {verdict_class}\">{verdict_label}</span></h3>\
<table><thead><tr><th>Hops</th><th>Artifact</th><th>Via</th><th>Result</th></tr></thead><tbody>"
));
for node in &trace {
let node_id = html_escape(&node.artifact_id);
let result_cell = match &node.status {
Some(status) => {
let (cls, label) = match status {
rivet_core::results::TestStatus::Pass => ("badge-ok", "pass"),
rivet_core::results::TestStatus::Fail => ("badge-error", "fail"),
rivet_core::results::TestStatus::Error => ("badge-error", "error"),
rivet_core::results::TestStatus::Skip => ("badge-warn", "skip"),
rivet_core::results::TestStatus::Blocked => ("badge-warn", "blocked"),
};
format!("<span class=\"badge {cls}\">{label}</span>")
}
None => String::from("<span class=\"meta\">&mdash;</span>"),
};
html.push_str(&format!(
"<tr><td>{hops}</td>\
<td><a hx-get=\"/artifacts/{node_id}\" hx-target=\"#content\" hx-push-url=\"true\" href=\"/artifacts/{node_id}\">{node_id}</a></td>\
<td><span class=\"link-pill\">{link_type}</span> {via}</td>\
<td>{result_cell}</td></tr>",
hops = node.distance,
link_type = html_escape(&node.link_type),
via = html_escape(&node.via_target),
));
}
html.push_str("</tbody></table></div>");
}

// Documents referencing this artifact — reverse index from DocumentStore.
// Groups [[ID]] occurrences per document so the user can jump from an
// artifact to every doc that cites it.
Expand Down
35 changes: 35 additions & 0 deletions rivet-cli/tests/serve_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,41 @@ fn artifact_detail_source_link_opens_source_view() {
child.wait().ok();
}

/// REQ-238 (#547): the artifact detail view renders a graphical "Test Result
/// Trace" panel — the dashboard counterpart of `rivet trace-results`. It walks
/// forward from the artifact to the verifications/tests that trace back to it,
/// rolls them into a verdict badge, and lists each reached node with its
/// result. REQ-001 has downstream links, so the card (with the Hops/Via table)
/// must be present.
///
/// rivet: verifies REQ-238
#[test]
fn artifact_detail_renders_test_result_trace() {
let (mut child, port) = start_server();

let (status, body, _headers) = fetch(port, "/artifacts/REQ-001", false);
assert_eq!(status, 200);

assert!(
body.contains("Test Result Trace"),
"artifact detail must render the Test Result Trace panel for an artifact \
with downstream verifications"
);
// A verdict badge must be present (passing / failing / no test evidence).
assert!(
body.contains("passing") || body.contains("failing") || body.contains("no test evidence"),
"the trace panel must show a rolled-up verdict badge"
);
// The reached-node table columns are unique to this panel.
assert!(
body.contains("<th>Hops</th>") && body.contains("<th>Via</th>"),
"the trace panel must list reached nodes in a Hops/Artifact/Via/Result table"
);

child.kill().ok();
child.wait().ok();
}

// ── Embed resolution in documents ──────────────────────────────────────

/// The documents page should not contain any embed-error spans for valid
Expand Down
Loading