[Bps-public-commit] rt-extension-rest2 branch, transaction-cfs, created. 1.07-3-g52ef66d
Jim Brandt
jbrandt at bestpractical.com
Fri Aug 9 13:43:11 EDT 2019
The branch, transaction-cfs has been created
at 52ef66d789ed1ddbd925464d63a954f2ef72cc87 (commit)
- Log -----------------------------------------------------------------
commit 460500d02a233c785a2381ab3e50548810d4b692
Author: michel <michel at bestpractical.com>
Date: Tue Jul 30 23:48:47 2019 +0200
Add Transaction Custom Field updates on Correspond and Comment
diff --git a/README b/README
index 40138e3..cbab6db 100644
--- a/README
+++ b/README
@@ -275,6 +275,13 @@ USAGE
-d '{ "Content": "Testing a correspondence", "ContentType": "text/plain" }'
'https://myrt.com/REST/2.0/ticket/6/correspond'
+ # Correspond a ticket with a transaction custom field
+ curl -X POST -H "Content-Type: application/json" -u 'root:password'
+ -d '{ "Content": "Testing a correspondence", "ContentType": "text/plain",
+ "TxnCustomFields": {"MyField": "custom field value"}
+ }'
+ 'https://myrt.com/REST/2.0/ticket/6/correspond'
+
# Comment a ticket
curl -X POST -H "Content-Type: text/plain" -u 'root:password'
-d 'Testing a comment'
diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index c4b61c6..195d883 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -311,6 +311,13 @@ Below are some examples using the endpoints above.
-d '{ "Content": "Testing a correspondence", "ContentType": "text/plain" }'
'https://myrt.com/REST/2.0/ticket/6/correspond'
+ # Correspond a ticket with a transaction custom field
+ curl -X POST -H "Content-Type: application/json" -u 'root:password'
+ -d '{ "Content": "Testing a correspondence", "ContentType": "text/plain",
+ "TxnCustomFields": {"MyField": "custom field value"}
+ }'
+ 'https://myrt.com/REST/2.0/ticket/6/correspond'
+
# Comment a ticket
curl -X POST -H "Content-Type: text/plain" -u 'root:password'
-d 'Testing a comment'
diff --git a/lib/RT/Extension/REST2/Resource/Message.pm b/lib/RT/Extension/REST2/Resource/Message.pm
index 57c8ef1..1c3ef33 100644
--- a/lib/RT/Extension/REST2/Resource/Message.pm
+++ b/lib/RT/Extension/REST2/Resource/Message.pm
@@ -69,8 +69,7 @@ sub add_message {
Subject => $args{Subject},
);
- my ( $Trans, $msg, $TransObj ) ;
-
+ my ( $Trans, $msg, $TransObj );
if ($self->type eq 'correspond') {
( $Trans, $msg, $TransObj ) = $self->record->Correspond(
MIMEObj => $MIME,
@@ -93,12 +92,62 @@ sub add_message {
\400, $msg || "Message failed for unknown reason");
}
+ my ( $update_ret, $update_msg ) = $self->_update_txn_custom_fields(
+ $TransObj, $args{TxnCustomFields} || $args{TransactionCustomFields} );
+ $msg .= " - CF Processing Error: transaction custom fields not updated" unless $update_ret;
+
$self->created_transaction($TransObj);
$self->response->body(JSON::to_json([$msg], { pretty => 1 }));
return 1;
}
+sub _update_txn_custom_fields {
+ my $self = shift;
+ my $TransObj = shift;
+ my $TxnCustomFields = shift;
+
+ # generate a hash suitable for UpdateCustomFields
+ # ie the keys are the "full names" of the custom fields
+ my %txn_custom_fields;
+
+ # Create an empty Transaction object to pass to GetCustomFieldInputName
+ # UpdateCustomFields expects ARGS where the Txn input name doesn't have
+ # an Id yet. It uses $self to determine which Txn to operate on.
+ my $EmptyTxn = RT::Transaction->new( RT->SystemUser );
+
+ foreach my $cf_name ( keys %{$TxnCustomFields} ) {
+ my $cf_obj = $TransObj->LoadCustomFieldByIdentifier($cf_name);
+
+ unless ( $cf_obj and $cf_obj->Id ) {
+ RT->Logger->error( "Unable to load transaction custom field: $cf_name" );
+ return ( 0, "Unable to load transaction custom field: $cf_name", undef );
+ }
+
+ my $txn_input_name = RT::Interface::Web::GetCustomFieldInputName(
+ Object => $EmptyTxn,
+ CustomField => $cf_obj,
+ Grouping => undef
+ );
+
+ $txn_custom_fields{$txn_input_name} = $TxnCustomFields->{$cf_name};
+ }
+
+ my ( $txn_ret, $txn_msg );
+ if ( keys %$TxnCustomFields ) {
+ ( $txn_ret, $txn_msg ) = $TransObj->UpdateCustomFields( %txn_custom_fields );
+
+ if ( !$txn_ret ) {
+ # the correspond/comment is already a success, the mails have been sent
+ # so we can't return an error here
+ RT->Logger->error( "Could not update transaction custom fields: $txn_msg" );
+ return ( 0, $txn_msg );
+ }
+ }
+
+ return ( 1, $txn_msg );
+}
+
sub create_path {
my $self = shift;
my $id = $self->created_transaction->Id;
diff --git a/xt/transaction-customfields.t b/xt/transaction-customfields.t
new file mode 100644
index 0000000..53a91b6
--- /dev/null
+++ b/xt/transaction-customfields.t
@@ -0,0 +1,74 @@
+use strict;
+use warnings;
+use RT::Extension::REST2::Test tests => undef;
+use Test::Deep;
+
+my $mech = RT::Extension::REST2::Test->mech;
+my ( $baseurl, $m ) = RT::Test->started_ok;
+diag "Started server at $baseurl";
+
+my $auth = RT::Extension::REST2::Test->authorization_header;
+my $rest_base_path = '/REST/2.0';
+my $admin = RT::Extension::REST2::Test->user;
+$admin->PrincipalObj->GrantRight( Right => 'SuperUser' );
+
+my $queue = RT::Test->load_or_create_queue( Name => "General" );
+
+my( $id, $msg);
+
+my $cf = RT::CustomField->new( RT->SystemUser );
+my $cfid;
+($cfid, $msg) = $cf->Create(Name => 'TxnCF', Type => 'FreeformSingle', MaxValues => '0', LookupType => RT::Transaction->CustomFieldLookupType );
+ok($cfid,$msg);
+
+($id,$msg) = $cf->AddToObject($queue);
+ok($id,$msg);
+
+my $ticket = RT::Ticket->new(RT->SystemUser);
+my ( $ticket1_id, $transid );
+($ticket1_id, $transid, $msg) = $ticket->Create(Queue => $queue->id, Subject => 'TxnCF test',);
+ok( $ticket1_id, $msg );
+
+my $res = $mech->get("$rest_base_path/ticket/$ticket1_id", 'Authorization' => $auth);
+is( $res->code, 200, 'Fetched ticket via REST2 API');
+
+{
+ my $payload = { Content => "reply one",
+ ContentType => "text/plain",
+ TxnCustomFields => { "TxnCF" => "txncf value one"},
+ };
+ my $res = $mech->post_json("$rest_base_path/ticket/$ticket1_id/correspond", $payload, 'Authorization' => $auth);
+ is( $res->code, 201, 'correspond response code is 201');
+ is_deeply( $mech->json_response, [ "Correspondence added" ], 'message is "Correspondence Added"');
+
+ my $ticket = RT::Ticket->new(RT->SystemUser);
+ my ( $ret, $msg ) = $ticket->Load( $ticket1_id );
+ ok( $ret, $msg );
+ my $txns = $ticket->Transactions;
+ $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' );
+ my $txn = $txns->Last;
+ ok( $txn->Id, "Found Correspond transaction" );
+ is( $txn->FirstCustomFieldValue('TxnCF'), "txncf value one", 'Found transaction custom field');
+}
+
+# TODO Determine how to use RT::Test::Web tools to check and clear expected warnings
+
+=pod
+
+{
+ my $payload = { Content => "reply two",
+ ContentType => "text/plain",
+ TxnCustomFields => { "not a real CF name" => "txncf value"},
+ };
+ my $res = $mech->post_json("$rest_base_path/ticket/$ticket1_id/correspond", $payload, 'Authorization' => $auth);
+
+ # Doesn't work like RT
+ my @warnings = $m->get_warnings;
+
+ is( $res->code, 201, 'Correspond response code is 201 because correspond succeeded');
+ is( $mech->json_response, [ "Correspondence added - CF Processing Error: transaction custom fields not updated" ], 'Bogus cf name');
+}
+
+=cut
+
+done_testing();
commit 3c29c50242146ee6f664262ac5ae31c2a608290d
Author: Jim Brandt <jbrandt at bestpractical.com>
Date: Fri Aug 9 11:31:05 2019 -0400
Move custom field updater to Utils for use elsewhere
diff --git a/lib/RT/Extension/REST2/Resource/Record/Writable.pm b/lib/RT/Extension/REST2/Resource/Record/Writable.pm
index 6d04d3b..4036425 100644
--- a/lib/RT/Extension/REST2/Resource/Record/Writable.pm
+++ b/lib/RT/Extension/REST2/Resource/Record/Writable.pm
@@ -5,7 +5,7 @@ use warnings;
use Moose::Role;
use namespace::autoclean;
use JSON ();
-use RT::Extension::REST2::Util qw( deserialize_record error_as_json expand_uid );
+use RT::Extension::REST2::Util qw( deserialize_record error_as_json expand_uid update_custom_fields );
use List::MoreUtils 'uniq';
with 'RT::Extension::REST2::Resource::Role::RequestBodyIsJSON'
@@ -52,7 +52,7 @@ sub update_record {
AttributesRef => [ $self->record->WritableAttributes ],
);
- push @results, $self->_update_custom_fields($data->{CustomFields});
+ push @results, update_custom_fields($self->record, $data->{CustomFields});
push @results, $self->_update_role_members($data);
push @results, $self->_update_disabled($data->{Disabled})
unless grep { $_ eq 'Disabled' } $self->record->WritableAttributes;
@@ -62,84 +62,6 @@ sub update_record {
return @results;
}
-sub _update_custom_fields {
- my $self = shift;
- my $data = shift;
-
- my $record = $self->record;
- my @results;
-
- foreach my $cfid (keys %{ $data }) {
- my $val = $data->{$cfid};
-
- my $cf = $record->LoadCustomFieldByIdentifier($cfid);
- next unless $cf->ObjectTypeFromLookupType($cf->__Value('LookupType'))->isa(ref $record);
-
- if ($cf->SingleValue) {
- if (ref($val) eq 'ARRAY') {
- $val = $val->[0];
- }
- elsif (ref($val)) {
- die "Invalid value type for CustomField $cfid";
- }
-
- my ($ok, $msg) = $record->AddCustomFieldValue(
- Field => $cf,
- Value => $val,
- );
- push @results, $msg;
- }
- else {
- my %count;
- my @vals = ref($val) eq 'ARRAY' ? @$val : $val;
- for (@vals) {
- $count{$_}++;
- }
-
- my $ocfvs = $cf->ValuesForObject( $record );
- my %ocfv_id;
- while (my $ocfv = $ocfvs->Next) {
- my $content = $ocfv->Content;
- $count{$content}--;
- push @{ $ocfv_id{$content} }, $ocfv->Id;
- }
-
- # we want to provide a stable order, so first go by the order
- # provided in the argument list, and then for any custom fields
- # that are being removed, remove in sorted order
- for my $key (uniq(@vals, sort keys %count)) {
- my $count = $count{$key};
- if ($count == 0) {
- # new == old, no change needed
- }
- elsif ($count > 0) {
- # new > old, need to add new
- while ($count-- > 0) {
- my ($ok, $msg) = $record->AddCustomFieldValue(
- Field => $cf,
- Value => $key,
- );
- push @results, $msg;
- }
- }
- elsif ($count < 0) {
- # old > new, need to remove old
- while ($count++ < 0) {
- my $id = shift @{ $ocfv_id{$key} };
- my ($ok, $msg) = $record->DeleteCustomFieldValue(
- Field => $cf,
- ValueId => $id,
- );
- push @results, $msg;
- }
- }
- }
- }
- }
-
- return @results;
-}
-
sub _update_role_members {
my $self = shift;
my $data = shift;
@@ -306,7 +228,7 @@ sub create_record {
my ($ok, @rest) = $record->$method(%args);
if ($ok && $cfs) {
- $self->_update_custom_fields($cfs);
+ update_custom_fields($record, $cfs);
}
return ($ok, @rest);
diff --git a/lib/RT/Extension/REST2/Util.pm b/lib/RT/Extension/REST2/Util.pm
index e814c1c..e59a69c 100644
--- a/lib/RT/Extension/REST2/Util.pm
+++ b/lib/RT/Extension/REST2/Util.pm
@@ -4,6 +4,7 @@ use warnings;
use JSON ();
use Scalar::Util qw( blessed );
+use List::MoreUtils 'uniq';
use Sub::Exporter -setup => {
exports => [qw[
@@ -19,6 +20,7 @@ use Sub::Exporter -setup => {
query_string
custom_fields_for
format_datetime
+ update_custom_fields
]]
};
@@ -247,4 +249,82 @@ sub custom_fields_for {
return;
}
+sub update_custom_fields {
+ my $record = shift;
+ my $data = shift;
+
+ my @results;
+
+ foreach my $cfid (keys %{ $data }) {
+ my $val = $data->{$cfid};
+
+ my $cf = $record->LoadCustomFieldByIdentifier($cfid);
+ next unless $cf->ObjectTypeFromLookupType($cf->__Value('LookupType'))->isa(ref $record);
+
+ if ($cf->SingleValue) {
+ if (ref($val) eq 'ARRAY') {
+ $val = $val->[0];
+ }
+ elsif (ref($val)) {
+ die "Invalid value type for CustomField $cfid";
+ }
+
+ my ($ok, $msg) = $record->AddCustomFieldValue(
+ Field => $cf,
+ Value => $val,
+ );
+ push @results, $msg;
+ }
+ else {
+ my %count;
+ my @vals = ref($val) eq 'ARRAY' ? @$val : $val;
+ for (@vals) {
+ $count{$_}++;
+ }
+
+ my $ocfvs = $cf->ValuesForObject( $record );
+ my %ocfv_id;
+ while (my $ocfv = $ocfvs->Next) {
+ my $content = $ocfv->Content;
+ $count{$content}--;
+ push @{ $ocfv_id{$content} }, $ocfv->Id;
+ }
+
+ # we want to provide a stable order, so first go by the order
+ # provided in the argument list, and then for any custom fields
+ # that are being removed, remove in sorted order
+ for my $key (uniq(@vals, sort keys %count)) {
+ my $count = $count{$key};
+ if ($count == 0) {
+ # new == old, no change needed
+ }
+ elsif ($count > 0) {
+ # new > old, need to add new
+ while ($count-- > 0) {
+ my ($ok, $msg) = $record->AddCustomFieldValue(
+ Field => $cf,
+ Value => $key,
+ );
+ push @results, $msg;
+ }
+ }
+ elsif ($count < 0) {
+ # old > new, need to remove old
+ while ($count++ < 0) {
+ my $id = shift @{ $ocfv_id{$key} };
+ my ($ok, $msg) = $record->DeleteCustomFieldValue(
+ Field => $cf,
+ ValueId => $id,
+ );
+ push @results, $msg;
+ }
+ }
+ }
+ }
+ }
+
+ return @results;
+}
+
+
1;
commit 52ef66d789ed1ddbd925464d63a954f2ef72cc87
Author: Jim Brandt <jbrandt at bestpractical.com>
Date: Fri Aug 9 13:39:24 2019 -0400
Add custom fields on comment and correspond
Also refactor return values for transaction cf updates
to return messages the same as ticket custom field
updates.
diff --git a/README b/README
index cbab6db..5871ce5 100644
--- a/README
+++ b/README
@@ -278,15 +278,19 @@ USAGE
# Correspond a ticket with a transaction custom field
curl -X POST -H "Content-Type: application/json" -u 'root:password'
-d '{ "Content": "Testing a correspondence", "ContentType": "text/plain",
- "TxnCustomFields": {"MyField": "custom field value"}
- }'
+ "TxnCustomFields": {"MyField": "custom field value"} }'
'https://myrt.com/REST/2.0/ticket/6/correspond'
- # Comment a ticket
+ # Comment on a ticket
curl -X POST -H "Content-Type: text/plain" -u 'root:password'
-d 'Testing a comment'
'https://myrt.com/REST/2.0/ticket/6/comment'
+ # Comment on a ticket with custom field update
+ curl -X POST -H "Content-Type: text/plain" -u 'root:password'
+ -d '{ "Content": "Testing a comment", "ContentType": "text/plain", "CustomFields": {"Severity": "High"} }'
+ 'https://myrt.com/REST/2.0/ticket/6/comment'
+
# Create an Asset
curl -X POST -H "Content-Type: application/json" -u 'root:password'
-d '{"Name" : "Asset From Rest", "Catalog" : "General assets", "Content" : "Some content"}'
diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index 195d883..e0315c5 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -314,15 +314,19 @@ Below are some examples using the endpoints above.
# Correspond a ticket with a transaction custom field
curl -X POST -H "Content-Type: application/json" -u 'root:password'
-d '{ "Content": "Testing a correspondence", "ContentType": "text/plain",
- "TxnCustomFields": {"MyField": "custom field value"}
- }'
+ "TxnCustomFields": {"MyField": "custom field value"} }'
'https://myrt.com/REST/2.0/ticket/6/correspond'
- # Comment a ticket
+ # Comment on a ticket
curl -X POST -H "Content-Type: text/plain" -u 'root:password'
-d 'Testing a comment'
'https://myrt.com/REST/2.0/ticket/6/comment'
+ # Comment on a ticket with custom field update
+ curl -X POST -H "Content-Type: text/plain" -u 'root:password'
+ -d '{ "Content": "Testing a comment", "ContentType": "text/plain", "CustomFields": {"Severity": "High"} }'
+ 'https://myrt.com/REST/2.0/ticket/6/comment'
+
# Create an Asset
curl -X POST -H "Content-Type: application/json" -u 'root:password'
-d '{"Name" : "Asset From Rest", "Catalog" : "General assets", "Content" : "Some content"}'
diff --git a/lib/RT/Extension/REST2/Resource/Message.pm b/lib/RT/Extension/REST2/Resource/Message.pm
index 1c3ef33..a35cd51 100644
--- a/lib/RT/Extension/REST2/Resource/Message.pm
+++ b/lib/RT/Extension/REST2/Resource/Message.pm
@@ -6,7 +6,7 @@ use Moose;
use namespace::autoclean;
extends 'RT::Extension::REST2::Resource';
-use RT::Extension::REST2::Util qw( error_as_json );
+use RT::Extension::REST2::Util qw( error_as_json update_custom_fields );
sub dispatch_rules {
Path::Dispatcher::Rule::Regex->new(
@@ -61,6 +61,7 @@ sub from_json {
sub add_message {
my $self = shift;
my %args = @_;
+ my @results;
my $MIME = HTML::Mason::Commands::MakeMIMEEntity(
Interface => 'REST',
@@ -92,12 +93,12 @@ sub add_message {
\400, $msg || "Message failed for unknown reason");
}
- my ( $update_ret, $update_msg ) = $self->_update_txn_custom_fields(
- $TransObj, $args{TxnCustomFields} || $args{TransactionCustomFields} );
- $msg .= " - CF Processing Error: transaction custom fields not updated" unless $update_ret;
+ push @results, $msg;
+ push @results, update_custom_fields($self->record, $args{CustomFields});
+ push @results, $self->_update_txn_custom_fields( $TransObj, $args{TxnCustomFields} || $args{TransactionCustomFields} );
$self->created_transaction($TransObj);
- $self->response->body(JSON::to_json([$msg], { pretty => 1 }));
+ $self->response->body(JSON::to_json(\@results, { pretty => 1 }));
return 1;
}
@@ -106,6 +107,7 @@ sub _update_txn_custom_fields {
my $self = shift;
my $TransObj = shift;
my $TxnCustomFields = shift;
+ my @results;
# generate a hash suitable for UpdateCustomFields
# ie the keys are the "full names" of the custom fields
@@ -121,7 +123,7 @@ sub _update_txn_custom_fields {
unless ( $cf_obj and $cf_obj->Id ) {
RT->Logger->error( "Unable to load transaction custom field: $cf_name" );
- return ( 0, "Unable to load transaction custom field: $cf_name", undef );
+ push @results, "Unable to load transaction custom field: $cf_name";
}
my $txn_input_name = RT::Interface::Web::GetCustomFieldInputName(
@@ -133,19 +135,18 @@ sub _update_txn_custom_fields {
$txn_custom_fields{$txn_input_name} = $TxnCustomFields->{$cf_name};
}
- my ( $txn_ret, $txn_msg );
if ( keys %$TxnCustomFields ) {
- ( $txn_ret, $txn_msg ) = $TransObj->UpdateCustomFields( %txn_custom_fields );
+ # UpdateCustomFields currently doesn't return messages on updates
+ # Stub it out for now.
+ my @return = $TransObj->UpdateCustomFields( %txn_custom_fields );
- if ( !$txn_ret ) {
- # the correspond/comment is already a success, the mails have been sent
- # so we can't return an error here
- RT->Logger->error( "Could not update transaction custom fields: $txn_msg" );
- return ( 0, $txn_msg );
+ # Simulate return messages until we get real results
+ if ( @return && $return[0] == 1 ) {
+ push @results, 'Custom fields updated';
}
}
- return ( 1, $txn_msg );
+ return @results;
}
sub create_path {
diff --git a/xt/ticket-customfields.t b/xt/ticket-customfields.t
index 5e8174f..d276ff3 100644
--- a/xt/ticket-customfields.t
+++ b/xt/ticket-customfields.t
@@ -260,6 +260,37 @@ my ($ticket_url, $ticket_id);
is_deeply($content->{CustomFields}, { $single_cf_id => ['Modified Again'], $multi_cf_id => [] }, 'Same CF value');
}
+# Ticket Comment with custom field
+{
+ my $payload = {
+ Content => 'This is some content for a comment',
+ ContentType => 'text/plain',
+ Subject => 'This is a subject',
+ CustomFields => {
+ $single_cf_id => 'Yet another modified CF',
+ },
+ };
+
+ $user->PrincipalObj->GrantRight( Right => 'CommentOnTicket' );
+
+ my $res = $mech->get($ticket_url,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200);
+ my $content = $mech->json_response;
+
+ my ($hypermedia) = grep { $_->{ref} eq 'comment' } @{ $content->{_hyperlinks} };
+ ok($hypermedia, 'got comment hypermedia');
+ like($hypermedia->{_url}, qr[$rest_base_path/ticket/$ticket_id/comment$]);
+
+ $res = $mech->post_json($mech->url_for_hypermedia('comment'),
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201);
+ cmp_deeply($mech->json_response, [re(qr/Comments added|Message recorded/), "Single Modified Again changed to Yet another modified CF"]);
+}
+
# Ticket Creation with ModifyCustomField
{
my $payload = {
diff --git a/xt/transaction-customfields.t b/xt/transaction-customfields.t
index 53a91b6..93c5650 100644
--- a/xt/transaction-customfields.t
+++ b/xt/transaction-customfields.t
@@ -39,7 +39,7 @@ is( $res->code, 200, 'Fetched ticket via REST2 API');
};
my $res = $mech->post_json("$rest_base_path/ticket/$ticket1_id/correspond", $payload, 'Authorization' => $auth);
is( $res->code, 201, 'correspond response code is 201');
- is_deeply( $mech->json_response, [ "Correspondence added" ], 'message is "Correspondence Added"');
+ is_deeply( $mech->json_response, [ "Correspondence added", "Custom fields updated" ], 'message is "Correspondence Added"');
my $ticket = RT::Ticket->new(RT->SystemUser);
my ( $ret, $msg ) = $ticket->Load( $ticket1_id );
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list