From 102e81174d8ba52a83b837d082825af837a3f04b Mon Sep 17 00:00:00 2001 From: iximeow Date: Sun, 25 Jun 2023 03:43:35 -0700 Subject: per-repo build histories --- src/dbctx.rs | 23 +++++++----- src/main.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/sql.rs | 4 +-- 3 files changed, 131 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/dbctx.rs b/src/dbctx.rs index c9454fc..f545158 100644 --- a/src/dbctx.rs +++ b/src/dbctx.rs @@ -347,15 +347,22 @@ impl DbCtx { } pub fn last_job_from_remote(&self, id: u64) -> Result, String> { + self.recent_jobs_from_remote(id, 1) + .map(|mut jobs| jobs.pop()) + } + + pub fn recent_jobs_from_remote(&self, id: u64, limit: u64) -> Result, String> { let conn = self.conn.lock().unwrap(); - let mut job_query = conn.prepare(sql::LAST_JOB_FROM_REMOTE).unwrap(); - let mut result = job_query.query([id]).unwrap(); + let mut job_query = conn.prepare(sql::LAST_JOBS_FROM_REMOTE).unwrap(); + let mut result = job_query.query([id, limit]).unwrap(); - let job = result.next().expect("can get next row, which may be None").map(|row| { - let (id, artifacts_path, state, run_host, remote_id, commit_id, created_time, start_time, complete_time, build_token, job_timeout, source, build_result, final_text) = row.try_into().unwrap(); + let mut jobs = Vec::new(); + + while let Some(row) = result.next().unwrap() { + let (id, artifacts_path, state, run_host, remote_id, commit_id, created_time, start_time, complete_time, build_token, job_timeout, source, build_result, final_text)= row.try_into().unwrap(); let state: u8 = state; - Job { + jobs.push(Job { id, artifacts_path, state: state.try_into().unwrap(), @@ -370,10 +377,10 @@ impl DbCtx { source, build_result, final_text, - } - }); + }); + } - Ok(job) + Ok(jobs) } pub fn get_pending_jobs(&self) -> Result, String> { diff --git a/src/main.rs b/src/main.rs index badbc3f..f4009bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -302,6 +302,8 @@ async fn handle_ci_index(State(ctx): State>) -> impl IntoResponse { } } + let repo_html = format!("{}", &repo.name, &repo.name); + let row_html: String = match most_recent_job { Some(job) => { let job_commit = ctx.commit_sha(job.commit_id).expect("job has a commit"); @@ -353,7 +355,7 @@ async fn handle_ci_index(State(ctx): State>) -> impl IntoResponse { } }; - let entries = [repo.name.as_str(), last_build_time.as_str(), job_html.as_str(), commit_html.as_str(), &duration, &status, &result]; + let entries = [repo_html.as_str(), last_build_time.as_str(), job_html.as_str(), commit_html.as_str(), &duration, &status, &result]; let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len()); let mut row_html = String::new(); @@ -363,7 +365,7 @@ async fn handle_ci_index(State(ctx): State>) -> impl IntoResponse { row_html } None => { - let entries = [repo.name.as_str()]; + let entries = [repo_html.as_str()]; let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len()); let mut row_html = String::new(); @@ -514,6 +516,115 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State( (StatusCode::OK, Html(html)) } +async fn handle_repo_summary(Path(path): Path, State(ctx): State>) -> impl IntoResponse { + eprintln!("get repo summary: {:?}", path); + + let mut last_builds = Vec::new(); + + let (repo_id, repo_name): (u64, String) = match ctx.conn.lock().unwrap() + .query_row("select id, repo_name from repos where repo_name=?1;", [&path], |row| Ok((row.get(0).unwrap(), row.get(1).unwrap()))) + .optional() + .unwrap() { + Some(elem) => elem, + None => { + eprintln!("no repo named {}", path); + return (StatusCode::NOT_FOUND, Html(String::new())); + } + }; + + for remote in ctx.remotes_by_repo(repo_id).expect("can get repo from a path") { + let mut last_ten_jobs = ctx.recent_jobs_from_remote(remote.id, 10).expect("can look up jobs for a repo"); + last_builds.extend(last_ten_jobs.drain(..)); + } + last_builds.sort_by_key(|job| -(job.created_time as i64)); + + let mut response = String::new(); + response.push_str("\n"); + response.push_str(&format!(" ci.butactuallyin.space - {} \n", repo_name)); + response.push_str("\n"); + response.push_str(&format!("

{} build history

\n", repo_name)); + response.push_str("full repos index

\n"); + + response.push_str(""); + response.push_str("\n"); + let headings = ["last build", "job", "build commit", "duration", "status", "result"]; + for heading in headings { + response.push_str(&format!("", heading)); + } + response.push_str("\n"); + + for job in last_builds.iter().take(10) { + let job_commit = ctx.commit_sha(job.commit_id).expect("job has a commit"); + let commit_html = match commit_url(&job, &job_commit, &ctx) { + Some(url) => format!("{}", url, &job_commit), + None => job_commit.clone() + }; + + let job_html = format!("{}", job_url(&job, &job_commit, &ctx), job.id); + + let last_build_time = Utc.timestamp_millis_opt(job.created_time as i64).unwrap().to_rfc2822(); + let duration = if let Some(start_time) = job.start_time { + if let Some(complete_time) = job.complete_time { + if complete_time < start_time { + if job.state == JobState::Started { + // this job has been restarted. the completed time is stale. + // further, this is a currently active job. + let now_ms = SystemTime::now().duration_since(UNIX_EPOCH).expect("now is after then").as_millis() as u64; + let mut duration = duration_as_human_string(now_ms - start_time); + duration.push_str(" (ongoing)"); + duration + } else { + "invalid data".to_string() + } + } else { + let duration_ms = complete_time - start_time; + let duration = duration_as_human_string(duration_ms); + duration + } + } else { + let now_ms = SystemTime::now().duration_since(UNIX_EPOCH).expect("now is after then").as_millis() as u64; + let mut duration = duration_as_human_string(now_ms - start_time); + duration.push_str(" (ongoing)"); + duration + } + } else { + "not yet run".to_owned() + }; + + let status = format!("{:?}", job.state).to_lowercase(); + + let result = match job.build_result { + Some(0) => "pass", + Some(_) => "fail", + None => match job.state { + JobState::Pending => { "unstarted" }, + JobState::Started => { "in progress" }, + _ => { "unreported" } + } + }; + + let entries = [last_build_time.as_str(), job_html.as_str(), commit_html.as_str(), &duration, &status, &result]; + let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len()); + + let mut row_html = String::new(); + for entry in entries { + row_html.push_str(&format!("", entry)); + } + + response.push_str(""); + response.push_str(&row_html); + response.push_str("\n"); + } + response.push_str(""); + + (StatusCode::OK, Html(response)) +} + async fn handle_repo_event(Path(path): Path<(String, String)>, headers: HeaderMap, State(ctx): State>, body: Bytes) -> impl IntoResponse { let json: Result = serde_json::from_slice(&body); eprintln!("repo event: {:?} {:?} {:?}", path.0, path.1, headers); @@ -630,6 +741,7 @@ async fn make_app_server(cfg_path: &PathBuf, db_path: &PathBuf) -> Router { Router::new() .route("/:owner/:repo/:sha", get(handle_commit_status)) + .route("/:owner", get(handle_repo_summary)) .route("/:owner/:repo", post(handle_repo_event)) .route("/", get(handle_ci_index)) .fallback(fallback_get) diff --git a/src/sql.rs b/src/sql.rs index b262e9a..6e0141a 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -110,6 +110,6 @@ pub const REMOTES_FOR_REPO: &'static str = "\ pub const ALL_REPOS: &'static str = "\ select * from repos;"; -pub const LAST_JOB_FROM_REMOTE: &'static str = "\ - select * from jobs where remote_id=?1 order by created_time desc limit 1;"; +pub const LAST_JOBS_FROM_REMOTE: &'static str = "\ + select * from jobs where remote_id=?1 order by created_time desc limit ?2;"; -- cgit v1.1
{}
{}