Alister West

home is where your code is ...

Perl - Request Tracker (RT) - Scrips

I used to hate RT. Not because of its complexity but because it doesn't have any good developer documentation. Moving the wiki to http://requesttracker.wikia.com/ was a great start (10 year old software without a plaintext search on their old wiki was really quite un-acceptable). RTv4 also removed a lot of cruft/bloat/obfuscation by making the objects simple and remove the *_Overlay layers.

The email list isn't that great to search, and the scrips on the website are good but nearly all of them are a starting point (not a solution). I'm putting a few things here for future reference.

RT Script Environment Setup

Assume I'm using this to setup the env for all scripts.

#!/usr/bin/env perl

use lib qw( /var/home/alister/rt3/local/lib
            /var/home/alister/rt3/lib );

use RT;
use RT::Interface::CLI qw/CleanEnv/;
use RT::User;

use Data::Dump qw/dump/;
$|++;

CleanEnv();
RT::LoadConfig();
RT::Init();

Get Users in a Group

Such a simple thing - but it isn't mentioned anywhere where you'd expect. Originally from http://requesttracker.wikia.com/wiki/CodeSnippets

my $group = RT::Group->new( $RT::SystemUser );
# RT::Group->Load takes $id
$group->LoadUserDefinedGroup( 'My Group Name' );
die "couldn't load group" unless $group->id;

# List all user members of the group
my $users = $group->UserMembersObj;
$users->OrderBy( FIELD => 'Name' );
while ( my $user = $users->Next ) {
    print $user->Name, "\n";
    print $user->EmailAddress, "\n";
}

Scrip OnCreate set a users organisation based on their domain.

# in etc/RT_SiteConfig.pm
# Set( %OrgLookup, (
#   'gt.net' => 'Gossamer-Threads',
#   'gmail.com' => 'Google',
# ));

my $user = $self->TransactionObj->TicketObj->CreatorObj;

return 1 unless $user;
return 1 if $user->Name =~ /(?:root|Nobody|RT_System)/;
return 1 if $user->Organization;

(my $domain = $user->EmailAddress) =~ s/.*\@(.*)>?$/$1/;

my %map = RT->Config->Get('OrgLookup');
my $org = defined $map{$domain} ? $map{$domain} : $domain;

$user->SetOrganization( $org );
$RT::Logger->info( "Set Organisation of ". $user->Name . " to '$org'" );

OnStatus change - SetOwner to CurrentUser

# CustomCleanupCode:
# Change Status if 'Taken' via Basics (and didn't change Status)
# Done in cleanup incase a Owner was already set.
my $trans = $self->TransactionObj;
my $ticket = $self->TicketObj;

# $RT::Logger->info( "(scrip) Owner: ". $ticket->Owner. ", CurrentUser: ". $trans->Creator );
if (     $trans->OldValue =~ /^(RRT_)?Pool$/ 
     and $trans->NewValue =~ /InProgress$/ 
     and $ticket->Owner == 6 ) {
    my ($status, $msg) = $ticket->SetOwner( $trans->Creator, 'Force' );
}

Custom Condition - On CustomField Change

# You probably want any updates to a customfield to run in 
# TransactionBatch > CustomCleanup so that any manual updates
# to a CF don't get applied.
#
my $cf_name = "MyCustomField";
my $trans = $self->TransactionObj;

return 0 unless $trans->Type eq 'CustomField';
no warnings 'once';

my $cf = RT::CustomField->new($RT::SystemUser);
$cf->LoadByName( Name => $cf_name );

if ($cf->Id == $trans->Field) {
    if ($trans->OldValue ne $trans->NewValue) {
        $RT::Logger->debug(
            'XXX CF $cf_name changed: '.$trans->OldValue.' to '.$trans->NewValue
        );
        return 1;
    }
}

return 0;

TransactionBatch mode

# Sometimes you may want to force an owner change for a ticket.
# If the user changes the owner, and the scrip changes the owner then you'll get an error like:
#  - "You can only reassign tickets that you own or that are unowned"
# To avoid this you must run your scrip in Batch mode.

# Prepare
my $ticket = $self->Ticket;
my $transaction;
if (my $batch = $ticket->TransactionBatch) {
    # get the applicable transaction
    $transaction = (grep { ($_->Type eq 'Owner') ? 1: 0;} @$batch)[0];
} else {
    $transaction = $self->TransactionObj;
}

Condition - On OwnerChange (ignore system users)

my $trans = $self->TransactionObj;
if ( ($trans->Field || '')  eq 'Owner') {
    my $owner = $trans->NewValue;
    $RT::Logger->debug("XXX SetOwner: $owner");

    # We don't care about system users, root or nobody
    return 1 if $owner > 12;
}
return 0;

When Ticket+Unowned+CommentByAdminCc then SetOwner(Commentor)

  • Change global scrip #4 "On Create notify AdminCcs"

    • Description => "On Create Notify AdminCcs as Comment".
    • Action => "Notify AdminCcs as Comment"
    • Template => "Global: Admin Comment"
  • Then install this script!

    • This will update Owner to be the Commentor if its an AdminCc.
    • This will then happen on a reply to an email, but will also happen when making a comment in the web-interface.

Beware that if this action was a Correspond it would notify the Requestor - which is probably not what you want.

# Condition
my $ticket = $self->TicketObj;
my $trans  = $self->TransactionObj;
# return 0 unless $ticket->QueueObj->Name =~ /General/i;
return 0 unless $trans->Type eq 'Comment';
return 0 unless $ticket->OwnerObj->Name eq 'Nobody';
return 0 unless $ticket->IsAdminCc( $trans->CreatorObj->Id )
             || $ticket->QueueObj->IsAdminCc( $trans->CreatorObj->Id );
return 0 unless $self->TransactionObj->IsInbound;
return 0 unless $ticket->QueueObj->Lifecycle->IsInitial($ticket->Status)
return 0 unless $trans->Message->First =~ /^\s*(take|mine)\b/mi;
return 1;

# Action  -  requires AdminCc's to have OwnTicket privilege
my $creator = $self->TransactionObj->Creator;
my ($ok, $msg) = $self->TicketObj->SetOwner( $creator );
$RT::Logger->warn( "SetOwner(". $creator->Name . ") failed! $msg") if !$ok;
# TODO SetStatus to next (see RT::Action::AutoOpen)
return $ok;
By Alister West