diff options
-rwxr-xr-x | showdependencytree.cgi | 358 |
1 files changed, 318 insertions, 40 deletions
diff --git a/showdependencytree.cgi b/showdependencytree.cgi index bab36da61..14d504567 100755 --- a/showdependencytree.cgi +++ b/showdependencytree.cgi @@ -1,4 +1,4 @@ -#!/usr/bonsaitools/bin/perl -w +#!/usr/bonsaitools/bin/perl -wT # -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public @@ -19,10 +19,13 @@ # 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"; # Shut up misguided -w warnings about "used only once": @@ -40,34 +43,85 @@ $::usergroupset = $::usergroupset; # More warning suppression silliness. ###################################################################### # Make sure the bug ID is a positive integer representing an existing -# bug that the user is authorized to access. +# 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 ###################################################################### -my $id = $::FORM{'id'}; -my $linkedid = qq{<a href="show_bug.cgi?id=$id">$id</a>}; - -print "Content-type: text/html\n\n"; -PutHeader("Dependency tree", "Dependency tree", "Bug $linkedid"); +# +# Globals +# A hash to count visited bugs, and also to avoid processing repeated bugs my %seen; +# 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 +# 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, $me, $target) = (@_); - if (exists $seen{$i}) { - return; - } + my ($i, $target, $depth) = (@_); + my $bgcolor = "#d9d9d9"; + my $fgcolor = "#000000"; + my $me; + if (! defined $depth) { $depth = 1; } + if (exists $seen{$i}) { return; } $seen{$i} = 1; - SendSQL("select $target from dependencies where $me = $i order by $target"); + 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) { - print "<ul>\n"; + my $list_started = 0; foreach my $kid (@list) { my ($bugid, $stat, $milestone) = ("", "", ""); my ($userid, $short_desc) = ("", ""); @@ -79,36 +133,260 @@ sub DumpKids { ($bugid, $stat, $userid, $short_desc) = (FetchSQLData()); } - if (!defined $bugid) { - next; - } - my $opened = ($stat eq "NEW" || $stat eq "ASSIGNED" || - $stat eq "REOPENED"); - print "<li>"; - if (!$opened) { - print "<strike>"; - } - $short_desc = html_quote($short_desc); - SendSQL("select login_name from profiles where userid = $userid"); - my ($owner) = (FetchSQLData()); - if ( (Param('usetargetmilestone')) && ($milestone) ) { - print qq{<a href="show_bug.cgi?id=$kid">$kid [$milestone, $owner] - $short_desc</a>}; - } else { - print qq{<a href="show_bug.cgi?id=$kid">$kid [$owner] - $short_desc</a>}; - } - if (!$opened) { - print "</strike>"; - } - DumpKids($kid, $me, $target); + 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;"> + |; + } + $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>"; } + } # End hideable output + + # Store the maximum depth so far + $realdepth = $realdepth < $depth ? $depth : $realdepth; + DumpKids($kid, $target, $depth + 1); } - print "</ul>\n"; + if ($list_started) { $html .= "</ul>"; } } } -print "<h1>Bugs that bug $linkedid depends on</h1>"; -DumpKids($id, "blocked", "dependson"); -print "<h1>Bugs that depend on bug $linkedid</h1>"; -undef %seen; -DumpKids($id, "dependson", "blocked"); +# 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 = (); + + DumpKids($i, $target); + my $tmphtml = $html; + + # Output correct heading + $html = "<h3>Bugs that bug $linked_id ".($target eq "blocked" ? + "blocks" : "depends on"); + + # 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 %seen) < 2) { + $html .= " 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 --> + Depth: + + </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=" Unlimited "> + + </form> + </td> + <td bgcolor="$bgcolor"> + + Limit to: + + </td> + <td bgcolor="$bgcolor"> + <form method="get" action="$scriptname" + style="display: inline; margin: 0px;"> + + <!-- Limit entry form: the button can't 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"> + <input type="submit" value="Change" | + . ( $realdepth < 2 ? "disabled" : "" ) . qq|> + + </form> + </td> + <td bgcolor="$bgcolor"> + <form method="get" action="$scriptname" + style="display: inline; margin: 0px;"> + + <!-- Minus one (-1) 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=" -1 " | + . ( $realdepth < 2 || ( $maxdepth && $maxdepth < 2 ) ? + "disabled" : "" ) . qq|> + + </form> + </td> + <td bgcolor="$bgcolor"> + <form method="get" action="$scriptname" + style="display: inline; margin: 0px;"> + + <!-- plus one form +1 + 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=" +1 " | + . ( $realdepth < 2 || ! $maxdepth || $maxdepth >= $realdepth ? + "disabled" : "" ) . qq|> + + </form> + </td> + <td bgcolor="$bgcolor"> + <form method="get" action="$scriptname" + style="display: inline; margin: 0px;"> + + <!-- set to one form --> + <input type="submit" value="Set to 1" | + . ( $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> + </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(); |