gecko-dev/webtools/bugzilla/showdependencytree.cgi

414 lines
13 KiB
Perl
Executable File

#!/usr/bonsaitools/bin/perl -wT
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# 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.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Andreas Franke <afranke@mathweb.org>
# Christian Reis <kiko@async.com.br>
use diagnostics;
use strict;
use lib qw(.);
require "CGI.pl";
use vars %::FORM;
ConnectToDatabase();
quietly_check_login();
# More warning suppression silliness.
$::userid = $::userid;
$::usergroupset = $::usergroupset;
######################################################################
# Begin Data/Security Validation
######################################################################
# Make sure the bug ID is a positive integer representing an existing
# bug that the user is authorized to access
ValidateBugID($::FORM{'id'});
my $id = $::FORM{'id'};
my $hide_resolved = $::FORM{'hide_resolved'} || 0;
my $maxdepth = $::FORM{'maxdepth'} || 0;
if ($maxdepth !~ /^\d+$/) { $maxdepth = 0 };
if ($hide_resolved !~ /^\d+$/ || $hide_resolved != 1) { $hide_resolved = 0 };
######################################################################
# End Data/Security Validation
######################################################################
#
# Globals
# A hash to count visited bugs, and also to avoid processing repeated bugs
my %seen;
# A hash to keep track of the bugs we print for the 'as buglist' links.
my %printed;
# HTML output generated in the parse of the dependency tree. This is a
# global only to avoid excessive complication in the recursion invocation
my $html;
# Saves the largest of the two actual depths of the trees
my $realdepth = 0;
# The scriptname for use as FORM ACTION.
my $scriptname = $::ENV{'SCRIPT_NAME'}; # showdependencytree.cgi
#
# Functions
# DumpKids recurses through the bug hierarchy starting at bug i, and
# appends the bug information found to the html global variable. The
# parameters are not straightforward, so look at the examples.
#
# DumpKids(i, target [, depth])
#
# Params
# i: The bug id to analyze
# target: The type we are looking for; either "blocked" or "dependson"
# Optional
# depth: The current dependency depth we are analyzing, used during
# recursion
# Globals Modified
# html: Bug descriptions are appended here
# realdepth: We set the maximum depth of recursion reached
# seen: We store the bugs analyzed so far
# printed: We store those bugs we actually print, for the "buglist" link
# Globals Referenced
# maxdepth
# hide_resolved
#
# Examples:
# DumpKids(163, "blocked");
# will look for bugs that depend on bug 163
# DumpKids(163, "dependson");
# will look for bugs on which bug 163 depends
sub DumpKids {
my ($i, $target, $depth) = (@_);
my $bgcolor = "#d9d9d9";
my $fgcolor = "#000000";
my $me;
if (! defined $depth) { $depth = 1; }
if ($target eq "blocked") {
$me = "dependson";
} else {
$me = "blocked";
}
SendSQL("select $target from dependencies where $me=$i order by $target");
my @list;
while (MoreSQLData()) {
push(@list, FetchOneColumn());
}
if (@list) {
my $list_started = 0;
foreach my $kid (@list) {
my ($bugid, $stat, $milestone) = ("", "", "");
my ($userid, $short_desc) = ("", "");
if (Param('usetargetmilestone')) {
SendSQL(SelectVisible("select bugs.bug_id, bug_status, target_milestone, assigned_to, short_desc from bugs where bugs.bug_id = $kid", $::userid, $::usergroupset));
($bugid, $stat, $milestone, $userid, $short_desc) = (FetchSQLData());
} else {
SendSQL(SelectVisible("select bugs.bug_id, bug_status, assigned_to, short_desc from bugs where bugs.bug_id = $kid", $::userid, $::usergroupset));
($bugid, $stat, $userid, $short_desc) = (FetchSQLData());
}
if (! defined $bugid) { next; }
my $opened = IsOpenedState($stat);
if ($hide_resolved && ! $opened) { next; }
# If we specify a maximum depth, we hide the output when
# that depth has occured, but continue recursing so we know
# the real maximum depth of the tree.
if (! $maxdepth || $depth <= $maxdepth) {
if (! $list_started) { $html .= "<ul>"; $list_started = 1 }
$html .= "<li>";
if (! $opened) {
$html .= qq|<strike><span style="color: $fgcolor; background-color: $bgcolor;">
|;
}
if (exists $seen{$kid}) {
$short_desc = "&lt;<em>This bug appears elsewhere in this tree</em>&gt;";
}
else {
$short_desc = html_quote($short_desc);
}
SendSQL("select login_name from profiles where userid = $userid");
my ($owner) = (FetchSQLData());
if ((Param('usetargetmilestone')) && ($milestone)) {
$html .= qq|
<a href="show_bug.cgi?id=$kid">$kid [$milestone, $owner]
- $short_desc.</a>
|;
} else {
$html .= qq|
<a href="show_bug.cgi?id=$kid">$kid [$owner] -
$short_desc.</a>\n|;
}
if (! $opened) { $html .= "</span></strike>"; }
$printed{$kid} = 1;
} # End hideable output
# Store the maximum depth so far
$realdepth = $realdepth < $depth ? $depth : $realdepth;
if (!(exists $seen{$kid})) {
$seen{$kid} = 1;
DumpKids($kid, $target, $depth + 1);
}
}
if ($list_started) { $html .= "</ul>"; }
}
}
# makeTreeHTML calls DumpKids and generates the HTML output for a
# dependency/blocker section.
#
# makeTreeHTML(i, linked_id, target);
#
# Params
# i: Bug id
# linked_id: Linkified bug_id used to linkify the bug
# target: The type we are looking for; either "blocked" or "dependson"
# Globals modified
# html [Also modified in our call to DumpKids]
# Globals referenced
# seen [Also modified by DumpKids]
# maxdepth
# realdepth
#
# Example:
# $depend_html = makeTreeHTML(83058, <A HREF="...">83058</A>, "dependson");
# Will generate HTML for bugs that depend on bug 83058
sub makeTreeHTML {
my ($i, $linked_id, $target) = @_;
# Clean up globals for this run
$html = "";
%seen = ();
%printed = ();
DumpKids($i, $target);
my $tmphtml = $html;
# Output correct heading
$html = "<h3>Bugs that bug $linked_id ".($target eq "blocked" ?
"blocks" : "depends on");
if ((scalar keys %printed) > 0) {
$html .= ' (<a href="buglist.cgi?bug_id=' . join(',', keys %printed) .
'">view as bug list</a>)';
}
# Provide feedback for omitted bugs
if ($maxdepth || $hide_resolved) {
$html .= " <small><b>(Only ";
if ($hide_resolved) { $html .= "open "; }
$html .= "bugs ";
if ($maxdepth) { $html .= "whose depth is less than $maxdepth "; }
$html .= "will be shown)</b></small>";
}
$html .= "</h3>";
$html .= $tmphtml;
# If no bugs were found, say so
if ((scalar keys %printed) == 0) {
$html .= "&nbsp;&nbsp;&nbsp;&nbsp;None<p>\n";
}
return $html;
}
# Draw the actual form controls that make up the hide/show resolved and
# depth control toolbar.
#
# drawDepForm()
#
# Params
# none
# Globals modified
# none
# Globals referenced
# hide_resolved
# maxdepth
# realdepth
sub drawDepForm {
my $bgcolor = "#d0d0d0";
my ($hide_msg, $hide_input);
# Set the text and action for the hide resolved button.
if ($hide_resolved) {
$hide_input = '<input type="hidden" name="hide_resolved" value="0">';
$hide_msg = "Show Resolved";
} else {
$hide_input = '<input type="hidden" name="hide_resolved" value="1">';
$hide_msg = "Hide Resolved";
}
print qq|
<table cellpadding="3" border="0" cellspacing="0">
<tr>
<!-- Hide/show resolved button
Swaps text depending on the state of hide_resolved -->
<td bgcolor="$bgcolor" align="center">
<form method="get" action="$scriptname"
style="display: inline; margin: 0px;">
<input name="id" type="hidden" value="$id">
| . ( $maxdepth ?
qq|<input name="maxdepth" type="hidden" value="$maxdepth">|
: "" ) . qq|
$hide_input
<input type="submit" value="$hide_msg">
</form>
</td>
<td bgcolor="$bgcolor">
<!-- depth section -->
Max Depth:
</td>
<td bgcolor="$bgcolor">
</td>
<td bgcolor="$bgcolor">
<form method="get" action="$scriptname"
style="display: inline; margin: 0px;">
<!-- set to one form -->
<input type="submit" value="&nbsp;1&nbsp;" |
. ( $realdepth < 2 || $maxdepth == 1 ? "disabled" : "" ) . qq|>
<input name="id" type="hidden" value="$id">
<input name="maxdepth" type="hidden" value="1">
<input name="hide_resolved" type="hidden" value="$hide_resolved">
</form>
</td>
<td bgcolor="$bgcolor">
<form method="get" action="$scriptname"
style="display: inline; margin: 0px;">
<!-- Minus one form
Allow subtracting only when realdepth and maxdepth > 1 -->
<input name="id" type="hidden" value="$id">
<input name="maxdepth" type="hidden" value="|
. ( $maxdepth == 1 ? 1 : ( $maxdepth ?
$maxdepth-1 : $realdepth-1 ) ) . qq|">
<input name="hide_resolved" type="hidden" value="$hide_resolved">
<input type="submit" value="&nbsp;&lt;&nbsp;" |
. ( $realdepth < 2 || ( $maxdepth && $maxdepth < 2 ) ?
"disabled" : "" ) . qq|>
</form>
</td>
<td bgcolor="$bgcolor">
<form method="get" action="$scriptname"
style="display: inline; margin: 0px;">
<!-- Limit entry form: the button can not do anything when total depth
is less than two, so disable it -->
<input name="maxdepth" size="4" maxlength="4" value="|
. ( $maxdepth > 0 ? $maxdepth : "" ) . qq|">
<input name="id" type="hidden" value="$id">
<input name="hide_resolved" type="hidden"
value="$hide_resolved">
<noscript>
<input type="submit" value="Change" |
. ( $realdepth < 2 ? "disabled" : "" ) . qq|>
</noscript>
</form>
</td>
<td bgcolor="$bgcolor">
<form method="get" action="$scriptname"
style="display: inline; margin: 0px;">
<!-- plus one form
Disable button if total depth < 2, or if depth set to unlimited -->
<input name="id" type="hidden" value="$id">
| . ( $maxdepth ? qq|
<input name="maxdepth" type="hidden" value="|.($maxdepth+1).qq|">| : "" )
. qq|
<input name="hide_resolved" type="hidden" value="$hide_resolved">
<input type="submit" value="&nbsp;&gt;&nbsp;" |
. ( $realdepth < 2 || ! $maxdepth || $maxdepth >= $realdepth ?
"disabled" : "" ) . qq|>
</form>
</td>
<td bgcolor="$bgcolor">
<form method="get" action="$scriptname"
style="display: inline; margin: 0px;">
<!-- Unlimited button -->
<input name="id" type="hidden" value="$id">
<input name="hide_resolved" type="hidden" value="$hide_resolved">
<input type="submit" value="&nbsp;Unlimited&nbsp;">
</form>
</td>
</tr></table>
|;
}
######################################################################
# Main Section
######################################################################
my $linked_id = qq|<a href="show_bug.cgi?id=$id">$id</a>|;
# Start the tree walk and save results. The tree walk generates HTML but
# needs to be called before the page output starts so we have the
# realdepth, which is necessary for generating the control toolbar.
# Get bugs we depend on
my $depend_html = makeTreeHTML($id, $linked_id, "dependson");
my $tmpdepth = $realdepth;
$realdepth = 0;
# Get bugs we block
my $block_html = makeTreeHTML($id, $linked_id, "blocked");
# Select maximum depth found for use in the toolbar
$realdepth = $realdepth < $tmpdepth ? $tmpdepth : $realdepth;
#
# Actual page output happens here
print "Content-type: text/html\n\n";
PutHeader("Dependency tree for Bug $id", "Dependency tree for Bug $linked_id");
drawDepForm();
print $depend_html;
print $block_html;
drawDepForm();
PutFooter();