diff options
author | Max Kanat-Alexander <mkanat@bugzilla.org> | 2010-07-13 03:41:12 +0200 |
---|---|---|
committer | Max Kanat-Alexander <mkanat@bugzilla.org> | 2010-07-13 03:41:12 +0200 |
commit | d403dca90ecda9fb290dbbc9507bdda79dae7b59 (patch) | |
tree | 585056cded55ffbffa866b56f348f9b2a6438039 | |
parent | 33e1f44e56b7864a793738574a564675bd019270 (diff) | |
download | bugzilla-d403dca90ecda9fb290dbbc9507bdda79dae7b59.tar.gz bugzilla-d403dca90ecda9fb290dbbc9507bdda79dae7b59.tar.xz |
Bug 415813: Implement Bug.update() as an API for WebServices
r=dkl, a=mkanat
-rw-r--r-- | Bugzilla/Bug.pm | 15 | ||||
-rw-r--r-- | Bugzilla/WebService/Bug.pm | 480 | ||||
-rw-r--r-- | Bugzilla/WebService/Constants.pm | 1 |
3 files changed, 494 insertions, 2 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index efee14826..e28c752df 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -265,9 +265,14 @@ use constant MAX_LINE_LENGTH => 254; # of Bugzilla. (These are the field names that the WebService and email_in.pl # use.) use constant FIELD_MAP => { + blocks => 'blocked', + is_confirmed => 'everconfirmed', + cc_accessible => 'cclist_accessible', creation_time => 'creation_ts', creator => 'reporter', description => 'comment', + depends_on => 'dependson', + dupe_of => 'dup_id', id => 'bug_id', last_change_time => 'delta_ts', platform => 'rep_platform', @@ -3706,11 +3711,17 @@ sub LogActivityEntry { # Convert WebService API and email_in.pl field names to internal DB field # names. sub map_fields { - my ($params) = @_; + my ($params, $except) = @_; my %field_values; foreach my $field (keys %$params) { - my $field_name = FIELD_MAP->{$field} || $field; + my $field_name; + if ($except->{$field}) { + $field_name = $field; + } + else { + $field_name = FIELD_MAP->{$field} || $field; + } $field_values{$field_name} = $params->{$field}; } diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm index dd46d870e..c083bd491 100644 --- a/Bugzilla/WebService/Bug.pm +++ b/Bugzilla/WebService/Bug.pm @@ -47,6 +47,7 @@ use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component); use constant DATE_FIELDS => { comments => ['new_since'], search => ['last_change_time', 'creation_time'], + update => ['deadline'], }; use constant READ_ONLY => qw( @@ -448,6 +449,93 @@ sub possible_duplicates { return { bugs => \@hashes }; } +sub update { + my ($self, $params) = validate(@_, 'ids'); + + my $user = Bugzilla->login(LOGIN_REQUIRED); + my $dbh = Bugzilla->dbh; + + $params = Bugzilla::Bug::map_fields($params, { summary => 1 }); + + my $ids = delete $params->{ids}; + defined $ids || ThrowCodeError('param_required', { param => 'ids' }); + + my @bugs = map { Bugzilla::Bug->check($_) } @$ids; + + my %values = %$params; + $values{other_bugs} = \@bugs; + + if (exists $values{comment} and exists $values{comment}{comment}) { + $values{comment}{body} = delete $values{comment}{comment}; + } + + # Prevent bugs that could be triggered by specifying fields that + # have valid "set_" functions in Bugzilla::Bug, but shouldn't be + # called using those field names. + delete $values{dependencies}; + delete $values{flags}; + + foreach my $bug (@bugs) { + if (!$user->can_edit_product($bug->product_obj->id) ) { + ThrowUserError("product_edit_denied", + { product => $bug->product }); + } + + $bug->set_all(\%values); + } + + my %all_changes; + $dbh->bz_start_transaction(); + foreach my $bug (@bugs) { + $all_changes{$bug->id} = $bug->update(); + } + $dbh->bz_commit_transaction(); + + foreach my $bug (@bugs) { + $bug->send_changes($all_changes{$bug->id}); + } + + my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() }; + # This doesn't normally belong in FIELD_MAP, but we do want to translate + # "bug_group" back into "groups". + $api_name{'bug_group'} = 'groups'; + + my @result; + foreach my $bug (@bugs) { + my %hash = ( + id => $self->type('int', $bug->id), + last_change_time => $self->type('dateTime', $bug->delta_ts), + changes => {}, + ); + + # alias is returned in case users pass a mixture of ids and aliases, + # so that they can know which set of changes relates to which value + # they passed. + if (Bugzilla->params->{'usebugaliases'}) { + $hash{alias} = $self->type('string', $bug->alias); + } + else { + # For API reasons, we always want the alias field to appear, we + # just don't want it to have a value if aliases are turned off. + $hash{alias} = $self->type('string', ''); + } + + my %changes = %{ $all_changes{$bug->id} }; + foreach my $field (keys %changes) { + my $change = $changes{$field}; + my $api_field = $api_name{$field} || $field; + $hash{changes}->{$api_field} = { + removed => $self->type('string', $change->[0]), + added => $self->type('string', $change->[1]) + }; + } + + push(@result, \%hash); + } + + return { bugs => \@result }; +} + sub create { my ($self, $params) = @_; Bugzilla->login(LOGIN_REQUIRED); @@ -2046,6 +2134,398 @@ code of 32000. =back +=item C<update> + +B<UNSTABLE> + +=over + +=item B<Description> + +Allows you to update the fields of a bug. Automatically sends emails +out about the changes. + +=item B<Params> + +=over + +=item C<ids> + +Array of C<int>s or C<string>s. The ids or aliases of the bugs that +you want to modify. + +=back + +B<Note>: All following fields specify the values you want to set on the +bugs you are updating. + +=over + +=item C<alias> + +(string) The alias of the bug. You can only set this if you are modifying +a single bug. If there is more than one bug specified in C<ids>, passing in +a value for C<alias> will cause an error to be thrown. + +=item C<assigned_to> + +C<string> The full login name of the user this bug is assigned to. + +=item C<blocks> + +=item C<depends_on> + +C<hash> These specify the bugs that this bug blocks or depends on, +respectively. To set these, you should pass a hash as the value. The hash +may contain the following fields: + +=over + +=item C<add> An array of C<int>s. Bug ids to add to this field. + +=item C<remove> An array of C<int>s. Bug ids to remove from this field. +If the bug ids are not already in the field, they will be ignored. + +=item C<set> An array of C<int>s. An exact set of bug ids to set this +field to, overriding the current value. If you specify C<set>, then C<add> +and C<remove> will be ignored. + +=back + +=item C<cc> + +C<hash> The users on the cc list. To modify this field, pass a hash, which +may have the following fields: + +=over + +=item C<add> Array of C<string>s. User names to add to the CC list. +They must be full user names, and an error will be thrown if you pass +in an invalid user name. + +=item C<remove> Array of C<string>s. User names to remove from the CC +list. They must be full user names, and an error will be thrown if you +pass in an invalid user name. + +=back + +=item C<cc_accessible> + +C<boolean> Whether or not users in the CC list are allowed to access +the bug, even if they aren't in a group that can normally access the bug. + +=item C<comment> + +C<hash>. A comment on the change. The hash may contain the following fields: + +=over + +=item C<body> C<string> The actual text of the comment. +B<Note>: For compatibility with the parameters to L</add_comment>, +you can also call this field C<comment>, if you want. + +=item C<is_private> C<boolean> Whether the comment is private or not. +If you try to make a comment private and you don't have the permission +to, an error will be thrown. + +=back + +=item C<comment_is_private> + +C<hash> This is how you update the privacy of comments that are already +on a bug. This is a hash, where the keys are the C<int> id of comments (not +their count on a bug, like #1, #2, #3, but their globally-unique id, +as returned by L</comments>) and the value is a C<boolean> which specifies +whether that comment should become private (C<true>) or public (C<false>). + +The comment ids must be valid for the bug being updated. Thus, it is not +practical to use this while updating multiple bugs at once, as a single +comment id will never be valid on multiple bugs. + +=item C<component> + +C<string> The Component the bug is in. + +=item C<deadline> + +C<dateTime> The Deadline field--a date specifying when the bug must +be completed by. The time specified is ignored--only the date is +significant. + +=item C<dupe_of> + +C<int> The bug that this bug is a duplicate of. If you want to mark +a bug as a duplicate, the safest thing to do is to set this value +and I<not> set the C<status> or C<resolution> fields. They will +automatically be set by Bugzilla to the appropriate values for +duplicate bugs. + +=item C<estimated_time> + +C<double> The total estimate of time required to fix the bug, in hours. +This is the I<total> estimate, not the amount of time remaining to fix it. + +=item C<groups> + +C<hash> The groups a bug is in. To modify this field, pass a hash, which +may have the following fields: + +=over + +=item C<add> Array of C<string>s. The names of groups to add. Passing +in an invalid group name or a group that you cannot add to this bug will +cause an error to be thrown. + +=item C<remove> Array of C<string>s. The names of groups to remove. Passing +in an invalid group name or a group that you cannot remove from this bug +will cause an error to be thrown. + +=back + +=item C<keywords> + +C<hash> Keywords on the bug. To modify this field, pass a hash, which +may have the following fields: + +=over + +=item C<add> An array of C<strings>s. The names of keywords to add to +the field on the bug. Passing something that isn't a valid keyword name +will cause an error to be thrown. + +=item C<remove> An array of C<string>s. The names of keywords to remove +from the field on the bug. Passing something that isn't a valid keyword +name will cause an error to be thrown. + +=item C<set> An array of C<strings>s. An exact set of keywords to set the +field to, on the bug. Passing something that isn't a valid keyword name +will cause an error to be thrown. Specifying C<set> overrides C<add> and +C<remove>. + +=back + +=item C<op_sys> + +C<string> The Operating System ("OS") field on the bug. + +=item C<platform> + +C<string> The Platform or "Hardware" field on the bug. + +=item C<priority> + +C<string> The Priority field on the bug. + +=item C<product> + +C<string> The name of the product that the bug is in. If you change +this, you will probably also want to change C<target_milestone>, +C<version>, and C<component>, since those have different legal +values in every product. + +If you cannot change the C<target_milestone> field, it will be reset to +the default for the product, when you move a bug to a new product. + +You may also wish to add or remove groups, as which groups are +valid on a bug depends on the product. Groups that are not valid +in the new product will be automatically removed, and groups which +are mandatory in the new product will be automaticaly added, but no +other automatic group changes will be done. + +Note that users can only move a bug into a product if they would +normally have permission to file new bugs in that product. + +=item C<qa_contact> + +C<string> The full login name of the bug's QA Contact. + +=item C<reporter_accessible> + +C<boolean> Whether or not the bug's reporter is allowed to access +the bug, even if he or she isn't in a group that can normally access +the bug. + +=item C<remaining_time> + +C<double> How much work time is remaining to fix the bug, in hours. +If you set C<work_time> but don't explicitly set C<remaining_time>, +then the C<work_time> will be deducted from the bug's C<remaining_time>. + +=item C<reset_assigned_to> + +C<boolean> If true, the C<assigned_to> field will be reset to the +default for the component that the bug is in. (If you have set the +component at the same time as using this, then the component used +will be the new component, not the old one.) + +=item C<reset_qa_contact> + +C<boolean> If true, the C<qa_contact> field will be reset to the +default for the component that the bug is in. (If you have set the +component at the same time as using this, then the component used +will be the new component, not the old one.) + +=item C<resolution> + +C<string> The current resolution. May only be set if you are closing +a bug or if you are modifying an already-closed bug. Attempting to set +the resolution to I<any> value (even an empty or null string) on an +open bug will cause an error to be thrown. + +If you change the C<status> field to an open status, the resolution +field will automatically be cleared, so you don't have to clear it +manually. + +=item C<see_also> + +C<hash> The See Also field on a bug, specifying URLs to bugs in other +bug trackers. To modify this field, pass a hash, which may have the +following fields: + +=over + +=item C<add> An array of C<strings>s. URLs to add to the field. +Each URL must be a valid URL to a bug-tracker, or an error will +be thrown. + +=item C<remove> An array of C<string>s. URLs to remove from the field. +Invalid URLs will be ignored. + +=back + +=item C<severity> + +C<string> The Severity field of a bug. + +=item C<status> + +C<string> The status you want to change the bug to. Note that if +a bug is changing from open to closed, you should also specify +a C<resolution>. + +=item C<summary> + +C<string> The Summary field of the bug. + +=item C<target_milestone> + +C<string> The bug's Target Milestone. + +=item C<url> + +C<string> The "URL" field of a bug. + +=item C<version> + +C<string> The bug's Version field. + +=item C<whiteboard> + +C<string> The Status Whiteboard field of a bug. + +=item C<work_time> + +C<double> The number of hours worked on this bug as part of this change. +If you set C<work_time> but don't explicitly set C<remaining_time>, +then the C<work_time> will be deducted from the bug's C<remaining_time>. + +=back + +You can also set the value of any custom field by passing its name as +a parameter, and the value to set the field to. For multiple-selection +fields, the value should be an array of strings. + +=item B<Returns> + +A C<hash> with a single field, "bugs". This points to an array of hashes +with the following fields: + +=over + +=item C<id> + +C<int> The id of the bug that was updated. + +=item C<alias> + +C<string> The alias of the bug that was updated, if aliases are enabled and +this bug has an alias. + +=item C<last_change_time> + +C<dateTime> The exact time that this update was done at, for this bug. +If no update was done (that is, no fields had their values changed and +no comment was added) then this will instead be the last time the bug +was updated. + +=item C<changes> + +C<hash> The changes that were actually done on this bug. 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 values that were added to this field, +possibly a comma-and-space-separated list if multiple values were added. + +=item C<removed> (C<string>) The values that were removed from this +field, possibly a comma-and-space-separated list if multiple values were +removed. + +=back + +=back + +Here's an example of what a return value might look like: + + { + bugs => [ + { + id => 123, + alias => 'foo', + last_change_time => '2010-01-01T12:34:56', + changes => { + status => { + removed => 'NEW', + added => 'ASSIGNED' + }, + keywords => { + removed => 'bar', + added => 'qux, quo, qui', + } + }, + } + ] + } + +=item B<Errors> + +This function can throw all of the errors that L</get> can throw, plus: + +=over + +=item 103 (Invalid Alias) + +Either you tried to set an alias when changing multiple bugs at once, +or the alias you specified is invalid for some reason. + +=back + +FIXME: Plus a whole load of other errors that we haven't documented yet, +which we won't even know about until after we do QA for 4.0. + +=item B<History> + +=over + +=item Added in Bugzilla B<4.0>. + +=back + +=back + + =item C<update_see_also> B<EXPERIMENTAL> diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm index 0ada98ba4..44b7bb4e0 100644 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -66,6 +66,7 @@ use constant WS_ERROR_CODE => { alias_in_use => 103, alias_is_numeric => 103, alias_has_comma_or_space => 103, + alias_not_allowed => 103, # Misc. bug field errors illegal_field => 104, freetext_too_long => 104, |