From 5db53c9ecf6270bd8ea6092c0893e96703f6ad61 Mon Sep 17 00:00:00 2001 From: Simon Green Date: Sun, 7 Dec 2014 07:48:33 +1000 Subject: Bug 1052202 - Web Service module to update and delete components r=dylan, a=glob --- Bugzilla/WebService/Component.pm | 438 ++++++++++++++++++++- Bugzilla/WebService/Constants.pm | 11 +- Bugzilla/WebService/Product.pm | 2 +- .../WebService/Server/REST/Resources/Component.pm | 28 ++ 4 files changed, 473 insertions(+), 6 deletions(-) (limited to 'Bugzilla') diff --git a/Bugzilla/WebService/Component.pm b/Bugzilla/WebService/Component.pm index 893e244b8..a050aef53 100644 --- a/Bugzilla/WebService/Component.pm +++ b/Bugzilla/WebService/Component.pm @@ -19,13 +19,24 @@ use Bugzilla::Error; use Bugzilla::WebService::Constants; use Bugzilla::WebService::Util qw(translate params_to_objects validate); -use constant MAPPED_FIELDS => { +use constant CREATE_MAPPED_FIELDS => { default_assignee => 'initialowner', default_qa_contact => 'initialqacontact', default_cc => 'initial_cc', is_open => 'isactive', }; +use constant MAPPED_FIELDS => { + is_open => 'is_active', +}; + +use constant MAPPED_RETURNS => { + initialowner => 'default_assignee', + initialqacontact => 'default_qa_contact', + cc_list => 'default_cc', + isactive => 'isopen', +}; + sub create { my ($self, $params) = @_; @@ -40,7 +51,7 @@ sub create { my $product = $user->check_can_admin_product($params->{product}); # Translate the fields - my $values = translate($params, MAPPED_FIELDS); + my $values = translate($params, CREATE_MAPPED_FIELDS); $values->{product} = $product; # Create the component and return the newly created id. @@ -48,6 +59,172 @@ sub create { return { id => $self->type('int', $component->id) }; } +sub _component_params_to_objects { + # We can't use Util's _param_to_objects since name is a hash + my $params = shift; + my $user = Bugzilla->user; + + my @components = (); + + if (defined $params->{ids}) { + push @components, @{ Bugzilla::Component->new_from_list($params->{ids}) }; + } + + if (defined $params->{names}) { + # To get the component objects for product/component combination + # first obtain the product object from the passed product name + foreach my $name_hash (@{$params->{names}}) { + my $product = $user->can_admin_product($name_hash->{product}); + push @components, @{ Bugzilla::Component->match({ + product_id => $product->id, + name => $name_hash->{component} + })}; + } + } + + my %seen_component_ids = (); + + my @accessible_components; + foreach my $component (@components) { + # Skip if we already included this component + next if $seen_component_ids{$component->id}++; + + # Can the user see and admin this product? + my $product = $component->product; + $user->check_can_admin_product($product->name); + + push @accessible_components, $component; + } + + return \@accessible_components; +} + +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 => "components" }); + + defined($params->{names}) || defined($params->{ids}) + || ThrowCodeError('params_required', + { function => 'Component.update', params => ['ids', 'names'] }); + + my $component_objects = _component_params_to_objects($params); + + # If the user tries to change component name for several + # components of the same product then throw an error + if ($params->{name}) { + my %unique_product_comps; + foreach my $comp (@$component_objects) { + if($unique_product_comps{$comp->product_id}) { + ThrowUserError("multiple_components_update_not_allowed"); + } + else { + $unique_product_comps{$comp->product_id} = 1; + } + } + } + + my $values = translate($params, MAPPED_FIELDS); + + # We delete names and ids to keep only new values to set. + delete $values->{names}; + delete $values->{ids}; + + $dbh->bz_start_transaction(); + foreach my $component (@$component_objects) { + $component->set_all($values); + } + + my %changes; + foreach my $component (@$component_objects) { + my $returned_changes = $component->update(); + $changes{$component->id} = translate($returned_changes, MAPPED_RETURNS); + } + $dbh->bz_commit_transaction(); + + my @result; + foreach my $component (@$component_objects) { + my %hash = ( + id => $component->id, + changes => {}, + ); + + foreach my $field (keys %{ $changes{$component->id} }) { + my $change = $changes{$component->id}->{$field}; + + if ($field eq 'default_assignee' + || $field eq 'default_qa_contact' + || $field eq 'default_cc' + ) { + # We need to convert user ids to login names + my @old_user_ids = split(/[,\s]+/, $change->[0]); + my @new_user_ids = split(/[,\s]+/, $change->[1]); + + my @old_users = map { $_->login } + @{Bugzilla::User->new_from_list(\@old_user_ids)}; + my @new_users = map { $_->login } + @{Bugzilla::User->new_from_list(\@new_user_ids)}; + + $hash{changes}{$field} = { + removed => $self->type('string', join(', ', @old_users)), + added => $self->type('string', join(', ', @new_users)), + }; + } + else { + $hash{changes}{$field} = { + removed => $self->type('string', $change->[0]), + added => $self->type('string', $change->[1]) + }; + } + } + + push(@result, \%hash); + } + + return { components => \@result }; +} + +sub delete { + 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 => "components" }); + + defined($params->{names}) || defined($params->{ids}) + || ThrowCodeError('params_required', + { function => 'Component.delete', params => ['ids', 'names'] }); + + my $component_objects = _component_params_to_objects($params); + + $dbh->bz_start_transaction(); + my %changes; + foreach my $component (@$component_objects) { + my $returned_changes = $component->remove_from_db(); + } + $dbh->bz_commit_transaction(); + + my @result; + foreach my $component (@$component_objects) { + push @result, { id => $component->id }; + } + + return { components => \@result }; +} + 1; __END__ @@ -147,3 +324,260 @@ specified product. =back +=head2 update + +=over + +=item B + +This allows you to update one or more components in Bugzilla. + +=item B + +PUT /rest/component/ + +PUT /rest/component// + +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 components you are updating. +You must set one or both of these parameters. + +=over + +=item C + +C of Cs. Numeric ids of the components that you wish to update. + +=item C + +C of Ces. Names of the components that you wish to update. The +hash keys are C and C, representing the name of the product +and the component you wish to change. + +=back + +B The following parameters specify the new values you want to set for +the components you are updating. + +=over + +=item C + +C A new name for this component. If you try to set this while updating +more than one component for a product, an error will occur, as component names +must be unique per product. + +=item C + +C Update the long description for these components to this value. + +=item C + +C The login name of the default assignee of the component. + +=item C + +C An array of strings with each element representing one login name of the default CC list. + +=item C + +C The login name of the default QA contact for the component. + +=item C + +C True if the component is currently allowing bugs to be entered +into it, False otherwise. + +=back + +=item B + +A C with a single field "components". This points to an array of hashes +with the following fields: + +=over + +=item C + +C The id of the component that was updated. + +=item C + +C The changes that were actually done on this component. 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: + + { + components => [ + { + id => 123, + changes => { + name => { + removed => 'FooName', + added => 'BarName' + }, + default_assignee => { + removed => 'foo@company.com', + added => 'bar@company.com', + } + } + } + ] + } + +=back + +=item B + +=over + +=item 51 (User does not exist) + +One of the contact e-mail addresses is not a valid Bugzilla user. + +=item 106 (Product access denied) + +The product you are trying to modify does not exist or you don't have access to it. + +=item 706 (Product admin denied) + +You do not have the permission to change components for this product. + +=item 105 (Component name too long) + +The name specified for this component was longer than the maximum +allowed length. + +=item 1200 (Component name already exists) + +You specified the name of a component that already exists. +(Component names must be unique per product in Bugzilla.) + +=item 1210 (Component blank name) + +You must specify a non-blank name for this component. + +=item 1211 (Component must have description) + +You must specify a description for this component. + +=item 1212 (Component name is not unique) + +You have attempted to set more than one component in the same product with the +same name. Component names must be unique in each product. + +=item 1213 (Component needs a default assignee) + +A default assignee is required for this component. + +=back + +=item B + +=over + +=item Added in Bugzilla B<5.0>. + +=back + +=back + +=head2 delete + +=over + +=item B + +This allows you to delete one or more components in Bugzilla. + +=item B + +DELETE /rest/component/ + +DELETE /rest/component// + +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 components you are deleting. +You must set one or both of these parameters. + +=over + +=item C + +C of Cs. Numeric ids of the components that you wish to delete. + +=item C + +C of Ces. Names of the components that you wish to delete. The +hash keys are C and C, representing the name of the product +and the component you wish to delete. + +=back + +=item B + +A C with a single field "components". This points to an array of hashes +with the following field: + +=over + +=item C + +C The id of the component that was deleted. + +=back + +=item B + +=over + +=item 106 (Product access denied) + +The product you are trying to modify does not exist or you don't have access to it. + +=item 706 (Product admin denied) + +You do not have the permission to delete components for this product. + +=item 1202 (Component has bugs) + +The component you are trying to delete currently has bugs assigned to it. +You must move these bugs before trying to delete the component. + +=back + +=item B + +=over + +=item Added in Bugzilla B<5.0> + +=back + +=back diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm index cfd934c4e..cf2666551 100644 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -184,6 +184,7 @@ use constant WS_ERROR_CODE => { product_must_have_description => 703, product_must_have_version => 704, product_must_define_defaultmilestone => 705, + product_admin_denied => 706, # Group errors are 800-900 empty_group_name => 800, @@ -207,9 +208,13 @@ use constant WS_ERROR_CODE => { flag_type_not_editable => 1105, # Component errors are 1200-1300 - component_already_exists => 1200, - component_is_last => 1201, - component_has_bugs => 1202, + component_already_exists => 1200, + component_is_last => 1201, + component_has_bugs => 1202, + component_blank_name => 1210, + component_blank_description => 1211, + multiple_components_update_not_allowed => 1212, + component_need_initialowner => 1213, # Errors thrown by the WebService itself. The ones that are negative # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php diff --git a/Bugzilla/WebService/Product.pm b/Bugzilla/WebService/Product.pm index 0e78836bf..6330b6402 100644 --- a/Bugzilla/WebService/Product.pm +++ b/Bugzilla/WebService/Product.pm @@ -857,7 +857,7 @@ C of Cs. Numeric ids of the products that you wish to update. =item C -C or Cs. Names of the products that you wish to update. +C of Cs. Names of the products that you wish to update. =back diff --git a/Bugzilla/WebService/Server/REST/Resources/Component.pm b/Bugzilla/WebService/Server/REST/Resources/Component.pm index 198c09332..47a8b9e0f 100644 --- a/Bugzilla/WebService/Server/REST/Resources/Component.pm +++ b/Bugzilla/WebService/Server/REST/Resources/Component.pm @@ -28,6 +28,34 @@ sub _rest_resources { success_code => STATUS_CREATED } }, + qr{^/component/(\d+)$}, { + PUT => { + method => 'update', + params => sub { + return { ids => [ $_[0] ] }; + } + }, + DELETE => { + method => 'delete', + params => sub { + return { ids => [ $_[0] ] }; + } + }, + }, + qr{^/component/([^/]+)/([^/]+)$}, { + PUT => { + method => 'update', + params => sub { + return { names => [ { product => $_[0], component => $_[1] } ] }; + } + }, + DELETE => { + method => 'delete', + params => sub { + return { names => [ { product => $_[0], component => $_[1] } ] }; + } + }, + }, ]; return $rest_resources; } -- cgit v1.2.3-24-g4f1b