Compare commits
No commits in common. "0c459e2770ae299ab34ad2302f3ab5b1bbd88515" and "86fe96f0cde83ee98fb5ecaad492cfe319ff2925" have entirely different histories.
0c459e2770
...
86fe96f0cd
11 changed files with 143 additions and 325 deletions
87
Cargo.lock
generated
87
Cargo.lock
generated
|
@ -256,6 +256,16 @@ version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "config"
|
name = "config"
|
||||||
version = "0.15.6"
|
version = "0.15.6"
|
||||||
|
@ -329,31 +339,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossterm"
|
|
||||||
version = "0.28.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"crossterm_winapi",
|
|
||||||
"mio",
|
|
||||||
"parking_lot",
|
|
||||||
"rustix",
|
|
||||||
"signal-hook",
|
|
||||||
"signal-hook-mio",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossterm_winapi"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -935,8 +920,8 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"colored",
|
||||||
"config",
|
"config",
|
||||||
"crossterm",
|
|
||||||
"directories",
|
"directories",
|
||||||
"gray_matter",
|
"gray_matter",
|
||||||
"open",
|
"open",
|
||||||
|
@ -970,6 +955,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.169"
|
version = "0.2.169"
|
||||||
|
@ -1042,7 +1033,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
@ -1555,27 +1545,6 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "signal-hook"
|
|
||||||
version = "0.3.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"signal-hook-registry",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "signal-hook-mio"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"mio",
|
|
||||||
"signal-hook",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
|
@ -2084,28 +2053,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu",
|
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
|
@ -9,6 +9,7 @@ reqwest = { version = "0.12", features = ["json"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
colored = "2.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
config = "0.15"
|
config = "0.15"
|
||||||
directories = "6.0"
|
directories = "6.0"
|
||||||
|
@ -16,5 +17,4 @@ tempfile = "3.8"
|
||||||
gray_matter = { version = "0.2", default-features = false, features = ["toml"] }
|
gray_matter = { version = "0.2", default-features = false, features = ["toml"] }
|
||||||
open = "5.2"
|
open = "5.2"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
tabwriter = "1.4"
|
tabwriter = "1.4"
|
||||||
crossterm = "0.28"
|
|
34
readme.md
34
readme.md
|
@ -24,10 +24,9 @@ cargo install jirac
|
||||||
Usage: jirac <COMMAND>
|
Usage: jirac <COMMAND>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
create Create an issue
|
create Create a ticket
|
||||||
list Find issues currently assigned to you
|
list Find tickets currently assigned to you
|
||||||
search Search for issues
|
init Setup the configuration
|
||||||
init Set up the configuration
|
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
@ -69,7 +68,7 @@ jirac create ticket.md
|
||||||
jirac create --project KEY
|
jirac create --project KEY
|
||||||
```
|
```
|
||||||
|
|
||||||
### Listing tickets
|
## Listing tickets
|
||||||
|
|
||||||
```
|
```
|
||||||
Find tickets currently assigned to you
|
Find tickets currently assigned to you
|
||||||
|
@ -81,29 +80,6 @@ Options:
|
||||||
-h, --help Print help
|
-h, --help Print help
|
||||||
```
|
```
|
||||||
|
|
||||||
### Search for tickets
|
|
||||||
|
|
||||||
```
|
|
||||||
Search for issues
|
|
||||||
|
|
||||||
Usage: jirac search [OPTIONS] <JQL>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<JQL> A JQL string
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--json Print JSON rather than pretty print
|
|
||||||
-h, --help Print help
|
|
||||||
```
|
|
||||||
|
|
||||||
Use [JQL](https://support.atlassian.com/jira-software-cloud/docs/use-advanced-search-with-jira-query-language-jql/) to search for Issues.
|
|
||||||
|
|
||||||
*Find all in-progress tickets in a project*
|
|
||||||
|
|
||||||
```
|
|
||||||
jirac search 'project = KEY AND status = "In Progress" ORDER BY created DESC'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Get the following information:
|
Get the following information:
|
||||||
|
@ -112,7 +88,7 @@ Get the following information:
|
||||||
* You email
|
* You email
|
||||||
* [An API key](https://id.atlassian.com/manage-profile/security/api-tokens)
|
* [An API key](https://id.atlassian.com/manage-profile/security/api-tokens)
|
||||||
|
|
||||||
Then run the `jirac init` command
|
Then run the the `jirac init` command
|
||||||
|
|
||||||
```
|
```
|
||||||
Setup the configuration
|
Setup the configuration
|
||||||
|
|
24
src/cli.rs
24
src/cli.rs
|
@ -10,37 +10,27 @@ pub struct Cli {
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
/// Create an issue
|
/// Create a ticket
|
||||||
Create {
|
Create {
|
||||||
/// The project key in which to create the issue
|
/// The project key in which to create the ticket
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
project: Option<String>,
|
project: Option<String>,
|
||||||
|
|
||||||
/// Open the new issue in a browser
|
/// Open the new ticket in a browser
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
open: bool,
|
open: bool,
|
||||||
|
|
||||||
/// A Markdown file
|
/// A markdown file
|
||||||
#[arg(value_name = "MARKDOWN_FILE")]
|
#[arg(value_name = "MARKDOWN_FILE")]
|
||||||
markdown_file: Option<PathBuf>,
|
markdown_file: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
/// Find issues currently assigned to you
|
/// Find tickets currently assigned to you
|
||||||
List {
|
List {
|
||||||
/// Print JSON rather than pretty print
|
/// Print json rather than pretty print
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
json: bool,
|
json: bool,
|
||||||
},
|
},
|
||||||
/// Search for issues
|
/// Setup the configuration
|
||||||
Search {
|
|
||||||
/// Print JSON rather than pretty print
|
|
||||||
#[arg(long)]
|
|
||||||
json: bool,
|
|
||||||
|
|
||||||
/// A JQL string
|
|
||||||
#[arg(value_name = "JQL")]
|
|
||||||
jql: String,
|
|
||||||
},
|
|
||||||
/// Set up the configuration
|
|
||||||
Init {
|
Init {
|
||||||
/// Jira instance URL
|
/// Jira instance URL
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod create;
|
pub mod create;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod search;
|
|
||||||
|
|
116
src/cmd/list.rs
116
src/cmd/list.rs
|
@ -1,5 +1,115 @@
|
||||||
use crate::jira_config::JiraConfig;
|
use crate::jira_config::JiraConfig;
|
||||||
use crate::types::issue::{display_issues_json, display_issues_pretty};
|
use colored::Colorize;
|
||||||
|
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
struct JiraIssue {
|
||||||
|
key: String,
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
href: String,
|
||||||
|
fields: JiraIssueResponseFields,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
struct JiraIssueResponseFields {
|
||||||
|
summary: String,
|
||||||
|
status: Status,
|
||||||
|
created: chrono::DateTime<chrono::Utc>,
|
||||||
|
priority: Priority,
|
||||||
|
assignee: Person,
|
||||||
|
reporter: Person,
|
||||||
|
creator: Person,
|
||||||
|
#[serde(rename = "duedate")]
|
||||||
|
due_date: Option<chrono::NaiveDate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct JiraSearchResponse {
|
||||||
|
issues: Vec<JiraIssue>,
|
||||||
|
total: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
struct Status {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
struct Priority {
|
||||||
|
name: String,
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
struct Person {
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
href: String,
|
||||||
|
#[serde(rename = "displayName")]
|
||||||
|
display_name: String,
|
||||||
|
#[serde(rename = "accountId")]
|
||||||
|
account_id: String,
|
||||||
|
#[serde(rename = "emailAddress")]
|
||||||
|
email_address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_jira_issues(
|
||||||
|
config: &JiraConfig,
|
||||||
|
) -> Result<JiraSearchResponse, Box<dyn std::error::Error>> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||||
|
|
||||||
|
// JQL query to find issues assigned to the specified user
|
||||||
|
let jql =
|
||||||
|
"assignee = currentUser() AND resolution = Unresolved order by updated DESC".to_string();
|
||||||
|
let query = [("jql", jql.as_str())];
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.get(format!("{}/rest/api/2/search", config.url))
|
||||||
|
.basic_auth(&config.email, Some(&config.api_token))
|
||||||
|
.headers(headers)
|
||||||
|
.query(&query)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
return Err(format!("Failed to fetch issues: {}", error_text).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response.json::<JiraSearchResponse>().await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Found {} issues:", issues.len());
|
||||||
|
println!("{:-<80}", "");
|
||||||
|
|
||||||
|
for issue in issues {
|
||||||
|
let mut tw = tabwriter::TabWriter::new(vec![]);
|
||||||
|
writeln!(tw, "{}:\t{}", "Key".blue(), issue.key)?;
|
||||||
|
writeln!(tw, "{}:\t{}", "Summary".blue(), issue.fields.summary)?;
|
||||||
|
writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?;
|
||||||
|
writeln!(tw, "{}:\t{}", "Created".blue(), issue.fields.created)?;
|
||||||
|
writeln!(tw, "{}:\t{:?}", "Due Date".blue(), issue.fields.due_date)?;
|
||||||
|
writeln!(tw, "{}:\t{}", "URL".blue(), issue.href.underline())?;
|
||||||
|
tw.flush().unwrap();
|
||||||
|
|
||||||
|
let written = String::from_utf8(tw.into_inner().unwrap()).unwrap();
|
||||||
|
print!("{}", written);
|
||||||
|
println!("{:-<80}", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_issues_json(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let j = serde_json::to_string_pretty(issues)?;
|
||||||
|
println!("{}", j);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?;
|
let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?;
|
||||||
|
@ -7,9 +117,7 @@ pub async fn list(json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("Fetching issues assigned...");
|
println!("Fetching issues assigned...");
|
||||||
}
|
}
|
||||||
|
|
||||||
let jql = "assignee = currentUser() AND resolution = Unresolved order by updated DESC";
|
match list_jira_issues(&config).await {
|
||||||
|
|
||||||
match crate::jql::run(&config, jql).await {
|
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if json {
|
if json {
|
||||||
if response.issues.is_empty() {
|
if response.issues.is_empty() {
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
use crate::jira_config::JiraConfig;
|
|
||||||
use crate::types::issue::{display_issues_json, display_issues_pretty};
|
|
||||||
|
|
||||||
pub async fn exec(json: bool, jql: String) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?;
|
|
||||||
if !json {
|
|
||||||
println!("Searching for issues...");
|
|
||||||
}
|
|
||||||
|
|
||||||
match crate::jql::run(&config, &jql).await {
|
|
||||||
Ok(response) => {
|
|
||||||
if json {
|
|
||||||
if response.issues.is_empty() {
|
|
||||||
println!("[]");
|
|
||||||
} else {
|
|
||||||
display_issues_json(&response.issues)?;
|
|
||||||
}
|
|
||||||
} else if response.issues.is_empty() {
|
|
||||||
println!("No results found for query.");
|
|
||||||
} else {
|
|
||||||
display_issues_pretty(&response.issues)?;
|
|
||||||
println!("Total issues: {}", response.total);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error fetching issues: {}", e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
37
src/jql.rs
37
src/jql.rs
|
@ -1,37 +0,0 @@
|
||||||
use crate::jira_config::JiraConfig;
|
|
||||||
use crate::types::issue::JiraIssue;
|
|
||||||
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct JiraSearchResponse {
|
|
||||||
pub issues: Vec<JiraIssue>,
|
|
||||||
pub total: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(
|
|
||||||
config: &JiraConfig,
|
|
||||||
jql: &str,
|
|
||||||
) -> Result<JiraSearchResponse, Box<dyn std::error::Error>> {
|
|
||||||
let client = reqwest::Client::new();
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
|
||||||
|
|
||||||
let query = [("jql", jql)];
|
|
||||||
|
|
||||||
let response = client
|
|
||||||
.get(format!("{}/rest/api/2/search", config.url))
|
|
||||||
.basic_auth(&config.email, Some(&config.api_token))
|
|
||||||
.headers(headers)
|
|
||||||
.query(&query)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
|
||||||
let error_text = response.text().await?;
|
|
||||||
return Err(format!("Failed to fetch issues: {}", error_text).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(response.json::<JiraSearchResponse>().await?)
|
|
||||||
}
|
|
|
@ -1,8 +1,6 @@
|
||||||
mod cli;
|
mod cli;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod jira_config;
|
mod jira_config;
|
||||||
mod jql;
|
|
||||||
mod types;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::{Cli, Commands};
|
use cli::{Cli, Commands};
|
||||||
|
@ -19,7 +17,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
markdown_file,
|
markdown_file,
|
||||||
} => cmd::create::create(project, open, markdown_file).await?,
|
} => cmd::create::create(project, open, markdown_file).await?,
|
||||||
Commands::List { json } => cmd::list::list(json).await?,
|
Commands::List { json } => cmd::list::list(json).await?,
|
||||||
Commands::Search { json, jql } => cmd::search::exec(json, jql).await?,
|
|
||||||
Commands::Init { url, email, token } => {
|
Commands::Init { url, email, token } => {
|
||||||
JiraConfig::init(url, email, token).await?;
|
JiraConfig::init(url, email, token).await?;
|
||||||
println!("Configuration initialized successfully!");
|
println!("Configuration initialized successfully!");
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
pub mod issue;
|
|
|
@ -1,129 +0,0 @@
|
||||||
use crossterm::style::Stylize;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct JiraIssue {
|
|
||||||
pub key: String,
|
|
||||||
#[serde(rename = "self")]
|
|
||||||
pub href: String,
|
|
||||||
pub fields: JiraIssueResponseFields,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct JiraIssueResponseFields {
|
|
||||||
pub summary: String,
|
|
||||||
pub status: Status,
|
|
||||||
pub created: chrono::DateTime<chrono::Utc>,
|
|
||||||
pub priority: Priority,
|
|
||||||
pub assignee: Person,
|
|
||||||
pub reporter: Person,
|
|
||||||
pub creator: Person,
|
|
||||||
#[serde(rename = "duedate")]
|
|
||||||
pub due_date: Option<chrono::NaiveDate>,
|
|
||||||
pub comment: Option<Comments>,
|
|
||||||
pub votes: Votes,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Status {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Priority {
|
|
||||||
pub name: String,
|
|
||||||
pub id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Person {
|
|
||||||
#[serde(rename = "self")]
|
|
||||||
pub href: String,
|
|
||||||
#[serde(rename = "displayName")]
|
|
||||||
pub display_name: String,
|
|
||||||
#[serde(rename = "accountId")]
|
|
||||||
pub account_id: String,
|
|
||||||
#[serde(rename = "emailAddress")]
|
|
||||||
pub email_address: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Comments {
|
|
||||||
#[serde(rename = "self")]
|
|
||||||
pub href: String,
|
|
||||||
pub total: u32,
|
|
||||||
#[serde(rename = "maxResults")]
|
|
||||||
pub max_results: u32,
|
|
||||||
#[serde(rename = "startAt")]
|
|
||||||
pub start_at: u32,
|
|
||||||
pub comments: Vec<Comment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Comment {
|
|
||||||
#[serde(rename = "self")]
|
|
||||||
pub href: String,
|
|
||||||
pub id: String,
|
|
||||||
pub author: Person,
|
|
||||||
pub body: String,
|
|
||||||
#[serde(rename = "updateAuthor")]
|
|
||||||
pub update_author: Person,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Votes {
|
|
||||||
#[serde(rename = "self")]
|
|
||||||
pub href: String,
|
|
||||||
#[serde(rename = "votes")]
|
|
||||||
pub count: i32,
|
|
||||||
#[serde(rename = "hasVoted")]
|
|
||||||
pub has_voted: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
println!("Found {} issues:", issues.len());
|
|
||||||
println!("{:-<80}", "");
|
|
||||||
|
|
||||||
for issue in issues {
|
|
||||||
let mut tw = tabwriter::TabWriter::new(vec![]);
|
|
||||||
writeln!(tw, "{}:\t{}", "Key".blue(), issue.key)?;
|
|
||||||
writeln!(tw, "{}:\t{}", "Summary".blue(), issue.fields.summary)?;
|
|
||||||
writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?;
|
|
||||||
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(),
|
|
||||||
}
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
tw,
|
|
||||||
"\u{1b}]8;;{}\u{7}{}\u{1b}]8;;\u{7}",
|
|
||||||
issue.href,
|
|
||||||
"Open Issue".green()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
tw.flush().unwrap();
|
|
||||||
|
|
||||||
let written = String::from_utf8(tw.into_inner().unwrap()).unwrap();
|
|
||||||
print!("{}", written);
|
|
||||||
println!("{:-<80}", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display_issues_json(issues: &[JiraIssue]) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let j = serde_json::to_string_pretty(issues)?;
|
|
||||||
println!("{}", j);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
Loading…
Reference in a new issue