From: David Lawrence Date: Fri, 11 Apr 2014 14:58:03 +0000 (+0000) Subject: Bug 540818 - Improve include_fields and exclude_fields to accept _default, _all and... X-Git-Tag: bugzilla-4.5.3~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=32931bb0370328c142e9d63f410d94f9db3c764d;p=thirdparty%2Fbugzilla.git Bug 540818 - Improve include_fields and exclude_fields to accept _default, _all and _custom keywords r=glob,a=justdave --- diff --git a/Bugzilla/WebService.pm b/Bugzilla/WebService.pm index 92ffed6591..9638d11323 100644 --- a/Bugzilla/WebService.pm +++ b/Bugzilla/WebService.pm @@ -319,6 +319,34 @@ for GET type requests. =back +There are several shortcut identifiers to ask for only certain groups of +fields to be returned or excluded. + +=over + +=item C<_all> + +All possible fields are returned if C<_all> is specified in C. + +=item C<_default> + +These fields are returned if C is empty or C<_default> +is specified. All fields described in the documentation are returned +by default unless specified otherwise. + +=item C<_extra> + +These fields are not returned by default and need to be manually specified +in C either by field name, or using C<_extra>. + +=item C<_custom> + +Only custom fields are returned if C<_custom> is specified in C. +This is normally specific to bug objects and not relevant for other returned +objects. + +=back + =head1 SEE ALSO =head2 Server Types diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm index f3ad8591db..3fc2322a1f 100644 --- a/Bugzilla/WebService/Bug.pm +++ b/Bugzilla/WebService/Bug.pm @@ -344,7 +344,7 @@ sub render_comment { # Helper for Bug.comments sub _translate_comment { - my ($self, $comment, $filters) = @_; + my ($self, $comment, $filters, $types, $prefix) = @_; my $attach_id = $comment->is_about_attachment ? $comment->extra_data : undef; @@ -369,7 +369,7 @@ sub _translate_comment { ]; } - return filter $filters, $comment_hash; + return filter($filters, $comment_hash, $types, $prefix); } sub get { @@ -1194,7 +1194,7 @@ sub _bug_to_hash { # All the basic bug attributes are here, in alphabetical order. # A bug attribute is "basic" if it doesn't require an additional # database call to get the info. - my %item = ( + my %item = %{ filter $params, { alias => $self->type('string', $bug->alias), creation_time => $self->type('dateTime', $bug->creation_ts), # No need to format $bug->deadline specially, because Bugzilla::Bug @@ -1214,15 +1214,14 @@ sub _bug_to_hash { url => $self->type('string', $bug->bug_file_loc), version => $self->type('string', $bug->version), whiteboard => $self->type('string', $bug->status_whiteboard), - ); - + } }; # First we handle any fields that require extra SQL calls. # We don't do the SQL calls at all if the filter would just # eliminate them anyway. if (filter_wants $params, 'assigned_to') { $item{'assigned_to'} = $self->type('email', $bug->assigned_to->login); - $item{'assigned_to_detail'} = $self->_user_to_hash($bug->assigned_to, $params, 'assigned_to'); + $item{'assigned_to_detail'} = $self->_user_to_hash($bug->assigned_to, $params, undef, 'assigned_to'); } if (filter_wants $params, 'blocks') { my @blocks = map { $self->type('int', $_) } @{ $bug->blocked }; @@ -1237,11 +1236,11 @@ sub _bug_to_hash { if (filter_wants $params, 'cc') { my @cc = map { $self->type('email', $_) } @{ $bug->cc }; $item{'cc'} = \@cc; - $item{'cc_detail'} = [ map { $self->_user_to_hash($_, $params, 'cc') } @{ $bug->cc_users } ]; + $item{'cc_detail'} = [ map { $self->_user_to_hash($_, $params, undef, 'cc') } @{ $bug->cc_users } ]; } if (filter_wants $params, 'creator') { $item{'creator'} = $self->type('email', $bug->reporter->login); - $item{'creator_detail'} = $self->_user_to_hash($bug->reporter, $params, 'creator'); + $item{'creator_detail'} = $self->_user_to_hash($bug->reporter, $params, undef, 'creator'); } if (filter_wants $params, 'depends_on') { my @depends_on = map { $self->type('int', $_) } @{ $bug->dependson }; @@ -1270,7 +1269,7 @@ sub _bug_to_hash { my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : ''; $item{'qa_contact'} = $self->type('email', $qa_login); if ($bug->qa_contact) { - $item{'qa_contact_detail'} = $self->_user_to_hash($bug->qa_contact, $params, 'qa_contact'); + $item{'qa_contact_detail'} = $self->_user_to_hash($bug->qa_contact, $params, undef, 'qa_contact'); } } if (filter_wants $params, 'see_also') { @@ -1286,7 +1285,7 @@ sub _bug_to_hash { my @custom_fields = Bugzilla->active_custom_fields; foreach my $field (@custom_fields) { my $name = $field->name; - next if !filter_wants $params, $name; + next if !filter_wants($params, $name, ['default', 'custom']); if ($field->type == FIELD_TYPE_BUG_ID) { $item{$name} = $self->type('int', $bug->$name); } @@ -1306,9 +1305,12 @@ sub _bug_to_hash { # Timetracking fields are only sent if the user can see them. if (Bugzilla->user->is_timetracker) { - $item{'estimated_time'} = $self->type('double', $bug->estimated_time); - $item{'remaining_time'} = $self->type('double', $bug->remaining_time); - + if (filter_wants $params, 'estimated_time') { + $item{'estimated_time'} = $self->type('double', $bug->estimated_time); + } + if (filter_wants $params, 'remaining_time') { + $item{'remaining_time'} = $self->type('double', $bug->remaining_time); + } if (filter_wants $params, 'actual_time') { $item{'actual_time'} = $self->type('double', $bug->actual_time); } @@ -1316,27 +1318,29 @@ sub _bug_to_hash { # The "accessible" bits go here because they have long names and it # makes the code look nicer to separate them out. - $item{'is_cc_accessible'} = $self->type('boolean', - $bug->cclist_accessible); - $item{'is_creator_accessible'} = $self->type('boolean', - $bug->reporter_accessible); + if (filter_wants $params, 'is_cc_accessible') { + $item{'is_cc_accessible'} = $self->type('boolean', $bug->cclist_accessible); + } + if (filter_wants $params, 'is_creator_accessible') { + $item{'is_creator_accessible'} = $self->type('boolean', $bug->reporter_accessible); + } - return filter $params, \%item; + return \%item; } sub _user_to_hash { - my ($self, $user, $filters, $prefix) = @_; + my ($self, $user, $filters, $types, $prefix) = @_; my $item = filter $filters, { id => $self->type('int', $user->id), real_name => $self->type('string', $user->name), name => $self->type('email', $user->login), email => $self->type('email', $user->email), - }, $prefix; + }, $types, $prefix; return $item; } sub _attachment_to_hash { - my ($self, $attach, $filters) = @_; + my ($self, $attach, $filters, $types, $prefix) = @_; my $item = filter $filters, { creation_time => $self->type('dateTime', $attach->attached), @@ -1350,25 +1354,25 @@ sub _attachment_to_hash { is_private => $self->type('int', $attach->isprivate), is_obsolete => $self->type('int', $attach->isobsolete), is_patch => $self->type('int', $attach->ispatch), - }; + }, $types, $prefix; # creator/attacher require an extra lookup, so we only send them if # the filter wants them. foreach my $field (qw(creator attacher)) { - if (filter_wants $filters, $field) { + if (filter_wants $filters, $field, $types, $prefix) { $item->{$field} = $self->type('email', $attach->attacher->login); } } - if (filter_wants $filters, 'data') { + if (filter_wants $filters, 'data', $types, $prefix) { $item->{'data'} = $self->type('base64', $attach->data); } - if (filter_wants $filters, 'size') { + if (filter_wants $filters, 'size', $types, $prefix) { $item->{'size'} = $self->type('int', $attach->datasize); } - if (filter_wants $filters, 'flags') { + if (filter_wants $filters, 'flags', $types, $prefix) { $item->{'flags'} = [ map { $self->_flag_to_hash($_) } @{$attach->flags} ]; } @@ -2342,6 +2346,9 @@ Two items are returned: An array of hashes that contains information about the bugs with the valid ids. Each hash contains the following items: +These fields are returned by default or by specifying C<_default> +in C. + =over =item C @@ -2588,7 +2595,11 @@ C The value of the "status whiteboard" field on the bug. Every custom field in this installation will also be included in the return value. Most fields are returned as Cs. However, some -field types have different return values: +field types have different return values. + +Normally custom fields are returned by default similar to normal bug +fields or you can specify only custom fields by using C<_custom> in +C. =over diff --git a/Bugzilla/WebService/Classification.pm b/Bugzilla/WebService/Classification.pm index 012af6268c..bbc967ce7a 100644 --- a/Bugzilla/WebService/Classification.pm +++ b/Bugzilla/WebService/Classification.pm @@ -41,39 +41,37 @@ sub get { @classification_objs = grep { $selectable_class{$_->id} } @classification_objs; } - my @classifications = map { filter($params, $self->_classification_to_hash($_)) } @classification_objs; + my @classifications = map { $self->_classification_to_hash($_, $params) } @classification_objs; return { classifications => \@classifications }; } sub _classification_to_hash { - my ($self, $classification) = @_; + my ($self, $classification, $params) = @_; my $user = Bugzilla->user; return unless (Bugzilla->params->{'useclassification'} || $user->in_group('editclassifications')); my $products = $user->in_group('editclassifications') ? $classification->products : $user->get_selectable_products($classification->id); - my %hash = ( + + return filter $params, { id => $self->type('int', $classification->id), name => $self->type('string', $classification->name), description => $self->type('string', $classification->description), sort_key => $self->type('int', $classification->sortkey), - products => [ map { $self->_product_to_hash($_) } @$products ], - ); - - return \%hash; + products => [ map { $self->_product_to_hash($_, $params) } @$products ], + }; } sub _product_to_hash { - my ($self, $product) = @_; - my %hash = ( + my ($self, $product, $params) = @_; + + return filter $params, { id => $self->type('int', $product->id), name => $self->type('string', $product->name), description => $self->type('string', $product->description), - ); - - return \%hash; + }, undef, 'products'; } 1; diff --git a/Bugzilla/WebService/Product.pm b/Bugzilla/WebService/Product.pm index 97f84f413f..2def778863 100644 --- a/Bugzilla/WebService/Product.pm +++ b/Bugzilla/WebService/Product.pm @@ -255,7 +255,7 @@ sub _product_to_hash { sub _component_to_hash { my ($self, $component, $params) = @_; - my $field_data = { + my $field_data = filter $params, { id => $self->type('int', $component->id), name => @@ -271,9 +271,9 @@ sub _component_to_hash { 0, is_active => $self->type('boolean', $component->is_active), - }; + }, undef, 'components'; - if (filter_wants($params, 'flag_types', 'components')) { + if (filter_wants($params, 'flag_types', undef, 'components')) { $field_data->{flag_types} = { bug => [map { @@ -285,12 +285,13 @@ sub _component_to_hash { } @{$component->flag_types->{'attachment'}}], }; } - return filter($params, $field_data, 'components'); + + return $field_data; } sub _flag_type_to_hash { - my ($self, $flag_type) = @_; - return { + my ($self, $flag_type, $params) = @_; + return filter $params, { id => $self->type('int', $flag_type->id), name => @@ -313,12 +314,12 @@ sub _flag_type_to_hash { $self->type('int', $flag_type->grant_group_id), request_group => $self->type('int', $flag_type->request_group_id), - }; + }, undef, 'flag_types'; } sub _version_to_hash { my ($self, $version, $params) = @_; - my $field_data = { + return filter $params, { id => $self->type('int', $version->id), name => @@ -327,13 +328,12 @@ sub _version_to_hash { 0, is_active => $self->type('boolean', $version->is_active), - }; - return filter($params, $field_data, 'versions'); + }, undef, 'versions'; } sub _milestone_to_hash { my ($self, $milestone, $params) = @_; - my $field_data = { + return filter $params, { id => $self->type('int', $milestone->id), name => @@ -342,8 +342,7 @@ sub _milestone_to_hash { $self->type('int', $milestone->sortkey), is_active => $self->type('boolean', $milestone->is_active), - }; - return filter($params, $field_data, 'milestones'); + }, undef, 'milestones'; } 1; diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm index fef354c449..f69ae8ed4c 100644 --- a/Bugzilla/WebService/User.pm +++ b/Bugzilla/WebService/User.pm @@ -181,11 +181,11 @@ sub get { } my $in_group = $self->_filter_users_by_group( \@user_objects, $params); - @users = map {filter $params, { + @users = map { filter $params, { id => $self->type('int', $_->id), real_name => $self->type('string', $_->name), name => $self->type('email', $_->login), - }} @$in_group; + } } @$in_group; return { users => \@users }; } @@ -233,7 +233,7 @@ sub get { my $in_group = $self->_filter_users_by_group(\@user_objects, $params); foreach my $user (@$in_group) { - my $user_info = { + my $user_info = filter $params, { id => $self->type('int', $user->id), real_name => $self->type('string', $user->name), name => $self->type('email', $user->login), @@ -270,7 +270,7 @@ sub get { } } - push(@users, filter($params, $user_info)); + push(@users, $user_info); } return { users => \@users }; diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm index bb27a0a331..bba6122e59 100644 --- a/Bugzilla/WebService/Util.pm +++ b/Bugzilla/WebService/Util.pm @@ -107,19 +107,19 @@ sub extract_flags { return (\@old_flags, \@new_flags); } -sub filter ($$;$) { - my ($params, $hash, $prefix) = @_; +sub filter($$;$$) { + my ($params, $hash, $types, $prefix) = @_; my %newhash = %$hash; foreach my $key (keys %$hash) { - delete $newhash{$key} if !filter_wants($params, $key, $prefix); + delete $newhash{$key} if !filter_wants($params, $key, $types, $prefix); } return \%newhash; } -sub filter_wants ($$;$) { - my ($params, $field, $prefix) = @_; +sub filter_wants($$;$$) { + my ($params, $field, $types, $prefix) = @_; # Since this is operation is resource intensive, we will cache the results # This assumes that $params->{*_fields} doesn't change between calls @@ -130,28 +130,58 @@ sub filter_wants ($$;$) { return $cache->{$field}; } + # Mimic old behavior if no types provided + my %field_types = map { $_ => 1 } (ref $types ? @$types : ($types || 'default')); + my %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] }; my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] }; - my $wants = 1; - if (defined $params->{exclude_fields} && $exclude{$field}) { - $wants = 0; + my %include_types; + my %exclude_types; + + # Only return default fields if nothing is specified + $include_types{default} = 1 if !%include; + + # Look for any field types requested + foreach my $key (keys %include) { + next if $key !~ /^_(.*)$/; + $include_types{$1} = 1; + delete $include{$key}; } - elsif (defined $params->{include_fields} && !$include{$field}) { - if ($prefix) { - # Include the field if the parent is include (and this one is not excluded) - $wants = 0 if !$include{$prefix}; - } - else { - # We want to include this if one of the sub keys is included - my $key = $field . '.'; - my $len = length($key); - $wants = 0 if ! grep { substr($_, 0, $len) eq $key } keys %include; - } + foreach my $key (keys %exclude) { + next if $key !~ /^_(.*)$/; + $exclude_types{$1} = 1; + delete $exclude{$key}; + } + + # If the user has asked to include all or exclude all + return $cache->{$field} = 0 if $exclude_types{'all'}; + return $cache->{$field} = 1 if $include_types{'all'}; + + # Explicit inclusion/exclusion + return $cache->{$field} = 0 if $exclude{$field}; + return $cache->{$field} = 1 if $include{$field}; + + # If the user has not asked for any fields specifically or if the user has asked + # for one or more of the field's types (and not excluded them) + foreach my $type (keys %field_types) { + return $cache->{$field} = 0 if $exclude_types{$type}; + return $cache->{$field} = 1 if $include_types{$type}; + } + + my $wants = 0; + if ($prefix) { + # Include the field if the parent is include (and this one is not excluded) + $wants = 1 if $include{$prefix}; + } + else { + # We want to include this if one of the sub keys is included + my $key = $field . '.'; + my $len = length($key); + $wants = 1 if grep { substr($_, 0, $len) eq $key } keys %include; } - $cache->{$field} = $wants; - return $wants; + return $cache->{$field} = $wants; } sub taint_data {