Bug 54750
Refactor script to procedures, fix minor bug with imagemaps git-svn-id: svn://svn.office.custis.ru/3rdparty/bugzilla.org/trunk@468 6955db30-a419-402b-8a0d-67ecbb4d7f56master
parent
1d36d1b45b
commit
a67b096859
|
@ -39,11 +39,91 @@ Bugzilla->login();
|
|||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
my $vars = {};
|
||||
|
||||
# Check params
|
||||
my $display = $cgi->param('display') || 'tree';
|
||||
if (!defined $cgi->param('id') && $display ne 'doall') {
|
||||
ThrowCodeError("missing_bug_id");
|
||||
}
|
||||
|
||||
# Connect to the shadow database if this installation is using one to improve
|
||||
# performance.
|
||||
my $dbh = Bugzilla->switch_to_shadow_db();
|
||||
|
||||
local our (%seen, %edgesdone, %bugtitles);
|
||||
my $webdotbase = Bugzilla->params->{webdotbase};
|
||||
my $webtwopibase = Bugzilla->params->{webtwopibase};
|
||||
|
||||
my ($seen, $edges, $baselist, $deps) = GetEdges($display, $cgi->param('id'));
|
||||
my ($nodes, $bugtitles) = GetNodes($seen, $baselist, $deps, $vars);
|
||||
my $filename = MakeDot($edges, $nodes);
|
||||
|
||||
# Remote dot server. We don't hardcode 'urlbase' here in case
|
||||
# 'sslbase' is in use.
|
||||
if ($webdotbase =~ /^https?:/)
|
||||
{
|
||||
$webdotbase =~ s/%([a-z]*)%/Bugzilla->params->{$1}/eg;
|
||||
my $url = $webdotbase . $filename;
|
||||
$vars->{image_url} = $url . ".gif";
|
||||
$vars->{map_url} = $url . ".map";
|
||||
}
|
||||
# Local dot installation
|
||||
else
|
||||
{
|
||||
# First, generate the png image file from the .dot source
|
||||
my ($pngfh, $pngfilename) = DotTemp('.png');
|
||||
DotInto($pngfh, $webdotbase, '-Tpng', $filename) or ($vars->{timeout} = 1);
|
||||
$vars->{image_url} = DotRel($pngfilename);
|
||||
|
||||
# Then, generate a imagemap datafile that contains the corner data
|
||||
# for drawn bug objects. Pass it on to CreateImagemap that
|
||||
# turns this monster into html.
|
||||
my ($mapfh, $mapfilename) = DotTemp('.map');
|
||||
DotInto($mapfh, $webdotbase, '-Tismap', $filename) or ($vars->{timeout} = 1);
|
||||
$vars->{image_map} = CreateImagemap($mapfilename, $bugtitles);
|
||||
}
|
||||
|
||||
# Cleanup any old .dot files created from previous runs.
|
||||
CleanupOldDots();
|
||||
|
||||
my @bugs = keys %$baselist;
|
||||
$vars->{bug_id} = join ', ', @bugs;
|
||||
$vars->{multiple_bugs} = scalar(keys %$baselist) > 1;
|
||||
$vars->{display} = $display;
|
||||
|
||||
# Generate and return the UI (HTML page) from the appropriate template.
|
||||
print $cgi->header();
|
||||
$template->process("bug/dependency-graph.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
# DotInto: Run local dot with timeout support and write output into filehandle
|
||||
sub DotInto
|
||||
{
|
||||
my $intofh = shift;
|
||||
my $cmd = [ @_ ];
|
||||
my $r = 1;
|
||||
binmode $intofh;
|
||||
my $quot = $^O =~ /MSWin/ ? '"' : "'";
|
||||
my $dottimeout = int(Bugzilla->params->{localdottimeout});
|
||||
if ($dottimeout && $dottimeout > 0)
|
||||
{
|
||||
# This creepy way is the only one that seems to be truly crossplatform
|
||||
$cmd = "perl -e $quot\$SIG{ALRM} = sub { exit 1 }; alarm $dottimeout; exec(" .
|
||||
join(",", map { 'q{'.$_.'}' } @$cmd) .
|
||||
");$quot";
|
||||
}
|
||||
else
|
||||
{
|
||||
$cmd = join " ", map { $quot.$_.$quot } @$cmd;
|
||||
}
|
||||
if (my $pid = open DOT, "$cmd|")
|
||||
{
|
||||
binmode DOT;
|
||||
print $intofh $_ while <DOT>;
|
||||
close DOT;
|
||||
}
|
||||
$r = undef unless tell $intofh;
|
||||
close $intofh;
|
||||
return $r;
|
||||
}
|
||||
|
||||
# CreateImagemap: This sub grabs a local filename as a parameter, reads the
|
||||
# dot-generated image map datafile residing in that file and turns it into
|
||||
|
@ -55,24 +135,28 @@ local our (%seen, %edgesdone, %bugtitles);
|
|||
# The dot mapdata lines have the following format (\nsummary is optional):
|
||||
# rectangle (LEFTX,TOPY) (RIGHTX,BOTTOMY) URLBASE/show_bug.cgi?id=BUGNUM BUGNUM[\nSUMMARY]
|
||||
|
||||
sub CreateImagemap {
|
||||
my $mapfilename = shift;
|
||||
sub CreateImagemap
|
||||
{
|
||||
my ($mapfilename, $bugtitles) = @_;
|
||||
my $map = "<map name=\"imagemap\">\n";
|
||||
my $default;
|
||||
|
||||
open MAP, "<$mapfilename";
|
||||
while(my $line = <MAP>) {
|
||||
if($line =~ /^default ([^ ]*)(.*)$/) {
|
||||
while (my $line = <MAP>)
|
||||
{
|
||||
if ($line =~ /^default ([^ ]*)(.*)$/)
|
||||
{
|
||||
$default = qq{<area alt="" shape="default" href="$1">\n};
|
||||
}
|
||||
|
||||
if ($line =~ /^rectangle \((.*),(.*)\) \((.*),(.*)\) (http[^ ]*) (\d+)(\\n.*)?$/) {
|
||||
if ($line =~ /^rectangle \((.*),(.*)\) \((.*),(.*)\) (http[^ ]*(\d+))/iso)
|
||||
{
|
||||
my ($leftx, $rightx, $topy, $bottomy, $url, $bugid) = ($1, $3, $2, $4, $5, $6);
|
||||
|
||||
# Pick up bugid from the mapdata label field. Getting the title from
|
||||
# bugtitle hash instead of mapdata allows us to get the summary even
|
||||
# when showsummary is off, and also gives us status and resolution.
|
||||
my $bugtitle = html_quote(clean_text($bugtitles{$bugid}));
|
||||
my $bugtitle = html_quote(clean_text($bugtitles->{$bugid})) || '';
|
||||
$map .= qq{<area alt="$bugtitle" name="bug$bugid" shape="rect" } .
|
||||
qq{title="$bugtitle" href="$url" } .
|
||||
qq{coords="$leftx,$topy,$rightx,$bottomy">\n};
|
||||
|
@ -84,206 +168,265 @@ sub CreateImagemap {
|
|||
return $map;
|
||||
}
|
||||
|
||||
sub AddLink {
|
||||
my ($blocked, $dependson, $fh) = (@_);
|
||||
sub AddLink
|
||||
{
|
||||
my ($blocked, $dependson, $seen, $edges) = (@_);
|
||||
my $key = "$blocked,$dependson";
|
||||
if (!exists $edgesdone{$key}) {
|
||||
$edgesdone{$key} = 1;
|
||||
print $fh "$blocked -> $dependson\n";
|
||||
$seen{$blocked} = 1;
|
||||
$seen{$dependson} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# The list of valid directions. Some are not proposed in the dropdrown
|
||||
# menu despite the fact that they are valid.
|
||||
my @valid_rankdirs = ('LR', 'RL', 'TB', 'BT');
|
||||
my $default_rankdir = Bugzilla->params->{graph_rankdir} || 'LR';
|
||||
|
||||
my $rankdir = $cgi->param('rankdir') || $default_rankdir;
|
||||
# Make sure the submitted 'rankdir' value is valid.
|
||||
if (lsearch(\@valid_rankdirs, $rankdir) < 0) {
|
||||
$rankdir = $default_rankdir;
|
||||
}
|
||||
|
||||
my $display = $cgi->param('display') || 'tree';
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
|
||||
if (!defined $cgi->param('id') && $display ne 'doall') {
|
||||
ThrowCodeError("missing_bug_id");
|
||||
}
|
||||
|
||||
my ($fh, $filename) = File::Temp::tempfile("XXXXXXXXXX",
|
||||
SUFFIX => '.dot',
|
||||
DIR => $webdotdir);
|
||||
my $urlbase = Bugzilla->params->{urlbase};
|
||||
|
||||
print $fh "digraph G {";
|
||||
print $fh qq{
|
||||
ranksep=0.5;
|
||||
graph [URL="${urlbase}query.cgi" rankdir=$rankdir overlap=false splines=true]
|
||||
node [URL="${urlbase}show_bug.cgi?id=\\N" shape=note fontsize=10 fontname="Consolas" style=filled fillcolor=white]
|
||||
edge [color=blue arrowtail=none arrowhead=none len=0.5]
|
||||
};
|
||||
|
||||
my %baselist;
|
||||
my %deps;
|
||||
|
||||
if ($display eq 'doall') {
|
||||
my $dependencies = $dbh->selectall_arrayref(
|
||||
"SELECT blocked, dependson FROM dependencies");
|
||||
|
||||
foreach my $dependency (@$dependencies) {
|
||||
my ($blocked, $dependson) = @$dependency;
|
||||
AddLink($blocked, $dependson, $fh);
|
||||
$deps{$dependson}++;
|
||||
}
|
||||
} else {
|
||||
foreach my $i (split('[\s,]+', $cgi->param('id'))) {
|
||||
my $bug = Bugzilla::Bug->check($i);
|
||||
$baselist{$bug->id} = 1;
|
||||
}
|
||||
|
||||
my @stack = keys(%baselist);
|
||||
|
||||
if ($display eq 'web') {
|
||||
my $sth = $dbh->prepare(q{SELECT blocked, dependson
|
||||
FROM dependencies
|
||||
WHERE blocked = ? OR dependson = ?});
|
||||
|
||||
foreach my $id (@stack) {
|
||||
my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
|
||||
foreach my $dependency (@$dependencies) {
|
||||
my ($blocked, $dependson) = @$dependency;
|
||||
if ($blocked != $id && !exists $seen{$blocked}) {
|
||||
push @stack, $blocked;
|
||||
}
|
||||
if ($dependson != $id && !exists $seen{$dependson}) {
|
||||
push @stack, $dependson;
|
||||
}
|
||||
AddLink($blocked, $dependson, $fh);
|
||||
$deps{$dependson}++;
|
||||
}
|
||||
}
|
||||
}
|
||||
# This is the default: a tree instead of a spider web.
|
||||
else {
|
||||
my @blocker_stack = @stack;
|
||||
foreach my $id (@blocker_stack) {
|
||||
my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
|
||||
foreach my $blocker_id (@$blocker_ids) {
|
||||
push(@blocker_stack, $blocker_id) unless $seen{$blocker_id};
|
||||
AddLink($id, $blocker_id, $fh);
|
||||
}
|
||||
}
|
||||
my @dependent_stack = @stack;
|
||||
foreach my $id (@dependent_stack) {
|
||||
my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
|
||||
foreach my $dep_bug_id (@$dep_bug_ids) {
|
||||
push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id};
|
||||
AddLink($dep_bug_id, $id, $fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $k (keys(%baselist)) {
|
||||
$seen{$k} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
my $sth = $dbh->prepare(q{
|
||||
SELECT t1.bug_status, t1.resolution, t1.short_desc, t1.estimated_time, SUM(t3.work_time), t1.assigned_to, t2.login_name, t4.name, t5.name, t1.bug_severity
|
||||
FROM bugs AS t1
|
||||
LEFT JOIN profiles AS t2 ON t2.userid=t1.assigned_to
|
||||
LEFT JOIN longdescs AS t3 ON t3.bug_id=t1.bug_id AND t3.work_time > 0
|
||||
LEFT JOIN products AS t4 ON t4.id=t1.product_id
|
||||
LEFT JOIN components AS t5 ON t5.id=t1.component_id
|
||||
WHERE t1.bug_id=?
|
||||
GROUP BY t1.bug_id
|
||||
});
|
||||
foreach my $k (keys(%seen)) {
|
||||
# Retrieve bug information from the database
|
||||
my ($stat, $resolution, $summary, $time, $wtime, $assignee, $asslogin, $product, $component, $bug_severity) = $dbh->selectrow_array($sth, undef, $k);
|
||||
$stat ||= 'NEW';
|
||||
$resolution ||= '';
|
||||
$summary ||= '';
|
||||
my $truncatedsummary = substr($summary, 0, 32);
|
||||
if (length($truncatedsummary) ne length($summary)) {
|
||||
$truncatedsummary .= '...';
|
||||
}
|
||||
|
||||
# Resolution and summary are shown only if user can see the bug
|
||||
if (!Bugzilla->user->can_see_bug($k)) {
|
||||
$resolution = $summary = '';
|
||||
}
|
||||
|
||||
$vars->{short_desc} = $summary if $k eq $cgi->param('id');
|
||||
|
||||
my $bgnodecolor=GetColorByState($stat, 1);
|
||||
my $nodecolor=GetColorByState($stat);
|
||||
|
||||
my $assigneecolor="white";
|
||||
$assigneecolor="red1" if $bug_severity eq "blocker";
|
||||
$assigneecolor="rosybrown1" if $bug_severity eq "critical";
|
||||
|
||||
my @params = ("color=" . $nodecolor);
|
||||
|
||||
push @params, "fillcolor=azure2" if exists $baselist{$k};
|
||||
# my $bgfillnodecolor="white";
|
||||
# $bgfillnodecolor="red" if exists $baselist{$k};
|
||||
|
||||
# if ($assignee == Bugzilla->user->id)
|
||||
# {
|
||||
# push @params, "shape=box";
|
||||
# }
|
||||
# else
|
||||
# {
|
||||
# push @params, "shape=egg";
|
||||
# }
|
||||
|
||||
my $important = $time > 40 || $wtime > 40 || ($deps{$k}||0) > 4;
|
||||
if ($important)
|
||||
if (!exists $edges->{$key})
|
||||
{
|
||||
push @params, "width=3", "height=1.5", "fontsize=13";
|
||||
$edges->{$key} = 1;
|
||||
$seen->{$blocked} = 1;
|
||||
$seen->{$dependson} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($summary ne "" && ($cgi->param('showsummary') || $important)) {
|
||||
$summary =~ s/([\\\"])/\\$1/g;
|
||||
# push(@params, qq{label="$k\\n$summary"});
|
||||
push(@params, qq{
|
||||
sub DotTemp
|
||||
{
|
||||
my ($suffix) = @_;
|
||||
return File::Temp::tempfile("XXXXXXXXXX",
|
||||
SUFFIX => $suffix,
|
||||
DIR => bz_locations()->{webdotdir});
|
||||
}
|
||||
|
||||
# Fix filename
|
||||
sub DotRel
|
||||
{
|
||||
my ($fn) = @_;
|
||||
# On Windows $pngfilename will contain \ instead of /
|
||||
$fn =~ s|\\|/|g if $^O eq 'MSWin32';
|
||||
# Under mod_perl, pngfilename will have an absolute path, and we
|
||||
# need to make that into a relative path.
|
||||
my $cgi_root = bz_locations()->{cgi_path};
|
||||
$fn =~ s!^\Q$cgi_root\E/?!!;
|
||||
return $fn;
|
||||
}
|
||||
|
||||
sub GetEdges
|
||||
{
|
||||
my ($display, $ids) = @_;
|
||||
my $seen = {};
|
||||
my $edges = {};
|
||||
my $baselist = {};
|
||||
my $deps = {};
|
||||
my $dbh = Bugzilla->switch_to_shadow_db;
|
||||
if ($display eq 'doall')
|
||||
{
|
||||
my $dependencies = $dbh->selectall_arrayref("SELECT blocked, dependson FROM dependencies");
|
||||
foreach my $dependency (@$dependencies)
|
||||
{
|
||||
my ($blocked, $dependson) = @$dependency;
|
||||
AddLink($blocked, $dependson, $seen, $edges);
|
||||
$deps->{$dependson}++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach my $i (split('[\s,]+', $ids))
|
||||
{
|
||||
my $bug = Bugzilla::Bug->check($i);
|
||||
$baselist->{$bug->id} = 1;
|
||||
$seen->{$bug->id} = 1;
|
||||
}
|
||||
|
||||
my @stack = keys %$baselist;
|
||||
|
||||
if ($display eq 'web')
|
||||
{
|
||||
my $sth = $dbh->prepare("SELECT blocked, dependson
|
||||
FROM dependencies
|
||||
WHERE blocked = ? OR dependson = ?");
|
||||
foreach my $id (@stack)
|
||||
{
|
||||
my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
|
||||
foreach my $dependency (@$dependencies)
|
||||
{
|
||||
my ($blocked, $dependson) = @$dependency;
|
||||
if ($blocked != $id && !exists $seen->{$blocked}) {
|
||||
push @stack, $blocked;
|
||||
}
|
||||
if ($dependson != $id && !exists $seen->{$dependson}) {
|
||||
push @stack, $dependson;
|
||||
}
|
||||
AddLink($blocked, $dependson, $seen, $edges);
|
||||
$deps->{$dependson}++;
|
||||
}
|
||||
}
|
||||
}
|
||||
# Only bugs with direct dependencies to selected
|
||||
else
|
||||
{
|
||||
my @blocker_stack = @stack;
|
||||
foreach my $id (@blocker_stack)
|
||||
{
|
||||
my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
|
||||
foreach my $blocker_id (@$blocker_ids)
|
||||
{
|
||||
push @blocker_stack, $blocker_id unless $seen->{$blocker_id};
|
||||
AddLink($id, $blocker_id, $seen, $edges);
|
||||
}
|
||||
}
|
||||
my @dependent_stack = @stack;
|
||||
foreach my $id (@dependent_stack)
|
||||
{
|
||||
my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
|
||||
foreach my $dep_bug_id (@$dep_bug_ids)
|
||||
{
|
||||
push @dependent_stack, $dep_bug_id unless $seen->{$dep_bug_id};
|
||||
AddLink($dep_bug_id, $id, $seen, $edges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ($seen, $edges, $baselist, $deps);
|
||||
}
|
||||
|
||||
sub GetNodes
|
||||
{
|
||||
my ($seen, $baselist, $deps, $vars) = @_;
|
||||
return [] unless keys %$seen;
|
||||
my $nodes = [];
|
||||
my $bugtitles = {};
|
||||
# Retrieve bug information from the database
|
||||
my $rows = Bugzilla->dbh->selectall_arrayref(
|
||||
"SELECT
|
||||
t1.bug_id,
|
||||
t1.bug_status,
|
||||
t1.resolution,
|
||||
t1.short_desc,
|
||||
t1.estimated_time,
|
||||
SUM(t3.work_time) AS work_time,
|
||||
t1.assigned_to,
|
||||
t2.login_name AS assigned_to_login,
|
||||
t4.name AS product,
|
||||
t5.name AS component,
|
||||
t1.bug_severity
|
||||
FROM bugs AS t1
|
||||
LEFT JOIN profiles AS t2 ON t2.userid=t1.assigned_to
|
||||
LEFT JOIN longdescs AS t3 ON t3.bug_id=t1.bug_id AND t3.work_time > 0
|
||||
LEFT JOIN products AS t4 ON t4.id=t1.product_id
|
||||
LEFT JOIN components AS t5 ON t5.id=t1.component_id
|
||||
WHERE t1.bug_id IN (".join(",", ("?") x scalar keys %$seen).")
|
||||
GROUP BY t1.bug_id", {Slice=>{}}, keys %$seen) || {};
|
||||
foreach my $row (@$rows)
|
||||
{
|
||||
# Resolution and summary are shown only if user can see the bug
|
||||
$row->{resolution} = $row->{short_desc} = '' unless Bugzilla->user->can_see_bug($row->{bug_id});
|
||||
$row->{bug_status} ||= 'NEW';
|
||||
if (length $row->{short_desc} > 32)
|
||||
{
|
||||
$row->{short_desc} = substr($row->{short_desc}, 0, 32) . '...';
|
||||
}
|
||||
# Current bug
|
||||
$vars->{short_desc} = $row->{short_desc} if $row->{bug_id} eq Bugzilla->cgi->param('id');
|
||||
|
||||
my $bgnodecolor = GetColorByState($row->{bug_status}, 1);
|
||||
my $nodecolor = GetColorByState($row->{bug_status});
|
||||
|
||||
my $assigneecolor = "white";
|
||||
$assigneecolor = "red1" if $row->{bug_severity} eq "blocker";
|
||||
$assigneecolor = "rosybrown1" if $row->{bug_severity} eq "critical";
|
||||
|
||||
my @params = ("color=" . $nodecolor);
|
||||
push @params, "fillcolor=azure2" if exists $baselist->{$row->{bug_id}};
|
||||
|
||||
my $important =
|
||||
($row->{estimated_time} || 0) > 40 ||
|
||||
($row->{work_time} || 0) > 40 ||
|
||||
($deps->{$row->{bug_id}} || 0) > 4;
|
||||
if ($important)
|
||||
{
|
||||
push @params, "width=3", "height=1.5", "fontsize=13";
|
||||
}
|
||||
|
||||
if ($row->{short_desc})
|
||||
{
|
||||
$row->{short_desc} =~ s/([\\\"])/\\$1/g;
|
||||
push @params, <<"EOF";
|
||||
label=<
|
||||
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
|
||||
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
|
||||
<TR>
|
||||
<TD BGCOLOR=$bgnodecolor>$k</TD>
|
||||
<TD BGCOLOR="$assigneecolor"><FONT POINT-SIZE="8">$asslogin</FONT></TD>
|
||||
</TR>
|
||||
<TD BGCOLOR=$bgnodecolor>$row->{bug_id}</TD>
|
||||
<TD BGCOLOR="$assigneecolor"><FONT POINT-SIZE="8">$row->{assigned_to_login}</FONT></TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD COLSPAN="2" ALIGN="TEXT">
|
||||
<FONT POINT-SIZE="8">
|
||||
$truncatedsummary
|
||||
</FONT>
|
||||
$row->{short_desc}
|
||||
</FONT>
|
||||
</TD>
|
||||
</TR>
|
||||
</TR>
|
||||
</TABLE>
|
||||
>
|
||||
});
|
||||
EOF
|
||||
}
|
||||
|
||||
push @$nodes, $row->{bug_id} . (@params ? " [" . join(',', @params) . "]" : "");
|
||||
|
||||
# Push the bug tooltip texts into a global hash so that
|
||||
# CreateImagemap sub (used with local dot installations) can
|
||||
# use them later on.
|
||||
$bugtitles->{$row->{bug_id}} = "[".trim("$row->{bug_status} $row->{resolution}")." $row->{assigned_to_login}]";
|
||||
|
||||
# Show the bug summary in tooltips only if not shown on
|
||||
# the graph and it is non-empty (the user can see the bug)
|
||||
if ($row->{short_desc}) {
|
||||
$bugtitles->{$row->{bug_id}} .= " $row->{product}/$row->{component} - $row->{short_desc}";
|
||||
}
|
||||
}
|
||||
return ($nodes, $bugtitles);
|
||||
}
|
||||
|
||||
if (@params) {
|
||||
print $fh "$k [" . join(',', @params) . "]\n";
|
||||
} else {
|
||||
print $fh "$k\n";
|
||||
sub MakeDot
|
||||
{
|
||||
my ($edges, $nodes) = @_;
|
||||
my ($fh, $filename) = DotTemp('.dot');
|
||||
my $urlbase = Bugzilla->params->{urlbase};
|
||||
no warnings 'utf8';
|
||||
print $fh "digraph G {";
|
||||
print $fh <<"EOF";
|
||||
ranksep=0.5;
|
||||
graph [URL="${urlbase}query.cgi" rankdir=LR overlap=false splines=true]
|
||||
node [URL="${urlbase}show_bug.cgi?id=\\N" shape=note fontsize=10 fontname="Consolas" style=filled fillcolor=white]
|
||||
edge [color=blue arrowtail=none arrowhead=none len=0.5]
|
||||
EOF
|
||||
OutLinks($edges, $fh);
|
||||
print $fh "$_\n" foreach @$nodes;
|
||||
print $fh "}\n";
|
||||
close $fh;
|
||||
chmod 0777, $filename;
|
||||
return $filename;
|
||||
}
|
||||
|
||||
sub OutLinks
|
||||
{
|
||||
my ($edges, $fh) = @_;
|
||||
my ($blocked, $dependson);
|
||||
for (keys %$edges)
|
||||
{
|
||||
($blocked, $dependson) = split /,/, $_, 2;
|
||||
print $fh "$blocked -> $dependson\n";
|
||||
}
|
||||
}
|
||||
|
||||
# Push the bug tooltip texts into a global hash so that
|
||||
# CreateImagemap sub (used with local dot installations) can
|
||||
# use them later on.
|
||||
$bugtitles{$k} = "[".trim("$stat $resolution")." $asslogin]";
|
||||
|
||||
# Show the bug summary in tooltips only if not shown on
|
||||
# the graph and it is non-empty (the user can see the bug)
|
||||
if ($summary ne "") {
|
||||
$bugtitles{$k} .= " $product/$component - $summary";
|
||||
sub CleanupOldDots
|
||||
{
|
||||
my $since = time() - 24 * 60 * 60;
|
||||
# Can't use glob, since even calling that fails taint checks for perl < 5.6
|
||||
my $webdotdir = bz_locations()->{webdotdir};
|
||||
opendir DIR, $webdotdir;
|
||||
my @files = grep { /\.dot$|\.png$|\.map$/ && -f "$webdotdir/$_" } readdir(DIR);
|
||||
closedir DIR;
|
||||
foreach my $f (@files)
|
||||
{
|
||||
$f = "$webdotdir/$f";
|
||||
# Here we are deleting all old files. All entries are from the
|
||||
# $webdot directory. Since we're deleting the file (not following
|
||||
# symlinks), this can't escape to delete anything it shouldn't
|
||||
# (unless someone moves the location of $webdotdir, of course)
|
||||
trick_taint($f);
|
||||
if (file_mod_time($f) < $since) {
|
||||
unlink $f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,169 +451,3 @@ sub GetColorByState
|
|||
$colorbystate{is_open_state($state) ? 'opened' : 'closed'});
|
||||
return $color;
|
||||
}
|
||||
|
||||
print $fh "}\n";
|
||||
close $fh;
|
||||
|
||||
chmod 0777, $filename;
|
||||
|
||||
sub buildtree
|
||||
{
|
||||
my $hash = shift;
|
||||
my $track = shift;
|
||||
my $r = {};
|
||||
for (@_)
|
||||
{
|
||||
unless ($track->{$_})
|
||||
{
|
||||
$track->{$_} = 1;
|
||||
$r->{$_} = buildtree($hash, $track, keys %{$hash->{$_}});
|
||||
}
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
|
||||
sub makemap # hgap="'.int(($nodes->{$_}->[0]-$x)*20).'" vshift="'.int(($nodes->{$_}->[1]-$y)*20).'"
|
||||
{
|
||||
my ($nodes, $edges, $tree, $x, $y, $header) = @_;
|
||||
my $map = '';
|
||||
for (keys %$tree)
|
||||
{
|
||||
$map .= '<node background_color="'.$nodes->{$_}->[7].'" id="node_'.$_.'" text="'.$nodes->{$_}->[4].'">'."\n";
|
||||
$map .= makemap($nodes, $edges, $tree->{$_}, $nodes->{$_}->[0], $nodes->{$_}->[1]);
|
||||
for my $r (keys %{$edges->{$_} || {}})
|
||||
{
|
||||
$map .= '<arrowlink destination="node_'.$r.'" endarrow="default" />'."\n"
|
||||
if $edges->{$_}->{$r};
|
||||
}
|
||||
$map .= '</node>' . "\n";
|
||||
}
|
||||
if ($header)
|
||||
{
|
||||
$map = <<EOF;
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<map version="0.9.0">
|
||||
<node background_color="#0000a0" id="root" text="$header">
|
||||
$map
|
||||
</node>
|
||||
</map>
|
||||
EOF
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
my $dottimeout = int(Bugzilla->params->{localdottimeout});
|
||||
my $usetwopi = scalar $cgi->param('usetwopi') && Bugzilla->params->{webtwopibase} ? 1 : 0;
|
||||
my $webdotbase = Bugzilla->params->{ $usetwopi ? 'webtwopibase' : 'webdotbase' };
|
||||
|
||||
if ($webdotbase =~ /^https?:/) {
|
||||
# Remote dot server. We don't hardcode 'urlbase' here in case
|
||||
# 'sslbase' is in use.
|
||||
$webdotbase =~ s/%([a-z]*)%/Bugzilla->params->{$1}/eg;
|
||||
my $url = $webdotbase . $filename;
|
||||
$vars->{image_url} = $url . ".gif";
|
||||
$vars->{map_url} = $url . ".map";
|
||||
} else {
|
||||
# Local dot installation
|
||||
|
||||
# First, generate the png image file from the .dot source
|
||||
|
||||
my ($pngfh, $pngfilename) = File::Temp::tempfile("XXXXXXXXXX",
|
||||
SUFFIX => '.png',
|
||||
DIR => $webdotdir);
|
||||
binmode $pngfh;
|
||||
my $quot = $^O =~ /MSWin/ ? '"' : "'";
|
||||
my $cmd = [$webdotbase, '-Tpng', $filename];
|
||||
if ($dottimeout && $dottimeout > 0)
|
||||
{
|
||||
# This creepy way is the only one that seems to be truly crossplatform
|
||||
$cmd = "perl -e $quot\$SIG{ALRM} = sub { exit 1 }; alarm $dottimeout; exec(" .
|
||||
join(",", map { 'q{'.$_.'}' } @$cmd) .
|
||||
");$quot";
|
||||
}
|
||||
else
|
||||
{
|
||||
$cmd = join " ", map { $quot.$_.$quot } @$cmd;
|
||||
}
|
||||
if (my $pid = open DOT, "$cmd|")
|
||||
{
|
||||
binmode DOT;
|
||||
print $pngfh $_ while <DOT>;
|
||||
close DOT;
|
||||
}
|
||||
unless (tell $pngfh)
|
||||
{
|
||||
$vars->{timeout} = 1;
|
||||
}
|
||||
close $pngfh;
|
||||
|
||||
# On Windows $pngfilename will contain \ instead of /
|
||||
$pngfilename =~ s|\\|/|g if $^O eq 'MSWin32';
|
||||
|
||||
# Under mod_perl, pngfilename will have an absolute path, and we
|
||||
# need to make that into a relative path.
|
||||
my $cgi_root = bz_locations()->{cgi_path};
|
||||
$pngfilename =~ s#^\Q$cgi_root\E/?##;
|
||||
|
||||
$vars->{'image_url'} = $pngfilename;
|
||||
|
||||
# Then, generate a imagemap datafile that contains the corner data
|
||||
# for drawn bug objects. Pass it on to CreateImagemap that
|
||||
# turns this monster into html.
|
||||
|
||||
my ($mapfh, $mapfilename) = File::Temp::tempfile("XXXXXXXXXX",
|
||||
SUFFIX => '.map',
|
||||
DIR => $webdotdir);
|
||||
binmode $mapfh;
|
||||
$cmd = [$webdotbase, '-Tismap', $filename];
|
||||
if ($dottimeout && $dottimeout > 0)
|
||||
{
|
||||
# This creepy way is the only one that seems to be truly crossplatform
|
||||
$cmd = "perl -e $quot\$SIG{ALRM} = sub { exit 1 }; alarm $dottimeout; exec(" .
|
||||
join(",", map { 'q{'.$_.'}' } @$cmd) .
|
||||
");$quot";
|
||||
}
|
||||
else
|
||||
{
|
||||
$cmd = join " ", map { $quot.$_.$quot } @$cmd;
|
||||
}
|
||||
open(DOT, "$cmd|");
|
||||
binmode DOT;
|
||||
print $mapfh $_ while <DOT>;
|
||||
close DOT;
|
||||
close $mapfh;
|
||||
$vars->{'image_map'} = CreateImagemap($mapfilename);
|
||||
}
|
||||
|
||||
# Cleanup any old .dot files created from previous runs.
|
||||
my $since = time() - 24 * 60 * 60;
|
||||
# Can't use glob, since even calling that fails taint checks for perl < 5.6
|
||||
opendir(DIR, $webdotdir);
|
||||
my @files = grep { /\.dot$|\.png$|\.map$/ && -f "$webdotdir/$_" } readdir(DIR);
|
||||
closedir DIR;
|
||||
foreach my $f (@files)
|
||||
{
|
||||
$f = "$webdotdir/$f";
|
||||
# Here we are deleting all old files. All entries are from the
|
||||
# $webdot directory. Since we're deleting the file (not following
|
||||
# symlinks), this can't escape to delete anything it shouldn't
|
||||
# (unless someone moves the location of $webdotdir, of course)
|
||||
trick_taint($f);
|
||||
if (file_mod_time($f) < $since) {
|
||||
unlink $f;
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure we only include valid integers (protects us from XSS attacks).
|
||||
my @bugs = grep(detaint_natural($_), split(/[\s,]+/, $cgi->param('id')));
|
||||
$vars->{'bug_id'} = join(', ', @bugs);
|
||||
$vars->{'multiple_bugs'} = ($cgi->param('id') =~ /[ ,]/);
|
||||
$vars->{'display'} = $display;
|
||||
$vars->{'rankdir'} = $rankdir;
|
||||
$vars->{'showsummary'} = $cgi->param('showsummary');
|
||||
$vars->{'usetwopi'} = $usetwopi;
|
||||
|
||||
# Generate and return the UI (HTML page) from the appropriate template.
|
||||
print $cgi->header();
|
||||
$template->process("bug/dependency-graph.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
|
Loading…
Reference in New Issue