[Bps-public-commit] rt-extension-rest2 branch, custom-field-handling-improvements, created. 1.07-2-gde47b37

Aaron Trevena ast at bestpractical.com
Fri Feb 28 05:06:43 EST 2020


The branch, custom-field-handling-improvements has been created
        at  de47b37ac89fa2312cf50e57a1c042172608e9e4 (commit)

- Log -----------------------------------------------------------------
commit 0f2f2b6f79b30bf22f82351897b1207b947259ee
Author: Andrew Ruthven <puck at catalystcloud.nz>
Date:   Fri Feb 28 10:05:04 2020 +0000

    Treat CustomFields in a manner that is more consistent to other object types

diff --git a/Changes b/Changes
index c756d8c..e6c887b 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,8 @@
 Revision history for RT-Extension-REST2
 
+Unreleased
+ - Return more information about CustomFields when returning ticket
+
 1.07 2019-05-24
  - Accept 'Content' as a parameter on create. The documentation previously showed
    this in examples, but it wasn't yet supported. Now it works as documented.
diff --git a/lib/RT/Extension/REST2/Util.pm b/lib/RT/Extension/REST2/Util.pm
index e814c1c..2474e83 100644
--- a/lib/RT/Extension/REST2/Util.pm
+++ b/lib/RT/Extension/REST2/Util.pm
@@ -115,8 +115,13 @@ sub serialize_record {
     if (my $cfs = custom_fields_for($record)) {
         my %values;
         while (my $cf = $cfs->Next) {
-            my $key    = $cf->Id;
-            my $values = $values{$key} ||= [];
+            if (! defined $values{$cf->Id}) {
+                $values{$cf->Id} = {
+                    %{ expand_uid($cf->UID) },
+                    name   => $cf->Name,
+                    values => [],
+                };
+            }
             my $ocfvs  = $cf->ValuesForObject( $record );
             my $type   = $cf->Type;
             while (my $ocfv = $ocfvs->Next) {
@@ -131,11 +136,11 @@ sub serialize_record {
                         _url         => RT::Extension::REST2->base_uri . "/download/cf/" . $ocfv->id,
                     };
                 }
-                push @$values, $content;
+                push @{ $values{$cf->Id}{values} }, $content;
             }
         }
 
-        $data{CustomFields} = \%values;
+        push @{ $data{CustomFields} }, values %values;
     }
     return \%data;
 }

commit de47b37ac89fa2312cf50e57a1c042172608e9e4
Author: Andrew Ruthven <puck at catalystcloud.nz>
Date:   Fri Feb 28 10:05:39 2020 +0000

    New and updated Tests for CustomFields handling

diff --git a/xt/asset-customfields.t b/xt/asset-customfields.t
index 499c400..3003665 100644
--- a/xt/asset-customfields.t
+++ b/xt/asset-customfields.t
@@ -96,10 +96,15 @@ my ($asset_url, $asset_id);
     is($content->{id}, $asset_id);
     is($content->{Status}, 'new');
     is($content->{Name}, 'Asset creation using REST');
-    is_deeply($content->{'CustomFields'}, {}, 'Asset custom field not present');
+    is_deeply($content->{'CustomFields'}, [], 'Asset custom field not present');
     is_deeply([grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [], 'No CF hypermedia');
 }
 
+my $no_asset_cf_values = bag(
+  { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+  { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+);
+
 # Rights Test - With ShowAsset and SeeCustomField
 {
     $user->PrincipalObj->GrantRight( Right => 'SeeCustomField');
@@ -113,7 +118,9 @@ my ($asset_url, $asset_id);
     is($content->{id}, $asset_id);
     is($content->{Status}, 'new');
     is($content->{Name}, 'Asset creation using REST');
-    is_deeply($content->{CustomFields}, { $single_cf_id => [], $multi_cf_id => [] }, 'No asset custom field values');
+    cmp_deeply($content->{CustomFields}, $no_asset_cf_values, 'No asset custom field values');
+
+
     cmp_deeply(
         [grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }],
         [{
@@ -198,7 +205,7 @@ my ($asset_url, $asset_id);
     my $content = $mech->json_response;
     is($content->{Name}, 'Asset update using REST');
     is($content->{Status}, 'allocated');
-    is_deeply($content->{CustomFields}, { $single_cf_id => [], $multi_cf_id => [] }, 'No update to CF');
+    cmp_deeply($content->{CustomFields}, $no_asset_cf_values, 'No update to CF');
 }
 
 # Asset Update with ModifyCustomField
@@ -223,10 +230,15 @@ my ($asset_url, $asset_id);
     );
     is($res->code, 200);
 
+    my $modified_asset_cf_values = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified CF'] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+
     my $content = $mech->json_response;
     is($content->{Name}, 'More updates using REST');
     is($content->{Status}, 'in-use');
-    is_deeply($content->{CustomFields}, { $single_cf_id => ['Modified CF'], $multi_cf_id => [] }, 'New CF value');
+    cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'New CF value');
 
     # make sure changing the CF doesn't add a second OCFV
     $payload->{CustomFields}{$single_cf_id} = 'Modified Again';
@@ -242,8 +254,13 @@ my ($asset_url, $asset_id);
     );
     is($res->code, 200);
 
+    $modified_asset_cf_values = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified Again'] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+
     $content = $mech->json_response;
-    is_deeply($content->{CustomFields}, { $single_cf_id => ['Modified Again'], $multi_cf_id => [] }, 'New CF value');
+    cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'New CF value');
 
     # stop changing the CF, change something else, make sure CF sticks around
     delete $payload->{CustomFields}{$single_cf_id};
@@ -261,7 +278,7 @@ my ($asset_url, $asset_id);
     is($res->code, 200);
 
     $content = $mech->json_response;
-    is_deeply($content->{CustomFields}, { $single_cf_id => ['Modified Again'], $multi_cf_id => [] }, 'Same CF value');
+    cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'Same CF value');
 }
 
 # Asset Creation with ModifyCustomField
@@ -290,11 +307,16 @@ my ($asset_url, $asset_id);
     );
     is($res->code, 200);
 
+    my $asset_cf_values = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Hello world!'] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+
     my $content = $mech->json_response;
     is($content->{id}, $asset_id);
     is($content->{Status}, 'new');
     is($content->{Name}, 'Asset creation using REST');
-    is_deeply($content->{'CustomFields'}{$single_cf_id}, ['Hello world!'], 'Asset custom field');
+    cmp_deeply($content->{'CustomFields'}, $asset_cf_values, 'Asset custom field');
 }
 
 # Asset Creation for multi-value CF
