3 Commits

Author SHA1 Message Date
2b37c5b18c Add Gitea Action support for issue-to-work conversion
Implemented Gitea actions to support converting Gitea issues into work packages in OpenProject. Modified various modules to include necessary structs, functions, and tests to handle Gitea issues, ensure URL validation, and adapt work package creation based on Gitea issues.
2024-10-25 00:29:34 +02:00
9ef98e5044 Refactor the issue2work action
- separation the logic to handle issues from gitlab into the gitlab mod
- create a kind of visitor pattern to check between the different providers
2024-10-24 23:14:14 +02:00
9aec267e0a WIP on integrate gitea 2024-05-20 19:16:21 +02:00
11 changed files with 39 additions and 62 deletions

View File

@@ -0,0 +1,4 @@
kind: Added
body: Add an option `--assign-to-me` to automatically assign the openproject's user
to the newly created work package
time: 2024-03-17T21:35:42.724855046+01:00

View File

@@ -0,0 +1,3 @@
kind: Added
body: Allow to configure multiple gitlab instances
time: 2024-03-17T21:35:56.447907065+01:00

View File

@@ -1,5 +0,0 @@
## v0.2.0 - 2024-11-14
### Added
* Add an option `--assign-to-me` to automatically assign the openproject's user to the newly created work package
* Allow to configure multiple gitlab instances
* Create work pakcage from gitea

View File

@@ -6,12 +6,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v0.2.0 - 2024-11-14
### Added
* Add an option `--assign-to-me` to automatically assign the openproject's user to the newly created work package
* Allow to configure multiple gitlab instances
* Create work pakcage from gitea
## v0.1.0 - 2024-01-08 ## v0.1.0 - 2024-01-08
### Added ### Added
* Initiate changie versioning * Initiate changie versioning

View File

@@ -1,6 +1,9 @@
use crate::config::Config; use crate::config::Config;
use crate::error::GeneralError; use crate::error::GeneralError;
use crate::gitea::issue::Issue; use crate::gitea::issue::Issue;
use crate::openproject::client::Client;
use crate::openproject::root::RootClient;
use crate::openproject::user::GetMe;
use url::Url; use url::Url;
pub(crate) async fn debug(config: Config) -> Result<(), GeneralError> { pub(crate) async fn debug(config: Config) -> Result<(), GeneralError> {
@@ -16,9 +19,8 @@ pub(crate) async fn debug(config: Config) -> Result<(), GeneralError> {
println!("issue: {:?}", issue); println!("issue: {:?}", issue);
Ok(()) return Ok(());
/*
let open_project_client = Client::from_config(&config.openproject); let open_project_client = Client::from_config(&config.openproject);
println!("base_url: {}", open_project_client.base_url); println!("base_url: {}", open_project_client.base_url);
println!("base_url: will get root"); println!("base_url: will get root");
@@ -28,6 +30,4 @@ pub(crate) async fn debug(config: Config) -> Result<(), GeneralError> {
println!("me: {:?}", u); println!("me: {:?}", u);
Ok(()) Ok(())
*/
} }

View File

@@ -1,21 +1,22 @@
use url::Url;
use crate::cli::Issue2Work; use crate::cli::Issue2Work;
use crate::config::Config; use crate::config::Config;
use crate::error::GeneralError; use crate::error::GeneralError;
use crate::gitea::client::has_client_for_url;
use crate::gitea::issue::{issue_html_url_to_api, Issue}; use crate::gitea::issue::{issue_html_url_to_api, Issue};
use crate::gitea::client::has_client_for_url;
use crate::openproject::user::{GetMe, User}; use crate::openproject::user::{GetMe, User};
use crate::openproject::work::WorkPackageWriter; use crate::openproject::work::{WorkPackage, WorkPackageWriter, WorkPackageWriterAssignee};
use crate::planning::Issue2WorkActionTrait; use crate::planning::Issue2WorkActionTrait;
use url::Url;
pub(crate) struct GiteaAction {} pub(crate) struct GiteaAction {}
impl Issue2WorkActionTrait for GiteaAction { impl Issue2WorkActionTrait for GiteaAction {
async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> { async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> {
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap()); let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
let issue: Issue = gitea_client.get(issue_html_url_to_api(url)?).await?; let issue: Issue = gitea_client
let open_project_client = .get(issue_html_url_to_api(url)?)
crate::openproject::client::Client::from_config(&config.openproject); .await?;
let open_project_client = crate::openproject::client::Client::from_config(&config.openproject);
let work_package = create_work_package_from_issue( let work_package = create_work_package_from_issue(
&issue, &issue,
@@ -25,7 +26,7 @@ impl Issue2WorkActionTrait for GiteaAction {
Some(u) Some(u)
} }
false => None, false => None,
}, }
); );
let work_package = open_project_client let work_package = open_project_client
@@ -47,12 +48,16 @@ impl Issue2WorkActionTrait for GiteaAction {
fn create_work_package_from_issue(issue: &Issue, assignee: Option<User>) -> WorkPackageWriter { fn create_work_package_from_issue(issue: &Issue, assignee: Option<User>) -> WorkPackageWriter {
WorkPackageWriter { WorkPackageWriter {
subject: format!("{} ({}/{})", issue.title, issue.repository.full_name, issue.number), subject: format!(
"{} ({})",
issue.title,
issue.repository.full_name
),
work_type: "TASK".into(), work_type: "TASK".into(),
description: crate::openproject::work::DescriptionWriter { description: crate::openproject::work::DescriptionWriter {
format: "markdown".into(), format: "markdown".into(),
raw: format!("From Gitea issue: {} \n\n{}", issue.html_url, issue.body), raw: format!("From Gitea issue: {} \n\n{}", issue.html_url, issue.body),
}, },
assignee: assignee.into(), assignee: assignee.into()
} }
} }

View File

@@ -1,4 +1,4 @@
use crate::config::{Config, GiteaConfig}; use crate::config::{Config, GiteaConfig, GitlabConfig};
use crate::error::GeneralError; use crate::error::GeneralError;
use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION}; use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION};
use reqwest::{ClientBuilder, StatusCode}; use reqwest::{ClientBuilder, StatusCode};
@@ -54,7 +54,7 @@ impl Client {
pub async fn get<T: DeserializeOwned>(&self, url: Url) -> Result<T, GeneralError> { pub async fn get<T: DeserializeOwned>(&self, url: Url) -> Result<T, GeneralError> {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.append(AUTHORIZATION, format!("token {}", self.token).parse()?); headers.append(AUTHORIZATION, self.token.parse()?);
headers.append(ACCEPT, "application/json".parse()?); headers.append(ACCEPT, "application/json".parse()?);
let client = ClientBuilder::new() let client = ClientBuilder::new()
@@ -92,19 +92,7 @@ mod test {
token: "<PASSWORD>".into(), token: "<PASSWORD>".into(),
}; };
assert_eq!( assert_eq!(is_client_for_url(&Url::parse("https://gitea.champs-libres.be/something/somewhere").unwrap(), &config), true);
is_client_for_url( assert_eq!(is_client_for_url(&Url::parse("https://somewhere.else/something/somewhere").unwrap(), &config), false);
&Url::parse("https://gitea.champs-libres.be/something/somewhere").unwrap(),
&config
),
true
);
assert_eq!(
is_client_for_url(
&Url::parse("https://somewhere.else/something/somewhere").unwrap(),
&config
),
false
);
} }
} }

