From dbfd6207290d1eee53fddec4c7c3b4aac0b2d47a Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Apr 2015 18:48:36 +0100 Subject: Bug 1051056: The REST API needs to be versioned so that new changes can be made that do not break compatibility r=dylan,a=glob --- Bugzilla/API/1_0/Resource/FlagType.pm | 890 ++++++++++++++++++++++++++++++++++ 1 file changed, 890 insertions(+) create mode 100644 Bugzilla/API/1_0/Resource/FlagType.pm (limited to 'Bugzilla/API/1_0/Resource/FlagType.pm') diff --git a/Bugzilla/API/1_0/Resource/FlagType.pm b/Bugzilla/API/1_0/Resource/FlagType.pm new file mode 100644 index 000000000..297be1510 --- /dev/null +++ b/Bugzilla/API/1_0/Resource/FlagType.pm @@ -0,0 +1,890 @@ +# 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::API::1_0::Resource::FlagType; + +use 5.10.1; +use strict; +use warnings; + +use Bugzilla::API::1_0::Constants; +use Bugzilla::API::1_0::Util; + +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); +use Moo; + +extends 'Bugzilla::API::1_0::Resource'; + +############## +# Constants # +############## + +use constant READ_ONLY => qw( + get +); + +use constant PUBLIC_METHODS => qw( + create + get + update +); + +sub REST_RESOURCES { + my $rest_resources = [ + qr{^/flag_type$}, { + POST => { + method => 'create', + success_code => STATUS_CREATED + } + }, + qr{^/flag_type/([^/]+)/([^/]+)$}, { + GET => { + method => 'get', + params => sub { + return { product => $_[0], + component => $_[1] }; + } + } + }, + qr{^/flag_type/([^/]+)$}, { + GET => { + method => 'get', + params => sub { + return { product => $_[0] }; + } + }, + PUT => { + method => 'update', + params => sub { + my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names'; + return { $param => [ $_[0] ] }; + } + } + }, + ]; + return $rest_resources; +} + +############ +# Methods # +############ + +sub get { + my ($self, $params) = @_; + my $dbh = Bugzilla->switch_to_shadow_db(); + my $user = Bugzilla->user; + + defined $params->{product} + || ThrowCodeError('param_required', + { function => 'Bug.flag_types', + param => 'product' }); + + my $product = delete $params->{product}; + my $component = delete $params->{component}; + + $product = Bugzilla::Product->check({ name => $product, cache => 1 }); + $component = Bugzilla::Component->check( + { name => $component, product => $product, cache => 1 }) if $component; + + my $flag_params = { product_id => $product->id }; + $flag_params->{component_id} = $component->id if $component; + my $matched_flag_types = Bugzilla::FlagType::match($flag_params); + + my $flag_types = { bug => [], attachment => [] }; + foreach my $flag_type (@$matched_flag_types) { + push(@{ $flag_types->{bug} }, $self->_flagtype_to_hash($flag_type, $product)) + if $flag_type->target_type eq 'bug'; + push(@{ $flag_types->{attachment} }, $self->_flagtype_to_hash($flag_type, $product)) + if $flag_type->target_type eq 'attachment'; + } + + return $flag_types; +} + +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 => as_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 => as_int($flag_type_id), + name => as_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 => as_string($change->[0]), + added => as_string($change->[1]) + }; + } + + push(@result, \%hash); + } + + return { flagtypes => \@result }; +} + +sub _flagtype_to_hash { + my ($self, $flagtype, $product) = @_; + my $user = Bugzilla->user; + + my @values = ('X'); + push(@values, '?') if ($flagtype->is_requestable && $user->can_request_flag($flagtype)); + push(@values, '+', '-') if $user->can_set_flag($flagtype); + + my $item = { + id => as_int($flagtype->id), + name => as_string($flagtype->name), + description => as_string($flagtype->description), + type => as_string($flagtype->target_type), + values => \@values, + is_active => as_boolean($flagtype->is_active), + is_requesteeble => as_boolean($flagtype->is_requesteeble), + is_multiplicable => as_boolean($flagtype->is_multiplicable) + }; + + if ($product) { + my $inclusions = $self->_flagtype_clusions_to_hash($flagtype->inclusions, $product->id); + my $exclusions = $self->_flagtype_clusions_to_hash($flagtype->exclusions, $product->id); + # if we have both inclusions and exclusions, the exclusions are redundant + $exclusions = [] if @$inclusions && @$exclusions; + # no need to return anything if there's just "any component" + $item->{inclusions} = $inclusions if @$inclusions && $inclusions->[0] ne ''; + $item->{exclusions} = $exclusions if @$exclusions && $exclusions->[0] ne ''; + } + + return $item; +} + +sub _flagtype_clusions_to_hash { + my ($self, $clusions, $product_id) = @_; + my $result = []; + foreach my $key (keys %$clusions) { + my ($prod_id, $comp_id) = split(/:/, $clusions->{$key}, 2); + if ($prod_id == 0 || $prod_id == $product_id) { + if ($comp_id) { + my $component = Bugzilla::Component->new({ id => $comp_id, cache => 1 }); + push @$result, $component->name; + } + else { + return [ '' ]; + } + } + } + return $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::API::1_0::Resource::FlagType - API for creating flags. + +=head1 DESCRIPTION + +This part of the Bugzilla API allows you to create new flags + +=head1 METHODS + +=head2 Get Flag Types + +=over + +=item C + +=item B + +Get information about valid flag types that can be set for bugs and attachments. + +=item B + +You have several options for retreiving information about flag types. The first +part is the request method and the rest is the related path needed. + +To get information about all flag types for a product: + +GET /rest/flag_type/ + +To get information about flag_types for a product and component: + +GET /rest/flag_type// + +The returned data format is the same as below. + +=item B + +You must pass a product name and an optional component name. + +=over + +=item C (string) - The name of a valid product. + +=item C (string) - An optional valid component name associated with the product. + +=back + +=item B + +A hash containing two keys, C and C. Each key value is an array of hashes, +containing the following keys: + +=over + +=item C + +C An integer id uniquely identifying this flag type. + +=item C + +C The name for the flag type. + +=item C + +C The target of the flag type which is either C or C. + +=item C + +C The description of the flag type. + +=item C + +C An array of string values that the user can set on the flag type. + +=item C + +C Users can ask specific other users to set flags of this type. + +=item C + +C Multiple flags of this type can be set for the same bug or attachment. + +=back + +=item B + +=over + +=item 106 (Product Access Denied) + +Either the product does not exist or you don't have access to it. + +=item 51 (Invalid Component) + +The component provided does not exist in the product. + +=back + +=item B + +=over + +=item Added in Bugzilla B<5.0>. + +=back + +=back + +=head2 Create Flag + +=over + +=item C + +=item B + +Creates a new FlagType + +=item B + +POST /rest/flag_type + +The params to include in the POST body as well as the returned data format, +are the same as below. + +=item B + +At a minimum the following two arguments must be supplied: + +=over + +=item C (string) - The name of the new Flag Type. + +=item C (string) - A description for the Flag Type object. + +=back + +=item B + +C flag_id + +The ID of the new FlagType object is returned. + +=item B + +=over + +=item name B + +C A short name identifying this type. + +=item description B + +C A comprehensive description of this type. + +=item inclusions B + +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 components of I, +components C1 and C3 of I, and C7 of I. + +=item exclusions B + +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 + +C 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 + +C Flag of this type appear in the UI and can be set. Default is B. + +=item is_requestable B + +C Users can ask for flags of this type to be set. Default is B. + +=item cc_list B + +C 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 + +C Users can ask specific other users to set flags of this type as +opposed to just asking the wind. Default is B. + +=item is_multiplicable B + +C Multiple flags of this type can be set on the same bug. Default is B. + +=item grant_group B + +C 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. + +=item request_group B + +C 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. + +=back + +=item B + +=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 + +=over + +=item Added in Bugzilla B<5.0>. + +=back + +=back + +=head2 update + +=over + +=item B + +This allows you to update a flag type in Bugzilla. + +=item B + +PUT /rest/flag_type/ + +The params to include in the PUT body as well as the returned data format, +are the same as below. The C and C params will be overridden as +it is pulled from the URL path. + +=item B + +B The following parameters specify which products you are updating. +You must set one or both of these parameters. + +=over + +=item C + +C of Cs. Numeric ids of the flag types that you wish to update. + +=item C + +C of Cs. 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 The following parameters specify the new values you want to set for +the products you are updating. + +=over + +=item name + +C A short name identifying this type. + +=item description + +C A comprehensive description of this type. + +=item inclusions B + +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 components of I, +components C1 and C3 of I, and C7 of I. + +=item exclusions B + +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 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 Flag of this type appear in the UI and can be set. + +=item is_requestable + +C Users can ask for flags of this type to be set. + +=item cc_list + +C 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 Users can ask specific other users to set flags of this type as +opposed to just asking the wind. + +=item is_multiplicable + +C Multiple flags of this type can be set on the same bug. + +=item grant_group + +C 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 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 + +A C with a single field "flagtypes". This points to an array of hashes +with the following fields: + +=over + +=item C + +C The id of the product that was updated. + +=item C + +C The name of the product that was updated. + +=item C + +C 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 + +C The value that this field was changed to. + +=item C + +C 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 + +=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 + +=over + +=item Added in Bugzilla B<5.0>. + +=back + +=back + +=head1 B + +=over + +=item REST_RESOURCES + +=back -- cgit v1.2.3-24-g4f1b