use std::collections::HashMap;
use anyhow::{Context, Result};
use crate::jira::JiraClient;
pub async fn run(
client: &JiraClient,
issue_key: &str,
field: Option<&str>,
raw: bool,
) -> Result<()> {
if let (Some(fid), true) = (field, raw) {
let value = client
.get_editmeta_field_raw(issue_key, fid)
.await
.context("Failed to fetch editmeta")?;
println!("{}", serde_json::to_string_pretty(&value)?);
return Ok(());
}
let (defs_res, issue_res) = tokio::join!(
client.get_all_fields(),
client.get_issue_all_fields(issue_key),
);
let defs = defs_res.context("Failed to fetch field definitions")?;
let issue = issue_res.context("Failed to fetch issue")?;
let name_map: HashMap<String, String> = defs.into_iter().map(|f| (f.id, f.name)).collect();
let fields = issue
.get("fields")
.and_then(|f| f.as_object())
.ok_or_else(|| anyhow::anyhow!("No 'fields' object in Jira response"))?;
if let Some(fid) = field {
match fields.get(fid) {
Some(v) => {
println!("{}", serde_json::to_string_pretty(v)?);
return Ok(());
}
None => anyhow::bail!("Field '{fid}' not present on issue {issue_key}"),
}
}
let col_id = 30usize;
let col_name = 30usize;
println!(
"{:<col_id$} {:<col_name$} Value",
"Field ID", "Field Name"
);
println!("{}", "─".repeat(col_id + 2 + col_name + 2 + 50));
let mut rows: Vec<(&String, &serde_json::Value)> = fields.iter().collect();
rows.sort_by_key(|(k, _)| {
let is_custom = k.starts_with("customfield_");
(is_custom, k.as_str())
});
for (id, value) in rows {
if value.is_null() {
continue; }
let name = name_map
.get(id.as_str())
.map_or("—", std::string::String::as_str);
let display = format_value(value);
println!("{id:<col_id$} {name:<col_name$} {display}");
}
println!();
println!("Tip: use --field FIELD_ID to see the full raw value of a specific field.");
Ok(())
}
fn format_value(v: &serde_json::Value) -> String {
match v {
serde_json::Value::Null => "(null)".into(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::String(s) => truncate(&s.replace('\n', "↵"), 80),
serde_json::Value::Object(_) if v.get("type").and_then(|t| t.as_str()) == Some("doc") => {
truncate(&crate::jira::adf::json_to_text(v).replace('\n', "↵"), 80)
}
serde_json::Value::Array(a) => {
if a.is_empty() {
return "(empty list)".into();
}
let items: Vec<String> = a
.iter()
.take(5)
.map(|item| {
item.get("name")
.or_else(|| item.get("value"))
.or_else(|| item.get("displayName"))
.and_then(|n| n.as_str())
.map_or_else(
|| truncate(&item.to_string(), 20),
std::string::ToString::to_string,
)
})
.collect();
let suffix = if a.len() > 5 {
format!(", … ({} total)", a.len())
} else {
String::new()
};
format!("[{}{}]", items.join(", "), suffix)
}
serde_json::Value::Object(_) => {
for key in &["name", "value", "displayName", "key"] {
if let Some(s) = v.get(key).and_then(|n| n.as_str()) {
return format!("{{{key}: {s}}}");
}
}
truncate(&v.to_string(), 80)
}
}
}
fn truncate(s: &str, max: usize) -> String {
if s.chars().count() <= max {
s.to_string()
} else {
let end = s.char_indices().nth(max).map_or(s.len(), |(i, _)| i);
format!("{}…", &s[..end])
}
}