Extract the jql execution logic from the list command

This commit is contained in:
Daan Boerlage 2025-01-21 19:48:21 +01:00
parent 86fe96f0cd
commit 4fbab7102a
Signed by: daan
GPG key ID: FCE070E1E4956606
5 changed files with 119 additions and 112 deletions

View file

@ -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<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(())
}
use crate::types::issue::{display_issues_json, display_issues_pretty};
pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
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...");
}
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() {

37
src/jql.rs Normal file
View 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?)
}

View file

@ -1,6 +1,8 @@
mod cli;
mod cmd;
mod jira_config;
mod jql;
mod types;
use clap::Parser;
use cli::{Cli, Commands};

1
src/types.rs Normal file
View file

@ -0,0 +1 @@
pub mod issue;

75
src/types/issue.rs Normal file
View 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(())
}