diff --git a/ReadMe.md b/ReadMe.md index 05afdd8..29a2827 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -33,6 +33,7 @@ When searching, the CLI will return a list for the user to choose from after que | Flag | Description | | ----------------------------- | --------------------------------------------------------------------------------------------------- | | `-n, --new` | Creates a new ticket instead of updating an existing one ( cannot be used with `--search` ) | +| `-a, --all` | Returns all item in the bin instead of searching | | `-c, --comment ` | Comment for time tracking | | `--time-worked ` | Add time in the format of 1h1m where 1 can be replaced with any number (hours must be less than 24) | | `-s, --search ` | Keyword search using ElasticNow (returns all tickets in bin by default) | diff --git a/src/cli/args.rs b/src/cli/args.rs index d96d3ee..d7812a2 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -21,9 +21,13 @@ pub struct Args { pub enum Commands { /// Run time tracking options utilizing ElasticNow and ServiceNow Timetrack { - #[clap(short, long, conflicts_with_all = ["search","no_tkt"], action = clap::ArgAction::SetTrue)] + #[clap(short, long, conflicts_with_all = ["search","no_tkt", "all"], action = clap::ArgAction::SetTrue)] /// Creates a new ticket instead of updating an existing one ( cannot be used with --search ) new: bool, + #[clap(short, long, conflicts_with_all = ["search","no_tkt"], action = clap::ArgAction::SetTrue)] + /// Returns all item in the bin instead of searching + all: bool, + #[clap(short, long)] /// Comment for time tracking comment: String, @@ -33,7 +37,7 @@ pub enum Commands { help = format!("Add time in the format of {} where 1 can be replaced with any number (hours must be less than 20)", Colour::Green.bold().paint("1h1m"))) ] time_worked: String, - #[clap(short, long, required_unless_present_any = ["new", "no_tkt"])] + #[clap(short, long, required_unless_present_any = ["new", "no_tkt", "all"])] /// Keyword search using ElasticNow (returns all tickets in bin by default) search: Option, #[clap(short, long, visible_alias = "assignment-group")] diff --git a/src/elasticnow/elasticnow.rs b/src/elasticnow/elasticnow.rs index 839de0b..b040355 100644 --- a/src/elasticnow/elasticnow.rs +++ b/src/elasticnow/elasticnow.rs @@ -28,6 +28,24 @@ pub struct Ticket { pub type_: String, } +impl ChooseOptions for SearchResult { + fn get_debug_string(&self) -> String { + format!("{}: {}", self.source.number, self.source.short_description) + } + fn get_number(&self) -> String { + self.source.number.clone() + } + fn get_id(&self) -> String { + self.source.id.clone() + } +} + +pub trait ChooseOptions { + fn get_debug_string(&self) -> String; + fn get_id(&self) -> String; + fn get_number(&self) -> String; +} + pub struct ElasticNow { instance: String, pub client: Client, diff --git a/src/elasticnow/servicenow.rs b/src/elasticnow/servicenow.rs index 80dd29b..2fe418f 100644 --- a/src/elasticnow/servicenow.rs +++ b/src/elasticnow/servicenow.rs @@ -7,7 +7,7 @@ use reqwest::Client; use std::error::Error; use tracing::debug; -use super::servicenow_structs::CHGCreation; +use super::servicenow_structs::{CHGCreation, ShortDescNumberID}; pub struct ServiceNow { username: String, @@ -48,6 +48,27 @@ impl ServiceNow { .send() .await } + pub async fn get_all_tickets_in_bin( + &self, + bin: &str, + ) -> Result, Box> { + let resp = self + .get(&format!( + "{}/api/now/table/task?sysparm_fields=sys_id,short_description,number,assignment_group&sysparm_query=active=true^assignment_group.name={}", + self.instance, bin + )) + .await?; + if !resp.status().is_success() { + return Err(format!("HTTP Error while querying ServiceNow: {}", resp.status()).into()); + } + let result = debug_resp_json_deserialize::>>(resp).await; + if result.is_err() { + let error_msg = format!("JSON error: {}", result.unwrap_err()); + tracing::error!("{}", error_msg); + return Err(error_msg.into()); + } + Ok(result.unwrap().result) + } pub async fn get_user_group(&self, username: &str) -> Result> { let resp = self.get(&format!( "{}/api/now/table/sys_user?user_name={}&sysparm_limit=1&sysparm_display_value=true&sysparm_exclude_reference_link=true&sysparm_fields=u_default_group", diff --git a/src/elasticnow/servicenow_structs.rs b/src/elasticnow/servicenow_structs.rs index 2ff0206..fbd2959 100644 --- a/src/elasticnow/servicenow_structs.rs +++ b/src/elasticnow/servicenow_structs.rs @@ -1,3 +1,4 @@ +use crate::elasticnow::elasticnow::ChooseOptions; use crate::elasticnow::servicenow; use serde::{Deserialize, Serialize}; @@ -113,6 +114,25 @@ impl TimeWorked { } } } +#[derive(Debug, Serialize, Deserialize)] + +pub struct ShortDescNumberID { + pub short_description: String, + pub number: String, + pub sys_id: String, +} + +impl ChooseOptions for ShortDescNumberID { + fn get_debug_string(&self) -> String { + format!("{}: {}", self.number, self.short_description) + } + fn get_number(&self) -> String { + self.number.clone() + } + fn get_id(&self) -> String { + self.sys_id.clone() + } +} #[derive(Debug, Serialize, Deserialize)] pub struct CostCenter { diff --git a/src/main.rs b/src/main.rs index bcba5f7..73e54d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,16 @@ use ansi_term::Colour; use elasticnow::cli::{self, args, config}; +use elasticnow::elasticnow::elasticnow::ChooseOptions; use elasticnow::elasticnow::elasticnow::{ElasticNow, SearchResult}; use elasticnow::elasticnow::servicenow::ServiceNow; use std::collections::HashMap; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + +struct ValueOption { + value: String, + display_value: String, +} + #[tokio::main] async fn main() { tracing_subscriber::registry() @@ -19,8 +26,9 @@ async fn main() { search, bin, no_tkt, + all, }) => { - run_timetrack(new, comment, time_worked, search, bin, no_tkt).await; + run_timetrack(new, comment, time_worked, search, bin, no_tkt, all).await; } Some(cli::args::Commands::StdChg { @@ -62,6 +70,7 @@ async fn run_timetrack( search: Option, bin: Option, no_tkt: bool, + all: bool, ) { let (config, sn_client) = check_config(); tracing::debug!("New: {:?}", new); @@ -82,10 +91,24 @@ async fn run_timetrack( if new { sys_id = new_ticket(&sn_client, &config).await; } else { - let es_now_client = ElasticNow::new(&config.id, &config.instance); - let keywords = search.clone().unwrap_or("".to_string()); - let tkt_options = search_tickets(es_now_client, &tkt_bin, &keywords).await; - let tkt_options_string = search_results_to_string(&tkt_options); + let tkt_options_string: Vec; + let tkt_options: Vec; + if all { + let tkt_options_res = sn_client.get_all_tickets_in_bin(&tkt_bin).await; + if tkt_options_res.is_err() { + tracing::error!("Unable to get tickets: {:?}", tkt_options_res.err()); + std::process::exit(2); + } + let tkt_options_generic = tkt_options_res.unwrap(); + tkt_options = generic_options_to_value_option(&tkt_options_generic); + tkt_options_string = search_results_to_string(&tkt_options_generic); + } else { + let es_now_client = ElasticNow::new(&config.id, &config.instance); + let keywords = search.clone().unwrap_or("".to_string()); + let tkt_options_generic = search_tickets(es_now_client, &tkt_bin, &keywords).await; + tkt_options = generic_options_to_value_option(&tkt_options_generic); + tkt_options_string = search_results_to_string(&tkt_options_generic); + } let item = cli::args::choose_options(tkt_options_string); tracing::debug!("Selected item: {}", &item); match &*item { @@ -101,7 +124,7 @@ async fn run_timetrack( tracing::error!("Unexpected error on input"); std::process::exit(2); } - sys_id = tkt.unwrap().source.id; + sys_id = tkt.unwrap().value; } } } @@ -291,20 +314,29 @@ async fn search_tickets(es_now_client: ElasticNow, bin: &str, keywords: &str) -> resp.unwrap() } -fn search_results_to_string(result: &Vec) -> Vec { +fn generic_options_to_value_option>(result: &Vec) -> Vec { + let mut value_vec: Vec = Vec::new(); + for r in result { + let val_opt = ValueOption { + display_value: r.get_number(), + value: r.get_id(), + }; + value_vec.push(val_opt); + } + value_vec +} + +fn search_results_to_string>(result: &Vec) -> Vec { let mut string_vec: Vec = Vec::new(); for r in result { - string_vec.push(format!( - "{}: {}", - r.source.number, r.source.short_description - )); + string_vec.push(r.get_debug_string()); } string_vec } -fn get_search_result_from_input(input: &str, result: Vec) -> Option { +fn get_search_result_from_input(input: &str, result: Vec) -> Option { for r in result { - if input.starts_with(&r.source.number) { + if input.starts_with(&r.display_value) { return Some(r); } }