jira-terminal 2.5.0

This is a command line application that can be used as a personal productivity tool for interacting with JIRA
use crate::config;
use crate::jira::api;
use crate::jira::utils;
use clap::ArgMatches;
use std::io::{stdin, BufRead};

#[derive(Debug, Default)]
struct CreationPayload {
    project: String,
    parent: Option<String>,
    issuetype: Option<String>,
    labels: Option<Vec<String>>,
    priority: Option<String>,
    summary: String,
    description: Option<String>,
    components: Option<Vec<String>>,
    assignee: String,
    custom: Option<String>,
}

impl CreationPayload {
    fn json(self) -> json::JsonValue {
        let mut payload = json::object! {
            "project": {
                "key": self.project
            },
            "summary": self.summary,
            "assignee": {
                "accountId": self.assignee
            }
        };
        if let Some(parent) = self.parent {
            payload["parent"]["key"] = parent.into();
        }
        if let Some(issuetype) = self.issuetype {
            payload["issuetype"]["id"] = issuetype.into();
        }
        if let Some(labels) = self.labels {
            payload["labels"] = labels.into();
        }
        if let Some(priority) = self.priority {
            payload["priority"] = priority.into();
        }
        if let Some(description) = self.description {
            payload["description"] = description.into();
        }
        if let Some(components) = self.components {
            let mut component_lists: Vec<json::JsonValue> = vec![];
            for component in components {
                component_lists.push(json::object! {
                    "name": component
                });
            }
            payload["components"] = component_lists.into();
        }
        if let Some(custom) = self.custom {
            let custom_fields = custom.split(',');
            for custom_field in custom_fields {
                if let Some((key, value)) = custom_field.split_once(':') {
                    payload[config::get_alias_or(key.to_string())] =
                        config::get_alias_or(value.to_string()).into();
                }
            }
        }

        json::object! {
            "fields": payload
        }
    }
}

fn split_and_apply_alias(entries: Option<String>) -> Option<Vec<String>> {
    let entry_value = entries.as_ref()?;
    let entry_list = entry_value.split(',');
    let mut entry_vector: Vec<String> = vec![];
    for entry in entry_list {
        entry_vector.push(config::get_alias_or(entry.to_string()));
    }
    Some(entry_vector)
}

fn get_or_ask(matches: &ArgMatches, key: &str, message: &str) -> Option<String> {
    if matches.is_present(key) {
        return Some(matches.value_of(key).unwrap().to_string());
    }
    if matches.is_present("quiet") {
        return None;
    }
    if matches.is_present("minimal") && key != "summary" {
        return None;
    }
    let mut entry = String::new();
    println!("{message} (Press enter to leave it to default.)");
    stdin()
        .read_line(&mut entry)
        .expect("Failed to read input.");
    if entry.trim().is_empty() {
        return None;
    }
    Some(config::get_alias_or(entry.trim().to_string()))
}

pub fn handle_issue_creation(matches: &ArgMatches) {
    let mut project: String = matches.value_of("project").unwrap_or("").to_string();
    let mut parent: Option<String> = None;
    if matches.is_present("main") {
        let main = matches.value_of("main").unwrap();
        let split = main.split_once('-');
        if split.is_none() {
            eprintln!("Invalid ticket id passed as main option.");
            std::process::exit(1);
        }
        project = split.unwrap().0.to_string();
        parent = Some(main.to_string());
    }
    if project.is_empty() {
        eprintln!("Cannot determine project. ");
        std::process::exit(1);
    }
    let summary = get_or_ask(
        matches,
        "summary",
        "Please enter the summary of the project: ",
    );
    if summary.is_none() {
        eprintln!("Summary is a required field.");
        std::process::exit(1);
    }
    let mut description = matches.value_of("description").unwrap_or("").to_string();
    if !matches.is_present("quiet") && description.is_empty() {
        println!("Please enter the description of issue. (Use ctrl+d to end the description)");
        let input = stdin();
        let mut line = String::new();
        let mut stream = input.lock();
        while let Ok(n) = stream.read_line(&mut line) {
            if n == 0 {
                break;
            }
            description = format!("{description}\n{line}");

            line = String::new();
        }
    }
    let assignee = if matches.is_present("assignee") {
        let assignee_query = matches.value_of("assignee").unwrap();
        utils::get_account_id(assignee_query.to_string())
    } else {
        config::get_config("account_id".to_string())
    };
    if assignee.is_empty() {
        eprintln!("Please provide appropriate user email to continue.");
        std::process::exit(1);
    }
    let issuetype = get_or_ask(matches, "type", "Please enter type of issue: ");
    let labels = get_or_ask(
        matches,
        "labels",
        "Please enter comma separated list of labels to assign: ",
    );
    let priority = get_or_ask(
        matches,
        "priority",
        "Please enter the priority of the ticket: ",
    );
    let components = get_or_ask(
        matches,
        "components",
        "Please enter Comma separated list of components of ticket: ",
    );
    let custom = if matches.is_present("custom") {
        Some(matches.value_of("custom").unwrap_or("").to_string())
    } else {
        None
    };
    let payload = CreationPayload {
        project: project.clone(),
        parent,
        issuetype: utils::get_issuetype_id(project, issuetype),
        priority,
        custom,
        summary: summary.unwrap(),
        labels: split_and_apply_alias(labels),
        components: split_and_apply_alias(components),
        assignee,
        description: if description.is_empty() {
            None
        } else {
            Some(description)
        },
    };
    let json_node = payload.json();
    let created_api_response = api::post_call("issue".to_string(), json_node, 2);
    if created_api_response.is_err() {
        eprintln!("Unable to create ticket.");
        std::process::exit(1);
    }
    let response = json::parse(&created_api_response.unwrap());
    if let Ok(response_object) = response {
        let key = &response_object["key"];
        println!("New Ticket KEY: {key} Created.");
    }
}