[Bps-public-commit] rt-extension-rest2 branch, rest2_api_search_enhancements, created. 1.07-8-g0498caa

Aaron Trevena ast at bestpractical.com
Mon Mar 30 06:08:22 EDT 2020


The branch, rest2_api_search_enhancements has been created
        at  0498caa050032d9d7da85176ced49f9cddfa7269 (commit)

- Log -----------------------------------------------------------------
commit 2b2f3422517561fb6ce928fed50d9ea9e387e887
Author: Andrew Ruthven <puck at catalystcloud.nz>
Date:   Tue Oct 29 07:35:21 2019 +0000

    Allow including CustomFields in search results.
    
    Based on Github PR #36

diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index c4b61c6..fdfe7b0 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -629,13 +629,15 @@ You can use additional fields parameters to expand child blocks, for
 example (line wrapping inserted for readability):
 
     XX_RT_URL_XX/REST/2.0/tickets
-      ?fields=Owner,Status,Created,Subject,Queue
+      ?fields=Owner,Status,Created,Subject,Queue,CustomFields
       &fields[Queue]=Name,Description
+      &fields[CustomFields]=name,values
 
 Says that in the result set for tickets, the extra fields for Owner, Status,
 Created, Subject and Queue should be included. But in addition, for the Queue
-block, also include Name and Description. The results would be similar to
-this (only one ticket is displayed):
+block, also include Name and Description, then include name and values for the
+CustomFields. The results would be similar to this (only one ticket is
+displayed in this example):
 
    "items" : [
       {
@@ -656,7 +658,18 @@ this (only one ticket is displayed):
             "Name" : "General",
             "Description" : "The default queue",
             "_url" : "XX_RT_URL_XX/REST/2.0/queue/1"
-         }
+         },
+         "CustomFields" : [
+             {
+                 "id" : "1",
+                 "type" : "customfield",
+                 "_url" : "XX_RT_URL_XX/REST/2.0/customfield/1",
+                 "name" : "My Custom Field",
+                 "values" : [
+                     "CustomField value"
+                 },
+             }
+         ]
       }
       { … },
       …
diff --git a/lib/RT/Extension/REST2/Resource.pm b/lib/RT/Extension/REST2/Resource.pm
index d7e611c..b66952e 100644
--- a/lib/RT/Extension/REST2/Resource.pm
+++ b/lib/RT/Extension/REST2/Resource.pm
@@ -5,7 +5,7 @@ use warnings;
 use Moose;
 use MooseX::NonMoose;
 use namespace::autoclean;
-use RT::Extension::REST2::Util qw(expand_uid format_datetime );
+use RT::Extension::REST2::Util qw(expand_uid format_datetime custom_fields_for);
 
 extends 'Web::Machine::Resource';
 