@@ -330,7 +352,12 @@ for my $value (
     is($content->{Name}, 'Multi-value CF');
 
     my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference
-    is_deeply($content->{'CustomFields'}, { $multi_cf_id => $output, $single_cf_id => [] }, 'Asset custom field');
+    my $asset_cf_values = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => $output },
+    );
+
+    cmp_deeply($content->{'CustomFields'}, $asset_cf_values, 'Asset custom field');
 }
 
 {
@@ -359,8 +386,13 @@ for my $value (
         is($res->code, 200);
 
         my $content = $mech->json_response;
-        my @values = sort @{ $content->{CustomFields}{$multi_cf_id} };
-        is_deeply(\@values, $output, $name || 'New CF value');
+        my $values;
+        for my $cf (@{ $content->{CustomFields} }) {
+            next unless $cf->{id} == $multi_cf_id;
+
+            $values = [ sort @{ $cf->{values} } ];
+        }
+        cmp_deeply($values, $output, $name || 'New CF value');
     }
 
     # starting point: ['multiple', 'values'],
diff --git a/xt/ticket-customfields.t b/xt/ticket-customfields.t
index 5e8174f..949253b 100644
--- a/xt/ticket-customfields.t
+++ b/xt/ticket-customfields.t
@@ -91,10 +91,15 @@ my ($ticket_url, $ticket_id);
     is($content->{Type}, 'ticket');
     is($content->{Status}, 'new');
     is($content->{Subject}, 'Ticket creation using REST');
-    is_deeply($content->{'CustomFields'}, {}, 'Ticket custom field not present');
+    is_deeply($content->{'CustomFields'}, [], 'Ticket custom field not present');
     is_deeply([grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [], 'No CF hypermedia');
 }
 
+my $no_ticket_cf_values = bag(
+  { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+  { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+);
+
 # Rights Test - With ShowTicket and SeeCustomField
 {
     $user->PrincipalObj->GrantRight( Right => 'SeeCustomField');
@@ -109,7 +114,7 @@ my ($ticket_url, $ticket_id);
     is($content->{Type}, 'ticket');
     is($content->{Status}, 'new');
     is($content->{Subject}, 'Ticket creation using REST');
-    is_deeply($content->{CustomFields}, { $single_cf_id => [], $multi_cf_id => [] }, 'No ticket custom field values');
+    cmp_deeply($content->{CustomFields}, $no_ticket_cf_values, 'No ticket custom field values');
     cmp_deeply(
         [grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }],
         [{
@@ -194,7 +199,7 @@ my ($ticket_url, $ticket_id);
     my $content = $mech->json_response;
     is($content->{Subject}, 'Ticket update using REST');
     is($content->{Priority}, 42);
-    is_deeply($content->{CustomFields}, { $single_cf_id => [], $multi_cf_id => [] }, 'No update to CF');
+    cmp_deeply($content->{CustomFields}, $no_ticket_cf_values, 'No update to CF');
 }
 
 # Ticket Update with ModifyCustomField
@@ -219,10 +224,15 @@ my ($ticket_url, $ticket_id);
     );
     is($res->code, 200);
 
+    my $modified_single_cf_value = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified CF'] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+
     my $content = $mech->json_response;
     is($content->{Subject}, 'More updates using REST');
     is($content->{Priority}, 43);
-    is_deeply($content->{CustomFields}, { $single_cf_id => ['Modified CF'], $multi_cf_id => [] }, 'New CF value');
+    cmp_deeply($content->{CustomFields}, $modified_single_cf_value, 'New CF value');
 
     # make sure changing the CF doesn't add a second OCFV
     $payload->{CustomFields}{$single_cf_id} = 'Modified Again';
@@ -238,8 +248,13 @@ my ($ticket_url, $ticket_id);
     );
     is($res->code, 200);
 
+    my $modified_again_single_cf_value = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified Again'] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+
     $content = $mech->json_response;
-    is_deeply($content->{CustomFields}, { $single_cf_id => ['Modified Again'], $multi_cf_id => [] }, 'New CF value');
+    cmp_deeply($content->{CustomFields}, $modified_again_single_cf_value, 'New CF value');
 
     # stop changing the CF, change something else, make sure CF sticks around
     delete $payload->{CustomFields}{$single_cf_id};
@@ -257,7 +272,7 @@ my ($ticket_url, $ticket_id);
     is($res->code, 200);
 
     $content = $mech->json_response;
-    is_deeply($content->{CustomFields}, { $single_cf_id => ['Modified Again'], $multi_cf_id => [] }, 'Same CF value');
+    cmp_deeply($content->{CustomFields}, $modified_again_single_cf_value, 'Same CF value');
 }
 
 # Ticket Creation with ModifyCustomField
@@ -289,12 +304,17 @@ my ($ticket_url, $ticket_id);
     );
     is($res->code, 200);
 
+    my $ticket_cf_value = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Hello world!'] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+
     my $content = $mech->json_response;
     is($content->{id}, $ticket_id);
     is($content->{Type}, 'ticket');
     is($content->{Status}, 'new');
     is($content->{Subject}, 'Ticket creation using REST');
-    is_deeply($content->{'CustomFields'}{$single_cf_id}, ['Hello world!'], 'Ticket custom field');
+    cmp_deeply($content->{CustomFields}, $ticket_cf_value, 'Ticket custom field');
 }
 
 # Ticket Creation for multi-value CF
