160 lines
5.2 KiB
Rust
160 lines
5.2 KiB
Rust
use anyhow::Context as _;
|
|
|
|
/// Read all issues to generate the full techtree
|
|
pub async fn collect_tree(ctx: &crate::Context) -> anyhow::Result<crate::tree::Tree> {
|
|
let mut issues = vec![];
|
|
for page in 1.. {
|
|
let new = ctx
|
|
.forgejo
|
|
.issue_list_issues(
|
|
&ctx.owner,
|
|
&ctx.repo,
|
|
forgejo_api::structs::IssueListIssuesQuery {
|
|
// We also want the closed issues
|
|
state: Some(forgejo_api::structs::IssueListIssuesQueryState::All),
|
|
// We cannot turn off pagination entirely, but let's set the limit as high as
|
|
// Forgejo lets us.
|
|
limit: Some(10000),
|
|
page: Some(page),
|
|
// Only issues
|
|
r#type: Some(forgejo_api::structs::IssueListIssuesQueryType::Issues),
|
|
..Default::default()
|
|
},
|
|
)
|
|
.await
|
|
.with_context(|| format!("Failed fetching page {page} of the issue list"))?;
|
|
|
|
if new.len() == 0 {
|
|
break;
|
|
}
|
|
|
|
issues.extend(new);
|
|
}
|
|
|
|
let mut tree = crate::tree::Tree::new();
|
|
let mut issue_numbers = Vec::new();
|
|
|
|
for issue in issues.iter() {
|
|
let element = match element_from_issue(issue) {
|
|
Ok(el) => el,
|
|
Err(e) => {
|
|
let maybe_number = issue
|
|
.number
|
|
.map(|n| n.to_string())
|
|
.unwrap_or("<unknown>".to_owned());
|
|
log::warn!("Failed processing issue #{maybe_number}: {e:?}");
|
|
continue;
|
|
}
|
|
};
|
|
|
|
issue_numbers.push(element.issue_number);
|
|
tree.add_element(element);
|
|
}
|
|
|
|
for issue in issue_numbers.into_iter() {
|
|
let dependencies = ctx
|
|
.forgejo
|
|
.issue_list_issue_dependencies(
|
|
&ctx.owner,
|
|
&ctx.repo,
|
|
// Why the hell is the issue number a string here?
|
|
&issue.to_string(),
|
|
forgejo_api::structs::IssueListIssueDependenciesQuery {
|
|
limit: Some(10000),
|
|
..Default::default()
|
|
},
|
|
)
|
|
.await
|
|
.with_context(|| format!("Failed to fetch issue dependencies for #{issue}",))?;
|
|
|
|
for dep in dependencies {
|
|
// Check that the dependency is actually an issue in the techtree and not external
|
|
let dep_repo = dep
|
|
.repository
|
|
.context("Dependency issue without repository info")?;
|
|
if dep_repo.owner.as_ref() != Some(&ctx.owner)
|
|
|| dep_repo.name.as_ref() != Some(&ctx.repo)
|
|
{
|
|
log::warn!(
|
|
"Issue #{issue} depends on external {}#{}, ignoring.",
|
|
dep_repo.full_name.as_deref().unwrap_or("unknown?"),
|
|
dep.number.unwrap_or(9999)
|
|
);
|
|
continue;
|
|
}
|
|
|
|
let dep_number = dep.number.context("Missing issue number in dependency")?;
|
|
if !tree.find_element_by_issue_number(dep_number).is_some() {
|
|
log::warn!("Found dependency from #{issue} on non-tracked issue #{dep_number}!");
|
|
} else {
|
|
tree.add_dependency_by_issue_number(issue, dep_number);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(tree)
|
|
}
|
|
|
|
fn element_from_issue(issue: &forgejo_api::structs::Issue) -> anyhow::Result<crate::tree::Element> {
|
|
let issue_number = issue.number.context("Missing issue number")?;
|
|
let description = issue
|
|
.title
|
|
.as_deref()
|
|
.context("Issue is missing a title")?
|
|
.to_owned();
|
|
|
|
let labels = issue
|
|
.labels
|
|
.as_ref()
|
|
.context("Issue does not have any labels")?;
|
|
|
|
let ty_labels: Vec<_> = labels
|
|
.iter()
|
|
.filter_map(|l| l.name.as_deref())
|
|
.filter(|l| l.starts_with("Type/"))
|
|
.collect();
|
|
|
|
if ty_labels.len() == 0 {
|
|
anyhow::bail!("Issue #{issue_number} has no type label!");
|
|
}
|
|
if ty_labels.len() > 1 {
|
|
anyhow::bail!("Issue #{issue_number} has more than one type label!");
|
|
}
|
|
|
|
let ty = ty_labels
|
|
.first()
|
|
.expect("TODO")
|
|
.strip_prefix("Type/")
|
|
.expect("TODO")
|
|
.to_owned();
|
|
|
|
let has_completed_label = labels
|
|
.iter()
|
|
.any(|l| l.name.as_deref() == Some("Completed"));
|
|
|
|
let status = match issue.state.context("Missing issue state")? {
|
|
forgejo_api::structs::StateType::Open => {
|
|
if has_completed_label {
|
|
crate::tree::ElementStatus::Completed
|
|
} else if issue.assignee.is_some()
|
|
|| issue
|
|
.assignees
|
|
.as_ref()
|
|
.map(|v| v.len() > 0)
|
|
.unwrap_or(false)
|
|
{
|
|
crate::tree::ElementStatus::Assigned
|
|
} else {
|
|
crate::tree::ElementStatus::Missing
|
|
}
|
|
}
|
|
forgejo_api::structs::StateType::Closed => anyhow::bail!("Ignoring closed issue!"),
|
|
};
|
|
|
|
Ok(crate::tree::Element {
|
|
issue_number,
|
|
description,
|
|
ty,
|
|
status,
|
|
})
|
|
}
|