]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 319598: Add support for saved tabular and graphical reports
authorJulien Heyman <jheyman@portaildulibre.fr>
Tue, 7 Aug 2012 21:59:18 +0000 (23:59 +0200)
committerFrédéric Buclin <LpSolit@gmail.com>
Tue, 7 Aug 2012 21:59:18 +0000 (23:59 +0200)
r/a=LpSolit

Bugzilla/DB/Schema.pm
Bugzilla/Report.pm [new file with mode: 0644]
Bugzilla/User.pm
report.cgi
template/en/default/global/messages.html.tmpl
template/en/default/global/useful-links.html.tmpl
template/en/default/global/user-error.html.tmpl
template/en/default/reports/report.html.tmpl

index 156dca37818da879d7e4375084b4ca6903ac6e74..728176684ff2153be15d0c64504fea29fc3894cc 100644 (file)
@@ -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 => [
diff --git a/Bugzilla/Report.pm b/Bugzilla/Report.pm
new file mode 100644 (file)
index 0000000..4c9f332
--- /dev/null
@@ -0,0 +1,134 @@
+# 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
index 9c869dc635dfdb4c35d8b3571cdbcac5c9d2a7fe..708d12c64eef9b22669d4122e513136d385088de 100644 (file)
@@ -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
index 83561fde51f61e19cd276753a8be1cc2fc92307e..5778841b378fe1aa87e9e0600b84f07fbfaf93aa 100755 (executable)
@@ -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');
index d39890c13c8a9b05e9faa5b83dedf5cd95bbd1c5..d93ed537e05c2e68a96f97058a96247003953ffc 100644 (file)
     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 %]
index 1b5ba9a30ff1f99df2f33df60bf1c87dccf6ba69..5959ab65653f4ba786878f6b0f2605355b316106 100644 (file)
     </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") %]
index 0d51eaa1bb1739d153695dd97bf1ef8fba12c595..fbb9ca169eb39f632a049a259b6eff9caa43fa4d 100644 (file)
     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.
index 84cefbded33250d7d335c3be9b93f8fbd0d8837d..76049d04ed1faf536c44b528a4100b5b97a2f268 100644 (file)
     </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 %]