Skip to content

Commit fef8f2b

Browse files
committed
jiracli: Add dependency graphs
Can currently graph immediate dependencies of an issue, or recursive dependencies of an epic. Still needs some color scheme work.
1 parent cee593d commit fef8f2b

File tree

2 files changed

+172
-1
lines changed

2 files changed

+172
-1
lines changed

cpanfile

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ requires 'LWP::UserAgent';
55
requires 'JSON::XS';
66
requires 'HTTP::Request';
77
requires 'Term::ReadLine';
8+
requires 'GraphViz2';

jiracli

+171-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use utf8;
2525
use Data::Dumper qw/Dumper/;
2626
use Env qw($HOME);
2727
use Config::Simple;
28+
use GraphViz2;
2829
use JIRA::REST;
2930
use Term::ReadLine;
3031

@@ -161,6 +162,135 @@ sub create {
161162
return "Created issue $res->{key}";
162163
}
163164

165+
sub status_color {
166+
my $status = shift;
167+
168+
return 'black';
169+
}
170+
171+
sub status_bgcolor {
172+
my $status = shift;
173+
174+
if ($status eq 'done') {
175+
return 'gray';
176+
} else {
177+
return 'black';
178+
}
179+
}
180+
181+
sub status_width {
182+
my $status = shift;
183+
184+
return 2.5 if $status !~ m/(new|done)/;
185+
186+
return 1.0;
187+
}
188+
189+
sub status_label {
190+
my ($status, $label) = @_;
191+
192+
if ($status ne 'indeterminate') {
193+
return "$label ($status)";
194+
} else {
195+
return $label
196+
}
197+
}
198+
199+
sub graph {
200+
my $iss = shift;
201+
my $recursionmap = shift;
202+
my $graph = shift;
203+
204+
$iss = $curissue if !defined($iss);
205+
return "No issue specified" if !defined($iss);
206+
207+
if (defined $recursionmap && defined $recursionmap->{$iss}) {
208+
return;
209+
}
210+
211+
my $res = eval {
212+
$jira->GET("/issue/$iss", { expand => 'names' });
213+
};
214+
return "Error with getting issue '$iss': $@" unless ($res);
215+
216+
217+
$graph = GraphViz2->new(
218+
edge => { color => 'grey' },
219+
global => { directed => 1 },
220+
graph => { label => "$iss Dependencies", rankdir => 'LR' },
221+
node => { shape => 'box', style => 'rounded,filled', fontcolor => 'white' },
222+
) unless defined $graph;
223+
224+
my $status = ($res->{fields}->{status}->{statusCategory}->{key});
225+
my $color = status_color($status);
226+
my $bgcolor = status_bgcolor($status);
227+
my $width = status_width($status);
228+
$graph->add_node(name => $iss, shape => 'box', color => $bgcolor, fillcolor => $bgcolor) unless defined ($recursionmap);
229+
230+
my $epic_issues = undef;
231+
my $do_recurse = 0;
232+
if (is_epic($res)) {
233+
$jql = '"' . $cfg->param('epiclink') . '"="' . $res->{fields}->{$customfields_n2i->{$cfg->param('epicname')}} . '"';
234+
my $perpage_stash = $perpage;
235+
$perpage = 1000;
236+
$epic_issues = eval { do_search($jql, 0, [ qw/summary status/ ]) };
237+
return "Error with search: $@" unless $epic_issues;
238+
$perpage = $perpage_stash;
239+
$do_recurse = 1;
240+
}
241+
242+
$recursionmap = { $iss => 1 } unless defined $recursionmap;
243+
244+
foreach my $link (@{$res->{fields}->{issuelinks}}) {
245+
my $outward = defined ($link->{outwardIssue}) ? 1 : 0;
246+
my $linktype = $link->{type}->{name};
247+
my $fromkey = '';
248+
my $tokey = '';
249+
my $key = '';
250+
my $status = '';
251+
252+
if ($outward) {
253+
$fromkey = $iss;
254+
$key = $tokey = $link->{outwardIssue}->{key};
255+
$status = $link->{outwardIssue}->{fields}->{status}->{statusCategory}->{key};
256+
} else {
257+
$tokey = $iss;
258+
$key = $fromkey = $link->{inwardIssue}->{key};
259+
$status = $link->{inwardIssue}->{fields}->{status}->{statusCategory}->{key};
260+
}
261+
262+
263+
$color = status_color($status);
264+
my $bgcolor = status_bgcolor($status);
265+
my $width = status_width($status);
266+
my $label = status_label($status, $link->{type}->{outward});
267+
268+
$graph->add_node(name => $key, color => $bgcolor, fillcolor => $bgcolor);
269+
$graph->add_edge(from => $fromkey, to => $tokey, label => $label, color => $bgcolor, penwidth => $width, fillcolor => $bgcolor, fontcolor => $color);
270+
271+
$recursionmap->{$key} = 1;
272+
273+
graph($key, $recursionmap, $graph);
274+
}
275+
276+
if ($do_recurse) {
277+
foreach my $link (@{$epic_issues->{issues}}) {
278+
my $status = $link->{fields}->{status}->{statusCategory}->{key};
279+
280+
$color = status_color($status);
281+
my $bgcolor = status_bgcolor($status);
282+
my $width = status_width($status);
283+
my $label = status_label($status, 'Epic Link');
284+
285+
$graph->add_node(name => $link->{key}, color => $bgcolor, fillcolor => $bgcolor);
286+
$graph->add_edge(from => $iss, to => $link->{key}, label => $label, fontcolor => 'black', penwidth => $width, fillcolor => $bgcolor, color => $bgcolor);
287+
graph($link->{key}, $recursionmap, $graph);
288+
}
289+
}
290+
291+
eval { $graph->run(format => 'svg', output_file => "./$iss.svg") };
292+
}
293+
164294
sub help {
165295
my $topic = shift;
166296

@@ -177,6 +307,13 @@ Commands:
177307
c
178308
create Create a new issue in the current project
179309
310+
e
311+
epic Jump to epic for the current issue
312+
313+
g
314+
graph [key] Creates a dependency graph, outputs to key.svg
315+
Uses current issue if no key is provided.
316+
180317
h
181318
help This help text
182319
@@ -329,6 +466,16 @@ sub search {
329466
return $ret;
330467
}
331468

469+
sub is_epic {
470+
my $obj = shift;
471+
472+
if (defined $obj->{fields}->{$customfields_n2i->{$cfg->param('epicname')}}) {
473+
return 1;
474+
}
475+
476+
return 0;
477+
}
478+
332479
sub show {
333480
my $key = shift;
334481

@@ -339,11 +486,28 @@ sub show {
339486
};
340487
return "Error with getting issue '$key': $@" unless ($res);
341488

489+
my $isstype = "Issue";
490+
my $epic_text = "Epic: ";
491+
492+
if (is_epic($res)) {
493+
$isstype = "Epic";
494+
$isstype = "Epic";
495+
$epic_text .= 'This issue is an epic';
496+
} else {
497+
$epic_text .= defined($res->{fields}->{$customfields_n2i->{$cfg->param('epiclink')}})
498+
? $res->{fields}->{$customfields_n2i->{$cfg->param('epiclink')}}
499+
: 'This issue is not part of an epic';
500+
}
501+
342502
$curissue = $key;
503+
504+
$prompt = "$curproj show $curissue J> ";
505+
343506
return <<EOF
344-
Issue [$key]:
507+
$isstype [$key]:
345508
Summary: $res->{fields}->{summary}
346509
Priority: $res->{fields}->{priority}->{name}
510+
$epic_text
347511
Description:
348512
$res->{fields}->{description}
349513
EOF
@@ -359,6 +523,12 @@ my $main_cmds = {
359523
'c' => \&create,
360524
'create' => \&create,
361525

526+
'e' => \&epic,
527+
'epic' => \&epic,
528+
529+
'g' => \&graph,
530+
'graph' => \&graph,
531+
362532
'h' => \&help,
363533
'help' => \&help,
364534

0 commit comments

Comments
 (0)