Commit 32931bb0 authored by David Lawrence's avatar David Lawrence

Bug 540818 - Improve include_fields and exclude_fields to accept _default, _all…

Bug 540818 - Improve include_fields and exclude_fields to accept _default, _all and _custom keywords r=glob,a=justdave
parent ad9b1495
...@@ -319,6 +319,34 @@ for GET type requests. ...@@ -319,6 +319,34 @@ for GET type requests.
=back =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<include_fields>.
=item C<_default>
These fields are returned if C<include_fields> 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<include_fields> either by field name, or using C<_extra>.
=item C<_custom>
Only custom fields are returned if C<_custom> is specified in C<include_fields>.
This is normally specific to bug objects and not relevant for other returned
objects.
=back
=head1 SEE ALSO =head1 SEE ALSO
=head2 Server Types =head2 Server Types
......
...@@ -344,7 +344,7 @@ sub render_comment { ...@@ -344,7 +344,7 @@ sub render_comment {
# Helper for Bug.comments # Helper for Bug.comments
sub _translate_comment { sub _translate_comment {
my ($self, $comment, $filters) = @_; my ($self, $comment, $filters, $types, $prefix) = @_;
my $attach_id = $comment->is_about_attachment ? $comment->extra_data my $attach_id = $comment->is_about_attachment ? $comment->extra_data
: undef; : undef;
...@@ -369,7 +369,7 @@ sub _translate_comment { ...@@ -369,7 +369,7 @@ sub _translate_comment {
]; ];
} }
return filter $filters, $comment_hash; return filter($filters, $comment_hash, $types, $prefix);
} }
sub get { sub get {
...@@ -1194,7 +1194,7 @@ sub _bug_to_hash { ...@@ -1194,7 +1194,7 @@ sub _bug_to_hash {
# All the basic bug attributes are here, in alphabetical order. # All the basic bug attributes are here, in alphabetical order.
# A bug attribute is "basic" if it doesn't require an additional # A bug attribute is "basic" if it doesn't require an additional
# database call to get the info. # database call to get the info.
my %item = ( my %item = %{ filter $params, {
alias => $self->type('string', $bug->alias), alias => $self->type('string', $bug->alias),
creation_time => $self->type('dateTime', $bug->creation_ts), creation_time => $self->type('dateTime', $bug->creation_ts),
# No need to format $bug->deadline specially, because Bugzilla::Bug # No need to format $bug->deadline specially, because Bugzilla::Bug
...@@ -1214,15 +1214,14 @@ sub _bug_to_hash { ...@@ -1214,15 +1214,14 @@ sub _bug_to_hash {
url => $self->type('string', $bug->bug_file_loc), url => $self->type('string', $bug->bug_file_loc),
version => $self->type('string', $bug->version), version => $self->type('string', $bug->version),
whiteboard => $self->type('string', $bug->status_whiteboard), whiteboard => $self->type('string', $bug->status_whiteboard),
); } };
# First we handle any fields that require extra SQL calls. # First we handle any fields that require extra SQL calls.
# We don't do the SQL calls at all if the filter would just # We don't do the SQL calls at all if the filter would just
# eliminate them anyway. # eliminate them anyway.
if (filter_wants $params, 'assigned_to') { if (filter_wants $params, 'assigned_to') {
$item{'assigned_to'} = $self->type('email', $bug->assigned_to->login); $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') { if (filter_wants $params, 'blocks') {
my @blocks = map { $self->type('int', $_) } @{ $bug->blocked }; my @blocks = map { $self->type('int', $_) } @{ $bug->blocked };
...@@ -1237,11 +1236,11 @@ sub _bug_to_hash { ...@@ -1237,11 +1236,11 @@ sub _bug_to_hash {
if (filter_wants $params, 'cc') { if (filter_wants $params, 'cc') {
my @cc = map { $self->type('email', $_) } @{ $bug->cc }; my @cc = map { $self->type('email', $_) } @{ $bug->cc };
$item{'cc'} = \@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') { if (filter_wants $params, 'creator') {
$item{'creator'} = $self->type('email', $bug->reporter->login); $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') { if (filter_wants $params, 'depends_on') {
my @depends_on = map { $self->type('int', $_) } @{ $bug->dependson }; my @depends_on = map { $self->type('int', $_) } @{ $bug->dependson };
...@@ -1270,7 +1269,7 @@ sub _bug_to_hash { ...@@ -1270,7 +1269,7 @@ sub _bug_to_hash {
my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : ''; my $qa_login = $bug->qa_contact ? $bug->qa_contact->login : '';
$item{'qa_contact'} = $self->type('email', $qa_login); $item{'qa_contact'} = $self->type('email', $qa_login);
if ($bug->qa_contact) { 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') { if (filter_wants $params, 'see_also') {
...@@ -1286,7 +1285,7 @@ sub _bug_to_hash { ...@@ -1286,7 +1285,7 @@ sub _bug_to_hash {
my @custom_fields = Bugzilla->active_custom_fields; my @custom_fields = Bugzilla->active_custom_fields;
foreach my $field (@custom_fields) { foreach my $field (@custom_fields) {
my $name = $field->name; 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) { if ($field->type == FIELD_TYPE_BUG_ID) {
$item{$name} = $self->type('int', $bug->$name); $item{$name} = $self->type('int', $bug->$name);
} }
...@@ -1306,9 +1305,12 @@ sub _bug_to_hash { ...@@ -1306,9 +1305,12 @@ sub _bug_to_hash {
# Timetracking fields are only sent if the user can see them. # Timetracking fields are only sent if the user can see them.
if (Bugzilla->user->is_timetracker) { if (Bugzilla->user->is_timetracker) {
if (filter_wants $params, 'estimated_time') {
$item{'estimated_time'} = $self->type('double', $bug->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); $item{'remaining_time'} = $self->type('double', $bug->remaining_time);
}
if (filter_wants $params, 'actual_time') { if (filter_wants $params, 'actual_time') {
$item{'actual_time'} = $self->type('double', $bug->actual_time); $item{'actual_time'} = $self->type('double', $bug->actual_time);
} }
...@@ -1316,27 +1318,29 @@ sub _bug_to_hash { ...@@ -1316,27 +1318,29 @@ sub _bug_to_hash {
# The "accessible" bits go here because they have long names and it # The "accessible" bits go here because they have long names and it
# makes the code look nicer to separate them out. # makes the code look nicer to separate them out.
$item{'is_cc_accessible'} = $self->type('boolean', if (filter_wants $params, 'is_cc_accessible') {
$bug->cclist_accessible); $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_creator_accessible') {
$item{'is_creator_accessible'} = $self->type('boolean', $bug->reporter_accessible);
}
return filter $params, \%item; return \%item;
} }
sub _user_to_hash { sub _user_to_hash {
my ($self, $user, $filters, $prefix) = @_; my ($self, $user, $filters, $types, $prefix) = @_;
my $item = filter $filters, { my $item = filter $filters, {
id => $self->type('int', $user->id), id => $self->type('int', $user->id),
real_name => $self->type('string', $user->name), real_name => $self->type('string', $user->name),
name => $self->type('email', $user->login), name => $self->type('email', $user->login),
email => $self->type('email', $user->email), email => $self->type('email', $user->email),
}, $prefix; }, $types, $prefix;
return $item; return $item;
} }
sub _attachment_to_hash { sub _attachment_to_hash {
my ($self, $attach, $filters) = @_; my ($self, $attach, $filters, $types, $prefix) = @_;
my $item = filter $filters, { my $item = filter $filters, {
creation_time => $self->type('dateTime', $attach->attached), creation_time => $self->type('dateTime', $attach->attached),
...@@ -1350,25 +1354,25 @@ sub _attachment_to_hash { ...@@ -1350,25 +1354,25 @@ sub _attachment_to_hash {
is_private => $self->type('int', $attach->isprivate), is_private => $self->type('int', $attach->isprivate),
is_obsolete => $self->type('int', $attach->isobsolete), is_obsolete => $self->type('int', $attach->isobsolete),
is_patch => $self->type('int', $attach->ispatch), is_patch => $self->type('int', $attach->ispatch),
}; }, $types, $prefix;
# creator/attacher require an extra lookup, so we only send them if # creator/attacher require an extra lookup, so we only send them if
# the filter wants them. # the filter wants them.
foreach my $field (qw(creator attacher)) { 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); $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); $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); $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} ]; $item->{'flags'} = [ map { $self->_flag_to_hash($_) } @{$attach->flags} ];
} }
...@@ -2342,6 +2346,9 @@ Two items are returned: ...@@ -2342,6 +2346,9 @@ Two items are returned:
An array of hashes that contains information about the bugs with An array of hashes that contains information about the bugs with
the valid ids. Each hash contains the following items: the valid ids. Each hash contains the following items:
These fields are returned by default or by specifying C<_default>
in C<include_fields>.
=over =over
=item C<actual_time> =item C<actual_time>
...@@ -2588,7 +2595,11 @@ C<string> The value of the "status whiteboard" field on the bug. ...@@ -2588,7 +2595,11 @@ C<string> The value of the "status whiteboard" field on the bug.
Every custom field in this installation will also be included in the Every custom field in this installation will also be included in the
return value. Most fields are returned as C<string>s. However, some return value. Most fields are returned as C<string>s. 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<include_fields>.
=over =over
......
...@@ -41,39 +41,37 @@ sub get { ...@@ -41,39 +41,37 @@ sub get {
@classification_objs = grep { $selectable_class{$_->id} } @classification_objs; @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 }; return { classifications => \@classifications };
} }
sub _classification_to_hash { sub _classification_to_hash {
my ($self, $classification) = @_; my ($self, $classification, $params) = @_;
my $user = Bugzilla->user; my $user = Bugzilla->user;
return unless (Bugzilla->params->{'useclassification'} || $user->in_group('editclassifications')); return unless (Bugzilla->params->{'useclassification'} || $user->in_group('editclassifications'));
my $products = $user->in_group('editclassifications') ? my $products = $user->in_group('editclassifications') ?
$classification->products : $user->get_selectable_products($classification->id); $classification->products : $user->get_selectable_products($classification->id);
my %hash = (
return filter $params, {
id => $self->type('int', $classification->id), id => $self->type('int', $classification->id),
name => $self->type('string', $classification->name), name => $self->type('string', $classification->name),
description => $self->type('string', $classification->description), description => $self->type('string', $classification->description),
sort_key => $self->type('int', $classification->sortkey), sort_key => $self->type('int', $classification->sortkey),
products => [ map { $self->_product_to_hash($_) } @$products ], products => [ map { $self->_product_to_hash($_, $params) } @$products ],
); };
return \%hash;
} }
sub _product_to_hash { sub _product_to_hash {
my ($self, $product) = @_; my ($self, $product, $params) = @_;
my %hash = (
return filter $params, {
id => $self->type('int', $product->id), id => $self->type('int', $product->id),
name => $self->type('string', $product->name), name => $self->type('string', $product->name),
description => $self->type('string', $product->description), description => $self->type('string', $product->description),
); }, undef, 'products';
return \%hash;
} }
1; 1;
......
...@@ -255,7 +255,7 @@ sub _product_to_hash { ...@@ -255,7 +255,7 @@ sub _product_to_hash {
sub _component_to_hash { sub _component_to_hash {
my ($self, $component, $params) = @_; my ($self, $component, $params) = @_;
my $field_data = { my $field_data = filter $params, {
id => id =>
$self->type('int', $component->id), $self->type('int', $component->id),
name => name =>
...@@ -271,9 +271,9 @@ sub _component_to_hash { ...@@ -271,9 +271,9 @@ sub _component_to_hash {
0, 0,
is_active => is_active =>
$self->type('boolean', $component->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} = { $field_data->{flag_types} = {
bug => bug =>
[map { [map {
...@@ -285,12 +285,13 @@ sub _component_to_hash { ...@@ -285,12 +285,13 @@ sub _component_to_hash {
} @{$component->flag_types->{'attachment'}}], } @{$component->flag_types->{'attachment'}}],
}; };
} }
return filter($params, $field_data, 'components');
return $field_data;
} }
sub _flag_type_to_hash { sub _flag_type_to_hash {
my ($self, $flag_type) = @_; my ($self, $flag_type, $params) = @_;
return { return filter $params, {
id => id =>
$self->type('int', $flag_type->id), $self->type('int', $flag_type->id),
name => name =>
...@@ -313,12 +314,12 @@ sub _flag_type_to_hash { ...@@ -313,12 +314,12 @@ sub _flag_type_to_hash {
$self->type('int', $flag_type->grant_group_id), $self->type('int', $flag_type->grant_group_id),
request_group => request_group =>
$self->type('int', $flag_type->request_group_id), $self->type('int', $flag_type->request_group_id),
}; }, undef, 'flag_types';
} }
sub _version_to_hash { sub _version_to_hash {
my ($self, $version, $params) = @_; my ($self, $version, $params) = @_;
my $field_data = { return filter $params, {
id => id =>
$self->type('int', $version->id), $self->type('int', $version->id),
name => name =>
...@@ -327,13 +328,12 @@ sub _version_to_hash { ...@@ -327,13 +328,12 @@ sub _version_to_hash {
0, 0,
is_active => is_active =>
$self->type('boolean', $version->is_active), $self->type('boolean', $version->is_active),
}; }, undef, 'versions';
return filter($params, $field_data, 'versions');
} }
sub _milestone_to_hash { sub _milestone_to_hash {
my ($self, $milestone, $params) = @_; my ($self, $milestone, $params) = @_;
my $field_data = { return filter $params, {
id => id =>
$self->type('int', $milestone->id), $self->type('int', $milestone->id),
name => name =>
...@@ -342,8 +342,7 @@ sub _milestone_to_hash { ...@@ -342,8 +342,7 @@ sub _milestone_to_hash {
$self->type('int', $milestone->sortkey), $self->type('int', $milestone->sortkey),
is_active => is_active =>
$self->type('boolean', $milestone->is_active), $self->type('boolean', $milestone->is_active),
}; }, undef, 'milestones';
return filter($params, $field_data, 'milestones');
} }
1; 1;
......
...@@ -181,11 +181,11 @@ sub get { ...@@ -181,11 +181,11 @@ sub get {
} }
my $in_group = $self->_filter_users_by_group( my $in_group = $self->_filter_users_by_group(
\@user_objects, $params); \@user_objects, $params);
@users = map {filter $params, { @users = map { filter $params, {
id => $self->type('int', $_->id), id => $self->type('int', $_->id),
real_name => $self->type('string', $_->name), real_name => $self->type('string', $_->name),
name => $self->type('email', $_->login), name => $self->type('email', $_->login),
}} @$in_group; } } @$in_group;
return { users => \@users }; return { users => \@users };
} }
...@@ -233,7 +233,7 @@ sub get { ...@@ -233,7 +233,7 @@ sub get {
my $in_group = $self->_filter_users_by_group(\@user_objects, $params); my $in_group = $self->_filter_users_by_group(\@user_objects, $params);
foreach my $user (@$in_group) { foreach my $user (@$in_group) {
my $user_info = { my $user_info = filter $params, {
id => $self->type('int', $user->id), id => $self->type('int', $user->id),
real_name => $self->type('string', $user->name), real_name => $self->type('string', $user->name),
name => $self->type('email', $user->login), name => $self->type('email', $user->login),
...@@ -270,7 +270,7 @@ sub get { ...@@ -270,7 +270,7 @@ sub get {
} }
} }
push(@users, filter($params, $user_info)); push(@users, $user_info);
} }
return { users => \@users }; return { users => \@users };
......
...@@ -107,19 +107,19 @@ sub extract_flags { ...@@ -107,19 +107,19 @@ sub extract_flags {
return (\@old_flags, \@new_flags); return (\@old_flags, \@new_flags);
} }
sub filter ($$;$) { sub filter($$;$$) {
my ($params, $hash, $prefix) = @_; my ($params, $hash, $types, $prefix) = @_;
my %newhash = %$hash; my %newhash = %$hash;
foreach my $key (keys %$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; return \%newhash;
} }
sub filter_wants ($$;$) { sub filter_wants($$;$$) {
my ($params, $field, $prefix) = @_; my ($params, $field, $types, $prefix) = @_;
# Since this is operation is resource intensive, we will cache the results # Since this is operation is resource intensive, we will cache the results
# This assumes that $params->{*_fields} doesn't change between calls # This assumes that $params->{*_fields} doesn't change between calls
...@@ -130,28 +130,58 @@ sub filter_wants ($$;$) { ...@@ -130,28 +130,58 @@ sub filter_wants ($$;$) {
return $cache->{$field}; 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 %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] };
my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] }; my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
my $wants = 1; my %include_types;
if (defined $params->{exclude_fields} && $exclude{$field}) { my %exclude_types;
$wants = 0;
# 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};
}
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};
} }
elsif (defined $params->{include_fields} && !$include{$field}) {
my $wants = 0;
if ($prefix) { if ($prefix) {
# Include the field if the parent is include (and this one is not excluded) # Include the field if the parent is include (and this one is not excluded)
$wants = 0 if !$include{$prefix}; $wants = 1 if $include{$prefix};
} }
else { else {
# We want to include this if one of the sub keys is included # We want to include this if one of the sub keys is included
my $key = $field . '.'; my $key = $field . '.';
my $len = length($key); my $len = length($key);
$wants = 0 if ! grep { substr($_, 0, $len) eq $key } keys %include; $wants = 1 if grep { substr($_, 0, $len) eq $key } keys %include;
}
} }
$cache->{$field} = $wants; return $cache->{$field} = $wants;
return $wants;
} }
sub taint_data { sub taint_data {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment