Fix HTML report for retried requests.

This commit is contained in:
jcamiel 2023-06-22 11:16:59 +02:00
parent 2a06ab1287
commit 9f81b10d38
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
5 changed files with 91 additions and 49 deletions

View File

@ -36,16 +36,13 @@ impl Testcase {
/// Creates an HTML testcase.
pub fn from(hurl_result: &HurlResult, filename: &str) -> Testcase {
let id = Uuid::new_v4();
let errors = hurl_result.errors().into_iter().cloned().collect();
Testcase {
id: id.to_string(),
filename: filename.to_string(),
time_in_ms: hurl_result.time_in_ms,
success: hurl_result.success,
errors: hurl_result
.entries
.iter()
.flat_map(|e| e.errors.clone())
.collect(),
errors,
}
}

View File

@ -21,8 +21,10 @@ use crate::report::html::timeline::svg::Attribute::{
};
use crate::report::html::timeline::svg::{Element, ElementKind};
use crate::report::html::timeline::unit::{Pixel, Px};
use crate::report::html::timeline::util::{new_failure_icon, new_success_icon, trunc_str};
use crate::report::html::timeline::{svg, CallContext, CALL_HEIGHT};
use crate::report::html::timeline::util::{
new_failure_icon, new_retry_icon, new_success_icon, trunc_str,
};
use crate::report::html::timeline::{svg, CallContext, CallContextKind, CALL_HEIGHT};
use crate::report::html::Testcase;
use std::iter::zip;
@ -47,6 +49,8 @@ impl Testcase {
root.add_child(symbol);
let symbol = new_failure_icon("failure");
root.add_child(symbol);
let symbol = new_retry_icon("retry");
root.add_child(symbol);
// Add a flat background.
let mut elt = Element::new(ElementKind::Rect);
@ -57,15 +61,17 @@ impl Testcase {
elt.add_attr(Fill("#fbfafd".to_string()));
root.add_child(elt);
// Add horizontal lines
let x = 0.px();
let y = margin_top;
let elt = new_grid(calls, y, width, height);
root.add_child(elt);
if !calls.is_empty() {
// Add horizontal lines
let x = 0.px();
let y = margin_top;
let elt = new_grid(calls, y, width, height);
root.add_child(elt);
// Add calls info
let elt = new_calls(calls, call_ctxs, x, y);
root.add_child(elt);
// Add calls info
let elt = new_calls(calls, call_ctxs, x, y);
root.add_child(elt);
}
root.to_string()
}
@ -96,10 +102,10 @@ fn new_calls(
// Icon success / failure
let mut elt = svg::new_use();
let icon = if call_ctx.success {
"#success"
} else {
"#failure"
let icon = match call_ctx.kind {
CallContextKind::Success => "#success",
CallContextKind::Failure => "#failure",
CallContextKind::Retry => "#retry",
};
elt.add_attr(Href(icon.to_string()));
elt.add_attr(X(x.0 - 6.0));
@ -117,7 +123,7 @@ fn new_calls(
let text = format!("{} {url}", call.request.method);
let text = trunc_str(&text, 24);
let mut elt = svg::new_text(x.0, y.0, &text);
if !call_ctx.success {
if call_ctx.kind == CallContextKind::Failure {
elt.add_attr(Fill("red".to_string()));
}
group.add_child(elt);
@ -126,7 +132,7 @@ fn new_calls(
x += 180.px();
let text = format!("{}", call.response.status);
let mut elt = svg::new_text(x.0, y.0, &text);
if !call_ctx.success {
if call_ctx.kind == CallContextKind::Failure {
elt.add_attr(Fill("red".to_string()));
}
group.add_child(elt);

View File

@ -33,14 +33,20 @@ mod waterfall;
const CALL_HEIGHT: Pixel = Pixel(24.0);
const CALL_INSET: Pixel = Pixel(3.0);
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum CallContextKind {
Success, // call context parent entry is successful
Failure, // call context parent entry is in error and has not been retried
Retry, // call context parent entry is in error and has been retried
}
/// A structure that holds information to construct a SVG view
/// of a [`Call`]
pub struct CallContext {
pub success: bool, // If the parent entry is successful or not
pub line: usize, // Line number of the source entry (1-based)
pub entry_index: usize, // Index of the runtime EntryResult
pub kind: CallContextKind, // If the parent entry is successful, retried or in error.
pub line: usize, // Line number of the source entry (1-based)
pub entry_index: usize, // Index of the runtime EntryResult
pub call_entry_index: usize, // Index of the runtime Call in the current entry
pub call_index: usize, // Index of the runtime Call in the whole run
pub call_index: usize, // Index of the runtime Call in the whole run
pub source_filename: String,
pub run_filename: String,
}
@ -80,13 +86,24 @@ impl Testcase {
/// Constructs a list of call contexts to record source line code, runtime entry and call indices.
fn get_call_contexts(&self, hurl_file: &HurlFile, entries: &[EntryResult]) -> Vec<CallContext> {
let mut calls_ctx = vec![];
for (entry_index, e) in entries.iter().enumerate() {
let next_e = entries.get(entry_index + 1);
let retry = match next_e {
None => false, // last entry of the whole run can't be retried
Some(next_e) => e.entry_index == next_e.entry_index,
};
let kind = match (e.errors.is_empty(), retry) {
(true, _) => CallContextKind::Success,
(false, true) => CallContextKind::Retry,
(false, false) => CallContextKind::Failure,
};
for (call_entry_index, _) in e.calls.iter().enumerate() {
let entry_src_index = e.entry_index - 1;
let entry_src = hurl_file.entries.get(entry_src_index).unwrap();
let line = entry_src.request.space0.source_info.start.line;
let ctx = CallContext {
success: e.errors.is_empty(),
kind,
line,
entry_index: entry_index + 1,
call_entry_index: call_entry_index + 1,

View File

@ -68,6 +68,11 @@ pub fn new_failure_icon(id: &str) -> Element {
new_icon(id, 512.px(), 512.px(), "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z", "red")
}
/// Returns the SVG retry icon identified by id.
pub fn new_retry_icon(id: &str) -> Element {
new_icon(id, 512.px(), 512.px(), "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z", "gold")
}
/// Returns a SVG icon identified by `id`, with a `width` pixel by `height` pixel size, `path` and `color`.
fn new_icon(id: &str, width: Pixel, height: Pixel, path: &str, color: &str) -> Element {
let mut symbol = svg::new_symbol();

View File

@ -30,9 +30,9 @@ use crate::report::html::timeline::unit::{
Byte, Interval, Microsecond, Millisecond, Pixel, Px, Scale, Second, TimeUnit,
};
use crate::report::html::timeline::util::{
new_failure_icon, new_stripes, new_success_icon, trunc_str,
new_failure_icon, new_retry_icon, new_stripes, new_success_icon, trunc_str,
};
use crate::report::html::timeline::{svg, CallContext, CALL_HEIGHT, CALL_INSET};
use crate::report::html::timeline::{svg, CallContext, CallContextKind, CALL_HEIGHT, CALL_INSET};
use crate::report::html::Testcase;
/// Returns the start and end date for these entries.
@ -79,6 +79,8 @@ impl Testcase {
root.add_child(elt);
let elt = new_failure_icon("failure");
root.add_child(elt);
let elt = new_retry_icon("retry");
root.add_child(elt);
// We add some space for the right last grid labels.
let pixels_x = Interval::new(0.px(), width);
@ -335,10 +337,10 @@ fn new_call_tooltip(
// Icon + URL + method
let mut elt = svg::new_use();
let icon = if call_ctx.success {
"#success"
} else {
"#failure"
let icon = match call_ctx.kind {
CallContextKind::Success => "#success",
CallContextKind::Failure => "#failure",
CallContextKind::Retry => "#retry",
};
elt.add_attr(Href(icon.to_string()));
elt.add_attr(X(x.0));
@ -351,7 +353,10 @@ fn new_call_tooltip(
let text = trunc_str(&text, 54);
let text = format!("{text} {}", call.response.status);
let mut elt = svg::new_text(x.0 + 30.0, y.0 + 16.0, &text);
let color = if call_ctx.success { "#555" } else { "red" };
let color = match call_ctx.kind {
CallContextKind::Success | CallContextKind::Retry => "#555",
CallContextKind::Failure => "red",
};
elt.add_attr(Fill(color.to_string()));
elt.add_attr(FontWeight("bold".to_string()));
group.add_child(elt);
@ -436,30 +441,40 @@ fn new_call_tooltip(
// Run URL
y += 56.px();
let run = format!(
let href = format!(
"{}#e{}:c{}",
call_ctx.run_filename, call_ctx.entry_index, call_ctx.call_entry_index
);
let mut elt = svg::new_text(x.0, y.0, "(view run)");
elt.add_attr(Fill("royalblue".to_string()));
elt.add_attr(TextDecoration("underline".to_string()));
let mut a = new_a(&run);
a.add_child(elt);
group.add_child(a);
let elt = new_link(x, y, "(view run)", &href);
group.add_child(elt);
// Source URL
let href = format!("{}#l{}", call_ctx.source_filename, call_ctx.line);
let elt = new_link(x + 90.px(), y, "(view source)", &href);
group.add_child(elt);
// Timings explanation
y += delta_y;
let run = format!("{}#l{}", call_ctx.source_filename, call_ctx.line);
let mut elt = svg::new_text(x.0, y.0, "(view source)");
elt.add_attr(Fill("royalblue".to_string()));
elt.add_attr(TextDecoration("underline".to_string()));
let mut a = new_a(&run);
a.add_child(elt);
group.add_child(a);
let elt = new_link(
x,
y,
"Explanation",
"https://hurl.dev/docs/response.html#timings",
);
group.add_child(elt);
group
}
fn new_link(x: Pixel, y: Pixel, text: &str, href: &str) -> Element {
let mut elt = svg::new_text(x.0, y.0, text);
elt.add_attr(Fill("royalblue".to_string()));
elt.add_attr(TextDecoration("underline".to_string()));
let mut a = new_a(href);
a.add_child(elt);
a
}
/// Returns the highlighted span time of a call.
fn new_call_sel(
call: &Call,
@ -472,8 +487,10 @@ fn new_call_sel(
let offset_x_start = to_pixel(offset_x_start, scale_x);
let offset_x_end = (call.timings.end_call - times.start).to_std().unwrap();
let offset_x_end = to_pixel(offset_x_end, scale_x);
let color = if call_ctx.success { "green" } else { "red" };
let color = match call_ctx.kind {
CallContextKind::Success | CallContextKind::Retry => "green",
CallContextKind::Failure => "red",
};
let mut elt = svg::new_rect(
offset_x_start.0,
0.0,