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