Extract the jql execution logic from the list command
This commit is contained in:
parent
86fe96f0cd
commit
4fbab7102a
5 changed files with 119 additions and 112 deletions
116
src/cmd/list.rs
116
src/cmd/list.rs
|
@ -1,115 +1,5 @@
|
||||||
use crate::jira_config::JiraConfig;
|
use crate::jira_config::JiraConfig;
|
||||||
use colored::Colorize;
|
use crate::types::issue::{display_issues_json, display_issues_pretty};
|
||||||
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<chrono::Utc>,
|
|
||||||
priority: Priority,
|
|
||||||
assignee: Person,
|
|
||||||
reporter: Person,
|
|
||||||
creator: Person,
|
|
||||||
#[serde(rename = "duedate")]
|
|
||||||
due_date: Option<chrono::NaiveDate>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct JiraSearchResponse {
|
|
||||||
issues: Vec<JiraIssue>,
|
|
||||||
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<JiraSearchResponse, Box<dyn std::error::Error>> {
|
|
||||||
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::<JiraSearchResponse>().await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
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<dyn std::error::Error>> {
|
|
||||||
let j = serde_json::to_string_pretty(issues)?;
|
|
||||||
println!("{}", j);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?;
|
let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?;
|
||||||
|
@ -117,7 +7,9 @@ pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("Fetching issues assigned...");
|
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) => {
|
Ok(response) => {
|
||||||
if json {
|
if json {
|
||||||
if response.issues.is_empty() {
|
if response.issues.is_empty() {
|
||||||
|
|
37
src/jql.rs
Normal file
37
src/jql.rs
Normal file
|
@ -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<JiraIssue>,
|
||||||
|
pub total: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(
|
||||||
|
config: &JiraConfig,
|
||||||
|
jql: &str,
|
||||||
|
) -> Result<JiraSearchResponse, Box<dyn std::error::Error>> {
|
||||||
|
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::<JiraSearchResponse>().await?)
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
mod cli;
|
mod cli;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod jira_config;
|
mod jira_config;
|
||||||
|
mod jql;
|
||||||
|
mod types;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::{Cli, Commands};
|
use cli::{Cli, Commands};
|
||||||
|
|
1
src/types.rs
Normal file
1
src/types.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod issue;
|
75
src/types/issue.rs
Normal file
75
src/types/issue.rs
Normal file
|
@ -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<chrono::Utc>,
|
||||||
|
pub priority: Priority,
|
||||||
|
pub assignee: Person,
|
||||||
|
pub reporter: Person,
|
||||||
|
pub creator: Person,
|
||||||
|
#[serde(rename = "duedate")]
|
||||||
|
pub due_date: Option<chrono::NaiveDate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<dyn std::error::Error>> {
|
||||||
|
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<dyn std::error::Error>> {
|
||||||
|
let j = serde_json::to_string_pretty(issues)?;
|
||||||
|
println!("{}", j);
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue