WorkFlow

From Request Tracker Wiki
Jump to navigation Jump to search

Modeling Workflow in RT

We had the requirement to make sure that when equipment was ordered for installation all necessary tasks were completed by the involved parties. Creating a single ticket and assigning it to each responsible party in turn had the problem of serializing the tasks, not giving advance warning, and not having a predetermined checklist of things to do. This is a fairly simple attempt to model a planned workflow in RT, using a master ticket for the installation, and child tickets for the subtask.

Queue Setup

First, set up a new queue, which we'll call Installation. Nothing special here. Make sure that the correct people have permission to update the queue including the permission to set custom fields.

Custom Field Setup

Next, create a custom field for each subtask. The name of the field should suggest the subtask to the reader, because the field description doesn't show up anyplace useful. We created custom fields 'Order Status', 'Power', 'DNS', 'Rack', 'Array', and 'OSInstall'. Make each of type 'Select one value', which will make them into drop-down lists. For 'Order Status', we're using values such as 'Requirements', 'In Purchasing', 'Vendor', and 'Delivered' to indicate the status of the order. For the other fields we use the simple values 'Required' and 'Completed'. (We are still exploring whether the complexity of additional status values for these are useful.) The 'Required' status will become magic in the next steps. Go back to the Installation queue and enable these custom fields.

Template Setup

Now we need some templates for the child tickets. These determine what will be in the child tickets when they are first created. Create them in the Installation queue; they don't need to be global. Name them something related to the subtask name for your sanity, but the name itself doesn't link them. Here is the PowerTicket template:

===Create-Ticket: power
Subject: Power::{$Tickets{'TOP'}->Subject}
Parents: TOP
Queue: Installation
Owner: smith
AdminCc: jones
Content: Power requirements are needed for this equipment.
Please see the parent ticket for details.
ENDOFCONTENT

The Owner of the ticket should be someone already defined in your RT database who will be responsible for the subtask, in this example smith. The AdminCc can be used for that person's backup or other interested parties. As of RT 3.4.2, both of these have to be users, not groups. It would be very nice if groups worked, to allow more dynamic updating of responsibilities if someone is away. Notification of the new owners is taken care of by the normal Create Ticket actions in the queue.

The Queue is the queue in which the new ticket is created. At the moment, we've elected to create them in the same queue as the parent ticket, but it may make more sense to create them in other queues. The Parent link will keep everything tied together if you do.

Action Setup

Now we can glue the templates to the custom fields. To make this clean, I created a custom Condition, installed in $RTHOME/local/lib/RT/Condition/FieldRequired.pm:

# Local condition to check that a custom field
# has been set to "Required".
# -Chuck Boeheim 3/13/06

package RT::Condition::FieldRequired;
require RT::Condition;

use strict;
use vars qw/@ISA/;
@ISA = qw(RT::Condition);


=head2 IsApplicable

If the field named as an argument becomes 'Required'.
Only triggers on transitions, not if it already had
that value.

=cut

sub IsApplicable {
    my $self = shift;
    my $field = $self->Argument;
    my $trans = $self->TransactionObj;
    if ($trans->Type eq 'Create') {
       return 1 if $trans->TicketObj->FirstCustomFieldValue($field) =~ /^Required/;
    }
    if ($trans->Type eq 'CustomField') {
       my $cf = RT::CustomField->new($self->CurrentUser);
       $cf->Load($field);
       return 1 if $trans->Field == $cf->Id && $trans->NewValue =~ /^Required/;
    }
    return undef;
}

1;

The new action can be installed with the following script, which needs to have your custom field names put in the appropriate place:

# To install, install FieldRequired.pm in local/lib/RT/Conditions, and
# this script in local/etc/FieldRequired.install
#     /path/to/rt3/sbin/rt-setup-database --action insert \
#         --datafile /path/to/rt3/local/etc/FieldRequired.install
#
@ScripConditions = (
    { Name        => 'On Power Required',
      Description => 'Trigger when custom field Power becomes Required',
      ExecModule  => 'FieldRequired',
      Argument    => 'Power',                # Your custom field name
      ApplicableTransTypes => 'Any' },
    { Name        => 'On DNS Required',
      Description => 'Trigger when custom field DNS becomes Required',
      ExecModule  => 'FieldRequired',
      Argument    => 'DNS',                   # Next custom field name
      ApplicableTransTypes => 'Any' },
# Repeat for additional fields
);

Then add scrips to the Installation queue for each subtask that use your new conditions:

Description: Create Power Ticket
Condition: On Power Required        <== The ScripCondition Name in the previous step
Action: Create Tickets
Template: PowerTicket                   <== The template name in the template step above
Stage: TransactionCreate

At this point, if you create a ticket in the Installation queue and set a custom field to 'Required' it will create the child ticket. It will also do this if you update an existing ticket to set a 'Required' status. This allows you to create a ticket for planning, and only initiate the subtasks at the time they are necessary.

Completing Subtasks

Now one final piece of glue to update the parent status when the child completes. Create another scrip in the queue(s) that have the child tickets. (This is one reason to keep the child tickets in the installation queue.)

Description: On Resolve, Update Parent
Condition: On Resolve
Action: User Defined
Template: Global Template Blank
Stage: TransactionCreate

CustomCondition: None

CustomActionPreparationCode:

return 1;

CustomActionCleanupCode:

return 1 if ($self->TransactionObj->NewValue !~ /^(?:resolved|deleted|rejected)$/);
# Figure out which kind of child this is
my $cf_value = 'Completed';
my $cf_name;

my $subject = $self->TicketObj->Subject;
$cf_name = 'Power'      if $subject =~ /^Power::/;
$cf_name = 'Network'    if $subject =~ /^Network::/;
# Repeat for your custom field names.
# There may be a better way to do this.

# Alternative:
# Setup the templates to use the same name as the custom fields, then you can
# extract the name of each field from the subject, using Perl regex grouping.
# my $subject = $self->TicketObj->Subject;
# if($subject =~ /^(.*)::/)
# {$cf_name = $1;}

return undef unless $cf_name;

my $actor = $self->TransactionObj->CreatorObj;
my $actorname = $actor->RealName . ' (' . $actor->EmailAddress . ')';
my $CF_Obj = RT::CustomField->new($self->CurrentUser);
# current ticket is member of(child of some parents)
my $MemberOf = $self->TicketObj->MemberOf;
while (my $l = $MemberOf->Next ) {
  # we can't check non local objects
  next unless( $l->TargetURI->IsLocal );

  # Update the custom field in the parent ticket to show completed.
  $CF_Obj->LoadByName( Name  => $cf_name,
                   Queue => $l->TargetObj->QueueObj->Id);
  $CF_Obj->AddValueForObject( Object  => $l->TargetObj,
                          Content => $cf_value );
  my $id = $self->TicketObj->id;
  my $status = $self->TicketObj->Status;
  $l->TargetObj->Correspond(Content => <<'END');
Child ticket completed:
Ticket:  $id
Status:  $status
Subject: $subject
By:      $actorname
END
}

return 1;