Commit dad29731 authored by gerv%gerv.net's avatar gerv%gerv.net

Bug 173005 - Add bar charts, pie charts etc. to reporting. Patch by gerv; 2xr=joel.

parent 5b6b45ec
......@@ -258,25 +258,38 @@ foreach my $module (@{$modules}) {
}
print "\nThe following Perl modules are optional:\n" unless $silent;
my $charts = 0;
$charts++ if have_vers("GD","1.19");
$charts++ if have_vers("Chart::Base","0.99");
my $xmlparser = have_vers("XML::Parser",0);
my $gd = have_vers("GD","1.20");
my $chartbase = have_vers("Chart::Base","0.99");
my $xmlparser = have_vers("XML::Parser",0);
my $gdgraph = have_vers("GD::Graph",0);
my $gdtextalign = have_vers("GD::Text::Align",0);
print "\n" unless $silent;
if (($charts != 2) && !$silent) {
print "If you you want to see graphical bug dependency charts, you may install\n",
"the optional libgd and the Perl modules GD-1.19 and Chart::Base-0.99b, e.g. by\n",
"running (as root)\n\n",
" perl -MCPAN -e'install \"LDS/GD-1.19.tar.gz\"'\n",
" perl -MCPAN -e'install \"N/NI/NINJAZ/Chart-0.99b.tar.gz\"'\n\n";
if ((!$gd || !$chartbase) && !$silent) {
print "If you you want to see graphical bug charts (plotting historical ";
print "data over \ntime), you should install libgd and the following Perl "; print "modules:\n\n";
print "GD: perl -MCPAN -e'install \"GD\"'\n" if !$gd;
print "Chart 0.99b: perl -MCPAN " .
"-e'install \"N/NI/NINJAZ/Chart-0.99b.tar.gz\"'\n" if !$chartbase;
print "\n";
}
if (!$xmlparser && !$silent) {
print "If you want to use the bug import/export feature to move bugs to or from\n",
"other bugzilla installations, you will need to install the XML::Parser module by\n",
"running (as root)\n\n",
"running (as root):\n\n",
" perl -MCPAN -e'install \"XML::Parser\"'\n\n";
}
if ((!$gd || !$gdgraph || !$gdtextalign) && !$silent) {
print "If you you want to see graphical bug reports (bar, pie and line ";
print "charts of \ncurrent data), you should install libgd and the ";
print "following Perl modules:\n\n";
print "GD: perl -MCPAN -e'install \"GD\"'\n" if !$gd;
print "GD::Graph: perl -MCPAN " .
"-e'install \"GD::Graph\"'\n" if !$gdgraph;
print "GD::Text::Align: perl -MCPAN " .
"-e'install \"GD::Text::Align\"'\n" if !$gdtextalign;
print "\n";
}
if (%missing) {
print "\n\n";
print "Bugzilla requires some Perl modules which are either missing from your\n",
......@@ -580,6 +593,7 @@ $contenttypes = {
"xml" => "text/xml" ,
"js" => "application/x-javascript" ,
"csv" => "text/plain" ,
"png" => "image/png" ,
};
');
......
......@@ -131,7 +131,8 @@ sub PrefillForm {
"bug_file_loc_type", "status_whiteboard",
"status_whiteboard_type", "bug_id",
"bugidtype", "keywords", "keywords_type",
"x_axis_field", "y_axis_field", "z_axis_field")
"x_axis_field", "y_axis_field", "z_axis_field",
"chart_format", "cumulate")
{
# This is a bit of a hack. The default, empty list has
# three entries to accommodate the needs of the email fields -
......
......@@ -34,24 +34,58 @@ ConnectToDatabase();
GetVersionTable();
quietly_check_login();
confirm_login();
if ($::FORM{'action'} ne "plot") {
my $action = $cgi->param('action') || 'menu';
if ($action eq "menu") {
# No need to do any searching in this case, so bail out early.
print "Content-Type: text/html\n\n";
$template->process("reports/menu.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
$::FORM{'y_axis_field'} || ThrowCodeError("no_y_axis_defined");
my $col_field = $cgi->param('x_axis_field') || '';
my $row_field = $cgi->param('y_axis_field') || '';
my $tbl_field = $cgi->param('z_axis_field') || '';
if (!($col_field || $row_field || $tbl_field)) {
ThrowUserError("no_axes_defined");
}
my $width = $cgi->param('width');
my $height = $cgi->param('height');
if ($::FORM{'z_axis_field'} && !$::FORM{'x_axis_field'}) {
ThrowUserError("z_axis_defined_with_no_x_axis");
if (defined($width)) {
(detaint_natural($width) && $width > 0)
|| ThrowCodeError("invalid_dimensions");
$width <= 2000 || ThrowUserError("chart_too_large");
}
my $col_field = $::FORM{'x_axis_field'};
my $row_field = $::FORM{'y_axis_field'};
my $tbl_field = $::FORM{'z_axis_field'};
if (defined($height)) {
(detaint_natural($height) && $height > 0)
|| ThrowCodeError("invalid_dimensions");
$height <= 2000 || ThrowUserError("chart_too_large");
}
# These shenanigans are necessary to make sure that both vertical and
# horizontal 1D tables convert to the correct dimension when you ask to
# display them as some sort of chart.
if ($::FORM{'format'} && $::FORM{'format'} eq "table") {
if ($col_field && !$row_field) {
# 1D *tables* should be displayed vertically (with a row_field only)
$row_field = $col_field;
$col_field = '';
}
}
else {
if ($row_field && !$col_field) {
# 1D *charts* should be displayed horizontally (with an col_field only)
$col_field = $row_field;
$row_field = '';
}
}
my %columns;
$columns{'bug_severity'} = "bugs.bug_severity";
......@@ -83,6 +117,9 @@ my $search = new Bugzilla::Search('fields' => \@selectnames,
'params' => $params);
my $query = $search->getSQL();
$::SIG{TERM} = 'DEFAULT';
$::SIG{PIPE} = 'DEFAULT';
SendSQL($query);
# We have a hash of hashes for the data itself, and a hash to hold the
......@@ -90,9 +127,11 @@ SendSQL($query);
my %data;
my %names;
# Read the bug data and increment the counts.
# Read the bug data and count the bugs for each possible value of row, column
# and table.
while (MoreSQLData()) {
my ($row, $col, $tbl) = FetchSQLData();
$row = "" if ($row eq $columns{''});
$col = "" if ($col eq $columns{''});
$tbl = "" if ($tbl eq $columns{''});
......@@ -102,25 +141,106 @@ while (MoreSQLData()) {
$names{"tbl"}{$tbl}++;
}
# Determine the labels for the rows and columns
my @col_names = sort(keys(%{$names{"col"}}));
my @row_names = sort(keys(%{$names{"row"}}));
my @tbl_names = sort(keys(%{$names{"tbl"}}));
# The GD::Graph package requires a particular format of data, so once we've
# gathered everything into the hashes and made sure we know the size of the
# data, we reformat it into an array of arrays of arrays of data.
push(@tbl_names, "-total-") if (scalar(@tbl_names) > 1);
my @image_data;
foreach my $tbl (@tbl_names) {
my @tbl_data;
push(@tbl_data, \@col_names);
foreach my $row (@row_names) {
my @col_data;
foreach my $col (@col_names) {
$data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0;
push(@col_data, $data{$tbl}{$col}{$row});
if ($tbl ne "-total-") {
# This is a bit sneaky. We spend every loop except the last
# building up the -total- data, and then last time round,
# we process it as another tbl, and push() the total values
# into the image_data array.
$data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row};
}
}
push(@tbl_data, \@col_data);
}
push(@image_data, \@tbl_data);
}
$vars->{'col_field'} = $col_field;
$vars->{'row_field'} = $row_field;
$vars->{'tbl_field'} = $tbl_field;
$vars->{'names'} = \%names;
$vars->{'data'} = \%data;
$vars->{'time'} = time();
$cgi->delete('format');
$vars->{'col_names'} = \@col_names;
$vars->{'row_names'} = \@row_names;
$vars->{'tbl_names'} = \@tbl_names;
$vars->{'width'} = $width if $width;
$vars->{'height'} = $height if $height;
$vars->{'query'} = $query;
$vars->{'debug'} = $::FORM{'debug'};
my $formatparam = $cgi->param('format');
if ($action eq "wrap") {
# So which template are we using? If action is "wrap", we will be using
# no format (it gets passed through to be the format of the actual data),
# and either report.csv.tmpl (CSV), or report.html.tmpl (everything else).
# report.html.tmpl produces an HTML framework for either tables of HTML
# data, or images generated by calling report.cgi again with action as
# "plot".
$formatparam =~ s/[^a-zA-Z\-]//g;
trick_taint($formatparam);
$vars->{'format'} = $formatparam;
$formatparam = '';
# We need a number of different variants of the base URL for different
# URLs in the HTML.
$vars->{'buglistbase'} = $cgi->canonicalise_query(
"x_axis_field", "y_axis_field", "z_axis_field", "format", @axis_fields);
$vars->{'imagebase'} = $cgi->canonicalise_query(
$tbl_field, "action", "ctype", "format", "width", "height");
$vars->{'switchbase'} = $cgi->canonicalise_query(
"action", "ctype", "format", "width", "height");
$vars->{'data'} = \%data;
}
elsif ($action eq "plot") {
# If action is "plot", we will be using a format as normal (pie, bar etc.)
# and a ctype as normal (currently only png.)
$vars->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0;
$vars->{'data'} = \@image_data;
}
else {
ThrowUserError("unknown_action", {action => $cgi->param('action')});
}
my $format = GetFormat("reports/report", $formatparam, $cgi->param('ctype'));
# Calculate the base query URL for the hyperlinked numbers
$vars->{'querybase'} = $cgi->canonicalise_query("x_axis_field",
"y_axis_field",
"z_axis_field",
@axis_fields);
$vars->{'query'} = $cgi->query_string();
# If we get a template or CGI error, it comes out as HTML, which isn't valid
# PNG data, and the browser just displays a "corrupt PNG" message. So, you can
# set debug=1 to always get an HTML content-type, and view the error.
$format->{'ctype'} = "text/html" if $::FORM{'debug'};
# Generate and return the result from the appropriate template.
my $format = GetFormat("reports/report", $::FORM{'format'}, $::FORM{'ctype'});
print "Content-Type: $format->{'ctype'}\n\n";
# Problems with this CGI are often due to malformed data. Setting debug=1
# prints out both data structures.
if ($::FORM{'debug'}) {
use Data::Dumper;
print "<pre>data hash:\n";
print Dumper(%data) . "\n\n";
print "data array:\n";
print Dumper(@image_data) . "\n\n</pre>";
}
$template->process("$format->{'template'}", $vars)
|| ThrowTemplateError($template->error());
......@@ -119,6 +119,10 @@
The [% component FILTER html %] component doesn't exist in the
[% product FILTER html %] product.
[% ELSIF error == "invalid_dimensions" %]
[% title = "Invalid Dimensions" %]
The width or height specified is not a positive integer.
[% ELSIF error == "mismatched_bug_ids_on_obsolete" %]
Attachment [% attach_id FILTER html %] ([% description FILTER html %])
is attached to bug [% attach_bug_id FILTER html %], but you tried to
......
<!-- 1.0@bugzilla.org -->
[%# 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
......
......@@ -114,6 +114,10 @@
[% title = "Bugs Not Changed" %]
Um, you apparently did not change anything on the selected bugs.
[% ELSIF error == "chart_too_large" %]
[% title = "Chart Too Large" %]
Sorry, but 2000 x 2000 is the maximum size for a chart.
[% ELSIF error == "comment_required" %]
[% title = "Comment Required" %]
You have to specify a <b>comment</b> on this change.
......@@ -361,6 +365,10 @@
[% title = "New Password Missing" %]
You must enter a new password.
[% ELSIF error == "no_axes_defined" %]
[% title = "No Axes Defined" %]
You didn't define any axes to plot.
[% ELSIF error == "no_bugs_chosen" %]
[% title = "No Bugs Chosen" %]
You apparently didn't choose any bugs to modify.
......@@ -547,11 +555,6 @@
[% title = "Value Out Of Range" %]
Value is out of range for field <em>[% field_descs.$field %]</em>.
[% ELSIF error == "z_axis_defined_with_no_x_axis" %]
[% title = "Nonsensical Options" %]
You've defined a field for multiple tables without having defined
a horizontal axis for those tables.
[% ELSIF error == "zero_length_file" %]
[% title = "File Is Empty" %]
The file you are trying to attach is empty!
......
......@@ -55,7 +55,7 @@ function addSidebar() {
<p>
<a href="query.cgi">Query existing bug reports</a><br>
<a href="enter_bug.cgi">Enter a new bug report</a><br>
<a href="reports.cgi">Get summary reports</a><br>
<a href="report.cgi">Summary reports and charts</a><br>
</p><p>
[% IF username %]
<a href="userprefs.cgi">Change password or user preferences</a><br>
......
......@@ -29,7 +29,7 @@
%]
<p>
Bugzilla allows you to view and track the state of your bug database in
Bugzilla allows you to view and track the state of the bug database in
all manner of exciting ways.
</p>
......@@ -46,6 +46,12 @@
</strong> -
tables of bug counts in 1, 2 or 3 dimensions, as HTML or CSV.
</li>
<li>
<strong>
<a href="query.cgi?format=report-graph">Graphical reports</a>
</strong> -
line graphs, bar and pie charts.
</li>
</ul>
<h2>Change Over Time</h2>
......
[%# 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>
#%]
[% y_label = "Bugs" %]
[% PROCESS "global/field-descs.html.tmpl" %]
[% col_field_disp = field_descs.$col_field || col_field %]
[% FILTER null;
USE graph = GD.Graph.bars(width, height);
graph.set(x_label => col_field_disp,
y_label => y_label,
y_tick_number => 8,
y_number_format => "%d",
x_label_position => 0.5,
bar_spacing => 8,
shadow_depth => 4,
shadowclr => 'dred',
show_values => 1,
legend_placement => "RT");
graph.set(cumulate => "true",
show_values => 0) IF cumulate;
# Workaround for the fact that set_legend won't take row_names directly,
# because row_names is an array reference rather than an array.
graph.set_legend(row_names.0, row_names.1, row_names.2, row_names.3,
row_names.4, row_names.5, row_names.6, row_names.7,
row_names.8, row_names.9, row_names.10, row_names.11,
row_names.12, row_names.13, row_names.14, row_names.15);
graph.plot(data.0).png | stdout(1);
END;
-%]
[%# 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>
#%]
[% y_label = "Bugs" %]
[% PROCESS "global/field-descs.html.tmpl" %]
[% col_field_disp = field_descs.$col_field || col_field %]
[% IF cumulate %]
[% USE graph = GD.Graph.area(width, height) %]
[% graph.set(cumulate => "true") %]
[% ELSE %]
[% USE graph = GD.Graph.lines(width, height) %]
[% END %]
[% FILTER null;
graph.set(x_label => col_field_disp,
y_label => y_label,
y_tick_number => 8,
x_label_position => 0.5,
legend_placement => "RT",
line_width => 2);
# Workaround for the fact that set_legend won't take row_names directly,
# because row_names is an array reference rather than an array.
graph.set_legend(row_names.0, row_names.1, row_names.2, row_names.3,
row_names.4, row_names.5, row_names.6, row_names.7,
row_names.8, row_names.9, row_names.10, row_names.11,
row_names.12, row_names.13, row_names.14, row_names.15);
graph.plot(data.0).png | stdout(1);
END;
-%]
[%# 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>
#%]
[% PROCESS "global/field-descs.html.tmpl" %]
[% col_field_disp = field_descs.$col_field || col_field %]
[% FILTER null;
USE graph = GD.Graph.pie(width, height);
graph.set(title => col_field_disp,
pie_height => 20,
start_angle => 180);
graph.plot(data.0).png | stdout(1);
END;
-%]
......@@ -19,39 +19,33 @@
# Contributor(s): Gervase Markham <gerv@gerv.net>
#%]
[%# INTERFACE:
# See report.html.tmpl.
# See report-table.html.tmpl.
#%]
[% tbl_names = names.tbl.keys.sort %]
[% col_names = names.col.keys.sort %]
[% row_names = names.row.keys.sort %]
[% num_bugs = "Number of bugs" %]
[% tbl_field_disp = field_descs.$tbl_field || tbl_field %]
[% col_field_disp = field_descs.$col_field || col_field %]
[% row_field_disp = field_descs.$row_field || row_field %]
[% FOREACH tbl = tbl_names %]
[% IF tbl_field -%]
[% tbl FILTER html %]
[% END %]
[% row_field FILTER csv -%]
[% IF col_field -%]
\ [% col_field FILTER csv -%],
[% FOREACH col = col_names -%]
[% col FILTER csv -%],
[% END -%]
[% ELSE -%]
[% -%],Number of bugs
[% END %]
[% "$tbl_field_disp: $tbl\n" FILTER csv IF tbl_field %]
[% row_field_disp FILTER csv IF row_field %]
[% " / " IF col_field AND row_field %]
[% col_field_disp FILTER csv %],
[% IF col_field -%]
[% FOREACH col = col_names -%]
[% col FILTER csv -%],
[% END -%]
[% ELSE -%]
[% num_bugs %],
[% END %]
[% FOREACH row = row_names %]
[% row FILTER csv -%],
[% FOREACH col = col_names %]
[% IF data.$tbl AND data.$tbl.$col AND data.$tbl.$col.$row %]
[% data.$tbl.$col.$row -%],
[% ELSE %]
[% -%]0,
[% END %]
[% FOREACH row = row_names %]
[% row FILTER csv -%],
[% FOREACH col = col_names %]
[% IF data.$tbl AND data.$tbl.$col AND data.$tbl.$col.$row %]
[% data.$tbl.$col.$row -%],
[% ELSE %]
[% -%]0,
[% END %]
[% END %]
[% END %]
......@@ -21,92 +21,51 @@
#%]
[%# INTERFACE:
# querybase: The base query for this table, in URL form
# query: The query for this table, in URL form
# data: hash of hash of hash of numbers. Bug counts.
# names: hash of hash of strings. Names of tables, rows and columns.
# buglistbase: The base query for this table, in URL form
# col_field: string. Name of the field being plotted as columns.
# row_field: string. Name of the field being plotted as rows.
# tbl_field: string. Name of the field being plotted as tables.
# col_names: array. List of values for the field being plotted as columns.
# row_names: array. List of values for the field being plotted as rows.
# data: <depends on format>. Data to plot. Only data.$tbl is accessed.
# tbl: Name of a hash in data which is the table to be plotted.
#%]
[% PROCESS "global/field-descs.html.tmpl" %]
[% tbl_field_disp = field_descs.$tbl_field || tbl_field %]
[% col_field_disp = field_descs.$col_field || col_field %]
[% row_field_disp = field_descs.$row_field || row_field %]
[% title = BLOCK %]
Report:
[% "$tbl_field_disp / " IF tbl_field %]
[% "$col_field_disp / " IF col_field %]
[% row_field_disp %]
[% END %]
[% PROCESS global/header.html.tmpl
style = "
.t1 { background-color: #ffffff } /* white */
.t2 { background-color: #dfefff } /* light blue */
.t3 { background-color: #dddddd } /* grey */
.t4 { background-color: #c3d3ed } /* darker blue */
.ttotal { background-color: #cfffdf } /* light green */
"
%]
<div align="right">
[% time2str("%Y-%m-%d %H:%M:%S", time) %]
</div>
[% tbl_names = names.tbl.keys.sort %]
[% col_names = names.col.keys.sort %]
[% row_names = names.row.keys.sort %]
[% total_name = "Total" %]
[% FOREACH tbl = tbl_names %]
[% FOREACH row = row_names %]
[% FOREACH col = col_names %]
[% data.$tbl.$col.$row = (data.$tbl.$col.$row || 0) %]
[% IF tbl_field %]
[%# Calculate values for the Total table %]
[% data.$total_name.$col.$row =
(data.$total_name.$col.$row || 0) + data.$tbl.$col.$row %]
[% END %]
[% END %]
[% END %]
[% END %]
[% IF tbl_field %]
[% tbl_names.push(total_name) %]
[% IF tbl == "-total-" %]
[% urlbase = BLOCK %]buglist.cgi?[% buglistbase %][% END %]
[% ELSE %]
[% urlbase = BLOCK %]buglist.cgi?[% buglistbase %]&amp;
[% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %][% END %]
[% END %]
<div align="center">
[% FOREACH tbl = tbl_names %]
<table>
[% IF tbl_field %]
<tr>
<td>
</td>
<td align="center">
<h2>[% tbl FILTER html %]</h2>
</td>
</tr>
[% END %]
<table>
[% IF tbl_field %]
<tr>
<td>
</td>
<td align="center">
<strong>[% col_field_disp FILTER html %]</strong>
<td align="center">
<h2>[% tbl_disp %]</h2>
</td>
</tr>
[% END %]
<tr>
<td>
</td>
<td align="center">
<strong>[% col_field_disp FILTER html %]</strong>
</td>
</tr>
<tr>
<td valign="middle">
<strong>[% row_field_disp FILTER html %]</strong>
</td>
<td>
<tr>
<td valign="middle">
<strong>[% row_field_disp FILTER html %]</strong>
</td>
<td>
[% classes = [ [ "t1", "t2" ] , [ "t3", "t4" ] ] %]
......@@ -150,8 +109,7 @@
[% col_idx = 1 - col_idx %]
<td class="[% classes.$row_idx.$col_idx %]" align="center">
[% IF data.$tbl.$col.$row AND data.$tbl.$col.$row > 0 %]
<a href="buglist.cgi?[% querybase FILTER html %]&amp;
[% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %]&amp;
<a href="[% urlbase %]&amp;
[% row_field FILTER url_quote %]=[% row FILTER url_quote %]&amp;
[% col_field FILTER url_quote %]=[% col FILTER url_quote %]">
[% data.$tbl.$col.$row %]</a>
......@@ -161,8 +119,7 @@
</td>
[% END %]
<td class="ttotal" align="right">
<a href="buglist.cgi?[% querybase FILTER html %]&amp;
[% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %]&amp;
<a href="[% urlbase %]&amp;
[% row_field FILTER url_quote %]=[% row FILTER url_quote %]">
[% row_total %]</a>
[% grand_total = grand_total + row_total %]
......@@ -179,8 +136,7 @@
[% NEXT IF col == "" %]
<td class="ttotal" align="center">
<a href="buglist.cgi?[% querybase FILTER html %]&amp;
[% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %]&amp;
<a href="[% urlbase %]&amp;
[% col_field FILTER url_quote %]=[% col FILTER url_quote %]">
[% col_totals.$col %]</a>
<strong>
......@@ -188,24 +144,13 @@
[% END %]
<td class="ttotal" align="right">
<strong>
<a href="buglist.cgi?[% querybase FILTER html %]">[% grand_total %]</a>
<a href="buglist.cgi?[% urlbase %]">[% grand_total %]</a>
</strong>
</td>
</tr>
</table>
</td>
</tr>
</table>
<br>
[% END %]
<a href="query.cgi?[% query FILTER html %]&amp;format=report-table">Edit this report</a>
</div>
<br>
[% PROCESS global/footer.html.tmpl %]
</td>
</tr>
</table>
[%# 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>
#%]
[% PROCESS "global/field-descs.html.tmpl" %]
[% FOREACH tbl = tbl_names %]
[% PROCESS "reports/report-table.csv.tmpl" %]
[% END %]
<!-- 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:
# col_field: string. Name of the field being plotted as columns.
# row_field: string. Name of the field being plotted as rows.
# tbl_field: string. Name of the field being plotted as tables.
# tbl_names: array. List of values for the field being plotted as tables.
# time: integer. Seconds since the epoch.
# data: <depends on format>. Data to plot.
# format: string. Format of the individual reports.
# width: integer. For image charts, height of the image.
# height: integer. For image charts, width of the image.
# switchbase: string. Base URL for format switching.
# cumulate: boolean. For bar/line charts, whether to cumulate data sets.
#%]
[% DEFAULT width = 600
height = 350
%]
[%# We ignore row_field for pie charts %]
[% IF format == "pie" %]
[% row_field = "" %]
[% END %]
[% PROCESS "global/field-descs.html.tmpl" %]
[% tbl_field_disp = field_descs.$tbl_field || tbl_field %]
[% col_field_disp = field_descs.$col_field || col_field %]
[% row_field_disp = field_descs.$row_field || row_field %]
[% title = BLOCK %]
Report:
[% tbl_field_disp IF tbl_field %]
[% " / " IF tbl_field AND (col_field OR row_field) %]
[% row_field_disp IF row_field %]
[% " / " IF col_field AND row_field %]
[% col_field_disp %]
[% END %]
[% PROCESS global/header.html.tmpl
style = "
.t1 { background-color: #ffffff } /* white */
.t2 { background-color: #dfefff } /* light blue */
.t3 { background-color: #dddddd } /* grey */
.t4 { background-color: #c3d3ed } /* darker blue */
.ttotal { background-color: #cfffdf } /* light green */
"
h3 = time2str("%Y-%m-%d %H:%M:%S", time)
%]
[% IF debug %]
<p>[% query FILTER html %]</p>
[% END %]
<div align="center">
[% FOREACH tbl = tbl_names %]
[% IF tbl == "-total-" %]
[% tbl_disp = "Total" %]
[% ELSE %]
[% tbl_disp = tbl %]
[% END %]
[% IF format == "table" %]
[% PROCESS "reports/report-table.html.tmpl" %]
[% ELSE %]
[% IF tbl %]
<h2>[% tbl_disp FILTER html %]</h2>
[% END %]
[% imageurl = BLOCK %]report.cgi?[% imagebase %]&amp;format=
[% format FILTER url_quote %]&amp;ctype=png&amp;action=plot&amp;
[% IF tbl_field AND tbl != "-total-" %]
[% tbl_field FILTER url_quote %]=[% tbl FILTER url_quote %]&amp;
[% END %]width=[% width %]&amp;height=[% height %]
[% END %]
<img src="[% imageurl %]" width="[% width %]" height="[% height %]">
[% END %]
<br>
[% END %]
<table>
<tr>
<td>
[% formats = [ { name => "pie", description => "Pie" },
{ name => "bar", description => "Bar" },
{ name => "line", description => "Line" },
{ name => "table", description => "Table" } ] %]
[% formaturl = "report.cgi?$switchbase&width=$width&height=$height" _
"&action=wrap" %]
[% FOREACH other_format = formats %]
[% NEXT IF other_format.name == "pie" AND row_field %]
[% UNLESS other_format.name == format %]
<a href="[% formaturl %]&format=[% other_format.name %]">
[% END %]
[% other_format.description %]
[% "</a>" UNLESS other_format.name == format %] |
[% END %]
<a href="[% formaturl %]&ctype=csv">CSV</a>
</td>
[% IF format != "table" %]
<td>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</td>
[% sizeurl = "report.cgi?$switchbase&action=wrap&format=$format" %]
<td align="center">
<a href="[% sizeurl %]&width=[% width %]&height=
[% height + 100 %]">Taller</a><br>
<a href="[% sizeurl %]&width=[% width - 100 %]&height=
[% height %]">Thinner</a> *
<a href="[% sizeurl %]&width=[% width + 100 %]&height=
[% height %]">Fatter</a>&nbsp;&nbsp;&nbsp;&nbsp;<br>
<a href="[% sizeurl %]&width=[% width %]&height=
[% height - 100 %]">Shorter</a><br>
</td>
[% END %]
<tr>
</table>
<p>
[% IF format == "table" %]
<a href="query.cgi?[% switchbase %]&format=report-table">Edit
this report</a>
[% ELSE %]
<a href="query.cgi?[% switchbase %]&format=report-graph&chart_format=
[% format %]&cumulate=[% cumulate %]">Edit this report</a>
[% END %]
</p>
</div>
[% PROCESS global/footer.html.tmpl %]
<!-- 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']);"
%]
[% PROCESS "search/search-report-select.html.tmpl" %]
<p>
Produce a pictorial graph of bug counts by choosing one or more fields as
your axes, and then refining your set of bugs using the rest of the form.
If you choose a third axis, it will be represented by multiple tables of data.
Note: vertical axis settings will be ignored for pie charts.
</p>
[% button_name = "Generate Report" %]
<form method="get" action="report.cgi" name="reportform">
<table align="center">
<tr>
<td valign="middle">
<b>Vertical Axis:</b><br>
[% PROCESS select name = 'y_axis_field' %]<br>
<br>
<b>Plot Data Sets:</b><br>
<input type="radio" name="cumulate" value="0"
[% " checked" IF default.cumulate.0 != "1" %]>
Individually<br>
<input type="radio" name="cumulate" value="1"
[% " checked" IF default.cumulate.0 == "1" %]>
Added
</td>
<td width="150px" height="150px">
<table border="1" width="100%" height="100%">
<tr>
<td align="center" valign="middle">
<b>Multiple Images:</b><br>
[% PROCESS select name = 'z_axis_field' %]
</td>
</tr>
</table>
</td>
<td rowspan="2">
<b>Format:</b><br>
[% chart_formats = [
{ name => "line", description => "Line Graph" },
{ name => "bar", description => "Bar Chart" },
{ name => "pie", description => "Pie Chart" } ] %]
[% default.chart_format.0 = default.chart_format.0 || "bar" %]
[% FOREACH chart_format = chart_formats %]
<input type="radio" name="format"
value="[% chart_format.name FILTER html %]"
[% " checked" IF default.chart_format.0 == chart_format.name %]>
[% chart_format.description FILTER html %]<br>
[% END %]
</td>
</tr>
<tr>
<td>
</td>
<td align="center">
<b>Horizontal Axis:</b>
[% PROCESS select name = 'x_axis_field' %]
</td>
<td>
</td>
</tr>
</table>
<hr>
[% PROCESS search/form.html.tmpl %]
<br>
<input type="submit" value="[% button_name %]">
<input type="hidden" name="action" value="wrap">
<hr>
[% PROCESS "search/boolean-charts.html.tmpl" %]
</form>
[% PROCESS global/footer.html.tmpl %]
<!-- 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:
# name: string. The name of the select block to output.
# default.$name.0: string. The default value for the block, if any.
#%]
[% PROCESS "global/field-descs.html.tmpl" %]
[% BLOCK select %]
[% fields = ["product", "component", "version", "rep_platform",
"op_sys", "bug_status", "resolution", "bug_severity",
"priority", "target_milestone", "keywords", "assigned_to",
"reporter", "qa_contact", "votes" ] %]
<select name="[% name FILTER html %]">
<option value="">&lt;none&gt;</option>
[% FOREACH field = fields %]
[% NEXT IF field == "target_milestone" AND !Param('usetargetmilestone') %]
[% NEXT IF field == "qa_contact" AND !Param('useqacontact') %]
[% NEXT IF field == "votes" AND !Param('usevotes') %]
<option value="[% field FILTER html %]"
[% " selected" IF default.$name.0 == field %]>
[% field_descs.$field || field FILTER html %]</option>
[% END %]
</select>
[% END %]
......@@ -29,12 +29,12 @@
onload = "selectProduct(document.forms['reportform']);"
%]
[% PROCESS "global/field-descs.html.tmpl" %]
[% PROCESS "search/search-report-select.html.tmpl" %]
<p>
Produce a table of bug counts by choosing one or more fields to plot against
each other, and then refining your set of bugs using the rest of the form. If
you choose a third axis, it will be represented by multiple tables of data.
Produce a table of bug counts by choosing one or more fields as your axes,
and then refining your set of bugs using the rest of the form.
If you choose a third axis, it will be represented by multiple tables of data.
</p>
[% button_name = "Generate Report" %]
......@@ -47,7 +47,7 @@
</td>
<td align="center">
<b>Horizontal Axis:</b>
[% PROCESS select sel = { name => 'x_axis_field', noop = 1 } %]
[% PROCESS select name = 'x_axis_field' %]
</td>
<td>&nbsp;&nbsp;</td>
<td rowspan="2">
......@@ -60,14 +60,14 @@
<tr>
<td valign="middle" align="center">
<b>Vertical Axis:</b><br>
[% PROCESS select sel = { name => 'y_axis_field' } %]
[% PROCESS select name = 'y_axis_field' %]
</td>
<td width="150px" height="150px">
<table border="1" width="100%" height="100%">
<tr>
<td align="center" valign="middle">
<b>Multiple Tables:</b><br>
[% PROCESS select sel = { name => 'z_axis_field', noop = 1 } %]
[% PROCESS select name = 'z_axis_field' %]
</td>
</tr>
</table>
......@@ -82,7 +82,7 @@
<br>
<input type="submit" value="[% button_name %]">
<input type="hidden" name="format" value="table">
<input type="hidden" name="action" value="plot">
<input type="hidden" name="action" value="wrap">
<hr>
[% PROCESS "search/boolean-charts.html.tmpl" %]
......@@ -90,30 +90,3 @@
</form>
[% PROCESS global/footer.html.tmpl %]
[%############################################################################%]
[%# Block for SELECT fields #%]
[%############################################################################%]
[% BLOCK select %]
[% fields = ["product", "component", "version", "rep_platform",
"op_sys", "bug_status", "resolution", "bug_severity",
"priority", "target_milestone", "keywords", "assigned_to",
"reporter", "qa_contact", "votes" ] %]
<select name="[% sel.name %]">
[% IF sel.noop %]
<option value="">&lt;none&gt;</option>
[% END %]
[% FOREACH field = fields %]
[% NEXT IF field == "target_milestone" AND !Param('usetargetmilestone') %]
[% NEXT IF field == "qa_contact" AND !Param('useqacontact') %]
[% NEXT IF field == "votes" AND !Param('usevotes') %]
<option value="[% field FILTER html %]"
[% " selected" IF default.${sel.name}.0 == field %]>
[% field_descs.$field || field FILTER html %]</option>
[% END %]
</select>
[% END %]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment