summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Search.pm4
-rwxr-xr-xchecksetup.pl2
-rw-r--r--globals.pl12
-rwxr-xr-xquery.cgi4
-rwxr-xr-xreport.cgi130
-rw-r--r--t/004template.t1
-rw-r--r--template/en/default/global/code-error.html.tmpl4
-rw-r--r--template/en/default/reports/table.csv.tmpl41
-rw-r--r--template/en/default/reports/table.html.tmpl143
-rw-r--r--template/en/default/search/search-report-table.html.tmpl125
10 files changed, 465 insertions, 1 deletions
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index d6e7a9b7f..642965eb2 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -813,6 +813,10 @@ sub init {
$suppseen{$str} = 1;
}
}
+
+ # Make sure we create a legal SQL query.
+ @andlist = ("1 = 1") if !@andlist;
+
my $query = ("SELECT DISTINCT " .
join(', ', @fields) .
", COUNT(DISTINCT ugmap.group_id) AS cntuseringroups, " .
diff --git a/checksetup.pl b/checksetup.pl
index b752f9b65..27bcf26f9 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -563,6 +563,7 @@ $contenttypes = {
"rdf" => "application/xml" ,
"xml" => "text/xml" ,
"js" => "application/x-javascript" ,
+ "csv" => "text/plain" ,
};
');
@@ -932,6 +933,7 @@ END
js => sub { return $_; },
html_linebreak => sub { return $_; },
url_quote => sub { return $_; },
+ csv => sub { return $_; },
},
}) || die ("Could not create Template: " . Template->error() . "\n");
diff --git a/globals.pl b/globals.pl
index 242c8a5f6..64031bc85 100644
--- a/globals.pl
+++ b/globals.pl
@@ -1564,6 +1564,18 @@ $::template ||= Template->new(
# filter should be used for a full URL that may have
# characters that need encoding.
url_quote => \&url_quote ,
+
+ # In CSV, quotes are doubled, and any value containing a quote or a
+ # comma is enclosed in quotes.
+ csv => sub
+ {
+ my ($var) = @_;
+ $var =~ s/"/""/;
+ if ($var =~ /",/) {
+ $var = "\"$var\"";
+ }
+ return $var;
+ } ,
} ,
}
) || die("Template creation failed: " . Template->error());
diff --git a/query.cgi b/query.cgi
index cd679e8e8..4bda141c3 100755
--- a/query.cgi
+++ b/query.cgi
@@ -130,7 +130,9 @@ sub PrefillForm {
"long_desc", "long_desc_type", "bug_file_loc",
"bug_file_loc_type", "status_whiteboard",
"status_whiteboard_type", "bug_id",
- "bugidtype", "keywords", "keywords_type") {
+ "bugidtype", "keywords", "keywords_type",
+ "x_axis_field", "y_axis_field")
+ {
# This is a bit of a hack. The default, empty list has
# three entries to accommodate the needs of the email fields -
# we use each position to denote the relevant field. Array
diff --git a/report.cgi b/report.cgi
new file mode 100755
index 000000000..525897771
--- /dev/null
+++ b/report.cgi
@@ -0,0 +1,130 @@
+#!/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): Gervase Markham <gerv@gerv.net>
+# <rdean@cambianetworks.com>
+
+use diagnostics;
+use strict;
+use lib ".";
+
+require "CGI.pl";
+
+use vars qw($template $vars);
+
+use Bugzilla::Search;
+
+ConnectToDatabase();
+
+GetVersionTable();
+
+quietly_check_login();
+
+# If other report types are added, some of this code can be moved into a sub,
+# and the correct sub chosen based on $::FORM{'type'}.
+
+$::FORM{'y_axis_field'} || ThrowCodeError("no_y_axis_defined");
+
+my $col_field = $::FORM{'x_axis_field'};
+my $row_field = $::FORM{'y_axis_field'};
+
+my %columns;
+$columns{'bug_severity'} = "bugs.bug_severity";
+$columns{'priority'} = "bugs.priority";
+$columns{'rep_platform'} = "bugs.rep_platform";
+$columns{'assigned_to'} = "map_assigned_to.login_name";
+$columns{'reporter'} = "map_reporter.login_name";
+$columns{'qa_contact'} = "map_qa_contact.login_name";
+$columns{'bug_status'} = "bugs.bug_status";
+$columns{'resolution'} = "bugs.resolution";
+$columns{'component'} = "map_components.name";
+$columns{'product'} = "map_products.name";
+$columns{'version'} = "bugs.version";
+$columns{'op_sys'} = "bugs.op_sys";
+$columns{'votes'} = "bugs.votes";
+$columns{'keywords'} = "bugs.keywords";
+$columns{'target_milestone'} = "bugs.target_milestone";
+
+my @axis_fields = ($row_field);
+# The X axis (horizontal) is optional
+push(@axis_fields, $col_field) if $col_field;
+
+my @selectnames = map($columns{$_}, @axis_fields);
+
+my $search = new Bugzilla::Search('fields' => \@selectnames,
+ 'url' => $::buffer);
+my $query = $search->getSQL();
+
+$query =~ s/DISTINCT//;
+SendSQL($query, $::userid);
+
+# We have a hash for each direction for the totals, and a hash of hashes for
+# the data itself.
+my %data;
+my %row_totals;
+my %col_totals;
+my $grand_total;
+
+# Read the bug data and increment the counts.
+while (MoreSQLData()) {
+ my ($row, $col) = FetchSQLData();
+ $row = "" if !defined($row);
+ $col = "" if !defined($col);
+
+ $data{$row}{$col}++;
+ $row_totals{$row}++;
+ $col_totals{$col}++;
+ $grand_total++;
+}
+
+$vars->{'data'} = \%data;
+$vars->{'row_totals'} = \%row_totals;
+$vars->{'col_totals'} = \%col_totals;
+$vars->{'grand_total'} = $grand_total;
+
+# Determine the labels for the rows and columns
+my @row_names = sort(keys(%row_totals));
+my @col_names = sort(keys(%col_totals));
+
+$vars->{'row_names'} = \@row_names;
+$vars->{'col_names'} = \@col_names;
+
+$vars->{'row_field'} = $row_field;
+$vars->{'col_field'} = $col_field;
+
+$::buffer =~ s/format=[^&]*&?//g;
+
+# Calculate the base query URL for the hyperlinked numbers
+my $buglistbase = $::buffer;
+$buglistbase =~ s/$row_field=[^&]*&?//g;
+$buglistbase =~ s/$col_field=[^&]*&?//g;
+
+$vars->{'buglistbase'} = $buglistbase;
+$vars->{'buffer'} = $::buffer;
+
+$::FORM{'type'} =~ s/[^a-zA-Z\-]//g;
+
+# Generate and return the result from the appropriate template.
+my $format = GetFormat("reports/$::FORM{'type'}",
+ $::FORM{'format'},
+ $::FORM{'ctype'});
+print "Content-Type: $format->{'contenttype'}\n\n";
+$template->process("$format->{'template'}", $vars)
+ || ThrowTemplateError($template->error());
diff --git a/t/004template.t b/t/004template.t
index 41d515435..02541d351 100644
--- a/t/004template.t
+++ b/t/004template.t
@@ -81,6 +81,7 @@ my $template = Template->new(
js => sub { return $_ } ,
strike => sub { return $_ } ,
url_quote => sub { return $_ } ,
+ csv => sub { return $_ } ,
},
}
);
diff --git a/template/en/default/global/code-error.html.tmpl b/template/en/default/global/code-error.html.tmpl
index 55fac2aca..bf93977ad 100644
--- a/template/en/default/global/code-error.html.tmpl
+++ b/template/en/default/global/code-error.html.tmpl
@@ -78,6 +78,10 @@
[% ELSIF error == "no_bug_data" %]
No data when fetching bug [% bug_id %].
+ [% ELSIF error == "no_y_axis_defined" %]
+ No Y axis was defined when creating report. The X axis is optional,
+ but the Y axis is compulsory.
+
[% ELSIF error == "template_error" %]
[% template_error_msg %]
diff --git a/template/en/default/reports/table.csv.tmpl b/template/en/default/reports/table.csv.tmpl
new file mode 100644
index 000000000..96b0c3971
--- /dev/null
+++ b/template/en/default/reports/table.csv.tmpl
@@ -0,0 +1,41 @@
+[%# 1.0@bugzilla.org %]
+[%# 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:
+ # See report.html.tmpl.
+ #%]
+[% row_field FILTER csv -%]
+
+[% IF col_field -%]
+ \ [% col_field FILTER csv -%],
+[% FOREACH col = col_names -%]
+[% col FILTER csv -%],
+[% END -%]
+[% ELSE -%]
+,Number of bugs,
+[% END %]
+
+[% FOREACH row = row_names %]
+[% row FILTER csv -%],
+[% FOREACH col = col_names %]
+[% IF data.$row AND data.$row.$col %][% data.$row.$col -%],[% ELSE %]0,[% END %]
+[% END %]
+
+[% END %]
diff --git a/template/en/default/reports/table.html.tmpl b/template/en/default/reports/table.html.tmpl
new file mode 100644
index 000000000..d9a04d5fd
--- /dev/null
+++ b/template/en/default/reports/table.html.tmpl
@@ -0,0 +1,143 @@
+ <!-- 1.0@bugzilla.org -->
+[%# 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>
+ # <rdean@cambianetworks.com>
+ #%]
+
+[%# INTERFACE:
+ # basequery: The base query for this table, in URL form
+ # row_field: string. The field name for the data in table rows
+ # col_field: string. The field name for the data in table columns
+ # col_names: array of strings. Values for the columns
+ # row_names: array of strings. Values for the rows
+ # col_totals: hash of integers. Totals for the columns, indexed by col_names.
+ # row_totals: hash of integers. Totals for the rows, indexed by row_names.
+ # data: hash of hash of numbers. Bug counts indexed by col_names and
+ # row_names values.
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Report"
+ onload = "selectProduct(document.forms['queryform']);"
+ style = "
+ .t1 { background-color: #ffffff }
+ .t2 { background-color: #dfefff }
+ .t3 { background-color: #dddddd }
+ .t4 { background-color: #c3d3ed }
+ .ttotal { background-color: #cfffdf }
+ "
+%]
+
+<div align="center">
+ <table>
+ <tr>
+ <td>
+ </td>
+ <td align="center">
+ <strong>[% col_field FILTER html %]</strong>
+ </td>
+ </tr>
+
+ <tr>
+ <td valign="middle">
+ <strong>[% row_field FILTER html %]</strong>
+ </td>
+ <td>
+
+
+[% classes = [ [ "t1", "t2" ] , [ "t3", "t4" ] ] %]
+[% col_idx = 0 %]
+[% row_idx = 0 %]
+
+<table>
+ [% IF col_names %]
+ <tr>
+ <td class="[% classes.$row_idx.$col_idx %]">
+ </td>
+ [% FOREACH col = col_names %]
+ [%# If no col header, skip the col. This makes display look right if
+ there's no defined X axis. Not doing this gives us two cols. %]
+ [% NEXT IF col == "" %]
+ [% col_idx = 1 - col_idx %]
+ <td class="[% classes.$row_idx.$col_idx %]">
+ [% col FILTER html %]
+ </td>
+ [% END %]
+ <td class="ttotal">
+ Total
+ </td>
+ </tr>
+ [% END %]
+
+ [% FOREACH row = row_names %]
+ [% row_idx = 1 - row_idx %]
+ <tr>
+ <td class="[% classes.$row_idx.$col_idx %]">
+ [% row FILTER html %]
+ </td>
+ [% FOREACH col = col_names %]
+ [% NEXT IF col == "" %]
+ [% col_idx = 1 - col_idx %]
+ <td class="[% classes.$row_idx.$col_idx %]" align="center">
+ [% IF data.$row.$col AND data.$row.$col > 0 %]
+ <a href="buglist.cgi?[% buglistbase %]&
+ [% row_field FILTER url_quote %]=[% row FILTER url_quote %]&
+ [% col_field FILTER url_quote %]=[% col FILTER url_quote %]">
+ [% data.$row.$col %]</a>
+ [% ELSE %]
+ .
+ [% END %]
+ </td>
+ [% END %]
+ <td class="ttotal">
+ [% row_totals.$row %]
+ </td>
+ </tr>
+ [% END %]
+
+ <tr>
+ [% row_idx = 1 - row_idx %]
+ <td class="ttotal">
+ Total
+ </td>
+ [% FOREACH col = col_names %]
+ [% NEXT IF col == "" %]
+ <td class="ttotal" align="center">
+ [% col_totals.$col %]
+ </td>
+ [% END %]
+ <td class="ttotal">
+ <strong>
+ [% grand_total %]
+ </strong>
+ </td>
+ </tr>
+</table>
+
+
+ </td>
+ </tr>
+ </table>
+
+ <a href="query.cgi?[% buffer %]&format=report-table">Edit this report</a>
+</div>
+
+<br>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/search/search-report-table.html.tmpl b/template/en/default/search/search-report-table.html.tmpl
new file mode 100644
index 000000000..32f816135
--- /dev/null
+++ b/template/en/default/search/search-report-table.html.tmpl
@@ -0,0 +1,125 @@
+<!-- 1.0@bugzilla.org -->
+[%# 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:
+ # This template has no interface. However, to use it, you need to fulfill
+ # the interfaces of the templates it contains.
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Generate Report"
+ onload = "selectProduct(document.forms['reportform']);"
+%]
+
+<p>
+ Produce a table of bug counts by choosing two fields to plot against each
+ other, and then refining your set of bugs using the rest of the form.
+</p>
+
+[% button_name = "Generate Report" %]
+
+<form method="get" action="report.cgi" name="reportform">
+
+<table>
+ <tr>
+ <th align="center">
+ Vertical Axis
+ </th>
+ <th align="center">
+ Horizontal Axis
+ </th>
+ <th>
+ &nbsp;&nbsp;
+ </th>
+ <th align="center">
+ Format
+ </th>
+ </tr>
+
+ <tr>
+ <td align="center">
+ [% PROCESS select sel = { name => 'y_axis_field' } %]
+ </td>
+ <td align="center">
+ [% PROCESS select sel = { name => 'x_axis_field', noop = 1 } %]
+ </td>
+ <td>
+ &nbsp;&nbsp;
+ </td>
+ <td>
+ <input type="radio" name="ctype" value="html" checked>HTML
+ <input type="radio" name="ctype" value="csv">CSV
+ </td>
+ </tr>
+</table>
+
+<hr>
+
+[% PROCESS search/form.html.tmpl %]
+
+<br>
+<input type="submit" value="[% button_name %]">
+<input type="hidden" name="type" value="table">
+<hr>
+
+[% PROCESS "search/boolean-charts.html.tmpl" %]
+
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
+
+[%############################################################################%]
+[%# Block for SELECT fields #%]
+[%############################################################################%]
+
+[% BLOCK select %]
+ [% fields = [
+ { name => "", description => "---" },
+ { name => "product", description => "Product" },
+ { name => "component", description => "Component" },
+ { name => "version", description => "Version" },
+ { name => "rep_platform", description => "Platform" },
+ { name => "op_sys", description => "OS" },
+ { name => "bug_status", description => "Status" },
+ { name => "resolution", description => "Resolution" },
+ { name => "bug_severity", description => "Severity" },
+ { name => "priority", description => "Priority" },
+ { name => "target_milestone", description => "Target Milestone" },
+ { name => "keywords", description => "Keywords" },
+ { name => "assigned_to", description => "Assignee" },
+ { name => "reporter", description => "Reporter" },
+ { name => "qa_contact", description => "QA Contact" },
+ { name => "votes", description => "Votes" } ] %]
+
+ <select name="[% sel.name %]">
+ [% FOREACH field = fields %]
+ [% NEXT IF field.name == "" AND !sel.noop %]
+ [% NEXT IF field.name == "target_milestone" AND
+ !Param('usetargetmilestone') %]
+ [% NEXT IF field.name == "qa_contact" AND !Param('useqacontact') %]
+ [% NEXT IF field.name == "votes" AND !Param('usevotes') %]
+
+ <option value="[% field.name FILTER html %]"
+ [% " selected" IF default.${sel.name}.0 == field.name %]>
+ [% field.description FILTER html %]</option>
+ [% END %]
+ </select>
+[% END %]