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.
This commit is contained in:
parent
9ef98e5044
commit
2b37c5b18c
63
src/gitea/action.rs
Normal file
63
src/gitea/action.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use url::Url;
|
||||||
|
use crate::cli::Issue2Work;
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::error::GeneralError;
|
||||||
|
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::work::{WorkPackage, WorkPackageWriter, WorkPackageWriterAssignee};
|
||||||
|
use crate::planning::Issue2WorkActionTrait;
|
||||||
|
|
||||||
|
pub(crate) struct GiteaAction {}
|
||||||
|
|
||||||
|
impl Issue2WorkActionTrait for GiteaAction {
|
||||||
|
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 issue: Issue = gitea_client
|
||||||
|
.get(issue_html_url_to_api(url)?)
|
||||||
|
.await?;
|
||||||
|
let open_project_client = crate::openproject::client::Client::from_config(&config.openproject);
|
||||||
|
|
||||||
|
let work_package = create_work_package_from_issue(
|
||||||
|
&issue,
|
||||||
|
match args.assign_to_me {
|
||||||
|
true => {
|
||||||
|
let u = open_project_client.me().await?;
|
||||||
|
Some(u)
|
||||||
|
}
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let work_package = open_project_client
|
||||||
|
.create_work_package(&work_package, &args.project_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"new work package created: {:?}, edit at {}/projects/{}/work_packages/{}",
|
||||||
|
work_package.subject, config.openproject.base_url, args.project_id, work_package.id
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
||||||
|
has_client_for_url(&url, &config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_work_package_from_issue(issue: &Issue, assignee: Option<User>) -> WorkPackageWriter {
|
||||||
|
WorkPackageWriter {
|
||||||
|
subject: format!(
|
||||||
|
"{} ({})",
|
||||||
|
issue.title,
|
||||||
|
issue.repository.full_name
|
||||||
|
),
|
||||||
|
work_type: "TASK".into(),
|
||||||
|
description: crate::openproject::work::DescriptionWriter {
|
||||||
|
format: "markdown".into(),
|
||||||
|
raw: format!("From Gitea issue: {} \n\n{}", issue.html_url, issue.body),
|
||||||
|
},
|
||||||
|
assignee: assignee.into()
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
use crate::config::{Config, GiteaConfig, GitlabConfig};
|
use crate::config::{Config, GiteaConfig, GitlabConfig};
|
||||||
use crate::error::GeneralError;
|
use crate::error::GeneralError;
|
||||||
use gitlab::AsyncGitlab;
|
|
||||||
use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION};
|
use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION};
|
||||||
use reqwest::{ClientBuilder, StatusCode};
|
use reqwest::{ClientBuilder, StatusCode};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
@ -12,6 +11,35 @@ pub struct Client {
|
|||||||
base_uri: String,
|
base_uri: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_client_for_url(url: &Url, config: &GiteaConfig) -> bool {
|
||||||
|
if url.domain() == Some(config.domain.as_str()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn has_client_for_url(url: &Url, config: &Config) -> bool {
|
||||||
|
for c in &config.gitea {
|
||||||
|
if is_client_for_url(url, c) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_for_url(url: &Url, config: &Config) -> Result<Client, GeneralError> {
|
||||||
|
for c in &config.gitea {
|
||||||
|
if is_client_for_url(url, c) {
|
||||||
|
return Ok(Client::from_config(&c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(GeneralError {
|
||||||
|
description: format!("No gitea client found for url {}", url),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn from_config(config: &GiteaConfig) -> Self {
|
pub fn from_config(config: &GiteaConfig) -> Self {
|
||||||
Self::new(&config.token, &config.domain)
|
Self::new(&config.token, &config.domain)
|
||||||
@ -52,3 +80,19 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_client_for_url() {
|
||||||
|
let config = GiteaConfig {
|
||||||
|
domain: "gitea.champs-libres.be".into(),
|
||||||
|
token: "<PASSWORD>".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(is_client_for_url(&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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
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 crate::error::GeneralError;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Issue {
|
pub struct Issue {
|
||||||
id: u64,
|
pub id: u64,
|
||||||
number: u64,
|
number: u64,
|
||||||
title: String,
|
pub title: String,
|
||||||
body: String,
|
pub body: String,
|
||||||
repository: Repository,
|
pub repository: Repository,
|
||||||
|
pub html_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IssueClient {
|
pub trait IssueClient {
|
||||||
@ -16,7 +19,38 @@ pub trait IssueClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IssueClient for Client {
|
impl IssueClient for Client {
|
||||||
fn get_issue(owner_or_organisation: &String, repo: &String, number: &u64) -> Option<Issue> {
|
fn get_issue(_owner_or_organisation: &String, _repo: &String, number: &u64) -> Option<Issue> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn issue_html_url_to_api(url: &Url) -> Result<Url, GeneralError> {
|
||||||
|
let mut parts = url.path_segments().unwrap();
|
||||||
|
let domain = parts.next().unwrap();
|
||||||
|
let repo = parts.next().unwrap();
|
||||||
|
let issue = parts.next().unwrap();
|
||||||
|
let iid = parts.next().unwrap();
|
||||||
|
|
||||||
|
if (!issue.eq("issues")) {
|
||||||
|
return Err(GeneralError {
|
||||||
|
description: format!("Issue url is not valid: {}", url),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = Url::parse(format!("{}://{}/api/v1/repos/{}/{}/issues/{}", url.scheme(), url.host().unwrap(), domain, repo, iid).as_str()).unwrap();
|
||||||
|
|
||||||
|
Ok(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_issue_html_url_to_api() {
|
||||||
|
let url = Url::parse("https://gitea.champs-libres.be/champs-libres/test/issues/1").unwrap();
|
||||||
|
let result = issue_html_url_to_api(&url).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result.as_str(), "https://gitea.champs-libres.be/api/v1/repos/champs-libres/test/issues/1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod issue;
|
pub mod issue;
|
||||||
pub mod repository;
|
pub mod repository;
|
||||||
|
pub(crate) mod action;
|
||||||
|
@ -5,5 +5,5 @@ pub struct Repository {
|
|||||||
id: u64,
|
id: u64,
|
||||||
name: String,
|
name: String,
|
||||||
owner: String,
|
owner: String,
|
||||||
full_name: String,
|
pub full_name: String,
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ impl Issue2WorkActionTrait for GitlabAction {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool {
|
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
||||||
has_client_for_url(&url, &config)
|
has_client_for_url(&url, &config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ pub async fn client_for_url(url: &Url, config: &Config) -> Result<AsyncGitlab, G
|
|||||||
|
|
||||||
pub fn has_client_for_url(url: &Url, config: &Config) -> bool {
|
pub fn has_client_for_url(url: &Url, config: &Config) -> bool {
|
||||||
for c in &config.gitlab {
|
for c in &config.gitlab {
|
||||||
if (is_client_for_url(url, c)) {
|
if is_client_for_url(url, c) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,11 @@ pub struct UserLink {
|
|||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
#[serde(rename = "_type")]
|
#[serde(rename = "_type")]
|
||||||
|
#[allow(unused_variables)]
|
||||||
pub d_type: String,
|
pub d_type: String,
|
||||||
|
#[allow(unused_variables)]
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
|
#[allow(unused_variables)]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(rename = "_links")]
|
#[serde(rename = "_links")]
|
||||||
pub d_links: UserLink,
|
pub d_links: UserLink,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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 {
|
||||||
@ -24,3 +25,14 @@ pub struct WorkPackage {
|
|||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub subject: String,
|
pub subject: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Option<User>> for WorkPackageWriterAssignee {
|
||||||
|
fn from(value: Option<User>) -> Self {
|
||||||
|
WorkPackageWriterAssignee {
|
||||||
|
href: match value {
|
||||||
|
None => None,
|
||||||
|
Some(w) => Some(w.clone().d_links.d_self.href),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,34 +1,27 @@
|
|||||||
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::action::GiteaAction;
|
||||||
use crate::gitlab::action::GitlabAction;
|
use crate::gitlab::action::GitlabAction;
|
||||||
use crate::planning::Issue2WorkActionTrait;
|
use crate::planning::Issue2WorkActionTrait;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
gitlab_issue2work_action: GitlabAction,
|
gitlab_issue2work_action: GitlabAction,
|
||||||
}
|
gitea_issue2work_action: GiteaAction,
|
||||||
|
|
||||||
trait Issue2WorkPackageAction {
|
|
||||||
fn issue2work_package_action(
|
|
||||||
&self,
|
|
||||||
config: &Config,
|
|
||||||
args: &Issue2Work,
|
|
||||||
) -> Result<(), GeneralError>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn issue2work(config: Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
pub(crate) async fn issue2work(config: Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
||||||
let url = Url::parse(&*args.issue_url).expect("issue_url is not valid");
|
let url = Url::parse(&*args.issue_url).expect("issue_url is not valid");
|
||||||
let app = App {
|
let app = App {
|
||||||
gitlab_issue2work_action: GitlabAction {},
|
gitlab_issue2work_action: GitlabAction {},
|
||||||
|
gitea_issue2work_action: GiteaAction {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (app
|
if app.gitlab_issue2work_action.supports(&url, &config, args) {
|
||||||
.gitlab_issue2work_action
|
|
||||||
.supports(&url, &config, args)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
app.gitlab_issue2work_action.run(&url, &config, args).await
|
app.gitlab_issue2work_action.run(&url, &config, args).await
|
||||||
|
} else if app.gitea_issue2work_action.supports(&url, &config, args) {
|
||||||
|
app.gitea_issue2work_action.run(&url, &config, args).await
|
||||||
} else {
|
} else {
|
||||||
Err(GeneralError {
|
Err(GeneralError {
|
||||||
description: format!("This action is not supported for this url: {}", url),
|
description: format!("This action is not supported for this url: {}", url),
|
||||||
|
Loading…
Reference in New Issue
Block a user