[Bps-public-commit] rt-extension-rest2 branch, tickets-bulk-message, created. 1.12-4-g6e105df

? sunnavy sunnavy at bestpractical.com
Thu Jun 17 15:44:09 EDT 2021


The branch, tickets-bulk-message has been created
        at  6e105df77e4d5a182c2e24d64c5bc3430df7e2a4 (commit)

- Log -----------------------------------------------------------------
commit 74939689042fa958a14895ad637e13c99fc7832b
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Jun 17 23:57:24 2021 +0800

    Abstract _add_message to return results instead of touching http response
    
    Thus we can reuse it in TicketsBulk later.
    
    Note that "CustomRoles" argument conversion is moved before input
    validation: as custom role arguments are like "RT::CustomRole-1": "root"
    in web UI(/Ticket/Update.html), the validation code customized in web UI
    is more compatible in REST2 with this change.

diff --git a/lib/RT/Extension/REST2/Resource/Message.pm b/lib/RT/Extension/REST2/Resource/Message.pm
index 48ffbf6..4421446 100644
--- a/lib/RT/Extension/REST2/Resource/Message.pm
+++ b/lib/RT/Extension/REST2/Resource/Message.pm
@@ -106,26 +106,43 @@ sub from_json {
 sub add_message {
     my $self = shift;
     my %args = @_;
+
+    my ( $return_code, @results ) = $self->_add_message(%args);
+    if ( $return_code != 201 ) {
+        return error_as_json( $self->response, \$return_code, join "\n", @results );
+    }
+
+    $self->response->body( JSON::to_json( \@results, { pretty => 1 } ) );
+    return 1;
+}
+
+sub _add_message {
+    my $self = shift;
+    my %args = @_;
     my @results;
 
-    my $MIME = HTML::Mason::Commands::MakeMIMEEntity(
-        Interface => 'REST',
-        $args{NoContent} ? () : (Body => $args{Content} || $self->request->content),
-        Type      => $args{ContentType} || $self->request->content_type,
-        Subject   => $args{Subject},
-    );
+    # update_role_members wants custom role IDs (like RT::CustomRole-ID)
+    # rather than role names.
+    %args = ( %args, %{ fix_custom_role_ids( $self->record, $args{CustomRoles} ) } ) if $args{CustomRoles};
 
     # Check for any bad input data before making updates
     my ($ok, $errmsg, $return_code) = $self->validate_input(\%args);
     if (!$ok) {
         if ( $return_code ) {
-            return error_as_json($self->response, \$return_code, $errmsg);
+            return ($return_code, $errmsg);
         }
         else {
-            return error_as_json($self->response, \400, $errmsg);
+            return (400, $errmsg);
         }
     }
 
+    my $MIME = HTML::Mason::Commands::MakeMIMEEntity(
+        Interface => 'REST',
+        $args{NoContent} ? () : (Body => $args{Content} || $self->request->content),
+        Type      => $args{ContentType} || $self->request->content_type,
+        Subject   => $args{Subject},
+    );
+
     # Process attachments
     foreach my $attachment (@{$args{Attachments}}) {
         $MIME->attach(
@@ -149,22 +166,19 @@ sub add_message {
         );
     }
     else {
-        return \400;
+        push @results, $self->current_user->loc('Unknown type');
+        return ( 400, @results );
     }
 
     if (!$Trans) {
-        return error_as_json(
-            $self->response,
-            \400, $msg || "Message failed for unknown reason");
+        push @results, $msg || $self->current_user->loc("Message failed for unknown reason");
+        return ( 400, @results );
     }
 
     push @results, $msg;
     push @results, update_custom_fields($self->record, $args{CustomFields});
 
-    # update_role_members wants custom role IDs (like RT::CustomRole-ID)
-    # rather than role names.
-    my $renamed_custom_roles = fix_custom_role_ids($self->record, $args{CustomRoles});
-    push @results, update_role_members($self->record, $renamed_custom_roles);
+    push @results, update_role_members($self->record, \%args);
     push @results, $self->_update_txn_custom_fields( $TransObj, $args{TxnCustomFields} || $args{TransactionCustomFields} );
 
     # Set ticket status if we were passed a "Status":"foo" argument
@@ -174,9 +188,8 @@ sub add_message {
     }
 
     $self->created_transaction($TransObj);
-    $self->response->body(JSON::to_json(\@results, { pretty => 1 }));
 
-    return 1;
+    return ( 201, @results );
 }
 
 sub _update_txn_custom_fields {

commit ac978561ef14b8907973f6ec7c6fba1ebfb519c7
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Jun 18 02:24:40 2021 +0800

    Add /tickets/bulk/correspond and /tickets/bulk/comment endpoints

diff --git a/lib/RT/Extension/REST2/Resource/TicketsBulk.pm b/lib/RT/Extension/REST2/Resource/TicketsBulk.pm
index c9f1f2a..8194c96 100644
--- a/lib/RT/Extension/REST2/Resource/TicketsBulk.pm
+++ b/lib/RT/Extension/REST2/Resource/TicketsBulk.pm
@@ -14,9 +14,18 @@ use RT::Extension::REST2::Resource::Ticket;
 use JSON ();
 
 sub dispatch_rules {
-    Path::Dispatcher::Rule::Regex->new( regex => qr{^/tickets/bulk/?$} );
+    Path::Dispatcher::Rule::Regex->new( regex => qr{^/tickets/bulk/?$} ),
+    Path::Dispatcher::Rule::Regex->new(
+        regex => qr{^/tickets/bulk/(correspond|comment)$},
+        block => sub { { type => shift->pos(1) } },
+    )
 }
 
+has type => (
+    is       => 'ro',
+    isa      => 'Str',
+);
+
 sub post_is_create    { 1 }
 sub create_path       { '/tickets/bulk' }
 sub charsets_provided { [ 'utf-8' ] }
@@ -52,17 +61,60 @@ sub from_json {
     }
     else {
         for my $param ( @$params ) {
-            my $resource = RT::Extension::REST2::Resource::Ticket->new(
-                request      => $self->request,
-                response     => $self->response,
-                record_class => 'RT::Ticket',
-            );
-            my ( $ok, $msg ) = $resource->create_record( $param );
-            if ( ref( $ok ) || !$ok ) {
-                push @results, { message => $msg || "Create failed for unknown reason" };
+            if ( $self->type ) {
+                my $id = delete $param->{id};
+                if ( $id && $id =~ /^\d+$/ ) {
+                    my $ticket = RT::Ticket->new($self->current_user);
+                    $ticket->Load($id);
+                    my $resource = RT::Extension::REST2::Resource::Message->new(
+                        request      => $self->request,
+                        response     => $self->response,
+                        type         => $self->type,
+                        record       => $ticket,
+                    );
+
+                    my @errors;
+
+                    # Ported from RT::Extension::REST2::Resource::Message::from_json
+                    if ( $param->{Attachments} ) {
+                        foreach my $attachment ( @{ $param->{Attachments} } ) {
+                            foreach my $field ( 'FileName', 'FileType', 'FileContent' ) {
+                                push @errors, "$field is a required field for each attachment in Attachments"
+                                    unless $attachment->{$field};
+                            }
+                        }
+                    }
+
+                    $param->{NoContent} = 1 unless $param->{Content};
+                    if ( !$param->{NoContent} && !$param->{ContentType} ) {
+                        push @errors, "ContentType is a required field for application/json";
+                    }
+
+                    if (@errors) {
+                        push @results, [ $id, @errors ];
+                        next;
+                    }
+
+                    my ( $return_code, @messages ) = $resource->_add_message(%$param);
+                    push @results, [ $id, @messages ];
+                }
+                else {
+                    push @results, [ $id, 'Resource does not exist' ];
+                }
             }
             else {
-                push @results, expand_uid( $resource->record->UID );
+                my $resource = RT::Extension::REST2::Resource::Ticket->new(
+                    request      => $self->request,
+                    response     => $self->response,
+                    record_class => 'RT::Ticket',
+                );
+                my ( $ok, $msg ) = $resource->create_record($param);
+                if ( ref($ok) || !$ok ) {
+                    push @results, { message => $msg || "Create failed for unknown reason" };
+                }
+                else {
+                    push @results, expand_uid( $resource->record->UID );
+                }
             }
         }
     }

commit 96b713a6cba048ae390a3209d8f77b1a0842175f
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Jun 18 02:47:35 2021 +0800

    Test /tickets/bulk/correspond and /tickets/bulk/comment endpoints

diff --git a/xt/tickets-bulk.t b/xt/tickets-bulk.t
index b6a67bf..0b8b23b 100644
--- a/xt/tickets-bulk.t
+++ b/xt/tickets-bulk.t
@@ -176,6 +176,7 @@ my @ticket_ids;
         ],
         'json response content'
     );
+    $user->PrincipalObj->RevokeRight( Right => 'ModifyTicket' );
 }
 
 {
@@ -185,5 +186,80 @@ my @ticket_ids;
     }
 }
 
