Add a view command
This commit is contained in:
parent
5233ef19fc
commit
b229ce9c5c
5 changed files with 165 additions and 6 deletions
|
@ -40,6 +40,15 @@ pub enum Commands {
|
|||
#[arg(value_name = "JQL")]
|
||||
jql: String,
|
||||
},
|
||||
View {
|
||||
/// Print JSON rather than pretty print
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
|
||||
/// An issue key, for example, KEY-123
|
||||
#[arg(value_name = "ISSUE")]
|
||||
issue: String,
|
||||
},
|
||||
/// Set up the configuration
|
||||
Init {
|
||||
/// Jira instance URL
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod create;
|
||||
pub mod search;
|
||||
pub mod view;
|
||||
|
|
148
src/cmd/view.rs
Normal file
148
src/cmd/view.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
use crate::jira_config::JiraConfig;
|
||||
use crate::types::issue::JiraIssue;
|
||||
use crossterm::style::{Color, Stylize};
|
||||
use std::io::Write;
|
||||
|
||||
async fn fetch_issue(
|
||||
config: &JiraConfig,
|
||||
href: &str,
|
||||
) -> Result<JiraIssue, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let response = client
|
||||
.get(href)
|
||||
.basic_auth(&config.email, Some(&config.api_token))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let error_text = response.text().await?;
|
||||
return Err(format!("Failed to fetch issue: {}", error_text).into());
|
||||
}
|
||||
|
||||
let issue_response = response.json::<JiraIssue>().await?;
|
||||
|
||||
Ok(issue_response)
|
||||
}
|
||||
|
||||
fn pretty_print(issue: &JiraIssue) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("\n== Title {:=<71}", "");
|
||||
println!(
|
||||
"{}: {}",
|
||||
issue.key.clone().green(),
|
||||
issue.fields.summary.clone().bold().green()
|
||||
);
|
||||
|
||||
println!("\n== Details {:=<69}", "");
|
||||
let mut tw = tabwriter::TabWriter::new(vec![]);
|
||||
writeln!(tw, "{}:\t{}", "Priority".blue(), issue.fields.priority.name)?;
|
||||
writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?;
|
||||
writeln!(
|
||||
tw,
|
||||
"{}:\t{} <{}>",
|
||||
"Assignee".blue(),
|
||||
issue.fields.assignee.display_name,
|
||||
issue
|
||||
.fields
|
||||
.assignee
|
||||
.email_address
|
||||
.clone()
|
||||
.unwrap_or("invalid@example.com".to_string())
|
||||
)?;
|
||||
writeln!(
|
||||
tw,
|
||||
"{}:\t{} <{}>",
|
||||
"Reporter".blue(),
|
||||
issue.fields.reporter.display_name,
|
||||
issue
|
||||
.fields
|
||||
.reporter
|
||||
.email_address
|
||||
.clone()
|
||||
.unwrap_or("invalid@example.com".to_string())
|
||||
)?;
|
||||
writeln!(
|
||||
tw,
|
||||
"{}:\t{}",
|
||||
"Created".blue(),
|
||||
issue.fields.created.with_timezone(&chrono::Local)
|
||||
)?;
|
||||
writeln!(
|
||||
tw,
|
||||
"{}:\t{}",
|
||||
"Due Date".blue(),
|
||||
match issue.fields.due_date {
|
||||
None => "None".to_string(),
|
||||
Some(x) => x.to_string(),
|
||||
}
|
||||
)?;
|
||||
|
||||
tw.flush().unwrap();
|
||||
|
||||
let written = String::from_utf8(tw.into_inner().unwrap()).unwrap();
|
||||
print!("{}", written);
|
||||
println!("\n== Description {:=<65}", "");
|
||||
match issue.fields.description.clone() {
|
||||
Some(x) => println!("{}", x),
|
||||
None => println!("(Issue does not have a description)"),
|
||||
}
|
||||
|
||||
println!("\n== Comments {:=<68}", "");
|
||||
for comment in issue.fields.comment.clone().unwrap_or_default().comments {
|
||||
println!(
|
||||
"{} at {}",
|
||||
comment.author.display_name.red(),
|
||||
comment.created.with_timezone(&chrono::Local)
|
||||
);
|
||||
println!("{}", comment.body);
|
||||
}
|
||||
println!("\n== Actions {:=<69}", "");
|
||||
println!(
|
||||
"\u{1b}]8;;{}\u{7}{}\u{1b}]8;;\u{7}",
|
||||
issue.href,
|
||||
"Open Issue".green().underline(Color::Green)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn json_print(issues: &JiraIssue) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let j = serde_json::to_string_pretty(issues)?;
|
||||
println!("{}", j);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn exec(json: bool, issue_key: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?;
|
||||
if !json {
|
||||
println!("Loading issue data");
|
||||
}
|
||||
|
||||
let jql = format!("key = '{}'", issue_key);
|
||||
|
||||
let matched_issues = match crate::jql::run(&config, &jql).await {
|
||||
Ok(x) => x,
|
||||
Err(reason) => {
|
||||
eprintln!("Error fetching issue: {}", reason);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let matched_issue = match matched_issues.issues.first() {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
eprintln!("No issue found with that key");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let fetched_issue = fetch_issue(&config, &matched_issue.href).await?;
|
||||
|
||||
if json {
|
||||
json_print(&fetched_issue)?;
|
||||
} else {
|
||||
pretty_print(&fetched_issue)?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -21,8 +21,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
Commands::List { json } => {
|
||||
let jql = "assignee = currentUser() AND resolution = Unresolved order by updated DESC";
|
||||
cmd::search::exec(json, jql).await?
|
||||
},
|
||||
}
|
||||
Commands::Search { json, jql } => cmd::search::exec(json, &jql).await?,
|
||||
Commands::View { json, issue } => cmd::view::exec(json, &issue).await?,
|
||||
Commands::Init { url, email, token } => {
|
||||
JiraConfig::init(url, email, token).await?;
|
||||
println!("Configuration initialized successfully!");
|
||||
|
|
|
@ -13,6 +13,7 @@ pub struct JiraIssue {
|
|||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct JiraIssueResponseFields {
|
||||
pub summary: String,
|
||||
pub description: Option<String>,
|
||||
pub status: Status,
|
||||
pub created: chrono::DateTime<chrono::Utc>,
|
||||
pub priority: Priority,
|
||||
|
@ -36,7 +37,7 @@ pub struct Priority {
|
|||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Person {
|
||||
#[serde(rename = "self")]
|
||||
pub href: String,
|
||||
|
@ -48,10 +49,8 @@ pub struct Person {
|
|||
pub email_address: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
pub struct Comments {
|
||||
#[serde(rename = "self")]
|
||||
pub href: String,
|
||||
pub total: u32,
|
||||
#[serde(rename = "maxResults")]
|
||||
pub max_results: u32,
|
||||
|
@ -60,7 +59,7 @@ pub struct Comments {
|
|||
pub comments: Vec<Comment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Comment {
|
||||
#[serde(rename = "self")]
|
||||
pub href: String,
|
||||
|
@ -69,6 +68,7 @@ pub struct Comment {
|
|||
pub body: String,
|
||||
#[serde(rename = "updateAuthor")]
|
||||
pub update_author: Person,
|
||||
pub created: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
|
|
Loading…
Reference in a new issue