mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-06 02:48:25 +00:00
fix: include duration_ms in stage.failed events for sidebar display
StageFailedProps lacked duration_ms, so extract_stage_durations_from_events only found durations from stage.completed events. Failed stages showed "0s" in the sidebar despite the command.completed event having the real duration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0c81f61af0
commit
de78094d7e
5 changed files with 41 additions and 30 deletions
|
|
@ -50,10 +50,12 @@ pub struct StageCompletedProps {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StageFailedProps {
|
||||
pub index: usize,
|
||||
pub index: usize,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub failure: Option<FailureDetail>,
|
||||
pub will_retry: bool,
|
||||
pub failure: Option<FailureDetail>,
|
||||
pub will_retry: bool,
|
||||
#[serde(default)]
|
||||
pub duration_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1700,11 +1700,12 @@ mod tests {
|
|||
// 3. Outcome → StageFailed event
|
||||
let failure = outcome.failure.clone().unwrap();
|
||||
let event = Event::StageFailed {
|
||||
node_id: "code".into(),
|
||||
name: "code".into(),
|
||||
index: 0,
|
||||
failure: failure.clone(),
|
||||
will_retry: false,
|
||||
node_id: "code".into(),
|
||||
name: "code".into(),
|
||||
index: 0,
|
||||
failure: failure.clone(),
|
||||
will_retry: false,
|
||||
duration_ms: 0,
|
||||
};
|
||||
|
||||
// 4. Verify classification survived all the way through
|
||||
|
|
|
|||
|
|
@ -181,11 +181,12 @@ pub enum Event {
|
|||
max_attempts: usize,
|
||||
},
|
||||
StageFailed {
|
||||
node_id: String,
|
||||
name: String,
|
||||
index: usize,
|
||||
failure: FailureDetail,
|
||||
will_retry: bool,
|
||||
node_id: String,
|
||||
name: String,
|
||||
index: usize,
|
||||
failure: FailureDetail,
|
||||
will_retry: bool,
|
||||
duration_ms: u64,
|
||||
},
|
||||
StageRetrying {
|
||||
node_id: String,
|
||||
|
|
@ -1652,11 +1653,13 @@ fn event_body_from_event(event: &Event) -> EventBody {
|
|||
index,
|
||||
failure,
|
||||
will_retry,
|
||||
duration_ms,
|
||||
..
|
||||
} => EventBody::StageFailed(fabro_types::StageFailedProps {
|
||||
index: *index,
|
||||
failure: Some(failure.clone()),
|
||||
will_retry: *will_retry,
|
||||
index: *index,
|
||||
failure: Some(failure.clone()),
|
||||
will_retry: *will_retry,
|
||||
duration_ms: *duration_ms,
|
||||
}),
|
||||
Event::StageRetrying {
|
||||
index,
|
||||
|
|
@ -2994,14 +2997,15 @@ mod tests {
|
|||
#[test]
|
||||
fn run_event_stage_failure_keeps_failure_detail() {
|
||||
let stored = to_run_event(&fixtures::RUN_3, &Event::StageFailed {
|
||||
node_id: "code".to_string(),
|
||||
name: "Code".to_string(),
|
||||
index: 1,
|
||||
failure: FailureDetail::new(
|
||||
node_id: "code".to_string(),
|
||||
name: "Code".to_string(),
|
||||
index: 1,
|
||||
failure: FailureDetail::new(
|
||||
"lint failed",
|
||||
crate::outcome::FailureCategory::Deterministic,
|
||||
),
|
||||
will_retry: true,
|
||||
will_retry: true,
|
||||
duration_ms: 5000,
|
||||
});
|
||||
|
||||
assert_eq!(stored.event_name(), "stage.failed");
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ pub fn extract_stage_durations_from_events(events: &[EventEnvelope]) -> HashMap<
|
|||
let mut durations = HashMap::new();
|
||||
for envelope in events {
|
||||
let value = envelope.payload.as_value();
|
||||
if value.get("event").and_then(serde_json::Value::as_str) != Some("stage.completed") {
|
||||
let event_name = value.get("event").and_then(serde_json::Value::as_str);
|
||||
if event_name != Some("stage.completed") && event_name != Some("stage.failed") {
|
||||
continue;
|
||||
}
|
||||
let Some(node_id) = value.get("node_id").and_then(serde_json::Value::as_str) else {
|
||||
|
|
|
|||
|
|
@ -209,15 +209,17 @@ impl RunLifecycle<WorkflowGraph> for EventLifecycle {
|
|||
let stage_index = state.stage_index;
|
||||
let scope = stage_scope_for(state, &gv.id);
|
||||
|
||||
let duration_ms = u64::try_from(ctx.result.duration.as_millis()).unwrap();
|
||||
self.emitter.emit_scoped(
|
||||
&Event::StageFailed {
|
||||
node_id: gv.id.clone(),
|
||||
name: gv.label().to_string(),
|
||||
index: stage_index,
|
||||
failure: outcome.failure.clone().unwrap_or_else(|| {
|
||||
node_id: gv.id.clone(),
|
||||
name: gv.label().to_string(),
|
||||
index: stage_index,
|
||||
failure: outcome.failure.clone().unwrap_or_else(|| {
|
||||
FailureDetail::new("handler failed", FailureCategory::TransientInfra)
|
||||
}),
|
||||
will_retry: true,
|
||||
duration_ms,
|
||||
},
|
||||
&scope,
|
||||
);
|
||||
|
|
@ -260,13 +262,14 @@ impl RunLifecycle<WorkflowGraph> for EventLifecycle {
|
|||
if outcome.status == StageStatus::Fail {
|
||||
self.emitter.emit_scoped(
|
||||
&Event::StageFailed {
|
||||
node_id: gv.id.clone(),
|
||||
name: gv.label().to_string(),
|
||||
index: stage_index,
|
||||
failure: outcome.failure.clone().unwrap_or_else(|| {
|
||||
node_id: gv.id.clone(),
|
||||
name: gv.label().to_string(),
|
||||
index: stage_index,
|
||||
failure: outcome.failure.clone().unwrap_or_else(|| {
|
||||
FailureDetail::new("handler failed", FailureCategory::Deterministic)
|
||||
}),
|
||||
will_retry: false,
|
||||
duration_ms,
|
||||
},
|
||||
&scope,
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue