[Bps-public-commit] rt-extension-rest2 branch, master, updated. 14a7444bac90d7aa00897d52b65f66f22da912f4

Shawn Moore shawn at bestpractical.com
Thu Dec 15 14:16:11 EST 2016


The branch, master has been updated
       via  14a7444bac90d7aa00897d52b65f66f22da912f4 (commit)
       via  686d5fab5aafa17f89ab44151be9d881a169df71 (commit)
       via  9435acfcf7cc56b794af9453079338152d2b4190 (commit)
       via  97b58def3875076dee0c3f95e3f41a6bc2569da3 (commit)
       via  5de1d7217fcc354df372205b61b5d68c3835525a (commit)
       via  9c5eb9c6a00bb3ec840e096dd79f501dbb5ca2bb (commit)
      from  982cdb3eb5f50501a5215f6f2cf89388b2f1becd (commit)

Summary of changes:
 .../REST2/Resource/Collection/QueryByJSON.pm       |  7 ++-
 lib/RT/Extension/REST2/Resource/Queue.pm           |  1 +
 .../Extension/REST2/Resource/Record/Hypermedia.pm  | 48 +++++++++++++++
 lib/RT/Extension/REST2/Resource/Record/Readable.pm | 26 +-------
 lib/RT/Extension/REST2/Resource/Ticket.pm          | 12 +---
 lib/RT/Extension/REST2/Resource/Transaction.pm     | 22 ++++++-
 lib/RT/Extension/REST2/Util.pm                     | 28 +++++++++
 t/lib/RT/Extension/REST2/Test.pm.in                | 13 ++++
 t/queues.t                                         |  4 ++
 t/tickets.t                                        | 29 ++++++++-
 t/transactions.t                                   | 69 ++++++++++++++++++----
 11 files changed, 213 insertions(+), 46 deletions(-)
 create mode 100644 lib/RT/Extension/REST2/Resource/Record/Hypermedia.pm

- Log -----------------------------------------------------------------
commit 9c5eb9c6a00bb3ec840e096dd79f501dbb5ca2bb
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Dec 15 17:59:03 2016 +0000

    Factor out a Hypermedia role for resources
    
    This is helpful because Moose does not support role method aliases
    when there's no role composition

diff --git a/lib/RT/Extension/REST2/Resource/Queue.pm b/lib/RT/Extension/REST2/Resource/Queue.pm
index 10ec15a..ba1a138 100644
--- a/lib/RT/Extension/REST2/Resource/Queue.pm
+++ b/lib/RT/Extension/REST2/Resource/Queue.pm
@@ -8,6 +8,7 @@ use namespace::autoclean;
 extends 'RT::Extension::REST2::Resource::Record';
 with (
     'RT::Extension::REST2::Resource::Record::Readable',
+    'RT::Extension::REST2::Resource::Record::Hypermedia',
     'RT::Extension::REST2::Resource::Record::DeletableByDisabling',
     'RT::Extension::REST2::Resource::Record::Writable',
 );
diff --git a/lib/RT/Extension/REST2/Resource/Record/Hypermedia.pm b/lib/RT/Extension/REST2/Resource/Record/Hypermedia.pm
new file mode 100644
index 0000000..247c4dc
--- /dev/null
+++ b/lib/RT/Extension/REST2/Resource/Record/Hypermedia.pm
@@ -0,0 +1,31 @@
+package RT::Extension::REST2::Resource::Record::Hypermedia;
+use strict;
+use warnings;
+
+use Moose::Role;
+use namespace::autoclean;
+
+sub hypermedia_links {
+    my $self = shift;
+    return [ $self->_self_link ];
+}
+
+sub _self_link {
+    my $self = shift;
+    my $record = $self->record;
+
+    my $class = blessed($record);
+    $class =~ s/^RT:://;
+    $class = lc $class;
+    my $id = $record->id;
+
+    return {
+        ref     => 'self',
+        type    => $class,
+        id      => $id,
+        _url    => RT::Extension::REST2->base_uri . "/$class/$id",
+    };
+}
+
+1;
+
diff --git a/lib/RT/Extension/REST2/Resource/Record/Readable.pm b/lib/RT/Extension/REST2/Resource/Record/Readable.pm
index e0e9bdf..b347d89 100644
--- a/lib/RT/Extension/REST2/Resource/Record/Readable.pm
+++ b/lib/RT/Extension/REST2/Resource/Record/Readable.pm
@@ -19,33 +19,13 @@ sub serialize {
     my $record = $self->record;
     my $data = serialize_record($record);
 
-    $data->{_hyperlinks} = $self->hypermedia_links;
+    if ($self->does('RT::Extension::REST2::Resource::Record::Hypermedia')) {
+        $data->{_hyperlinks} = $self->hypermedia_links;
+    }
 
     return $data;
 }
 
-sub _self_link {
-    my $self = shift;
-    my $record = $self->record;
-
-    my $class = blessed($record);
-    $class =~ s/^RT:://;
-    $class = lc $class;
-    my $id = $record->id;
-
-    return {
-        ref     => 'self',
-        type    => $class,
-        id      => $id,
-        _url    => RT::Extension::REST2->base_uri . "/$class/$id",
-    };
-}
-
-sub hypermedia_links {
-    my $self = shift;
-    return [ $self->_self_link ];
-}
-
 sub charsets_provided { [ 'utf-8' ] }
 sub default_charset   {   'utf-8'   }
 
diff --git a/lib/RT/Extension/REST2/Resource/Ticket.pm b/lib/RT/Extension/REST2/Resource/Ticket.pm
index 40c0f33..ef858da 100644
--- a/lib/RT/Extension/REST2/Resource/Ticket.pm
+++ b/lib/RT/Extension/REST2/Resource/Ticket.pm
@@ -7,7 +7,8 @@ use namespace::autoclean;
 
 extends 'RT::Extension::REST2::Resource::Record';
 with (
-    'RT::Extension::REST2::Resource::Record::Readable'
+    'RT::Extension::REST2::Resource::Record::Readable',
+    'RT::Extension::REST2::Resource::Record::Hypermedia'
         => { -alias => { hypermedia_links => '_default_hypermedia_links' } },
     'RT::Extension::REST2::Resource::Record::Deletable',
     'RT::Extension::REST2::Resource::Record::Writable',
diff --git a/t/queues.t b/t/queues.t
index a654c80..2517ead 100644
--- a/t/queues.t
+++ b/t/queues.t
@@ -51,6 +51,8 @@ my $queue_url;
                                      CorrespondAddress CommentAddress);
 
     my $links = $content->{_hyperlinks};
+    is(scalar @$links, 1);
+
     is($links->[0]{ref}, 'self');
     is($links->[0]{id}, 1);
     is($links->[0]{type}, 'queue');
@@ -180,6 +182,8 @@ my ($features_url, $features_id);
                                      CorrespondAddress CommentAddress Description);
 
     my $links = $content->{_hyperlinks};
+    is(scalar @$links, 1);
+
     is($links->[0]{ref}, 'self');
     is($links->[0]{id}, $features_id);
     is($links->[0]{type}, 'queue');
diff --git a/t/tickets.t b/t/tickets.t
index 627ed4c..dad6b69 100644
--- a/t/tickets.t
+++ b/t/tickets.t
@@ -92,6 +92,8 @@ my ($ticket_url, $ticket_id);
                                      Created Due Priority EffectiveId);
 
     my $links = $content->{_hyperlinks};
+    is(scalar @$links, 2);
+
     is($links->[0]{ref}, 'self');
     is($links->[0]{id}, 1);
     is($links->[0]{type}, 'ticket');
diff --git a/t/transactions.t b/t/transactions.t
index bf6735e..bbb8a6d 100644
--- a/t/transactions.t
+++ b/t/transactions.t
@@ -63,6 +63,8 @@ my ($create_txn_url, $create_txn_id);
     ok(exists $content->{$_}) for qw(Created);
 
     my $links = $content->{_hyperlinks};
+    is(scalar(@$links), 1);
+
     is($links->[0]{ref}, 'self');
     is($links->[0]{id}, $create_txn_id);
     is($links->[0]{type}, 'transaction');

commit 5de1d7217fcc354df372205b61b5d68c3835525a
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Dec 15 18:05:11 2016 +0000

    Add hypermedia link from transaction to its attachments
    
    ...if it has any

