commit
822e4a9a7f
@ -0,0 +1,4 @@ |
|||||||
|
/target |
||||||
|
*.json |
||||||
|
*.env |
||||||
|
*.sh |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@ |
|||||||
|
[package] |
||||||
|
name = "it2jira" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
actix-web = "4.2.1" |
||||||
|
reqwest = {version = "0.11.11", features = ["blocking"]} |
||||||
|
serde_json = "1.0.85" |
||||||
|
futures = "0.3.24" |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct IssueTrakClient<'a> { |
||||||
|
api_key : &'a str, |
||||||
|
req_client : reqwest::blocking::Client, |
||||||
|
base_url : &'a str |
||||||
|
} |
||||||
|
impl<'a> IssueTrakClient<'a> { |
||||||
|
pub fn new(api_key: &'a str, base_url: &'a str) -> Self { |
||||||
|
IssueTrakClient { |
||||||
|
api_key : api_key, |
||||||
|
req_client : reqwest::blocking::Client::new(), |
||||||
|
base_url : base_url |
||||||
|
} |
||||||
|
} |
||||||
|
fn gen_hmac(&self, header_data: String) -> String { |
||||||
|
let mut mac = HmacSha512::new_from_slice(self.api_key.as_bytes()).unwrap(); |
||||||
|
mac.update(header_data.as_bytes()); |
||||||
|
let res: Vec<u8> = mac.finalize().into_bytes().iter().map(|b| *b).collect(); |
||||||
|
base64::encode(res) |
||||||
|
} |
||||||
|
fn build_headers(&self, req_type: &str, endpoint_path: &str, body_str: &str) -> HeaderMap { |
||||||
|
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true); |
||||||
|
let guid = Uuid::new_v4().to_string(); |
||||||
|
let auth_str = format!("{}\n{}\n{}\n{}\n{}\n{}",req_type, &guid, ×tamp, endpoint_path, "",body_str);
|
||||||
|
println!("auth_str = {:?}", auth_str); |
||||||
|
let auth_hash = self.gen_hmac(auth_str); |
||||||
|
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("Accept", "application/json".parse().unwrap()); |
||||||
|
headers.insert("Content-Type", "application/json; charset=utf-8".parse().unwrap()); |
||||||
|
headers.insert("Accept-Language", "en-US".parse().unwrap()); |
||||||
|
headers.insert("X-IssueTrak-API-Request-ID", guid.parse().unwrap()); |
||||||
|
headers.insert("X-IssueTrak-API-Timestamp", timestamp.parse().unwrap()); |
||||||
|
headers.insert("X-IssueTrak-API-Authorization", auth_hash.parse().unwrap()); |
||||||
|
println!("headers = {:?}\n\n", headers); |
||||||
|
headers |
||||||
|
} |
||||||
|
|
||||||
|
pub fn issue_by_number(&self, issue_number: i32) { |
||||||
|
let endpoint_suffix = format!("api/v1/issues/true/{issue_number}"); |
||||||
|
let full_endpoint = format!("{}api/v1/issues/true/{}", self.base_url, issue_number); |
||||||
|
println!("full_endpoint = {:?}\n", full_endpoint); |
||||||
|
let header_data = self.build_headers("GET", &endpoint_suffix,""); |
||||||
|
let res = self.req_client.get(full_endpoint) |
||||||
|
.headers(header_data) |
||||||
|
.send(); |
||||||
|
println!("res = {:?}", res); |
||||||
|
} |
||||||
@ -0,0 +1 @@ |
|||||||
|
pub mod outgoing; |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
use reqwest::{Client,Url}; |
||||||
|
use reqwest::header::{HeaderMap, HeaderValue}; |
||||||
|
use futures::future; |
||||||
|
|
||||||
|
pub struct JiraClient<'a> { |
||||||
|
http_client: Client, |
||||||
|
basic_auth: (&'a str, &'a str), |
||||||
|
base_url: &'a str |
||||||
|
} |
||||||
|
impl<'a> JiraClient<'a> { |
||||||
|
pub fn new(base_url: &'a str, api_key: &'a str, user: &'a str) -> Self { |
||||||
|
let mut headers = HeaderMap::new(); |
||||||
|
headers.insert("Accept", HeaderValue::from_static("application/json")); |
||||||
|
let client = Client::new(); |
||||||
|
JiraClient { http_client: client, basic_auth: (user, api_key), base_url: base_url } |
||||||
|
} |
||||||
|
pub async fn get_issue(&self, issue_id: String) -> Result<serde_json::Value, Box<dyn std::error::Error>>{ |
||||||
|
let endpoint = format!("{}/rest/api/3/issue/{}",self.base_url , issue_id); |
||||||
|
let resp = self.http_client.get(endpoint) |
||||||
|
.basic_auth(self.basic_auth.0, Some(self.basic_auth.1)) |
||||||
|
.send().await?.text().await?; |
||||||
|
let js: serde_json::Value = serde_json::from_str(&resp)?; |
||||||
|
Ok(js) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,10 @@ |
|||||||
|
mod jira; |
||||||
|
use crate::jira::outgoing; |
||||||
|
use reqwest::{Client, ClientBuilder}; |
||||||
|
mod neutral; |
||||||
|
mod tests; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
|
||||||
|
println!("Hello, world!"); |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
use reqwest; |
||||||
|
use serde_json::Value; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct TicketData { |
||||||
|
class : String, // Type in Jira
|
||||||
|
status : String, |
||||||
|
priority : String, |
||||||
|
subject : String, // Summary in Jira
|
||||||
|
issue_trak_num : String, |
||||||
|
issue_trak_link: String, |
||||||
|
assignee : String, // Next Action in IssueTrak
|
||||||
|
comment : String, // Note in IssueTrak
|
||||||
|
// Not yet implemented Solution : String
|
||||||
|
} |
||||||
|
|
||||||
|
impl TicketData { |
||||||
|
pub fn from_jira_data(json_value: Value) -> Result<Self, Box<dyn std::error::Error>>{ |
||||||
|
// All of the data we need is under the fields key
|
||||||
|
let json_fields = match json_value.get("fields") { |
||||||
|
Some(f) => {f.clone()}, |
||||||
|
_ => { return Err("No fields")?} |
||||||
|
}; |
||||||
|
|
||||||
|
/* |
||||||
|
Handling Jira comments is going to a nightmare. This will be implemented later |
||||||
|
*/ |
||||||
|
|
||||||
|
// Believe it or not this is easier than dealing with creating structs for the reposonse
|
||||||
|
// if the fields are blank 'null' will be returned as a string
|
||||||
|
Ok(TicketData { |
||||||
|
class : json_fields.get("issuetype").unwrap().get("name").unwrap().to_string(), |
||||||
|
status: json_fields.get("status").unwrap().get("name").unwrap().to_string(), |
||||||
|
priority: json_fields.get("priority").unwrap().get("name").unwrap().to_string(), |
||||||
|
subject: json_fields.get("summary").unwrap().to_string(), |
||||||
|
issue_trak_num: json_fields.get("customfield_10065").unwrap().to_string(), |
||||||
|
issue_trak_link: json_fields.get("customfield_10060").unwrap().to_string(), |
||||||
|
assignee: json_fields.get("assignee").unwrap().get("displayName").unwrap().to_string(), |
||||||
|
comment: "Test".to_string()//json_fields.get("comment").unwrap().get("displayName").unwrap().to_string(),
|
||||||
|
//solution: json_fields.get("solu").unwrap().get("displayName").unwrap().to_string()
|
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
#[cfg(test)] |
||||||
|
mod test { |
||||||
|
use actix_web::test; |
||||||
|
use serde_json; |
||||||
|
use crate::jira::outgoing; |
||||||
|
use crate::neutral::TicketData; |
||||||
|
use dotenv:dotenv; |
||||||
|
use std::env::var; |
||||||
|
|
||||||
|
#[actix_web::test] |
||||||
|
async fn get_ticket_data_from_jira() { |
||||||
|
let jcl = outgoing::JiraClient::new(var("JIRA_INSTANCE").except("No JIRA_INSTANCE configured in .env"), var("JIRA_USER_PW").except("No JIRA_USER_PW configured in .env"), var("JIRA_USER").except("No JIRA_USER configured in .env")); |
||||||
|
let jit_project = "LEAF-511"; |
||||||
|
println!("Testing Jira issue = {:?}", jit_project); |
||||||
|
let res: Result<serde_json::Value, Box<dyn std::error::Error>> = jcl.get_issue(jit_project.to_string()).await; |
||||||
|
if !res.is_ok() { |
||||||
|
println!("res = {:?}", res); |
||||||
|
assert!(res.is_ok()) |
||||||
|
}
|
||||||
|
println!("res = {:?}", res); |
||||||
|
let ticket_data = TicketData::from_jira_data(res.unwrap()); |
||||||
|
println!("ticketData = {:?}", ticket_data); |
||||||
|
assert!(ticket_data.is_ok()) |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue