=head1 Ticket Lifecycles By default, RT comes with ticket statuses that work for many types of workflows: new, open, stalled, resolved, rejected, and deleted. But there can be any number of workflows where these status values don't completely fit. RT allows you to add new custom status values and define their behavior with a feature called Lifecycles. =head1 Adding a New Status Because Statuses are controlled via lifecycles, you must manipulate the entire lifecycle configuration to add a status. In earlier versions of RT new statuses could be added by adding a new element to an array in RT's config file. But because lifecyles are built around statuses, the entire lifecycle configuration must be modified even if you only need new statuses. =head2 Copy Lifecycle Config First, copy the C<%Lifecycles> hash from C and paste it into C. =head2 Add Status Value Add the status to the set where your new status belongs. This example adds C to the active statuses: active => [ 'open', 'approved', 'stalled' ], =head2 Update Transitions Now the transitions section must be updated so that the new status can transition to the existing statuses and also so the existing statuses can transition to the new status. new => [qw( open approved stalled resolved rejected deleted)], open => [qw(new approved stalled resolved rejected deleted)], approved => [qw(new open stalled resolved rejected deleted)], stalled => [qw(new open approved rejected resolved deleted)], resolved => [qw(new open approved stalled rejected deleted)], rejected => [qw(new open approved stalled resolved deleted)], deleted => [qw(new open approved stalled rejected resolved )], =head1 Order Processing Example This guide demonstrates lifecycles using an order fulfillment system as a real-world example. You can find full lifecycles documentation in L. As with all RT custom configuration, if you are customizing the RT lifecycle, make your changes in your C file, not directly in C. If you are adding a new lifecycle, you can add a new entry with: Set(%Lifecycles, my_new_lifecycle => { ... } ); The detailed configuration options are discussed below. Once you add it and restart the server, the new lifecycle will be available on the queue configuration page. To show how you might use custom lifecycles, we're going to configure an RT lifecycle to process orders of some sort. In our order example, each ticket in the queue is considered a separate order and the orders have the following statuses: =over =item pending The order just came in untouched, pending purchase validation =item processing The order is being looked at for transaction processing =item delivery The order is out for delivery =item delivered The order was successfully delivered to its destination =item refunded The order was delivered but subsequently refunded =item declined There was an error in the process validation and the order was denied purchase =back In this particular example, the only status an order can start with is 'pending.' When a process coordinator chooses to take this order, it goes into processing. The order can then either be delivered or denied processing. Once denied, the lifecycle for that order ends. If it is delivered, the order can still be refunded. The following sections walk through each part of the configuration. You can find the full configuration at the end in case you want to see the exact syntax or use it to experiment with. =head2 Defining Status Values Every queue has a lifecycle assigned to it. Without changing any configuration, you are given two lifecycles to choose from: "default" and "approvals." The approvals lifecycle is used by the internal approvals queue, and should not be changed or used by other queues. Do not modify the approvals lifecycle unless you fully understand how RT approvals work. =for html Lifecycle choices =for :text [Lifecycle choices F] =for :man [Lifecycle choices F] In RT 4.0, the C<@ActiveStatus> and C<@InactiveStatus> configurations which were previously available are gone. The logic defined by those options is now a subset of RT's lifecycle features, as described here. A ticket naturally has three states: initial (I), active (I and I), and inactive (I, I, and I). These default settings look like this in the C file: default => { initial => [ 'new' ], active => [ 'open', 'stalled' ], inactive => [ 'resolved', 'rejected', 'deleted' ], The initial state is the default starting place for new tickets, although you can create tickets with other statuses. Initial is generally used to acknowledge that a request has been made, but not yet acted on. RT sets the Started date on a ticket when it is moved out of the initial state. Active tickets are currently being worked on, inactive tickets have reached some final state. By default, inactive tickets don't show up in search results. The AutoOpen action sets a ticket's status to the first active status. You can find more details in L. Now we want to set up some statuses appropriate for order fulfillment, so we create a new top-level key called C and add our new status values. Set( %Lifecycles, orders => { initial => [ 'pending' ], active => [ 'processing', 'delivery' ], inactive => [ 'delivered', 'returned', 'declined', 'deleted' ], # ..., }); We still use the initial, active and inactive categories, but we are able to define status values that are appropriate for the workflow we want to create. This should make the system more intuitive for users. =head2 Transitions The typical lifecycle follows the path initial -> active -> inactive. Obviously the path of a ticket can get more complicated than this, which is where transitions come into play. Transitions manage the flow of a ticket from status to status. This section of the configuration has keys, which are the current status, and values that define which other statuses the ticket can transition to. Here are the transitions we define for our order process. Set( %Lifecycles, orders => { # ..., transitions => { '' => [qw(pending processing declined)], pending => [qw(processing declined deleted)], processing => [qw(pending declined delivery delivered deleted)], delivery => [qw(pending delivered returned deleted)], delivered => [qw(pending returned deleted)], returned => [qw(pending delivery deleted)], deleted => [qw(pending processing delivered delivery returned)], }, # ..., }); If a ticket is in the delivered status, it doesn't make sense for it to transition to processing or declined since the customer already has the order. However, it can transition to returned since they could send it back. The configuration above defines this for RT. The C<''> entry defines the valid statuses when a ticket is created. Deleted is a special status in RT that allows you to remove a ticket from active use. You may need to do this if a ticket is created by mistake, or a duplicate is created. Once deleted, a ticket will never show up in search results. As you can see, the system will allow you to transition to deleted from any status. =head2 Rights and Access Control Your workflow may have several people working on tickets at different steps, and for some you may want to make sure only certain users can perform certain actions. For example, the company may have a rule that only the quality assurance team is allowed to approve (or decline) an order for delivery. You can apply labels to transitions and assign rights to them to allow you to apply this sort of access control. This is done with a rights entry: Set( %Lifecycles, orders => { # ..., rights => { '* -> declined' => 'DeclineOrder', '* -> delivery' => 'ApproveOrder', }, # ..., }); This configuration tells RT to require the right DeclineOrder for a transition from any status (C<*>) to C. The ApproveOrder right is similar, but for C. These rights take the place of the standard ModifyTicket right, not in addition to it, so keep that in mind when creating and assigning new rights. Once these rights are configured and loaded (by restarting the web server), they can be assigned in the web UI to groups, queues, and users. The rights show up on the rights pages in a Status tab alongside the standard RT rights tabs. =for html Lifecycle group rights =for :text [Lifecycle group rights F] =for :man [Lifecycle group rights F] After a status transition right is granted, users with the right will see the status in the drop-down, and possibly any related actions (see L). =head2 Default Status There are interfaces to RT from which it isn't possible to define a status, like sending an email to create a ticket, but tickets require a status. To handle these cases, you can set default status values for RT to use when the user doesn't explicitly set a value. Looking at the defaults section in the standard RT configuration, you can see the events for which you can define a default status. For example, 'on_create' => 'new' automatically gives newly created tickets a C status when the requestor doesn't supply a status. We can do the same for our process. Set( %Lifecycles, orders => { defaults => { on_create => 'pending', }, # ..., }); Only a small number of defaults are needed because in practice there are relatively few cases where a ticket will find itself without a status or in an ambiguous state. =head2 Actions To customize how transitions are presented in RT, lifecycles have an C section where you can customize how an action (e.g. changing status from new -> open) looks and functions. You can customize the action's label, which is how it appears to users, and the type of update, either comment or reply. As an example, in the default RT configuration the action "new -> open" has the default label "Open it" and an update value of C. Using the lifecycles configuration, you can change the label to anything you like. You can set the update option to C or C, which tells RT to process the action as a comment (not sent to requestors) or a reply (sent to requestors). This part of the lifecycles configuration replaces the previous C<$ResolveDefaultUpdateType> configuration value. To mimic that option, set the update type to C for all transitions to C. Here is an example of a change we might make for our order process: Set( %Lifecycles, orders => { # ..., actions => [ 'pending -> processing' => { label => 'Open For Processing', update => 'Comment', }, 'pending -> declined' => { label => 'Decline', update => 'Respond', }, # ... ], # ... }); Alternatively, supplying no update type results in a "quick" action that changes the status immediately without going through the ticket update page. RT's default "Delete" action is a "quick" action, for example: # from the RT "default" lifecycle 'new -> deleted' => { label => 'Delete', }, If the transition has an associated right, it must be granted for a user to see the action. For example, if we give a group the DeclineOrder right as shown in the earlier example, members of that group will see a Decline option in their Actions menu if a ticket has a pending status. The L at the end shows other action entries that make the Decline option available in more cases. =for html Action menu decline =for :text [Action menu decline F] =for :man [Action menu decline F] =head2 Mapping Between Queues As we've demonstrated, each queue can have its own custom lifecycle, but in RT you sometimes want to move a ticket from one queue to another. A ticket will have a status in a given queue, but that status may not exist in another queue you want to move the ticket to, or it may exist but mean something different. To allow tickets to move between queues with different lifecycles, RT needs to know how to set the status appropriately. The lifecycle configuration has a C<__maps__> entry to allow you to specify the mappings you want between different queues. Sometimes statuses between queues don't or can't match perfectly, but if you need to move tickets between those queues, it's important that you provide a complete mapping, defining the most sensible mapping you can. If you don't provide a mapping, users will see an error when they try to move a ticket between queues with different lifecycles but no mapping. Set( %Lifecycles, orders => { # ..., __maps__ => { 'default -> orders' => { 'new' => 'pending', 'open' => 'processing', # ..., }, 'orders -> default' => { 'pending' => 'new', 'processing' => 'open', # ..., }, # ..., }, # ..., }); In the example above, we first define mappings between the default queue and our new orders queue. The second block defines the reverse for tickets that might be moved from the orders queue to a queue that uses the default lifecycle. =head2 Full Configuration Here is the full configuration if you want to add it to your RT instance to experiment. Set(%Lifecycles, # 'orders' shows up as a lifecycle choice when you create a new # queue or modify an existing one orders => { # All the appropriate order statuses initial => [ 'pending' ], active => [ 'processing', 'delivery' ], inactive => [ 'delivered', 'returned', 'declined' ], # Default order statuses for certain actions defaults => { on_create => 'pending', }, # Status change restrictions transitions => { '' => [qw(pending processing declined)], pending => [qw(processing declined deleted)], processing => [qw(pending declined delivery delivered deleted)], delivery => [qw(pending delivered returned deleted)], delivered => [qw(pending returned deleted)], returned => [qw(pending delivery deleted)], deleted => [qw(pending processing delivered delivery returned)], }, # Rights for different actions rights => { # These rights are in the default lifecycle '* -> deleted' => 'DeleteTicket', '* -> *' => 'ModifyTicket', # Maybe we want to create rights to keep QA rigid '* -> declined' => 'DeclineOrder', '* -> delivery' => 'ApproveOrder', }, # Actions for the web UI actions => [ 'pending -> processing' => { label => 'Open For Processing', update => 'Comment', }, 'pending -> delivered' => { label => 'Mark as being delivered', update => 'Comment', }, 'pending -> declined' => { label => 'Decline', update => 'Respond', }, 'pending -> deleted' => { label => 'Delete', }, 'processing -> declined' => { label => 'Decline', update => 'Respond', }, 'processing -> delivery' => { label => 'Out for delivery', update => 'Comment', }, 'delivery -> delivered' => { label => 'Mark as delivered', update => 'Comment', }, 'delivery -> returned' => { label => 'Returned to Manufacturer', update => 'Respond', }, 'delivered -> returned' => { label => 'Returned to Manufacturer', update => 'Respond', }, 'returned -> delivery' => { label => 'Re-deliver Order', update => 'Respond', }, 'deleted -> pending' => { label => 'Undelete', update => 'Respond', }, ], }, # Status mapping different different lifecycles __maps__ => { 'default -> orders' => { 'new' => 'pending', 'open' => 'processing', 'stalled' => 'processing', 'resolved' => 'delivered', 'rejected' => 'declined', 'deleted' => 'deleted', }, 'orders -> default' => { 'pending' => 'new', 'processing' => 'open', 'delivered' => 'resolved', 'returned' => 'open', # closest matching we have in 'default' 'declined' => 'rejected', 'deleted' => 'deleted', }, }, ); Here is an example history of a ticket following this lifecycle: =for html Lifecycle history =for :text [Lifecycle history F] =for :man [Lifecycle history F]