Commit 14d7441b authored by Julien Heyman's avatar Julien Heyman Committed by Frédéric Buclin

Bug 319598: Add support for saved tabular and graphical reports

r/a=LpSolit
parent eb04bb66
......@@ -1026,6 +1026,23 @@ use constant ABSTRACT_SCHEMA => {
],
},
reports => {
FIELDS => [
id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
user_id => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
query => {TYPE => 'LONGTEXT', NOTNULL => 1},
],
INDEXES => [
reports_user_id_idx => {FIELDS => [qw(user_id name)],
TYPE => 'UNIQUE'},
],
},
component_cc => {
FIELDS => [
......
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use strict;
package Bugzilla::Report;
use base qw(Bugzilla::Object);
use Bugzilla::CGI;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
use constant DB_TABLE => 'reports';
# Do not track reports saved by users.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
id
user_id
name
query
);
use constant UPDATE_COLUMNS => qw(
name
query
);
use constant VALIDATORS => {
name => \&_check_name,
query => \&_check_query,
};
##############
# Validators #
##############
sub _check_name {
my ($invocant, $name) = @_;
$name = clean_text($name);
$name || ThrowUserError("report_name_missing");
$name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
if (length($name) > MAX_LEN_QUERY_NAME) {
ThrowUserError("query_name_too_long");
}
return $name;
}
sub _check_query {
my ($invocant, $query) = @_;
$query || ThrowUserError("buglist_parameters_required");
my $cgi = new Bugzilla::CGI($query);
$cgi->clean_search_url;
return $cgi->query_string;
}
#############
# Accessors #
#############
sub query { return $_[0]->{'query'}; }
sub set_name { $_[0]->set('name', $_[1]); }
sub set_query { $_[0]->set('query', $_[1]); }
###########
# Methods #
###########
sub create {
my $class = shift;
my $param = shift;
Bugzilla->login(LOGIN_REQUIRED);
$param->{'user_id'} = Bugzilla->user->id;
unshift @_, $param;
my $self = $class->SUPER::create(@_);
}
sub check {
my $class = shift;
my $report = $class->SUPER::check(@_);
my $user = Bugzilla->user;
if ( grep($_->id eq $report->id, @{$user->reports})) {
return $report;
} else {
ThrowUserError('report_access_denied');
}
}
1;
__END__
=head1 NAME
Bugzilla::Report - Bugzilla report class.
=head1 SYNOPSIS
use Bugzilla::Report;
my $report = new Bugzilla::Report(1);
my $report = Bugzilla::Report->check({id => $id});
my $name = $report->name;
my $query = $report->query;
my $report = Bugzilla::Report->create({ name => $name, query => $query });
$report->set_name($new_name);
$report->set_query($new_query);
$report->update();
$report->remove_from_db;
=head1 DESCRIPTION
Report.pm represents a Report object. It is an implementation
of L<Bugzilla::Object>, and thus provides all methods that
L<Bugzilla::Object> provides.
=cut
......@@ -578,6 +578,25 @@ sub save_last_search {
return $search;
}
sub reports {
my $self = shift;
return $self->{reports} if defined $self->{reports};
return [] unless $self->id;
my $dbh = Bugzilla->dbh;
my $report_ids = $dbh->selectcol_arrayref(
'SELECT id FROM reports WHERE user_id = ?', undef, $self->id);
require Bugzilla::Report;
$self->{reports} = Bugzilla::Report->new_from_list($report_ids);
return $self->{reports};
}
sub flush_reports_cache {
my $self = shift;
delete $self->{reports};
}
sub settings {
my ($self) = @_;
......@@ -2275,6 +2294,17 @@ Should only be called by C<Bugzilla::Auth::login>, for the most part.
Returns the disable text of the user, if any.
=item C<reports>
Returns an arrayref of the user's own saved reports. The array contains
L<Bugzilla::Reports> objects.
=item C<flush_reports_cache>
Some code modifies the set of stored reports. Because C<Bugzilla::User> does
not handle these modifications, but does cache the result of calling C<reports>
internally, such code must call this method to flush the cached result.
=item C<settings>
Returns a hash of hashes which holds the user's settings. The first key is
......
......@@ -15,6 +15,8 @@ use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Field;
use Bugzilla::Search;
use Bugzilla::Report;
use Bugzilla::Token;
use List::MoreUtils qw(uniq);
......@@ -34,6 +36,7 @@ if (grep(/^cmd-/, $cgi->param())) {
Bugzilla->login();
my $action = $cgi->param('action') || 'menu';
my $token = $cgi->param('token');
if ($action eq "menu") {
# No need to do any searching in this case, so bail out early.
......@@ -41,6 +44,52 @@ if ($action eq "menu") {
$template->process("reports/menu.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
elsif ($action eq 'add') {
my $user = Bugzilla->login(LOGIN_REQUIRED);
check_hash_token($token, ['save_report']);
my $name = clean_text($cgi->param('name'));
my $query = $cgi->param('query');
if (my ($report) = grep{ lc($_->name) eq lc($name) } @{$user->reports}) {
$report->set_query($query);
$report->update;
$vars->{'message'} = "report_updated";
} else {
my $report = Bugzilla::Report->create({name => $name, query => $query});
$vars->{'message'} = "report_created";
}
$user->flush_reports_cache;
print $cgi->header();
$vars->{'reportname'} = $name;
$template->process("global/message.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
elsif ($action eq 'del') {
my $user = Bugzilla->login(LOGIN_REQUIRED);
my $report_id = $cgi->param('saved_report_id');
check_hash_token($token, ['delete_report', $report_id]);
my $report = Bugzilla::Report->check({id => $report_id});
$report->remove_from_db();
$user->flush_reports_cache;
print $cgi->header();
$vars->{'message'} = 'report_deleted';
$vars->{'reportname'} = $report->name;
$template->process("global/message.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
# Sanitize the URL, to make URLs shorter.
......@@ -209,6 +258,7 @@ $vars->{'width'} = $width if $width;
$vars->{'height'} = $height if $height;
$vars->{'query'} = $query;
$vars->{'saved_report_id'} = $cgi->param('saved_report_id');
$vars->{'debug'} = $cgi->param('debug');
my $formatparam = $cgi->param('format');
......
......@@ -795,6 +795,15 @@
or you don't have access to it. The following is a list of the
products you can choose from.
[% ELSIF message_tag == "report_created" %]
OK, you have a new saved report named <em>[% reportname FILTER html %]</em>.
[% ELSIF message_tag == "report_deleted" %]
OK, the <em>[% reportname FILTER html %]</em> report is gone.
[% ELSIF message_tag == "report_updated" %]
The saved report <em>[% reportname FILTER html %]</em> has been updated.
[% ELSIF message_tag == "remaining_time_zeroed" %]
The [% field_descs.remaining_time FILTER html %] field has been
set to zero automatically as part of closing this [% terms.bug %]
......
......@@ -56,7 +56,18 @@
</li>
[% END %]
[%# Individual bugs addition %]
[% IF user.reports.size %]
<li id="reports-saved">
<ul class="links">
[% FOREACH r = user.reports %]
<li>[% '<span class="separator">| </span>' IF print_pipe %]
<a href="report.cgi?[% r.query FILTER html %]&amp;saved_report_id=
[%~ r.id FILTER uri %]">[% r.name FILTER html %]</a></li>
[% print_pipe = 1 %]
[% END %]
</ul>
</li>
[% END %]
[%# Sections of links to more things users can do on this installation. %]
[% Hook.process("end") %]
......
......@@ -1480,6 +1480,14 @@
To reassign [% terms.abug %], you must provide an address for
the new assignee.
[% ELSIF error == "report_name_missing" %]
[% title = "No Report Name Specified" %]
You must enter a name for your report.
[% ELSIF error == "report_access_denied" %]
[% title = "Report Access Denied" %]
You cannot access this report.
[% ELSIF error == "require_component" %]
[% title = "Component Needed" %]
To file this [% terms.bug %], you must first choose a component.
......
......@@ -150,18 +150,35 @@
</tr>
</table>
<p>
[% IF format == "table" %]
<a href="query.cgi?[% switchbase %]&amp;format=report-table">Edit
this report</a>
[% ELSE %]
<a href="query.cgi?[% switchbase %]&amp;chart_format=
[% format %]&amp;format=report-graph&amp;cumulate=[% cumulate %]">
Edit this report
</a>
[% END %]
</p>
<table>
<tr>
<td>
[% IF format == "table" %]
<a href="query.cgi?[% switchbase %]&amp;format=report-table">Edit this report</a>
[% ELSE %]
<a href="query.cgi?[% switchbase %]&amp;chart_format=
[%~ format %]&amp;format=report-graph&amp;cumulate=[% cumulate %]">
Edit this report</a>
[% END %]
</td>
<td>&nbsp;</td>
<td>
[% IF saved_report_id %]
<a href="report.cgi?action=del&amp;saved_report_id=[% saved_report_id FILTER uri %]&amp;token=
[%~ issue_hash_token(['delete_report', saved_report_id]) FILTER uri %]">Forget this report</a>
[% ELSE %]
<form method="get" action="report.cgi">
<input type="submit" id="remember" value="Remember report"> as
<input type="hidden" name="query" value="[% switchbase %]&amp;format=[% format FILTER html %]&amp;action=wrap">
<input type="hidden" name="action" value="add">
<input type="hidden" name="token" value="[% issue_hash_token(['save_report']) FILTER html %]">
<input type="text" id="name" name="name" size="20" value="" maxlength="64">
</form>
[% END %]
</td>
</tr>
</table>
</div>
[% PROCESS global/footer.html.tmpl %]
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