dpm_engine/run/
projection.rs1use chrono::{DateTime, TimeDelta, Utc};
8use dpm_model::{
9 LineageId, LineageStatus, ObservedStatus, Orphan, Plan, RunSnapshot, RunState, RunView,
10 WorkStatus,
11};
12
13pub const STALE_AFTER: TimeDelta = TimeDelta::seconds(300);
15
16#[must_use]
21pub fn project(
22 snapshot: RunSnapshot,
23 plan: Option<&Plan>,
24 lineage: Option<LineageId>,
25 now: DateTime<Utc>,
26) -> RunView {
27 let RunSnapshot {
28 record,
29 last,
30 activity,
31 operations,
32 } = snapshot;
33 let current = super::same_history(record.contract.lineage_id, lineage);
34 let last_receipt_at = activity.latest.as_ref().map_or(last.recorded_at, |latest| {
35 latest.recorded_at.max(last.recorded_at)
36 });
37 let silent = now.signed_duration_since(last_receipt_at) >= STALE_AFTER;
38 let status = match last.state {
39 RunState::Failed => ObservedStatus::Failed,
40 RunState::Interrupted => ObservedStatus::Interrupted,
41 RunState::Completed => ObservedStatus::Completed,
42 _ if !current => ObservedStatus::Unknown,
43 _ if silent => ObservedStatus::Stale,
44 RunState::Working => ObservedStatus::Working,
45 RunState::Waiting => ObservedStatus::Waiting,
46 };
47 let stale_at = matches!(status, ObservedStatus::Working | ObservedStatus::Waiting)
48 .then(|| super::stale_after(last_receipt_at))
49 .flatten();
50 let orphan = (!last.state.is_terminal() && current)
51 .then(|| plan.and_then(|plan| orphan(&record, plan)))
52 .flatten();
53 RunView {
54 state: last.state,
55 state_since: last.recorded_at,
56 state_detail: last.detail,
57 status,
58 last_receipt_at,
59 stale_at,
60 activity,
61 lineage: if current {
62 LineageStatus::Current
63 } else {
64 LineageStatus::Foreign
65 },
66 orphan,
67 operations,
68 evaluated_at: now,
69 run: record,
70 }
71}
72
73fn orphan(record: &dpm_model::RunRecord, plan: &Plan) -> Option<Orphan> {
74 let Some(work) = plan.work_items.get(&record.work) else {
75 return Some(Orphan::WorkMissing);
76 };
77 if work.execution.owner.as_ref() != Some(&record.executor) {
78 return Some(Orphan::OwnerChanged {
79 owner: work.execution.owner.clone(),
80 });
81 }
82 match work.execution.status {
83 WorkStatus::Claimed | WorkStatus::InProgress => None,
84 status => Some(Orphan::NotExecuting { status }),
85 }
86}