diff --git a/src/cmd/list.rs b/src/cmd/list.rs index aa50486..2b8ea19 100644 --- a/src/cmd/list.rs +++ b/src/cmd/list.rs @@ -1,115 +1,5 @@ use crate::jira_config::JiraConfig; -use colored::Colorize; -use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; -use serde::{Deserialize, Serialize}; -use std::io::Write; - -#[derive(Debug, Deserialize, Serialize)] -struct JiraIssue { - key: String, - #[serde(rename = "self")] - href: String, - fields: JiraIssueResponseFields, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JiraIssueResponseFields { - summary: String, - status: Status, - created: chrono::DateTime, - priority: Priority, - assignee: Person, - reporter: Person, - creator: Person, - #[serde(rename = "duedate")] - due_date: Option, -} - -#[derive(Deserialize)] -struct JiraSearchResponse { - issues: Vec, - total: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct Status { - name: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct Priority { - name: String, - id: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct Person { - #[serde(rename = "self")] - href: String, - #[serde(rename = "displayName")] - display_name: String, - #[serde(rename = "accountId")] - account_id: String, - #[serde(rename = "emailAddress")] - email_address: String, -} - -async fn list_jira_issues( - config: &JiraConfig, -) -> Result> { - let client = reqwest::Client::new(); - - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - - // JQL query to find issues assigned to the specified user - let jql = - "assignee = currentUser() AND resolution = Unresolved order by updated DESC".to_string(); - let query = [("jql", jql.as_str())]; - - let response = client - .get(format!("{}/rest/api/2/search", config.url)) - .basic_auth(&config.email, Some(&config.api_token)) - .headers(headers) - .query(&query) - .send() - .await?; - - if !response.status().is_success() { - let error_text = response.text().await?; - return Err(format!("Failed to fetch issues: {}", error_text).into()); - } - - Ok(response.json::().await?) -} - -fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box> { - println!("Found {} issues:", issues.len()); - println!("{:-<80}", ""); - - for issue in issues { - let mut tw = tabwriter::TabWriter::new(vec![]); - writeln!(tw, "{}:\t{}", "Key".blue(), issue.key)?; - writeln!(tw, "{}:\t{}", "Summary".blue(), issue.fields.summary)?; - writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?; - writeln!(tw, "{}:\t{}", "Created".blue(), issue.fields.created)?; - writeln!(tw, "{}:\t{:?}", "Due Date".blue(), issue.fields.due_date)?; - writeln!(tw, "{}:\t{}", "URL".blue(), issue.href.underline())?; - tw.flush().unwrap(); - - let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); - print!("{}", written); - println!("{:-<80}", ""); - } - - Ok(()) -} - -fn display_issues_json(issues: &[JiraIssue]) -> Result<(), Box> { - let j = serde_json::to_string_pretty(issues)?; - println!("{}", j); - Ok(()) -} +use crate::types::issue::{display_issues_json, display_issues_pretty}; pub async fn list(json: bool) -> Result<(), Box> { let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?; @@ -117,7 +7,9 @@ pub async fn list(json: bool) -> Result<(), Box> { println!("Fetching issues assigned..."); } - match list_jira_issues(&config).await { + let jql = "assignee = currentUser() AND resolution = Unresolved order by updated DESC"; + + match crate::jql::run(&config, jql).await { Ok(response) => { if json { if response.issues.is_empty() { diff --git a/src/jql.rs b/src/jql.rs new file mode 100644 index 0000000..a281f3d --- /dev/null +++ b/src/jql.rs @@ -0,0 +1,37 @@ +use crate::jira_config::JiraConfig; +use crate::types::issue::JiraIssue; +use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct JiraSearchResponse { + pub issues: Vec, + pub total: u32, +} + +pub async fn run( + config: &JiraConfig, + jql: &str, +) -> Result> { + let client = reqwest::Client::new(); + + let mut headers = HeaderMap::new(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let query = [("jql", jql)]; + + let response = client + .get(format!("{}/rest/api/2/search", config.url)) + .basic_auth(&config.email, Some(&config.api_token)) + .headers(headers) + .query(&query) + .send() + .await?; + + if !response.status().is_success() { + let error_text = response.text().await?; + return Err(format!("Failed to fetch issues: {}", error_text).into()); + } + + Ok(response.json::().await?) +} diff --git a/src/main.rs b/src/main.rs index cc526c8..57173bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ mod cli; mod cmd; mod jira_config; +mod jql; +mod types; use clap::Parser; use cli::{Cli, Commands}; diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..d93d369 --- /dev/null +++ b/src/types.rs @@ -0,0 +1 @@ +pub mod issue; diff --git a/src/types/issue.rs b/src/types/issue.rs new file mode 100644 index 0000000..de73577 --- /dev/null +++ b/src/types/issue.rs @@ -0,0 +1,75 @@ +use colored::Colorize; +use serde::{Deserialize, Serialize}; +use std::io::Write; + +#[derive(Debug, Deserialize, Serialize)] +pub struct JiraIssue { + pub key: String, + #[serde(rename = "self")] + pub href: String, + pub fields: JiraIssueResponseFields, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct JiraIssueResponseFields { + pub summary: String, + pub status: Status, + pub created: chrono::DateTime, + pub priority: Priority, + pub assignee: Person, + pub reporter: Person, + pub creator: Person, + #[serde(rename = "duedate")] + pub due_date: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Status { + pub name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Priority { + pub name: String, + pub id: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Person { + #[serde(rename = "self")] + pub href: String, + #[serde(rename = "displayName")] + pub display_name: String, + #[serde(rename = "accountId")] + pub account_id: String, + #[serde(rename = "emailAddress")] + pub email_address: String, +} + +pub fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box> { + println!("Found {} issues:", issues.len()); + println!("{:-<80}", ""); + + for issue in issues { + let mut tw = tabwriter::TabWriter::new(vec![]); + writeln!(tw, "{}:\t{}", "Key".blue(), issue.key)?; + writeln!(tw, "{}:\t{}", "Summary".blue(), issue.fields.summary)?; + writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?; + writeln!(tw, "{}:\t{}", "Created".blue(), issue.fields.created)?; + writeln!(tw, "{}:\t{:?}", "Due Date".blue(), issue.fields.due_date)?; + writeln!(tw, "{}:\t{}", "URL".blue(), issue.href.underline())?; + tw.flush().unwrap(); + + let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); + print!("{}", written); + println!("{:-<80}", ""); + } + + Ok(()) +} + +pub fn display_issues_json(issues: &[JiraIssue]) -> Result<(), Box> { + let j = serde_json::to_string_pretty(issues)?; + println!("{}", j); + Ok(()) +}