Skip to content

Commit

Permalink
add get all tickets option
Browse files Browse the repository at this point in the history
  • Loading branch information
bck01215 committed Jun 26, 2024
1 parent af8fe4b commit 37f3843
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 16 deletions.
1 change: 1 addition & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>` | Comment for time tracking |
| `--time-worked <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 <SEARCH>` | Keyword search using ElasticNow (returns all tickets in bin by default) |
Expand Down
8 changes: 6 additions & 2 deletions src/cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<String>,
#[clap(short, long, visible_alias = "assignment-group")]
Expand Down
18 changes: 18 additions & 0 deletions src/elasticnow/elasticnow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ pub struct Ticket {
pub type_: String,
}

impl ChooseOptions<SearchResult> 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<T> {
fn get_debug_string(&self) -> String;
fn get_id(&self) -> String;
fn get_number(&self) -> String;
}

pub struct ElasticNow {
instance: String,
pub client: Client,
Expand Down
23 changes: 22 additions & 1 deletion src/elasticnow/servicenow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -48,6 +48,27 @@ impl ServiceNow {
.send()
.await
}
pub async fn get_all_tickets_in_bin(
&self,
bin: &str,
) -> Result<Vec<ShortDescNumberID>, Box<dyn Error>> {
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::<SNResult<Vec<ShortDescNumberID>>>(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<String, Box<dyn Error>> {
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",
Expand Down
20 changes: 20 additions & 0 deletions src/elasticnow/servicenow_structs.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::elasticnow::elasticnow::ChooseOptions;
use crate::elasticnow::servicenow;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -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<ShortDescNumberID> 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 {
Expand Down
58 changes: 45 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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 {
Expand Down Expand Up @@ -62,6 +70,7 @@ async fn run_timetrack(
search: Option<String>,
bin: Option<String>,
no_tkt: bool,
all: bool,
) {
let (config, sn_client) = check_config();
tracing::debug!("New: {:?}", new);
Expand All @@ -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<String>;
let tkt_options: Vec<ValueOption>;
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 {
Expand All @@ -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;
}
}
}
Expand Down Expand Up @@ -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<SearchResult>) -> Vec<String> {
fn generic_options_to_value_option<T: ChooseOptions<T>>(result: &Vec<T>) -> Vec<ValueOption> {
let mut value_vec: Vec<ValueOption> = 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<T: ChooseOptions<T>>(result: &Vec<T>) -> Vec<String> {
let mut string_vec: Vec<String> = 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<SearchResult>) -> Option<SearchResult> {
fn get_search_result_from_input(input: &str, result: Vec<ValueOption>) -> Option<ValueOption> {
for r in result {
if input.starts_with(&r.source.number) {
if input.starts_with(&r.display_value) {
return Some(r);
}
}
Expand Down

0 comments on commit 37f3843

Please sign in to comment.