diff --git a/lib/RT/Extension/REST2/Resource/Transaction.pm b/lib/RT/Extension/REST2/Resource/Transaction.pm
index 235a2c8..9ddc077 100644
--- a/lib/RT/Extension/REST2/Resource/Transaction.pm
+++ b/lib/RT/Extension/REST2/Resource/Transaction.pm
@@ -6,7 +6,27 @@ use Moose;
 use namespace::autoclean;
 
 extends 'RT::Extension::REST2::Resource::Record';
-with 'RT::Extension::REST2::Resource::Record::Readable';
+with 'RT::Extension::REST2::Resource::Record::Readable',
+     'RT::Extension::REST2::Resource::Record::Hypermedia'
+         => { -alias => { hypermedia_links => '_default_hypermedia_links' } };
+
+sub hypermedia_links {
+    my $self = shift;
+    my $links = $self->_default_hypermedia_links(@_);
+
+    my $class = 'transaction';
+    my $id = $self->record->id;
+
+    my $attachments = $self->record->Attachments;
+    if ($attachments->Count) {
+        push @$links, {
+            ref  => 'attachments',
+            _url => RT::Extension::REST2->base_uri . "/$class/$id/attachments",
+        };
+    }
+
+    return $links;
+}
 
 __PACKAGE__->meta->make_immutable;
 
diff --git a/t/transactions.t b/t/transactions.t
index bbb8a6d..21144f5 100644
--- a/t/transactions.t
+++ b/t/transactions.t
@@ -11,16 +11,23 @@ my $user = RT::Extension::REST2::Test->user;
 $user->PrincipalObj->GrantRight( Right => 'CreateTicket' );
 $user->PrincipalObj->GrantRight( Right => 'ModifyTicket' );
 $user->PrincipalObj->GrantRight( Right => 'ShowTicket' );
+$user->PrincipalObj->GrantRight( Right => 'ShowTicketComments' );
 
 my $ticket = RT::Ticket->new($user);
 $ticket->Create(Queue => 'General', Subject => 'hello world');
 ok($ticket->Id, 'got an id');
-$ticket->SetPriority(42);
-$ticket->SetSubject('new subject');
-$ticket->SetPriority(43);
+my ($ok, $msg) = $ticket->SetPriority(42);
+ok($ok, $msg);
+($ok, $msg) = $ticket->SetSubject('new subject');
+ok($ok, $msg);
+($ok, $msg) = $ticket->SetPriority(43);
+ok($ok, $msg);
+($ok, $msg) = $ticket->Comment(Content => "hello world", TimeTaken => 50);
+ok($ok, $msg);
 
 # search transactions for a specific ticket
 my ($create_txn_url, $create_txn_id);
