diff --git a/Cargo.lock b/Cargo.lock index f494d15..c49e80a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,7 @@ version = "0.1.0" dependencies = [ "chrono", "console_error_panic_hook", + "futures", "github-api", "gloo-net", "gloo-storage", diff --git a/Cargo.toml b/Cargo.toml index 419429a..7770689 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,3 +66,4 @@ tracing-wasm = "0.2" console_error_panic_hook = "0.1" web-sys = { version = "0.3", features = ["Window", "Location"] } js-sys = "0.3" +futures = "0.3" diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 34596d9..baee2e3 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -29,3 +29,4 @@ js-sys.workspace = true chrono.workspace = true thiserror.workspace = true serde.workspace = true +futures.workspace = true diff --git a/crates/app/index.html b/crates/app/index.html index ad84949..3ad5e2d 100644 --- a/crates/app/index.html +++ b/crates/app/index.html @@ -6,6 +6,18 @@ + + + Lepo — GitHub Repo Monitor diff --git a/crates/app/src/pages/dashboard.rs b/crates/app/src/pages/dashboard.rs index b0f83ef..93eb54e 100644 --- a/crates/app/src/pages/dashboard.rs +++ b/crates/app/src/pages/dashboard.rs @@ -1,6 +1,7 @@ //! Dashboard page: monitor many repos via a sortable table or compact cards //! (AGENTS.md §5.1, §5.2; DESIGN.md "Dashboard Pattern"). +use futures::future::join_all; use github_api::{GithubApi, GithubClient, IssueParams, PullParams}; use leptos::prelude::*; use leptos::reactive::callback::Callable; @@ -84,18 +85,23 @@ pub fn DashboardPage() -> impl IntoView { Some(c) => c, None => return Vec::new(), }; - let mut out = Vec::with_capacity(repos.len()); - for r in repos.iter() { - let (repo, issues, pulls, ci) = fetch_bundle(&client, r).await; - rate_limit.update(&client); - out.push(RepoCardData { - r#ref: r.clone(), + // Fetch every repo's bundle concurrently instead of one-at-a-time. + // Each bundle already fans out its 4 sub-requests internally, so the + // whole dashboard now completes in roughly one repo's worth of latency + // rather than N repos × 4 sequential requests. + let out: Vec = join_all(repos.iter().map(|r| fetch_bundle(&client, r))) + .await + .into_iter() + .enumerate() + .map(|(i, (repo, issues, pulls, ci))| RepoCardData { + r#ref: repos[i].clone(), repo, issues, pulls, ci, - }); - } + }) + .collect(); + rate_limit.update(&client); out } }); @@ -439,7 +445,8 @@ fn repo_table( } /// Fetches metadata, issues, pulls, and latest CI run for one repo, -/// tolerating partial failure on any single piece. +/// tolerating partial failure on any single piece. The four sub-requests run +/// concurrently so a single repo's bundle costs ~1 round-trip of latency. async fn fetch_bundle( client: &GithubClient, r: &RepoRef, @@ -449,40 +456,30 @@ async fn fetch_bundle( Vec, Option, ) { - let repo = client.get_repo(&r.owner, &r.name).await.ok(); - let issues = client - .list_issues( - &r.owner, - &r.name, - &IssueParams { - state: "open".into(), - labels: vec![], - sort: String::new(), - per_page: 30, - }, - ) - .await - .map(|(v, _)| v) - .unwrap_or_default(); - let pulls = client - .list_pulls( - &r.owner, - &r.name, - &PullParams { - state: "open".into(), - sort: String::new(), - per_page: 30, - }, - ) - .await - .map(|(v, _)| v) - .unwrap_or_default(); - let ci = client - .latest_workflow_run(&r.owner, &r.name) - .await - .ok() - .flatten(); - (repo, issues, pulls, ci) + let repo_fut = client.get_repo(&r.owner, &r.name); + let issue_params = IssueParams { + state: "open".into(), + labels: vec![], + sort: String::new(), + per_page: 30, + }; + let issues_fut = client.list_issues(&r.owner, &r.name, &issue_params); + let pull_params = PullParams { + state: "open".into(), + sort: String::new(), + per_page: 30, + }; + let pulls_fut = client.list_pulls(&r.owner, &r.name, &pull_params); + let ci_fut = client.latest_workflow_run(&r.owner, &r.name); + + let (repo, issues, pulls, ci) = futures::join!(repo_fut, issues_fut, pulls_fut, ci_fut,); + + ( + repo.ok(), + issues.map(|(v, _)| v).unwrap_or_default(), + pulls.map(|(v, _)| v).unwrap_or_default(), + ci.ok().flatten(), + ) } /// Compact card grid of monitored repos (used when the user prefers cards).