use gouqi::relationships::{GraphOptions, RelationshipGraph};
use gouqi::{Credentials, Jira};
use std::env;
use tracing::{error, info};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let host = env::var("JIRA_HOST").map_err(|_| "Missing JIRA_HOST environment variable")?;
let issue_key = env::args().nth(1).unwrap_or_else(|| "DEMO-1".to_string());
info!("Connecting to Jira at: {}", host);
info!("Analyzing relationships for issue: {}", issue_key);
let credentials = if let (Ok(username), Ok(password)) =
(env::var("JIRA_USERNAME"), env::var("JIRA_PASSWORD"))
{
Credentials::Basic(username, password)
} else if let Ok(token) = env::var("JIRA_TOKEN") {
Credentials::Bearer(token)
} else {
info!("No credentials provided, using anonymous access");
Credentials::Anonymous
};
let jira = Jira::new(host, credentials)?;
info!("Extracting all relationships to depth 2...");
match jira.issues().get_relationship_graph(&issue_key, 2, None) {
Ok(graph) => {
print_graph_summary(&graph);
let json = serde_json::to_string_pretty(&graph)?;
println!("\n=== JSON Export (AI-friendly format) ===");
println!("{}", json);
}
Err(e) => error!("Failed to extract relationship graph: {:?}", e),
}
info!("\nExtracting only blocking relationships...");
let blocking_options = GraphOptions {
include_types: Some(vec!["blocks".to_string(), "blocked_by".to_string()]),
exclude_types: None,
include_custom: false,
bidirectional: true,
};
match jira
.issues()
.get_relationship_graph(&issue_key, 3, Some(blocking_options))
{
Ok(blocking_graph) => {
print_graph_summary(&blocking_graph);
analyze_blocking_chain(&blocking_graph, &issue_key);
}
Err(e) => error!("Failed to extract blocking relationships: {:?}", e),
}
info!("\nDemonstrating bulk relationship extraction...");
let related_issues = vec![
format!("{}", issue_key),
format!("DEMO-2"),
format!("DEMO-3"),
];
match jira.issues().get_bulk_relationships(&related_issues, None) {
Ok(bulk_graph) => {
print_graph_summary(&bulk_graph);
}
Err(e) => error!("Failed to extract bulk relationships: {:?}", e),
}
Ok(())
}
fn print_graph_summary(graph: &RelationshipGraph) {
println!("\n=== Relationship Graph Summary ===");
println!("Source: {}", graph.metadata.source);
println!("Issues: {}", graph.metadata.issue_count);
println!("Total relationships: {}", graph.metadata.relationship_count);
println!("Max depth: {}", graph.metadata.max_depth);
println!("Generated: {}", graph.metadata.timestamp);
if let Some(root) = &graph.metadata.root_issue {
println!("Root issue: {}", root);
}
for (issue_key, relationships) in &graph.issues {
if !relationships.is_empty() {
println!("\nIssue: {}", issue_key);
if !relationships.blocks.is_empty() {
println!(" Blocks: {}", relationships.blocks.join(", "));
}
if !relationships.blocked_by.is_empty() {
println!(" Blocked by: {}", relationships.blocked_by.join(", "));
}
if !relationships.relates_to.is_empty() {
println!(" Relates to: {}", relationships.relates_to.join(", "));
}
if let Some(parent) = &relationships.parent {
println!(" Parent: {}", parent);
}
if !relationships.children.is_empty() {
println!(" Children: {}", relationships.children.join(", "));
}
if let Some(epic) = &relationships.epic {
println!(" Epic: {}", epic);
}
if !relationships.custom.is_empty() {
for (custom_type, targets) in &relationships.custom {
println!(" {}: {}", custom_type, targets.join(", "));
}
}
}
}
}
fn analyze_blocking_chain(graph: &RelationshipGraph, root_issue: &str) {
println!("\n=== Blocking Chain Analysis ===");
if let Some(relationships) = graph.get_relationships(root_issue) {
if !relationships.blocks.is_empty() {
println!("{} blocks:", root_issue);
for blocked_issue in &relationships.blocks {
print_blocking_chain(graph, blocked_issue, 1);
}
}
if !relationships.blocked_by.is_empty() {
println!("{} is blocked by:", root_issue);
for blocking_issue in &relationships.blocked_by {
print_blocking_chain_reverse(graph, blocking_issue, 1);
}
}
}
if let Some(path) = graph.get_path(root_issue, root_issue) {
if path.len() > 1 {
println!("⚠️ Circular dependency detected: {}", path.join(" -> "));
}
}
}
fn print_blocking_chain(graph: &RelationshipGraph, issue: &str, depth: usize) {
let indent = " ".repeat(depth);
println!("{}├─ {}", indent, issue);
if let Some(relationships) = graph.get_relationships(issue) {
for blocked_issue in &relationships.blocks {
if depth < 5 {
print_blocking_chain(graph, blocked_issue, depth + 1);
}
}
}
}
fn print_blocking_chain_reverse(graph: &RelationshipGraph, issue: &str, depth: usize) {
let indent = " ".repeat(depth);
println!("{}├─ {}", indent, issue);
if let Some(relationships) = graph.get_relationships(issue) {
for blocking_issue in &relationships.blocked_by {
if depth < 5 {
print_blocking_chain_reverse(graph, blocking_issue, depth + 1);
}
}
}
}