showdependencygraph.cgi 8.73 KB
Newer Older
1
#!/usr/bin/perl -wT
2 3
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
4 5 6 7 8 9 10 11 12 13
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
14
# The Original Code is the Bugzilla Bug Tracking System.
15
#
16
# The Initial Developer of the Original Code is Netscape Communications
17 18 19 20
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
21
# Contributor(s): Terry Weissman <terry@mozilla.org>
22
#                 Gervase Markham <gerv@gerv.net>
23 24 25

use strict;

26 27
use lib qw(.);

28
use File::Temp;
29
use Bugzilla;
30
use Bugzilla::Config qw(:DEFAULT $webdotdir);
31
use Bugzilla::Util;
32
use Bugzilla::BugMail;
33
use Bugzilla::Bug;
34

35 36
require "CGI.pl";

37
Bugzilla->login();
38

39 40
my $cgi = Bugzilla->cgi;

41 42
# Connect to the shadow database if this installation is using one to improve
# performance.
43
Bugzilla->switch_to_shadow_db();
44

45
use vars qw($template $vars $userid);
46 47 48

my %seen;
my %edgesdone;
49 50 51 52 53 54 55 56 57 58 59 60
my %bugtitles; # html title attributes for imagemap areas


# 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
# an HTML map element. THIS SUB IS ONLY USED FOR LOCAL DOT INSTALLATIONS.
# The map datafile won't necessarily contain the bug summaries, so we'll
# pull possible HTML titles from the %bugtitles hash (filled elsewhere
# in the code)

# The dot mapdata lines have the following format (\nsummary is optional):
# rectangle (LEFTX,TOPY) (RIGHTX,BOTTOMY) URLBASE/show_bug.cgi?id=BUGNUM BUGNUM[\nSUMMARY]
61

62 63 64 65 66 67 68 69
sub CreateImagemap {
    my $mapfilename = shift;
    my $map = "<map name=\"imagemap\">\n";
    my $default;

    open MAP, "<$mapfilename";
    while(my $line = <MAP>) {
        if($line =~ /^default ([^ ]*)(.*)$/) {
70
            $default = qq{<area alt="" shape="default" href="$1">\n};
71
        }
72

73
        if ($line =~ /^rectangle \((.*),(.*)\) \((.*),(.*)\) (http[^ ]*)(.*)?$/) {
74 75 76 77 78 79 80 81 82 83 84
            my ($leftx, $rightx, $topy, $bottomy, $url) = ($1, $3, $2, $4, $5);

            # 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 ($bugid) = ($6 =~ /^\s*(\d+)/);
            my $bugtitle = value_quote($bugtitles{$bugid});
            $map .= qq{<area alt="bug $bugid" name="bug$bugid" shape="rect" } .
                    qq{title="$bugtitle" href="$url" } .
                    qq{coords="$leftx,$topy,$rightx,$bottomy">\n};
85 86 87 88 89 90 91 92
        }
    }
    close MAP;

    $map .= "$default</map>";
    return $map;
}

93
sub AddLink {
94
    my ($blocked, $dependson, $fh) = (@_);
95 96 97
    my $key = "$blocked,$dependson";
    if (!exists $edgesdone{$key}) {
        $edgesdone{$key} = 1;
98
        print $fh "$blocked -> $dependson\n";
99 100 101 102 103
        $seen{$blocked} = 1;
        $seen{$dependson} = 1;
    }
}

104
my $rankdir = $cgi->param('rankdir') || "LR";
105

106
if (!defined $cgi->param('id') && !defined $cgi->param('doall')) {
107
    ThrowCodeError("missing_bug_id");
108
}
109

110 111
my ($fh, $filename) = File::Temp::tempfile("XXXXXXXXXX",
                                           SUFFIX => '.dot',
112
                                           DIR => $webdotdir);
113
my $urlbase = Param('urlbase');
114

115 116
print $fh "digraph G {";
print $fh qq{
117
graph [URL="${urlbase}query.cgi", rankdir=$rankdir, size="64,64"]
118 119 120
node [URL="${urlbase}show_bug.cgi?id=\\N", style=filled, color=lightgrey]
};

121
my %baselist;
122

