manager: Add first version of the techtree manager

The techtree manager is a CI tool that derives the techtree graph from
the forgejo issues in this repository.  It then adds graph
visualizations to each issue and the wiki, to give an overview of the
tree.
This commit is contained in:
Rahix 2025-05-22 09:48:01 +02:00
parent 8ab73f4a4a
commit 870e54e263
9 changed files with 2768 additions and 0 deletions

View file

@ -0,0 +1,125 @@
use anyhow::Context as _;
use forgejo_api::Forgejo;
mod collect;
mod event_meta;
mod issue;
mod tree;
mod wiki;
async fn run() -> anyhow::Result<()> {
let meta = if std::env::var("TECHTREE_FAKE").ok().is_some() {
log::warn!("Fake tree!");
event_meta::fake()
} else {
event_meta::get_issue_event_meta_from_env().context("Failed reading issue event data")?
};
log::info!(
"Running due to event \"{:?}\" on issue #{} ...",
meta.action,
meta.issue.number
);
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
log::info!("Timestamp of this run is {timestamp}");
let token =
std::env::var("GITHUB_TOKEN").context("Failed accessing GITHUB_TOKEN auth token")?;
let auth = forgejo_api::Auth::Token(&token);
let server_url = url::Url::parse(
&std::env::var("GITHUB_SERVER_URL")
.context("Failed reading GITHUB_SERVER_URL server url")?,
)
.context("Failed parsing GITHUB_SERVER_URL as a url")?;
let forgejo = Forgejo::new(auth, server_url).context("Could not create API access object")?;
let new_comment_id = if meta.action == event_meta::IssueAction::Opened {
let res = issue::make_bot_comment(&forgejo, &meta, meta.issue.number).await;
match res {
Ok(id) => Some(id),
Err(e) => {
log::warn!(
"Error while creating the informational comment on issue #{}:\n{e:?}",
meta.issue.number
);
None
}
}
} else {
None
};
let tree = collect::collect_tree(&forgejo, &meta)
.await
.context("Failed to collect the techtree from issue metadata")?;
let mermaid = tree.to_mermaid();
let wiki_text = format!(
r##"This page is automatically updated to show the latest and greatest FAFO techtree:
```mermaid
{mermaid}
```
"##
);
log::info!("Updating the wiki overview...");
wiki::update_wiki_overview(&forgejo, &meta.issue.repository, timestamp.to_string(), wiki_text)
.await
.context("Failed to update the techtree wiki page")?;
'issues: for issue in tree.iter_issues() {
let subtree = tree.subtree_for_issue(issue).unwrap();
let hash = subtree.stable_hash();
let comment_id = if new_comment_id.is_some() && issue == meta.issue.number {
new_comment_id.unwrap()
} else {
let bot_comment = issue::find_bot_comment(&forgejo, &meta.issue.repository, issue)
.await
.with_context(|| format!("Failed searching for bot comment for issue #{issue}"))?;
if let Some(bot_comment) = bot_comment {
if bot_comment.body.contains(&hash) {
log::info!("Issue #{issue} is up-to-date, not editing comment.");
continue 'issues;
}
bot_comment.id
} else {
log::warn!("Missing bot comment in issue #{issue}");
issue::make_bot_comment(&forgejo, &meta, issue)
.await
.with_context(|| {
format!("Failed to create a retrospective bot comment on issue #{issue}")
})?
}
};
let mermaid = subtree.to_mermaid();
let full_text = format!(
r##"## Partial Techtree
```mermaid
{mermaid}
```
<small>Digest: {hash}; Last Updated: {timestamp}</small>"##
);
log::info!("Updating bot comment in issue #{issue} ...");
issue::update_bot_comment(&forgejo, &meta.issue.repository, comment_id, full_text)
.await
.with_context(|| format!("Failed to update the bot comment in issue #{issue}"))?;
}
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
run().await
}