=head1 Scrip Conditions & Actions

The core building blocks of scrips in RT are the conditions and actions
you select when configuring the scrip. A condition defines the criteria
for an action to run in the context of the current transaction. The
result is true or false: if true, the condition is satisfied and the
action runs, if false, the action is skipped. Actions define something
to be done when a condition is true and they can be anything you
can capture in code: sending a notification (email, SMS, etc.),
changing things in RT like data on a ticket, or calling out to
external systems, DBs, or APIs.

You can view all of the scrips that come standard with RT
by going to Admin > Global > Scrips. In the scrips list you'll see each has a
condition and an action and these are provided with the initial RT
installation. You might also see additional conditions and actions added
by extensions or through a local customization.

You can view the default conditions and actions provided in RT at
Admin > Global > Conditions and Admin > Global > Actions. This document
describes how to add new conditions and actions to RT so you can provide
additional automation to your workflows.

As you look at the default conditions and actions provided with RT, you'll
notice that it's not possible to edit them. This is to prevent changes from
accidentally impacting default RT functionality. However, it is easy to copy
a default condition or action, modify it, and then change existing scrips or
create new scrips that use your new conditions and actions.

=head2 Scrips on Queues, Catalogs, and Classes

Starting in RT 6, scrips are available for queues (tickets),
catalogs (assets), and classes (articles). The examples below show code
with a ticket object, accessed with C<$self-E<gt>TicketObj>. To access
an asset object, use C<$self-E<gt>AssetObj>. For articles, use
C<$self-E<gt>ArticleObj>.

For conditions or actions that could be used for multiple types, you
can also access the current object with C<$self-E<gt>Object>. The
loaded object will be based on the currently running scrip condition
or action. Not all methods are available for all objects,
so make sure to only call methods you have verified are available
when using this approach.

=head2 User Defined Conditions and Actions

The simplest way to add a custom condition or action is to create a new
scrip and select "User Defined" as the Condition or Action. You can then
put your custom code right in the "User Defined" boxes on the bottom of
the scrip modification page. You can put any code described in the later
sections in the User Defined box in the RT web UI and it will run just
as it would in a module file.

However, you might prefer writing your condition or action in a module
with the code in a file. This allows you to track it in version control
and call it from other places like C<rt-crontool>. The following sections
describe how to create these modules.

=head2 Custom Conditions

Let's assume you have a custom lifecycle with a status
called 'review' and you want an 'On Review Needed' condition so you can
trigger actions when a ticket is put in review status. You notice RT
already has 'On Resolve' and other similar conditions, so you look at
the configuration at Admin > Global > Conditions and click on 'On Resolve'.

The condition has a Name, which is displayed in the Condition dropdown when
you create a scrip, and a Description to identify it. The Condition Module is
the RT module that executes the condition, in this case C<StatusChange>. You
can find the code in C</opt/rt6/lib/RT/Condition/StatusChange.pm> and view
the documentation at L<RT::Condition::StatusChange>.

Parameters to Pass shows the actual parameter that is passed to the module
when this condition is executed. When you look at the module documentation
it makes sense when you see that C<StatusChange> accepts a valid status and
returns true if the transaction is setting the status to the provided value.
Finally, Applicable Transaction Types lists the transactions for which this
condition will run, and in this case it's transactions of type 'Status'.

=for html <img alt="Status Change Condition" src="../images/status-change-condition.png">

=for :text [Status change condition F<docs/images/status-change-condition.png>]

=for :man [Status change condition F<docs/images/status-change-condition.png>]

This is really close to what we might need for our 'On Review Needed' so
you can click the Copy Condition button to copy the current condition. On
the new condition page, you can update the Name and Description and set
the Parameters to Pass to 'review'. Then click save and you have your new
condition. You can now create a new scrip and select it from the Condition
dropdown.

=head2 Custom Condition Module

Now assume we have an additional requirement to check if a custom field value
'Special' is selected when we check the review status. For this one
we'll need to write some code. To start, create a new file for your new
SpecialReviewNeeded module here:

    /opt/rt6/local/lib/RT/Condition/SpecialReviewNeeded.pm

Creating it in the C<local> directory will keep it safe when you apply
RT upgrades in the future.

The basics of a condition module are as follows:

    package RT::Condition::SpecialReviewNeeded;

    use strict;
    use warnings;
    use base 'RT::Condition';

    sub IsApplicable {
        my $self = shift;

        # Your code here

        return 1; # True if condition is true, false if not
    }

    1; # Don't forget module needs this

C<IsApplicable> is the method you will override from the C<RT::Condition>
base class. The return value of this method, true or false, determines
whether the condition passes or not.

C<$self> gives you access to the ticket object and transaction object via:

    $self->TransactionObj
    $self->TicketObj

These are your main hooks into the current ticket and transaction.

