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