summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xduplicates.cgi354
-rw-r--r--template/default/report/duplicates.html.tmpl275
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 %]