diff options
author | Simon Green <sgreen@redhat.com> | 2014-05-22 01:17:46 +0200 |
---|---|---|
committer | Simon Green <sgreen@redhat.com> | 2014-05-22 01:17:46 +0200 |
commit | 9bd3f08dce23acc052819d97d1f082666c354b20 (patch) | |
tree | 6fee7c37bb9bff84a681593eff2e565e7bdb5f3e /Bugzilla/WebService | |
parent | abf6ec5cff89b9507aa978d5345559239886c014 (diff) | |
download | bugzilla-9bd3f08dce23acc052819d97d1f082666c354b20.tar.gz bugzilla-9bd3f08dce23acc052819d97d1f082666c354b20.tar.xz |
Bug 1008764: Add a web service to create and update Flag types
r=glob, a=justdave
Diffstat (limited to 'Bugzilla/WebService')
-rw-r--r-- | Bugzilla/WebService/Constants.pm | 13 | ||||
-rw-r--r-- | Bugzilla/WebService/FlagType.pm | 647 | ||||
-rw-r--r-- | Bugzilla/WebService/Server/REST/Resources/FlagType.pm | 56 |
3 files changed, 714 insertions, 2 deletions
diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm index e18e2b8ec..5164ec0c1 100644 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -81,8 +81,9 @@ use constant WS_ERROR_CODE => { illegal_field => 104, freetext_too_long => 104, # Component errors - require_component => 105, - component_name_too_long => 105, + require_component => 105, + component_name_too_long => 105, + product_unknown_component => 105, # Invalid Product no_products => 106, entry_access_denied => 106, @@ -191,6 +192,13 @@ use constant WS_ERROR_CODE => { # Search errors are 1000-1100 buglist_parameters_required => 1000, + # Flag type errors are 1100-1200 + flag_type_name_invalid => 1101, + flag_type_description_invalid => 1102, + flag_type_cc_list_invalid => 1103, + flag_type_sortkey_invalid => 1104, + flag_type_not_editable => 1105, + # Errors thrown by the WebService itself. The ones that are negative # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php xmlrpc_invalid_value => -32600, @@ -269,6 +277,7 @@ sub WS_DISPATCH { 'Bugzilla' => 'Bugzilla::WebService::Bugzilla', 'Bug' => 'Bugzilla::WebService::Bug', 'Classification' => 'Bugzilla::WebService::Classification', + 'FlagType' => 'Bugzilla::WebService::FlagType', 'Group' => 'Bugzilla::WebService::Group', 'Product' => 'Bugzilla::WebService::Product', 'User' => 'Bugzilla::WebService::User', diff --git a/Bugzilla/WebService/FlagType.pm b/Bugzilla/WebService/FlagType.pm new file mode 100644 index 000000000..d755b8885 --- /dev/null +++ b/Bugzilla/WebService/FlagType.pm @@ -0,0 +1,647 @@ +# 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::WebService::FlagType; + +use 5.10.1; +use strict; + +use parent qw(Bugzilla::WebService); +use Bugzilla::Component; +use Bugzilla::Constants; +use Bugzilla::Error; +use Bugzilla::FlagType; +use Bugzilla::Product; +use Bugzilla::Util qw(trim); + +use List::MoreUtils qw(uniq); + +sub create { + my ($self, $params) = @_; + + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + + Bugzilla->user->in_group('editcomponents') + || scalar(@{$user->get_products_by_permission('editcomponents')}) + || ThrowUserError("auth_failure", { group => "editcomponents", + action => "add", + object => "flagtypes" }); + + $params->{name} || ThrowCodeError('param_required', { param => 'name' }); + $params->{description} || ThrowCodeError('param_required', { param => 'description' }); + + my %args = ( + sortkey => 1, + name => undef, + inclusions => ['0:0'], # Default to __ALL__:__ALL__ + cc_list => '', + description => undef, + is_requestable => 'on', + exclusions => [], + is_multiplicable => 'on', + request_group => '', + is_active => 'on', + is_specifically_requestable => 'on', + target_type => 'bug', + grant_group => '', + ); + + foreach my $key (keys %args) { + $args{$key} = $params->{$key} if defined($params->{$key}); + } + + $args{name} = trim($params->{name}); + $args{description} = trim($params->{description}); + + # Is specifically requestable is actually is_requesteeable + if (exists $args{is_specifically_requestable}) { + $args{is_requesteeble} = delete $args{is_specifically_requestable}; + } + + # Default is on for the tickbox flags. + # If the user has set them to 'off' then undefine them so the flags are not ticked + foreach my $arg_name (qw(is_requestable is_multiplicable is_active is_requesteeble)) { + if (defined($args{$arg_name}) && ($args{$arg_name} eq '0')) { + $args{$arg_name} = undef; + } + } + + # Process group inclusions and exclusions + $args{inclusions} = _process_lists($params->{inclusions}) if defined $params->{inclusions}; + $args{exclusions} = _process_lists($params->{exclusions}) if defined $params->{exclusions}; + + my $flagtype = Bugzilla::FlagType->create(\%args); + + return { id => $self->type('int', $flagtype->id) }; +} + +sub update { + my ($self, $params) = @_; + + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + + Bugzilla->login(LOGIN_REQUIRED); + $user->in_group('editcomponents') + || scalar(@{$user->get_products_by_permission('editcomponents')}) + || ThrowUserError("auth_failure", { group => "editcomponents", + action => "edit", + object => "flagtypes" }); + + defined($params->{names}) || defined($params->{ids}) + || ThrowCodeError('params_required', + { function => 'FlagType.update', params => ['ids', 'names'] }); + + # Get the list of unique flag type ids we are updating + my @flag_type_ids = defined($params->{ids}) ? @{$params->{ids}} : (); + if (defined $params->{names}) { + push @flag_type_ids, map { $_->id } + @{ Bugzilla::FlagType::match({ name => $params->{names} }) }; + } + @flag_type_ids = uniq @flag_type_ids; + + # We delete names and ids to keep only new values to set. + delete $params->{names}; + delete $params->{ids}; + + # Process group inclusions and exclusions + # We removed them from $params because these are handled differently + my $inclusions = _process_lists(delete $params->{inclusions}) if defined $params->{inclusions}; + my $exclusions = _process_lists(delete $params->{exclusions}) if defined $params->{exclusions}; + + $dbh->bz_start_transaction(); + my %changes = (); + + foreach my $flag_type_id (@flag_type_ids) { + my ($flagtype, $can_fully_edit) = $user->check_can_admin_flagtype($flag_type_id); + + if ($can_fully_edit) { + $flagtype->set_all($params); + } + elsif (scalar keys %$params) { + ThrowUserError('flag_type_not_editable', { flagtype => $flagtype }); + } + + # Process the clusions + foreach my $type ('inclusions', 'exclusions') { + my $clusions = $type eq 'inclusions' ? $inclusions : $exclusions; + next if not defined $clusions; + + my @extra_clusions = (); + if (!$user->in_group('editcomponents')) { + my $products = $user->get_products_by_permission('editcomponents'); + # Bring back the products the user cannot edit. + foreach my $item (values %{$flagtype->$type}) { + my ($prod_id, $comp_id) = split(':', $item); + push(@extra_clusions, $item) unless grep { $_->id == $prod_id } @$products; + } + } + + $flagtype->set_clusions({ + $type => [@$clusions, @extra_clusions], + }); + } + + my $returned_changes = $flagtype->update(); + $changes{$flagtype->id} = { + name => $flagtype->name, + changes => $returned_changes, + }; + } + $dbh->bz_commit_transaction(); + + my @result; + foreach my $flag_type_id (keys %changes) { + my %hash = ( + id => $self->type('int', $flag_type_id), + name => $self->type('string', $changes{$flag_type_id}{name}), + changes => {}, + ); + + foreach my $field (keys %{ $changes{$flag_type_id}{changes} }) { + my $change = $changes{$flag_type_id}{changes}{$field}; + $hash{changes}{$field} = { + removed => $self->type('string', $change->[0]), + added => $self->type('string', $change->[1]) + }; + } + + push(@result, \%hash); + } + + return { flagtypes => \@result }; +} + +sub _process_lists { + my $list = shift; + my $user = Bugzilla->user; + + my @products; + if ($user->in_group('editcomponents')) { + @products = Bugzilla::Product->get_all; + } + else { + @products = @{$user->get_products_by_permission('editcomponents')}; + } + + my @component_list; + + foreach my $item (@$list) { + # A hash with products as the key and component names as the values + if(ref($item) eq 'HASH') { + while (my ($product_name, $component_names) = each %$item) { + my $product = Bugzilla::Product->check({name => $product_name}); + unless (grep { $product->name eq $_->name } @products) { + ThrowUserError('product_access_denied', { name => $product_name }); + } + my @component_ids; + + foreach my $comp_name (@$component_names) { + my $component = Bugzilla::Component->check({product => $product, name => $comp_name}); + ThrowCodeError('param_invalid', { param => $comp_name}) unless defined $component; + push @component_list, $product->id . ':' . $component->id; + } + } + } + elsif(!ref($item)) { + # These are whole products + my $product = Bugzilla::Product->check({name => $item}); + unless (grep { $product->name eq $_->name } @products) { + ThrowUserError('product_access_denied', { name => $item }); + } + push @component_list, $product->id . ':0'; + } + else { + # The user has passed something invalid + ThrowCodeError('param_invalid', { param => $item }); + } + } + + return \@component_list; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::WebService::FlagType - API for creating flags. + +=head1 DESCRIPTION + +This part of the Bugzilla API allows you to create new flags + +=head1 METHODS + +See L<Bugzilla::WebService> for a description of what B<STABLE>, B<UNSTABLE>, +and B<EXPERIMENTAL> mean, and for more description about error codes. + +=head2 Create Flag + +=over + +=item C<create> B<UNSTABLE> + +=item B<Description> + +Creates a new FlagType + +=item B<REST> + +POST /rest/flagtype + +The params to include in the POST body as well as the returned data format, +are the same as below. + +=item B<Params> + +At a minimum the following two arguments must be supplied: + +=over + +=item C<name> (string) - The name of the new Flag Type. + +=item C<description> (string) - A description for the Flag Type object. + +=back + +=item B<Returns> + +C<int> flag_id + +The ID of the new FlagType object is returned. + +=item B<Params> + +=over + +=item name B<required> + +C<string> A short name identifying this type. + +=item description B<required> + +C<string> A comprehensive description of this type. + +=item inclusions B<optional> + +An array of strings or a hash containing product names, and optionally +component names. If you provide a string, the flag type will be shown on +all bugs in that product. If you provide a hash, the key represents the +product name, and the value is the components of the product to be included. + +For example: + + [ 'FooProduct', + { + BarProduct => [ 'C1', 'C3' ], + BazProduct => [ 'C7' ] + } + ] + +This flag will be added to B<All> components of I<FooProduct>, +components C1 and C3 of I<BarProduct>, and C7 of I<BazProduct>. + +=item exclusions B<optional> + +An array of strings or hashes containing product names. This uses the same +fromat as inclusions. + +This will exclude the flag from all products and components specified. + +=item sortkey B<optional> + +C<int> A number between 1 and 32767 by which this type will be sorted when +displayed to users in a list; ignore if you don't care what order the types +appear in or if you want them to appear in alphabetical order. + +=item is_active B<optional> + +C<boolean> Flag of this type appear in the UI and can be set. Default is B<true>. + +=item is_requestable B<optional> + +C<boolean> Users can ask for flags of this type to be set. Default is B<true>. + +=item cc_list B<optional> + +C<array> An array of strings. If the flag type is requestable, who should +receive e-mail notification of requests. This is an array of e-mail addresses +which do not need to be Bugzilla logins. + +=item is_specifically_requestable B<optional> + +C<boolean> Users can ask specific other users to set flags of this type as +opposed to just asking the wind. Default is B<true>. + +=item is_multiplicable B<optional> + +C<boolean> Multiple flags of this type can be set on the same bug. Default is B<true>. + +=item grant_group B<optional> + +C<string> The group allowed to grant/deny flags of this type (to allow all +users to grant/deny these flags, select no group). Default is B<no group>. + +=item request_group B<optional> + +C<string> If flags of this type are requestable, the group allowed to request +them (to allow all users to request these flags, select no group). Note that +the request group alone has no effect if the grant group is not defined! +Default is B<no group>. + +=back + +=item B<Errors> + +=over + +=item 51 (Group Does Not Exist) + +The group name you entered does not exist, or you do not have access to it. + +=item 105 (Unknown component) + +The component does not exist for this product. + +=item 106 (Product Access Denied) + +Either the product does not exist or you don't have editcomponents privileges +to it. + +=item 501 (Illegal Email Address) + +One of the e-mail address in the CC list is invalid. An e-mail in the CC +list does NOT need to be a valid Bugzilla user. + +=item 1101 (Flag Type Name invalid) + +You must specify a non-blank name for this flag type. It must +no contain spaces or commas, and must be 50 characters or less. + +=item 1102 (Flag type must have description) + +You must specify a description for this flag type. + +=item 1103 (Flag type CC list is invalid + +The CC list must be 200 characters or less. + +=item 1104 (Flag Type Sort Key Not Valid) + +The sort key is not a valid number. + +=item 1105 (Flag Type Not Editable) + +This flag type is not available for the products you can administer. Therefore +you can not edit attributes of the flag type, other than the inclusion and +exclusion list. + +=back + +=item B<History> + +=over + +=item Added in Bugzilla B<5.0>. + +=back + +=back + +=head2 update + +B<EXPERIMENTAL> + +=over + +=item B<Description> + +This allows you to update a flag type in Bugzilla. + +=item B<REST> + +PUT /rest/flagtype/<product_id_or_name> + +The params to include in the PUT body as well as the returned data format, +are the same as below. The C<ids> and C<names> params will be overridden as +it is pulled from the URL path. + +=item B<Params> + +B<Note:> The following parameters specify which products you are updating. +You must set one or both of these parameters. + +=over + +=item C<ids> + +C<array> of C<int>s. Numeric ids of the flag types that you wish to update. + +=item C<names> + +C<array> of C<string>s. Names of the flag types that you wish to update. If +many flag types have the same name, this will change ALL of them. + +=back + +B<Note:> The following parameters specify the new values you want to set for +the products you are updating. + +=over + +=item name + +C<string> A short name identifying this type. + +=item description + +C<string> A comprehensive description of this type. + +=item inclusions B<optional> + +An array of strings or a hash containing product names, and optionally +component names. If you provide a string, the flag type will be shown on +all bugs in that product. If you provide a hash, the key represents the +product name, and the value is the components of the product to be included. + +for example + + [ 'FooProduct', + { + BarProduct => [ 'C1', 'C3' ], + BazProduct => [ 'C7' ] + } + ] + +This flag will be added to B<All> components of I<FooProduct>, +components C1 and C3 of I<BarProduct>, and C7 of I<BazProduct>. + +=item exclusions B<optional> + +An array of strings or hashes containing product names. +This uses the same fromat as inclusions. + +This will exclude the flag from all products and components specified. + +=item sortkey + +C<int> A number between 1 and 32767 by which this type will be sorted when +displayed to users in a list; ignore if you don't care what order the types +appear in or if you want them to appear in alphabetical order. + +=item is_active + +C<boolean> Flag of this type appear in the UI and can be set. + +=item is_requestable + +C<boolean> Users can ask for flags of this type to be set. + +=item cc_list + +C<array> An array of strings. If the flag type is requestable, who should +receive e-mail notification of requests. This is an array of e-mail addresses +which do not need to be Bugzilla logins. + +=item is_specifically_requestable + +C<boolean> Users can ask specific other users to set flags of this type as +opposed to just asking the wind. + +=item is_multiplicable + +C<boolean> Multiple flags of this type can be set on the same bug. + +=item grant_group + +C<string> The group allowed to grant/deny flags of this type (to allow all +users to grant/deny these flags, select no group). + +=item request_group + +C<string> If flags of this type are requestable, the group allowed to request +them (to allow all users to request these flags, select no group). Note that +the request group alone has no effect if the grant group is not defined! + +=back + +=item B<Returns> + +A C<hash> with a single field "flagtypes". This points to an array of hashes +with the following fields: + +=over + +=item C<id> + +C<int> The id of the product that was updated. + +=item C<name> + +C<string> The name of the product that was updated. + +=item C<changes> + +C<hash> The changes that were actually done on this product. The keys are +the names of the fields that were changed, and the values are a hash +with two keys: + +=over + +=item C<added> + +C<string> The value that this field was changed to. + +=item C<removed> + +C<string> The value that was previously set in this field. + +=back + +Note that booleans will be represented with the strings '1' and '0'. + +Here's an example of what a return value might look like: + + { + products => [ + { + id => 123, + changes => { + name => { + removed => 'FooFlagType', + added => 'BarFlagType' + }, + is_requestable => { + removed => '1', + added => '0', + } + } + } + ] + } + +=back + +=item B<Errors> + +=over + +=item 51 (Group Does Not Exist) + +The group name you entered does not exist, or you do not have access to it. + +=item 105 (Unknown component) + +The component does not exist for this product. + +=item 106 (Product Access Denied) + +Either the product does not exist or you don't have editcomponents privileges +to it. + +=item 501 (Illegal Email Address) + +One of the e-mail address in the CC list is invalid. An e-mail in the CC +list does NOT need to be a valid Bugzilla user. + +=item 1101 (Flag Type Name invalid) + +You must specify a non-blank name for this flag type. It must +no contain spaces or commas, and must be 50 characters or less. + +=item 1102 (Flag type must have description) + +You must specify a description for this flag type. + +=item 1103 (Flag type CC list is invalid + +The CC list must be 200 characters or less. + +=item 1104 (Flag Type Sort Key Not Valid) + +The sort key is not a valid number. + +=item 1105 (Flag Type Not Editable) + +This flag type is not available for the products you can administer. Therefore +you can not edit attributes of the flag type, other than the inclusion and +exclusion list. + +=back + +=item B<History> + +=over + +=item Added in Bugzilla B<5.0>. + +=back + +=back diff --git a/Bugzilla/WebService/Server/REST/Resources/FlagType.pm b/Bugzilla/WebService/Server/REST/Resources/FlagType.pm new file mode 100644 index 000000000..745785838 --- /dev/null +++ b/Bugzilla/WebService/Server/REST/Resources/FlagType.pm @@ -0,0 +1,56 @@ +# 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::WebService::Server::REST::Resources::FlagType; + +use 5.10.1; +use strict; + +use Bugzilla::WebService::Constants; +use Bugzilla::WebService::FlagType; + +use Bugzilla::Error; + +BEGIN { + *Bugzilla::WebService::FlagType::rest_resources = \&_rest_resources; +}; + +sub _rest_resources { + my $rest_resources = [ + qr{^/flagtype$}, { + POST => { + method => 'create', + success_code => STATUS_CREATED + } + }, + qr{^/flagtype/([^/]+)$}, { + PUT => { + method => 'update', + params => sub { + my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names'; + return { $param => [ $_[0] ] }; + } + } + }, + ]; + return $rest_resources; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Webservice::Server::REST::Resources::FlagType - The Flag Type REST API + +=head1 DESCRIPTION + +This part of the Bugzilla REST API allows you to create and update Flag types. + +See L<Bugzilla::WebService::FlagType> for more details on how to use this +part of the REST API. |