diff options
author | gerv%gerv.net <> | 2003-06-26 08:22:50 +0200 |
---|---|---|
committer | gerv%gerv.net <> | 2003-06-26 08:22:50 +0200 |
commit | 4f6b75a65628b0d86c760309dd81dd03f5c6d308 (patch) | |
tree | 5363459b06e75bc620ceab9dfd045b0f66b6c27c /chart.cgi | |
parent | da6143f4aae6af35f60b8230b82f649b3b0cbd05 (diff) | |
download | bugzilla-4f6b75a65628b0d86c760309dd81dd03f5c6d308.tar.gz bugzilla-4f6b75a65628b0d86c760309dd81dd03f5c6d308.tar.xz |
Bug 16009 - generic charting. Patch by gerv; r,a=justdave.
Diffstat (limited to 'chart.cgi')
-rwxr-xr-x | chart.cgi | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/chart.cgi b/chart.cgi new file mode 100755 index 000000000..ceaecbbab --- /dev/null +++ b/chart.cgi @@ -0,0 +1,312 @@ +#!/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> + +# Glossary: +# series: An individual, defined set of data plotted over time. +# line: A set of one or more series, to be summed and drawn as a single +# line when the series is plotted. +# chart: A set of lines +# So when you select rows in the UI, you are selecting one or more lines, not +# series. + +# Generic Charting TODO: +# +# JS-less chart creation - hard. +# Broken image on error or no data - need to do much better. +# Centralise permission checking, so UserInGroup('editbugs') not scattered +# everywhere. +# Better protection on collectstats.pl for second run in a day +# User documentation :-) +# +# Bonus: +# Offer subscription when you get a "series already exists" error? + +use strict; +use lib qw(.); + +require "CGI.pl"; +use Bugzilla::Chart; +use Bugzilla::Series; + +use vars qw($cgi $template $vars); + +# Go back to query.cgi if we are adding a boolean chart parameter. +if (grep(/^cmd-/, $cgi->param())) { + my $params = $cgi->canonicalise_query("format", "ctype", "action"); + print "Location: query.cgi?format=" . $cgi->param('query_format') . + ($params ? "&$params" : "") . "\n\n"; + exit; +} + +my $template = Bugzilla->template; +my $action = $cgi->param('action'); +my $series_id = $cgi->param('series_id'); + +# Because some actions are chosen by buttons, we can't encode them as the value +# of the action param, because that value is localisation-dependent. So, we +# encode it in the name, as "action-<action>". Some params even contain the +# series_id they apply to (e.g. subscribe, unsubscribe.) +my @actions = grep(/^action-/, $cgi->param()); +if ($actions[0] && $actions[0] =~ /^action-([^\d]+)(\d*)$/) { + $action = $1; + $series_id = $2 if $2; +} + +$action ||= "assemble"; + +# Go to buglist.cgi if we are doing a search. +if ($action eq "search") { + my $params = $cgi->canonicalise_query("format", "ctype", "action"); + print "Location: buglist.cgi" . ($params ? "?$params" : "") . "\n\n"; + exit; +} + +ConnectToDatabase(); + +confirm_login(); + +# All these actions relate to chart construction. +if ($action =~ /^(assemble|add|remove|sum|subscribe|unsubscribe)$/) { + # These two need to be done before the creation of the Chart object, so + # that the changes they make will be reflected in it. + if ($action =~ /^subscribe|unsubscribe$/) { + my $series = new Bugzilla::Series($series_id); + $series->$action($::userid); + } + + my $chart = new Bugzilla::Chart($cgi); + + if ($action =~ /^remove|sum$/) { + $chart->$action(getSelectedLines()); + } + elsif ($action eq "add") { + my @series_ids = getAndValidateSeriesIDs(); + $chart->add(@series_ids); + } + + view($chart); +} +elsif ($action eq "plot") { + plot(); +} +elsif ($action eq "wrap") { + # For CSV "wrap", we go straight to "plot". + if ($cgi->param('ctype') && $cgi->param('ctype') eq "csv") { + plot(); + } + else { + wrap(); + } +} +elsif ($action eq "create") { + assertCanCreate($cgi); + my $series = new Bugzilla::Series($cgi); + + if (ref($series)) { + $vars->{'message'} = "series_created"; + } + else { + $vars->{'message'} = "series_already_exists"; + $series = new Bugzilla::Series($series); + } + + $vars->{'series'} = $series; + + print "Content-Type: text/html\n\n"; + $template->process("global/message.html.tmpl", $vars) + || ThrowTemplateError($template->error()); +} +elsif ($action eq "edit") { + $series_id || ThrowCodeError("invalid_series_id"); + assertCanEdit($series_id); + + my $series = new Bugzilla::Series($series_id); + edit($series); +} +elsif ($action eq "alter") { + $series_id || ThrowCodeError("invalid_series_id"); + assertCanEdit($series_id); + + my $series = new Bugzilla::Series($series_id); + $series->alter($cgi); + edit($series); +} +else { + ThrowCodeError("unknown_action"); +} + +exit; + +# Find any selected series and return either the first or all of them. +sub getAndValidateSeriesIDs { + my @series_ids = grep(/^\d+$/, $cgi->param("name")); + + return wantarray ? @series_ids : $series_ids[0]; +} + +# Return a list of IDs of all the lines selected in the UI. +sub getSelectedLines { + my @ids = map { /^select(\d+)$/ ? $1 : () } $cgi->param(); + + return @ids; +} + +# Check if the user is the owner of series_id or is an admin. +sub assertCanEdit { + my ($series_id) = @_; + + return if UserInGroup("admin"); + + my $dbh = Bugzilla->dbh; + my $iscreator = $dbh->selectrow_array("SELECT creator = ? FROM series " . + "WHERE series_id = ?", undef, + $::userid, $series_id); + $iscreator || ThrowUserError("illegal_series_edit"); +} + +# Check if the user is permitted to create this series with these parameters. +sub assertCanCreate { + my ($cgi) = shift; + + UserInGroup("editbugs") || ThrowUserError("illegal_series_creation"); + + # Only admins may create public queries + UserInGroup('admin') || $cgi->delete('public'); + + # Check permission for frequency + my $min_freq = 7; + if ($cgi->param('frequency') < $min_freq && !UserInGroup("admin")) { + ThrowUserError("illegal_frequency", { 'minimum' => $min_freq }); + } +} + +sub validateWidthAndHeight { + $vars->{'width'} = $cgi->param('width'); + $vars->{'height'} = $cgi->param('height'); + + if (defined($vars->{'width'})) { + (detaint_natural($vars->{'width'}) && $vars->{'width'} > 0) + || ThrowCodeError("invalid_dimensions"); + } + + if (defined($vars->{'height'})) { + (detaint_natural($vars->{'height'}) && $vars->{'height'} > 0) + || ThrowCodeError("invalid_dimensions"); + } + + # The equivalent of 2000 square seems like a very reasonable maximum size. + # This is merely meant to prevent accidental or deliberate DOS, and should + # have no effect in practice. + if ($vars->{'width'} && $vars->{'height'}) { + (($vars->{'width'} * $vars->{'height'}) <= 4000000) + || ThrowUserError("chart_too_large"); + } +} + +sub edit { + my $series = shift; + + $vars->{'category'} = Bugzilla::Chart::getVisibleSeries(); + $vars->{'creator'} = new Bugzilla::User($series->{'creator'}); + + # If we've got any parameters, use those in preference to the values + # read from the database. This is a bit ugly, but I can't see a better + # way to make this work in the no-JS situation. + if ($cgi->param('category') || $cgi->param('subcategory') || + $cgi->param('name') || $cgi->param('frequency') || + $cgi->param('public')) + { + $vars->{'default'} = new Bugzilla::Series($series->{'series_id'}, + $cgi->param('category') || $series->{'category'}, + $cgi->param('subcategory') || $series->{'subcategory'}, + $cgi->param('name') || $series->{'name'}, + $series->{'creator'}, + $cgi->param('frequency') || $series->{'frequency'}); + + $vars->{'default'}{'public'} + = $cgi->param('public') || $series->{'public'}; + } + else { + $vars->{'default'} = $series; + } + + print "Content-Type: text/html\n\n"; + $template->process("reports/edit-series.html.tmpl", $vars) + || ThrowTemplateError($template->error()); +} + +sub plot { + validateWidthAndHeight(); + $vars->{'chart'} = new Bugzilla::Chart($cgi); + + my $format = &::GetFormat("reports/chart", + "", + $cgi->param('ctype')); + + # Debugging PNGs is a pain; we need to be able to see the error messages + if ($cgi->param('debug')) { + print "Content-Type: text/html\n\n"; + $vars->{'chart'}->dump(); + } + + print "Content-Type: $format->{'ctype'}\n\n"; + $template->process($format->{'template'}, $vars) + || ThrowTemplateError($template->error()); +} + +sub wrap { + validateWidthAndHeight(); + + # We create a Chart object so we can validate the parameters + my $chart = new Bugzilla::Chart($cgi); + + $vars->{'time'} = time(); + + $vars->{'imagebase'} = $cgi->canonicalise_query( + "action", "action-wrap", "ctype", "format", "width", "height"); + + print "Content-Type:text/html\n\n"; + $template->process("reports/chart.html.tmpl", $vars) + || ThrowTemplateError($template->error()); +} + +sub view { + my $chart = shift; + + # Set defaults + foreach my $field ('category', 'subcategory', 'name', 'ctype') { + $vars->{'default'}{$field} = $cgi->param($field) || 0; + } + + # Pass the state object to the display UI. + $vars->{'chart'} = $chart; + $vars->{'category'} = Bugzilla::Chart::getVisibleSeries(); + + print "Content-Type: text/html\n\n"; + + # If we have having problems with bad data, we can set debug=1 to dump + # the data structure. + $chart->dump() if $cgi->param('debug'); + + $template->process("reports/create-chart.html.tmpl", $vars) + || ThrowTemplateError($template->error()); +} |