summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/ExternalBugs/lib/WebService.pm')
-rw-r--r--extensions/ExternalBugs/lib/WebService.pm1205
1 files changed, 1205 insertions, 0 deletions
diff --git a/extensions/ExternalBugs/lib/WebService.pm b/extensions/ExternalBugs/lib/WebService.pm
new file mode 100644
index 000000000..7822f55b0
--- /dev/null
+++ b/extensions/ExternalBugs/lib/WebService.pm
@@ -0,0 +1,1205 @@
+# 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/.
+
+package Bugzilla::Extension::ExternalBugs::WebService;
+
+use strict;
+use warnings;
+use 5.10.1;
+
+use base qw(Bugzilla::WebService);
+use List::MoreUtils qw(uniq);
+use Scalar::Util qw(looks_like_number);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::Util qw(trick_taint);
+use Bugzilla::WebService::Util qw(validate);
+
+use Bugzilla::Extension::ExternalBugs::Bug;
+use Bugzilla::Extension::ExternalBugs::Type;
+use Bugzilla::Extension::ExternalBugs::Util qw(resolve_ext_tracker_url);
+
+use constant PUBLIC_METHODS => qw(
+ get_ext_data
+ add_external_bug
+ update_external_bug
+ remove_external_bug
+ get_tracker
+ create_tracker
+ edit_tracker
+);
+
+sub get_ext_data {
+ my ($self, $params) = @_;
+ my $input = Bugzilla->input_params;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+
+ my $id = $input->{'id'};
+ $id || ThrowCodeError('param_required', {param => 'id'});
+
+ my $ext_bug = Bugzilla::Extension::ExternalBugs::Bug->new($id);
+ $ext_bug->update_ext_info();
+
+ return {
+ id => $ext_bug->id,
+ ext_bz_id => $ext_bug->ext_bz_id,
+ ext_bz_bug_id => $ext_bug->ext_bz_bug_id,
+ ext_status => $ext_bug->ext_status,
+ ext_priority => $ext_bug->ext_priority,
+ ext_description => $ext_bug->ext_description,
+ ext_last_updated => $ext_bug->ext_last_updated
+ };
+}
+
+## REDHAT EXTENSION START 725627
+sub add_external_bug {
+ my ($self, $params) = validate(@_, 'bug_ids');
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $dbh = Bugzilla->dbh;
+
+ my $bug_ids = delete $params->{bug_ids};
+ defined $bug_ids || ThrowCodeError('param_required', {param => 'bug_ids'});
+
+ my @bugs = map { Bugzilla::Bug->check($_) } @$bug_ids;
+ my @result = ();
+ my $minor_update = delete $params->{minor_update} ? 1 : 0;
+
+ # Turn the external_bugs param into something useful
+ my $external_bugs = $self->_get_external_bugs_details($params->{external_bugs});
+ scalar(@$external_bugs)
+ || ThrowCodeError('param_required', {param => 'external_bugs'});
+
+ 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';
+
+ # Add the values
+ $dbh->bz_start_transaction();
+ foreach my $bug (@bugs) {
+ $bug->set_all({external_bug => {add => $external_bugs}});
+ my $changes = $bug->update();
+
+ # We don't need to do anything if nothing changed
+ next unless scalar keys %$changes;
+
+ $bug->send_changes($changes, undef, $minor_update);
+
+ # The below is straight out of Bugzilla/WebService/Bug.pm (update sub)
+ my %hash = (
+ id => $self->type('int', $bug->id),
+ last_change_time => $self->type('dateTime', $bug->delta_ts),
+ changes => {},
+ );
+
+ ## REDHAT EXTENSION BEGIN
+ # Extention: Aliases are arrays not strings at Red Hat
+ # 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.
+ $hash{alias} = [map { $self->type('string', $_) } @{$bug->alias}];
+ ## REDHAT EXTENSION END
+
+ foreach my $field (keys %$changes) {
+ my $change = $changes->{$field};
+ my $api_field = $api_name{$field} || $field;
+
+ # We normalize undef to an empty string, so that the API
+ # stays consistent for things like Deadline that can become
+ # empty.
+ $change->[0] = '' if !defined $change->[0];
+ $change->[1] = '' if !defined $change->[1];
+ $hash{changes}->{$api_field} = {
+ removed => $self->type('string', $change->[0]),
+ added => $self->type('string', $change->[1])
+ };
+ }
+
+ push(@result, \%hash);
+ }
+ $dbh->bz_commit_transaction();
+
+ return {bugs => \@result};
+}
+
+sub update_external_bug {
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+
+ # Make sure at least one update value is provided
+ unless (exists $params->{ext_description}
+ or exists $params->{ext_priority}
+ or exists $params->{ext_status})
+ {
+ ThrowCodeError('params_required',
+ {params => [qw/ext_description ext_priority ext_status/]});
+ }
+
+ # Lets get the external bug(s)
+ my $external_bugs = $self->_get_external_bugs($params);
+
+ foreach my $external_bug (@$external_bugs) {
+ if (exists $params->{ext_description}) {
+ trick_taint($params->{ext_description});
+ $external_bug->set_ext_description($params->{ext_description});
+ }
+ if (exists $params->{ext_priority}) {
+ trick_taint($params->{ext_priority});
+ $external_bug->set_ext_priority($params->{ext_priority});
+ }
+ if (exists $params->{ext_status}) {
+ trick_taint($params->{ext_status});
+ $external_bug->set_ext_status($params->{ext_status});
+ }
+ $external_bug->update();
+ }
+
+ return {external_bugs => $self->_external_bugs_to_hash($external_bugs)};
+}
+
+sub remove_external_bug {
+ my ($self, $params) = @_;
+
+ Bugzilla->login(LOGIN_REQUIRED);
+
+ # Lets get the external bug(s)
+ my $external_bugs = $self->_get_external_bugs($params);
+ my $minor_update = delete $params->{minor_update} ? 1 : 0;
+
+ # Get group them by bug id
+ my %bug_ids = ();
+ foreach my $external_bug (@$external_bugs) {
+ push @{$bug_ids{$external_bug->bug_id}}, $external_bug;
+ }
+
+ # Make the changes and send notification
+ foreach my $bug_id (keys %bug_ids) {
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bug->set_all({external_bug => {remove => $bug_ids{$bug_id}}});
+ my $changes = $bug->update();
+
+ # We don't need to do anything if nothing changed
+ next unless scalar keys %$changes;
+
+ $bug->send_changes($changes, undef, $minor_update);
+ }
+
+ return {external_bugs => $self->_external_bugs_to_hash($external_bugs)};
+}
+
+sub _get_external_bugs_details {
+
+ # Santises the external_bugs array
+ my $self = shift;
+ my $external_bugs = shift;
+ my @external_bugs = ();
+
+ foreach my $external_bug (@$external_bugs) {
+ my %fields = ();
+
+ # Rewrite all this crap for external tracking
+ # If there's a URL, resolve it to a tracker ID and tracker URL.
+ if (defined($external_bug->{ext_bz_bug_url})) {
+ my ($tracker_id, $tracker_url)
+ = resolve_ext_tracker_url($external_bug->{ext_bz_bug_url});
+ delete $external_bug->{ext_bz_bug_url};
+ $external_bug->{ext_type_id} = $tracker_id;
+ $external_bug->{ext_bz_bug_id} = $tracker_url;
+ }
+
+ foreach my $key (qw/ext_status ext_description ext_priority ext_bz_bug_id/) {
+ $fields{$key} = '';
+ $fields{$key} = delete $external_bug->{$key} if exists $external_bug->{$key};
+ }
+
+ $fields{ext_bz_id} = $self->_get_ext_bz($external_bug)->id;
+
+ # ext_bz_bug_id is mandatory
+ unless ($fields{ext_bz_bug_id}) {
+ ThrowCodeError('param_required', {param => 'ext_bz_bug_id'});
+ }
+
+ # Any non blank fields left over is an error
+ my @missing = grep { $external_bug->{$_} } keys %$external_bug;
+ if (scalar @missing) {
+ ThrowCodeError('ext_bug_not_param', {keys => \@missing});
+ }
+
+ push @external_bugs, \%fields;
+ }
+
+ return \@external_bugs;
+}
+
+sub _get_ext_bz {
+
+ # Turns ext_type_id, ext_type_description or ext_type_url into
+ # a ExternalTracker Type object
+ my ($self, $params) = @_;
+
+ my %match = ();
+ foreach my $key (qw/id description url/) {
+ if ($params->{"ext_type_$key"}) {
+ $match{$key} = delete $params->{"ext_type_$key"};
+ }
+ }
+
+ if (scalar(keys(%match)) == 0) {
+ ThrowCodeError('ext_bug_type_params_missing');
+ }
+
+ my $ext_bzs = Bugzilla::Extension::ExternalBugs::Type->match(\%match);
+ if (scalar @$ext_bzs == 0) {
+ ThrowCodeError('ext_bug_type_no_match');
+ }
+ if (scalar @$ext_bzs > 1) {
+ ThrowCodeError('ext_bug_type_not_unique');
+ }
+
+ return $ext_bzs->[0];
+}
+
+sub _get_external_bugs {
+
+ # Given some params, will find all external tracker bugs that match
+ # that criteria that the logged in user can see.
+
+ my ($self, $params) = @_;
+
+ my $external_bugs = ();
+ if (my $ids = $params->{ids}) {
+ $external_bugs = Bugzilla::Extension::ExternalBugs::Bug->new_from_list($ids);
+ }
+ else {
+ my %match = ();
+ $match{ext_bz_id} = $self->_get_ext_bz($params)->id; # Always returns a value
+ $match{ext_bz_bug_id} = $params->{ext_bz_bug_id}
+ || ThrowCodeError('param_required', {param => 'ext_bz_bug_id'});
+ $match{bug_id} = $params->{bug_ids} if defined $params->{bug_ids};
+
+ $external_bugs = Bugzilla::Extension::ExternalBugs::Bug->match(\%match);
+ if (scalar @$external_bugs == 0) {
+ ThrowCodeError('ext_bug_no_match');
+ }
+ }
+
+ # We need to filter the list to bugs the user can see
+ @$external_bugs
+ = grep { Bugzilla->user->can_see_bug($_->bug_id) } @$external_bugs;
+
+ if (scalar @$external_bugs == 0) {
+ ThrowCodeError('ext_bug_no_match');
+ }
+
+ return $external_bugs;
+}
+
+## REDHAT EXTENSION END 725627
+
+sub _external_bugs_to_hash {
+ my ($self, $external_bugs) = @_;
+
+ my @ets;
+
+ foreach my $et (@$external_bugs) {
+ my %hash = (
+ "ext_status" => $et->ext_status,
+ "ext_bz_bug_id" => $et->ext_bz_bug_id,
+ "bug_id" => $et->bug_id,
+ "ext_description" => $et->ext_description,
+ "ext_priority" => $et->ext_priority,
+ "ext_bz_id" => $et->ext_bz_id,
+ "type" => {
+ "description" => $et->type->description,
+ "url" => $et->type->url,
+ "full_url" => $et->type->full_url,
+ "type" => $et->type->type,
+ "id" => $et->type->id,
+ },
+ );
+
+ push(@ets, \%hash);
+ }
+
+ return (\@ets);
+}
+
+sub _load_tracker {
+ my $id = shift;
+
+ my $tracker;
+
+ if (looks_like_number($id)) {
+ $tracker = Bugzilla::Extension::ExternalBugs::Type->new($id);
+ }
+
+ unless ($tracker) {
+ $tracker = Bugzilla::Extension::ExternalBugs::Type->new({name => $id});
+ }
+
+ ThrowUserError("ext_bz_invalid_ext_bz_id", {badvalue => $id}) unless $tracker;
+
+ return ($tracker);
+}
+
+sub get_tracker {
+ my ($self, $params) = @_;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ my $trackers = $params->{'trackers'};
+ my $all = $params->{'all'};
+ my @trackers;
+ my @results;
+
+ if ($trackers && @$trackers) {
+ foreach my $tracker (@$trackers) {
+ push(@trackers, _load_tracker($tracker));
+ }
+ }
+ elsif ($all) {
+ @trackers = @{Bugzilla::Extension::ExternalBugs::Type->match()};
+ }
+ else {
+ ThrowCodeError('params_required', {params => ['trackers', 'all']});
+ }
+
+ foreach my $tracker (@trackers) {
+ if ($user->can_see_ext_bz($tracker->id)) {
+ push(@results, _tracker_to_hash($tracker));
+ }
+ }
+
+ return (\@results);
+}
+
+sub create_tracker {
+ my ($self, $params) = @_;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ $user->in_group('editcomponents')
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "external_bugzilla"});
+
+ my $args = {description => $params->{description} // $params->{'tracker'},};
+
+ my @fields = qw(
+ url
+ full_url
+ type
+ can_get
+ matchrx
+ format
+ );
+
+ foreach my $field (@fields) {
+ $args->{$field} = $params->{$field} if (defined($params->{$field}));
+ }
+
+ $args->{ext_bz_regex_id} = $params->{regex} if (defined($params->{regex}));
+
+ if (defined($params->{groups})) {
+ $args->{groups}
+ = ref $params->{groups} ? $params->{groups} : [$params->{groups}];
+ }
+
+ my $tracker = Bugzilla::Extension::ExternalBugs::Type->create($args);
+
+ return _tracker_to_hash($tracker);
+}
+
+sub edit_tracker {
+ my ($self, $params) = @_;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ $user->in_group('editcomponents')
+ || ThrowUserError("auth_failure",
+ {group => "editcomponents", action => "edit", object => "external_bugzilla"});
+
+ my $id = $params->{'tracker'};
+
+ my $tracker;
+ if ($id) {
+ $tracker = _load_tracker($id);
+ }
+ else {
+ ThrowCodeError('params_required', {params => ['tracker',]});
+ }
+
+ my $groups = [];
+ if (defined $params->{groups}) {
+ $groups = ref $params->{groups} ? $params->{groups} : [$params->{groups}];
+ }
+
+## $tracker->set_description($params->{'description'}) if (defined($params->{'description'}));
+ $tracker->set_url($params->{'url'}) if (defined($params->{'url'}));
+ $tracker->set_full_url($params->{'full_url'})
+ if (defined($params->{'full_url'}));
+ $tracker->set_type($params->{'type'}) if (defined($params->{'type'}));
+ $tracker->set_regex($params->{'regex'}) if (defined($params->{'regex'}));
+ $tracker->set_can_get($params->{'can_get'}) if (defined($params->{'can_get'}));
+## $tracker->set_can_send($params->{'can_send'}) if (defined($params->{'can_send'}));
+## $tracker->set_must_send($params->{'must_send'}) if (defined($params->{'must_send'}));
+## $tracker->set_send_once($params->{'send_once'}) if (defined($params->{'send_once'}));
+ $tracker->set_matchrx($params->{'matchrx'}) if (defined($params->{'matchrx'}));
+ $tracker->set_format($params->{'format'}) if (defined($params->{'format'}));
+ $tracker->set_groups($groups) if (defined($params->{'groups'}));
+ my $changes = $tracker->update();
+
+## return _tracker_to_hash($tracker);
+ return $changes;
+}
+
+sub _tracker_to_hash {
+ my $tracker = shift;
+
+ my %hash = (
+ "description" => $tracker->description,
+ "url" => $tracker->url,
+ "full_url" => $tracker->full_url,
+ "type" => $tracker->type,
+ "id" => $tracker->id,
+ "regexs" => $tracker->regex_description,
+ "can_get" => $tracker->can_get,
+ "can_send" => $tracker->can_send,
+ "must_send" => $tracker->must_send,
+ "send_once" => $tracker->send_once,
+ "matchrx" => $tracker->matchrx,
+ "format" => $tracker->format,
+ );
+
+ my @groups = map { $_->name } @{$tracker->groups};
+ $hash{"groups"} = \@groups;
+
+ return \%hash;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Extension::ExternalBugs::WebService - The API for creating,
+changing, and deleting external bug trackers.
+
+=head1 DESCRIPTION
+
+This part of the Bugzilla API allows you to create, change and delete
+external bug tracking information for existing bugs in Red Hat Bugzilla.
+This is a Red Hat specific extension.
+
+=head1 METHODS
+
+See L<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
+
+=head1 Functions
+
+All functions require that you are logged in.
+
+=head1 Webservice Group
+
+All functions belong to the ExternalBugs group, for example: ExternalBugs.add_external_bug
+
+=head2 get_ext_data
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Get the current data for an external tracker, inlcuding attempting to sync data.
+
+B<Note:> This method will not send bug mail.
+
+=item B<Params>
+
+=over
+
+=item C<id>
+
+C<int> The id of the external tracker bug.
+
+=back
+
+=item B<Returns>
+
+A C<hash> with the following fields:
+
+=over
+
+=item C<id>
+
+C<int> The id of the bug that was updated.
+
+=item C<ext_bz_id>
+
+C<int> The id of the external tracker.
+
+=item C<ext_bz_bug_id>
+
+C<int> The bug id of the external tracker bug (i.e. the bug number of the
+external tracker)
+
+=item C<ext_status>
+
+C<string> The status of the external bug
+
+=item C<ext_priority>
+
+C<string> The priority of the external bug
+
+=item C<ext_description>
+
+C<string> The description of the external bug
+
+=item C<ext_last_updated>
+
+C<string> The date the External Bug was last synced.
+
+=back
+
+=back
+
+=head2 add_external_bug
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Add one or more external tracker bugs to one or more existing bugs.
+
+=item B<Params>
+
+=over
+
+=item C<bug_ids>
+
+C<array> - An array of bug ids.
+
+=item C<external_bugs>
+
+C<array> - An array of C<struct>s representing the external
+trackers you wish to add. Each hash has the following keys.
+either one of C<ext_bz_bug_id> or C<ext_bz_bug_url> must be supplied.
+
+If C<ext_bz_bug_url> is given, no other parameters are required.
+
+If C<ext_bz_bug_id> is given, at least one of ext_type_id,
+ext_type_description or ext_type_url must also be specified.
+
+=over
+
+=item C<ext_bz_bug_url>
+
+C<string> The full URL to the external bug to be linked to
+
+=item C<ext_type_id>
+
+C<int> The internal id of the external bugzilla type
+
+=item C<ext_type_description>
+
+C<string> The description the external bugzilla type
+
+=item C<ext_type_url>
+
+C<string> The URL value the external bugzilla type
+
+=item C<ext_bz_bug_id>
+
+C<string> The external bug id (i.e. the bug number of the external tracker)
+
+=item C<ext_status>
+
+C<string> The status of the external bug
+
+=item C<ext_description>
+
+C<string> The description of the external bug
+
+=item C<ext_priority>
+
+C<string> The priority of the external bug
+
+=back
+
+=item C<minor_update>
+
+C<boolean> If set to true, this is considered a minor update and no mail is sent
+to users who do not want minor update emails. If current user is not in the
+minor_update_group, this parameter is simply ignored.
+
+=back
+
+=item B<Returns>
+
+A C<hash> with a single field, "bugs". This points to an C<array> of
+C<struct>s 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
+
+=item B<Errors>
+
+=over
+
+=item 50 (Parameter required)
+
+You did not specify a required option
+
+=item 1001 (Invalid field)
+
+You specified a field in the external_bugs array that is not valid.
+
+=item 1002 (External Tracker type not specified)
+
+You must specify at least one of ext_type_id, ext_type_description
+or ext_type_url.
+
+=item 1003 (External Tracker type not found)
+
+The combination of ext_type_* fields did not match any External Trackers.
+
+=item 1004 (External Tracker type not unique)
+
+The combination of ext_type_* fields matched more than one External Tracker.
+
+=back
+
+=back
+
+=head2 update_external_bug
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Changes one or more external tracker bugs.
+
+B<Note:> This method will not send bug mail.
+
+=item B<Params>
+
+At least one of ext_desciption, ext_priority or ext_status must be specified.
+Additionally, either ids, or one ext_type_* and ext_bz_bug_id must be
+specified. If the 'ids' key is specified, all other values are ignored for
+determining which external tracker bugs are updated.
+
+=over
+
+=item C<ids>
+
+C<array> An array of integer external tracker bug ids.
+
+=item C<ext_type_id>
+
+C<int> The internal id of the external bugzilla type
+
+=item C<ext_type_description>
+
+C<string> The description the external bugzilla type
+
+=item C<ext_type_url>
+
+C<string> The URL value the external bugzilla type
+
+=item C<ext_bz_bug_id>
+
+C<sting> or C<array> of C<string>s The external bug id (i.e. the bug number of the external tracker)
+
+=item C<bug_ids>
+
+C<array> - An array of bug ids.
+
+=item C<ext_status>
+
+C<string> The new status of the external bug
+
+=item C<ext_description>
+
+C<string> The new description of the external bug
+
+=item C<ext_priority>
+
+C<string> The new priority of the external bug
+
+=back
+
+=item B<Returns>
+
+A C<hash> with a single field, "external_bugs". This points to an C<array> of
+C<struct>s with the following fields:
+
+=over
+
+=item C<id>
+
+C<int> The id of the external tracker bug that was updated.
+
+=item C<bug_id>
+
+C<int> The Red Hat Bugzilla bug number this external tracker bug is
+attached to.
+
+=item C<ext_bz_id>
+
+C<int> The id of the external tracker updated. More information is
+available in the type field.
+
+=item C<ext_bz_bug_id>
+
+C<int> The bug id of the external tracker bug (i.e. the bug number of the
+external tracker)
+
+=item C<ext_status>
+
+C<string> The status of the external bug
+
+=item C<ext_description>
+
+C<string> The description of the external bug
+
+=item C<ext_priority>
+
+C<string> The priority of the external bug
+
+=item C<type>
+
+C<struct> Contains information about the External Tracker type.
+
+=back
+
+=item B<Errors>
+
+=over
+
+=item 50 (Parameter required)
+
+You did not specify a required option
+
+=item 1002 (External Tracker type not specified)
+
+You must specify at least one of ext_type_id, ext_type_description
+or ext_type_url.
+
+=item 1003 (External Tracker type not found)
+
+The combination of ext_type_* fields did not match any External Trackers.
+
+=item 1004 (External Tracker type not unique)
+
+The combination of ext_type_* fields matched more than one External Tracker.
+
+=item 1006 (No External Tracker bugs found)
+
+No external tracker bugs were found that matched your criteria
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<4.2.1-2>, as a Red Hat extension.
+
+=back
+
+=back
+
+
+=head2 remove_external_bug
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Removes one or more external tracker bugs.
+
+=item B<Params>
+
+Either ids, or one ext_type_* and ext_bz_bug_id must be specified. If the
+'ids' key is specified, all other values are ignored.
+
+=over
+
+=item C<ids>
+
+C<array> An array of integer external tracker bug ids.
+
+=item C<ext_type_id>
+
+C<int> The internal id of the external bugzilla type
+
+=item C<ext_type_description>
+
+C<string> The description the external bugzilla type
+
+=item C<ext_type_url>
+
+C<string> The URL value the external bugzilla type
+
+=item C<ext_bz_bug_id>
+
+C<sting> or C<array> of C<string>s The external bug id (i.e. the bug number of the external tracker)
+
+=item C<bug_ids>
+
+C<array> - An array of bug ids.
+
+=item C<minor_update>
+
+C<boolean> If set to true, this is considered a minor update and no mail is sent
+to users who do not want minor update emails. If current user is not in the
+minor_update_group, this parameter is simply ignored.
+
+=back
+
+=item B<Returns>
+
+A C<hash> with a single field, "external_bugs". This points to an C<array> of
+C<struct>s representing the external tracker bugs deleted with the following fields:
+
+=over
+
+=item C<id>
+
+C<int> The id of the external tracker bug that was updated.
+
+=item C<bug_id>
+
+C<int> The Red Hat Bugzilla bug number this external tracker bug is
+attached to.
+
+=item C<ext_bz_id>
+
+C<int> The id of the external tracker updated. More information is
+available in the type field.
+
+=item C<ext_bz_bug_id>
+
+C<int> The bug id of the external tracker bug (i.e. the bug number of the
+external tracker)
+
+=item C<ext_status>
+
+C<string> The status of the external bug
+
+=item C<ext_description>
+
+C<string> The description of the external bug
+
+=item C<ext_priority>
+
+C<string> The priority of the external bug
+
+=item C<type>
+
+C<struct> Contains information about the External Tracker type.
+
+=back
+
+
+=item B<Errors>
+
+=over
+
+=item 50 (Parameter required)
+
+You did not specify a required option
+
+=item 1002 (External Tracker type not specified)
+
+You must specify at least one of ext_type_id, ext_type_description
+or ext_type_url.
+
+=item 1003 (External Tracker type not found)
+
+The combination of ext_type_* fields did not match any External Trackers.
+
+=item 1004 (External Tracker type not unique)
+
+The combination of ext_type_* fields matched more than one External Tracker.
+
+=item 1006 (No External Tracker bugs found)
+
+No external tracker bugs were found that matched your criteria
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<4.2.1-2>, as a Red Hat extension.
+
+=back
+
+=back
+
+=head2 get_tracker
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Get one or more external trackers.
+
+=item B<Params>
+
+Either I<trackers>, or I<all> must be specified.
+
+If a tracker is protected by group membership then it will not be visible if you
+are not a member of any of the groups.
+
+=over
+
+=item C<trackers>
+
+C<array> An array of tracker ids or descriptions.
+
+=item C<all>
+
+C<boolean> Get all trackers.
+
+=back
+
+=back
+
+=head2 create_tracker
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Create an external tracker.
+
+You must be in the I<editcomments> group to use this method.
+
+=item B<Params>
+
+=over
+
+=item C<description|tracker>
+
+C<string> The description to use for the tracker.
+
+=item C<can_get> C<Optional>
+
+C<boolean> Should Bugzilla try and sync data from this tracker?
+
+=item C<regex> C<Optional>
+
+C<string> The type of data this field contains.
+
+If this is not set then IDs are not validated.
+
+ Number | ^[1-9]\d*$ | a number
+ Mantis Numbers | ^\d+$ | a number with possible leading zeros
+ JIRA Issue | ^[A-Z\d]+\-\d+$ | upper case letters or digits, a dash, and numbers
+ Number or Alias | ^(:?[1-9]\d*|[0-9A-Za-z])$ | a number or alias
+ SFDC Case | ^\d{8}$ | eight digits with possible leading zeros
+
+=item C<format> C<Optional>
+
+C<string> A printf style sting for formatting an ID in the UI.
+
+See L</"matchrx"> below.
+
+e.g. Github: '%s %s %s %s'
+
+=item C<full_url>
+
+C<string> The full URL to a tracker.
+
+e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=%id%
+
+=item C<matchrx> C<Optional>
+
+C<string> A regex used to split up the ID for use in formatting.
+
+See L</"format"> above.
+
+e.g. Github: (.*)\/(.*)/(issues|pull)/(.*)
+
+=item C<type> C<Optional>
+
+C<integer> The type of tracker that it is. Used for syncing data.
+
+Supported types:
+
+ None
+ Bugzilla
+ Gerrit
+ GitHub
+ JIRA
+ KBase
+ Redmine
+ SFDC
+ SFDC_DEV
+ SFDC_QA
+ SFDC_Sandbox
+ SFDC_Stage
+
+Type 'None' does not support syncing.
+
+=item C<url>
+
+C<string> The root URL for the site.
+
+e.g. https://bugzilla.mozilla.org
+
+=item C<groups> C<Optional>
+
+C<array> of group names to limit access to.
+
+=back
+
+=back
+
+=head2 edit_tracker
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Edit an external tracker.
+
+You must be in the I<editcomments> group to use this method.
+
+=item B<Params>
+
+=over
+
+=item C<tracker>
+
+C<integer|string> The ID or description of the tracker.
+
+=item C<can_get> C<Optional>
+
+C<boolean> Should Bugzilla try and sync data from this tracker?
+
+=item C<regex> C<Optional>
+
+C<string> The type of data this field contains.
+
+If this is not set then IDs are not validated.
+
+ Number | ^[1-9]\d*$ | a number
+ Mantis Numbers | ^\d+$ | a number with possible leading zeros
+ JIRA Issue | ^[A-Z\d]+\-\d+$ | upper case letters or digits, a dash, and numbers
+ Number or Alias | ^(:?[1-9]\d*|[0-9A-Za-z])$ | a number or alias
+ SFDC Case | ^\d{8}$ | eight digits with possible leading zeros
+
+=item C<format> C<Optional>
+
+C<string> A printf style sting for formatting an ID in the UI.
+
+See L</"matchrx"> below.
+
+e.g. Github: '%s %s %s %s'
+
+=item C<full_url>
+
+C<string> The full URL to a tracker.
+
+e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=%id%
+
+=item C<matchrx> C<Optional>
+
+C<string> A regex used to split up the ID for use in formatting.
+
+See L</"format"> above.
+
+e.g. Github: (.*)\/(.*)/(issues|pull)/(.*)
+
+=item C<type>
+
+C<integer> The type of tracker that it is. Used for syncing data.
+
+Supported types:
+
+ None
+ Bugzilla
+ Gerrit
+ GitHub
+ JIRA
+ KBase
+ Redmine
+ SFDC
+ SFDC_DEV
+ SFDC_QA
+ SFDC_Sandbox
+ SFDC_Stage
+
+Type 'None' does not support syncing.
+
+=item C<url>
+
+C<string> The root URL for the site.
+
+e.g. https://bugzilla.mozilla.org
+
+=item C<groups>
+
+C<array> of group names to limit access to.
+
+=back
+
+=back