[Rt-commit] rt branch, 5.0/rest2-support-attachments-when-creating-ticket, created. rt-5.0.0-58-gba3b448a58

? sunnavy sunnavy at bestpractical.com
Tue Oct 27 17:06:02 EDT 2020


The branch, 5.0/rest2-support-attachments-when-creating-ticket has been created
        at  ba3b448a581ac3a18edb74b95611eb43e989f6e6 (commit)

- Log -----------------------------------------------------------------
commit 7b375e0b77f40609c0c33eb7323068e0b978d5b2
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 04:44:26 2020 +0800

    Abstract code to process file uploads to avoid duplication for REST2

diff --git a/lib/RT/REST2/Resource/Message.pm b/lib/RT/REST2/Resource/Message.pm
index 799e64aabc..9efeae374d 100644
--- a/lib/RT/REST2/Resource/Message.pm
+++ b/lib/RT/REST2/Resource/Message.pm
@@ -55,7 +55,7 @@ use namespace::autoclean;
 use MIME::Base64;
 
 extends 'RT::REST2::Resource';
-use RT::REST2::Util qw( error_as_json update_custom_fields );
+use RT::REST2::Util qw( error_as_json update_custom_fields process_uploads );
 
 sub dispatch_rules {
     Path::Dispatcher::Rule::Regex->new(
@@ -104,23 +104,8 @@ sub from_multipart {
 
     my $json = JSON::decode_json($json_str);
 
-    my @attachments = $self->request->upload('Attachments');
-    foreach my $attachment (@attachments) {
-        open my $filehandle, '<', $attachment->tempname;
-        if (defined $filehandle && length $filehandle) {
-            my ( @content, $buffer );
-            while ( my $bytesread = read( $filehandle, $buffer, 72*57 ) ) {
-                push @content, MIME::Base64::encode_base64($buffer);
-            }
-            close $filehandle;
-
-            push @{$json->{Attachments}},
-                {
-                    FileName    => $attachment->filename,
-                    FileType    => $attachment->headers->{'content-type'},
-                    FileContent => join("\n", @content),
-                };
-        }
+    if ( my @attachments = $self->request->upload('Attachments') ) {
+        $json->{Attachments} = [ process_uploads(@attachments) ];
     }
 
     return $self->from_json($json);
diff --git a/lib/RT/REST2/Resource/Record/Writable.pm b/lib/RT/REST2/Resource/Record/Writable.pm
index 8fdcb4bd57..d603bd090b 100644
--- a/lib/RT/REST2/Resource/Record/Writable.pm
+++ b/lib/RT/REST2/Resource/Record/Writable.pm
@@ -53,7 +53,7 @@ use warnings;
 use Moose::Role;
 use namespace::autoclean;
 use JSON ();
-use RT::REST2::Util qw( deserialize_record error_as_json expand_uid update_custom_fields );
+use RT::REST2::Util qw( deserialize_record error_as_json expand_uid update_custom_fields process_uploads );
 use List::MoreUtils 'uniq';
 
 with 'RT::REST2::Resource::Role::RequestBodyIsJSON'
@@ -90,22 +90,8 @@ sub from_multipart {
                 my @values;
                 foreach my $single_value (@$value) {
                     if ( ref $single_value eq 'HASH' && ( my $field_name = $single_value->{UploadField} ) ) {
-                        my $file = $self->request->upload($field_name);
-                        if ($file) {
-                            open my $filehandle, '<', $file->tempname;
-                            if (defined $filehandle && length $filehandle) {
-                                my ( @content, $buffer );
-                                while ( my $bytesread = read( $filehandle, $buffer, 72*57 ) ) {
-                                    push @content, MIME::Base64::encode_base64($buffer);
-                                }
-                                close $filehandle;
-
-                                push @values, {
-                                    FileName    => $file->filename,
-                                    FileType    => $file->headers->{'content-type'},
-                                    FileContent => join("\n", @content),
-                                };
-                            }
+                        if ( my $file = $self->request->upload($field_name) ) {
+                            push @values, process_uploads($file);
                         }
                     }
                     else {
@@ -115,22 +101,8 @@ sub from_multipart {
                 $cfs->{$id} = \@values;
             }
             elsif ( ref $value eq 'HASH' && ( my $field_name = $value->{UploadField} ) ) {
-                my $file = $self->request->upload($field_name);
-                if ($file) {
-                    open my $filehandle, '<', $file->tempname;
-                    if (defined $filehandle && length $filehandle) {
-                        my ( @content, $buffer );
-                        while ( my $bytesread = read( $filehandle, $buffer, 72*57 ) ) {
-                            push @content, MIME::Base64::encode_base64($buffer);
-                        }
-                        close $filehandle;
-
-                        $cfs->{$id} = {
-                            FileName    => $file->filename,
-                            FileType    => $file->headers->{'content-type'},
-                            FileContent => join("\n", @content),
-                        };
-                    }
+                if ( my $file = $self->request->upload($field_name) ) {
+                    ( $cfs->{$id} ) = process_uploads($file);
                 }
             }
             else {
diff --git a/lib/RT/REST2/Util.pm b/lib/RT/REST2/Util.pm
index ccfca41d22..f0884123cb 100644
--- a/lib/RT/REST2/Util.pm
+++ b/lib/RT/REST2/Util.pm
@@ -69,6 +69,7 @@ use Sub::Exporter -setup => {
         custom_fields_for
         format_datetime
         update_custom_fields
+        process_uploads
     ]]
 };
 
@@ -444,5 +445,27 @@ sub update_custom_fields {
     return @results;
 }
 
+sub process_uploads {
+    my @attachments = @_;
+    my @ret;
+    foreach my $attachment (@attachments) {
+        open my $filehandle, '<', $attachment->tempname;
+        if ( defined $filehandle && length $filehandle ) {
+            my ( @content, $buffer );
+            while ( read( $filehandle, $buffer, 72 * 57 ) ) {
+                push @content, MIME::Base64::encode_base64($buffer);
+            }
+            close $filehandle;
+
+            push @ret,
+                {
+                FileName    => $attachment->filename,
+                FileType    => $attachment->headers->{'content-type'},
+                FileContent => join( "\n", @content ),
+                };
+        }
+    }
+    return @ret;
+}
 
 1;

commit 94a1f2cd4c8b21b547a90fea7204bf30a7d11b26
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 04:40:32 2020 +0800

    Fix typo

diff --git a/lib/RT/REST2.pm b/lib/RT/REST2.pm
index e7a4f6d27e..fade9fa963 100644
--- a/lib/RT/REST2.pm
+++ b/lib/RT/REST2.pm
@@ -295,7 +295,7 @@ The time, in minutes, you've taken to work on your response/comment, optional.
 =head3 Add Attachments
 
 You can attach any binary or text file to your response or comment by
-specifying C<Attachements> property in the JSON object, which should be a
+specifying C<Attachments> property in the JSON object, which should be a
 JSON array where each item represents a file you want to attach. Each item
 is a JSON object with the following properties:
 

commit ba3b448a581ac3a18edb74b95611eb43e989f6e6
Author: Dianne Skoll <dianne at bestpractical.com>
Date:   Wed Oct 28 04:46:34 2020 +0800

    Allow attachments to be added when a ticket is created

diff --git a/lib/RT/REST2.pm b/lib/RT/REST2.pm
index fade9fa963..505ef2d408 100644
--- a/lib/RT/REST2.pm
+++ b/lib/RT/REST2.pm
@@ -204,6 +204,10 @@ used for conflict avoidance
 (L<https://en.wikipedia.org/wiki/HTTP_ETag>). We'll first try updating this
 ticket with an I<invalid> C<ETag> to see what happens.
 
+You can add attachments when you create a ticket.  Simply add an "Attachments"
+entry in the JSON request content; the format of this entry is described
+in L<Add Attachments>
+
 =head3 Updating Tickets
 
 For updating tickets we use the C<PUT> verb, but otherwise it looks much
diff --git a/lib/RT/REST2/Resource/Record/Writable.pm b/lib/RT/REST2/Resource/Record/Writable.pm
index d603bd090b..bff09cf3ce 100644
--- a/lib/RT/REST2/Resource/Record/Writable.pm
+++ b/lib/RT/REST2/Resource/Record/Writable.pm
@@ -112,6 +112,10 @@ sub from_multipart {
         $json->{CustomFields} = $cfs;
     }
 
+    if ( my @attachments = $self->request->upload('Attachments') ) {
+        $json->{Attachments} = [ process_uploads(@attachments) ];
+    }
+
     return $self->from_json($json);
 }
 
diff --git a/lib/RT/REST2/Resource/Ticket.pm b/lib/RT/REST2/Resource/Ticket.pm
index 02a020d02c..5054dc2321 100644
--- a/lib/RT/REST2/Resource/Ticket.pm
+++ b/lib/RT/REST2/Resource/Ticket.pm
@@ -91,13 +91,27 @@ sub create_record {
         Object => $queue,
     ) and $queue->Disabled != 1;
 
-    if ( defined $data->{Content} ) {
+    if ( defined $data->{Content} || defined $data->{Attachments} ) {
         $data->{MIMEObj} = HTML::Mason::Commands::MakeMIMEEntity(
             Interface => 'REST',
             Subject   => $data->{Subject},
             Body      => delete $data->{Content},
             Type      => delete $data->{ContentType} || 'text/plain',
         );
+        if ( defined $data->{Attachments} ) {
+            return (\400, "Attachments must be an array") unless ref($data->{Attachments}) eq 'ARRAY';
+            foreach my $attachment (@{$data->{Attachments}}) {
+                return (\400, "Each element of Attachments must be a hash") unless ref($attachment) eq 'HASH';
+                foreach my $field (qw(FileName FileType FileContent)) {
+                    return (\400, "Field $field is required for each attachment in Attachments") unless $attachment->{$field};
+                }
+                $data->{MIMEObj}->attach(
+                    Type     => $attachment->{FileType},
+                    Filename => $attachment->{FileName},
+                    Data     => MIME::Base64::decode_base64($attachment->{FileContent}));
+            }
+            delete $data->{Attachments};
+        }
     }
 
     my ($ok, $txn, $msg) = $self->_create_record($data);
diff --git a/lib/RT/REST2/Util.pm b/lib/RT/REST2/Util.pm
index f0884123cb..dc2bf92b81 100644
--- a/lib/RT/REST2/Util.pm
+++ b/lib/RT/REST2/Util.pm
@@ -218,6 +218,7 @@ sub deserialize_record {
     # Sanitize input for the Perl API
     for my $field (sort keys %$data) {
         next if $field eq 'CustomFields';
+        next if $field eq 'Attachments';
 
         my $value = $data->{$field};
         next unless ref $value;
diff --git a/t/rest2/data/html.htm b/t/rest2/data/html.htm
new file mode 100644
index 0000000000..abce6679d6
--- /dev/null
+++ b/t/rest2/data/html.htm
@@ -0,0 +1 @@
+<p><i>Easy</i> as π</p>
diff --git a/t/rest2/data/plain.txt b/t/rest2/data/plain.txt
new file mode 100644
index 0000000000..8ab686eafe
--- /dev/null
+++ b/t/rest2/data/plain.txt
@@ -0,0 +1 @@
+Hello, World!
diff --git a/t/rest2/tickets.t b/t/rest2/tickets.t
index 1642e80f3f..9b9b0fdb43 100644
--- a/t/rest2/tickets.t
+++ b/t/rest2/tickets.t
@@ -4,6 +4,8 @@ use RT::Test::REST2 tests => undef;
 use Test::Deep;
 use MIME::Base64;
 
+use Encode qw(decode encode);
+
 # Test using integer priorities
 RT->Config->Set(EnablePriorityAsString => 0);
 my $mech = RT::Test::REST2->mech;
@@ -508,4 +510,131 @@ my ($ticket_url, $ticket_id);
     like($third_ticket->{_url}, qr{$rest_base_path/ticket/$ticket3_id$});
 }
 
+# Ticket Creation - with attachments
+{
+    my $payload = {
+        Subject => 'Ticket creation using REST, with attachments.',
+        Queue   => 'General',
+        Content => 'Testing ticket creation with attachments using REST API.',
+        Attachments => [
+            {   FileName    => 'plain.txt',
+                FileType    => 'text/plain',
+                FileContent => MIME::Base64::encode_base64('Hello, World!')
+            },
+            {   FileName    => 'html.htm',
+                FileType    => 'text/html',
+                FileContent => MIME::Base64::encode_base64(
+                    encode( 'UTF-8', "<p><i>Easy</i> as \x{03c0}</p>" )
+                ),
+            },
+            {   FileName => 'moon.png',
+                FileType => 'image/png',
+                FileContent =>
+                    'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADfSURBVDiNndM9TsNAFATgzy5yjZSAE85JBygETgENUPF3iBCitHAFQkcIhZ/Ryn9gRlrZmp2Z3ef3TBOHOMULPrDBMrhpi/4HI5xjix2+4nmJRbx/Yh7ahvkpRPVV4QDXwT3UQy46zGkAZDgK/iytefvHgCrkJsqZUH6cLnNbABSxd5Jhhf1IbkMXv8Qux7hH1Ic1xvk/jBWy6gavumvtwx7ectwZXkKh7MA95XgObeOtpI2U4zl0kGbpxgiPvwQUcXLrKFchc82f6Ur0PK49azOnmOI4TBu84zm4SV38DeIVYkrYJyNbAAAAAElFTkSuQmCC'
+            },
+        ]
+    };
+
+    my $res = $mech->post_json( "$rest_base_path/ticket", $payload,
+        'Authorization' => $auth, );
+    is( $res->code, 201 );
+    ok( $ticket_url = $res->header('location') );
+    ok( ($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)] );
+}
+
+# We have the attachments added above
+{
+    my $ticket = RT::Ticket->new($user);
+    $ticket->Load($ticket_id);
+    my $transaction_id = $ticket->Transactions->Last->id;
+    my $attachments    = $ticket->Attachments->ItemsArrayRef;
+
+    # The 5 attachments are:
+    # 1) Top-level multipart
+    # 2) Top-level ticket content
+    # 3-5) The three attachments added in the Attachments array
+    is( scalar(@$attachments), 5 );
+
+    is( $attachments->[0]->ContentType, 'multipart/mixed' );
+
+    is( $attachments->[1]->ContentType, 'text/plain' );
+    is( $attachments->[1]->Content,
+        'Testing ticket creation with attachments using REST API.' );
+
+    is( $attachments->[2]->ContentType, 'text/plain' );
+    is( $attachments->[2]->Filename,    'plain.txt' );
+    is( $attachments->[2]->Content,     'Hello, World!' );
+
+    is( $attachments->[3]->ContentType, 'text/html' );
+    is( $attachments->[3]->Filename,    'html.htm' );
+    is( $attachments->[3]->Content,     "<p><i>Easy</i> as \x{03c0}</p>" );
+
+    is( $attachments->[4]->ContentType, 'image/png' );
+    is( $attachments->[4]->Filename,    'moon.png' );
+    like( $attachments->[4]->Content, qr/IHDR/, "Looks like a PNG image" );
+}
+
+# Ticket Creation - with attachments, using multipart/form-data
+my $json = JSON->new->utf8;
+{
+    my $plain_name = 'plain.txt';
+    my $plain_path = RT::Test::get_relocatable_file($plain_name, 'data');
+    my $html_name  = 'html.htm';
+    my $html_path  = RT::Test::get_relocatable_file($html_name, 'data');
+    my $img_name   = 'image.png';
+    my $img_path   = RT::Test::get_relocatable_file($img_name, 'data');
+
+    my $payload = {
+        Subject => 'Ticket creation using REST, multipart/form-data, with attachments.',
+        Queue   => 'General',
+        Content => 'Testing ticket creation, multipart/form-data, with attachments using REST API.',
+    };
+
+    my $res = $mech->post( "$rest_base_path/ticket", $payload,
+                           'Authorization' => $auth,
+                           'Content_Type' => 'form-data',
+                           'Content' => [
+                               'JSON' => $json->encode($payload),
+                               'Attachments' => [$plain_path, $plain_name, 'text/plain'],
+                               'Attachments' => [$html_path,  $html_name,  'text/html' ],
+                               'Attachments' => [$img_path,   $img_name,   'image/png' ]
+                           ]);
+    is( $res->code, 201 );
+    ok( $ticket_url = $res->header('location') );
+    ok( ($ticket_id) = $ticket_url =~ qr[/ticket/(\d+)] );
+}
+
+# Validate that the ticket was created correctly
+
+{
+    my $ticket = RT::Ticket->new($user);
+    $ticket->Load($ticket_id);
+    my $transaction_id = $ticket->Transactions->Last->id;
+    my $attachments    = $ticket->Attachments->ItemsArrayRef;
+
+    # The 5 attachments are:
+    # 1) Top-level multipart
+    # 2) Top-level ticket content
+    # 3-5) The three attachments added in the Attachments array
+    is( scalar(@$attachments), 5 );
+
+    is( $attachments->[0]->ContentType, 'multipart/mixed' );
+
+    is( $attachments->[1]->ContentType, 'text/plain' );
+    is( $attachments->[1]->Content,
+        'Testing ticket creation, multipart/form-data, with attachments using REST API.' );
+
+    is( $attachments->[2]->ContentType, 'text/plain' );
+    is( $attachments->[2]->Filename,    'plain.txt' );
+    is( $attachments->[2]->Content,     "Hello, World!\n" );
+
+    is( $attachments->[3]->ContentType, 'text/html' );
+    is( $attachments->[3]->Filename,    'html.htm' );
+    is( $attachments->[3]->Content,     "<p><i>Easy</i> as \x{03c0}</p>\n" );
+
+    is( $attachments->[4]->ContentType, 'image/png' );
+    is( $attachments->[4]->Filename,    'image.png' );
+    like( $attachments->[4]->Content, qr/IHDR/, "Looks like a PNG image" );
+}
+
 done_testing;

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


More information about the rt-commit mailing list