123
if ($cgi->param('doall')) {
124 125 126 127
    SendSQL("SELECT blocked, dependson FROM dependencies");

    while (MoreSQLData()) {
        my ($blocked, $dependson) = FetchSQLData();
128
        AddLink($blocked, $dependson, $fh);
129 130
    }
} else {
131
    foreach my $i (split('[\s,]+', $cgi->param('id'))) {
132 133 134 135 136
        $i = trim($i);
        ValidateBugID($i);
        $baselist{$i} = 1;
    }

137 138 139 140 141
    my @stack = keys(%baselist);
    foreach my $id (@stack) {
        SendSQL("SELECT blocked, dependson 
                 FROM   dependencies 
                 WHERE  blocked = $id or dependson = $id");
142
        while (MoreSQLData()) {
143 144 145 146 147 148 149 150 151
            my ($blocked, $dependson) = FetchSQLData();
            if ($blocked != $id && !exists $seen{$blocked}) {
                push @stack, $blocked;
            }

            if ($dependson != $id && !exists $seen{$dependson}) {
                push @stack, $dependson;
            }

152
            AddLink($blocked, $dependson, $fh);
153
        }
154 155
    }

156 157 158
    foreach my $k (keys(%baselist)) {
        $seen{$k} = 1;
    }
159 160 161 162 163
}

foreach my $k (keys(%seen)) {
    my $summary = "";
    my $stat;
164 165 166 167 168 169 170 171 172 173 174 175
    my $resolution;

    # Retrieve bug information from the database
 
    SendSQL("SELECT bug_status, resolution, short_desc FROM bugs " .
            "WHERE bugs.bug_id = $k");
    ($stat, $resolution, $summary) = FetchSQLData();
    $stat ||= 'NEW';
    $resolution ||= '';
    $summary ||= '';

    # Resolution and summary are shown only if user can see the bug
176
    if (!Bugzilla->user->can_see_bug($k)) {
177
        $resolution = $summary = '';
178
    }
179 180


181
    my @params;
182

183
    if ($summary ne "" && $cgi->param('showsummary')) {
184 185
        $summary =~ s/([\\\"])/\\$1/g;
        push(@params, qq{label="$k\\n$summary"});
186
    }
187 188 189

    if (exists $baselist{$k}) {
        push(@params, "shape=box");
190 191
    }

192
    if (IsOpenedState($stat)) {
193 194
        push(@params, "color=green");
    }
195

196
    if (@params) {
197
        print $fh "$k [" . join(',', @params) . "]\n";
198
    } else {
199
        print $fh "$k\n";
200
    }
201 202 203 204 205 206 207 208

    # 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");

    # Show the bug summary in tooltips only if not shown on 
    # the graph and it is non-empty (the user can see the bug)
209
    if (!$cgi->param('showsummary') && $summary ne "") {
210 211
        $bugtitles{$k} .= " - $summary";
    }
212 213 214
}


215 216
print $fh "}\n";
close $fh;
217 218 219 220 221 222 223 224 225 226

chmod 0777, $filename;

my $webdotbase = Param('webdotbase');

if ($webdotbase =~ /^https?:/) {
     # Remote dot server
     my $url = PerformSubsts($webdotbase) . $filename;
     $vars->{'image_url'} = $url . ".gif";
     $vars->{'map_url'} = $url . ".map";
227
} else {
228
    # Local dot installation
229 230 231

    # First, generate the png image file from the .dot source

232 233
    my ($pngfh, $pngfilename) = File::Temp::tempfile("XXXXXXXXXX",
                                                     SUFFIX => '.png',
234
                                                     DIR => $webdotdir);
235 236 237
    binmode $pngfh;
    open(DOT, "$webdotbase -Tpng $filename|");
    binmode DOT;
238 239 240
    print $pngfh $_ while <DOT>;
    close DOT;
    close $pngfh;
241 242 243 244
    
    # On Windows $pngfilename will contain \ instead of /
    $pngfilename =~ s|\\|/|g if $^O eq 'MSWin32';
    
245
    $vars->{'image_url'} = $pngfilename;
246

247 248 249 250
    # 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.

251 252
    my ($mapfh, $mapfilename) = File::Temp::tempfile("XXXXXXXXXX",
                                                     SUFFIX => '.map',
253
                                                     DIR => $webdotdir);
254 255 256
    binmode $mapfh;
    open(DOT, "$webdotbase -Tismap $filename|");
    binmode DOT;
257 258 259
    print $mapfh $_ while <DOT>;
    close DOT;
    close $mapfh;
260
    $vars->{'image_map'} = CreateImagemap($mapfilename);
261 262 263 264
}

# Cleanup any old .dot files created from previous runs.
my $since = time() - 24 * 60 * 60;
265
# Can't use glob, since even calling that fails taint checks for perl < 5.6
266 267
opendir(DIR, $webdotdir);
my @files = grep { /\.dot$|\.png$|\.map$/ && -f "$webdotdir/$_" } readdir(DIR);
268 269
closedir DIR;
foreach my $f (@files)
270
{
271
    $f = "$webdotdir/$f";
272
    # Here we are deleting all old files. All entries are from the
273
    # $webdot directory. Since we're deleting the file (not following
274
    # symlinks), this can't escape to delete anything it shouldn't
275
    # (unless someone moves the location of $webdotdir, of course)
276
    trick_taint($f);
277
    if (file_mod_time($f) < $since) {
278 279 280 281
        unlink $f;
    }
}

282 283 284 285 286
$vars->{'bug_id'} = $cgi->param('id');
$vars->{'multiple_bugs'} = ($cgi->param('id') =~ /[ ,]/);
$vars->{'doall'} = $cgi->param('doall');
$vars->{'rankdir'} = $rankdir;
$vars->{'showsummary'} = $cgi->param('showsummary');
287

288
# Generate and return the UI (HTML page) from the appropriate template.
289
print $cgi->header();
290 291
$template->process("bug/dependency-graph.html.tmpl", $vars)
  || ThrowTemplateError($template->error());