]>
Commit | Line | Data |
---|---|---|
45030404 MKG |
1 | =head1 Ticket Lifecycles |
2 | ||
3 | By default, RT comes with ticket statuses that work for many types | |
4 | of workflows: new, open, stalled, resolved, rejected, and deleted. | |
5 | But there can be any number of workflows where these status values | |
6 | don't completely fit. RT allows you to add new custom status values and | |
7 | define their behavior with a feature called Lifecycles. | |
8 | ||
c33a4027 MKG |
9 | =head1 Adding a New Status |
10 | ||
11 | Because Statuses are controlled via lifecycles, you must manipulate the entire | |
12 | lifecycle configuration to add a status. In earlier versions of RT new statuses | |
13 | could be added by adding a new element to an array in RT's config file. But | |
14 | because lifecyles are built around statuses, the entire lifecycle configuration | |
15 | must be modified even if you only need new statuses. | |
16 | ||
17 | =head2 Copy Lifecycle Config | |
18 | ||
19 | First, copy the C<%Lifecycles> hash from C<RT_Config.pm> and paste it into | |
20 | C<RT_SiteConfig.pm>. | |
21 | ||
22 | =head2 Add Status Value | |
23 | ||
24 | Add the status to the set where your new status belongs. This example adds | |
25 | C<approved> to the active statuses: | |
26 | ||
27 | active => [ 'open', 'approved', 'stalled' ], | |
28 | ||
29 | =head2 Update Transitions | |
30 | ||
31 | Now the transitions section must be updated so that the new status can | |
32 | transition to the existing statuses and also so the existing statuses can | |
33 | transition to the new status. | |
34 | ||
35 | new => [qw( open approved stalled resolved rejected deleted)], | |
36 | open => [qw(new approved stalled resolved rejected deleted)], | |
37 | approved => [qw(new open stalled resolved rejected deleted)], | |
38 | stalled => [qw(new open approved rejected resolved deleted)], | |
39 | resolved => [qw(new open approved stalled rejected deleted)], | |
40 | rejected => [qw(new open approved stalled resolved deleted)], | |
41 | deleted => [qw(new open approved stalled rejected resolved )], | |
42 | ||
43 | =head1 Order Processing Example | |
44 | ||
45030404 MKG |
45 | This guide demonstrates lifecycles using an order fulfillment |
46 | system as a real-world example. You can find full lifecycles | |
47 | documentation in L<RT_Config/Lifecycles>. | |
48 | ||
49 | As with all RT custom configuration, if you are customizing the RT | |
50 | lifecycle, make your changes in your C<RT_SiteConfig.pm> file, not | |
51 | directly in C<RT_Config.pm>. If you are adding a new lifecycle, you can | |
52 | add a new entry with: | |
53 | ||
54 | Set(%Lifecycles, my_new_lifecycle => { ... } ); | |
55 | ||
56 | The detailed configuration options are discussed below. Once you add it | |
57 | and restart the server, the new lifecycle will be available on the | |
58 | queue configuration page. | |
59 | ||
45030404 MKG |
60 | To show how you might use custom lifecycles, we're going to configure |
61 | an RT lifecycle to process orders of some sort. In our order example, | |
62 | each ticket in the queue is considered a separate order and the orders | |
63 | have the following statuses: | |
64 | ||
65 | =over | |
66 | ||
67 | =item pending | |
68 | ||
69 | The order just came in untouched, pending purchase validation | |
70 | ||
71 | =item processing | |
72 | ||
73 | The order is being looked at for transaction processing | |
74 | ||
75 | =item delivery | |
76 | ||
77 | The order is out for delivery | |
78 | ||
79 | =item delivered | |
80 | ||
81 | The order was successfully delivered to its destination | |
82 | ||
83 | =item refunded | |
84 | ||
85 | The order was delivered but subsequently refunded | |
86 | ||
87 | =item declined | |
88 | ||
89 | There was an error in the process validation and the order was denied purchase | |
90 | ||
91 | =back | |
92 | ||
93 | In this particular example, the only status an order can start with is | |
94 | 'pending.' When a process coordinator chooses to take this order, it | |
95 | goes into processing. The order can then either be delivered or denied | |
96 | processing. Once denied, the lifecycle for that order ends. If it is | |
97 | delivered, the order can still be refunded. | |
98 | ||
99 | The following sections walk through each part of the configuration. | |
100 | You can find the full configuration at the end in case you want to | |
101 | see the exact syntax or use it to experiment with. | |
102 | ||
103 | =head2 Defining Status Values | |
104 | ||
105 | Every queue has a lifecycle assigned to it. Without changing any | |
106 | configuration, you are given two lifecycles to choose from: "default" | |
107 | and "approvals." The approvals lifecycle is used by the internal | |
108 | approvals queue, and should not be changed or used by other queues. Do | |
109 | not modify the approvals lifecycle unless you fully understand how RT | |
110 | approvals work. | |
111 | ||
112 | =for html <img alt="Lifecycle choices" src="../images/lifecycle-choices.png"> | |
113 | ||
114 | =for :text [Lifecycle choices F<docs/images/lifecycle-choices.png>] | |
115 | ||
116 | =for :man [Lifecycle choices F<docs/images/lifecycle-choices.png>] | |
117 | ||
118 | In RT 4.0, the C<@ActiveStatus> and C<@InactiveStatus> configurations | |
119 | which were previously available are gone. The logic defined by those | |
120 | options is now a subset of RT's lifecycle features, as described here. | |
121 | ||
122 | A ticket naturally has three states: initial (I<new>), active (I<open> and | |
123 | I<stalled>), and inactive (I<resolved>, I<rejected>, and I<deleted>). These | |
124 | default settings look like this in the C<RT_Config.pm> file: | |
125 | ||
126 | default => { | |
127 | initial => [ 'new' ], | |
128 | active => [ 'open', 'stalled' ], | |
129 | inactive => [ 'resolved', 'rejected', 'deleted' ], | |
130 | ||
131 | The initial state is the default starting place for new tickets, although | |
132 | you can create tickets with other statuses. Initial is generally used | |
133 | to acknowledge that a request has been made, but not yet acted on. RT | |
134 | sets the Started date on a ticket when it is moved out of the initial state. | |
135 | ||
136 | Active tickets are currently being worked on, inactive tickets have reached | |
137 | some final state. By default, inactive tickets don't show up in search | |
138 | results. The AutoOpen action sets a ticket's status to the first active | |
139 | status. You can find more details in L<RT_Config/"Lifecycle definitions">. | |
140 | ||
141 | Now we want to set up some statuses appropriate for order fulfillment, | |
142 | so we create a new top-level key called C<orders> and add our new status | |
143 | values. | |
144 | ||
145 | Set( %Lifecycles, orders => { | |
146 | initial => [ 'pending' ], | |
147 | active => [ 'processing', 'delivery' ], | |
148 | inactive => [ 'delivered', 'returned', 'declined', 'deleted' ], | |
149 | # ..., | |
150 | }); | |
151 | ||
152 | We still use the initial, active and inactive categories, but we are | |
153 | able to define status values that are appropriate for the workflow | |
154 | we want to create. This should make the system more intuitive for users. | |
155 | ||
156 | =head2 Transitions | |
157 | ||
158 | The typical lifecycle follows the path initial -> active -> inactive. | |
159 | Obviously the path of a ticket can get more complicated than this, which | |
160 | is where transitions come into play. | |
161 | ||
162 | Transitions manage the flow of a ticket from status to status. This | |
163 | section of the configuration has keys, which are the current status, | |
164 | and values that define which other statuses the ticket can transition | |
165 | to. Here are the transitions we define for our order process. | |
166 | ||
167 | Set( %Lifecycles, orders => { | |
168 | # ..., | |
169 | transitions => { | |
170 | '' => [qw(pending processing declined)], | |
171 | pending => [qw(processing declined deleted)], | |
172 | processing => [qw(pending declined delivery delivered deleted)], | |
173 | delivery => [qw(pending delivered returned deleted)], | |
174 | delivered => [qw(pending returned deleted)], | |
175 | returned => [qw(pending delivery deleted)], | |
176 | deleted => [qw(pending processing delivered delivery returned)], | |
177 | }, | |
178 | # ..., | |
179 | }); | |
180 | ||
181 | If a ticket is in the delivered status, it doesn't make sense for it to | |
182 | transition to processing or declined since the customer already has the | |
183 | order. However, it can transition to returned since they could send it back. | |
184 | The configuration above defines this for RT. | |
185 | ||
186 | The C<''> entry defines the valid statuses when a ticket is created. | |
187 | ||
188 | Deleted is a special status in RT that allows you to remove a ticket from | |
189 | active use. You may need to do this if a ticket is created by mistake, or | |
190 | a duplicate is created. Once deleted, a ticket will never show up in search | |
191 | results. As you can see, the system will allow you to | |
192 | transition to deleted from any status. | |
193 | ||
194 | =head2 Rights and Access Control | |
195 | ||
196 | Your workflow may have several people working on tickets at different | |
197 | steps, and for some you may want to make sure only certain users | |
198 | can perform certain actions. For example, the company may have a rule | |
199 | that only the quality assurance team is allowed to approve (or decline) | |
200 | an order for delivery. | |
201 | ||
202 | You can apply labels to transitions and assign rights to them to allow | |
203 | you to apply this sort of access control. This is done with a rights | |
204 | entry: | |
205 | ||
206 | Set( %Lifecycles, orders => { | |
207 | # ..., | |
208 | rights => { | |
209 | '* -> declined' => 'DeclineOrder', | |
210 | '* -> delivery' => 'ApproveOrder', | |
211 | }, | |
212 | # ..., | |
213 | }); | |
214 | ||
215 | This configuration tells RT to require the right DeclineOrder for a | |
216 | transition from any status (C<*>) to C<declined>. The ApproveOrder | |
217 | right is similar, but for C<delivery>. These rights take the place of | |
218 | the standard ModifyTicket right, not in addition to it, so keep that | |
219 | in mind when creating and assigning new rights. | |
220 | ||
221 | Once these rights are configured and loaded (by restarting the web | |
222 | server), they can be assigned in the web UI to groups, queues, and users. | |
223 | The rights show up on the rights pages in a Status tab alongside the | |
224 | standard RT rights tabs. | |
225 | ||
226 | =for html <img alt="Lifecycle group rights" src="../images/global-lifecycle-group-rights.png"> | |
227 | ||
228 | =for :text [Lifecycle group rights F<docs/images/global-lifecycle-group-rights.png>] | |
229 | ||
230 | =for :man [Lifecycle group rights F<docs/images/global-lifecycle-group-rights.png>] | |
231 | ||
232 | After a status transition right is granted, users with the right will see | |
233 | the status in the drop-down, and possibly any related actions (see | |
234 | L</Actions>). | |
235 | ||
236 | =head2 Default Status | |
237 | ||
238 | There are interfaces to RT from which it isn't possible to define a status, | |
239 | like sending an email to create a ticket, but tickets | |
240 | require a status. To handle these cases, you can set | |
241 | default status values for RT to use when the user doesn't explicitly set | |
242 | a value. | |
243 | ||
244 | Looking at the defaults section in the standard RT configuration, | |
245 | you can see the events for which you can define a default status. | |
246 | For example, 'on_create' => 'new' automatically gives newly created tickets | |
247 | a C<new> status when the requestor doesn't supply a status. We can do the same | |
248 | for our process. | |
249 | ||
250 | Set( %Lifecycles, orders => { | |
251 | defaults => { | |
252 | on_create => 'pending', | |
253 | }, | |
254 | # ..., | |
255 | }); | |
256 | ||
257 | Only a small number of defaults are needed because in practice there are | |
258 | relatively few cases where a ticket will find itself without a status or | |
259 | in an ambiguous state. | |
260 | ||
261 | =head2 Actions | |
262 | ||
263 | To customize how transitions are presented in RT, lifecycles have an | |
264 | C<actions> section where you can customize how an action (e.g. changing | |
265 | status from new -> open) looks and functions. You can customize the action's | |
266 | label, which is how it appears to users, and the type of update, either comment | |
267 | or reply. As an example, in the default RT configuration the action | |
268 | "new -> open" has the default label "Open it" and an update value of C<Respond>. | |
269 | ||
270 | Using the lifecycles configuration, you can change the label to anything you | |
271 | like. You can set the update option to C<Comment> or C<Respond>, which tells RT | |
272 | to process the action as a comment (not sent to requestors) or a reply (sent | |
273 | to requestors). | |
274 | ||
275 | This part of the lifecycles configuration replaces the previous | |
276 | C<$ResolveDefaultUpdateType> configuration value. To mimic that option, set | |
277 | the update type to C<Comment> for all transitions to C<resolved>. | |
278 | ||
279 | Here is an example of a change we might make for our order process: | |
280 | ||
281 | Set( %Lifecycles, orders => { | |
282 | # ..., | |
283 | actions => [ | |
284 | 'pending -> processing' => { | |
285 | label => 'Open For Processing', | |
286 | update => 'Comment', | |
287 | }, | |
288 | 'pending -> declined' => { | |
289 | label => 'Decline', | |
290 | update => 'Respond', | |
291 | }, | |
292 | # ... | |
293 | ], | |
294 | # ... | |
295 | }); | |
296 | ||
297 | Alternatively, supplying no update type results in a "quick" | |
298 | action that changes the status immediately without going through the | |
299 | ticket update page. RT's default "Delete" action is a "quick" action, | |
300 | for example: | |
301 | ||
302 | # from the RT "default" lifecycle | |
303 | 'new -> deleted' => { | |
304 | label => 'Delete', | |
305 | }, | |
306 | ||
307 | If the transition has an associated right, it must be granted for a user to | |
308 | see the action. For example, if we give a group the DeclineOrder right as | |
309 | shown in the earlier example, members of that group will see a Decline option | |
310 | in their Actions menu if a ticket has a pending status. The | |
311 | L</"Full Configuration"> at the end shows other action entries that | |
312 | make the Decline option available in more cases. | |
313 | ||
314 | =for html <img alt="Action menu decline" src="../images/action-decline.png"> | |
315 | ||
316 | =for :text [Action menu decline F<docs/images/action-decline.png>] | |
317 | ||
318 | =for :man [Action menu decline F<docs/images/action-decline.png>] | |
319 | ||
320 | =head2 Mapping Between Queues | |
321 | ||
322 | As we've demonstrated, each queue can have its own custom lifecycle, but | |
323 | in RT you sometimes want to move a ticket from one queue to another. | |
324 | A ticket will have a status in a given queue, but that status may not | |
325 | exist in another queue you want to move the ticket to, or it may exist | |
326 | but mean something different. To allow tickets to move between queues with | |
327 | different lifecycles, RT needs to know how to set the status appropriately. | |
328 | ||
329 | The lifecycle configuration has a C<__maps__> entry to allow you to | |
330 | specify the mappings you want between different queues. Sometimes statuses | |
331 | between queues don't or can't match perfectly, but if you need to move | |
332 | tickets between those queues, it's important that you provide a complete | |
333 | mapping, defining the most sensible mapping you can. | |
334 | ||
335 | If you don't provide a mapping, users will see an error when they try to | |
336 | move a ticket between queues with different lifecycles but no mapping. | |
337 | ||
320f0092 MKG |
338 | Set( %Lifecycles, |
339 | orders => { | |
340 | # ... | |
341 | }, | |
45030404 MKG |
342 | __maps__ => { |
343 | 'default -> orders' => { | |
344 | 'new' => 'pending', | |
345 | 'open' => 'processing', | |
346 | # ..., | |
347 | }, | |
348 | 'orders -> default' => { | |
349 | 'pending' => 'new', | |
350 | 'processing' => 'open', | |
351 | # ..., | |
352 | }, | |
353 | # ..., | |
354 | }, | |
355 | # ..., | |
356 | }); | |
357 | ||
358 | In the example above, we first define mappings between the default queue and | |
359 | our new orders queue. The second block defines the reverse for tickets that | |
360 | might be moved from the orders queue to a queue that uses the default lifecycle. | |
361 | ||
362 | =head2 Full Configuration | |
363 | ||
364 | Here is the full configuration if you want to add it to your RT instance | |
365 | to experiment. | |
366 | ||
367 | Set(%Lifecycles, | |
368 | ||
369 | # 'orders' shows up as a lifecycle choice when you create a new | |
370 | # queue or modify an existing one | |
371 | orders => { | |
372 | # All the appropriate order statuses | |
373 | initial => [ 'pending' ], | |
374 | active => [ 'processing', 'delivery' ], | |
320f0092 | 375 | inactive => [ 'delivered', 'returned', 'declined', 'deleted' ], |
45030404 MKG |
376 | |
377 | # Default order statuses for certain actions | |
378 | defaults => { | |
379 | on_create => 'pending', | |
380 | }, | |
381 | ||
382 | # Status change restrictions | |
383 | transitions => { | |
384 | '' => [qw(pending processing declined)], | |
385 | pending => [qw(processing declined deleted)], | |
386 | processing => [qw(pending declined delivery delivered deleted)], | |
387 | delivery => [qw(pending delivered returned deleted)], | |
388 | delivered => [qw(pending returned deleted)], | |
389 | returned => [qw(pending delivery deleted)], | |
390 | deleted => [qw(pending processing delivered delivery returned)], | |
391 | }, | |
392 | ||
393 | # Rights for different actions | |
394 | rights => { | |
395 | ||
396 | # These rights are in the default lifecycle | |
397 | '* -> deleted' => 'DeleteTicket', | |
398 | '* -> *' => 'ModifyTicket', | |
399 | ||
400 | # Maybe we want to create rights to keep QA rigid | |
401 | '* -> declined' => 'DeclineOrder', | |
402 | '* -> delivery' => 'ApproveOrder', | |
403 | }, | |
404 | ||
405 | # Actions for the web UI | |
406 | actions => [ | |
407 | 'pending -> processing' => { | |
408 | label => 'Open For Processing', | |
409 | update => 'Comment', | |
410 | }, | |
45030404 MKG |
411 | 'pending -> declined' => { |
412 | label => 'Decline', | |
413 | update => 'Respond', | |
414 | }, | |
415 | 'pending -> deleted' => { | |
416 | label => 'Delete', | |
417 | }, | |
418 | 'processing -> declined' => { | |
419 | label => 'Decline', | |
420 | update => 'Respond', | |
421 | }, | |
422 | 'processing -> delivery' => { | |
423 | label => 'Out for delivery', | |
424 | update => 'Comment', | |
425 | }, | |
426 | 'delivery -> delivered' => { | |
427 | label => 'Mark as delivered', | |
428 | update => 'Comment', | |
429 | }, | |
430 | 'delivery -> returned' => { | |
431 | label => 'Returned to Manufacturer', | |
432 | update => 'Respond', | |
433 | }, | |
434 | 'delivered -> returned' => { | |
435 | label => 'Returned to Manufacturer', | |
436 | update => 'Respond', | |
437 | }, | |
438 | 'returned -> delivery' => { | |
439 | label => 'Re-deliver Order', | |
440 | update => 'Respond', | |
441 | }, | |
442 | 'deleted -> pending' => { | |
443 | label => 'Undelete', | |
444 | update => 'Respond', | |
445 | }, | |
446 | ], | |
447 | }, | |
448 | ||
449 | # Status mapping different different lifecycles | |
450 | __maps__ => { | |
451 | 'default -> orders' => { | |
452 | 'new' => 'pending', | |
453 | 'open' => 'processing', | |
454 | 'stalled' => 'processing', | |
455 | 'resolved' => 'delivered', | |
456 | 'rejected' => 'declined', | |
457 | 'deleted' => 'deleted', | |
458 | }, | |
459 | 'orders -> default' => { | |
460 | 'pending' => 'new', | |
461 | 'processing' => 'open', | |
462 | 'delivered' => 'resolved', | |
463 | 'returned' => 'open', # closest matching we have in 'default' | |
464 | 'declined' => 'rejected', | |
465 | 'deleted' => 'deleted', | |
466 | }, | |
467 | }, | |
468 | ); | |
469 | ||
470 | Here is an example history of a ticket following this lifecycle: | |
471 | ||
472 | =for html <img alt="Lifecycle history" src="../images/order-history-example.png"> | |
473 | ||
474 | =for :text [Lifecycle history F<docs/images/order-history-example.png>] | |
475 | ||
476 | =for :man [Lifecycle history F<docs/images/order-history-example.png>] |