Compare commits

..

No commits in common. "476fa902004962abb2839b07c2a1ea8c7000f65f" and "b229ce9c5c5a4c662640ef5d5103aa82cef4c093" have entirely different histories.

3 changed files with 29 additions and 50 deletions

View file

@ -1,13 +1,13 @@
# Jirac # Jirac
A CLI for creating and managing Jira issues directly from your terminal. A CLI for creating and managing Jira tickets directly from your terminal.
## Features ## Features
- Create Jira issues from Markdown files or using your preferred editor - Create Jira tickets from markdown files or using your preferred editor
- List issues assigned to specific users - List tickets assigned to specific users
- Simple configuration using TOML - Simple configuration using TOML
- Interactive issue creation with templates - Interactive ticket creation with templates
- Execute JQL - Execute JQL
## Installation ## Installation
@ -27,7 +27,6 @@ Commands:
create Create an issue create Create an issue
list Find issues currently assigned to you list Find issues currently assigned to you
search Search for issues search Search for issues
view View an issue
init Set up 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)
@ -36,10 +35,10 @@ Options:
-V, --version Print version -V, --version Print version
``` ```
### Creating ab issue ### Creating a ticket
``` ```
Create an issue Create a ticket
Usage: jirac create [OPTIONS] [MARKDOWN_FILE] Usage: jirac create [OPTIONS] [MARKDOWN_FILE]
@ -47,8 +46,8 @@ Arguments:
[MARKDOWN_FILE] A markdown file [MARKDOWN_FILE] A markdown file
Options: Options:
--project <PROJECT> The project key in which to create the issue --project <PROJECT> The project key in which to create the ticket
--open Open the new issue in a browser --open Open the new ticket in a browser
-h, --help Print help -h, --help Print help
``` ```
@ -58,10 +57,10 @@ Options:
jirac create jirac create
``` ```
*From a Markdown file* *From a markdown file*
```sh ```sh
jirac create issue.md jirac create ticket.md
``` ```
*Specify a project* *Specify a project*
@ -70,10 +69,10 @@ jirac create issue.md
jirac create --project KEY jirac create --project KEY
``` ```
### Listing issues ### Listing tickets
``` ```
Find issues currently assigned to you Find tickets currently assigned to you
Usage: jirac list [OPTIONS] Usage: jirac list [OPTIONS]
@ -82,7 +81,7 @@ Options:
-h, --help Print help -h, --help Print help
``` ```
### Search for issues ### Search for tickets
``` ```
Search for issues Search for issues
@ -99,31 +98,12 @@ Options:
Use [JQL](https://support.atlassian.com/jira-software-cloud/docs/use-advanced-search-with-jira-query-language-jql/) to search for Issues. 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 issues in a project* *Find all in-progress tickets in a project*
``` ```
jirac search 'project = KEY AND status = "In Progress" ORDER BY created DESC' jirac search 'project = KEY AND status = "In Progress" ORDER BY created DESC'
``` ```
### View an issue
```
View an issue
Usage: jirac view [OPTIONS] <ISSUE>
Arguments:
<ISSUE> An issue key, for example, KEY-123
Options:
--json Print JSON rather than pretty print
-h, --help Print help
```
```sh
./target/release/jirac view KEY-123
```
## Configuration ## Configuration
Get the following information: Get the following information:

View file

@ -40,7 +40,6 @@ pub enum Commands {
#[arg(value_name = "JQL")] #[arg(value_name = "JQL")]
jql: String, jql: String,
}, },
/// View an issue
View { View {
/// Print JSON rather than pretty print /// Print JSON rather than pretty print
#[arg(long)] #[arg(long)]

View file

@ -10,7 +10,7 @@ use std::path::PathBuf;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
struct IssueMetadata { struct TicketMetadata {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
status: Option<String>, status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@ -101,15 +101,15 @@ struct Status {
name: String, name: String,
} }
fn get_issue_template( fn get_ticket_template(
issue_metadata: &IssueMetadata, ticket_metadata: &TicketMetadata,
) -> Result<String, Box<dyn std::error::Error>> { ) -> Result<String, Box<dyn std::error::Error>> {
let matter = toml::to_string(issue_metadata)?; let matter = toml::to_string(ticket_metadata)?;
let has_project = issue_metadata.project.is_some(); let has_project = ticket_metadata.project.is_some();
let template = format!( let template = format!(
r#"--- r#"---
# The issue can contain the following properties: # The ticket can contain the following properties:
# - status # - status
# - project (required) # - project (required)
# - assignee # - assignee
@ -139,9 +139,9 @@ fn get_editor() -> String {
fn parse_markdown( fn parse_markdown(
content: &str, content: &str,
) -> Result<(String, String, IssueMetadata), Box<dyn std::error::Error>> { ) -> Result<(String, String, TicketMetadata), Box<dyn std::error::Error>> {
let matter = Matter::<TOML>::new(); let matter = Matter::<TOML>::new();
let result = matter.parse_with_struct::<IssueMetadata>(content); let result = matter.parse_with_struct::<TicketMetadata>(content);
let (metadata, content) = match result { let (metadata, content) = match result {
Some(x) => (x.data, x.content), Some(x) => (x.data, x.content),
@ -153,7 +153,7 @@ fn parse_markdown(
// let metadata = result.data // let metadata = result.data
// .and_then(|d| toml::from_str(d).ok()) // .and_then(|d| toml::from_str(d).ok())
// .unwrap_or_else(|| IssueMetadata { // .unwrap_or_else(|| TicketMetadata {
// status: None, // status: None,
// project: None, // project: None,
// assignee: None, // assignee: None,
@ -182,7 +182,7 @@ fn parse_markdown(
fn create_temp_markdown(project_key: Option<String>) -> Result<String, Box<dyn std::error::Error>> { fn create_temp_markdown(project_key: Option<String>) -> Result<String, Box<dyn std::error::Error>> {
let mut temp_file = NamedTempFile::new()?; let mut temp_file = NamedTempFile::new()?;
let metadata = IssueMetadata { let metadata = TicketMetadata {
status: None, status: None,
project: project_key, project: project_key,
assignee: None, assignee: None,
@ -190,7 +190,7 @@ fn create_temp_markdown(project_key: Option<String>) -> Result<String, Box<dyn s
extra: Default::default(), extra: Default::default(),
}; };
let template = get_issue_template(&metadata)?; let template = get_ticket_template(&metadata)?;
temp_file.write_all(template.as_bytes())?; temp_file.write_all(template.as_bytes())?;
temp_file.flush()?; temp_file.flush()?;
@ -215,7 +215,7 @@ async fn create_jira_issue(
project_key: &str, project_key: &str,
title: &str, title: &str,
description: &str, description: &str,
metadata: &IssueMetadata, metadata: &TicketMetadata,
) -> Result<JiraResponse, Box<dyn std::error::Error>> { ) -> Result<JiraResponse, Box<dyn std::error::Error>> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@ -356,7 +356,7 @@ pub async fn create(
}; };
if content.trim().is_empty() { if content.trim().is_empty() {
return Err("Empty content. Aborting issue creation.".into()); return Err("Empty content. Aborting ticket creation.".into());
} }
let (title, description, metadata) = parse_markdown(&content)?; let (title, description, metadata) = parse_markdown(&content)?;
@ -375,7 +375,7 @@ pub async fn create(
} }
// Confirm creation // Confirm creation
println!("\nAbout to create an issue:"); println!("\nAbout to create ticket:");
println!("Project: {}", selected_project); println!("Project: {}", selected_project);
if let Some(status) = &metadata.status { if let Some(status) = &metadata.status {
println!("Status: {}", status); println!("Status: {}", status);
@ -399,7 +399,7 @@ pub async fn create(
let url = format!("{}/browse/{}", config.url, response.key); let url = format!("{}/browse/{}", config.url, response.key);
println!("Successfully created issue: {}", response.key); println!("Successfully created ticket: {}", response.key);
println!("URL: {}", url); println!("URL: {}", url);
if open { if open {