Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/cli/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{Context, Result};
use dialoguer::{Input, Password};

use crate::api::auth;
Expand All @@ -9,11 +9,13 @@ use crate::output;
pub async fn login_token() -> Result<()> {
let email: String = dialoguer::Input::new()
.with_prompt("Jira email")
.interact_text()?;
.interact_text()
.context("failed to read Jira email")?;

let token: String = dialoguer::Password::new()
.with_prompt("API token")
.interact()?;
.interact()
.context("failed to read API token")?;

auth::store_api_token(&email, &token)?;
println!("Credentials stored in keychain.");
Expand All @@ -28,11 +30,13 @@ pub async fn login_oauth() -> Result<()> {

let client_id: String = Input::new()
.with_prompt("OAuth Client ID")
.interact_text()?;
.interact_text()
.context("failed to read OAuth client ID")?;

let client_secret: String = Password::new()
.with_prompt("OAuth Client Secret")
.interact()?;
.interact()
.context("failed to read OAuth client secret")?;

// Store OAuth app credentials in keychain
crate::api::auth::store_oauth_app_credentials(&client_id, &client_secret)?;
Expand Down
22 changes: 15 additions & 7 deletions src/cli/init.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{Context, Result};
use dialoguer::{Confirm, Input, Select};

use crate::config::{
Expand All @@ -12,7 +12,8 @@ pub async fn handle() -> Result<()> {
// Step 1: Instance URL
let url: String = Input::new()
.with_prompt("Jira instance URL (e.g., https://yourorg.atlassian.net)")
.interact_text()?;
.interact_text()
.context("failed to read Jira instance URL")?;
let url = url.trim_end_matches('/').to_string();

// Step 2: Auth method
Expand All @@ -21,7 +22,8 @@ pub async fn handle() -> Result<()> {
.with_prompt("Authentication method")
.items(&auth_methods)
.default(0)
.interact()?;
.interact()
.context("failed to prompt for authentication method")?;

let global = GlobalConfig {
instance: InstanceConfig {
Expand Down Expand Up @@ -58,7 +60,8 @@ pub async fn handle() -> Result<()> {
let setup_project = Confirm::new()
.with_prompt("Configure this directory as a Jira project?")
.default(true)
.interact()?;
.interact()
.context("failed to prompt for project setup")?;

if setup_project {
let boards = client.list_boards(None, None).await?;
Expand All @@ -72,10 +75,14 @@ pub async fn handle() -> Result<()> {
let board_choice = Select::new()
.with_prompt("Select board")
.items(&board_names)
.interact()?;
.interact()
.context("failed to prompt for board selection")?;
let selected_board = &boards[board_choice];

let project_key: String = Input::new().with_prompt("Project key").interact_text()?;
let project_key: String = Input::new()
.with_prompt("Project key")
.interact_text()
.context("failed to read project key")?;

let project_config = format!(
"project = \"{}\"\nboard_id = {}\n",
Expand Down Expand Up @@ -118,7 +125,8 @@ pub async fn handle() -> Result<()> {
let selection = Select::new()
.with_prompt("Multiple story points fields found. Select one")
.items(&names)
.interact()?;
.interact()
.context("failed to prompt for story points field selection")?;
Some(matches[selection].0.clone())
}
};
Expand Down
23 changes: 15 additions & 8 deletions src/cli/issue/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{Context, Result};

use crate::api::client::JiraClient;
use crate::config::Config;
Expand Down Expand Up @@ -67,7 +67,8 @@ pub(super) async fn resolve_team_field(
let selection = dialoguer::Select::new()
.with_prompt(format!("Multiple teams named \"{}\"", team_name))
.items(&labels)
.interact()?;
.interact()
.context("failed to prompt for team selection")?;
Ok((field_id, duplicates[selection].id.clone()))
}
crate::partial_match::MatchResult::Ambiguous(matches) => {
Expand All @@ -82,7 +83,8 @@ pub(super) async fn resolve_team_field(
let selection = dialoguer::Select::new()
.with_prompt(format!("Multiple teams match \"{team_name}\""))
.items(&matches)
.interact()?;
.interact()
.context("failed to prompt for team selection")?;
let selected_name = &matches[selection];
let idx = teams
.iter()
Expand Down Expand Up @@ -115,7 +117,8 @@ pub(super) fn resolve_story_points_field_id(config: &Config) -> Result<String> {
pub(super) fn prompt_input(prompt: &str) -> Result<String> {
let input: String = dialoguer::Input::new()
.with_prompt(prompt)
.interact_text()?;
.interact_text()
.with_context(|| format!("failed to read {}", prompt))?;
Ok(input)
}

Expand Down Expand Up @@ -197,7 +200,8 @@ fn disambiguate_user(
let selection = dialoguer::Select::new()
.with_prompt(format!("Multiple users named \"{}\"", name))
.items(&labels)
.interact()?;
.interact()
.context("failed to prompt for user selection")?;
Ok((
duplicates[selection].account_id.clone(),
duplicates[selection].display_name.clone(),
Expand All @@ -214,7 +218,8 @@ fn disambiguate_user(
let selection = dialoguer::Select::new()
.with_prompt(format!("Multiple users match \"{name}\""))
.items(&matches)
.interact()?;
.interact()
.context("failed to prompt for user selection")?;
let selected_name = &matches[selection];
let idx = users
.iter()
Expand Down Expand Up @@ -428,7 +433,8 @@ pub(super) async fn resolve_asset(
let selection = dialoguer::Select::new()
.with_prompt(format!("Multiple assets match \"{}\"", input))
.items(&items)
.interact()?;
.interact()
.context("failed to prompt for asset selection")?;
Ok(duplicates[selection].object_key.clone())
}
crate::partial_match::MatchResult::Ambiguous(matches) => {
Expand Down Expand Up @@ -456,7 +462,8 @@ pub(super) async fn resolve_asset(
let selection = dialoguer::Select::new()
.with_prompt(format!("Multiple assets match \"{}\"", input))
.items(&items)
.interact()?;
.interact()
.context("failed to prompt for asset selection")?;
Ok(filtered[selection].object_key.clone())
}
crate::partial_match::MatchResult::None(_) => {
Expand Down
8 changes: 5 additions & 3 deletions src/cli/issue/links.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::json_output;
use anyhow::{Result, bail};
use anyhow::{Context, Result, bail};

use crate::api::client::JiraClient;
use crate::cli::{IssueCommand, OutputFormat};
Expand Down Expand Up @@ -74,7 +74,8 @@ pub(super) async fn handle_link(
let selection = dialoguer::Select::new()
.with_prompt(format!("Multiple types match \"{link_type_name}\""))
.items(&matches)
.interact()?;
.interact()
.context("failed to prompt for link type selection")?;
matches[selection].clone()
}
MatchResult::None(_) => {
Expand Down Expand Up @@ -143,7 +144,8 @@ pub(super) async fn handle_unlink(
let selection = dialoguer::Select::new()
.with_prompt(format!("Multiple types match \"{type_name}\""))
.items(&matches)
.interact()?;
.interact()
.context("failed to prompt for link type selection")?;
matches[selection].clone()
}
MatchResult::None(_) => {
Expand Down