summaryrefslogtreecommitdiff
path: root/rt/docs/customizing/lifecycles.pod
blob: 76e60003aa50906daed93fb5b6514dce4ac750b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
=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<RT_Config.pm> and paste it into
C<RT_SiteConfig.pm>.

=head2 Add Status Value

Add the status to the set where your new status belongs. This example adds
C<approved> 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<RT_Config/Lifecycles>.

As with all RT custom configuration, if you are customizing the RT
lifecycle, make your changes in your C<RT_SiteConfig.pm> file, not
directly in C<RT_Config.pm>. 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 <img alt="Lifecycle choices" src="../images/lifecycle-choices.png">

=for :text [Lifecycle choices F<docs/images/lifecycle-choices.png>]

=for :man [Lifecycle choices F<docs/images/lifecycle-choices.png>]

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<new>), active (I<open> and
I<stalled>), and inactive (I<resolved>, I<rejected>, and I<deleted>). These
default settings look like this in the C<RT_Config.pm> 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<RT_Config/"Lifecycle definitions">.

Now we want to set up some statuses appropriate for order fulfillment,
so we create a new top-level key called C<orders> 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<declined>. The ApproveOrder
right is similar, but for C<delivery>. 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 <img alt="Lifecycle group rights" src="../images/global-lifecycle-group-rights.png">

=for :text [Lifecycle group rights F<docs/images/global-lifecycle-group-rights.png>]

=for :man [Lifecycle group rights F<docs/images/global-lifecycle-group-rights.png>]

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</Actions>).

=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<new> 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<actions> 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<Respond>.

Using the lifecycles configuration, you can change the label to anything you
like. You can set the update option to C<Comment> or C<Respond>, 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<Comment> for all transitions to C<resolved>.

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</"Full Configuration"> at the end shows other action entries that
make the Decline option available in more cases.

=for html <img alt="Action menu decline" src="../images/action-decline.png">

=for :text [Action menu decline F<docs/images/action-decline.png>]

=for :man [Action menu decline F<docs/images/action-decline.png>]

=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 <img alt="Lifecycle history" src="../images/order-history-example.png">

=for :text [Lifecycle history F<docs/images/order-history-example.png>]

=for :man [Lifecycle history F<docs/images/order-history-example.png>]