#!/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 # Andreas Franke # Christian Reis use diagnostics; use strict; use lib qw(.); require "CGI.pl"; # Shut up misguided -w warnings about "used only once": use vars %::FORM; ConnectToDatabase(); quietly_check_login(); $::usergroupset = $::usergroupset; # More warning suppression silliness. ###################################################################### # 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; # 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, $target, $depth) = (@_); my $bgcolor = "#d9d9d9"; my $fgcolor = "#000000"; my $me; if (! defined $depth) { $depth = 1; } if (exists $seen{$i}) { return; } $seen{$i} = 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("select bug_id, bug_status, target_milestone, assigned_to, short_desc from bugs where bug_id = $kid and bugs.groupset & $::usergroupset = bugs.groupset"); ($bugid, $stat, $milestone, $userid, $short_desc) = (FetchSQLData()); } else { SendSQL("select bug_id, bug_status, assigned_to, short_desc from bugs where bug_id = $kid and bugs.groupset & $::usergroupset = bugs.groupset"); ($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 .= "
    "; $list_started = 1 } $html .= "
  • "; if (! $opened) { $html .= qq| |; } $short_desc = html_quote($short_desc); SendSQL("select login_name from profiles where userid = $userid"); my ($owner) = (FetchSQLData()); if ((Param('usetargetmilestone')) && ($milestone)) { $html .= qq| $kid [$milestone, $owner] - $short_desc. |; } else { $html .= qq| $kid [$owner] - $short_desc.\n|; } if (! $opened) { $html .= ""; } } # End hideable output # Store the maximum depth so far $realdepth = $realdepth < $depth ? $depth : $realdepth; DumpKids($kid, $target, $depth + 1); } if ($list_started) { $html .= "
"; } } } # 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, 83058, "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 = "

Bugs that bug $linked_id ".($target eq "blocked" ? "blocks" : "depends on"); # Provide feedback for omitted bugs if ($maxdepth || $hide_resolved) { $html .= " (Only "; if ($hide_resolved) { $html .= "open "; } $html .= "bugs "; if ($maxdepth) { $html .= "whose depth is less than $maxdepth "; } $html .= "will be shown)"; } $html .= "

"; $html .= $tmphtml; # If no bugs were found, say so if ((scalar keys %seen) < 2) { $html .= "    None

\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 = ''; $hide_msg = "Show Resolved"; } else { $hide_input = ''; $hide_msg = "Hide Resolved"; } print qq|
| . ( $maxdepth ? qq|| : "" ) . qq| $hide_input
Depth:
Limit to:
| . ( $maxdepth ? qq| | : "" ) . qq| = $realdepth ? "disabled" : "" ) . qq|>
|; } ###################################################################### # Main Section ###################################################################### my $linked_id = qq|$id|; # 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();