diff options
-rwxr-xr-x | duplicates.cgi | 354 | ||||
-rw-r--r-- | template/default/report/duplicates.html.tmpl | 275 |
2 files changed, 400 insertions, 229 deletions
diff --git a/duplicates.cgi b/duplicates.cgi index 78f29829f..a85eb6bb7 100755 --- a/duplicates.cgi +++ b/duplicates.cgi @@ -25,7 +25,7 @@ use diagnostics; use strict; -use CGI "param"; + use AnyDBM_File; use lib qw(.); @@ -33,260 +33,156 @@ use lib qw(.); require "globals.pl"; require "CGI.pl"; +# Use global templatisation variables. +use vars qw($template $vars); + ConnectToDatabase(1); GetVersionTable(); quietly_check_login(); -# Silly used-once warnings -$::userid = $::userid; -$::usergroupset = $::usergroupset; +use vars qw (%FORM $userid $usergroupset @legal_product); my %dbmcount; my %count; -my $dobefore = 0; -my $before = ""; my %before; # Get params from URL - -my $changedsince = 7; # default one week -my $maxrows = 100; # arbitrary limit on max number of rows -my $sortby = "dup_count"; # default to sorting by dup count - -if (defined(param("sortby"))) -{ - $sortby = param("sortby"); -} - -# Check for changedsince param, and see if it's a positive integer -if (defined(param("changedsince")) && param("changedsince") =~ /^\d{1,4}$/) -{ - $changedsince = param("changedsince"); +sub formvalue { + my ($name, $default) = (@_); + return $FORM{$name} || $default || ""; } -# check for max rows param, and see if it's a positive integer -if (defined(param("maxrows")) && param("maxrows") =~ /^\d{1,4}$/) -{ - $maxrows = param("maxrows"); -} +my $sortby = formvalue("sortby"); +my $changedsince = formvalue("changedsince", 7); +my $maxrows = formvalue("maxrows", 100); +my $openonly = formvalue("openonly"); +my $reverse = formvalue("reverse"); +my $product = formvalue("product"); +my $sortvisible = formvalue("sortvisible"); +my @buglist = (split(/[:,]/, formvalue("bug_id"))); -# Start the page -print "Content-type: text/html\n"; -print "\n"; -PutHeader("Most Frequently Reported Bugs"); +# Small backwards-compatibility hack, dated 2002-04-10. +$sortby = "count" if $sortby eq "dup_count"; # Open today's record of dupes -my $today = &days_ago(0); +my $today = days_ago(0); +my $yesterday = days_ago(1); -if (<data/duplicates/dupes$today*>) -{ - dbmopen(%dbmcount, "data/duplicates/dupes$today", 0644) || - &die_politely("Can't open today's dupes file: $!"); +if (<data/duplicates/dupes$today*>) { + dbmopen(%dbmcount, "data/duplicates/dupes$today", 0644) + || DisplayError("Can't open today ($today)'s dupes file: $!") + && exit; +} +elsif (<data/duplicates/dupes$yesterday*>) { + dbmopen(%dbmcount, "data/duplicates/dupes$yesterday", 0644) + || DisplayError("Can't open yesterday ($yesterday)'s dupes file: $!") + && exit; } -else -{ - # Try yesterday's, then (in case today's hasn't been created yet) - $today = &days_ago(1); - if (<data/duplicates/dupes$today*>) - { - dbmopen(%dbmcount, "data/duplicates/dupes$today", 0644) || - &die_politely("Can't open yesterday's dupes file: $!"); - } - else - { - &die_politely("There are no duplicate statistics for today ($today) or yesterday."); - } +else { + DisplayError("There are no duplicate statistics for today ($today) or + yesterday."); + exit; } # Copy hash (so we don't mess up the on-disk file when we remove entries) %count = %dbmcount; -my $key; -my $value; + +# Remove all those dupes under the threshold parameter. +# We do this, before the sorting, for performance reasons. my $threshold = Param("mostfreqthreshold"); -# Remove all those dupes under the threshold (for performance reasons) -while (($key, $value) = each %count) -{ - if ($value < $threshold) - { - delete $count{$key}; - } +while (my ($key, $value) = each %count) { + delete $count{$key} if ($value < $threshold); } # Try and open the database from "changedsince" days ago -$before = &days_ago($changedsince); - -if (<data/duplicates/dupes$before*>) -{ - dbmopen(%before, "data/duplicates/dupes$before", 0644) && ($dobefore = 1); -} - -print Param("mostfreqhtml"); - +my $dobefore = 0; my %delta; - -if ($dobefore) -{ - # Calculate the deltas if we are doing a "before" - foreach (keys(%count)) - { - $delta{$_} = $count{$_} - $before{$_}; - } -} - -# Sort, if required -my @sortedcount; - -if ($sortby eq "delta") -{ - @sortedcount = sort by_delta keys(%count); -} -elsif ($sortby eq "bug_no") -{ - @sortedcount = reverse sort by_bug_no keys(%count); -} -elsif ($sortby eq "dup_count") -{ - @sortedcount = sort by_dup_count keys(%count); -} - -my $i = 0; - -# Produce a string of bug numbers for a Bugzilla buglist. -my $commabugs = ""; -foreach (@sortedcount) -{ - last if ($i == $maxrows); - - $commabugs .= ($_ . ","); - $i++; -} - -# Avoid having a comma at the end - Bad Things happen. -chop $commabugs; - -print qq| - -<form method="POST" action="buglist.cgi"> -<input type="hidden" name="bug_id" value="$commabugs"> -<input type="hidden" name="order" value="Reuse same sort as last time"> -Give this to me as a <input type="submit" value="Bug List">. (Note: the order may not be the same.) -</form> - -<table BORDER> -<tr BGCOLOR="#CCCCCC"> - -<td><center><b> -<a href="duplicates.cgi?sortby=bug_no&maxrows=$maxrows&changedsince=$changedsince">Bug #</a> -</b></center></td> -<td><center><b> -<a href="duplicates.cgi?sortby=dup_count&maxrows=$maxrows&changedsince=$changedsince">Dupe<br>Count</a> -</b></center></td>\n|; - -if ($dobefore) -{ - print "<td><center><b> - <a href=\"duplicates.cgi?sortby=delta&maxrows=$maxrows&changedsince=$changedsince\">Change in - last<br>$changedsince day(s)</a></b></center></td>"; -} - -print " -<td><center><b>Component</b></center></td> -<td><center><b>Severity</b></center></td> -<td><center><b>Op Sys</b></center></td> -<td><center><b>Target<br>Milestone</b></center></td> -<td><center><b>Summary</b></center></td> -</tr>\n\n"; - -$i = 0; - -foreach (@sortedcount) -{ - my $id = $_; - SendSQL(SelectVisible("SELECT component, bug_severity, op_sys, target_milestone, short_desc, bug_status, resolution" . - " FROM bugs WHERE bugs.bug_id = $id", $::userid, $::usergroupset)); - next unless MoreSQLData(); - my ($component, $severity, $op_sys, $milestone, $summary, $bug_status, $resolution) = FetchSQLData(); - $summary = html_quote($summary); - - # Show all bugs except those CLOSED _OR_ VERIFIED but not INVALID or WONTFIX. - # We want to see VERIFIED INVALID and WONTFIX because common "bugs" which aren't - # bugs end up in this state. - unless ( ($bug_status eq "CLOSED") || ( ($bug_status eq "VERIFIED") && - ! ( ($resolution eq "INVALID") || ($resolution eq "WONTFIX") ) ) ) { - print "<tr>"; - print '<td><center>'; - if ( ($bug_status eq "RESOLVED") || ($bug_status eq "VERIFIED") ) { - print "<strike>"; - } - print "<A HREF=\"show_bug.cgi?id=" . $id . "\">"; - print $id . "</A>"; - if ( ($bug_status eq "RESOLVED") || ($bug_status eq "VERIFIED") ) { - print "</strike>"; - } - print "</center></td>"; - print "<td><center>$count{$id}</center></td>"; - if ($dobefore) - { - print "<td><center>$delta{$id}</center></td>"; - } - print "<td>$component</td>\n "; - print "<td><center>$severity</center></td>"; - print "<td><center>$op_sys</center></td>"; - print "<td><center>$milestone</center></td>"; - print "<td>$summary</td>"; - print "</tr>\n"; - - $i++; - } - - if ($i == $maxrows) - { - last; - } -} - -print "</table><br><br>"; -PutFooter(); - - -sub by_bug_no -{ - return ($a <=> $b); -} - -sub by_dup_count -{ - return -($count{$a} <=> $count{$b}); -} - -sub by_delta -{ - return -($delta{$a} <=> $delta{$b}); -} - -sub days_ago -{ - my ($dom, $mon, $year) = (localtime(time - ($_[0]*24*60*60)))[3, 4, 5]; - return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom; -} - -sub die_politely { - my $msg = shift; - - print <<FIN; -<p> -<table border=1 cellpadding=10> -<tr> -<td align=center> -<font color=blue>$msg</font> -</td> -</tr> -</table> -<p> -FIN - - PutFooter(); - exit; +my $whenever = days_ago($changedsince); + +if (<data/duplicates/dupes$whenever*>) { + dbmopen(%before, "data/duplicates/dupes$whenever", 0644) + || DisplayError("Can't open $changedsince days ago ($whenever)'s " . + "dupes file: $!"); + + # Calculate the deltas + ($delta{$_} = $count{$_} - $before{$_}) foreach (keys(%count)); + + $dobefore = 1; +} + +# Don't add CLOSED, and don't add VERIFIED unless they are INVALID or +# WONTFIX. We want to see VERIFIED INVALID and WONTFIX because common +# "bugs" which aren't bugs end up in this state. +my $generic_query = " + SELECT component, bug_severity, op_sys, target_milestone, + short_desc, bug_status, resolution + FROM bugs + WHERE (bug_status != 'CLOSED') + AND ((bug_status = 'VERIFIED' AND resolution IN ('INVALID', 'WONTFIX')) + OR (bug_status != 'VERIFIED')) + AND "; + +# Limit to a single product if requested +$generic_query .= (" product = " . SqlQuote($product) . " AND ") if $product; + +my @bugs; +my @bug_ids; +my $loop = 0; + +foreach my $id (keys(%count)) { + # Maximum row count is dealt with in the template. + # If there's a buglist, restrict the bugs to that list. + next if $sortvisible && $buglist[0] && (lsearch(\@buglist, $id) == -1); + + SendSQL(SelectVisible("$generic_query bugs.bug_id = $id", + $userid, + $usergroupset)); + + next unless MoreSQLData(); + my ($component, $bug_severity, $op_sys, $target_milestone, + $short_desc, $bug_status, $resolution) = FetchSQLData(); + + # Limit to open bugs only if requested + next if $openonly && ($resolution ne ""); + + push (@bugs, { id => $id, + count => $count{$id}, + delta => $delta{$id}, + component => $component, + bug_severity => $bug_severity, + op_sys => $op_sys, + target_milestone => $target_milestone, + short_desc => $short_desc, + bug_status => $bug_status, + resolution => $resolution }); + push (@bug_ids, $id); + $loop++; +} + +$vars->{'bugs'} = \@bugs; +$vars->{'bug_ids'} = \@bug_ids; + +$vars->{'dobefore'} = $dobefore; +$vars->{'sortby'} = $sortby; +$vars->{'sortvisible'} = $sortvisible; +$vars->{'changedsince'} = $changedsince; +$vars->{'maxrows'} = $maxrows; +$vars->{'openonly'} = $openonly; +$vars->{'reverse'} = $reverse; +$vars->{'product'} = $product; +$vars->{'products'} = \@::legal_product; + +print "Content-type: text/html\n\n"; + +# Generate and return the UI (HTML page) from the appropriate template. +$template->process("report/duplicates.html.tmpl", $vars) + || DisplayError("Template process failed: " . $template->error()) + && exit; + + +sub days_ago { + my ($dom, $mon, $year) = (localtime(time - ($_[0]*24*60*60)))[3, 4, 5]; + return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom; } diff --git a/template/default/report/duplicates.html.tmpl b/template/default/report/duplicates.html.tmpl new file mode 100644 index 000000000..1f606664a --- /dev/null +++ b/template/default/report/duplicates.html.tmpl @@ -0,0 +1,275 @@ +[%# 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): Gervase Markham <gerv@gerv.net> + #%] + +[%# INTERFACE: + # bugs: list of hashes. May be empty. Each hash has nine members: + # id: integer. The bug number + # count: integer. The number of dupes + # delta: integer. The change in count in the last $changedsince days + # component: string. The bug's component + # bug_severity: string. The bug's severity. + # op_sys: string. The bug's reported OS. + # target_milestone: string. The bug's TM. + # short_desc: string. The bug's summary. + # bug_status: string. The bug's status. + # + # bug_ids: list of integers. May be empty. The IDs of the bugs in $bugs. + # products: list of strings. The products this user can see. + # + # sortby: string. the column on which we are sorting the buglist. + # reverse: boolean. True if we are reversing the current sort. + # maxrows: integer. Max number of rows to display. + # changedsince: integer. The number of days ago for the changedsince column. + # openonly: boolean. True if we are only showing open bugs. + # product: string. Restrict to this product only. + #%] + +[% IF product %] + [% title = "Most Frequently Reported Bugs for $product" %] +[% ELSE %] + [% title = "Most Frequently Reported Bugs" %] +[% END%] + +[% INCLUDE global/header %] + +<p> + <a href="#explanation">What is this data?</a> + <br> + <a href="#params">Change parameters</a> +</p> + +[%# *** Column Headers *** %] + +<table border> + <tr BGCOLOR="#CCCCCC"> + [% FOREACH column = [ { name => "id", description => "Bug #" }, + { name => "count", description => "Dupe<br>Count" }, + { name => "delta", + description => "Change in last<br>$changedsince day(s)" }, + { name => "component", description => "Component" }, + { name => "bug_severity", description => "Severity" }, + { name => "op_sys", description => "Op Sys" }, + { name => "target_milestone", + description => "Target<br>Milestone" }, + { name => "short_desc", description => "Summary" } ] + %] + + [%# Small hack to keep delta column out if we don't need it %] + [% NEXT IF column.name == "delta" AND NOT dobefore %] + + <td> + <center> + <b> + [% bug_ids_string = bug_ids.join(',') %] + <a href="duplicates.cgi?sortby=[% column.name %] + [% "&reverse=1" IF NOT reverse AND sortby == column.name %] + [% "&maxrows=$maxrows" IF maxrows %] + [% "&changedsince=$changedsince" IF changedsince %] + [% "&openonly=1" IF openonly %] + [% "&product=$product" IF product %] + [% "&bug_id=$bug_ids_string&sortvisible=1" IF sortvisible %]"> + [% column.description %]</a> + </b> + </center> + </td> + [% END %] + </tr> + +[% IF NOT sortby %] + [% sortby = "count"; reverse = "1" %] +[% END %] + +[% IF sortby == "id" OR sortby == "count" OR sortby == "delta" %] + [%# Numeric sort %] + [% sortedbugs = bugs.nsort(sortby) %] +[% ELSE %] + [% sortedbugs = bugs.sort(sortby) %] +[% END %] + +[% IF reverse %] + [% bugs = sortedbugs.reverse %] +[% ELSE %] + [% bugs = sortedbugs %] +[% END %] + +[%# *** Buglist *** %] + +[%# We need to keep track of the bug IDs we are actually displaying, because + # if the user decides to sort the visible list, we need to know what that + # list actually is. %] +[% vis_bug_ids = [] %] + +[% FOREACH bug = bugs %] + [% LAST IF loop.index() >= maxrows %] + [% vis_bug_ids.push(bug.id) %] + + <tr> + <td> + <center> + [% "<strike>" IF bug.resolution != "" %] + <A HREF="show_bug.cgi?id=[% bug.id %]">[% bug.id %]</a> + [% "</strike>" IF bug.resolution != "" %] + </center> + </td> + + <td> + <center> + [% bug.count %] + </center> + </td> + + [% IF dobefore %] + <td><center>[% bug.delta %]</center></td> + [% END %] + + <td>[% bug.component %]</td> + <td><center>[% bug.bug_severity %]</center></td> + <td><center>[% bug.op_sys %]</center></td> + <td><center>[% bug.target_milestone %]</center></td> + <td>[% bug.short_desc FILTER html %]</td> + </tr> +[% END %] + +</table> + +<br> +<br> + +[%# *** Parameters *** %] + +[% bug_ids_string = vis_bug_ids.join(',') %] + +<h3><a name="params">Change Parameters</a></h3> + +<form method="get" action="duplicates.cgi"> + <input type="hidden" name="sortby" value="[% sortby %]" /> + <input type="hidden" name="reverse" value="[% reverse %]" /> + <input type="hidden" name="bug_id" value="[% bug_ids_string %]"> + <table> + <tr> + <td>When sorting or restricting, + work with:</td> + <td> + <input type="radio" name="sortvisible" id="entirelist" value="0" + [% "checked" IF NOT sortvisible %] /> + <label for="entirelist"> + entire list + </label> + <br /> + <input type="radio" name="sortvisible" id="visiblelist" value="1" + [% "checked" IF sortvisible %] /> + <label for="visiblelist"> + currently visible list + </label> + </td> + <td rowspan="4" valign="top">Restrict to products:</td> + <td rowspan="4" valign="top"> + <select name="product" size="5" multiple> + [% FOREACH p = products %] + <option name="[% p %]" + [% " selected" IF product == p %]>[% p %]</option> + [% END %] + </select> + </td> + </tr> + + <tr> + <td>Max rows:</td> + <td> + <input size="4" name="maxrows" value="[% maxrows %]" /> + </td> + </tr> + + <tr> + <td>Change column is change in the last:</td> + <td> + <input size="4" name="changedsince" value="[% changedsince %]" /> days + </td> + </tr> + + <tr> + <td> + <label for="openonly"> + Open bugs only: + </label> + </td> + <td> + <input type="checkbox" name="openonly" id="openonly" value="1" + [% "checked" IF openonly %] /> + </td> + </tr> + + </table> + + <input type="submit" value="Change" /> +</form> + +<form method="post" action="buglist.cgi"> + <input type="hidden" name="bug_id" value="[% bug_ids_string %]"> + <input type="hidden" name="order" value="Reuse same sort as last time"> + Or just give this to me as a <input type="submit" value="bug list">. + (Note: the order may not be the same.) +</form> + +<hr /> + +<b> + <a name="explanation">What are "Most Frequently Reported Bugs"?</a> +</b> + +<blockquote> + The Most Frequent Bugs page lists the known open bugs which + are reported most frequently in recent builds of Mozilla. It is + automatically generated from the Bugzilla database every 24 hours, by + counting the number of direct and indirect duplicates of bugs. + This information is provided in order to assist in minimizing + the amount of duplicate bugs entered into Bugzilla which in turn cuts down + on development time. +</blockquote> + +<b>How do I use this list?</b> + +<ul> + <li>Review the most frequent bugs list.</li> + <li>If problem is listed:</li> + + <ul> + <li>Click on Bug # link to confirm that you have found the same bug and + comment if you have additional information. Or move on with your testing + of the product. + </li> + </ul> + + <li>If problem not listed:</li> + + <ul> + <li>Go to the <a href="query.cgi">Bugzilla Search</a> + page to try and locate a similar bug that has already been written.</li> + <li>If you find your bug in Bugzilla, feel free to comment with any new or + additional data you may have.</li> + <li>If you cannot find your problem already documented in Bugzilla, go to + the + <a href="http://www.mozilla.org/quality/help/bug-form.html">Bugzilla + Helper</a> + and post a new bug.</li> + </ul> +</ul> + +[% INCLUDE global/footer %] |