summaryrefslogtreecommitdiffstats
path: root/Bugzilla/WebService
diff options
context:
space:
mode:
authorSimon Green <simon@simongreen.net>2014-12-06 22:48:33 +0100
committerSimon Green <sgreen@redhat.com>2014-12-06 22:48:33 +0100
commit5db53c9ecf6270bd8ea6092c0893e96703f6ad61 (patch)
tree4e8330e4cf7f496721a137409c0a9af499ec90b8 /Bugzilla/WebService
parent9d1dc1f24d6a43339ed00e8899cf579d2739401b (diff)
downloadbugzilla-5db53c9ecf6270bd8ea6092c0893e96703f6ad61.tar.gz
bugzilla-5db53c9ecf6270bd8ea6092c0893e96703f6ad61.tar.xz
Bug 1052202 - Web Service module to update and delete components
r=dylan, a=glob
Diffstat (limited to 'Bugzilla/WebService')
-rw-r--r--Bugzilla/WebService/Component.pm438
-rw-r--r--Bugzilla/WebService/Constants.pm11
-rw-r--r--Bugzilla/WebService/Product.pm2
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Component.pm28
4 files changed, 473 insertions, 6 deletions
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<Description>
+
+This allows you to update one or more components in Bugzilla.
+
+=item B<REST>
+
+PUT /rest/component/<component_id>
+
+PUT /rest/component/<product_name>/<component_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 components 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 components that you wish to update.
+
+=item C<names>
+
+C<array> of C<hash>es. Names of the components that you wish to update. The
+hash keys are C<product> and C<component>, representing the name of the product
+and the component you wish to change.
+
+=back
+
+B<Note:> The following parameters specify the new values you want to set for
+the components you are updating.
+
+=over
+
+=item C<name>
+
+C<string> 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<description>
+
+C<string> Update the long description for these components to this value.
+
+=item C<default_assignee>
+
+C<string> The login name of the default assignee of the component.
+
+=item C<default_cc>
+
+C<array> An array of strings with each element representing one login name of the default CC list.
+
+=item C<default_qa_contact>
+
+C<string> The login name of the default QA contact for the component.
+
+=item C<is_open>
+
+C<boolean> True if the component is currently allowing bugs to be entered
+into it, False otherwise.
+
+=back
+
+=item B<Returns>
+
+A C<hash> with a single field "components". This points to an array of hashes
+with the following fields:
+
+=over
+
+=item C<id>
+
+C<int> The id of the component that was updated.
+
+=item C<changes>
+
+C<hash> 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<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:
+
+ {
+ components => [
+ {
+ id => 123,
+ changes => {
+ name => {
+ removed => 'FooName',
+ added => 'BarName'
+ },
+ default_assignee => {
+ removed => 'foo@company.com',
+ added => 'bar@company.com',
+ }
+ }
+ }
+ ]
+ }
+
+=back
+
+=item B<Errors>
+
+=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<History>
+
+=over
+
+=item Added in Bugzilla B<5.0>.
+
+=back
+
+=back
+
+=head2 delete
+
+=over
+
+=item B<Description>
+
+This allows you to delete one or more components in Bugzilla.
+
+=item B<REST>
+
+DELETE /rest/component/<component_id>
+
+DELETE /rest/component/<product_name>/<component_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 components you are deleting.
+You must set one or both of these parameters.
+
+=over
+
+=item C<ids>
+
+C<array> of C<int>s. Numeric ids of the components that you wish to delete.
+
+=item C<names>
+
+C<array> of C<hash>es. Names of the components that you wish to delete. The
+hash keys are C<product> and C<component>, representing the name of the product
+and the component you wish to delete.
+
+=back
+
+=item B<Returns>
+
+A C<hash> with a single field "components". This points to an array of hashes
+with the following field:
+
+=over
+
+=item C<id>
+
+C<int> The id of the component that was deleted.
+
+=back
+
+=item B<Errors>
+
+=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<History>
+
+=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<array> of C<int>s. Numeric ids of the products that you wish to update.
=item C<names>
-C<array> or C<string>s. Names of the products that you wish to update.
+C<array> of C<string>s. 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;
}