@@ -30,7 +30,57 @@ sub expand_field {
     my $param_prefix = shift || 'fields';
 
     my $result;
-    if ($item->can('_Accessible') && $item->_Accessible($field => 'read')) {
+    if ($field eq 'CustomFields') {
+        # Handle CustomFields differently.
+        #
+        # I feel terrible that CustomFields are returned using lowercase, but
+        # this is to keep it consistent with when CustomFields are turned as
+        # part of an object fetch.
+
+        if (my $cfs = custom_fields_for($item)) {
+            my %values;
+            while (my $cf = $cfs->Next) {
+                if (! defined $values{$cf->Id}) {
+                    $values{$cf->Id} = {
+                        %{ expand_uid($cf->UID) },
+                    };
+                }
+
+                my $param_field = $param_prefix . '[' . $field . ']';
+                my @subfields = split( /,/, $self->request->param($param_field) || '' );
+
+                for my $subfield (@subfields) {
+                    if ($subfield =~ /^[Vv]alues/) {
+                        my $ocfvs = $cf->ValuesForObject( $item );
+                        my $type  = $cf->Type;
+                        $values{$cf->Id}{values} = [];
+
+                        while (my $ocfv = $ocfvs->Next) {
+                            my $content = $ocfv->Content;
+                            if ($type eq 'DateTime') {
+                                $content = format_datetime($content);
+                            }
+                            elsif ($type eq 'Image' or $type eq 'Binary') {
+                                $content = {
+                                    content_type => $ocfv->ContentType,
+                                    filename     => $content,
+                                    _url         => RT::Extension::REST2->base_uri . "/download/cf/" . $ocfv->id,
+                                };
+                            }
+                            push @{ $values{$cf->Id}{values} }, $content;
+                        }
+                    } else {
+                        my $subfield_result = $self->expand_field( $cf, $subfield, $param_field );
+                        $values{$cf->Id}->{lc($subfield)} = $subfield_result if defined $subfield_result;
+                    }
+                }
+            }
+
+            push @{ $result }, values %values
+                if %values;
+        }
+
+    } elsif ($item->can('_Accessible') && $item->_Accessible($field => 'read')) {
         # RT::Record derived object, so we can check access permissions.
 
         if ($item->_Accessible($field => 'type') =~ /(datetime|timestamp)/i) {
diff --git a/xt/ticket-customfields.t b/xt/ticket-customfields.t
index 5e8174f..894acc0 100644
--- a/xt/ticket-customfields.t
+++ b/xt/ticket-customfields.t
@@ -95,6 +95,29 @@ my ($ticket_url, $ticket_id);
     is_deeply([grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [], 'No CF hypermedia');
 }
 
+my $no_ticket_cf_values = bag(
+  { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+  { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+);
+
+# Rights Test - Searching asking for CustomFields without SeeCustomField
+{
+    my $res = $mech->get("$rest_base_path/tickets?query=id>0&fields=Status,Owner,CustomFields,Subject&fields[Owner]=Name&fields[CustomFields]=Name,values",
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+    my $content = $mech->json_response;
+    is(scalar @{$content->{items}}, 1);
+
+    my $ticket = $content->{items}->[0];
+
+    is($ticket->{Status}, 'new');
+    is($ticket->{Owner}{Name}, 'Nobody');
+    is_deeply($ticket->{CustomFields}, '', 'Ticket custom field not present');
+    is($ticket->{Subject}, 'Ticket creation using REST');
+    is(scalar keys %$ticket, 7);
+}
+
 # Rights Test - With ShowTicket and SeeCustomField
 {
     $user->PrincipalObj->GrantRight( Right => 'SeeCustomField');
@@ -331,7 +354,28 @@ for my $value (
     is($content->{Subject}, 'Multi-value CF');
 
     my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference
+    my $ticket_cf_value = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => $output },
+    );
     is_deeply($content->{'CustomFields'}, { $multi_cf_id => $output, $single_cf_id => [] }, 'Ticket custom field');
+
+    # Ticket Show - Fields, custom fields
+    {
+        $res = $mech->get("$rest_base_path/tickets?query=id>0&fields=Status,Owner,CustomFields,Subject&fields[Owner]=Name&fields[CustomFields]=Name,values",
+            'Authorization' => $auth,
+        );
+        is($res->code, 200);
+        my $content = $mech->json_response;
+
+        # Just look at the last one.
+        my $ticket = $content->{items}->[-1];
+
+        is($ticket->{Status}, 'new');
+        is($ticket->{id}, $ticket_id);
+        is($ticket->{Subject}, 'Multi-value CF');
+        cmp_deeply($ticket->{'CustomFields'}, $ticket_cf_value, 'Ticket custom field');
+    }
 }
 
 {

commit a4239563febc13a71d314c7f3e645c4e9f92fe1d
Author: gibus <gibus at easter-eggs.com>
Date:   Sat Oct 20 23:18:01 2018 +0200

    Allow 'entry_aggregator' property in JSON search
    
    Add documentation for 'entry_aggregator' property in JSON searches
    
    Based on github PR #20

diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index fdfe7b0..de75b4d 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -588,6 +588,36 @@ C<orderby=Created&order=ASC&orderby=id&order=DESC>. C<orderby> and
 C<order> query parameters are supported in both JSON and TicketSQL
 searches.
 
+The same C<field> is specified more than one time to express more than
+one condition on this field. For example:
+
+    [
+        { "field":    "id",
+          "operator": ">",
+          "value":    $min },
+
+        { "field":     "id",
+          "operator": "<",
+          "value":    $max }
+    ]
+
+By default, RT will aggregate these conditions with an C<OR>, except for
+when searching queues, where a C<AND> is applied. If you want to search
+for multiple conditions on the same field aggregated with an C<AND>
+(or an C<OR> for queues), you have to specify C<entry_aggregator> keys
+in corresponding hashes:
+
+    [
+        { "field":    "id",
+          "operator": ">",
+          "value":    $min },
+
+        { "field":             "id",
+          "operator":         "<",
+          "value":            $max,
+          "entry_aggregator": "AND" }
+    ]
+
 Results are returned in
 L<the format described below|/"Example of plural resources (collections)">.
 
diff --git a/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm b/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
index a834042..3be0190 100644
--- a/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
+++ b/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
@@ -57,6 +57,9 @@ sub limit_collection {
                 ? (OPERATOR => $limit->{operator})
                 : () ),
             CASESENSITIVE => ($limit->{case_sensitive} || 0),
+            ( $limit->{entry_aggregator}
+                ? (ENTRYAGGREGATOR => $limit->{entry_aggregator})
+                : () ),
         );
     }
 

commit bb859bc71f08971412f8f6e073e6263aa8487c54
Author: gibus <gibus at easter-eggs.com>
Date:   Sat Oct 20 23:17:42 2018 +0200

    Add tests for 'entry_aggregator' property in JSON searches
    
    Based on github PR #20

diff --git a/xt/search-json.t b/xt/search-json.t
index e1fee44..1d91ece 100644
--- a/xt/search-json.t
+++ b/xt/search-json.t
@@ -137,6 +137,7 @@ my $bravo_id = $bravo->Id;
     is($content->{message}, 'JSON object must be a ARRAY');
 }
 
+
 # Sorted search
 {
     my $res = $mech->post_json("$rest_base_path/queues?orderby=Description&order=DESC&orderby=id",
@@ -166,5 +167,86 @@ my $bravo_id = $bravo->Id;
     like($third->{_url}, qr{$rest_base_path/queue/$beta_id$});
 }
 
+# Aggregate conditions with OR, Queues defaults to AND
+{
+    my $res = $mech->post_json("$rest_base_path/queues",
+        [
+            { field => 'id', operator => '>', value => 2 },
+            { entry_aggregator => 'OR', field => 'id', operator => '<', value => 4 },
+        ],
+        'Authorization' => $auth,
+    );
+
+    my $content = $mech->json_response;
+    is($content->{count}, 4);
+    is($content->{page}, 1);
+    is($content->{per_page}, 20);
+    is($content->{total}, 4);
+    is(scalar @{$content->{items}}, 4);
+    my @ids = sort map {$_->{id}} @{$content->{items}};
+    is_deeply(\@ids, [1, $alpha_id, $beta_id, $bravo_id]);
+}
+
+# Aggregate conditions with AND, Queues defaults to AND
+{
+    my $res = $mech->post_json("$rest_base_path/queues",
+        [
+            { field => 'id', operator => '>', value => 2 },
+            { field => 'id', operator => '<', value => 4 },
+        ],
+        'Authorization' => $auth,
+    );
+
+    my $content = $mech->json_response;
+    is($content->{count}, 1);
+    is($content->{page}, 1);
+    is($content->{per_page}, 20);
+    is($content->{total}, 1);
+    is(scalar @{$content->{items}}, 1);
+    is($content->{items}->[0]->{id}, $alpha_id);
+}
+
+my $cf1 = RT::Test->load_or_create_custom_field(Name  => 'cf1', Type  => 'FreeformSingle', Queue => 'General');
+my $cf2 = RT::Test->load_or_create_custom_field(Name  => 'cf2', Type  => 'FreeformSingle', Queue => 'General');
+my $cf3 = RT::Test->load_or_create_custom_field(Name  => 'cf3', Type  => 'FreeformSingle', Queue => 'General');
+# Aggregate conditions with OR, CustomFields defaults to OR
+{
+    my $res = $mech->post_json("$rest_base_path/customfields",
+        [
+            { field => 'id', operator => '>', value => 2 },
+            { field => 'id', operator => '<', value => 4 },
+        ],
+        'Authorization' => $auth,
+    );
+
+    my $content = $mech->json_response;
+    is($content->{count}, 4);
+    is($content->{page}, 1);
+    is($content->{per_page}, 20);
+    is($content->{total}, 4);
+    is(scalar @{$content->{items}}, 4);
+    my @ids = sort map {$_->{id}} @{$content->{items}};
+    is_deeply(\@ids, [1, 2, 3, 4]);
+}
+
+# Aggregate conditions with AND, CustomFields defaults to OR
+{
+    my $res = $mech->post_json("$rest_base_path/customfields",
+        [
+            { field => 'id', operator => '>', value => 2 },
+            { entry_aggregator => 'AND', field => 'id', operator => '<', value => 4 },
+        ],
+        'Authorization' => $auth,
+    );
+
+    my $content = $mech->json_response;
+    is($content->{count}, 1);
+    is($content->{page}, 1);
+    is($content->{per_page}, 20);
+    is($content->{total}, 1);
+    is(scalar @{$content->{items}}, 1);
+    is($content->{items}->[0]->{id}, 3);
+}
+
 done_testing;
 

commit 7cf51deb2b09a6728257bb343c622e48417956b3
Author: Andrew Ruthven <puck at catalystcloud.nz>
Date:   Fri Oct 25 06:29:15 2019 +0000

    Improve the user experience of pagination.
    
    Collection results which require pagination now include "pages",
    "next_page" and "prev_page". The later two only if there are next
    or previous pages. For example:
    
    https://10.0.19.168/REST/2.0/queues/all?per_page=5\&page=3
    
    {
       "count" : 5,
       "pages" : 8,
       "page" : 3,
       "per_page" : 5,
       "prev_page" : "https://10.0.19.168/REST/2.0/queues/all?per_page=5&page=2",
       "next_page" : "https://10.0.19.168/REST/2.0/queues/all?per_page=5&page=4",
       "total" : 37,
       "items" : [ ... ]
    }
    
    Don't limit the page param by a guess of the number of pages.
    
    Because we haven't applied the limits to the query yet, the call
    to CountAll returns the total number of results. Therefore, using
    that to limit the number of available of pages is useless.
    
    Based on public github PR #33

diff --git a/Changes b/Changes
index c756d8c..8ef34d8 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,8 @@
 Revision history for RT-Extension-REST2
 
+Pending
+ - Insert total page count and previous/next links for plural collection results
+
 1.07 2019-05-24
  - Accept 'Content' as a parameter on create. The documentation previously showed
    this in examples, but it wasn't yet supported. Now it works as documented.
diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index de75b4d..6f1e6dc 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -69,6 +69,7 @@ see a response, typical of search results, like this:
        "total" : 1,
        "count" : 1,
        "page" : 1,
+       "pages" : 1,
        "per_page" : 20,
        "items" : [
           {
@@ -629,7 +630,9 @@ standard JSON format:
     {
        "count" : 20,
        "page" : 1,
+       "pages" : 191,
        "per_page" : 20,
+       "next_page" : "<collection path>?page=2"
        "total" : 3810,
        "items" : [
           { … },
@@ -646,7 +649,10 @@ is requested.
 All plural resources (such as C</tickets>) require pagination, controlled by
 the query parameters C<page> and C<per_page>.  The default page size is 20
 items, but it may be increased up to 100 (or decreased if desired).  Page
-numbers start at 1.
+numbers start at 1. The number of pages is returned, and if there is a next
+or previous page, then the URL for that page is returned in the next_page
+and prev_page variables respectively. It is up to you to store the required
+JSON to pass with the following page request.
 
 =head2 Fields
 
diff --git a/lib/RT/Extension/REST2/Resource/Collection.pm b/lib/RT/Extension/REST2/Resource/Collection.pm
index f646885..db8b11f 100644
--- a/lib/RT/Extension/REST2/Resource/Collection.pm
+++ b/lib/RT/Extension/REST2/Resource/Collection.pm
@@ -11,6 +11,7 @@ use Scalar::Util qw( blessed );
 use Web::Machine::FSM::States qw( is_status_code );
 use Module::Runtime qw( require_module );
 use RT::Extension::REST2::Util qw( serialize_record expand_uid format_datetime );
+use POSIX qw( ceil );
 
 has 'collection_class' => (
     is  => 'ro',
@@ -33,12 +34,14 @@ sub _build_collection {
 sub setup_paging {
     my $self = shift;
     my $per_page = $self->request->param('per_page') || 20;
-       $per_page = 20  if $per_page <= 0;
-       $per_page = 100 if $per_page > 100;
+    if    ($per_page !~ /^\d+$/ ) { $per_page = 20  }
+    elsif ($per_page == 0       ) { $per_page = 20  }
+    elsif ($per_page > 100      ) { $per_page = 100 }
     $self->collection->RowsPerPage($per_page);
 
     my $page = $self->request->param('page') || 1;
-       $page = 1 if $page < 0;
+    if    ($page !~ /^\d+$/  ) { $page = 1 }
+    elsif ($page == 0        ) { $page = 1 }
     $self->collection->GotoPage($page - 1);
 }
 
@@ -68,13 +71,39 @@ sub serialize {
         }
         push @results, $result;
     }
-    return {
+
+    my %results = (
         count       => scalar(@results)         + 0,
         total       => $collection->CountAll    + 0,
         per_page    => $collection->RowsPerPage + 0,
         page        => ($collection->FirstRow / $collection->RowsPerPage) + 1,
         items       => \@results,
+    );
+
+    my $uri = $self->request->uri;
+    my @query_form = $uri->query_form;
+    # find page and if it is set, delete it and it's value.
+    for my $i (0..$#query_form) {
+        if ($query_form[$i] eq 'page') {
+            delete @query_form[$i, $i + 1];
+            last;
+        }
+    }
+
+    $results{pages} = ceil($results{total} / $results{per_page});
+    if ($results{page} < $results{pages}) {
+        my $page = $results{page} + 1;
+        $uri->query_form( @query_form, page => $results{page} + 1 );
+        $results{next_page} = $uri->as_string;
+    };
+    if ($results{page} > 1) {
+        # If we're beyond the last page, set prev_page as the last page
+        # available, otherwise, the previous page.
+        $uri->query_form( @query_form, page => ($results{page} > $results{pages} ? $results{pages} : $results{page} - 1) );
+        $results{prev_page} = $uri->as_string;
     };
+
+    return \%results;
 }
 
 # XXX TODO: Bulk update via DELETE/PUT on a collection resource?
diff --git a/xt/pagination.t b/xt/pagination.t
new file mode 100644
index 0000000..60c077b
--- /dev/null
+++ b/xt/pagination.t
@@ -0,0 +1,166 @@
+use strict;
+use warnings;
+use RT::Extension::REST2::Test tests => undef;
+
+my $mech = RT::Extension::REST2::Test->mech;
+my $auth = RT::Extension::REST2::Test->authorization_header;
+my $rest_base_path = '/REST/2.0';
+my $user = RT::Extension::REST2::Test->user;
+
+my $alpha = RT::Test->load_or_create_queue( Name => 'Alpha' );
+my $bravo = RT::Test->load_or_create_queue( Name => 'Bravo' );
+$user->PrincipalObj->GrantRight( Right => 'SuperUser' );
+
+my $alpha_id = $alpha->Id;
+my $bravo_id = $bravo->Id;
+
+# Default per_page (20), only 1 page.
+{
+    my $res = $mech->post_json("$rest_base_path/queues/all",
+        [],
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    my $content = $mech->json_response;
+    is($content->{count}, 3);
+    is($content->{page}, 1);
+    is($content->{pages}, 1);
+    is($content->{per_page}, 20);
+    is($content->{total}, 3);
+    undef($content->{prev_page});
+    undef($content->{next_page});
+    is(scalar @{$content->{items}}, 3);
+}
+
+# per_page = 3, only 1 page.
+{
+    my $res = $mech->post_json("$rest_base_path/queues/all?per_page=3",
+        [],
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    my $content = $mech->json_response;
+    is($content->{count}, 3);
+    is($content->{page}, 1);
+    is($content->{pages}, 1);
+    is($content->{per_page}, 3);
+    is($content->{total}, 3);
+    undef($content->{prev_page});
+    undef($content->{next_page});
+    is(scalar @{$content->{items}}, 3);
+}
+
+# per_page = 1, 3 pages, page 1.
+{
+    my $url = "$rest_base_path/queues/all?per_page=1";
+    my $res = $mech->post_json($url,
+        [],
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    # Ensure our use of $url as a regex works.
+    $url =~ s/\?/\\?/;
+
+    my $content = $mech->json_response;
+    is($content->{count}, 1);
+    is($content->{page}, 1);
+    is($content->{pages}, 3);
+    is($content->{per_page}, 1);
+    is($content->{total}, 3);
+    undef($content->{prev_page});
+    like($content->{next_page}, qr[$url&page=2]);
+    is(scalar @{$content->{items}}, 1);
+}
+
+# per_page = 1, 3 pages, page 2.
+{
+    my $url = "$rest_base_path/queues/all?per_page=1";
+    my $res = $mech->post_json("$url&page=2",
+        [],
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    # Ensure our use of $url as a regex works.
+    $url =~ s/\?/\\?/;
+
+    my $content = $mech->json_response;
+    is($content->{count}, 1);
+    is($content->{page}, 2);
+    is($content->{pages}, 3);
+    is($content->{per_page}, 1);
+    is($content->{total}, 3);
+    like($content->{prev_page}, qr[$url&page=1]);
+    like($content->{next_page}, qr[$url&page=3]);
+    is(scalar @{$content->{items}}, 1);
+}
+
+# per_page = 1, 3 pages, page 3.
+{
+    my $url = "$rest_base_path/queues/all?per_page=1";
+    my $res = $mech->post_json("$url&page=3",
+        [],
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    # Ensure our use of $url as a regex works.
+    $url =~ s/\?/\\?/;
+
+    my $content = $mech->json_response;
+    is($content->{count}, 1);
+    is($content->{page}, 3);
+    is($content->{pages}, 3);
+    is($content->{per_page}, 1);
+    is($content->{total}, 3);
+    like($content->{prev_page}, qr[$url&page=2]);
+    undef($content->{next_page});
+    is(scalar @{$content->{items}}, 1);
+}
+
+# Test sanity checking for the pagination parameters.
+{
+    my $url = "$rest_base_path/queues/all";
+    for my $param ( 'per_page', 'page' ) {
+	for my $value ( 'abc', '-10', '30' ) {
+	    # No need to test the following combination.
+	    next if $param eq 'per_page' && $value eq '30';
+
+	    my $res = $mech->post_json("$url?$param=$value",
+		[],
+		'Authorization' => $auth,
+	    );
+	    is($res->code, 200);
+
+	    my $content = $mech->json_response;
+	    if ($param eq 'page') {
+		if ($value eq '30') {
+		    is($content->{count}, 0);
+		    is($content->{page}, 30);
+		    is(scalar @{$content->{items}}, 0);
+		    like($content->{prev_page}, qr[$url\?page=1]);
+		} else {
+		    is($content->{count}, 3);
+		    is($content->{page}, 1);
+		    is(scalar @{$content->{items}}, 3);
+		    is($content->{prev_page}, undef);
+		}
+	    }
+	    is($content->{pages}, 1);
+	    if ($param eq 'per_page') {
+		if ($value eq '30') {
+		    is($content->{per_page}, 30);
+		} else {
+		    is($content->{per_page}, 20);
+		}
+	    }
+	    is($content->{total}, 3);
+	    is($content->{next_page}, undef);
+	}
+    }
+}
+
+done_testing;
diff --git a/xt/tickets.t b/xt/tickets.t
index 695d9a4..670fd22 100644
--- a/xt/tickets.t
+++ b/xt/tickets.t
@@ -164,6 +164,7 @@ my ($ticket_url, $ticket_id);
     my $content = $mech->json_response;
     is($content->{count}, 1);
     is($content->{page}, 1);
+    is($content->{pages}, 1);
     is($content->{per_page}, 20);
     is($content->{total}, 1);
     is(scalar @{$content->{items}}, 1);

commit 3041f81b6a544e280d1424561d1e3f5b63b1f18e
Author: gibus <gibus at easter-eggs.com>
Date:   Mon Mar 30 10:07:03 2020 +0100

    Allow JSON searches to return disabled objects with 'find_disabled_rows=1' query parameter
    
    Add documentation for JSON searches to return disabled objects with 'find_disabled_rows=1' query parameter
    
    Based on public github PR #19

diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index 6f1e6dc..c2b5088 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -566,6 +566,8 @@ values).  An example:
         ]
     '
 
+By default, only enabled objects are returned. For results to include also disabled objectn you should specify C<find_disabled_rows=1> as query parameter.
+
 The JSON payload must be an array of hashes with the keys C<field> and C<value>
 and optionally C<operator>.
 
diff --git a/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm b/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
index 3be0190..9a7756c 100644
--- a/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
+++ b/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
@@ -45,6 +45,9 @@ sub limit_collection {
     my @fields      = $self->searchable_fields;
     my %searchable  = map {; $_ => 1 } @fields;
 
+    $collection->{'find_disabled_rows'} = 1
+        if $self->request->param('find_disabled_rows');
+
     for my $limit (@$query) {
         next unless $limit->{field}
                 and $searchable{$limit->{field}}

commit 184bdab75ebcaf252f8e1710efb62bcff74eca91
Author: gibus <gibus at easter-eggs.com>
Date:   Mon Mar 30 10:15:36 2020 +0100

    Tests for JSON searches returning disabled objects with 'find_disabled_rows=1'
    
    Based on github PR #19

diff --git a/xt/search-json.t b/xt/search-json.t
index 1d91ece..ad93aa3 100644
--- a/xt/search-json.t
+++ b/xt/search-json.t
@@ -248,5 +248,58 @@ my $cf3 = RT::Test->load_or_create_custom_field(Name  => 'cf3', Type  => 'Freefo
     is($content->{items}->[0]->{id}, 3);
 }
 
+# Find disabled row
+{
+    $alpha->SetDisabled(1);
+
+    my $res = $mech->post_json("$rest_base_path/queues",
+        [{ field => 'id', operator => '>', value => 2 }],
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    my $content = $mech->json_response;
+    is($content->{count}, 2);
+    is($content->{page}, 1);
+    is($content->{per_page}, 20);
+    is($content->{total}, 2);
+    is(scalar @{$content->{items}}, 2);
+
+    my ($first, $second) = @{ $content->{items} };
+    is($first->{type}, 'queue');
+    is($first->{id}, $beta_id);
+    like($first->{_url}, qr{$rest_base_path/queue/$beta_id$});
+
+    is($second->{type}, 'queue');
+    is($second->{id}, $bravo_id);
+    like($second->{_url}, qr{$rest_base_path/queue/$bravo_id$});
+
+    my $res_disabled = $mech->post_json("$rest_base_path/queues?find_disabled_rows=1",
+        [{ field => 'id', operator => '>', value => 2 }],
+        'Authorization' => $auth,
+    );
+    is($res_disabled->code, 200);
+
+    my $content_disabled = $mech->json_response;
+    is($content_disabled->{count}, 3);
+    is($content_disabled->{page}, 1);
+    is($content_disabled->{per_page}, 20);
+    is($content_disabled->{total}, 3);
+    is(scalar @{$content_disabled->{items}}, 3);
+
+    my ($first_disabled, $second_disabled, $third_disabled) = @{ $content_disabled->{items} };
+    is($first_disabled->{type}, 'queue');
+    is($first_disabled->{id}, $alpha_id);
+    like($first_disabled->{_url}, qr{$rest_base_path/queue/$alpha_id$});
+
+    is($second_disabled->{type}, 'queue');
+    is($second_disabled->{id}, $beta_id);
+    like($second_disabled->{_url}, qr{$rest_base_path/queue/$beta_id$});
+
+    is($third_disabled->{type}, 'queue');
+    is($third_disabled->{id}, $bravo_id);
+    like($third_disabled->{_url}, qr{$rest_base_path/queue/$bravo_id$});
+}
+
 done_testing;
 

commit 0498caa050032d9d7da85176ced49f9cddfa7269
Author: Aaron Trevena <ast at bestpractical.com>
Date:   Mon Mar 30 10:08:13 2020 +0100

    Update Changes file with new features

diff --git a/Changes b/Changes
index 8ef34d8..fd52e3a 100644
--- a/Changes
+++ b/Changes
@@ -2,6 +2,9 @@ Revision history for RT-Extension-REST2
 
 Pending
  - Insert total page count and previous/next links for plural collection results
+ - Allow 'entry_aggregator' property in JSON search
+ - Allow JSON searches to return disabled objects with 'find_disabled_rows=1' query parameter
+ - Allow including CustomFields in search results
 
 1.07 2019-05-24
  - Accept 'Content' as a parameter on create. The documentation previously showed

-----------------------------------------------------------------------


More information about the Bps-public-commit mailing list