From 1af6596b51159ea89728128767233ea213b4d1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 24 Oct 2025 17:32:10 +0200 Subject: [PATCH] Enhance issue handling by adding `utils` module Introduce `append_related_issues` function and `IssueRelated` enum to manage related issue linking. Update Gitea action to utilize the new functionality for appending related issues in work package content. Add corresponding tests. --- src/gitea/action.rs | 4 + src/planning/mod.rs | 1 + src/planning/utils.rs | 180 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 src/planning/utils.rs diff --git a/src/gitea/action.rs b/src/gitea/action.rs index 5b0a0ae..b8c8cb9 100644 --- a/src/gitea/action.rs +++ b/src/gitea/action.rs @@ -7,6 +7,7 @@ use crate::openproject::user::{GetMe, User}; use crate::openproject::work::WorkPackageWriter; use crate::planning::Issue2WorkActionTrait; use url::Url; +use crate::planning::utils::{append_related_issues, IssueRelated}; pub(crate) struct GiteaAction {} @@ -37,6 +38,9 @@ impl Issue2WorkActionTrait for GiteaAction { config.openproject.base_url, args.project_id, work_package.id ); + let content = append_related_issues(&IssueRelated::OpenProjectIssue(url.to_string()), &issue.body); + + println!( "new work package created: {:?}, edit at {}", work_package.subject, url diff --git a/src/planning/mod.rs b/src/planning/mod.rs index 55261ba..775625e 100644 --- a/src/planning/mod.rs +++ b/src/planning/mod.rs @@ -4,6 +4,7 @@ use crate::error::GeneralError; use url::Url; pub(crate) mod issue2work; +pub mod utils; pub trait Issue2WorkActionTrait { async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError>; diff --git a/src/planning/utils.rs b/src/planning/utils.rs new file mode 100644 index 0000000..ef8dbf4 --- /dev/null +++ b/src/planning/utils.rs @@ -0,0 +1,180 @@ +pub enum IssueRelated { + OpenProjectIssue(String), +} + +pub fn append_related_issues(issue: &IssueRelated, content: &String) -> String { + let mut splitted = content.lines(); + let mut new_content: Vec = Vec::new(); + + let mut found = false; + let mut iteration_started = false; + while let Some(line) = splitted.next() { + if line.contains("## Related issues") { + found = true; + new_content.push(line.parse().unwrap()); + // we go to the end of the section + while let Some(line) = splitted.next() { + if line.starts_with("-") || line.starts_with("*") { + iteration_started = true; + new_content.push(line.parse().unwrap()); + } else if (line.trim().is_empty()) && iteration_started { + new_content.append(&mut add_related_issues_section(issue)); + iteration_started = false; + found = true; + new_content.push(line.parse().unwrap()); + break; + } else if line.starts_with("#") { + new_content.push("new title found".to_string()); + found = true; + new_content.append(&mut add_related_issues_section(issue)); + new_content.push("".to_string()); + new_content.push(line.parse().unwrap()); + break; + } else { + new_content.push(line.parse().unwrap()); + } + } + } else { + new_content.push(line.parse().unwrap()); + } + } + if !found || iteration_started { + if !found { + new_content.append(&mut add_related_issues_title()); + } + new_content.append(&mut add_related_issues_section(issue)); + } + + new_content.join(&"\n") +} + +fn add_related_issues_title() -> Vec { + let mut previous: Vec = Vec::new(); + previous.push("".to_string()); + previous.push("## Related issues".to_string()); + previous.push("".to_string()); + previous +} + +fn add_related_issues_section(issue: &IssueRelated) -> Vec { + let mut previous: Vec = Vec::new(); + previous.push(convert_issue_link_items(issue)); + + previous +} + +fn convert_issue_link_items(issue: &IssueRelated) -> String { + match issue { + IssueRelated::OpenProjectIssue(issue_url) => format!("- [{}]({})", issue_url, issue_url), + } +} + +#[cfg(test)] +mod tests { + use crate::planning::utils::{append_related_issues, IssueRelated}; + + #[test] + pub fn test_append_related_issues_content_empty() { + let issue = IssueRelated::OpenProjectIssue("https://example/wp/1".to_string()); + assert_eq!( + r#" +## Related issues + +- [https://example/wp/1](https://example/wp/1)"#, + append_related_issues(&issue, &("".to_string())) + ); + } + + #[test] + pub fn test_append_related_issues_content_not_empty() { + let issue = IssueRelated::OpenProjectIssue("https://example/wp/1".to_string()); + assert_eq!( + r#"Something happens. + +## Some title + +Some content + +## Related issues + +- [https://example/wp/1](https://example/wp/1)"#, + append_related_issues( + &issue, + &"Something happens.\n\ +\n\ +## Some title\n\ +\n\ +Some content" + .to_string() + ), + ); + } + + #[test] + pub fn test_append_related_issues_existing_section_related_issues() { + let issue = IssueRelated::OpenProjectIssue("https://example/wp/1".to_string()); + assert_eq!( + r#"Something happens. + +## Some title + +Some content + +## Related issues + +- [https://example/wp/2](https://example/wp/2) +- [https://example/wp/1](https://example/wp/1)"#, + append_related_issues( + &issue, + &r#"Something happens. + +## Some title + +Some content + +## Related issues + +- [https://example/wp/2](https://example/wp/2)"# + .to_string() + ), + ); + } + + #[test] + pub fn test_append_related_issues_existing_section_related_issues_not_last() { + let issue = IssueRelated::OpenProjectIssue("https://example/wp/1".to_string()); + assert_eq!( + r#"Something happens. + +## Some title + +Some content + +## Related issues + +- [https://example/wp/2](https://example/wp/2) +- [https://example/wp/1](https://example/wp/1) + +## Other content + +Some other content"#, + append_related_issues( + &issue, + &r#"Something happens. + +## Some title + +Some content + +## Related issues + +- [https://example/wp/2](https://example/wp/2) + +## Other content + +Some other content"# + .to_string() + ), + ); + } +}