To check review status and the custom field value, we might add something
like this:

    # Setting status to review?
    return 0 unless $self->TransactionObj->Type eq 'Status'
        and $self->TransactionObj->NewValue eq 'review';

    # Is 'Special' set to Yes?
    return 0 unless $self->TicketObj->FirstCustomFieldValue('Special') eq 'Yes';

    return 1;

We've hardcoded C<review> and C<Special> here, but as with C<StatusChange>,
you could pass a value from the Parameters to Pass field. You can access this
value by calling the C<Argument> method.

    my $arg = $self->Argument;

Using passed arguments can make your conditions and actions more general
and potentially reusable.

Once the file is created, return to the RT web UI and create a new condition,
possibly by editing On Review Needed and clicking Copy Condition.
You can name it Special Review Needed and set the Condition Module to
SpecialReviewNeeded.

=head2 Custom Actions

Once you have the correct condition you can now think about the action. You
want to send email to a group of people, so to start you look at some of the
existing actions on the action display page at Admin > Global > Actions.
You find Notify AdminCcs, which might be close. Taking
a quick look you see it has a Name and Description, like conditions, and
the module it calls is C<Notify>, which can be found at
C</opt/rt6/lib/RT/Action/Notify.pm>.

The Parameter to Pass is AdminCc, and if you look at other notification
actions you'll see many use Notify and just pass a different ticket role.

Your reviewers aren't always AdminCcs on tickets, so you'd rather
send a notification to a group. You can create this new action using the
existing action module C<NotifyGroup>. On the action list page, click Create
and add something like the following:

    Name               Notify Review Group
    Description        Send notification to the review group
    Action Module      NotifyGroup
    Parameters to Pass Review Group

The 'Review Group' can be whatever your group name is. Then you can build a
template with some custom ticket information for reviewers and set up a new
scrip to send email to the review group whenever a ticket status is set to
review.

=head2 Custom Action Modules

As part of the request to add a condition to check for the 'Special' custom
field, we now want to route these special requests to the person who handles
them. This extra bit of functionality will require a module, maybe called
C<SetOwner>. Create the new file in:

    /local/lib/RT/Action/SetOwner.pm

The base action code looks like this:

    package RT::Action::SetOwner;

    use strict;
    use warnings;
    use base 'RT::Action';

    sub Prepare {
        my $self = shift;

        # Your code here

        return 1; # True if Commit should run, false if not
    }

    sub Commit {
        my $self = shift;

        # Your code here

        return 1; # True if action was successful
    }

    1; # Don't forget module needs this

Actions have two methods you can override. The C<Prepare> method provides
you with a chance to make sure the action should actually run.
If C<Prepare> returns false, C<Commit> will not run. You'll typically
handle this in your condition, in which case you can just omit C<Prepare>
from your action. However, when you have a condition that covers a common
general case, but you want to check one extra criteria for a particular
action, the C<Prepare> method can be helpful. In our example, you might
choose to keep just the On Review Needed condition and add the check
for the 'Special' custom field to the C<Prepare> method.

C<Commit> is where you do the actual work of the action. It should
return true on success. On failure, you can use C<RT::Logger> to
write errors or debugging information to RTs logs so you can track
down the problem.

In actions, C<$self> gives you access to the transaction and ticket
objects, just like conditions, via:

    $self->TransactionObj
    $self->TicketObj

For our C<SetOwner> action, we don't need C<Prepare> and can add the
following to C<Commit>:

    my $user = RT::User->new(RT->SystemUser);
    my ($ret, $msg) = $user->Load($self->Argument);
    RT::Logger->error('Unable to load user: '
                       . $self->Argument . " $msg") unless $ret;

    $self->TicketObj->SetOwner($user->Id);
    return 1;

The C<Argument> method returns the value set for Parameters to
Pass in the action configuration. This example expects the
argument to be the username of an RT user.

Now you can create the new action in RT. Go to the action page, click
Create, and enter the following:

    Name               Set Owner
    Description        Set owner
    Action Module      SetOwner
    Parameters to Pass reviewer_username

Click Create and the new action will be available when creating scrips.

=for html <img alt="Create Set Owner Action" src="../images/create-setowner-action.png">

=for :text [Create Set Owner action F<docs/images/create-setowner-action.png>]

=for :man [Create Set Owner action F<docs/images/create-setowner-action.png>]

Note that actions you perform in scrips can themselves create new
transactions, as is the case with C<SetOwner>. When this action runs,
the set owner transaction will fire the default On Owner Change Notify
Owner scrip, if it is enabled.

=head1 ADDITIONAL INFORMATION

When writing actions and conditions, it's helpful to look at the actions
and conditions provided with RT. You can find more information about
the methods available from ticket and transaction objects in your RT
distribution and on the
L<"Best Practical website"|http://docs.bestpractical.com>.
