]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 415813: Implement Bug.update() as an API for WebServices
authorMax Kanat-Alexander <mkanat@bugzilla.org>
Tue, 13 Jul 2010 01:42:01 +0000 (18:42 -0700)
committerMax Kanat-Alexander <mkanat@bugzilla.org>
Tue, 13 Jul 2010 01:42:01 +0000 (18:42 -0700)
r=dkl, a=mkanat

Bugzilla/Bug.pm
Bugzilla/WebService/Bug.pm
Bugzilla/WebService/Constants.pm

index e32add2e1013c5146d3a8125a676fc42afb3a7cd..ecd00ff0194ad18d448549cedcbcebf3c8f7f952 100644 (file)
@@ -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',
@@ -3707,11 +3712,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};
     }
 
index dd46d870e470f06560252f7b0d423a8762c82218..c083bd491ad17bf84345c16d6ee64b56735225cf 100644 (file)
@@ -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>
index 0ada98ba44396e34064e21b603b6eeeea9bcd755..44b7bb4e0004a2678975f16570e6b99047583d9d 100644 (file)
@@ -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,