dpm_model/work/
execution.rs1use crate::*;
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeSet;
6
7#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct ExecutionRecord {
11 pub status: WorkStatus,
13 #[serde(default)]
15 pub reported_progress_percent: u8,
16 pub artifact_ids: BTreeSet<ArtifactId>,
18 pub owner: Option<ActorId>,
20 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub handoffs: Vec<crate::Handoff>,
23 #[serde(default, skip_serializing_if = "Vec::is_empty")]
25 pub releases: Vec<crate::ClaimRelease>,
26 #[serde(default, skip_serializing_if = "crate::ExecutionEvents::is_empty")]
28 pub events: crate::ExecutionEvents,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub last_rejection: Option<ReviewRejection>,
32 #[serde(default, skip_serializing_if = "Vec::is_empty")]
34 pub attempts: Vec<crate::SubmissionAttempt>,
35 #[serde(default, skip_serializing_if = "Vec::is_empty")]
37 pub basis: Vec<crate::DependencyBasis>,
38 pub block_reason: Option<String>,
40}
41
42impl ExecutionRecord {
43 #[must_use]
49 pub fn latest_recorded_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
50 let events = [
51 self.events.claimed_at,
52 self.events.started_at,
53 self.events.submitted_at,
54 self.events.verified_at,
55 self.last_rejection.as_ref().map(|r| r.at),
56 ];
57 let reviews = self.attempts.iter().flat_map(|a| {
58 let closed = match &a.outcome {
59 AttemptOutcome::Pending => None,
60 AttemptOutcome::Rejected { at, .. } | AttemptOutcome::Verified { at, .. } => {
61 Some(*at)
62 }
63 };
64 [Some(a.submitted_at), closed]
65 });
66 events
67 .into_iter()
68 .chain(reviews)
69 .flatten()
70 .chain(self.handoffs.iter().map(|h| h.at))
71 .chain(self.releases.iter().map(|r| r.at))
72 .chain(self.basis.iter().map(|b| b.recorded_at))
73 .max()
74 }
75}
76
77#[cfg(test)]
78mod tests;