@@ -331,7 +351,11 @@ for my $value (
     is($content->{Subject}, 'Multi-value CF');
 
     my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference
-    is_deeply($content->{'CustomFields'}, { $multi_cf_id => $output, $single_cf_id => [] }, 'Ticket custom field');
+    my $ticket_cf_value = bag(
+        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => $output },
+    );
+    cmp_deeply($content->{'CustomFields'}, $ticket_cf_value, 'Ticket custom field');
 }
 
 {
@@ -359,9 +383,13 @@ for my $value (
         );
         is($res->code, 200);
 
+        my $ticket_cf_value = bag(
+            { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+            { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => bag(@$output) },
+        );
+
         my $content = $mech->json_response;
-        my @values = sort @{ $content->{CustomFields}{$multi_cf_id} };
-        is_deeply(\@values, $output, $name || 'New CF value');
+        cmp_deeply($content->{'CustomFields'}, $ticket_cf_value, $name || 'New CF value');
     }
 
     # starting point: ['multiple', 'values'],
diff --git a/xt/ticket-customroles.t b/xt/ticket-customroles.t
index 9ccfcdf..a2bc034 100644
--- a/xt/ticket-customroles.t
+++ b/xt/ticket-customroles.t
@@ -505,7 +505,7 @@ $user->PrincipalObj->GrantRight( Right => $_ )
         id           => $group->Id,
         Name         => 'Watcher Group',
         Domain       => 'UserDefined',
-        CustomFields => {},
+        CustomFields => [],
         Members      => [{
             type => 'user',
             id   => 'multi at example.com',
diff --git a/xt/ticket-watchers.t b/xt/ticket-watchers.t
index 1ceae9f..e6bf631 100644
--- a/xt/ticket-watchers.t
+++ b/xt/ticket-watchers.t
@@ -570,7 +570,7 @@ $user->PrincipalObj->GrantRight( Right => $_ )
         id           => $group->Id,
         Name         => 'Watcher Group',
         Domain       => 'UserDefined',
-        CustomFields => {},
+        CustomFields => [],
         Members      => [{
             type => 'user',
             id   => 'admincc at example.com',
diff --git a/xt/user-customfields.t b/xt/user-customfields.t
index 7d277c5..d46a1c0 100644
--- a/xt/user-customfields.t
+++ b/xt/user-customfields.t
@@ -1,6 +1,7 @@
 use strict;
 use warnings;
 use RT::Extension::REST2::Test tests => undef;
+use Test::Deep;
 
 my $mech = RT::Extension::REST2::Test->mech;
 
@@ -11,6 +12,7 @@ my $user = RT::Extension::REST2::Test->user;
 my $single_cf = RT::CustomField->new( RT->SystemUser );
 my ($ok, $msg) = $single_cf->Create( Name => 'Freeform', Type => 'FreeformSingle', LookupType => RT::User->CustomFieldLookupType );
 ok($ok, $msg);
+my $single_cf_id = $single_cf->Id();
 my $ocf = RT::ObjectCustomField->new( RT->SystemUser );
 ( $ok, $msg ) = $ocf->Add( CustomField => $single_cf->id, ObjectId => 0 );
 ok($ok, "Applied globally" );
@@ -18,6 +20,7 @@ ok($ok, "Applied globally" );
 my $multi_cf = RT::CustomField->new( RT->SystemUser );
 ($ok, $msg) = $multi_cf->Create( Name => 'Multi', Type => 'FreeformMultiple', LookupType => RT::User->CustomFieldLookupType );
 ok($ok, $msg);
+my $multi_cf_id = $multi_cf->Id();
 $ocf = RT::ObjectCustomField->new( RT->SystemUser );
 ( $ok, $msg ) = $ocf->Add( CustomField => $multi_cf->id, ObjectId => 0 );
 ok($ok, "Applied globally" );
@@ -65,7 +68,7 @@ my ($user_url, $user_id);
     is($content->{id}, $user_id);
     is($content->{EmailAddress}, 'user1 at example.com');
     is($content->{Name}, 'user1');
-    is_deeply($content->{'CustomFields'}, {}, 'User custom field not present');
+    is_deeply($content->{'CustomFields'}, [], 'User custom field not present');
 
     # can fetch user by name too
     $res = $mech->get("$rest_base_path/user/user1",
@@ -76,6 +79,11 @@ my ($user_url, $user_id);
     is_deeply($mech->json_response, $content, 'requesting user by name is same as user by id');
 }
 
+my $no_user_cf_values = bag(
+  { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+  { name => 'Multi',    id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+);
+
 # Rights Test - With SeeCustomField
 {
     $user->PrincipalObj->GrantRight( Right => 'SeeCustomField');
@@ -89,7 +97,7 @@ my ($user_url, $user_id);
     is($content->{id}, $user_id);
     is($content->{EmailAddress}, 'user1 at example.com');
     is($content->{Name}, 'user1');
-    is_deeply($content->{'CustomFields'}, { $single_cf->Id => [], $multi_cf->Id => [] }, 'User custom field not present');
+    cmp_deeply($content->{'CustomFields'}, $no_user_cf_values, 'User custom field not present');
 }
 
 # User Update without ModifyCustomField
@@ -117,7 +125,7 @@ my ($user_url, $user_id);
     is($content->{id}, $user_id);
     is($content->{EmailAddress}, 'user1 at example.com');
     is($content->{Name}, 'User1');
-    is_deeply($content->{CustomFields}, { $single_cf->Id => [], $multi_cf->Id => [] }, 'No update to CF');
+    cmp_deeply($content->{'CustomFields'}, $no_user_cf_values, 'User custom field not present');
 }
 
 # User Update with ModifyCustomField
@@ -126,7 +134,7 @@ my ($user_url, $user_id);
     my $payload = {
         EmailAddress  => 'user1+rt at example.com',
         CustomFields => {
-            $single_cf->Id => 'Modified CF',
+            $single_cf_id => 'Modified CF',
         },
     };
     my $res = $mech->put_json($user_url,
@@ -143,7 +151,12 @@ my ($user_url, $user_id);
 
     my $content = $mech->json_response;
     is($content->{EmailAddress}, 'user1+rt at example.com');
-    is_deeply($content->{CustomFields}, { $single_cf->Id => ['Modified CF'], $multi_cf->Id => [] }, 'New CF value');
+
+    my $set_user_cf_values = bag(
+        { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified CF'] },
+        { name => 'Multi',    id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+    cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'New CF value');
 
     # make sure changing the CF doesn't add a second OCFV
     $payload->{CustomFields}{$single_cf->Id} = 'Modified Again';
@@ -160,7 +173,12 @@ my ($user_url, $user_id);
     is($res->code, 200);
 
     $content = $mech->json_response;
-    is_deeply($content->{CustomFields}, { $single_cf->Id => ['Modified Again'], $multi_cf->Id => [] }, 'New CF value');
+
+    $set_user_cf_values = bag(
+        { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified Again'] },
+        { name => 'Multi',    id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+    cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'New CF value');
 
     # stop changing the CF, change something else, make sure CF sticks around
     delete $payload->{CustomFields}{$single_cf->Id};
@@ -178,7 +196,7 @@ my ($user_url, $user_id);
     is($res->code, 200);
 
     $content = $mech->json_response;
-    is_deeply($content->{CustomFields}, { $single_cf->Id => ['Modified Again'], $multi_cf->Id => [] }, 'Same CF value');
+    cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'Same CF value');
 }
 
 # User Creation with ModifyCustomField
@@ -209,7 +227,12 @@ my ($user_url, $user_id);
     my $content = $mech->json_response;
     is($content->{id}, $user_id);
     is($content->{Name}, 'user2');
-    is_deeply($content->{'CustomFields'}{$single_cf->Id}, ['Hello world!'], 'User custom field');
+
+    my $set_user_cf_values = bag(
+        { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Hello world!'] },
+        { name => 'Multi',    id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
+    );
+    cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'User custom field');
 }
 
 # User Creation for multi-value CF
@@ -241,7 +264,11 @@ for my $value (
     my $content = $mech->json_response;
     is($content->{id}, $user_id);
     my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference
-    is_deeply($content->{'CustomFields'}, { $multi_cf->Id => $output, $single_cf->Id => [] }, 'User custom field');
+    my $set_user_cf_values = bag(
+        { name => 'Freeform', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
+        { name => 'Multi',    id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => $output },
+    );
+    cmp_deeply($content->{'CustomFields'}, $set_user_cf_values, 'User custom field');
 }
 
 done_testing;

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


More information about the Bps-public-commit mailing list