diff --git a/techtree-manager/src/issue.rs b/techtree-manager/src/issue.rs index d87b852..3fd50c7 100644 --- a/techtree-manager/src/issue.rs +++ b/techtree-manager/src/issue.rs @@ -10,11 +10,12 @@ pub struct BotCommentInfo { pub async fn make_bot_comment( ctx: &crate::Context, issue_number: u64, -) -> anyhow::Result { +) -> anyhow::Result { let initial_message = "_Please be patient, this issue is currently being integrated into the techtree..._"; - let res = ctx.forgejo + let res = ctx + .forgejo .issue_create_comment( &ctx.owner, &ctx.repo, @@ -26,14 +27,18 @@ pub async fn make_bot_comment( ) .await?; - Ok(res.id.unwrap()) + Ok(BotCommentInfo { + id: res.id.unwrap(), + body: initial_message.to_owned(), + }) } pub async fn find_bot_comment( ctx: &crate::Context, issue_number: u64, ) -> anyhow::Result> { - let mut comments = ctx.forgejo + let mut comments = ctx + .forgejo .issue_get_comments( &ctx.owner, &ctx.repo, @@ -58,6 +63,27 @@ pub async fn find_bot_comment( })) } +/// Find existing bot comment or create a new one. +/// +/// Returns a tuple of the comment information and a boolean indicating whether the comment was +/// newly created. +pub async fn find_or_make_bot_comment( + ctx: &crate::Context, + issue_number: u64, +) -> anyhow::Result<(BotCommentInfo, bool)> { + if let Some(comment) = find_bot_comment(ctx, issue_number) + .await + .context("Failed to search for bot comment in issue")? + { + Ok((comment, false)) + } else { + make_bot_comment(ctx, issue_number) + .await + .context("Failed to make new bot comment in issue") + .map(|c| (c, true)) + } +} + pub async fn update_bot_comment( ctx: &crate::Context, id: CommentId, @@ -79,11 +105,32 @@ pub async fn update_bot_comment( Ok(()) } -pub async fn remove_stale_label( +pub async fn update_bot_comment_from_subtree( ctx: &crate::Context, - issue_number: u64, + id: CommentId, + subtree: &crate::tree::Subtree<'_>, + hash: &str, ) -> anyhow::Result<()> { - let labels = ctx.forgejo + let mermaid = subtree.to_mermaid(&ctx.repo_url.to_string()); + + let full_text = format!( + r##"## Partial Techtree +```mermaid +{mermaid} +``` + +Digest: {hash}; Last Updated: {timestamp}"##, + timestamp = ctx.timestamp, + ); + + update_bot_comment(&ctx, id, full_text).await?; + + Ok(()) +} + +pub async fn remove_stale_label(ctx: &crate::Context, issue_number: u64) -> anyhow::Result<()> { + let labels = ctx + .forgejo .issue_get_labels(&ctx.owner, &ctx.repo, issue_number) .await .context("Failed fetching issue labels")?; @@ -97,7 +144,8 @@ pub async fn remove_stale_label( if let Some(stale_label_id) = stale_label_id { log::info!("Removing `Stale` label from issue #{issue_number}..."); - let res = ctx.forgejo + let res = ctx + .forgejo .issue_remove_label( &ctx.owner, &ctx.repo, diff --git a/techtree-manager/src/main.rs b/techtree-manager/src/main.rs index 4902e7a..a8b6ef1 100644 --- a/techtree-manager/src/main.rs +++ b/techtree-manager/src/main.rs @@ -72,64 +72,43 @@ async fn run() -> anyhow::Result<()> { timestamp, }; - let new_comment_id = if meta.action == event_meta::IssueAction::Opened { - let bot_comment = issue::find_bot_comment(&ctx, meta.issue.number) - .await - .with_context(|| { - format!( - "Failed searching for bot comment for issue #{}", - meta.issue.number - ) - })?; - - let id = match bot_comment { - Some(comment) => Some(comment.id), - None => { - let res = issue::make_bot_comment(&ctx, 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 - } + if meta.action == event_meta::IssueAction::Opened { + log::debug!("Running for a newly opened issue. Taking care of the comment first..."); + match issue::find_or_make_bot_comment(&ctx, meta.issue.number).await { + Ok((_comment, is_new)) => { + if is_new { + log::info!( + "Waiting for a minute, as the issue is brand new. We will likely catch some more updates this way!" + ); + tokio::time::sleep(std::time::Duration::from_secs(60)).await; } } - }; - - log::info!( - "Waiting for a minute, as the issue is brand new. We will likely catch some more updates this way!" - ); - tokio::time::sleep(std::time::Duration::from_secs(60)).await; - - id - } else { - None - }; + Err(err) => { + log::warn!( + "Error while commenting on new issue #{}, continuing anyway... Error: {err:?}", + meta.issue.number + ); + } + } + } + log::info!("Collecting tree from issue metadata..."); let tree = collect::collect_tree(&ctx) .await .context("Failed to collect the techtree from issue metadata")?; log::info!("Rendering and publishing techtree to git repository..."); - render::render_and_publish(&ctx, &tree) + let rendered_tree = render::render(&ctx, &tree) .await - .context("Failed to render and publish the techtree to git")?; + .context("Failed to render the techtree")?; + render::publish(&ctx, &rendered_tree) + .await + .context("Failed to publish rendered tree to git")?; + // Wiki is disabled because the tree is too big for mermaid to handle if false { - let mermaid = tree.to_mermaid(&ctx.repo_url.to_string()); - 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(&ctx, wiki_text) + wiki::update_wiki_from_tree(&ctx, &tree) .await .context("Failed to update the techtree wiki page")?; } @@ -138,52 +117,23 @@ async fn run() -> anyhow::Result<()> { 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(&ctx, issue) + let (bot_comment, _is_new) = issue::find_or_make_bot_comment(&ctx, issue) + .await + .with_context(|| format!("Failed to find or make bot comment in issue #{issue}"))?; + + if bot_comment.body.contains(&hash) { + log::info!("Issue #{issue} is up-to-date, not editing comment."); + issue::remove_stale_label(&ctx, issue) .await - .with_context(|| format!("Failed searching for bot comment for issue #{issue}"))?; + .with_context(|| format!("Failed to remove `Stale` label from 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."); - issue::remove_stale_label(&ctx, issue) - .await - .with_context(|| { - format!("Failed to remove `Stale` label from issue #{issue}") - })?; - - continue 'issues; - } - bot_comment.id - } else { - log::warn!("Missing bot comment in issue #{issue}"); - - issue::make_bot_comment(&ctx, issue) - .await - .with_context(|| { - format!("Failed to create a retrospective bot comment on issue #{issue}") - })? - } - }; - - let mermaid = subtree.to_mermaid(&ctx.repo_url.to_string()); - - let full_text = format!( - r##"## Partial Techtree -```mermaid -{mermaid} -``` - -Digest: {hash}; Last Updated: {timestamp}"##, - timestamp = ctx.timestamp, - ); + continue 'issues; + } log::info!("Updating bot comment in issue #{issue} ..."); - issue::update_bot_comment(&ctx, comment_id, full_text) + issue::update_bot_comment_from_subtree(&ctx, bot_comment.id, &subtree, &hash) .await - .with_context(|| format!("Failed to update the bot comment in issue #{issue}"))?; + .with_context(|| format!("Failed to update bot comment in issue #{issue}"))?; issue::remove_stale_label(&ctx, issue) .await diff --git a/techtree-manager/src/render.rs b/techtree-manager/src/render.rs index 69682e9..f3d75b9 100644 --- a/techtree-manager/src/render.rs +++ b/techtree-manager/src/render.rs @@ -15,45 +15,27 @@ impl SuccessExt for Command { } } -pub async fn render_and_publish( - ctx: &crate::Context, - tree: &crate::tree::Tree, -) -> anyhow::Result<()> { - let render_repo = std::path::PathBuf::from("render-git"); +pub struct RenderedTree { + repo_dir: std::path::PathBuf, + dot_file: std::path::PathBuf, + svg_file: std::path::PathBuf, +} - if render_repo.is_dir() { - log::info!("Found old {render_repo:?} repository, removing..."); - std::fs::remove_dir_all(&render_repo) - .context("Failed to remove stale render repository")?; +pub async fn render( + _ctx: &crate::Context, + tree: &crate::tree::Tree, +) -> anyhow::Result { + let repo_dir = std::path::PathBuf::from("render-git"); + + if repo_dir.is_dir() { + log::info!("Found old {repo_dir:?} repository, removing..."); + std::fs::remove_dir_all(&repo_dir).context("Failed to remove stale render repository")?; } - std::fs::create_dir(&render_repo).context("Failed creating directory for rendered graph")?; + std::fs::create_dir(&repo_dir).context("Failed creating directory for rendered graph")?; - Command::new("git") - .arg("-C") - .arg(&render_repo) - .arg("init") - .arg("--initial-branch=render") - .arg("--quiet") - .success() - .context("Failed to initialize render repository")?; - - Command::new("git") - .arg("-C") - .arg(&render_repo) - .args(["config", "user.email", "git@fa-fo.de"]) - .success() - .context("Failed to configure identity for render repo")?; - - Command::new("git") - .arg("-C") - .arg(&render_repo) - .args(["config", "user.name", "Forgejo Actions"]) - .success() - .context("Failed to configure identity for render repo")?; - - let dot_file = render_repo.join("techtree.dot"); - let svg_file = render_repo.join("techtree.svg"); + let dot_file = repo_dir.join("techtree.dot"); + let svg_file = repo_dir.join("techtree.svg"); let dot_source = tree.to_dot(); std::fs::write(&dot_file, dot_source.as_bytes()) @@ -67,18 +49,49 @@ pub async fn render_and_publish( .success() .context("Failed to generate svg graph from dot source")?; + Ok(RenderedTree { + repo_dir, + dot_file, + svg_file, + }) +} + +pub async fn publish(ctx: &crate::Context, rendered: &RenderedTree) -> anyhow::Result<()> { Command::new("git") .arg("-C") - .arg(&render_repo) + .arg(&rendered.repo_dir) + .arg("init") + .arg("--initial-branch=render") + .arg("--quiet") + .success() + .context("Failed to initialize render repository")?; + + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) + .args(["config", "user.email", "git@fa-fo.de"]) + .success() + .context("Failed to configure identity for render repo")?; + + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) + .args(["config", "user.name", "Forgejo Actions"]) + .success() + .context("Failed to configure identity for render repo")?; + + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) .arg("add") - .arg(dot_file.file_name().unwrap()) - .arg(svg_file.file_name().unwrap()) + .arg(rendered.dot_file.strip_prefix(&rendered.repo_dir).unwrap()) + .arg(rendered.svg_file.strip_prefix(&rendered.repo_dir).unwrap()) .success() .context("Failed to add generated graph files to git index")?; Command::new("git") .arg("-C") - .arg(&render_repo) + .arg(&rendered.repo_dir) .arg("commit") .args(["-m", &format!("Updated techtree at {}", ctx.timestamp)]) .success() @@ -86,7 +99,7 @@ pub async fn render_and_publish( Command::new("git") .arg("-C") - .arg(&render_repo) + .arg(&rendered.repo_dir) .arg("push") .arg("--force") .arg("--quiet") diff --git a/techtree-manager/src/tree.rs b/techtree-manager/src/tree.rs index bec1c64..dae74f2 100644 --- a/techtree-manager/src/tree.rs +++ b/techtree-manager/src/tree.rs @@ -166,11 +166,14 @@ impl Tree { &|_g, (_, element)| element.to_dot_node_attributes(None), ); - format!(r#"digraph {{ + format!( + r#"digraph {{ ranksep=1.2 {:?} }} -"#, dot) +"#, + dot + ) } pub fn to_mermaid(&self, repo_url: &str) -> String { diff --git a/techtree-manager/src/wiki.rs b/techtree-manager/src/wiki.rs index 0f1de44..9c48ee6 100644 --- a/techtree-manager/src/wiki.rs +++ b/techtree-manager/src/wiki.rs @@ -1,12 +1,29 @@ use anyhow::Context as _; use base64::prelude::*; -pub async fn update_wiki_overview( +pub async fn update_wiki_from_tree( ctx: &crate::Context, - new_body: String, + tree: &crate::tree::Tree, ) -> anyhow::Result<()> { + let mermaid = tree.to_mermaid(&ctx.repo_url.to_string()); + let wiki_text = format!( + r##"This page is automatically updated to show the latest and greatest FAFO techtree: + +```mermaid +{mermaid} +``` +"## + ); + update_wiki_overview(&ctx, wiki_text) + .await + .context("Failed to update the techtree wiki page")?; + Ok(()) +} + +pub async fn update_wiki_overview(ctx: &crate::Context, new_body: String) -> anyhow::Result<()> { // TODO: Figure out why we get a 404 when the edit was successfull... - let _ = ctx.forgejo + let _ = ctx + .forgejo .repo_edit_wiki_page( &ctx.owner, &ctx.repo,