6.3. Examples

 < Day Day Up > 

The best way to understand what custom scrips can do is to look at some complete examples.

6.3.1. Your Ticket Was Stolen

The custom template we looked at earlier notified a user when another user stole their ticket. Let's examine that example in more detail. First, we'll implement it solely via the web interface, and then we'll reimplement it as custom modules.

From the web interface, we can create a new scrip. Let's call it Ticket Stolen. The condition should be set to User Defined. The action will be Notify Other Recipients. We will create a custom template to generate an email to the right recipient. If you created that template earlier, you can set the Template field to use that one now. Otherwise you can leave it empty for now.

The stage for this scrip will be TransactionCreate.

6.3.1.1. An OnSteal condition

Since there is no OnSteal condition built into RT, we need to create one.

The following code goes in the Custom condition text entry box:

     my $trans = $self->TransactionObj;     return 0 unless $trans->Field eq 'Owner';     return 1 if $trans->OldValue != RT::Nobody(  )->id(  );     return 0; 

The first thing we check is that the transaction to which the condition is being applied is changing the Owner field. If it's not, we return false. Obviously, stealing a ticket involves changing the ticket's owner.

Next, we check to make sure that the old owner is not the nobody user. If the ticket had no previous owner, then the ticket isn't being stolen. The RT::Nobody( )->id syntax is a bit of an oddity. We're calling a function called Nobody in the RT namespace. This function returns an RT::User object, upon which we call the id( ) method. If we just wrote RT::Nobody->id( ), the Perl interpreter would try to call the id( ) method on a non-existent RT::Nobody class.

Finally, we return false as a default. Adding this to the end of the condition code is a good practice, since it makes the default clear.

6.3.1.2. Our custom template

We are going to use the same template we saw earlier. Here it is again:

     To: { my $old_owner = RT::User->new( $self->CurrentUser );           $old_owner->Load( $Transaction->OldValue );           $old_owner->EmailAddress(  ) }     Subject: Ticket #{ $Ticket->Id(  ) } taken by { $Ticket->OwnerObj->Name(  ) }           A ticket you owned:             { $Ticket->Subject(  ) }           has been taken by {$Ticket->OwnerObj->Name(  )}.           { $RT::WebURL }Ticket/Display.html?id={ $Ticket->Id } 

You might wonder what will happen if the old owner doesn't have an email address. The answer is nothing. If the template's To header is empty, then RT will not try to send email. Although it's not necessary, you could add a bit of action preparation code like this if you wanted:

     my $old_owner = RT::User->new( $self->CurrentUser );     $old_owner->Load( $Transaction->OldValue );     return (defined $old_owner->EmailAddress(  )); 

Once the template is created, the Ticket Stolen scrip can use it. Once that's done, you're all set with your custom business logic.

6.3.2. AutoReply with a Password

When someone emails RT for the first time, RT will add them as a user to the database. However, by default this new user will not be able to log in to the system to view their tickets. If you want them to be able to log in, they need to have a password.

You can customize the existing AutoReply template to create a new password for them and include it in the response. For existing users, you can include their current password.

The customized template might look like this:

     Subject: AutoReply: { $Ticket->Subject(  ) }                 Greetings,           This message has been automatically generated in response to the     creation of a trouble ticket regarding:             "{ $Ticket->Subject(  ) }",     a summary of which appears below.           There is no need to reply to this message right now.  Your ticket     has been assigned an ID of [{ $rtname } #{ $Ticket->Id(  ) }].           Please include the string:                    [{ $rtname } #{ $Ticket->Id(  ) }]           in the subject line of all future correspondence about this     issue. To do so, you may reply to this message.           You may log into the Request Tracker system to view your past and     current tickets at:                     { $RT::WebURL }           {       if ( $Transaction->CreatorObj->id != $RT::Nobody->id            && ! $Transaction->CreatorObj->Privileged            && ! $Transaction->CreatorObj->HasPassword ) {                  my $user = RT::User->new( $RT::SystemUser );            $user->Load( $Transaction->CreatorObj->Id );                  my ($stat, $password) = $user->SetRandomPassword(  );                  if ($stat) {                my $username = $user->Name;                      $OUT .= "     When prompted to log in, please use the following credentials:                       Username: $username                 Password: $password     ";            } else {                $RT::Logger->error( 'Error trying to set random password for '                                    . $user->Name . ": $password" );                      $OUT .= "     There was an error when trying to assign you a new password.     Please contact your local RT administrator at for assistance.     ";            }       }     }                             Thank you,                             { $Ticket->QueueObj->CorrespondAddress(  ) }           -------------------------------------------------------------------------     {$Transaction->Content(  )} 

This template is the same as the one that ships with RT, except for the section in the middle, which automatically assigns a new password to the user if they do not already have one.

Several pieces are worth noting in this template. First, there is the check to see if the user has a password:

     unless ($Transaction->CreatorObj->id == $RT::Nobody->id             && $Transaction->CreatorObj->Privileged             && $Transaction->CreatorObj->HasPassword ) { ... 

We want to make sure that we don't try to give the Nobody user a password, as this user is solely for internal use. We also do not want to auto-generate a password for privileged users, because we assume that the RT administrator manually manages these users. Finally, we need to make sure that the user doesn't already have a password.

When we call $user->SetRandomPassword( ) we check the return value. This method returns a two item list. The first is a return code indicating success (true) or failure (false). If creating the password succeeded, the second item is the new password, which we include in the email. If the password could not be created for some reason, the second item is the error message. We make sure to log this error so that the RT administrator can follow up on it later.

6.3.3. Emergency Pages

If you are using RT to handle a support or sysadmin center, it might be useful to send a message to a pager for certain types of requests.

Let's take a look at how to set up RT to send pages for new tickets with a subject matching /^Emergency/i, but only from 6 p.m. to 8 a.m. These could come from users or from system monitoring software.

This can be done by creating a custom condition and template. The condition checks the subject and time. The template creates the SMS message. To send email, we can use RT's existing Notify action.

     package RT::Condition::OnAfterHoursEmergency;           use strict;     use base 'RT::Condition::Generic';           sub IsApplicable {         my $self = shift;               return 0 unless $self->TicketObj->Subject =~ /^Emergency/i;               my $hour = (localtime)[2];               return 0 unless $hour >= 18 || $hour <= 8;               return 1;     }           1; 

First, we check the ticket subject to make sure that it indicates an emergency. Then we check the time. This will work properly only if the server RT runs on has the correct time. If all of the checks pass, we return a true value. We also will want to make sure that this condition is checked only when creating a ticket, but that can be done when we register the condition with the system.

Now let's create our template:

     From:    Yoyodyne RT <rt@yoyodyne.example.com>     To:      6125559912@pager.example.com     Subject: Yoyodyne RT 911           { $Ticket->Subject(  ) } 

We want to keep the message short, so we use only the ticket's subject as the email body. The template can be created in the system with RT's web interface.

To add our custom condition, we need two steps. First, we need to save the code to our RT installation's local lib directory. This would be /opt/rt3/local/lib/RT/Condition/OnAfterHoursEmergency.pm in this example.

Then we can use a script like the one we saw before to register the condition with RT:

     #!/usr/bin/perl           use strict;     use lib "/opt/rt3/lib";           use RT;     use RT::Interface::CLI qw( CleanEnv GetCurrentUser );     use RT::ScripCondition;           CleanEnv(  );     RT::LoadConfig(  );     RT::Init(  );           my $user = GetCurrentUser(  );     unless( $user->Id ) {         print "No RT user found. Please consult your RT administrator.\n";         exit 1;     }           my $sc = RT::ScripCondition->new($user);           $sc->Create( Name                 => 'After Hours Emergency',                  Description          => 'An emergency ticket is created after hours',                  ExecModule           => 'OnAfterHoursEmergency',                  ApplicableTransTypes => 'Create',                ); 

Note that ApplicableTransTypes field is set to Create, ensuring that this condition is checked only when a new ticket is created. We could have done this in the condition module's IsApplicable( ) method, but this is more efficient.

To create a new scrip for this condition, we would pick After Hours Emergency from the condition drop down in the new scrip form. The action will be Notify Other Recipients. The actual recipients will be picked up from the template. For the template, we use the one we just created.

If we had different pager numbers for different queues, we could create several templates. Then we would set up scrips for each queue, all using the same condition and action, each with a different template.

6.3.4. Using TransactionBatch

Earlier, we talked about the two stages where scrips could run, TransactionCreate and TransactionBatch. The latter stage needs to be enabled in your configuration before it is available.

This might be needed, for example, if you wanted to send an email whenever any of the custom fields for a ticket were updated. Because of the way custom fields work, each change to a custom field is a separate transaction. If a ticket might have five custom fields, we do not want to send five emails every time that ticket is updated!

We can use the TransactionBatch stage to look at all of the transactions at once. Here's an example of a simple template that would run in the TransactionBatch stage:

     {         my @batch = @{ $Ticket->TransactionBatch };               foreach my $txn ( @batch ) {             if ( $txn->Type eq 'CustomField' ) {                 $OUT .= '* ' . $txn->Description . "\n";             }         }         return $OUT;     } 

This template could be used with a scrip that had this custom condition:

     my @batch = @{ $Ticket->TransactionBatch };     foreach my $txn ( @batch ) {         if ( $txn->Type eq 'CustomField' ) {             return 1;         }     }     return 0; 

This scrip simply checks all the transactions in the batch to see if any of them changed custom fields.

The action for this scrip would be one of the Notify actions, depending on exactly who should receive the email.

This is a simple example, but it illustrates the basic idea behind the TransactionBatch stage, which is to allow you to handle a group of transactions all at once.

6.3.5. A Simple Workflow

As we mentioned earlier, one of the uses for custom scrips is to implement a workflow system in RT. Let's look at a simple example of this concept, which uses the ability to have a scrip create a new ticket.

For our example, let's assume that we have two groups of people using RT, each with their own queue. Both of these groups are working in some sort of agency that does creative work, like a graphic design firm.

The first group is the designers. Their queue has tickets like "create mockup of poster for Faye Wong's spring tour." The other group is the account representatives. They use RT to track the designer's work, so they know when things are ready for review with the clients.

It would be possible to simply transfer tickets from one queue to the other, so that when a designer wanted a representative to review their work, they would transfer the ticket to the Review queue and assign ownership to Nobody. But if we wanted to use RT for time tracking, by updating the time worked field for tickets, this could get awkward. In this case, it's better to create a new ticket for each piece of work. And of course, ideally, every time a ticket in the Design queue is closed, a new ticket in the Review queue is opened. If a given project needs additional design work, the account representative can create a new ticket in the Design queue.

To make the automatic creation of Review tickets happen, we can create a new scrip where the condition is On Resolve, and the action is Create Tickets. We just need to create a custom template that creates the correct ticket in the Review queue. That template would look something like this:

     =  =  =Create-Ticket: design-review     Queue:     Review     Subject:   Review of { $Tickets{'TOP'}->Subject(  ) }     Owner:     { $Tickets{'TOP'}->Requestors->UserMembersObj->Next->Id(  ) }     Requestor: { $Tickets{'TOP'}->OwnerObj->EmailAddress(  ) }     RefersTo:  { $Tickets{'TOP'}->Id(  ) }     Content:   A ticket in the Design queue has been resolved and requires review. 

A template used by the Create Tickets action has a special format. It can create one or more tickets. Each ticket to be created has its own header, which in our example is the first line. The header is = = =Create-Ticket: followed by a name.

If you were creating multiple tickets, then each ticket could access the tickets already created via the %Tickets hash. This is handy if you want to create several tickets and link them together. The ticket that triggered the Create Tickets action is always available as $Tickets{'TOP'}.

In our example, we create only one ticket. The subject of the new ticket is based on the ticket being resolved. The owner of the ticket is the first requestor of the original ticket. The requestor is the ticket's owner. We want the new ticket to have a link back to the original ticket, so we set the RefersTo field.

Finally, the Content field is the body of the new ticket. This could be combined with a custom script to send an email whenever a new ticket is created in the Review queue with an owner (as opposed to being owned by Nobody). This way the owner of the new ticket would know that it was their responsibility to review the work just completed.

Approvals

Approvals are a useful way to get tickets OK'd by management. Unlike the review tickets from the simple workflow example above, approvals are managed through a separate Approval menu. The main rules of approvals are:

  • A ticket cannot be closed until all of its approvals are resolved.

  • Once an approval is rejected, its original ticket is immediately rejected, and all of its other approvals are rejected as well.

  • When an approval is resolved or rejected, comments from the person approving or rejecting the approval ticket are attached to the original ticket.

To set up the approval system for a queue, create a new scrip where the condition is On Create and the action is Create Tickets. The scrip should use a new template that looks like this:

     = = =Create-Ticket: manager-approval     Subject:  Approval of { $Tickets{'TOP'}->Subject(  ) }     Queue:    _ _ _Approvals     Type:     approval     Owner:    manager@example.com     Content:  Please review and approve this request.     Depended-On-By: TOP     ENDOFCONTENT 

From this point on, new tickets in that queue will be displayed with the status (pending approval), and the manager will receive a mail saying that there is a new ticket that needs approval. Once the manager approves it in the Approval menu, the staff can then process and resolve it as usual.

It is also possible to have multi-stage approvals, where an approval can happen only after another approval's successful resolution. For example, we can set up a CEO approval stage by editing the template above to add another paragraph of code:

     = = =Create-Ticket: ceo-approval     Subject:  Approval of { $Tickets{'TOP'}->Subject(  ) }     Queue:    _ _ _Approvals     Type:     approval     Owner:    ceo@example.com     Content:  Please review and approve this request.     Depended-On-By: TOP     Depends-On: manager-approval     ENDOFCONTENT 

The last line says that CEO's approval will be activated only after the successful resolution of the manager's approval. Note that you will still need the Depended-On-By: TOP line to make the original ticket depend on the CEO's approval.

The workflow for the approval system is already built into RT. RT creates the _ _Approvals queue as part of its installation process, but this queue does not show up in the normal list of queues. Tickets where the Type is approval are visible only through the Approvals menu.


     < Day Day Up > 


    RT Essentials
    RT Essentials
    ISBN: 0596006683
    EAN: 2147483647
    Year: 2005
    Pages: 166

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net