techtree/techtree-manager/src/issue.rs
Rahix cc05ba7407
All checks were successful
/ build (push) Successful in 1m4s
manager: Simplify very complex mermaid graphs
Forgejo has a limit of 5000 characters per mermaid graph.  We started
hitting this for some issues.  Regenerate a somewhat simplified version
of the subtree in this case which hopefully does not hit the same limit
again.
2025-05-25 03:44:07 +02:00

187 lines
5 KiB
Rust

use anyhow::Context as _;
pub type CommentId = u64;
pub struct BotCommentInfo {
pub body: String,
pub id: CommentId,
}
pub async fn make_bot_comment(
ctx: &crate::Context,
issue_number: u64,
) -> anyhow::Result<BotCommentInfo> {
let initial_message =
"_Please be patient, this issue is currently being integrated into the techtree..._";
let res = ctx
.forgejo
.issue_create_comment(
&ctx.owner,
&ctx.repo,
issue_number,
forgejo_api::structs::CreateIssueCommentOption {
body: initial_message.to_owned(),
updated_at: None,
},
)
.await?;
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<Option<BotCommentInfo>> {
let mut comments = ctx
.forgejo
.issue_get_comments(
&ctx.owner,
&ctx.repo,
issue_number,
forgejo_api::structs::IssueGetCommentsQuery {
..Default::default()
},
)
.await
.context("Failed fetching comments for issue")?;
comments.sort_by_key(|comment| comment.created_at);
let maybe_bot_comment = comments
.iter()
.rev()
.find(|comment| comment.user.as_ref().unwrap().id == Some(-2));
Ok(maybe_bot_comment.map(|c| BotCommentInfo {
body: c.body.clone().unwrap_or("".to_owned()),
id: c.id.unwrap(),
}))
}
/// 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,
new_body: String,
) -> anyhow::Result<()> {
ctx.forgejo
.issue_edit_comment(
&ctx.owner,
&ctx.repo,
id,
forgejo_api::structs::EditIssueCommentOption {
body: new_body,
updated_at: None,
},
)
.await
.context("Failed to update comment body")?;
Ok(())
}
pub async fn update_bot_comment_from_subtree(
ctx: &crate::Context,
id: CommentId,
subtree: &crate::tree::Subtree<'_>,
hash: &str,
) -> anyhow::Result<()> {
let mut mermaid = subtree.to_mermaid(&ctx.repo_url.to_string(), false);
// When the mermaid graph gets too big, regenerate a simplified version.
if mermaid.len() > 4990 {
log::info!("Mermaid graph is too big, generating simplified version...");
mermaid = subtree.to_mermaid(&ctx.repo_url.to_string(), true);
}
let full_text = if mermaid.len() > 4990 {
format!(
r##"## Partial Techtree
_Sorry, the partial techtree is still too big to be displayed for this element..._
<small>Digest: {hash}; Last Updated: {timestamp}</small>"##,
timestamp = ctx.timestamp,
)
} else {
format!(
r##"## Partial Techtree
```mermaid
{mermaid}
```
<small>Digest: {hash}; Last Updated: {timestamp}</small>"##,
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")?;
let stale_label_id = labels
.iter()
.filter(|l| l.name.as_deref() == Some("Stale"))
.next()
.map(|l| l.id.unwrap());
if let Some(stale_label_id) = stale_label_id {
log::info!("Removing `Stale` label from issue #{issue_number}...");
let res = ctx
.forgejo
.issue_remove_label(
&ctx.owner,
&ctx.repo,
issue_number,
stale_label_id,
forgejo_api::structs::DeleteLabelsOption {
updated_at: Some(time::OffsetDateTime::now_utc()),
},
)
.await;
if let Err(e) = res {
// At the moment, the token for Forgejo Actions cannot remove issue labels.
// See https://codeberg.org/forgejo/forgejo/issues/2415
log::warn!(
"Failed to remove `Stale` label for #{issue_number}. This is a known Forgejo limitation at the moment... ({e})"
);
}
}
Ok(())
}