+$user->PrincipalObj->GrantRight( Right => 'ShowTicket' );
+
+{
+    diag "no ReplyToTicket right";
+    my $res = $mech->post_json(
+        "$rest_base_path/tickets/bulk/correspond",
+        [ { id => $ticket_ids[0], Content => 'test correspond', ContentType => 'text/plain' } ],
+        'Authorization' => $auth,
+    );
+    is( $res->code, 201, "status code" );
+    is_deeply( $mech->json_response, [ [ $ticket_ids[0], "Permission Denied", ] ], 'permission denied' );
+
+    diag "grant ReplyToTicket right";
+    $user->PrincipalObj->GrantRight( Right => 'ReplyToTicket' );
+
+    $res = $mech->post_json(
+        "$rest_base_path/tickets/bulk/correspond",
+        [ { id => $ticket_ids[0], Content => 'test correspond', ContentType => 'text/plain' } ],
+        'Authorization' => $auth,
+    );
+    is( $res->code, 201, 'status code' );
+    is_deeply( $mech->json_response, [ [ $ticket_ids[0], "Correspondence added", ] ], 'Correspondence added' );
+
+    $user->PrincipalObj->GrantRight( Right => 'ModifyTicket' );
+    $res = $mech->post_json(
+        "$rest_base_path/tickets/bulk/correspond",
+        [ { id => $ticket_ids[0], Content => 'test correspond', ContentType => 'text/plain', Status => 'new' } ],
+        'Authorization' => $auth,
+    );
+    is( $res->code, 201, 'status code' );
+    is_deeply(
+        $mech->json_response,
+        [ [ $ticket_ids[0], "Correspondence added", "Status changed from 'open' to 'new'" ] ],
+        'Correspondence added'
+    );
+    $user->PrincipalObj->RevokeRight( Right => $_ ) for qw/ReplyToTicket ModifyTicket/;
+}
+
+{
+    diag "no CommentOnTicket right";
+    my $res = $mech->post_json(
+        "$rest_base_path/tickets/bulk/comment",
+        [ { id => $ticket_ids[0], Content => 'test comment', ContentType => 'text/plain' } ],
+        'Authorization' => $auth,
+    );
+    is( $res->code, 201, "status code" );
+    is_deeply( $mech->json_response, [ [ $ticket_ids[0], "Permission Denied", ] ], 'permission denied' );
+
+    diag "grant CommentOnTicket right";
+    $user->PrincipalObj->GrantRight( Right => 'CommentOnTicket' );
+
+    $res = $mech->post_json(
+        "$rest_base_path/tickets/bulk/comment",
+        [ { id => $ticket_ids[0], Content => 'test comment', ContentType => 'text/plain' } ],
+        'Authorization' => $auth,
+    );
+    is( $res->code, 201, 'status code' );
+    is_deeply( $mech->json_response, [ [ $ticket_ids[0], "Comments added", ] ], 'Comments added' );
+
+    # Do status change along with comment
+    $user->PrincipalObj->GrantRight( Right => $_ ) for qw/ShowTicket ModifyTicket/;
+    $res = $mech->post_json(
+        "$rest_base_path/tickets/bulk/comment",
+        [ { id => $ticket_ids[0], Content => 'test comment', ContentType => 'text/plain', Status => 'open' } ],
+        'Authorization' => $auth,
+    );
+    is( $res->code, 201, 'status code' );
+    is_deeply(
+        $mech->json_response,
+        [ [ $ticket_ids[0], "Comments added", "Status changed from 'new' to 'open'" ] ],
+        'Comments added'
+    );
+    $user->PrincipalObj->RevokeRight( Right => $_ ) for qw/CommentOnTicket ModifyTicket/;
+}
+
 done_testing;
 

commit 6e105df77e4d5a182c2e24d64c5bc3430df7e2a4
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Jun 18 03:22:37 2021 +0800

    Document /tickets/bulk/correspond and /tickets/bulk/comment endpoints

diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index fdf5d66..f98f786 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -492,6 +492,10 @@ curl for SSL like --cacert.
     PUT /tickets/bulk
         update multiple tickets' metadata; provide JSON content(array of hashes)
 
+    POST /tickets/bulk/correspond
+    POST /tickets/bulk/comment
+        add a reply or comment to multiple tickets; provide JSON content(array of hashes)
+
 =head3 Ticket Examples
 
 Below are some examples using the endpoints above.

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


More information about the Bps-public-commit mailing list