diff --git a/Cargo.lock b/Cargo.lock index 965901316..498768f7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,6 +191,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.98" @@ -801,6 +851,47 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + [[package]] name = "clipboard-win" version = "5.4.0" @@ -810,6 +901,12 @@ dependencies = [ "error-code", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "combine" version = "4.6.7" @@ -2561,6 +2658,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "0.4.8" @@ -3037,6 +3140,12 @@ dependencies = [ "serde", ] +[[package]] +name = "nonempty" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" + [[package]] name = "num-bigint" version = "0.4.6" @@ -3360,6 +3469,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -3952,7 +4067,7 @@ dependencies = [ "localtime", "log", "multibase", - "nonempty", + "nonempty 0.9.0", "qcheck", "radicle-cob", "radicle-crypto", @@ -3977,7 +4092,7 @@ dependencies = [ "fastrand", "git2", "log", - "nonempty", + "nonempty 0.9.0", "once_cell", "radicle-crypto", "radicle-dag", @@ -4033,6 +4148,25 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "radicle-job" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b2de1bb748a1b587f759137058c4ae917753652b5fb4ae3baee991a7b31a68" +dependencies = [ + "clap", + "indexmap 2.9.0", + "nonempty 0.11.0", + "once_cell", + "qcheck", + "radicle", + "serde", + "serde_json", + "thiserror 2.0.12", + "url", + "uuid", +] + [[package]] name = "radicle-ssh" version = "0.9.0" @@ -4062,7 +4196,7 @@ dependencies = [ "flate2", "git2", "log", - "nonempty", + "nonempty 0.9.0", "radicle-git-ext", "radicle-std-ext", "serde", @@ -4081,6 +4215,7 @@ dependencies = [ "infer", "log", "radicle", + "radicle-job", "radicle-surf", "radicle-types", "serde", @@ -4111,6 +4246,7 @@ dependencies = [ "log", "mime-infer", "radicle", + "radicle-job", "radicle-surf", "serde", "serde_json", @@ -5774,6 +5910,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +dependencies = [ + "rustix 1.0.7", + "windows-sys 0.59.0", +] + [[package]] name = "test-http-api" version = "0.1.0" @@ -6533,13 +6679,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "uuid" -version = "1.16.0" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] diff --git a/crates/radicle-tauri/Cargo.toml b/crates/radicle-tauri/Cargo.toml index 14f2cf595..f3d043aa5 100644 --- a/crates/radicle-tauri/Cargo.toml +++ b/crates/radicle-tauri/Cargo.toml @@ -21,6 +21,7 @@ either = { version = "1.15" } infer = { version = "0.19.0" } log = { version = "0.4.22" } radicle = { version = "0.16.1" } +radicle-job = { version = "0.2.0" } radicle-types = { version = "0.1.0", path = "../radicle-types" } radicle-surf = { version = "0.22.1", features = ["serde"] } serde = { version = "1.0.0", features = ["derive"] } diff --git a/crates/radicle-tauri/src/commands/cob.rs b/crates/radicle-tauri/src/commands/cob.rs index e9b54c7cf..aba884690 100644 --- a/crates/radicle-tauri/src/commands/cob.rs +++ b/crates/radicle-tauri/src/commands/cob.rs @@ -11,6 +11,7 @@ use tauri_plugin_dialog::DialogExt; use crate::AppState; pub mod issue; +pub mod job; pub mod patch; #[tauri::command] diff --git a/crates/radicle-tauri/src/commands/cob/job.rs b/crates/radicle-tauri/src/commands/cob/job.rs new file mode 100644 index 000000000..7120bd943 --- /dev/null +++ b/crates/radicle-tauri/src/commands/cob/job.rs @@ -0,0 +1,16 @@ +use radicle::git; +use radicle::identity::RepoId; +use radicle_types::cobs::job; +use radicle_types::error::Error; +use radicle_types::traits::job::Jobs; + +use crate::AppState; + +#[tauri::command] +pub fn list_jobs( + ctx: tauri::State, + rid: RepoId, + sha: git::Oid, +) -> Result, Error> { + ctx.list_jobs(rid, sha) +} diff --git a/crates/radicle-tauri/src/lib.rs b/crates/radicle-tauri/src/lib.rs index 997c7c125..366c2ea54 100644 --- a/crates/radicle-tauri/src/lib.rs +++ b/crates/radicle-tauri/src/lib.rs @@ -32,6 +32,7 @@ pub fn run() { cob::issue::issue_by_id, cob::issue::list_issues, cob::issue::rebuild_issue_cache, + cob::job::list_jobs, cob::patch::activity_by_patch, cob::patch::edit_patch, cob::patch::list_patches, diff --git a/crates/radicle-types/Cargo.toml b/crates/radicle-types/Cargo.toml index 88b6bfc90..775a8ee3e 100644 --- a/crates/radicle-types/Cargo.toml +++ b/crates/radicle-types/Cargo.toml @@ -12,6 +12,7 @@ log = { version = "0.4.22" } infer = { version = "0.19.0" } mime-infer = { version = "3.0.0" } radicle = { version = "0.16.1" } +radicle-job = { version = "0.2.0" } radicle-surf = { version = "0.22.1", features = ["serde"] } serde = { version = "1.0.0", features = ["derive"] } serde_json = { version = "1.0.0" } diff --git a/crates/radicle-types/bindings/repo/Job.ts b/crates/radicle-types/bindings/repo/Job.ts new file mode 100644 index 000000000..10c427cd2 --- /dev/null +++ b/crates/radicle-types/bindings/repo/Job.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Run } from "./Run"; + +export type Job = { jobId: string; commit: string; runs: Array }; diff --git a/crates/radicle-types/bindings/repo/Run.ts b/crates/radicle-types/bindings/repo/Run.ts new file mode 100644 index 000000000..a3b21d0ad --- /dev/null +++ b/crates/radicle-types/bindings/repo/Run.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Author } from "../cob/Author"; +import type { Status } from "./Status"; + +export type Run = { runId: string; node: Author; status: Status; log: string }; diff --git a/crates/radicle-types/bindings/repo/Status.ts b/crates/radicle-types/bindings/repo/Status.ts new file mode 100644 index 000000000..7810136ec --- /dev/null +++ b/crates/radicle-types/bindings/repo/Status.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type Status = "started" | "failed" | "succeeded"; diff --git a/crates/radicle-types/src/cobs.rs b/crates/radicle-types/src/cobs.rs index 4aefe15c2..c94734fa5 100644 --- a/crates/radicle-types/src/cobs.rs +++ b/crates/radicle-types/src/cobs.rs @@ -10,6 +10,7 @@ use radicle::node::{Alias, AliasStore}; pub mod diff; pub mod issue; +pub mod job; pub mod repo; pub mod stream; pub mod thread; diff --git a/crates/radicle-types/src/cobs/job.rs b/crates/radicle-types/src/cobs/job.rs new file mode 100644 index 000000000..7104ac15d --- /dev/null +++ b/crates/radicle-types/src/cobs/job.rs @@ -0,0 +1,80 @@ +use radicle::git; +use radicle::node::AliasStore; +use serde::Serialize; +use ts_rs::TS; +use url::Url; + +use crate::cobs; + +#[derive(Clone, Serialize, TS, Debug)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[ts(export_to = "repo/")] +pub struct Job { + #[ts(as = "String")] + pub job_id: radicle_job::JobId, + #[ts(as = "String")] + pub commit: git::Oid, + pub runs: Vec, +} + +impl PartialEq for Job { + fn eq(&self, other: &Self) -> bool { + self.job_id == other.job_id && self.commit == other.commit && self.runs == other.runs + } +} + +impl Job { + pub fn new(id: radicle_job::JobId, job: &radicle_job::Job, aliases: &impl AliasStore) -> Self { + Self { + job_id: id, + commit: *job.oid(), + runs: job + .runs() + .iter() + .flat_map(|(node_id, runs)| { + runs.iter().map(move |(run_id, run)| Run { + run_id: run_id.to_string(), + node: cobs::Author::new(&(*node_id).into(), aliases), + status: (*run.status()).into(), + log: run.log().clone(), + }) + }) + .collect(), + } + } +} + +#[derive(Clone, Serialize, TS, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[ts(export_to = "repo/")] +pub struct Run { + pub run_id: String, + pub node: cobs::Author, + pub status: Status, + #[ts(as = "String")] + pub log: Url, +} + +#[derive(Clone, Serialize, TS, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[ts(export_to = "repo/")] +pub enum Status { + Started, + Failed, + Succeeded, +} + +impl From for Status { + fn from(value: radicle_job::Status) -> Self { + match value { + radicle_job::Status::Started => Self::Started, + radicle_job::Status::Finished(reason) => match reason { + radicle_job::Reason::Failed => Self::Failed, + radicle_job::Reason::Succeeded => Self::Succeeded, + }, + } + } +} diff --git a/crates/radicle-types/src/lib.rs b/crates/radicle-types/src/lib.rs index b5dcfdad2..40185f8f2 100644 --- a/crates/radicle-types/src/lib.rs +++ b/crates/radicle-types/src/lib.rs @@ -1,5 +1,6 @@ use traits::cobs::Cobs; use traits::issue::{Issues, IssuesMut}; +use traits::job::Jobs; use traits::patch::{Patches, PatchesMut}; use traits::repo::Repo; use traits::thread::Thread; @@ -27,6 +28,7 @@ impl Thread for AppState {} impl Cobs for AppState {} impl Issues for AppState {} impl IssuesMut for AppState {} +impl Jobs for AppState {} impl Patches for AppState {} impl PatchesMut for AppState {} impl Profile for AppState { diff --git a/crates/radicle-types/src/traits.rs b/crates/radicle-types/src/traits.rs index 10b756603..ece5b1153 100644 --- a/crates/radicle-types/src/traits.rs +++ b/crates/radicle-types/src/traits.rs @@ -4,6 +4,7 @@ use crate::config::Config; pub mod cobs; pub mod issue; +pub mod job; pub mod patch; pub mod repo; pub mod thread; diff --git a/crates/radicle-types/src/traits/job.rs b/crates/radicle-types/src/traits/job.rs new file mode 100644 index 000000000..f054da28c --- /dev/null +++ b/crates/radicle-types/src/traits/job.rs @@ -0,0 +1,23 @@ +use radicle::storage::ReadStorage; +use radicle::{git, identity}; +use radicle_job::{Job, JobId, Jobs as JobsStore}; + +use crate::cobs::job; +use crate::error::Error; +use crate::traits::Profile; + +pub trait Jobs: Profile { + fn list_jobs(&self, rid: identity::RepoId, sha: git::Oid) -> Result, Error> { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let aliases = &profile.aliases(); + + let jobs = JobsStore::open(&repo).unwrap(); + let found_jobs: Result, _> = jobs.find_by_commit(sha)?.collect(); + + Ok(found_jobs? + .into_iter() + .map(|(id, job)| job::Job::new(id, &job, aliases)) + .collect()) + } +} diff --git a/crates/test-http-api/src/api.rs b/crates/test-http-api/src/api.rs index 48bf81fc6..f42d43174 100644 --- a/crates/test-http-api/src/api.rs +++ b/crates/test-http-api/src/api.rs @@ -25,6 +25,7 @@ use radicle_types::error::Error; use radicle_types::outbound::sqlite::Sqlite; use radicle_types::traits::cobs::Cobs; use radicle_types::traits::issue::{Issues, IssuesMut}; +use radicle_types::traits::job::Jobs; use radicle_types::traits::patch::{Patches, PatchesMut}; use radicle_types::traits::repo::{Repo, Show}; use radicle_types::traits::thread::Thread; @@ -41,6 +42,7 @@ impl Cobs for Context {} impl Thread for Context {} impl Issues for Context {} impl IssuesMut for Context {} +impl Jobs for Context {} impl Patches for Context {} impl PatchesMut for Context {} impl Profile for Context { @@ -90,6 +92,7 @@ pub fn router(ctx: Context) -> Router { .route("/save_embed_by_clipboard", post(save_embed_handler)) .route("/save_embed_by_bytes", post(save_embed_handler)) .route("/save_embed_to_disk", post(save_embed_handler)) + .route("/list_jobs", post(jobs_handler)) .layer( CorsLayer::new() .allow_origin(cors::Any) @@ -470,3 +473,18 @@ async fn revision_handler( Ok::<_, Error>(Json(revisions)) } + +#[derive(Serialize, Deserialize)] +struct JobsBody { + pub rid: identity::RepoId, + pub sha: git::Oid, +} + +async fn jobs_handler( + State(ctx): State, + Json(JobsBody { rid, sha }): Json, +) -> impl IntoResponse { + let jobs = ctx.list_jobs(rid, sha)?; + + Ok::<_, Error>(Json(jobs)) +} diff --git a/src/components/Changes.svelte b/src/components/Changes.svelte index 600b8072f..67e90a713 100644 --- a/src/components/Changes.svelte +++ b/src/components/Changes.svelte @@ -10,6 +10,7 @@ import CommitsContainer from "@app/components/CommitsContainer.svelte"; import Icon from "@app/components/Icon.svelte"; import Id from "@app/components/Id.svelte"; + import JobCob from "@app/components/JobCob.svelte"; import NakedButton from "@app/components/NakedButton.svelte"; interface Props { @@ -175,7 +176,7 @@
Base
- {#each [...commits].reverse() as commit} + {#each [...commits].reverse() as commit, idx}
+ {commit}> + {#if idx === commits.length - 1} + + {/if} +
{/each}
diff --git a/src/components/CobCommitTeaser.svelte b/src/components/CobCommitTeaser.svelte index 16896c30d..01ed6c2fb 100644 --- a/src/components/CobCommitTeaser.svelte +++ b/src/components/CobCommitTeaser.svelte @@ -1,5 +1,6 @@ @@ -99,6 +107,7 @@ {/if}
+ {@render children?.()}
+
{ + e.stopPropagation(); + }} onmouseenter={() => setVisible(true)} onmouseleave={() => setVisible(false)}> {@render toggle()} @@ -52,7 +58,9 @@
{@render popover()}
diff --git a/src/components/Icon.svelte b/src/components/Icon.svelte index 9711f3e3e..6d36b0576 100644 --- a/src/components/Icon.svelte +++ b/src/components/Icon.svelte @@ -21,6 +21,7 @@ | "checkbox-checked" | "checkbox-unchecked" | "checkmark" + | "checkmark-double" | "checkout" | "chevron-down" | "chevron-right" @@ -34,6 +35,7 @@ | "comment-cross" | "copy" | "cross" + | "cross-double" | "dashboard" | "delegate" | "diff" @@ -47,6 +49,7 @@ | "filter" | "folder-closed" | "folder-open" + | "help" | "home" | "hourglass" | "inbox" @@ -296,6 +299,23 @@ + {:else if name === "checkmark-double"} + + + + + + + + + + + + + + + + {:else if name === "checkout"} @@ -522,6 +542,33 @@ + {:else if name === "cross-double"} + + + + + + + + + + + + + + + + + + + + + + + + + + {:else if name === "dashboard"} @@ -721,6 +768,32 @@ + {:else if name === "help"} + + + + + + + + + + + + + + + + + + + + + + + + + {:else if name === "home"} diff --git a/src/components/JobCob.svelte b/src/components/JobCob.svelte new file mode 100644 index 000000000..503cfb94b --- /dev/null +++ b/src/components/JobCob.svelte @@ -0,0 +1,115 @@ + + + + +{#await invoke("list_jobs", { rid, sha: commit }) then jobs} + {#if jobs.length > 0} + + {#snippet toggle()} + {#if jobs.every(j => { + return j.runs.every(r => { + return r.status === "succeeded"; + }); + })} +
+ +
+ {:else if jobs.every(j => { + return j.runs.every(r => { + return r.status === "failed"; + }); + })} +
+ +
+ {:else} +
+ +
+ {/if} + {/snippet} + + {#snippet popover()} + {#each jobs as job} + {#each job.runs as run} + + + {#if run.status === "started"} +
+ Started +
+ {:else if run.status === "failed"} +
+ Failed +
+ {:else if run.status === "succeeded"} +
+ Passed +
+ {/if} + +
+ +
+
+
+ {/each} + {/each} + {/snippet} +
+ {/if} +{/await} diff --git a/src/components/RepoMetadata.svelte b/src/components/RepoMetadata.svelte index 5eaee997b..65f545028 100644 --- a/src/components/RepoMetadata.svelte +++ b/src/components/RepoMetadata.svelte @@ -11,6 +11,8 @@ import NodeId from "@app/components/NodeId.svelte"; import VisibilityBadge from "@app/components/VisibilityBadge.svelte"; + import JobCob from "./JobCob.svelte"; + interface Props { horizontal?: boolean; repo: RepoInfo; @@ -111,13 +113,14 @@ style:width="100%">