+my ($comment_txn_url, $comment_txn_id);
 {
     my $res = $mech->post_json("$rest_base_path/transactions",
         [
@@ -32,20 +39,25 @@ my ($create_txn_url, $create_txn_id);
     is($res->code, 200);
 
     my $content = $mech->json_response;
-    is($content->{count}, 4);
+    is($content->{count}, 5);
     is($content->{page}, 1);
     is($content->{per_page}, 20);
-    is($content->{total}, 4);
-    is(scalar @{$content->{items}}, 4);
+    is($content->{total}, 5);
+    is(scalar @{$content->{items}}, 5);
 
-    my ($create, $priority1, $subject, $priority2) = @{ $content->{items} };
-    is($create->{type}, 'transaction');
-    $create_txn_url = $create->{_url};
-    ok(($create_txn_id) = $create_txn_url =~ qr[/transaction/(\d+)]);
+    my ($create, $priority1, $subject, $priority2, $comment) = @{ $content->{items} };
 
+    is($create->{type}, 'transaction');
     is($priority1->{type}, 'transaction');
     is($subject->{type}, 'transaction');
     is($priority2->{type}, 'transaction');
+    is($comment->{type}, 'transaction');
+
+    $create_txn_url = $create->{_url};
+    ok(($create_txn_id) = $create_txn_url =~ qr[/transaction/(\d+)]);
+
+    $comment_txn_url = $comment->{_url};
+    ok(($comment_txn_id) = $comment_txn_url =~ qr[/transaction/(\d+)]);
 }
 
 # Transaction display
@@ -118,5 +130,40 @@ my ($create_txn_url, $create_txn_id);
     is($mech->json_response->{message}, 'Method Not Allowed');
 }
 
+# Comment transaction
+{
+    my $res = $mech->get($comment_txn_url,
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    my $content = $mech->json_response;
+    is($content->{id}, $comment_txn_id);
+    is($content->{Type}, 'Comment');
+    is($content->{TimeTaken}, 50);
+
+    ok(exists $content->{$_}) for qw(Created);
+
+    my $links = $content->{_hyperlinks};
+    is(scalar @$links, 2);
+
+    is($links->[0]{ref}, 'self');
+    is($links->[0]{id}, $comment_txn_id);
+    is($links->[0]{type}, 'transaction');
+    is($links->[0]{_url}, $comment_txn_url);
+
+    is($links->[1]{ref}, 'attachments');
+    is($links->[1]{_url}, $comment_txn_url . '/attachments');
+
+    my $creator = $content->{Creator};
+    is($creator->{id}, 'test');
+    is($creator->{type}, 'user');
+    like($creator->{_url}, qr{$rest_base_path/user/test$});
+
+    my $object = $content->{Object};
+    is($object->{id}, $ticket->Id);
+    is($object->{type}, 'ticket');
+    like($object->{_url}, qr{$rest_base_path/ticket/@{[$ticket->Id]}$});
+}
 done_testing;
 

commit 97b58def3875076dee0c3f95e3f41a6bc2569da3
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Dec 15 18:45:04 2016 +0000

    Handle GET ?query=... style searches

diff --git a/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm b/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
index b1e2dc1..346118c 100644
--- a/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
+++ b/lib/RT/Extension/REST2/Resource/Collection/QueryByJSON.pm
@@ -24,11 +24,14 @@ has 'query' => (
 
 sub _build_query {
     my $self = shift;
-    return JSON::decode_json( $self->request->content );
+    my $content = $self->request->method eq 'GET'
+                ? $self->request->param('query')
+                : $self->request->content;
+    return JSON::decode_json($content);
 }
 
 sub allowed_methods {
-    [ 'POST' ]
+    [ 'GET', 'POST' ]
 }
 
 sub searchable_fields {

commit 9435acfcf7cc56b794af9453079338152d2b4190
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Dec 15 18:45:23 2016 +0000

    Add helper methods for generating URLs

diff --git a/lib/RT/Extension/REST2/Util.pm b/lib/RT/Extension/REST2/Util.pm
index b455389..d2d1c76 100644
--- a/lib/RT/Extension/REST2/Util.pm
+++ b/lib/RT/Extension/REST2/Util.pm
@@ -14,6 +14,8 @@ use Sub::Exporter -setup => {
         error_as_json
         record_type
         record_class
+        escape_uri
+        query_string
     ]]
 };
 
@@ -176,4 +178,30 @@ sub record_class {
     return "RT::$type";
 }
 
+sub escape_uri {
+    my $uri = shift;
+    RT::Interface::Web::EscapeURI(\$uri);
+    return $uri;
+}
+
+sub query_string {
+    my %args = @_;
+    my @params;
+    for my $key (sort keys %args) {
+        my $value = $args{$key};
+        next unless defined $value;
+        $key = escape_uri($key);
+        if (UNIVERSAL::isa($value, 'ARRAY')) {
+            push @params,
+                map $key ."=". escape_uri($_),
+                    map defined $_ ? $_ : '',
+                        @$value;
+        } else {
+            push @params, $key . "=" . escape_uri($value);
+        }
+    }
+
+    return join '&', @params;
+}
+
 1;

commit 686d5fab5aafa17f89ab44151be9d881a169df71
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Dec 15 18:47:25 2016 +0000

    Support for transaction history hypermedia for any object

diff --git a/lib/RT/Extension/REST2/Resource/Record/Hypermedia.pm b/lib/RT/Extension/REST2/Resource/Record/Hypermedia.pm
index 247c4dc..3a06ee3 100644
--- a/lib/RT/Extension/REST2/Resource/Record/Hypermedia.pm
+++ b/lib/RT/Extension/REST2/Resource/Record/Hypermedia.pm
@@ -4,6 +4,8 @@ use warnings;
 
 use Moose::Role;
 use namespace::autoclean;
+use RT::Extension::REST2::Util qw(query_string);
+use JSON qw(to_json);
 
 sub hypermedia_links {
     my $self = shift;
@@ -27,5 +29,20 @@ sub _self_link {
     };
 }
 
+sub _transaction_history_link {
+    my $self = shift;
+    my $record = $self->record;
+
+    my $query = query_string(query => to_json [
+        { field => 'ObjectType', value => blessed($record) },
+        { field => 'ObjectId', value => $record->Id },
+    ]);
+
+    return {
+        ref     => 'history',
+        _url    => RT::Extension::REST2->base_uri . "/transactions?$query",
+    };
+}
+
 1;
 
diff --git a/lib/RT/Extension/REST2/Resource/Ticket.pm b/lib/RT/Extension/REST2/Resource/Ticket.pm
index ef858da..ed812dc 100644
--- a/lib/RT/Extension/REST2/Resource/Ticket.pm
+++ b/lib/RT/Extension/REST2/Resource/Ticket.pm
@@ -30,14 +30,7 @@ sub forbidden {
 sub hypermedia_links {
     my $self = shift;
     my $links = $self->_default_hypermedia_links(@_);
-
-    my $class = 'ticket';
-    my $id = $self->record->id;
-
-    push @$links, {
-        ref  => 'history',
-        _url => RT::Extension::REST2->base_uri . "/$class/$id/history",
-    };
+    push @$links, $self->_transaction_history_link;
     return $links;
 }
 
diff --git a/t/tickets.t b/t/tickets.t
index dad6b69..9d29be3 100644
--- a/t/tickets.t
+++ b/t/tickets.t
@@ -100,7 +100,7 @@ my ($ticket_url, $ticket_id);
     like($links->[0]{_url}, qr[$rest_base_path/ticket/$ticket_id$]);
 
     is($links->[1]{ref}, 'history');
-    like($links->[1]{_url}, qr[$rest_base_path/ticket/$ticket_id/history$]);
+    like($links->[1]{_url}, qr[$rest_base_path/transactions\?query=]);
 
     my $queue = $content->{Queue};
     is($queue->{id}, 1);

commit 14a7444bac90d7aa00897d52b65f66f22da912f4
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Dec 15 19:11:31 2016 +0000

    Test ticket -> transactions hypermedia

diff --git a/t/lib/RT/Extension/REST2/Test.pm.in b/t/lib/RT/Extension/REST2/Test.pm.in
index 927441f..030143b 100644
--- a/t/lib/RT/Extension/REST2/Test.pm.in
+++ b/t/lib/RT/Extension/REST2/Test.pm.in
@@ -86,6 +86,19 @@ sub mech { RT::Extension::REST2::Test::Mechanize->new }
 
         return $json->decode($res->content);
     }
+
+    sub get_hypermedia {
+        my $self = shift;
+        my $ref = shift;
+        my %args = @_;
+
+        my $json = $self->json_response;
+        my @matches = grep { $_->{ref} eq $ref } @{ $json->{_hyperlinks} };
+        Test::More::is(@matches, 1, "got one match for hypermedia with ref '$ref'") or return;
+
+        my $url = $matches[0]{_url};
+        return $self->get($url, %args);
+    }
 }
 
 1;
diff --git a/t/tickets.t b/t/tickets.t
index 9d29be3..2cb3d4a 100644
--- a/t/tickets.t
+++ b/t/tickets.t
@@ -179,4 +179,29 @@ my ($ticket_url, $ticket_id);
     is($content->{Priority}, 42);
 }
 
+# Transactions
+{
+    my $res = $mech->get($ticket_url,
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    $res = $mech->get_hypermedia('history',
+        'Authorization' => $auth,
+    );
+    is($res->code, 200);
+
+    my $content = $mech->json_response;
+    is($content->{count}, 3);
+    is($content->{page}, 1);
+    is($content->{per_page}, 20);
+    is($content->{total}, 3);
+    is(scalar @{$content->{items}}, 3);
+
+    for my $txn (@{ $content->{items} }) {
+        is($txn->{type}, 'transaction');
+        like($txn->{_url}, qr{$rest_base_path/transaction/\d+$});
+    }
+}
+
 done_testing;

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


More information about the Bps-public-commit mailing list