mirror of
https://seed.radicle.dev/rad:z3qg5TKmN83afz2fj9z3fQjU8vaYE.git
synced 2026-11-01 01:10:54 +08:00
135 lines
4.2 KiB
Rust
135 lines
4.2 KiB
Rust
use std::{
|
|
collections::HashSet,
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use html_page::{Document, Element, Tag};
|
|
use radicle_ci_broker::msg::RunResult;
|
|
use walkdir::WalkDir;
|
|
|
|
use crate::{LogFile, RunInfo};
|
|
|
|
const CSS: &str = include_str!("native-ci.css");
|
|
|
|
pub fn build_report(_log: &mut LogFile, state: &Path) -> Result<(), ReportError> {
|
|
let mut run_infos = collect_run_infos(state)?;
|
|
run_infos.sort_by(|a, b| a.timestamp.partial_cmp(&b.timestamp).unwrap());
|
|
|
|
let repos = repositories(&run_infos);
|
|
let doc = list_runs(&repos, &run_infos);
|
|
|
|
let index = state.join("index.html");
|
|
std::fs::write(&index, doc.to_string().as_bytes())
|
|
.map_err(|e| ReportError::WriteHtml(index.clone(), e))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn collect_run_infos(state: &Path) -> Result<Vec<RunInfo>, ReportError> {
|
|
let mut infos = vec![];
|
|
for entry in WalkDir::new(state) {
|
|
let entry = entry?;
|
|
if entry.path().ends_with("run.yaml") {
|
|
infos.push(read_file_info(entry.path())?);
|
|
}
|
|
}
|
|
Ok(infos)
|
|
}
|
|
|
|
fn repositories(infos: &[RunInfo]) -> Vec<&str> {
|
|
let mut ids: Vec<&str> = infos
|
|
.iter()
|
|
.map(|ri| ri.repo.as_str())
|
|
.collect::<HashSet<&str>>()
|
|
.iter()
|
|
.copied()
|
|
.collect();
|
|
ids.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
|
ids
|
|
}
|
|
|
|
fn runs_for_repo<'a>(repo: &str, infos: &'a [RunInfo]) -> Vec<&'a RunInfo> {
|
|
infos.iter().filter(|ri| ri.repo == repo).collect()
|
|
}
|
|
|
|
fn read_file_info(filename: &Path) -> Result<RunInfo, ReportError> {
|
|
let yaml = std::fs::read(filename).map_err(|e| ReportError::ReadRunInfo(filename.into(), e))?;
|
|
serde_yaml::from_slice(&yaml).map_err(|e| ReportError::DeserializeRunInfo(filename.into(), e))
|
|
}
|
|
|
|
fn list_runs(repos: &[&str], run_infos: &[RunInfo]) -> Document {
|
|
let mut doc = Document::default();
|
|
doc.push_to_head(&Element::new(Tag::Title).with_text("CI run logs"));
|
|
doc.push_to_head(&Element::new(Tag::Style).with_text(CSS));
|
|
|
|
for repo in repos {
|
|
let section = Element::new(Tag::H1)
|
|
.with_text("Repository ")
|
|
.with_child(Element::new(Tag::Code).with_text(repo));
|
|
doc.push_to_body(§ion);
|
|
|
|
let mut list = Element::new(Tag::Ol).with_class("runlist");
|
|
for ri in runs_for_repo(repo, run_infos) {
|
|
let commit = Element::new(Tag::Code)
|
|
.with_class("commit")
|
|
.with_text(&ri.commit);
|
|
|
|
let run_id = Element::new(Tag::Code)
|
|
.with_class("run_id")
|
|
.with_text(&ri.id);
|
|
|
|
let timestamp = Element::new(Tag::Span)
|
|
.with_class("timestamp")
|
|
.with_text(&ri.timestamp);
|
|
|
|
let (result_text, class) = match &ri.result {
|
|
RunResult::Success => ("success".into(), "success"),
|
|
RunResult::Failure => ("failure".into(), "failure"),
|
|
RunResult::Error(msg) => (format!("error: {}", msg), "failure"),
|
|
_ => (format!("unknown: {:?}", ri.result), "unknown"),
|
|
};
|
|
let result = Element::new(Tag::Span)
|
|
.with_class(class)
|
|
.with_text(&result_text);
|
|
|
|
let br = Element::new(Tag::Br);
|
|
|
|
let href = format!("{}", ri.log.display());
|
|
let link = Element::new(Tag::A)
|
|
.with_attribute("href", &href)
|
|
.with_text("CI run: ")
|
|
.with_child(run_id);
|
|
|
|
let item = Element::new(Tag::Li)
|
|
.with_child(timestamp)
|
|
.with_text(" ")
|
|
.with_child(result)
|
|
.with_child(br.clone())
|
|
.with_text("Commit: ")
|
|
.with_child(commit)
|
|
.with_child(br)
|
|
.with_child(link);
|
|
|
|
list.push_child(&item);
|
|
}
|
|
doc.push_to_body(&list);
|
|
}
|
|
|
|
doc
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum ReportError {
|
|
#[error(transparent)]
|
|
WalkDir(#[from] walkdir::Error),
|
|
|
|
#[error("failed to read file for run metadata: {0}")]
|
|
ReadRunInfo(PathBuf, #[source] std::io::Error),
|
|
|
|
#[error("failed to parse run metadata as YAML: {0}")]
|
|
DeserializeRunInfo(PathBuf, #[source] serde_yaml::Error),
|
|
|
|
#[error("failed to write HTML file {0}")]
|
|
WriteHtml(PathBuf, #[source] std::io::Error),
|
|
}
|