Radish alpha
r
rad:z2UcCU1LgMshWvXj6hXSDDrwB8q8M
Radicle Job Collaborative Object
Radicle
Git
Add timestamp to Run information
Merged fintohaps opened 4 months ago

Use the COB operation timestamp to add a timestamp to each Run.

This information is also output when using the rad job CLI.

4 files changed +59 -29 525dc9a9 e37ff344
modified Cargo.lock
@@ -277,6 +277,15 @@ dependencies = [
]

[[package]]
+
name = "chrono"
+
version = "0.4.42"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
+
dependencies = [
+
 "num-traits",
+
]
+

+
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1364,6 +1373,7 @@ dependencies = [
name = "radicle-job"
version = "0.4.0"
dependencies = [
+
 "chrono",
 "clap",
 "indexmap",
 "nonempty 0.11.0",
modified Cargo.toml
@@ -12,6 +12,7 @@ edition = "2021"
rust-version = "1.84.0"

[dependencies]
+
chrono = { version = "0.4", default-features = false }
clap = { version = "4.5.41", features = ["derive", "wrap_help"] }
indexmap = { version = "2.7.1", features = ["serde"] }
nonempty = "0.11.0"
modified src/display.rs
@@ -3,7 +3,8 @@
//! These can be used in tools that wish to display data, such as the `rad-job`
//! CLI tool.

-
use radicle::{git::Oid, node::NodeId};
+
use chrono::Utc;
+
use radicle::{cob, git::Oid, node::NodeId};
use serde::Serialize;
use url::Url;
use uuid::Uuid;
@@ -78,6 +79,7 @@ impl Job {
                        run_id: *run_id,
                        status: *run.status(),
                        log: run.log().clone(),
+
                        timestamp: *run.timestamp(),
                    })
                    .collect();
                runs.sort_by_cached_key(|run| run.run_id);
@@ -109,7 +111,13 @@ impl Job {
        for run in self.runs.iter() {
            line(&mut s, format!("  node {}", run.node_id));
            for run2 in run.runs.iter() {
-
                line(&mut s, format!("    run {} {:?}", run2.run_id, run2.status));
+
                let date =
+
                    chrono::DateTime::<Utc>::from_timestamp_secs(run2.timestamp.as_secs() as i64)
+
                        .expect("run timestamp is out of range");
+
                line(
+
                    &mut s,
+
                    format!("    run {} {:?} [{}]", run2.run_id, run2.status, date),
+
                );
                line(&mut s, format!("      log  {}", run2.log));
            }
        }
@@ -129,4 +137,5 @@ struct Run {
    run_id: Uuid,
    status: Status,
    log: Url,
+
    timestamp: cob::Timestamp,
}
modified src/lib.rs
@@ -376,28 +376,34 @@ impl Job {
        }
    }

-
    fn update(&mut self, node: NodeId, uuid: Uuid, reason: Reason) -> bool {
+
    fn update(
+
        &mut self,
+
        node: NodeId,
+
        uuid: Uuid,
+
        reason: Reason,
+
        timestamp: cob::Timestamp,
+
    ) -> bool {
        let Some(runs) = self.runs.get_mut(&node) else {
            return false;
        };
        let mut updated = false;
        runs.0.entry(uuid).and_modify(|run| {
            updated = true;
-
            *run = run.clone().finish(reason);
+
            *run = run.clone().finish(reason, timestamp);
        });
        updated
    }

-
    fn action(&mut self, node: NodeId, action: Action) {
+
    fn action(&mut self, node: NodeId, action: Action, timestamp: cob::Timestamp) {
        match action {
            // Cannot request for another `oid`, so we ignore any superfluous
            // request actions
            Action::Request { .. } => {}
            Action::Run { uuid, log } => {
-
                self.insert(node, uuid, Run::new(log));
+
                self.insert(node, uuid, Run::new(log, timestamp));
            }
            Action::Finished { uuid, reason } => {
-
                self.update(node, uuid, reason);
+
                self.update(node, uuid, reason, timestamp);
            }
        }
    }
@@ -422,7 +428,7 @@ impl store::Cob for Job {
            .map_err(|err| error::Build::MissingCommit { oid, err })?;
        let mut runs = Self::new(oid);
        for action in actions {
-
            runs.action(op.author, action);
+
            runs.action(op.author, action, op.timestamp);
        }
        Ok(runs)
    }
@@ -434,7 +440,7 @@ impl store::Cob for Job {
        _repo: &R,
    ) -> Result<(), Self::Error> {
        for action in op.actions {
-
            self.action(op.author, action);
+
            self.action(op.author, action, op.timestamp);
        }
        Ok(())
    }
@@ -469,7 +475,7 @@ impl<R: ReadRepository> Evaluate<R> for Job {
///
/// The `Run` also contains a [`Url`] so that any extra metadata, for example
/// logs, can be tracked outside of the `Run` itself.
-
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Run {
    /// The status of the run.
    ///
@@ -480,23 +486,27 @@ pub struct Run {
    status: Status,
    /// The [`Url`] of the [`Run`] where information is logged by the node.
    log: Url,
+
    /// The timestamp of the last operation to affect the run.
+
    timestamp: cob::Timestamp,
}

impl Run {
    /// Create a new `Run` with a [`Url`] that ideally points to the log of that
    /// process.
-
    pub fn new(log: Url) -> Self {
+
    pub fn new(log: Url, timestamp: cob::Timestamp) -> Self {
        Self {
            status: Status::Started,
            log,
+
            timestamp,
        }
    }

    /// Mark the `Run` as finished.
-
    fn finish(self, reason: Reason) -> Self {
+
    fn finish(self, reason: Reason, timestamp: cob::Timestamp) -> Self {
        Self {
            status: Status::Finished(reason),
            log: self.log,
+
            timestamp,
        }
    }

@@ -510,6 +520,11 @@ impl Run {
        &self.status
    }

+
    /// Return the timestamp of the last update to the `Run`.
+
    pub fn timestamp(&self) -> &cob::Timestamp {
+
        &self.timestamp
+
    }
+

    /// Returns `true` if the status of the `Run` is [`Status::Started`].
    pub fn is_started(&self) -> bool {
        match self.status {
@@ -909,23 +924,17 @@ mod test {
        let (bob_uuid, bob_log) = node_run();
        job.run(bob_uuid, bob_log.clone(), &bob.signer).unwrap();

-
        let alice_runs = job.runs_of(alice.signer.public_key());
-
        assert!(alice_runs.is_some());
-
        assert_eq!(
-
            *alice_runs.unwrap(),
-
            [(alice_uuid, Run::new(alice_log))]
-
                .into_iter()
-
                .collect::<Runs>()
-
        );
-

-
        let bob_runs = job.runs_of(bob.signer.public_key());
-
        assert!(bob_runs.is_some());
-
        assert_eq!(
-
            *bob_runs.unwrap(),
-
            [(bob_uuid, Run::new(bob_log))]
-
                .into_iter()
-
                .collect::<Runs>()
-
        );
+
        let alice_runs = job.runs_of(alice.signer.public_key()).unwrap();
+
        assert!(alice_runs.contains_key(&alice_uuid));
+
        let run = alice_runs.get(&alice_uuid).unwrap();
+
        assert_eq!(run.status, Status::Started);
+
        assert_eq!(run.log, alice_log);
+

+
        let bob_runs = job.runs_of(bob.signer.public_key()).unwrap();
+
        assert!(bob_runs.contains_key(&bob_uuid));
+
        let run = bob_runs.get(&bob_uuid).unwrap();
+
        assert_eq!(run.status, Status::Started);
+
        assert_eq!(run.log, bob_log);

        job.finish(alice_uuid, Reason::Succeeded, &alice.signer)
            .unwrap();
@@ -987,6 +996,7 @@ mod test {
                Run {
                    status: Status::Started,
                    log: Url::parse("https://example.com/ci/logs").unwrap(),
+
                    timestamp: radicle::cob::Timestamp::from_secs(1358182),
                },
            );
        }