summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2023-06-25 03:43:35 -0700
committeriximeow <me@iximeow.net>2023-06-25 03:43:35 -0700
commit102e81174d8ba52a83b837d082825af837a3f04b (patch)
tree2a1e98624b49ae2d19a7a092f38df21ea6def0a6 /src
parenta6a382d1a0328f63f13643c145243deff8ed10b9 (diff)
per-repo build histories
Diffstat (limited to 'src')
-rw-r--r--src/dbctx.rs23
-rw-r--r--src/main.rs116
-rw-r--r--src/sql.rs4
3 files changed, 131 insertions, 12 deletions
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<Option<Job>, 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<Vec<Job>, 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<Vec<PendingJob>, 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<Arc<DbCtx>>) -> impl IntoResponse {
}
}
+ let repo_html = format!("<a href=\"/{}\">{}</a>", &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<Arc<DbCtx>>) -> 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<Arc<DbCtx>>) -> 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<String>, State(ctx): State<Arc<DbCtx>>) -> 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("<html>\n");
+ response.push_str(&format!("<title> ci.butactuallyin.space - {} </title>\n", repo_name));
+ response.push_str("<style>\n");
+ response.push_str(".build-table { font-family: monospace; border: 1px solid black; border-collapse: collapse; }\n");
+ response.push_str(".row-item { padding-left: 4px; padding-right: 4px; border-right: 1px solid black; }\n");
+ response.push_str(".odd-row { background: #eee; }\n");
+ response.push_str(".even-row { background: #ddd; }\n");
+ response.push_str("</style>\n");
+ response.push_str(&format!("<h1>{} build history</h1>\n", repo_name));
+ response.push_str("<a href=/>full repos index</a><p> </p>\n");
+
+ response.push_str("<table class='build-table'>");
+ response.push_str("<tr>\n");
+ let headings = ["last build", "job", "build commit", "duration", "status", "result"];
+ for heading in headings {
+ response.push_str(&format!("<th class='row-item'>{}</th>", heading));
+ }
+ response.push_str("</tr>\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!("<a href=\"{}\">{}</a>", url, &job_commit),
+ None => job_commit.clone()
+ };
+
+ let job_html = format!("<a href=\"{}\">{}</a>", 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) => "<span style='color:green;'>pass</span>",
+ Some(_) => "<span style='color:red;'>fail</span>",
+ None => match job.state {
+ JobState::Pending => { "unstarted" },
+ JobState::Started => { "<span style='color:darkgoldenrod;'>in progress</span>" },
+ _ => { "<span style='color:red;'>unreported</span>" }
+ }
+ };
+
+ 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!("<td class='row-item'>{}</td>", entry));
+ }
+
+ response.push_str("<tr>");
+ response.push_str(&row_html);
+ response.push_str("</tr>\n");
+ }
+ response.push_str("</html>");
+
+ (StatusCode::OK, Html(response))
+}
+
async fn handle_repo_event(Path(path): Path<(String, String)>, headers: HeaderMap, State(ctx): State<Arc<DbCtx>>, body: Bytes) -> impl IntoResponse {
let json: Result<serde_json::Value, _> = 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;";