From 912e6d22bb84d890c3c0b588b7ad237474ae5a82 Mon Sep 17 00:00:00 2001 From: Andrew Ruthven Date: Tue, 26 Jan 2016 15:50:24 +1300 Subject: [PATCH 1/6] Don't try and use vsplit if the values are undefined. This causes a warning to be thrown if you send comments via REST without these fields being set. --- share/html/REST/1.0/Forms/ticket/comment | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/share/html/REST/1.0/Forms/ticket/comment b/share/html/REST/1.0/Forms/ticket/comment index 87d7dc81543..fa49f80196c 100644 --- a/share/html/REST/1.0/Forms/ticket/comment +++ b/share/html/REST/1.0/Forms/ticket/comment @@ -78,7 +78,8 @@ unless ($action =~ /^(?:Comment|Correspond)$/) { } my $text = $changes{Text}; -my @atts = @{ vsplit($changes{Attachment}) }; +my @atts = @{ vsplit($changes{Attachment}) } + if defined $changes{Attachment}; if (!$changes{Text} && @atts == 0) { $e = 1; @@ -117,12 +118,12 @@ unless ($ticket->CurrentUserHasRight('ModifyTicket') || goto OUTPUT; } -my $cc = join ", ", @{ vsplit($changes{Cc}) }; -my $bcc = join ", ", @{ vsplit($changes{Bcc}) }; -my ($n, $s) = $ticket->$action(MIMEObj => $ent, - CcMessageTo => $cc, - BccMessageTo => $bcc, - TimeTaken => $changes{TimeWorked} || 0); +my $cc = join ", ", @{ vsplit($changes{Cc} || '') }; +my $bcc = join ", ", @{ vsplit($changes{Bcc} || '') }; +my ($n, $s, $txn) = $ticket->$action(MIMEObj => $ent, + CcMessageTo => $cc, + BccMessageTo => $bcc, + TimeTaken => $changes{TimeWorked} || 0); $c = "# ".$s; if ($changes{Status}) { my ($status_n, $status_s) = $ticket->SetStatus($changes{'Status'} ); From 67a42fdd365453a1a09af2748fa4a80b9e6c6dc3 Mon Sep 17 00:00:00 2001 From: Andrew Ruthven Date: Tue, 26 Jan 2016 15:51:22 +1300 Subject: [PATCH 2/6] Provide a helper to create a Transaction CustomField Allow tests to easily create or reuse transaction custom fields on a queue. --- lib/RT/Test.pm | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm index 69019f6fe46..870ea07ada5 100644 --- a/lib/RT/Test.pm +++ b/lib/RT/Test.pm @@ -980,6 +980,33 @@ sub load_or_create_custom_field { return $obj; } +=head2 load_or_create_txn_custom_field + +=cut + +sub load_or_create_txn_custom_field { + my $self = shift; + my %args = ( Disabled => 0, @_ ); + my $obj = RT::CustomField->new( RT->SystemUser ); + if ( $args{'Name'} ) { + $obj->LoadByName( + Name => $args{'Name'}, + LookupType => RT::Transaction->CustomFieldLookupType, + ObjectId => $args{'Queue'}->id, + ); + } else { + die "Name is required"; + } + unless ( $obj->id ) { + $args{'LookupType'} = 'RT::Queue-RT::Ticket-RT::Transaction'; + my $queue = delete $args{'Queue'}; + my ($val, $msg) = $obj->Create( %args ); + $obj->AddToObject($queue); + } + + return $obj; +} + sub last_ticket { my $self = shift; my $current = shift; From 1bbe531be1a44bf62f3fc92c4d344acd3ca38a8d Mon Sep 17 00:00:00 2001 From: Andrew Ruthven Date: Tue, 26 Jan 2016 15:52:42 +1300 Subject: [PATCH 3/6] Allow REST 1.0 API to set Transaction CustomFields. --- share/html/REST/1.0/Forms/ticket/comment | 33 ++++++ t/web/rest-txn-cf.t | 140 +++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 t/web/rest-txn-cf.t diff --git a/share/html/REST/1.0/Forms/ticket/comment b/share/html/REST/1.0/Forms/ticket/comment index fa49f80196c..dd0601fa974 100644 --- a/share/html/REST/1.0/Forms/ticket/comment +++ b/share/html/REST/1.0/Forms/ticket/comment @@ -55,6 +55,8 @@ $id use MIME::Entity; use RT::Interface::REST; +my $cf_spec = RT::Interface::REST->custom_field_spec(1); + $RT::Logger->debug("Got ticket id=$id for comment"); $RT::Logger->debug("Got args @{[keys(%changes)]}."); @@ -107,6 +109,20 @@ $ent->attach( } } +{ + my ($status, @msg) = $m->comp( + '/Elements/ValidateCustomFields', + CustomFields => $ticket->TransactionCustomFields, + Object => RT::Transaction->new( $session{'CurrentUser'} ), + ARGSRef => \%ARGS + ); + unless ( $status ) { + $e = 1; + $c = "# " . join("\n# ", @msg); + goto OUTPUT; + } +} + unless ($ticket->CurrentUserHasRight('ModifyTicket') || ($action eq "Comment" && $ticket->CurrentUserHasRight("CommentOnTicket")) || @@ -130,6 +146,23 @@ if ($changes{Status}) { $c .= "\n# ".$status_s; } +my %v; +foreach my $k (keys %changes) { + if ($k =~ /^$cf_spec/) { + my $key = $1 || $2; + + my $cf = $txn->LoadCustomFieldByIdentifier($key); + + if (not $cf->id) { + $c .= "\n# Invalid custom field name ($key)"; + delete $changes{$k}; + next; + } + $v{"CustomField-".$cf->Id()} = delete $changes{$k}; + } +} +$txn->UpdateCustomFields(%v); + OUTPUT: return [ $c, $o, $k, $e ]; diff --git a/t/web/rest-txn-cf.t b/t/web/rest-txn-cf.t new file mode 100644 index 00000000000..2954bf02f8a --- /dev/null +++ b/t/web/rest-txn-cf.t @@ -0,0 +1,140 @@ +use strict; +use warnings; +use RT::Interface::REST; + +use RT::Test tests => 28; +use Test::Warn; + +my ($baseurl, $m) = RT::Test->started_ok; + +my $queue = RT::Test->load_or_create_queue(Name => 'General'); +ok($queue->Id, "loaded the General queue"); + +{ + my $cf = RT::Test->load_or_create_txn_custom_field( + Name => 'txn_cf', + Type => 'FreeformSingle', + Queue => $queue, + ); + ok($cf->Id, "created a CustomField: txn_cf"); +} + +my $other_queue = RT::Test->load_or_create_queue(Name => 'Other Queue'); +ok($other_queue->Id, "loaded the Other Queue queue"); + +{ + my $cf = RT::Test->load_or_create_txn_custom_field( + Name => 'txn_other_queue_cf', + Type => 'FreeformSingle', + Queue => $other_queue, + ); + ok($cf->Id, "created a CustomField"); +} + +$m->post("$baseurl/REST/1.0/ticket/new", [ + user => 'root', + pass => 'password', + format => 'l', +]); + +my $text = $m->content; +my @lines = $text =~ m{.*}g; +shift @lines; # header + +ok($text =~ s/Subject:\s*$/Subject: REST interface/m, "successfully replaced subject"); + +$m->post("$baseurl/REST/1.0/ticket/edit", [ + user => 'root', + pass => 'password', + + content => $text, +], Content_Type => 'form-data'); + +my ($id) = $m->content =~ /Ticket (\d+) created/; +ok($id, "got ticket #$id"); + +$text = join("\n", ( "Ticket: $id", "Action: correspond", "Content-Type: text/plain" )); +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => "$text\nText: Test with no CF", + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - no CF"); + +my $with_valid_cf = $text . "\nText: Test with valid CF\nCF.{txn_cf}: valid cf"; + +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => $with_valid_cf, + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - valid CF"); +unlike($m->content, qr{Invalid custom field name}, "no invalid custom field - valid CF"); + +my $ticket = RT::Ticket->new(RT->SystemUser); +$ticket->Load($id); +is($ticket->Id, $id, "loaded the REST-created ticket"); +is($ticket->Subject, "REST interface", "subject successfully set"); + +my $txn = $ticket->Transactions->Last; +my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; +like($msg->Content, qr/Test with valid CF/, "Transaction contains expected content - valid CF"); + +is($txn->FirstCustomFieldValue('txn_cf'), "valid cf", "CF successfully set - valid CF"); + + +my $with_nonexistant_cf = $text . "\nText: Test with invalid CF\nCF.{other_cf}: invalid cf"; + +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => $with_nonexistant_cf, + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - nonexistant CF"); +like($m->content, qr{Invalid custom field name}, "invalid custom field - nonexistant CF"); + +$ticket->Load($id); +is($ticket->Id, $id, "loaded the REST-created ticket"); +is($ticket->Subject, "REST interface", "subject successfully set"); + +$txn = $ticket->Transactions->Last; +($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; +like($msg->Content, qr/Test with invalid CF/, "Transaction contains expected content - invalid CF"); + +warning_like {$txn->FirstCustomFieldValue('other_cf')} qr"Couldn't load custom field by 'other_cf' identifier", "CF isn't set - invalid CF"; + +my $with_other_queue_cf = $text . "\nText: Test with other queue CF\nCF.{txn_other_queue_cf}: invalid cf"; + +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => $with_other_queue_cf, + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - other queue CF"); +like($m->content, qr{Invalid custom field name}, "invalid custom field - other queue CF"); + +$ticket->Load($id); +is($ticket->Id, $id, "loaded the REST-created ticket"); +is($ticket->Subject, "REST interface", "subject successfully set"); + +$txn = $ticket->Transactions->Last; +($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; +like($msg->Content, qr/Test with other queue CF/, "Transaction contains expected content - other queue CF"); + +warning_like {$txn->FirstCustomFieldValue('txn_other_queue_cf')} qr"Couldn't load custom field by 'txn_other_queue_cf' identifier", "CF isn't set - other queue CF"; From 162b1e70594f825c8a849f79c67eea4808646c8f Mon Sep 17 00:00:00 2001 From: Andrew Ruthven Date: Thu, 11 Feb 2016 13:46:21 +1300 Subject: [PATCH 4/6] Display Transaction CustomFields via the REST API --- share/html/REST/1.0/Forms/ticket/history | 31 ++++++++++++++++++++++++ t/web/rest-txn-cf.t | 13 +++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/share/html/REST/1.0/Forms/ticket/history b/share/html/REST/1.0/Forms/ticket/history index 0f6d0627aad..05295381c9e 100644 --- a/share/html/REST/1.0/Forms/ticket/history +++ b/share/html/REST/1.0/Forms/ticket/history @@ -158,6 +158,37 @@ if ($tid) { push @data, [Attachments => $attachlist]; } + # Display custom fields + my $CustomFields = $t->CustomFields; + while (my $cf = $CustomFields->Next()) { + next unless !%$fields + || exists $fields->{"cf.{".lc($cf->Name)."}"} + || exists $fields->{"cf-".lc $cf->Name}; + + my $vals = $t->CustomFieldValues($cf->Id()); + my @out = (); + my $count = $vals->Count; + next unless $count; + + if ( $count == 1) { + my $v = $vals->First; + push @out, $v->Content if $v; + } + else { + while (my $v = $vals->Next()) { + my $content = $v->Content; + if ( $v->Content =~ /,/ ) { + $content =~ s/([\\'])/\\$1/g; + push @out, q{'} . $content . q{'}; + } + else { + push @out, $content; + } + } + } + push @data, [ ('CF.{' . $cf->Name . '}') => join ',', @out ]; + } + my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; diff --git a/t/web/rest-txn-cf.t b/t/web/rest-txn-cf.t index 2954bf02f8a..ac482cbf421 100644 --- a/t/web/rest-txn-cf.t +++ b/t/web/rest-txn-cf.t @@ -2,7 +2,7 @@ use strict; use warnings; use RT::Interface::REST; -use RT::Test tests => 28; +use RT::Test tests => 29; use Test::Warn; my ($baseurl, $m) = RT::Test->started_ok; @@ -90,6 +90,17 @@ like($msg->Content, qr/Test with valid CF/, "Transaction contains expected conte is($txn->FirstCustomFieldValue('txn_cf'), "valid cf", "CF successfully set - valid CF"); +$m->post( + "$baseurl/REST/1.0/ticket/$id/history", + [ + user => 'root', + pass => 'password', + format => 'l', + ], + Content_Type => 'form-data' +); + +like($m->content, qr/CF.{txn_cf}: valid cf/, "Ticket history contains expected content - valid CF"); my $with_nonexistant_cf = $text . "\nText: Test with invalid CF\nCF.{other_cf}: invalid cf"; From b4e3ac5a2d8e7eb8729ec64d18fcb00c7e7e2f78 Mon Sep 17 00:00:00 2001 From: Andrew Ruthven Date: Thu, 11 Feb 2016 13:48:42 +1300 Subject: [PATCH 5/6] Allow setting Multiple value Transaction CustomFields via REST API --- share/html/REST/1.0/Forms/ticket/comment | 6 ++- t/web/rest-txn-cf.t | 47 +++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/share/html/REST/1.0/Forms/ticket/comment b/share/html/REST/1.0/Forms/ticket/comment index dd0601fa974..42b9b17f77b 100644 --- a/share/html/REST/1.0/Forms/ticket/comment +++ b/share/html/REST/1.0/Forms/ticket/comment @@ -158,7 +158,11 @@ foreach my $k (keys %changes) { delete $changes{$k}; next; } - $v{"CustomField-".$cf->Id()} = delete $changes{$k}; + if ($cf->SingleValue) { + $v{"CustomField-".$cf->Id()} = delete $changes{$k}; + } else { + $v{"CustomField-".$cf->Id()} = vsplit(delete $changes{$k}); + } } } $txn->UpdateCustomFields(%v); diff --git a/t/web/rest-txn-cf.t b/t/web/rest-txn-cf.t index ac482cbf421..74ed2244d10 100644 --- a/t/web/rest-txn-cf.t +++ b/t/web/rest-txn-cf.t @@ -2,7 +2,7 @@ use strict; use warnings; use RT::Interface::REST; -use RT::Test tests => 29; +use RT::Test tests => 37; use Test::Warn; my ($baseurl, $m) = RT::Test->started_ok; @@ -18,6 +18,14 @@ ok($queue->Id, "loaded the General queue"); ); ok($cf->Id, "created a CustomField: txn_cf"); } +{ + my $cf = RT::Test->load_or_create_txn_custom_field( + Name => 'txn_cf_multi', + Type => 'FreeformMultiple', + Queue => $queue, + ); + ok($cf->Id, "created a CustomField: txn_cf"); +} my $other_queue = RT::Test->load_or_create_queue(Name => 'Other Queue'); ok($other_queue->Id, "loaded the Other Queue queue"); @@ -149,3 +157,40 @@ $txn = $ticket->Transactions->Last; like($msg->Content, qr/Test with other queue CF/, "Transaction contains expected content - other queue CF"); warning_like {$txn->FirstCustomFieldValue('txn_other_queue_cf')} qr"Couldn't load custom field by 'txn_other_queue_cf' identifier", "CF isn't set - other queue CF"; + +my $with_valid_cf_multi = $text . "\nText: Test with multi CF\nCF.{txn_cf_multi}: Value 1, Value 2"; + +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => $with_valid_cf_multi, + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - valid CF multi"); +unlike($m->content, qr{Invalid custom field name}, "no invalid custom field - valid CF multi"); + +$ticket = RT::Ticket->new(RT->SystemUser); +$ticket->Load($id); +is($ticket->Id, $id, "loaded the REST-created ticket - valid CF multi"); +is($ticket->Subject, "REST interface", "subject successfully set - valid CF multi"); + +$txn = $ticket->Transactions->Last; +($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; +like($msg->Content, qr/Test with multi CF/, "Transaction contains expected content - valid CF multi"); + +is($txn->FirstCustomFieldValue('txn_cf_multi'), "Value 1", "CF successfully set - valid CF multi"); + +$m->post( + "$baseurl/REST/1.0/ticket/$id/history", + [ + user => 'root', + pass => 'password', + format => 'l', + ], + Content_Type => 'form-data' +); + +like($m->content, qr/CF.{txn_cf_multi}: Value 1,Value 2/, "Ticket history contains expected content - valid CF multi"); From 94be184ba10992289fa231c5b40eedff2a987a65 Mon Sep 17 00:00:00 2001 From: Andrew Ruthven Date: Thu, 17 Aug 2017 12:08:56 +1200 Subject: [PATCH 6/6] Fix "Unescaped left brace in regex" warning in perl 5.26 --- t/web/rest-txn-cf.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/web/rest-txn-cf.t b/t/web/rest-txn-cf.t index 74ed2244d10..85ff4751a5b 100644 --- a/t/web/rest-txn-cf.t +++ b/t/web/rest-txn-cf.t @@ -108,7 +108,7 @@ $m->post( Content_Type => 'form-data' ); -like($m->content, qr/CF.{txn_cf}: valid cf/, "Ticket history contains expected content - valid CF"); +like($m->content, qr/CF.\{txn_cf\}: valid cf/, "Ticket history contains expected content - valid CF"); my $with_nonexistant_cf = $text . "\nText: Test with invalid CF\nCF.{other_cf}: invalid cf"; @@ -193,4 +193,4 @@ $m->post( Content_Type => 'form-data' ); -like($m->content, qr/CF.{txn_cf_multi}: Value 1,Value 2/, "Ticket history contains expected content - valid CF multi"); +like($m->content, qr/CF.\{txn_cf_multi\}: Value 1,Value 2/, "Ticket history contains expected content - valid CF multi");