[Bps-public-commit] rt-extension-rest2 branch, saved-searches, created. 1.12-14-ge4442b5
? sunnavy
sunnavy at bestpractical.com
Thu May 27 16:30:47 EDT 2021
The branch, saved-searches has been created
at e4442b52ddd28808f631c2e236b6eaee4ab11c6b (commit)
- Log -----------------------------------------------------------------
commit 7f6868c16a443f6cc2d74de9dadc0eff0c5cf936
Author: sunnavy <sunnavy at bestpractical.com>
Date: Thu May 27 05:42:56 2021 +0800
Support additional fields parameters for Roles and CustomFields in Collection
E.g. fields=Requestor&fields[Requestor]=EmailAddress
diff --git a/lib/RT/Extension/REST2/Resource.pm b/lib/RT/Extension/REST2/Resource.pm
index 594f5c9..c58fe9b 100644
--- a/lib/RT/Extension/REST2/Resource.pm
+++ b/lib/RT/Extension/REST2/Resource.pm
@@ -36,7 +36,7 @@ sub expand_field {
while (my $cf = $cfs->Next) {
if (! defined $values{$cf->Id}) {
$values{$cf->Id} = {
- %{ expand_uid($cf->UID) },
+ %{ $self->_expand_object($cf, $field, $param_prefix) },
name => $cf->Name,
values => [],
};
@@ -71,13 +71,14 @@ sub expand_field {
my $group = $item->RoleGroup($role);
if ( !$group->Id ) {
- $data{$role} = expand_uid( RT->Nobody->UserObj->UID ) if $item->_ROLES->{$role}{Single};
+ $data{$role} = $self->_expand_object( RT->Nobody->UserObj, $field, $param_prefix )
+ if $item->_ROLES->{$role}{Single};
next;
}
my $gms = $group->MembersObj;
while ( my $gm = $gms->Next ) {
- push @{ $data{$role} }, expand_uid( $gm->MemberObj->Object->UID );
+ push @{ $data{$role} }, $self->_expand_object( $gm->MemberObj->Object, $field, $param_prefix );
}
# Avoid the extra array ref for single member roles
@@ -91,13 +92,14 @@ sub expand_field {
my $group = $item->RoleGroup($field);
if ( !$group->Id ) {
- $result = expand_uid( RT->Nobody->UserObj->UID ) if $item->_ROLES->{$field}{Single};
+ $result = $self->_expand_object( RT->Nobody->UserObj, $field, $param_prefix )
+ if $item->_ROLES->{$field}{Single};
next;
}
my $gms = $group->MembersObj;
while ( my $gm = $gms->Next ) {
- push @$result, expand_uid( $gm->MemberObj->Object->UID );
+ push @$result, $self->_expand_object( $gm->MemberObj->Object, $field, $param_prefix );
}
# Avoid the extra array ref for single member roles
@@ -112,14 +114,8 @@ sub expand_field {
} elsif ($item->can($field . 'Obj')) {
my $method = $field . 'Obj';
my $obj = $item->$method;
- if ( $obj->can('UID') and $result = expand_uid( $obj->UID ) ) {
- my $param_field = $param_prefix . '[' . $field . ']';
- my @subfields = split( /,/, $self->request->param($param_field) || '' );
-
- for my $subfield (@subfields) {
- my $subfield_result = $self->expand_field( $obj, $subfield, $param_field );
- $result->{$subfield} = $subfield_result if defined $subfield_result;
- }
+ if ( $obj->can('UID') ) {
+ $result = $self->_expand_object( $obj, $field, $param_prefix );
}
}
@@ -129,6 +125,25 @@ sub expand_field {
return $result // '';
}
+sub _expand_object {
+ my $self = shift;
+ my $object = shift;
+ my $field = shift;
+ my $param_prefix = shift || 'fields';
+
+ return unless $object->can('UID');
+
+ my $result = expand_uid( $object->UID ) or return;
+ my $param_field = $param_prefix . '[' . $field . ']';
+ my @subfields = split( /,/, $self->request->param($param_field) || '' );
+
+ for my $subfield (@subfields) {
+ my $subfield_result = $self->expand_field( $object, $subfield, $param_field );
+ $result->{$subfield} = $subfield_result if defined $subfield_result;
+ }
+ return $result;
+}
+
__PACKAGE__->meta->make_immutable;
1;
diff --git a/lib/RT/Extension/REST2/Resource/Assets.pm b/lib/RT/Extension/REST2/Resource/Assets.pm
index 5144833..ae218d9 100644
--- a/lib/RT/Extension/REST2/Resource/Assets.pm
+++ b/lib/RT/Extension/REST2/Resource/Assets.pm
@@ -28,7 +28,7 @@ sub expand_field {
if ( my $group = $item->RoleGroup($role) ) {
my $gms = $group->MembersObj;
while ( my $gm = $gms->Next ) {
- push @$members, expand_uid( $gm->MemberObj->Object->UID );
+ push @$members, $self->_expand_object( $gm->MemberObj->Object, $field, $param_prefix );
}
$members = shift @$members if $group->SingleMemberRoleGroup;
}
diff --git a/lib/RT/Extension/REST2/Resource/Tickets.pm b/lib/RT/Extension/REST2/Resource/Tickets.pm
index 8abbc7d..f060d49 100644
--- a/lib/RT/Extension/REST2/Resource/Tickets.pm
+++ b/lib/RT/Extension/REST2/Resource/Tickets.pm
@@ -77,7 +77,7 @@ sub expand_field {
if ( my $group = $item->RoleGroup($role) ) {
my $gms = $group->MembersObj;
while ( my $gm = $gms->Next ) {
- push @$members, expand_uid( $gm->MemberObj->Object->UID );
+ push @$members, $self->_expand_object( $gm->MemberObj->Object, $field, $param_prefix );
}
}
return $members;
commit 649dc129ac4a14f42aaf311ae0c754c26f8f1464
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue May 25 22:26:53 2021 +0800
Abstract setup_ordering to simplify limit_collection and also avoid duplication
diff --git a/lib/RT/Extension/REST2/Resource/Collection.pm b/lib/RT/Extension/REST2/Resource/Collection.pm
index ee3245a..c27df40 100644
--- a/lib/RT/Extension/REST2/Resource/Collection.pm
+++ b/lib/RT/Extension/REST2/Resource/Collection.pm
@@ -12,6 +12,7 @@ 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 );
+use Encode;
has 'collection_class' => (
is => 'ro',
@@ -45,6 +46,21 @@ sub setup_paging {
$self->collection->GotoPage($page - 1);
}
+sub setup_ordering {
+ my $self = shift;
+ my @orderby_cols;
+ my @orders = $self->request->param('order');
+ foreach my $orderby ($self->request->param('orderby')) {
+ $orderby = decode_utf8($orderby);
+ my $order = shift @orders || 'ASC';
+ $order = uc(decode_utf8($order));
+ $order = 'ASC' unless $order eq 'DESC';
+ push @orderby_cols, {FIELD => $orderby, ORDER => $order};
+ }
+ $self->collection->OrderByCols(@orderby_cols)
+ if @orderby_cols;
+}
+
sub limit_collection {
my $self = shift;
my $collection = $self->collection;
@@ -57,6 +73,7 @@ sub limit_collection {
sub search {
my $self = shift;
$self->setup_paging;
+ $self->setup_ordering;
return $self->limit_collection;
}
diff --git a/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm b/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
index 9a7756c..b4e606c 100644
--- a/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
+++ b/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
@@ -66,17 +66,6 @@ sub limit_collection {
);
}
- my @orderby_cols;
- my @orders = $self->request->param('order');
- foreach my $orderby ($self->request->param('orderby')) {
- my $order = shift @orders || 'ASC';
- $order = uc($order);
- $order = 'ASC' unless $order eq 'DESC';
- push @orderby_cols, {FIELD => $orderby, ORDER => $order};
- }
- $self->collection->OrderByCols(@orderby_cols)
- if @orderby_cols;
-
return 1;
}
diff --git a/lib/RT/Extension/REST2/Resource/Tickets.pm b/lib/RT/Extension/REST2/Resource/Tickets.pm
index f060d49..1398d28 100644
--- a/lib/RT/Extension/REST2/Resource/Tickets.pm
+++ b/lib/RT/Extension/REST2/Resource/Tickets.pm
@@ -46,25 +46,13 @@ sub allowed_methods {
[ 'GET', 'HEAD', 'POST' ]
}
-sub limit_collection {
+override 'limit_collection' => sub {
my $self = shift;
my ($ok, $msg) = $self->collection->FromSQL( $self->query );
return error_as_json( $self->response, 0, $msg ) unless $ok;
-
- my @orderby_cols;
- my @orders = $self->request->param('order');
- foreach my $orderby ($self->request->param('orderby')) {
- $orderby = decode_utf8($orderby);
- my $order = shift @orders || 'ASC';
- $order = uc(decode_utf8($order));
- $order = 'ASC' unless $order eq 'DESC';
- push @orderby_cols, {FIELD => $orderby, ORDER => $order};
- }
- $self->collection->OrderByCols(@orderby_cols)
- if @orderby_cols;
-
+ super();
return 1;
-}
+};
sub expand_field {
my $self = shift;
commit 2c9b41c00269c02552a5dd38b431f2fb7662350e
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue May 25 23:05:27 2021 +0800
Abstract serialize_record to easily subclass for collection
This is initially for searches, which are stored in attributes, but we
don't want to show them as normal attributes.
diff --git a/lib/RT/Extension/REST2/Resource/Collection.pm b/lib/RT/Extension/REST2/Resource/Collection.pm
index c27df40..7c74596 100644
--- a/lib/RT/Extension/REST2/Resource/Collection.pm
+++ b/lib/RT/Extension/REST2/Resource/Collection.pm
@@ -10,7 +10,7 @@ extends 'RT::Extension::REST2::Resource';
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 RT::Extension::REST2::Util qw( expand_uid format_datetime );
use POSIX qw( ceil );
use Encode;
@@ -84,7 +84,7 @@ sub serialize {
my @fields = defined $self->request->param('fields') ? split(/,/, $self->request->param('fields')) : ();
while (my $item = $collection->Next) {
- my $result = expand_uid( $item->UID );
+ my $result = $self->serialize_record( $item->UID );
# Allow selection of desired fields
if ($result) {
@@ -154,6 +154,12 @@ sub serialize {
return \%results;
}
+sub serialize_record {
+ my $self = shift;
+ my $record = shift;
+ return expand_uid($record);
+}
+
# XXX TODO: Bulk update via DELETE/PUT on a collection resource?
sub charsets_provided { [ 'utf-8' ] }
commit 39175cafffaa69d2798ea87cd56be9073b73e3e9
Author: sunnavy <sunnavy at bestpractical.com>
Date: Thu May 27 04:48:20 2021 +0800
Add /searches/ and /search/ endpoits for saved searches
diff --git a/lib/RT/Extension/REST2/Resource/Search.pm b/lib/RT/Extension/REST2/Resource/Search.pm
new file mode 100644
index 0000000..56545f1
--- /dev/null
+++ b/lib/RT/Extension/REST2/Resource/Search.pm
@@ -0,0 +1,110 @@
+package RT::Extension::REST2::Resource::Search;
+use strict;
+use warnings;
+
+use Moose;
+use namespace::autoclean;
+
+extends 'RT::Extension::REST2::Resource::Record';
+with 'RT::Extension::REST2::Resource::Record::Readable',
+ 'RT::Extension::REST2::Resource::Record::Hypermedia' =>
+ { -alias => { _self_link => '_default_self_link', hypermedia_links => '_default_hypermedia_links' } };
+
+sub dispatch_rules {
+ Path::Dispatcher::Rule::Regex->new(
+ regex => qr{^/search/?$},
+ block => sub { { record_class => 'RT::Attribute' } },
+ ),
+ Path::Dispatcher::Rule::Regex->new(
+ regex => qr{^/search/(.+)/?$},
+ block => sub {
+ my ($match, $req) = @_;
+ my $desc = $match->pos(1);
+ my $record = _load_search($req, $desc);
+
+ return { record_class => 'RT::Attribute', record_id => $record ? $record->Id : 0 };
+ },
+ );
+}
+
+sub _self_link {
+ my $self = shift;
+ my $result = $self->_default_self_link(@_);
+
+ $result->{type} = 'search';
+ $result->{_url} =~ s!/attribute/!/search/!;
+ return $result;
+}
+
+sub hypermedia_links {
+ my $self = shift;
+ my $links = $self->_default_hypermedia_links;
+ my $record = $self->record;
+ if ( my $content = $record->Content ) {
+ if ( ( $content->{SearchType} || 'Ticket' ) eq 'Ticket' ) {
+ my $id = $record->Id;
+ push @$links,
+ { _url => RT::Extension::REST2->base_uri . "/tickets?search=$id",
+ type => 'results',
+ ref => 'tickets',
+ };
+ }
+ }
+ return $links;
+}
+
+sub base_uri { join '/', RT::Extension::REST2->base_uri, 'search' }
+
+sub resource_exists {
+ my $self = shift;
+ my $record = $self->record;
+ return $record->Id && $record->Name =~ /^(?:SavedSearch$|Search -)/;
+}
+
+sub forbidden {
+ my $self = shift;
+ return 0 unless $self->resource_exists;
+ my $search = RT::SavedSearch->new( $self->current_user );
+ return $search->LoadById( $self->record->Id ) ? 0 : 1;
+}
+
+sub _load_search {
+ my $req = shift;
+ my $id = shift;
+
+ if ( $id =~ /\D/ ) {
+
+ my $attrs = RT::Attributes->new( $req->env->{"rt.current_user"} );
+
+ $attrs->Limit( FIELD => 'Name', VALUE => 'SavedSearch' );
+ $attrs->Limit( FIELD => 'Name', VALUE => 'Search -', OPERATOR => 'STARTSWITH' );
+ $attrs->Limit( FIELD => 'Description', VALUE => $id );
+
+ my @searches;
+ while ( my $attr = $attrs->Next ) {
+ my $search = RT::SavedSearch->new( $req->env->{"rt.current_user"} );
+ if ( $search->LoadById( $attr->Id ) ) {
+ push @searches, $search;
+ }
+ }
+
+ my $record_id;
+ if (@searches) {
+ if ( @searches > 1 ) {
+ RT->Logger->warning("Found multiple searches with description $id");
+ }
+ return $searches[0];
+ }
+ }
+ else {
+ my $search = RT::SavedSearch->new( $req->env->{"rt.current_user"} );
+ if ( $search->LoadById($id) ) {
+ return $search;
+ }
+ }
+ return;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/lib/RT/Extension/REST2/Resource/Searches.pm b/lib/RT/Extension/REST2/Resource/Searches.pm
new file mode 100644
index 0000000..3e0b7a1
--- /dev/null
+++ b/lib/RT/Extension/REST2/Resource/Searches.pm
@@ -0,0 +1,93 @@
+package RT::Extension::REST2::Resource::Searches;
+use strict;
+use warnings;
+
+use Moose;
+use namespace::autoclean;
+
+extends 'RT::Extension::REST2::Resource::Collection';
+with 'RT::Extension::REST2::Resource::Collection::ProcessPOSTasGET',
+ 'RT::Extension::REST2::Resource::Collection::QueryByJSON';
+
+sub dispatch_rules {
+ Path::Dispatcher::Rule::Regex->new(
+ regex => qr{^/searches/?$},
+ block => sub { { collection_class => 'RT::Attributes' } },
+ )
+}
+
+use Encode qw( decode_utf8 );
+use RT::Extension::REST2::Util qw( error_as_json );
+use RT::Search::Simple;
+
+sub allowed_methods {
+ [ 'GET', 'HEAD', 'POST' ]
+}
+
+sub limit_collection {
+ my $self = shift;
+ my @objects = RT::SavedSearch->new($self->current_user)->ObjectsForLoading;
+ if ( $self->current_user->HasRight( Object => $RT::System, Right => 'ShowSavedSearches' ) ) {
+ push @objects, RT::System->new( $self->current_user );
+ }
+
+ my $query = $self->query;
+ my @fields = $self->searchable_fields;
+ my %searchable = map {; $_ => 1 } @fields;
+
+ my @ids;
+ my @attrs;
+ for my $object (@objects) {
+ my $attrs = $object->Attributes;
+ $attrs->Limit( FIELD => 'Name', VALUE => 'SavedSearch' );
+ push @attrs, $attrs;
+ }
+
+ # Default system searches
+ my $attrs = RT::System->new( $self->current_user )->Attributes;
+ $attrs->Limit( FIELD => 'Name', VALUE => 'Search -', OPERATOR => 'STARTSWITH' );
+ push @attrs, $attrs;
+
+ for my $attrs (@attrs) {
+ for my $limit (@$query) {
+ next
+ unless $limit->{field}
+ and $searchable{ $limit->{field} }
+ and defined $limit->{value};
+
+ $attrs->Limit(
+ FIELD => $limit->{field},
+ VALUE => $limit->{value},
+ ( $limit->{operator} ? ( OPERATOR => $limit->{operator} )
+ : ()
+ ),
+ CASESENSITIVE => ( $limit->{case_sensitive} || 0 ),
+ ( $limit->{entry_aggregator} ? ( ENTRYAGGREGATOR => $limit->{entry_aggregator} )
+ : ()
+ ),
+ );
+ }
+ push @ids, map { $_->Id } @{ $attrs->ItemsArrayRef };
+ }
+
+ while ( @ids > 1000 ) {
+ my @batch = splice( @ids, 0, 1000 );
+ $self->Limit( FIELD => 'id', VALUE => \@ids, OPERATOR => 'IN' );
+ }
+ $self->collection->Limit( FIELD => 'id', VALUE => \@ids, OPERATOR => 'IN' );
+
+ return 1;
+}
+
+sub serialize_record {
+ my $self = shift;
+ my $record = shift;
+ my $result = $self->SUPER::serialize_record($record);
+ $result->{type} = 'search';
+ $result->{_url} =~ s!/attribute/!/search/!;
+ return $result;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
commit 133f561e2a22ab87adac3551ea659a32d65532ed
Author: sunnavy <sunnavy at bestpractical.com>
Date: Thu May 27 05:53:09 2021 +0800
Support to search tickets from saved searches
diff --git a/lib/RT/Extension/REST2/Resource/Collection/Search.pm b/lib/RT/Extension/REST2/Resource/Collection/Search.pm
new file mode 100644
index 0000000..2949bcd
--- /dev/null
+++ b/lib/RT/Extension/REST2/Resource/Collection/Search.pm
@@ -0,0 +1,161 @@
+package RT::Extension::REST2::Resource::Collection::Search;
+use strict;
+use warnings;
+
+use Moose::Role;
+use namespace::autoclean;
+
+requires 'collection';
+use Regexp::Common qw/delimited/;
+
+around BUILDARGS => sub {
+ my $orig = shift;
+ my $class = shift;
+
+ my %args = @_;
+
+ if ( my $id = $args{request}->param('search') ) {
+ my $search = RT::Extension::REST2::Resource::Search::_load_search( $args{request}, $id );
+
+ if ( $search && $search->Id ) {
+ if ( !defined $args{query} && !defined $args{request}->param('query') ) {
+ if ( my $query = $search->GetParameter('Query') ) {
+ $args{request}->parameters->set( query => $query );
+ }
+ }
+
+ if ( !defined $args{order} && !defined $args{request}->param('order') ) {
+ if ( my $order = $search->GetParameter('Order') ) {
+ $args{request}->parameters->set( order => split /\|/, $order );
+ }
+ }
+
+ if ( !defined $args{orderby} && !defined $args{request}->param('orderby') ) {
+ if ( my $orderby = $search->GetParameter('OrderBy') ) {
+ $args{request}->parameters->set( orderby => split /\|/, $orderby );
+ }
+ }
+
+ if ( !defined $args{per_page} && !defined $args{request}->param('per_page') ) {
+ if ( my $per_page = $search->GetParameter('RowsPerPage') ) {
+ $args{request}->parameters->set( per_page => $per_page );
+ }
+ }
+
+ if ( !defined $args{fields} && !defined $args{request}->param('fields') ) {
+ if ( my $format = $search->GetParameter('Format') ) {
+ my @attrs;
+
+ # Main logic is copied from share/html/Elements/CollectionAsTable/ParseFormat
+ while ( $format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go ) {
+ my $col = $1;
+ my $colref = {};
+
+ if ( $col =~ /^$RE{quoted}$/o ) {
+ substr( $col, 0, 1 ) = "";
+ substr( $col, -1, 1 ) = "";
+ $col =~ s/\\(.)/$1/g;
+ }
+
+ while ( $col =~ s{/(STYLE|CLASS|TITLE|ALIGN|SPAN|ATTRIBUTE):([^/]*)}{}i ) {
+ $colref->{ lc $1 } = $2;
+ }
+
+ unless ( length $col ) {
+ $colref->{'attribute'} = '' unless defined $colref->{'attribute'};
+ }
+ elsif ( $col =~ /^__(NEWLINE|NBSP)__$/ || $col =~ /^(NEWLINE|NBSP)$/ ) {
+ $colref->{'attribute'} = '';
+ }
+ elsif ( $col =~ /__(.*?)__/io ) {
+ while ( $col =~ s/^(.*?)__(.*?)__//o ) {
+ $colref->{'last_attribute'} = $2;
+ }
+ $colref->{'attribute'} = $colref->{'last_attribute'}
+ unless defined $colref->{'attribute'};
+ }
+ else {
+ $colref->{'attribute'} = $col
+ unless defined $colref->{'attribute'};
+ }
+
+ if ( $colref->{'attribute'} ) {
+ push @attrs, $colref->{'attribute'};
+ }
+ }
+
+ my %fields;
+
+ if (@attrs) {
+ my $record_class = $args{collection_class}->RecordClass;
+ while ( my $attr = shift @attrs ) {
+ if ( $attr =~ /^(Requestors?|AdminCc|Cc|CustomRole\.\{.+?\})(?:\.(.+))?/ ) {
+ my $role = $1;
+ my $field = $2;
+
+ if ( $role eq 'Requestors' ) {
+ $role = 'Requestor';
+ }
+ elsif ( $role =~ /^CustomRole\.\{(.+?)\}/ ) {
+ my $name = $1;
+ my $custom_role = RT::CustomRole->new( $args{request}->env->{"rt.current_user"} );
+ $custom_role->Load($name);
+ if ( $custom_role->Id ) {
+ $role = $custom_role->GroupType;
+ }
+ else {
+ next;
+ }
+ }
+
+ $fields{$role} = 1;
+ if ($field) {
+ $field = 'CustomFields' if $field =~ /^CustomField\./;
+ $args{request}->parameters->set(
+ "fields[$role]" => join ',',
+ $field,
+ $args{request}->parameters->get("fields[$role]") || ()
+ );
+ }
+ }
+ elsif ( $attr =~ /^CustomField\./ ) {
+ $fields{CustomFields} = 1;
+ }
+ elsif ( $attr
+ =~ /^(?:RefersTo|ReferredToBy|DependsOn|DependedOnBy|MemberOf|Members|Parents|Children)$/
+ )
+ {
+ $fields{_hyperlinks} = 1;
+ }
+ elsif ( $record_class->can('_Accessible') && $record_class->_Accessible( $attr => 'read' ) )
+ {
+ $fields{$attr} = 1;
+ }
+ elsif ( $attr =~ s/Relative$// ) {
+
+ # Date fields like LastUpdatedRelative
+ push @attrs, $attr;
+ }
+ elsif ( $attr =~ s/Name$// ) {
+
+ # Fields like OwnerName, QueueName
+ push @attrs, $attr;
+ $args{request}->parameters->set(
+ "fields[$attr]" => join ',',
+ 'Name',
+ $args{request}->parameters->get("fields[$attr]") || ()
+ );
+ }
+ }
+ }
+
+ $args{request}->parameters->set( 'fields' => join ',', sort keys %fields );
+ }
+ }
+ }
+ }
+
+ return $class->$orig( %args );
+};
+
+1;
diff --git a/lib/RT/Extension/REST2/Resource/Tickets.pm b/lib/RT/Extension/REST2/Resource/Tickets.pm
index 1398d28..34f16c6 100644
--- a/lib/RT/Extension/REST2/Resource/Tickets.pm
+++ b/lib/RT/Extension/REST2/Resource/Tickets.pm
@@ -6,7 +6,8 @@ use Moose;
use namespace::autoclean;
extends 'RT::Extension::REST2::Resource::Collection';
-with 'RT::Extension::REST2::Resource::Collection::ProcessPOSTasGET';
+with 'RT::Extension::REST2::Resource::Collection::ProcessPOSTasGET',
+ 'RT::Extension::REST2::Resource::Collection::Search';
sub dispatch_rules {
Path::Dispatcher::Rule::Regex->new(
commit e4442b52ddd28808f631c2e236b6eaee4ab11c6b
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri May 28 03:03:26 2021 +0800
Test ticket saved searches
diff --git a/xt/searches.t b/xt/searches.t
new file mode 100644
index 0000000..8f3fa7f
--- /dev/null
+++ b/xt/searches.t
@@ -0,0 +1,215 @@
+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;
+
+$user->PrincipalObj->GrantRight( Right => 'ModifySelf' );
+
+my $custom_role = RT::CustomRole->new( RT->SystemUser );
+my ( $ret, $msg ) = $custom_role->Create(
+ Name => 'Manager',
+ MaxValues => 0,
+);
+ok( $ret, "created custom role: $msg" );
+
+( $ret, $msg ) = $custom_role->AddToObject(1);
+ok( $ret, "added custom role to queue: $msg" );
+my $custom_role_type = $custom_role->GroupType;
+
+my $cf = RT::CustomField->new( RT->SystemUser );
+( $ret, $msg ) = $cf->Create( Name => 'Boss', Type => 'Freeform', LookupType => RT::User->CustomFieldLookupType );
+ok( $ret, "created custom field: $msg" );
+ok( $cf->AddToObject( RT::User->new( RT->SystemUser ) ) );
+
+ok( $user->SetCountry('US') );
+ok( $user->AddCustomFieldValue( Field => $cf, Value => 'root' ) );
+
+my $search1 = RT::SavedSearch->new($user);
+( $ret, $msg ) = $search1->Save(
+ Privacy => 'RT::User-' . $user->Id,
+ Type => 'Ticket',
+ Name => 'My tickets',
+ SearchParams => {
+ RowsPerPage => 50,
+ 'Format' => join( ',',
+ RT->Config->Get('DefaultSearchResultFormat'), '__Requestors.Country__',
+ '__CustomRole.{Manager}.CustomField.{Boss}__' ),
+ 'Query' => "Owner = '" . $user->Name . "'",
+ },
+);
+
+ok( $ret, "created $msg" );
+
+my $search2 = RT::SavedSearch->new( RT->SystemUser );
+( $ret, $msg ) = $search2->Save(
+ Privacy => 'RT::System-1',
+ Type => 'Ticket',
+ Name => 'Recently created tickets',
+ SearchParams => {
+ 'Format' => RT->Config->Get('DefaultSearchResultFormat'),
+ 'Query' => "Created >= 'yesterday'",
+ },
+);
+
+ok( $ret, "created $msg" );
+
+{
+ my $res = $mech->get( "$rest_base_path/searches", 'Authorization' => $auth, );
+ is( $res->code, 200, 'got /searches' );
+
+ my $content = $mech->json_response;
+ is( $content->{count}, 4, '4 searches' );
+ is( $content->{page}, 1, '1 page' );
+ is( $content->{per_page}, 20, '20 per_page' );
+ is( $content->{total}, 4, '4 total' );
+ is( scalar @{ $content->{items} }, 4, 'items count' );
+
+ for my $item ( @{ $content->{items} } ) {
+ ok( $item->{id}, 'search id' );
+ is( $item->{type}, 'search', 'search type' );
+ like( $item->{_url}, qr{$rest_base_path/search/$item->{id}}, 'search url' );
+ }
+
+ is( $content->{items}[-1]{id}, $search1->Id, 'search id' );
+}
+
+{
+ my $res = $mech->get( "$rest_base_path/search/" . $search2->Id, 'Authorization' => $auth, );
+ is( $res->code, 404, 'can not see system search without permission' );
+}
+
+$user->PrincipalObj->GrantRight( Right => 'SuperUser' );
+
+{
+ my $res = $mech->get( "$rest_base_path/search/" . $search2->Id, 'Authorization' => $auth, );
+ is( $res->code, 200, 'can see system search with permission' );
+}
+
+{
+ my $res = $mech->get( "$rest_base_path/searches", 'Authorization' => $auth, );
+ is( $res->code, 200, 'got /searches' );
+
+ my $content = $mech->json_response;
+ is( $content->{count}, 5, '5 searches' );
+ is( $content->{page}, 1, '1 page' );
+ is( $content->{per_page}, 20, '20 per_page' );
+ is( $content->{total}, 5, '5 total' );
+ is( scalar @{ $content->{items} }, 5, 'items count' );
+
+ for my $item ( @{ $content->{items} } ) {
+ ok( $item->{id}, 'search id' );
+ is( $item->{type}, 'search', 'search type' );
+ like( $item->{_url}, qr{$rest_base_path/search/$item->{id}}, 'search url' );
+ }
+
+ is( $content->{items}[3]{id}, $search1->Id, 'search id' );
+ is( $content->{items}[4]{id}, $search2->Id, 'search id' );
+}
+
+{
+ my $res = $mech->post_json(
+ "$rest_base_path/searches",
+ [ { field => 'Description', value => 'My tickets' } ],
+ 'Authorization' => $auth,
+ );
+ is( $res->code, 200, "got $rest_base_path/searches" );
+
+ my $content = $mech->json_response;
+ is( $content->{count}, 1, '1 search' );
+ is( $content->{page}, 1, '1 page' );
+ is( $content->{per_page}, 20, '20 per_page' );
+ is( $content->{total}, 1, '1 total' );
+ is( scalar @{ $content->{items} }, 1, 'items count' );
+
+ is( $content->{items}[0]{id}, $search1->Id, 'search id' );
+}
+
+# Single search
+{
+ my $res = $mech->get( "$rest_base_path/search/" . $search1->Id, 'Authorization' => $auth, );
+ is( $res->code, 200, "got $rest_base_path/search/" . $search1->Id );
+
+ my $content = $mech->json_response;
+ is( $content->{id}, $search1->id, 'id' );
+ is( $content->{Name}, 'SavedSearch', 'Name' );
+ is( $content->{Description}, 'My tickets', 'Description' );
+
+ my $links = $content->{_hyperlinks};
+ is( scalar @$links, 2, 'links count' );
+
+ is( $links->[0]{ref}, 'self', 'self link ref' );
+ is( $links->[0]{id}, $search1->Id, 'self link id' );
+ is( $links->[0]{type}, 'search', 'self link type' );
+ like( $links->[0]{_url}, qr[$rest_base_path/search/$links->[0]{id}$], 'self link url' );
+
+ is( $links->[1]{ref}, 'tickets', 'results link ref' );
+ is( $links->[1]{type}, 'results', 'results link type' );
+ like( $links->[1]{_url}, qr[$rest_base_path/tickets\?search=$content->{id}$], 'results link url' );
+
+ $res = $mech->get( "$rest_base_path/search/My tickets", 'Authorization' => $auth, );
+ is( $res->code, 200, "got $rest_base_path/search/" . $search1->Id );
+ is_deeply( $content, $mech->json_response, 'Access via search name' );
+}
+
+{
+ my $ticket1 = RT::Test->create_ticket(
+ Queue => 1,
+ Subject => 'test ticket',
+ Owner => $user->Id,
+ Requestor => $user->Id,
+ $custom_role_type => $user->Id
+ );
+ my $ticket2 = RT::Test->create_ticket( Queue => 1, Subject => 'test ticket' );
+ my $res = $mech->get( "$rest_base_path/tickets?search=" . $search1->Id, 'Authorization' => $auth, );
+ is( $res->code, 200, "got $rest_base_path/tickets?search=" . $search1->Id );
+
+ my $content = $mech->json_response;
+ is( $content->{count}, 1, '1 search' );
+ is( $content->{page}, 1, '1 page' );
+ is( $content->{per_page}, 50, '50 per_page' );
+ is( $content->{total}, 1, '1 total' );
+ is( scalar @{ $content->{items} }, 1, 'items count' );
+
+ my $item = $content->{items}[0];
+ is( $item->{id}, $ticket1->Id, 'ticket id' );
+ for my $field ( qw/Requestor Owner Status TimeLeft Subject Priority Created LastUpdated Told Queue/,
+ $custom_role_type )
+ {
+ ok( length $item->{$field}, "$field value not empty" );
+ }
+ is( $item->{Subject}, 'test ticket', 'Subject value' );
+ is( $item->{Queue}{Name}, 'General', 'Queue name' );
+ is( $item->{Requestor}[0]{id}, $user->Name, 'Requestor id' );
+ is( $item->{Requestor}[0]{Country}, 'US', 'Requestor Country' );
+
+ is( $item->{$custom_role_type}[0]{id}, $user->Name, 'Manager id' );
+ is( $item->{$custom_role_type}[0]{CustomFields}[0]{name}, 'Boss', 'Manager Boss' );
+ is( $item->{$custom_role_type}[0]{CustomFields}[0]{values}[0], 'root', 'Manager Boss name' );
+
+ $res = $mech->get( "$rest_base_path/tickets?search=My tickets", 'Authorization' => $auth, );
+ is( $res->code, 200, "got $rest_base_path/tickets?search=My tickets" );
+ is_deeply( $content, $mech->json_response, 'search tickets via search name' );
+
+ $res = $mech->get( "$rest_base_path/tickets?search=My tickets&per_page=10&fields=id", 'Authorization' => $auth, );
+ is( $res->code, 200, "got $rest_base_path/tickets?search=My tickets" );
+ $content = $mech->json_response;
+ is( $content->{count}, 1, '1 search' );
+ is( $content->{page}, 1, '1 page' );
+ is( $content->{per_page}, 10, '10 per_page' );
+ is( $content->{total}, 1, '1 total' );
+ is( scalar @{ $content->{items} }, 1, 'items count' );
+
+ $item = $content->{items}[0];
+ is( $item->{id}, $ticket1->Id, 'ticket id' );
+ for my $field ( qw/Requestor Owner Status TimeLeft Subject Priority Created LastUpdated Told Queue/,
+ $custom_role_type )
+ {
+ ok( !exists $item->{$field}, "$field not exists" );
+ }
+}
+
+done_testing;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list