diff options
Diffstat (limited to 'extensions/Review/lib/WebService.pm')
-rw-r--r-- | extensions/Review/lib/WebService.pm | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/extensions/Review/lib/WebService.pm b/extensions/Review/lib/WebService.pm new file mode 100644 index 000000000..f5530dd49 --- /dev/null +++ b/extensions/Review/lib/WebService.pm @@ -0,0 +1,446 @@ +# 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::WebService; + +use strict; +use warnings; + +use base qw(Bugzilla::WebService); + +use Bugzilla::Bug; +use Bugzilla::Component; +use Bugzilla::Error; +use Bugzilla::Util qw(detaint_natural); +use Bugzilla::WebService::Util 'filter'; + +sub suggestions { + my ($self, $params) = @_; + my $dbh = Bugzilla->switch_to_shadow_db(); + + my ($bug, $product, $component); + if (exists $params->{bug_id}) { + $bug = Bugzilla::Bug->check($params->{bug_id}); + $product = $bug->product_obj; + $component = $bug->component_obj; + } + elsif (exists $params->{product}) { + $product = Bugzilla::Product->check($params->{product}); + if (exists $params->{component}) { + $component = Bugzilla::Component->check({ + product => $product, name => $params->{component} + }); + } + } + else { + ThrowUserError("reviewer_suggestions_param_required"); + } + + my @reviewers; + if ($bug) { + # we always need to be authentiated to perform user matching + my $user = Bugzilla->user; + if (!$user->id) { + Bugzilla->set_user(Bugzilla::User->check({ name => 'nobody@mozilla.org' })); + push @reviewers, @{ $bug->mentors }; + Bugzilla->set_user($user); + } else { + push @reviewers, @{ $bug->mentors }; + } + } + if ($component) { + push @reviewers, @{ $component->reviewers_objs }; + } + if (!@{ $component->reviewers_objs }) { + push @reviewers, @{ $product->reviewers_objs }; + } + + my @result; + foreach my $reviewer (@reviewers) { + push @result, { + id => $self->type('int', $reviewer->id), + email => $self->type('email', $reviewer->login), + name => $self->type('string', $reviewer->name), + review_count => $self->type('int', $reviewer->review_count), + }; + } + return \@result; +} + +sub flag_activity { + my ($self, $params) = @_; + my $dbh = Bugzilla->switch_to_shadow_db(); + my %match_criteria; + + if (my $flag_id = $params->{flag_id}) { + detaint_natural($flag_id) + or ThrowUserError('invalid_flag_id', { flag_id => $flag_id }); + + $match_criteria{flag_id} = $flag_id; + } + + if (my $type_id = $params->{type_id}) { + detaint_natural($type_id) + or ThrowUserError('invalid_flag_type_id', { type_id => $type_id }); + + $match_criteria{type_id} = $type_id; + } + + for my $user_field (qw( requestee setter )) { + if (my $user_name = $params->{$user_field}) { + my $user = Bugzilla::User->check({ name => $user_name, cache => 1, _error => 'invalid_username' }); + + $match_criteria{ $user_field . "_id" } = $user->id; + } + } + + ThrowCodeError('param_required', { param => 'limit', function => 'Review.flag_activity()' }) + if defined $params->{offset} && !defined $params->{limit}; + + my $limit = delete $params->{limit}; + my $offset = delete $params->{offset}; + my $max_results = Bugzilla->params->{max_search_results}; + + if (!$limit || $limit > $max_results) { + $limit = $max_results; + } + + $match_criteria{LIMIT} = $limit; + $match_criteria{OFFSET} = $offset if defined $offset; + + # Throw error if no other parameters have been passed other than limit and offset + if (!grep(!/^(LIMIT|OFFSET)$/, keys %match_criteria)) { + ThrowUserError('flag_activity_parameters_required'); + } + + my $matches = Bugzilla::Extension::Review::FlagStateActivity->match(\%match_criteria); + my @results = map { $self->_flag_state_activity_to_hash($_, $params) } @$matches; + return \@results; +} + +sub rest_resources { + return [ + # bug-id + qr{^/review/suggestions/(\d+)$}, { + GET => { + method => 'suggestions', + params => sub { + return { bug_id => $_[0] }; + }, + }, + }, + # product/component + qr{^/review/suggestions/([^/]+)/(.+)$}, { + GET => { + method => 'suggestions', + params => sub { + return { product => $_[0], component => $_[1] }; + }, + }, + }, + # just product + qr{^/review/suggestions/([^/]+)$}, { + GET => { + method => 'suggestions', + params => sub { + return { product => $_[0] }; + }, + }, + }, + # named parameters + qr{^/review/suggestions$}, { + GET => { + method => 'suggestions', + }, + }, + # flag activity by flag id + qr{^/review/flag_activity/(\d+)$}, { + GET => { + method => 'flag_activity', + params => sub { + return { flag_id => $_[0] } + }, + }, + }, + # flag activity by user + qr{^/review/flag_activity/(requestee|setter|type_id)/(.*)$}, { + GET => { + method => 'flag_activity', + params => sub { + return { $_[0] => $_[1] }; + }, + }, + }, + # flag activity with only query strings + qr{^/review/flag_activity$}, { + GET => { method => 'flag_activity' }, + }, + ]; +} + +sub _flag_state_activity_to_hash { + my ($self, $fsa, $params) = @_; + + my %flag = ( + creation_time => $self->type('string', $fsa->flag_when), + type => $self->_flagtype_to_hash($fsa->type), + setter => $self->_user_to_hash($fsa->setter), + bug_id => $self->type('int', $fsa->bug_id), + attachment_id => $self->type('int', $fsa->attachment_id), + status => $self->type('string', $fsa->status), + ); + + $flag{requestee} = $self->_user_to_hash($fsa->requestee) if $fsa->requestee; + $flag{flag_id} = $self->type('int', $fsa->flag_id) unless $params->{flag_id}; + + return filter($params, \%flag); +} + +sub _flagtype_to_hash { + my ($self, $flagtype) = @_; + my $user = Bugzilla->user; + + return { + id => $self->type('int', $flagtype->id), + name => $self->type('string', $flagtype->name), + description => $self->type('string', $flagtype->description), + type => $self->type('string', $flagtype->target_type), + is_active => $self->type('boolean', $flagtype->is_active), + is_requesteeble => $self->type('boolean', $flagtype->is_requesteeble), + is_multiplicable => $self->type('boolean', $flagtype->is_multiplicable), + }; +} + +sub _user_to_hash { + my ($self, $user) = @_; + + return { + id => $self->type('int', $user->id), + real_name => $self->type('string', $user->name), + name => $self->type('email', $user->login), + }; +} + +1; +__END__ +=head1 NAME + +Bugzilla::Extension::Review::WebService - Functions for the Mozilla specific +'review' flag optimisations. + +=head1 METHODS + +See L<Bugzilla::WebService> for a description of how parameters are passed, +and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean. + +Although the data input and output is the same for JSONRPC, XMLRPC and REST, +the directions for how to access the data via REST is noted in each method +where applicable. + +=head2 suggestions + +B<EXPERIMENTAL> + +=over + +=item B<Description> + +Returns the list of suggestions for reviewers. + +=item B<REST> + +GET /rest/review/suggestions/C<bug-id> + +GET /rest/review/suggestions/C<product-name> + +GET /rest/review/suggestions/C<product-name>/C<component-name> + +GET /rest/review/suggestions?product=C<product-name> + +GET /rest/review/suggestions?product=C<product-name>&component=C<component-name> + +The returned data format is the same as below. + +=item B<Params> + +Query by Bug: + +=over + +=over + +=item C<bug_id> (integer) - The bug ID. + +=back + +=back + +Query by Product or Component: + +=over + +=over + +=item C<product> (string) - The product name. + +=item C<component> (string) - The component name (optional). If providing a C<component>, a C<product> must also be provided. + +=back + +=back + +=item B<Returns> + +An array of hashes with the following keys/values: + +=over + +=item C<id> (integer) - The user's ID. + +=item C<email> (string) - The user's email address (aka login). + +=item C<name> (string) - The user's display name (may not match the Bugzilla "real name"). + +=item C<review_count> (string) - The number of "review" and "feedback" requests in the user's queue. + +=back + +=back + +=head2 flag_activity + +B<EXPERIMENTAL> + +=over + +=item B<Description> + +Returns the history of flag status changes based on requestee, setter, flag_id, type_id, or all. + +=item B<REST> + +GET /rest/review/flag_activity/C<flag-id> + +GET /rest/review/flag_activity/requestee/C<requestee> + +GET /rest/review/flag_activity/setter/C<setter> + +GET /rest/review/flag_activity/type_id/C<type-id> + +GET /rest/review/flag_activity + +The returned data format is the same as below. + +=item B<Params> + +Use one or more of the following parameters to find specific flag status changes. + +=over + +=item C<flag_id> (integer) - The flag ID. + +Note that searching by C<flag_id> is not reliable because when flags are removed, flag_ids cease to exist. + +=item C<requestee> (string) - The bugzilla login of the flag's requestee + +=item C<setter> (string) - The bugzilla login of the flag's setter + +=item C<type_id> (int) - The flag type id of a change + +=back + +=item B<Returns> + +An array of hashes with the following keys/values: + +=over + +=item C<flag_id> (integer) + +The id of the flag that changed. This field may be absent after a flag is deleted. + +=item C<creation_time> (dateTime) + +Timestamp of when the flag status changed. + +=item C<type> (object) + +An object with the following fields: + +=over + +=item C<id> (integer) + +The flag type id of the flag that changed + +=item C<name> (string) + +The name of the flag type (review, feedback, etc) + +=item C<description> (string) + +A plain english description of the flag type. + +=item C<type> (string) + +The content of the target_type field of the flagtypes table. + +=item C<is_active> (boolean) + +Boolean flag indicating if the flag type is available for use. + +=item C<is_requesteeble> (boolean) + +Boolean flag indicating if the flag type is requesteeable. + +=item C<is_multiplicable> (boolean) + +Boolean flag indicating if the flag type is multiplicable. + +=back + +=item C<setter> (object) + +The setter is the bugzilla user that set the flag. It is represented by an object with the following fields. + +=over + +=item C<id> (integer) + +The id of the bugzilla user. A unique integer value. + +=item C<real_name> (string) + +The real name of the bugzilla user. + +=item C<name> (string) + +The bugzilla login of the bugzilla user (typically an email address). + +=back + +=item C<requestee> (object) + +The requestee is the bugzilla user that is specified by the flag. Optional - absent if there is no requestee. + +Requestee has the same keys/values as the setter object. + +=item C<bug_id> (integer) + +The id of the bugzilla bug that the changed flag belongs to. + +=item C<attachment_id> (integer) + +The id of the bugzilla attachment that the changed flag belongs to. + +=item C<status> (string) + +The status of the bugzilla flag that changed. One of C<+ - ? X>. + +=back + +=back |