[Bps-public-commit] rt-extension-rest2 branch, update-custom-roles-on-correspond-and-comment, created. 1.09-7-g95f2e43
Dianne Skoll
dianne at bestpractical.com
Tue Dec 8 11:42:41 EST 2020
The branch, update-custom-roles-on-correspond-and-comment has been created
at 95f2e4395dfdf7f860674cad8b88bf0b68e5a63c (commit)
- Log -----------------------------------------------------------------
commit e476923f2829a1099ac40397899d1445f3acf706
Author: Dianne Skoll <dianne at bestpractical.com>
Date: Tue Dec 8 11:40:37 2020 -0500
Allow custom role updates via the correspond and comment endpoints.
diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index ec7faa3..1538bda 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -269,6 +269,19 @@ below).
The time, in minutes, you've taken to work on your response/comment, optional.
+=item C<CustomRoles>
+
+A hash whose keys are custom role names and values are as described below:
+
+For a single-value custom role, the value must be a string representing an
+email address; the custom role is set to that address.
+
+For a multi-value custom role, the value can be a string representing
+an email address, in which case the address is added to the members of
+the custom role. Alternatively, it can be an array of email addresses,
+in which case the members of the custom role are set to those email
+addresses.
+
=back
=head3 Add Attachments
diff --git a/lib/RT/Extension/REST2/Resource/Message.pm b/lib/RT/Extension/REST2/Resource/Message.pm
index 9b05b00..da3d4c2 100644
--- a/lib/RT/Extension/REST2/Resource/Message.pm
+++ b/lib/RT/Extension/REST2/Resource/Message.pm
@@ -7,7 +7,7 @@ use namespace::autoclean;
use MIME::Base64;
extends 'RT::Extension::REST2::Resource';
-use RT::Extension::REST2::Util qw( error_as_json update_custom_fields );
+use RT::Extension::REST2::Util qw( error_as_json update_custom_fields update_custom_roles);
sub dispatch_rules {
Path::Dispatcher::Rule::Regex->new(
@@ -150,6 +150,7 @@ sub add_message {
push @results, $msg;
push @results, update_custom_fields($self->record, $args{CustomFields});
+ push @results, update_custom_roles($self->record, $args{CustomRoles});
push @results, $self->_update_txn_custom_fields( $TransObj, $args{TxnCustomFields} || $args{TransactionCustomFields} );
$self->created_transaction($TransObj);
diff --git a/lib/RT/Extension/REST2/Util.pm b/lib/RT/Extension/REST2/Util.pm
index 6a2ba19..30d58f7 100644
--- a/lib/RT/Extension/REST2/Util.pm
+++ b/lib/RT/Extension/REST2/Util.pm
@@ -21,6 +21,7 @@ use Sub::Exporter -setup => {
custom_fields_for
format_datetime
update_custom_fields
+ update_custom_roles
]]
};
@@ -396,5 +397,112 @@ sub update_custom_fields {
return @results;
}
+# Update custom roles associated with a ticket
+# Returns an array of results; one result for each attempted update
+sub update_custom_roles {
+ my $ticket = shift;
+ my $data = shift;
+
+ my @results;
+
+ return @results unless $data;
+
+ # Make sure we're passed a hash
+ if ( ref($data) ne 'HASH') {
+ push(@results, 'Expecting a hash value for CustomRoles');
+ return @results;
+ }
+
+ foreach my $rolename (keys %$data) {
+ my $val = $data->{$rolename};
+ push @results, update_custom_role($ticket, $rolename, $val);
+ }
+ return @results;
+}
+
+# Update a single custom role on a ticket
+sub update_custom_role {
+ my ($ticket, $rolename, $val) = @_;
+
+ # Get the custom role ID
+ my $cr = RT::CustomRole->new($ticket->CurrentUser);
+ if (!$cr->Load($rolename)) {
+ return "Could not load custom role '$rolename'";
+ }
+
+ # This is the name used internally by RT
+ my $role_type = 'RT::CustomRole-' . $cr->Id;
+
+ if ($cr->SingleValue) {
+ # Single-valued role
+ if (ref($val)) {
+ return "Expecting a single string for single-valued role '$rolename'";
+ }
+ my ($ok, $msg) = $ticket->AddWatcher(Type => $role_type,
+ User => $val);
+ if ($ok) {
+ return "Set '$rolename' to $val";
+ } else {
+ return $msg || "Unable to set '$rolename' to $val";
+ }
+ }
+
+ # Must be a multi-value custom role. If $val is a scalar,
+ # add the watcher. If $val is an array, replace existing watchers
+ # with $val
+ if (!ref($val)) {
+ my ($ok, $msg) = $ticket->AddWatcher(Type => $role_type,
+ User => $val);
+ if ($ok) {
+ return "Added $val to '$rolename'";
+ } else {
+ return $msg || "Unable to add $val to '$rolename'";
+ }
+ }
+
+ # Multi-value custom role and we should have been given an array
+ if (ref($val) ne 'ARRAY') {
+ return "Expecting scalar or array for '$rolename'";
+ }
+
+ # Get the existing role addresses
+ my $group = $ticket->RoleGroup($role_type);
+ my @existing_addresses = $group->MemberEmailAddresses;
+
+ my $ok = 1;
+ my $msg = '';
+
+ # Add addresses that are not currently members
+ # The loop below has O(N^2) behavior, but I expect that
+ # typically there will not be a lot of members in a custom
+ # role... revisit if this assumption turns out to be false.
+ foreach my $addr (@$val) {
+ next if grep { lc($_) eq lc($addr) } @existing_addresses;
+ my ($local_ok, $local_msg) = $ticket->AddWatcher(Type => $role_type,
+ User => $addr);
+ $ok = 0 unless $local_ok;
+ if ($local_msg) {
+ $msg .= ", " if $msg;
+ $msg .= $local_msg;
+ }
+ }
+
+ # Delete current members that are not part of passed-in value
+ # See comment above about O(N^2) behavior.
+ foreach my $addr (@existing_addresses) {
+ next if grep { lc($_) eq lc($addr) } @$val;
+ my ($local_ok, $local_msg) = $ticket->DeleteWatcher(Type => $role_type,
+ User => $addr);
+ $ok = 0 unless $local_ok;
+ if ($local_msg) {
+ $msg .= ", " if $msg;
+ $msg .= $local_msg;
+ }
+ }
+ if (!$ok) {
+ return $msg || "Unknown error updating '$role_type'";
+ }
+ return "Updated '$role_type'";
+}
1;
commit 95f2e4395dfdf7f860674cad8b88bf0b68e5a63c
Author: Dianne Skoll <dianne at bestpractical.com>
Date: Tue Dec 8 11:41:02 2020 -0500
Add unit test for new feature: Allow custom role updates via the correspond and comment endpoints
diff --git a/xt/ticket-correspond-update-customroles.t b/xt/ticket-correspond-update-customroles.t
new file mode 100644
index 0000000..57ef77c
--- /dev/null
+++ b/xt/ticket-correspond-update-customroles.t
@@ -0,0 +1,223 @@
+use strict;
+use warnings;
+use RT::Extension::REST2::Test tests => undef;
+use Test::Deep;
+
+BEGIN {
+ plan skip_all => 'RT 4.4 required'
+ unless RT::Handle::cmp_version($RT::VERSION, '4.4.0') >= 0;
+}
+
+my $mech = RT::Extension::REST2::Test->mech;
+
+my $auth = RT::Extension::REST2::Test->authorization_header;
+my $rest_base_path = '/REST/2.0';
+my $user = RT::Extension::REST2::Test->user;
+
+# Set up a couple of custom roles
+my $queue = RT::Test->load_or_create_queue( Name => "General" );
+
+my $single = RT::CustomRole->new(RT->SystemUser);
+my ($ok, $msg) = $single->Create(Name => 'Single Member', MaxValues => 1);
+ok($ok, $msg);
+my $single_id = $single->Id;
+
+($ok, $msg) = $single->AddToObject($queue->id);
+ok($ok, $msg);
+
+my $multi = RT::CustomRole->new(RT->SystemUser);
+($ok, $msg) = $multi->Create(Name => 'Multi Member');
+ok($ok, $msg);
+my $multi_id = $multi->Id;
+
+($ok, $msg) = $multi->AddToObject($queue->id);
+ok($ok, $msg);
+
+#for my $email (qw/multi at example.com test at localhost multi2 at example.com single2 at example.com/) {
+# my $user = RT::User->new(RT->SystemUser);
+# my ($ok, $msg) = $user->Create(Name => $email, EmailAddress => $email);
+# ok($ok, $msg);
+#}
+
+$user->PrincipalObj->GrantRight( Right => $_ )
+ for qw/CreateTicket ShowTicket ModifyTicket OwnTicket AdminUsers SeeGroup SeeQueue/;
+
+# Ticket Creation
+my ($ticket_url, $ticket_id);
+{
+ my $payload = {
+ Subject => 'Ticket creation using REST',
+ Queue => 'General',
+ Content => 'Testing ticket creation using REST API.',
+ };
+
+ 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+)]);
+}
+
+# Ticket Display
+{
+ $user->PrincipalObj->GrantRight( Right => 'ShowTicket' );
+
+ my $res = $mech->get($ticket_url,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200);
+
+ 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');
+
+ ok(exists $content->{$_}, "Content exists for $_") for qw(AdminCc TimeEstimated Started Cc
+ LastUpdated TimeWorked Resolved
+ RT::CustomRole-1 RT::CustomRole-2
+ Created Due Priority EffectiveId);
+}
+
+# Ticket Reply
+{
+ $user->PrincipalObj->GrantRight( Right => 'ReplyToTicket' );
+
+ my $res = $mech->get($ticket_url,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200);
+ my $content = $mech->json_response;
+
+ my ($hypermedia) = grep { $_->{ref} eq 'correspond' } @{ $content->{_hyperlinks} };
+ ok($hypermedia, 'got correspond hypermedia');
+ like($hypermedia->{_url}, qr[$rest_base_path/ticket/$ticket_id/correspond$]);
+
+ my $correspond_url = $mech->url_for_hypermedia('correspond');
+ my $comment_url = $correspond_url;
+ $comment_url =~ s/correspond/comment/;
+
+ $res = $mech->post_json($correspond_url,
+ {
+ Content => 'Hello from hypermedia!',
+ ContentType => 'text/plain',
+ CustomRoles => {
+ 'Single Member' => 'foo at bar.example',
+ 'Multi Member' => 'quux at cabbage.example',
+ },
+ },
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201);
+ $content = $mech->json_response;
+
+ # Because CustomRoles are set in an unpredictable order, sort the
+ # responses so we have a predictable order.
+ @$content = sort { $a cmp $b } (@$content);
+ cmp_deeply($content, ["Added quux\@cabbage.example to 'Multi Member'", re(qr/Correspondence added|Message recorded/), "Set 'Single Member' to foo\@bar.example"]);
+ like($res->header('Location'), qr{$rest_base_path/transaction/\d+$});
+ $res = $mech->get($res->header('Location'),
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200);
+ $content = $mech->json_response;
+ is($content->{Type}, 'Correspond');
+ is($content->{TimeTaken}, 0);
+ is($content->{Object}{type}, 'ticket');
+ is($content->{Object}{id}, $ticket_id);
+
+ $res = $mech->get($mech->url_for_hypermedia('attachment'),
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200);
+ $content = $mech->json_response;
+ is($content->{Content}, 'Hello from hypermedia!');
+ is($content->{ContentType}, 'text/plain');
+
+ # Load the ticket and check the custom roles
+ my $ticket = RT::Ticket->new($user);
+ $ticket->Load($ticket_id);
+
+ is($ticket->RoleAddresses("RT::CustomRole-$single_id"), 'foo at bar.example',
+ "Single Member role set correctly");
+ is($ticket->RoleAddresses("RT::CustomRole-$multi_id"), 'quux at cabbage.example',
+ "Multi Member role set correctly");
+
+ # Update again (this time as a comment)
+ $res = $mech->post_json($comment_url,
+ {
+ Content => 'Hello from hypermedia!',
+ ContentType => 'text/plain',
+ CustomRoles => {
+ 'Single Member' => 'foo-new at bar.example',
+ 'Multi Member' => 'quux-new at cabbage.example',
+ },
+ },
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201);
+
+ is($ticket->RoleAddresses("RT::CustomRole-$single_id"), 'foo-new at bar.example',
+ "Single Member role set correctly");
+ is($ticket->RoleAddresses("RT::CustomRole-$multi_id"), 'quux-new at cabbage.example, quux at cabbage.example',
+ "Multi Member role updated correctly");
+
+ # Supply an array for multi-member role
+ $res = $mech->post_json($correspond_url,
+ {
+ Content => 'Hello from hypermedia!',
+ ContentType => 'text/plain',
+ CustomRoles => {
+ 'Multi Member' => ['abacus at example.com', 'quux-new at cabbage.example'],
+ },
+ },
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201);
+
+ is($ticket->RoleAddresses("RT::CustomRole-$single_id"), 'foo-new at bar.example',
+ "Single Member role unchanged");
+ is($ticket->RoleAddresses("RT::CustomRole-$multi_id"), 'abacus at example.com, quux-new at cabbage.example',
+ "Multi Member role set correctly");
+
+ # Add an existing user to multi-member role
+ $res = $mech->post_json($correspond_url,
+ {
+ Content => 'Hello from hypermedia!',
+ ContentType => 'text/plain',
+ CustomRoles => {
+ 'Multi Member' => 'abacus at example.com',
+ },
+ },
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201);
+
+ is($ticket->RoleAddresses("RT::CustomRole-$single_id"), 'foo-new at bar.example',
+ "Single Member role unchanged");
+ is($ticket->RoleAddresses("RT::CustomRole-$multi_id"), 'abacus at example.com, quux-new at cabbage.example',
+ "Multi Member role unchanged");
+
+ # Supply an array for single-member role
+ $res = $mech->post_json($correspond_url,
+ {
+ Content => 'Hello from hypermedia!',
+ ContentType => 'text/plain',
+ CustomRoles => {
+ 'Single Member' => ['abacus at example.com', 'quux-new at cabbage.example'],
+ },
+ },
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201);
+ $content = $mech->json_response;
+ cmp_deeply($content, ['Correspondence added', "Expecting a single string for single-valued role 'Single Member'"], "Got expected respose");
+ is($ticket->RoleAddresses("RT::CustomRole-$single_id"), 'foo-new at bar.example',
+ "Single Member role unchanged");
+
+}
+
+done_testing;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list