1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
use anyhow::{anyhow, Context, Result}; use dialoguer::{theme::ColorfulTheme, Select}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt; use url::Url; use reqwest::blocking::Client; #[derive(Serialize)] struct IssueSearchRequestBody { jql: String, fields: Vec<String>, } #[derive(Deserialize)] struct IssueSearchResponseBody { issues: Option<Vec<Issue>>, } #[derive(Deserialize)] pub struct Issue { pub key: String, pub fields: HashMap<String, String>, } impl fmt::Display for Issue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{} - {}", &self.key, &self.fields.get("summary").unwrap() ) } } #[derive(Deserialize, Debug)] pub struct Config { jira_host: Url, jira_user: String, jira_password: Option<String>, } pub fn search_issues(config: Config, query: &str) -> Result<Vec<Issue>> { let body = IssueSearchRequestBody { jql: String::from(query), fields: vec![String::from("summary")], }; let resp = Client::new() .post(config.jira_host.join("/rest/api/2/search").unwrap()) .json(&body) .basic_auth(config.jira_user, config.jira_password) .send() .context("Unable to search JIRA for issues")? .json::<IssueSearchResponseBody>() .context("Unable to decode JIRA response")?; resp.issues .ok_or_else(|| anyhow!("No issues found for query")) } pub fn select_issue(issues: &[Issue]) -> Result<&Issue> { let selection = Select::with_theme(&ColorfulTheme::default()) .items(&issues) .default(0) .interact_opt()?; let index = selection.ok_or_else(|| anyhow!("No JIRA issue selected"))?; Ok(&issues[index]) }