diff options
Diffstat (limited to 'extensions/Review')
8 files changed, 302 insertions, 12 deletions
diff --git a/extensions/Review/Extension.pm b/extensions/Review/Extension.pm index cc0f0f3de..b00fc4e69 100644 --- a/extensions/Review/Extension.pm +++ b/extensions/Review/Extension.pm @@ -15,6 +15,8 @@ our $VERSION = '1'; use Bugzilla; use Bugzilla::Constants; use Bugzilla::Error; +use Bugzilla::Extension::Review::Util; +use Bugzilla::Install::Filesystem; use Bugzilla::User; use Bugzilla::Util qw(clean_text); @@ -35,7 +37,7 @@ BEGIN { } # -# reviewers +# monkey-patched methods # sub _product_reviewers { _reviewers($_[0], 'product', $_[1]) } @@ -124,6 +126,9 @@ sub object_columns { if ($class->isa('Bugzilla::Product')) { push @$columns, 'reviewer_required'; } + elsif ($class->isa('Bugzilla::User')) { + push @$columns, qw(review_request_count feedback_request_count needinfo_request_count); + } } sub object_update_columns { @@ -132,6 +137,9 @@ sub object_update_columns { if ($object->isa('Bugzilla::Product')) { push @$columns, 'reviewer_required'; } + elsif ($object->isa('Bugzilla::User')) { + push @$columns, qw(review_request_count feedback_request_count needinfo_request_count); + } } # @@ -157,23 +165,91 @@ sub object_end_of_set_all { sub object_end_of_create { my ($self, $args) = @_; my ($object, $params) = @$args{qw(object params)}; - return unless $object->isa('Bugzilla::Product') || $object->isa('Bugzilla::Component');; - my ($new, $new_users) = _new_reviewers_from_input(); - _update_reviewers($object, [], $new_users); + if ($object->isa('Bugzilla::Product') || $object->isa('Bugzilla::Component')) { + my ($new, $new_users) = _new_reviewers_from_input(); + _update_reviewers($object, [], $new_users); + } + elsif (_is_countable_flag($object) && $object->requestee_id && $object->status eq '?') { + _adjust_request_count($object, +1); + } } sub object_end_of_update { my ($self, $args) = @_; my ($object, $old_object, $changes) = @$args{qw(object old_object changes)}; - return unless $object->isa('Bugzilla::Product') || $object->isa('Bugzilla::Component');; - my ($new, $new_users) = _new_reviewers_from_input(); - my $old = $old_object->reviewers(1); - if ($old ne $new) { - _update_reviewers($object, $old_object->reviewers_objs(1), $new_users); - $changes->{reviewers} = [ $old ? $old : undef, $new ? $new : undef ]; + if ($object->isa('Bugzilla::Product') || $object->isa('Bugzilla::Component')) { + my ($new, $new_users) = _new_reviewers_from_input(); + my $old = $old_object->reviewers(1); + if ($old ne $new) { + _update_reviewers($object, $old_object->reviewers_objs(1), $new_users); + $changes->{reviewers} = [ $old ? $old : undef, $new ? $new : undef ]; + } + } + elsif (_is_countable_flag($object)) { + my ($old_status, $new_status) = ($old_object->status, $object->status); + if ($old_status ne '?' && $new_status eq '?') { + # setting flag to ? + _adjust_request_count($object, +1); + } + elsif ($old_status eq '?' && $new_status ne '?') { + # setting flag from ? + _adjust_request_count($old_object, -1); + } + elsif ($old_object->requestee_id && !$object->requestee_id) { + # removing requestee + _adjust_request_count($old_object, -1); + } + elsif (!$old_object->requestee_id && $object->requestee_id) { + # setting requestee + _adjust_request_count($object, +1); + } + elsif ($old_object->requestee_id && $object->requestee_id + && $old_object->requestee_id != $object->requestee_id) + { + # changing requestee + _adjust_request_count($old_object, -1); + _adjust_request_count($object, +1); + } + } +} + +sub object_before_delete { + my ($self, $args) = @_; + my $object = $args->{object}; + + if (_is_countable_flag($object) && $object->requestee_id && $object->status eq '?') { + _adjust_request_count($object, -1); + } +} + +sub _is_countable_flag { + my ($object) = @_; + return unless $object->isa('Bugzilla::Flag'); + my $type_name = $object->type->name; + return $type_name eq 'review' || $type_name eq 'feedback' || $type_name eq 'needinfo'; +} + +sub _adjust_request_count { + my ($flag, $add) = @_; + return unless my $requestee_id = $flag->requestee_id; + my $field = $flag->type->name . '_request_count'; + + # update the current user's object so things are display correctly on the + # post-processing page + my $user = Bugzilla->user; + if ($requestee_id == $user->id) { + $user->{$field} += $add; } + + # update database directly to avoid creating audit_log entries + $add = $add == -1 ? ' - 1' : ' + 1'; + Bugzilla->dbh->do( + "UPDATE profiles SET $field = $field $add WHERE userid = ?", + undef, + $requestee_id + ); } sub _new_reviewers_from_input { @@ -295,7 +371,7 @@ sub flag_end_of_update { } # -# web service / reports +# web service / pages # sub webservice { @@ -306,7 +382,18 @@ sub webservice { sub page_before_template { my ($self, $args) = @_; - return unless $args->{page_id} eq 'review_suggestions.html'; + + if ($args->{page_id} eq 'review_suggestions.html') { + $self->review_suggestions_report($args); + } + elsif ($args->{page_id} eq 'review_requests_rebuild.html') { + $self->review_requests_rebuild($args); + } +} + +sub review_suggestions_report { + my ($self, $args) = @_; + my $user = Bugzilla->login(LOGIN_REQUIRED); my $products = []; my @products = sort { lc($a->name) cmp lc($b->name) } @@ -338,6 +425,24 @@ sub page_before_template { $args->{vars}->{products} = $products; } +sub review_requests_rebuild { + my ($self, $args) = @_; + + Bugzilla->user->in_group('admin') + || ThrowUserError('auth_failure', { group => 'admin', + action => 'run', + object => 'review_requests_rebuild' }); + if (Bugzilla->cgi->param('rebuild')) { + my $processed_users = 0; + rebuild_review_counters(sub { + my ($count, $total) = @_; + $processed_users = $total; + }); + $args->{vars}->{rebuild} = 1; + $args->{vars}->{total} = $processed_users; + } +} + # # installation # @@ -449,4 +554,14 @@ sub install_update_db { ); } +sub install_filesystem { + my ($self, $args) = @_; + my $files = $args->{files}; + my $extensions_dir = bz_locations()->{extensionsdir}; + $files->{"$extensions_dir/Review/bin/review_requests_rebuild.pl"} = { + perms => Bugzilla::Install::Filesystem::OWNER_EXECUTE + }; +} + + __PACKAGE__->NAME; diff --git a/extensions/Review/bin/review_requests_rebuild.pl b/extensions/Review/bin/review_requests_rebuild.pl new file mode 100755 index 000000000..04f8b1042 --- /dev/null +++ b/extensions/Review/bin/review_requests_rebuild.pl @@ -0,0 +1,29 @@ +#!/usr/bin/perl + +# 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; +use warnings; +$| = 1; + +use FindBin qw($Bin); +use lib "$Bin/../../.."; + +use Bugzilla; +BEGIN { Bugzilla->extensions() } + +use Bugzilla::Constants; +use Bugzilla::Install::Util qw(indicate_progress); +use Bugzilla::Extension::Review::Util; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +rebuild_review_counters(sub{ + my ($count, $total) = @_; + indicate_progress({ current => $count, total => $total, every => 5 }); +}); diff --git a/extensions/Review/lib/Util.pm b/extensions/Review/lib/Util.pm new file mode 100644 index 000000000..7304f9ba6 --- /dev/null +++ b/extensions/Review/lib/Util.pm @@ -0,0 +1,63 @@ +# 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. + +package Bugzilla::Extension::Review::Util; +use strict; +use warnings; + +use base qw(Exporter); +use Bugzilla; + +our @EXPORT = qw( rebuild_review_counters ); + +sub rebuild_review_counters { + my ($callback) = @_; + my $dbh = Bugzilla->dbh; + + $dbh->bz_start_transaction; + my $rows = $dbh->selectall_arrayref(" + SELECT flags.requestee_id AS user_id, + flagtypes.name AS flagtype, + COUNT(*) as count + FROM flags + INNER JOIN profiles ON profiles.userid = flags.requestee_id + INNER JOIN flagtypes ON flagtypes.id = flags.type_id + WHERE flags.status = '?' + AND flagtypes.name IN ('review', 'feedback', 'needinfo') + GROUP BY flags.requestee_id, flagtypes.name + ", { Slice => {} }); + + my ($count, $total, $current) = (1, scalar(@$rows), { id => 0 }); + foreach my $row (@$rows) { + $callback->($count++, $total) if $callback; + if ($row->{user_id} != $current->{id}) { + _update_profile($dbh, $current) if $current->{id}; + $current = { id => $row->{user_id} }; + } + $current->{$row->{flagtype}} = $row->{count}; + } + _update_profile($dbh, $current) if $current->{id}; + $dbh->bz_commit_transaction; +} + +sub _update_profile { + my ($dbh, $data) = @_; + $dbh->do(" + UPDATE profiles + SET review_request_count = ?, + feedback_request_count = ?, + needinfo_request_count = ? + WHERE userid = ?", + undef, + $data->{review} || 0, + $data->{feedback} || 0, + $data->{needinfo} || 0, + $data->{id} + ); +} + +1; diff --git a/extensions/Review/template/en/default/hook/global/header-message.html.tmpl b/extensions/Review/template/en/default/hook/global/header-message.html.tmpl new file mode 100644 index 000000000..33e899492 --- /dev/null +++ b/extensions/Review/template/en/default/hook/global/header-message.html.tmpl @@ -0,0 +1,23 @@ +[%# 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. + #%] + +[% RETURN UNLESS + user.review_request_count + || user.feedback_request_count + || user.needinfo_request_count +%] + +<span id="badge" title="Flags requested of you: + [%- " review (" _ user.review_request_count _ ")" IF user.review_request_count -%] + [%- " feedback (" _ user.feedback_request_count _ ")" IF user.feedback_request_count -%] + [%- " needinfo (" _ user.needinfo_request_count _ ")" IF user.needinfo_request_count -%] +"> + <a href="request.cgi?action=queue&requestee=[% user.login FILTER uri %]&group=type"> + [%- user.review_request_count + user.feedback_request_count + user.needinfo_request_count ~%] + </a> +</span> diff --git a/extensions/Review/template/en/default/hook/global/header-start.html.tmpl b/extensions/Review/template/en/default/hook/global/header-start.html.tmpl index e086e9b55..585041e99 100644 --- a/extensions/Review/template/en/default/hook/global/header-start.html.tmpl +++ b/extensions/Review/template/en/default/hook/global/header-start.html.tmpl @@ -6,6 +6,13 @@ # defined by the Mozilla Public License, v. 2.0. #%] +[% IF user.review_request_count + || user.feedback_request_count + || user.needinfo_request_count +%] + [% style_urls.push('extensions/Review/web/styles/badge.css') %] +[% END %] + [% RETURN UNLESS template.name == 'attachment/edit.html.tmpl' || template.name == 'attachment/create.html.tmpl' || template.name == 'attachment/diff-header.html.tmpl' diff --git a/extensions/Review/template/en/default/hook/global/user-error-auth_failure_object.html.tmpl b/extensions/Review/template/en/default/hook/global/user-error-auth_failure_object.html.tmpl new file mode 100644 index 000000000..156f0aa93 --- /dev/null +++ b/extensions/Review/template/en/default/hook/global/user-error-auth_failure_object.html.tmpl @@ -0,0 +1,11 @@ +[%# 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. + #%] + +[% IF object == 'review_requests_rebuild' %] + rebuild review request counters +[% END %] diff --git a/extensions/Review/template/en/default/pages/review_requests_rebuild.html.tmpl b/extensions/Review/template/en/default/pages/review_requests_rebuild.html.tmpl new file mode 100644 index 000000000..5ec811126 --- /dev/null +++ b/extensions/Review/template/en/default/pages/review_requests_rebuild.html.tmpl @@ -0,0 +1,23 @@ +[%# 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. + #%] + +[% INCLUDE global/header.html.tmpl + title = "Review Requests Rebuild" +%] + +[% IF rebuild %] + Counters rebuilt for [% total FILTER html %] users. +[% ELSE %] + <form method="post"> + <input type="hidden" name="id" value="review_requests_rebuild.html"> + <input type="hidden" name="rebuild" value="1"> + <input type="submit" value="Rebuild Review Request Counters"> + </form> +[% END %] + +[% INCLUDE global/footer.html.tmpl %] diff --git a/extensions/Review/web/styles/badge.css b/extensions/Review/web/styles/badge.css new file mode 100644 index 000000000..8b0791f12 --- /dev/null +++ b/extensions/Review/web/styles/badge.css @@ -0,0 +1,19 @@ +/* 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. */ + +#badge { + background-color: #c00; + font-size: small; + font-weight: bold; + padding: 0 5px; + border-radius: 10px; + margin: 0 5px; +} + +#badge a { + color: #fff !important; +} |