View File

@@ -1,13 +1,13 @@
use crate::error::GeneralError;
use crate::gitea::client::Client; use crate::gitea::client::Client;
use crate::gitea::repository::Repository; use crate::gitea::repository::Repository;
use serde::Deserialize; use serde::Deserialize;
use url::Url; use url::Url;
use crate::error::GeneralError;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Issue { pub struct Issue {
pub id: u64, pub id: u64,
pub number: u64, number: u64,
pub title: String, pub title: String,
pub body: String, pub body: String,
pub repository: Repository, pub repository: Repository,
@@ -31,24 +31,13 @@ pub fn issue_html_url_to_api(url: &Url) -> Result<Url, GeneralError> {
let issue = parts.next().unwrap(); let issue = parts.next().unwrap();
let iid = parts.next().unwrap(); let iid = parts.next().unwrap();
if !issue.eq("issues") { if (!issue.eq("issues")) {
return Err(GeneralError { return Err(GeneralError {
description: format!("Issue url is not valid: {}", url), description: format!("Issue url is not valid: {}", url),
}); });
} }
let url = Url::parse( let url = Url::parse(format!("{}://{}/api/v1/repos/{}/{}/issues/{}", url.scheme(), url.host().unwrap(), domain, repo, iid).as_str()).unwrap();
format!(
"{}://{}/api/v1/repos/{}/{}/issues/{}",
url.scheme(),
url.host().unwrap(),
domain,
repo,
iid
)
.as_str(),
)
.unwrap();
Ok(url) Ok(url)
} }
@@ -62,9 +51,6 @@ mod tests {
let url = Url::parse("https://gitea.champs-libres.be/champs-libres/test/issues/1").unwrap(); let url = Url::parse("https://gitea.champs-libres.be/champs-libres/test/issues/1").unwrap();
let result = issue_html_url_to_api(&url).unwrap(); let result = issue_html_url_to_api(&url).unwrap();
assert_eq!( assert_eq!(result.as_str(), "https://gitea.champs-libres.be/api/v1/repos/champs-libres/test/issues/1");
result.as_str(),
"https://gitea.champs-libres.be/api/v1/repos/champs-libres/test/issues/1"
);
} }
} }

View File

@@ -1,4 +1,4 @@
pub(crate) mod action;
pub mod client; pub mod client;
pub mod issue; pub mod issue;
pub mod repository; pub mod repository;
pub(crate) mod action;

View File

@@ -1,5 +1,5 @@
use crate::openproject::user::User;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::openproject::user::User;
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct WorkPackageWriterAssignee { pub struct WorkPackageWriterAssignee {

View File

@@ -1,6 +1,8 @@
use crate::cli::Issue2Work; use crate::cli::Issue2Work;
use crate::config::Config; use crate::config::Config;
use crate::error::GeneralError; use crate::error::GeneralError;
use crate::gitlab::client::ClientProvider;
use gitlab::{AsyncGitlab, GitlabBuilder};
use url::Url; use url::Url;
pub(crate) mod issue2work; pub(crate) mod issue2work;