From dad297316ae335ccc20e5d0546525d1c117131c0 Mon Sep 17 00:00:00 2001 From: "gerv%gerv.net" <> Date: Tue, 29 Oct 2002 15:43:57 +0000 Subject: Bug 173005 - Add bar charts, pie charts etc. to reporting. Patch by gerv; 2xr=joel. --- report.cgi | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 141 insertions(+), 21 deletions(-) (limited to 'report.cgi') diff --git a/report.cgi b/report.cgi index f4cb74dad..9543bc5b7 100755 --- a/report.cgi +++ b/report.cgi @@ -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 "
data hash:\n";
+    print Dumper(%data) . "\n\n";
+    print "data array:\n";
+    print Dumper(@image_data) . "\n\n
"; +} + $template->process("$format->{'template'}", $vars) || ThrowTemplateError($template->error()); -- cgit v1.2.3-24-g4f1b