diff options
Diffstat (limited to 'rt/docs')
-rw-r--r-- | rt/docs/backups.pod | 108 | ||||
-rw-r--r-- | rt/docs/customizing/approvals.pod | 191 | ||||
-rw-r--r-- | rt/docs/customizing/lifecycles.pod | 478 | ||||
-rw-r--r-- | rt/docs/customizing/search_result_columns.pod | 180 | ||||
-rw-r--r-- | rt/docs/customizing/styling_rt.pod | 169 | ||||
-rw-r--r-- | rt/docs/initialdata.pod | 486 |
6 files changed, 1612 insertions, 0 deletions
diff --git a/rt/docs/backups.pod b/rt/docs/backups.pod new file mode 100644 index 000000000..648105c66 --- /dev/null +++ b/rt/docs/backups.pod @@ -0,0 +1,108 @@ +=head1 BACKUPS + +RT is often a critical piece of businesses and organizations. Backups are +absolutely necessary to ensure you can recover quickly from an incident. + +Make sure you take backups. Make sure they I<work>. + +There are many issues that can cause broken backups, such as a +C<max_allowed_packet> too low for MySQL (in either the client or server), or +encoding issues, or running out of disk space. + +Make sure your backup cronjobs notify someone if they fail instead of failing +silently until you need them. + +Test your backups regularly to discover any unknown problems B<before> they +become an issue. You don't want to discover problems with your backups while +tensely restoring from them in a critical data loss situation. + +=head2 DATABASE + +You should backup the entire RT database, although for improved speed and space +you can ignore the I<data> in the C<sessions> table. Make sure you still get +the C<sessions> schema, however. + +Database specific notes and example backup commands for each database are +below. Adjust the commands as necessary for connection details such as +database name (C<rt4> is the placeholder below), user, password, host, etc. +You should put the example commands into a shell script for backup and setup a +cronjob. Make sure output from cron goes to someone who reads mail! (Or into +RT. :) + +=head3 MySQL + + ( mysqldump rt4 --tables sessions --no-data; \ + mysqldump rt4 --ignore-table rt4.sessions --single-transaction ) \ + | gzip > rt-`date +%Y%M%d`.sql.gz + +If you're using a MySQL version older than 4.1.2 (only supported on RT 3.8.x +and older), you should be also pass the C<--default-character-set=binary> +option to the second C<mysqldump> command. + +The dump will be much faster if you can connect to the MySQL server over +localhost. This will use a local socket instead of the network. + +If you find your backups taking far far too long to complete (this point should +take quite a long time to get to on an RT database), there are some alternate +solutions. Percona maintains a highly regarded hot-backup tool for MySQL +called L<XtraBackup|http://www.percona.com/software/percona-xtrabackup/>. If +you have more resources, you can also setup replication to a slave using binary +logs and backup from there as necessary. This not only duplicates the data, +but lets you take backups without putting load on your production server. + +=head3 PostgreSQL + + ( pg_dump rt4 --table=sessions --schema-only; \ + pg_dump rt4 --exclude-table=sessions ) \ + | gzip > rt-`date +%Y%M%d`.sql.gz + +=head2 FILESYSTEM + +You will want to back up, at the very least, the following directories and files: + +=over 4 + +=item /opt/rt4 + +RT's source code, configuration, GPG data, and plugins. Your install location +may be different, of course. + +You can omit F<var/mason_data> and F<var/session_data> if you'd like since +those are temporary caches. Don't omit all of F<var/> however as it may +contain important GPG data. + +=item Webserver configuration + +Often F</etc/httpd> or F</etc/apache2>. This will depend on your OS, web +server, and internal configuration standards. + +=item /etc/aliases + +Your incoming mail aliases mapping addresses to queues. + +=item Mail server configuration + +If you're running an MTA like Postfix, Exim, SendMail, or qmail, you'll want to +backup their configuration files to minimize restore time. "Lightweight" mail +handling programs like fetchmail, msmtp, and ssmtp will also have configuration +files, although usually not as many nor as complex. You'll still want to back +them up. + +The location of these files is highly dependent on what software you're using. + +=item Crontab containing RT's cronjobs + +This may be F</etc/crontab>, F</etc/cron.d/rt>, a user-specific crontab file +(C<crontab -l $USER>), or some other file altogether. Even if you only have +the default cronjobs in place, it's one less piece to forget during a restore. +If you have custom L<< C<rt-crontool> >> invocations, you don't want to have to +recreate those. + +=back + +Simply saving a tarball should be sufficient, with something like: + + tar czvpf rt-backup-`date +%Y%M%d`.tar.gz /opt/rt4 /etc/aliases /etc/httpd ... + +Be sure to include all the directories and files you enumerated above! + diff --git a/rt/docs/customizing/approvals.pod b/rt/docs/customizing/approvals.pod new file mode 100644 index 000000000..af5aa3b0a --- /dev/null +++ b/rt/docs/customizing/approvals.pod @@ -0,0 +1,191 @@ +=head1 RT Approvals + +Some types of change requests processed through RT can +require an approval before being fulfilled. You can configure +RT to set up such an approval workflow for tickets in +queues you select. + +This document walks through the steps to set up a +"Change requests" queue with approvals. You should try +this in a test instance first. If you don't have a test RT +instance, you should read through the entire document first, +change the details as needed for your approval scenario, and then +set up approvals. + +=head2 Overview + +The approvals solution in RT involves using a special queue, +called ___Approvals, to hold approval requests. Scrips and +templates automatically create the necessary tickets +and process the approval or rejection. + +=head2 Change Management Queue + +Since this example will use a change management queue as the +queue where tickets need approval, first we'll set up the queue. + +Login into UI as the 'root' user. Go to Tools -> Configuration -> +Queues and create a new 'Change requests' queue. + +When you set up this queue, do not select the "approvals" Lifecycle. +That selection is for the ___Approvals queue itself, not for queues that +need tickets approved. + +=head3 Change Management Template + +Once the Change Management queue is created, select Templates +-> Create in the queue configuration menu. Enter the Name 'create approval', +leave the default Type as Perl and in the content area enter the following: + + ===Create-Ticket: Manager approval + Subject: Manager Approval for {$Tickets{TOP}->Id} - {$Tickets{TOP}->Subject} + Depended-On-By: TOP + Queue: ___Approvals + Owner: root + Requestors: {$Tickets{TOP}->RequestorAddresses} + Type: approval + Content-Type: text/plain + Due: {time + 3*24*60*60} + Content: Please approve me. + + Thanks. + ENDOFCONTENT + +All of the text should be against the left side of the textarea +with no spaces. + +Click create. + +You'll now use this template when you create the scrip. + +=head3 Change Management Scrip + +Now you need a scrip. On the queue configuration page, select +Scrips -> Create. For the Description, enter 'Create an approval +on ticket create', select the 'On Create' condition, 'Create Tickets' +action, and select the template you just created. Click create. + +=head3 Testing + +You can already test your first workflow with approvals. Create +a ticket in your new 'Change requests' queue. You're logged in as +'root' and the owner of the approval is root (based on the template), +so it's your job to approve or deny the request. Select Tools -> Approvals +in the RT main menu. You should see your first approval request. + +Select the 'Deny' radio button, write 'too expensive' in the notes area +and click Go! You just rejected the approval request. If you open the ticket +you created for testing then you will see that it's rejected +as well and has the correspondence: + + Greetings, + + Your ticket has been rejected by root. + + Approver's notes: too expensive + +You may need to search for the ticket since the rejected state means +it's no longer 'active'. + +Where did this message come from? From templates in the ___Approvals +queue. + +=head2 ___Approvals queue + +___Approvals is a special queue where all approvals are created. The queue +is disabled and is not shown in until you search for it. +Go to Tools -> Configuration -> Queues, leave "Name is" in the search +area and enter ___Approvals into the search +field. Check 'Include disabled queues in listing.' and click Go! +You should now see the ___Approvals queue configuration page. + +You may want to change the name of the ___Approvals queue, but parts of RT +expect it not to change. The name normally isn't shown to users, however, so +it will be largely invisible. + +=head2 Approvals' templates + +From the ___Approvals queue configuration page, click 'Templates' in the +page menu. You should see templates that are used after actions +on approvals. For example if you click on the 'Approval Rejected' +template in the list, you will see the template that generates +the correspondence mentioned above. + +=over 4 + +=item * New Pending Approval + +Owners of new approval requests get this message. + +=item * Approval Passed + +Recorded as correspondence on the ticket when it's approved by an +approver, but still requires more people to approve. + +=item * All Approvals Passed + +Recorded when no more approvals are required. + +=item * Approval Rejected + +Recorded when the approval request is rejected (denied). + +=item * Approval Ready for Owner + +Sent to the Owner of the ticket when it's approved and no more approvals +are required. + +=back + +You can customize these templates to meet your needs. However, +note that there is just one ___Approvals queue for the system, +so make sure changes work with all queues that use approvals. + +=head2 Approvers + +Navigate back to the template used to create approvals. It has +the following line: + + Owner: root + +With this code you set the owner of the approval request to root. +Approvals, as well as tickets, have Ccs, AdminCcs and Requestors. For +example the following line copies requestors from the Tickets +to the approval request: + + Requestors: {$Tickets{TOP}->RequestorAddresses} + +Let's create a group 'Change Approvers' and let any user of +this group approve 'Change Requests'. Create the group, and add root +as a member. Open the 'create an approval' template, and replace +the 'Owner:...' line with the following: + + AdminCcGroup: Change Approvers + +Note that this line only works in RT 4.0.5 and newer. + +Create another test ticket, and you as root still should be able to see +the newly created approval, but now because of the group membership. +You can accept or deny it. + +Any member of the group can accept/deny without consulting +the other members, which is useful with more complex +multistep workflows. + +=head2 Approvers' Rights + +Since the ___Approvals queue is a regular RT queue, you need +to grant rights to allow your approvers to operate on approval +requests. As root, you have super user rights and haven't needed +specific rights for this example. + +It's wise to grant rights via roles as there +is only one queue for all approvals in the system. + +To grant rights to your Change Approvers group, go to the queue +configuration page for the ___Approvals queue. Click on Group Rights +in the page menu. Grant ShowTicket and ModifyTicket rights to the +Owner and AdminCc roles. This should be enough for most cases. + +Now members of the 'Change Approvers' group can act on approvals +even if they have no SuperUser rights. diff --git a/rt/docs/customizing/lifecycles.pod b/rt/docs/customizing/lifecycles.pod new file mode 100644 index 000000000..76e60003a --- /dev/null +++ b/rt/docs/customizing/lifecycles.pod @@ -0,0 +1,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>] diff --git a/rt/docs/customizing/search_result_columns.pod b/rt/docs/customizing/search_result_columns.pod new file mode 100644 index 000000000..7eef416a7 --- /dev/null +++ b/rt/docs/customizing/search_result_columns.pod @@ -0,0 +1,180 @@ +=head1 RT Search Results + +Ticket search results in RT are presented as a table with multiple heading +rows, one for each element of ticket metadata you have selected. Each +row in the table represents one ticket and the appropriate metadata is +displayed in each column. You can see similar listings when you search +for other objects in RT like users, queues, templates, etc. + +For tickets, the Query Builder allows you to modify the column layout using +the Sorting and Display Columns sections at the bottom of the page. With +them you can add and remove data elements to sort by, change the sort order, +and add and remove which columns you want to see. + +Although the Add Columns box has an extensive list of available columns, there +are times when you need a value not listed. Sometimes what you want is a +value calculated based on existing ticket values, like finding the difference +between two date fields. RT provides a way to add this sort of customization +using something called a Column Map. + +=head2 Level of Difficulty + +The customizations described in this section require administrative access +to the RT server and the RT filesystem, typically root or sudo level access. +The customizations involve adding new code to RT, which is written in the +L<Perl|http://www.perl.org/> programming language and uses the +L<Mason|http://www.masonbook.com/> templating system. If you follow the example +closely, you should be able to set up simple column maps with a basic +understanding of these. For more complicated configurations, you may need +to do more research to understand the Perl and Mason syntax. + +=head2 Column Maps + +Each column in a ticket listing gets run through a bit of code called a +Column Map that allows you to perform transformations on the value before +it is displayed. In some cases, the value is just passed through. In others, +like DueRelative, a date is transformed to a relative time like "2 days ago." +You can tap into this functionality to add your own transformations or even +generate completely new values. + +To add to the existing Column Maps, you can use RT's callback +mechanism. This allows you to add code to RT without modifying the core files, +making upgrades much easier. As an example, we'll add a Column Map to the +ticket display and explain the necessary callbacks. You can read more about +callbacks in general in the L<writing_extensions/Callbacks> documentation. + +For our example, let's assume we want to display a response time column that +shows the difference between when a ticket is created and when someone +starts working on it (started date). The two initial values are already +available on the ticket, but it would be convenient to display the +calculated value in our search. + +=head2 Column Map Callback + +First we need to determine where to put our callback. RT's core Column Map code +for tickets is here: + + share/html/Elements/RT__Ticket/ColumnMap + +We'll look there first, both to see some sample Column Maps and also to look +for an appropriate callback to use to add our own. Looking in that file, +we see C<$COLUMN_MAP>, which is a large hashref with entries for each of the +items you see in the Add Columns section of the Query Builder. That's where +we need to add our new Column Map. + +Looking in the C<init> section, we find a callback with a C<CallbackName> +"Once" and it passes the C<$COLUMN_MAP> reference as an argument, so that's +the callback we need. + +Following the callback documentation, we determine we can put our callback +here: + + local/html/Callbacks/MyRT/Elements/RT__Ticket/ColumnMap/Once + +where F<Once> is the name of the file where we'll put our code. + +In the F<Once> file, we'll put the following code: + + <%init> + $COLUMN_MAP->{'TimeToFirstResponse'} = { + title => 'First Response', # loc + attribute => 'First Response', + value => sub { + my $ticket = shift; + return $ticket->StartedObj->DiffAsString($ticket->CreatedObj); + } + }; + </%init> + <%args> + $COLUMN_MAP + </%args> + +Starting with the C<args> section, the value we're interested in is +the C<$COLUMN_MAP> hash reference. Since it's a reference, it's pointing +to the actual data structure constructed in the core RT code. This means +we can add more entries and RT will have access to them. + +=head2 Column Map Parameters + +As you can see in the examples in the core F<ColumnMap> file, each entry +has a key and a hashref with several other parameters. The key needs to be a +unique value. If you using an existing value, you'll overwrite the original +values. + +The parameters in the hashref are as follows: + +=over + +=item title + +The title is what will be used in the header row to identify this value. +The C<# loc> is some special markup that allows RT to replace the value +with translations in other languages, if they are available. + +=item attribute + +This defines the value you can use to reference your new column map +from an RT Format configuration. You can edit formats in the Query +Builder's Advanced section. If you're not familiar with formats, it's +usually safe to set the attribute to the same value as C<title>. It should +be descriptive and unique. + +=item value + +This is where you can put code to transform or calculate the value that +will be displayed. This sets the value you see in the search results +for this column. + +=back + +=cut + +Each of these can be a value like a simple string or an anonymous +subroutine with code that runs to calculate the value. + +If you write a subroutine, as we do for C<value> in our example, RT will +pass the current object as the first parameter to the sub. Since +we're creating a column map for tickets, as RT processes the ticket for +each row in the search results, the ticket object for that ticket is made +available as the first parameter to our subroutine. + +This allows us to then call methods on the L<RT::Ticket> object to access +and process the value. In our case, we can get the L<RT::Date> objects for +the two dates and use the L<RT::Date/DiffAsString> method to calculate and +return the difference. + +When writing code to calculate values, remember that it will be run for each +row in search results. You should avoid doing things that are too time +intensive in that code, like calling a web service to fetch a value. + +=head2 Adding to Display Columns + +Now that we have our column map created, there is one more callback to add +to make it available for all of our users in the Add Columns section in +the Query Builder. This file builds the list of fields available: + + share/html/Search/Elements/BuildFormatString + +Looking there, we see the default callback (the callback without an +explicit C<CallbackName>) passes the C<@fields> array, so that will work. +Create the file: + + local/html/Callbacks/MyRT/Search/Elements/BuildFormatString/Default + +And put the following code in the F<Default> file: + + <%INIT> + push @{$Fields}, 'TimeToFirstResponse'; + </%INIT> + <%ARGS> + $Fields => undef + </%ARGS> + +This puts the hash key we chose for our column map in the fields list so it +will be available in the list of available fields. + +=head2 Last Steps + +Once you have the code in place, stop the RT web server, clear the Mason +cache, and restart the server. Watch the RT logs for any errors, and +navigate to the Query Build to use your new column map. diff --git a/rt/docs/customizing/styling_rt.pod b/rt/docs/customizing/styling_rt.pod new file mode 100644 index 000000000..c5802a84b --- /dev/null +++ b/rt/docs/customizing/styling_rt.pod @@ -0,0 +1,169 @@ +=head1 Customizing the Look of Your RT + +While the default RT color scheme nicely matches the Best Practical colors, +you may want to personalize your RT instance to make it better fit with +your company colors. + + +=head1 Selecting a Theme + +The fundamental look of RT comes from the selected theme. Different +RT versions have a default, and the RT admin can set the system-wide +theme with the C<$WebDefaultStylesheet> configuration value in the +F<RT_SiteConfig.pm> file. + +RT 4.0 comes with the following themes: + +=over + +=item web2 + +An approximation of the 3.8 style + +=item aileron + +The default layout for RT 4.0 + +=item ballard + +Theme which doesn't rely on JavaScript for menuing + +=back + +If you have granted the ModifySelf right to users on your system, +they can pick a different theme for themselves by going to +Logged in as -> Settings -> Options and selecting a different theme. + + +=head1 RT Theme Editor + +RT has some built-in controls to manage the look of the theme you select. +To use the Theme Editor, log in as a SuperUser (like root), and navigate +to Tools -> Configuration -> Tools -> Theme. + +=for html <img alt="RT theme editor, defaults" src="../images/theme_editor_defaults.png"> + +=for :text [RT theme editor image at F<docs/images/theme_editor_defaults.png>] + +=for :man [RT theme editor image at F<docs/images/theme_editor_defaults.png>] + +=head2 Logo and Colors + +From there you can upload a logo and pick colors for the various page +sections. RT will automatically pick out the six most frequent primary +colors from your logo and offer them as options next to the color wheel. +In less than a minute, you can upload a logo and set a few colors. + +Until you click "Save", color changes are temporary and are only shown +to you. When you find the color scheme you want, click Save to make it +the new theme for the entire RT instance. If you ever want to wipe the +slate clean, you can use one or both of the "Reset to default" buttons. + +=head2 Basic CSS Customization + +The theme editor lets you do a bit more if you know your way around CSS +or have a web designer who does. By writing your own styles in the +Custom CSS box, you can quickly customize the RT look and feel pretty +extensively. The primary RT elements are stubbed out for you in the +edit box. + +After making CSS changes, click Try to see how they look, and click Save +when you're done. + + +=head1 Advanced CSS Customization + +If you're more ambitious and good at CSS, you can go even further and +create your own theme. As with all modifications to RT, it's a bad idea +to just change the CSS for one of the standard RT themes in place. When +you upgrade, if you protect your modifications from being over-written, +you may miss out on updates that are required for new features. In the +worst case, an upgrade might wipe out all of your changes. + +Below are a few approaches to customizing RT's CSS. + +=head2 Additional files + +RT allows you to conveniently include additional CSS files after the +default CSS styles, via the C<@CSSFiles> configuration option. To add +an extra CSS file, for example F<my-site.css>, create the local overlay +directory: + + $ mkdir -p local/html/NoAuth/css/ + +And place your F<my-site.css> file in it. Finally, adjust your +C<@CSSFiles> in your F<RT_SiteConfig.pm>: + + Set( @CSSFiles, ('my-site.css') ); + +This technique is preferred to callbacks (below) because CSS included +via this way will be minified. It is also included across all styles, +unlike the callback technique. + +If you are writing an extension, see L<RT/AddStyleSheets> for how to +simply and programmatically add values to C<@CSSFiles>. + +=head2 Callbacks + +RT's CSS files are also Mason templates and the main CSS file, +conveniently called C<main.css>, has a C<Begin> and C<End> callback +allowing you to inject custom CSS. + +To create an End callback, create the callback directory and an +End file in that directory: + + $ mkdir -p local/html/Callbacks/MyRT/NoAuth/css/aileron/main.css + $ touch local/html/Callbacks/MyRT/NoAuth/css/aileron/main.css/End + +You can use any name you want for the C<MyRT> directory and the theme +directory should correspond with the theme you want to change. + +RT will now evaluate the contents of that file after it processes all +of the C<@import> statements in C<main.css>. + + +=head1 Designing Your Own Theme + +The above approaches work well if you need to change the look of +part of RT, but you may want to design your own RT theme +and leave the standard RT themes available to users unmodified. In +this case, you'll want to create your own CSS directory. + +As shown above, the C<local> directory is the place to put +local modifications to RT. Run the following commands in your +C</opt/rt4> directory (or wherever your RT is installed) to get +started: + + $ mkdir -p local/html/NoAuth/css/localstyle + $ cp -R share/html/NoAuth/css/aileron/* local/html/NoAuth/css/localstyle/ + +You can call your "localstyle" directory whatever you want and you don't +have to copy the aileron theme to start from, but it's a good place to +start off for RT4. + +Now set C<$WebDefaultStylesheet> in RT_SiteConfig.pm to the new directory +name you selected, for example: + + Set( $WebDefaultStylesheet, 'localstyle' ); + +If you restart your RT it should look just the same (assuming you copied +your current default theme), but if you go to your Options page you'll +see that the system default theme is now your new "localtheme." + +If you look at the CSS being loaded, you'll also see that the main css +file is now being loaded from your local directory. But you'll also see +that files are still being loaded from the main RT css directories as +well. Why? + +The place to start understanding the loading order of RT's CSS is the +C<main.css> file. You'll see it first loads C<..base/main.css> which +are the base styles for RT along with styles for other tools RT uses +like jQuery. After loading all of the base styles, C<main.css> then +imports a theme-specific version with overrides and new style elements +for the selected theme. So as long as you follow the CSS precedence rules +and use the correct specificity, you get the last chance to modify things. + +You can start modifying things by editing the CSS files in your new +localstyle directory. When you upgrade RT, you'll want to look specifically +at any changes to the style you started from to see if there are any new +styles you want to merge into your new style. diff --git a/rt/docs/initialdata.pod b/rt/docs/initialdata.pod new file mode 100644 index 000000000..6445fb0cd --- /dev/null +++ b/rt/docs/initialdata.pod @@ -0,0 +1,486 @@ +=head1 Summary of initialdata files + +It's often useful to be able to test configuration/database changes and then +apply the same changes in production without manually clicking around. It's +also helpful if you're developing customizations or extensions to be able to +get a fresh database back to the state you want for testing/development. + +This documentation applies to careful and thorough sysadmins as well as +extension authors who need to make database changes easily and repeatably for +new installs or upgrades. + +=head1 Examples + +RT ships with many initialdata files, only one of which is used to +configure a fresh install; the rest are used for upgrades, but function +the same despite being named differently. + + etc/initialdata + etc/upgrade/*/content + +The upgrade "content" files are meant to be incremental changes applied on top +of one another while the top level initialdata file is for fresh RT installs. + +Extensions may also ship with database changes in such files. You may find +some in your install with: + + find local/plugins -name initialdata -or -name content + +=head1 What can be in an initialdata file? + +initialdata files are Perl, but often consist primarily of a bunch of data +structures defining the new records you want and not much extra code. There's +nothing stopping you from writing a bunch of code, however! + +The basic template of a new initialdata file should look something like this: + + use strict; + use warnings; + + our @Queues = ( + # some definitions here + ); + + our @Groups = ( + # some other definitions here + ); + + 1; + +The C<@Queues> and C<@Groups> arrays are expected by RT and should contain +hashref definitions. There are many other arrays RT will look for and act on, +described below. None are required, all may be used. Keep in mind that since +they're just normal Perl arrays, you can C<push> onto them from a loop or +C<grep> out definitions based on conditionals or generate their content with +C<map>, etc. + +The complete list of possible arrays which can be used, along with +descriptions of the values to place in them, is below. + +=head2 C<@Users> + + push @Users, { + Name => 'john.doe', + Password => 'changethis', + Language => 'fr', + Timezone => 'America/Vancouver', + Privileged => 1, + Disabled => 0, + }; + +Each hashref in C<@Users> is treated as a new user to create and passed +straight into C<< RT::User->Create >>. All of the normal user fields are +available, as well as C<Privileged> and C<Disabled> (both booleans) which will +do the appropriate internal group/flag handling. + +For a full list of fields, read the documentation for L<RT::User/Create>. + +=head2 C<@Groups> + + push @Groups, { + Domain => 'UserDefined', + Name => 'Example Employees', + Description => 'All of the employees of my company', + }; + +Creates a new L<RT::Group> for each hashref. In almost all cases you'll want +to follow the example above to create a group just as if you had done it from +the admin interface. B<Do not> omit the C<< Domain => 'UserDefined' >> line. + +Additionally, the C<MemberOf> field is specially handled to make it easier to +add the new group to other groups. C<MemberOf> may be a single value or an +array ref. Each value should be a user-defined group name or hashref to pass +into L<< RT::Group->LoadByCols >>. Each group found will have the new group +added as a member. + +Unfortunately you can't specify the I<members> of a group at this time. As a +workaround, you can push a subref into C<@Final> which adds members to your new +groups. An example, using a convenience function to avoid repeating yourself: + + push @Final, sub { + add_members('My New Group Name' => qw(trs alex ruslan)); + add_members('My Second Group' => qw(jesse kevin sunnavy jim)); + }; + + sub add_members { + my $group_name = shift; + my @members = @_; + + my $group = RT::Group->new( RT->SystemUser ); + $group->LoadUserDefinedGroup($group_name); + + if ($group->id) { + for my $name (@members) { + my $member = RT::User->new( RT->SystemUser ); + $member->LoadByCols( Name => $name ); + + unless ($member->Id) { + RT->Logger->error("Unable to find user '$name'"); + next; + } + + my ($ok, $msg) = $group->AddMember( $member->PrincipalObj->Id ); + if ($ok) { + RT->Logger->info("Added member $name to $group_name"); + } else { + RT->Logger->error("Unable to AddMember $name to $group_name: $msg"); + } + } + } else { + RT->Logger->error("Unable to find group '$group_name'!"); + } + } + +=head2 C<@Queues> + + push @Queues, { + Name => 'Helpdesk', + CorrespondAddress => 'help@example.com', + CommentAddress => 'help-comment@example.com', + }; + +Creates a new L<RT::Queue> for each hashref. Refer to the documentation of +L<RT::Queue/Create> for the fields you can use. + +=head2 C<@CustomFields> + + push @CustomFields, { + Queue => 0, + Name => 'Favorite color', + Type => 'FreeformSingle', + LookupType => 'RT::Queue-RT::Ticket', + }; + +Creates a new L<RT::CustomField> for each hashref. It is the most complex of +the initialdata structures. The most commonly used fields are: + +=over 4 + +=item C<Name> + +The name of this CF as displayed in RT. + +=item C<Description> + +A short summary of what this CF is for. + +=item C<Queue> + +May be a Name or ID. The single queue or array ref of queues to apply this CF +to. This does not apply when C<LookupType> does not start with C<RT::Queue>. + +=item C<Type> + +One of the following on the left hand side: + + SelectSingle # Select one value + SelectMultiple # Select multiple values + + FreeformSingle # Enter one value + FreeformMultiple # Enter multiple values + + Text # Fill in one text area + Wikitext # Fill in one wikitext area + + BinarySingle # Upload one file + BinaryMultiple # Upload multiple files + + ImageSingle # Upload one image + ImageMultiple # Upload multiple images + + Combobox # Combobox: Select or enter one value + + AutocompleteSingle # Enter one value with autocompletion + AutocompleteMultiple # Enter multiple values with autocompletion + + Date # Select date + DateTime # Select datetime + + IPAddressSingle # Enter one IP address + IPAddressMultiple # Enter multiple IP addresses + + IPAddressRangeSingle # Enter one IP address range + IPAddressRangeMultiple # Enter multiple IP address ranges + +If you don't specify "Single" or "Multiple" in the type, you must specify +C<MaxValues>. + +=item C<LookupType> + +Labeled in the CF admin page as "Applies to". This determines whether your CF +is for Tickets, Transactions, Users, Groups, or Queues. Possible values: + + RT::Queue-RT::Ticket # Tickets + RT::Queue-RT::Ticket-RT::Transaction # Transactions + RT::User # Users + RT::Group # Groups + RT::Queue # Queues + +Ticket CFs are the most common, meaning C<RT::Queue-RT::Ticket> is the most +common C<LookupType>. + +=item C<RenderType> + +Only valid when C<Type> is "Select". Controls how the CF is displayed when +editing it. Valid values are: C<Select box>, C<List>, and C<Dropdown>. + +C<List> is either a list of radio buttons or a list of checkboxes depending on +C<MaxValues>. + +=item C<MaxValues> + +Determines whether this CF is a Single or Multiple type. 0 means multiple. 1 +means single. + +Make sure to set the C<MaxValues> field appropriately, otherwise you can end up +with unsupported CF types like a "Select multiple dates" (it doesn't Just +Work). + +You can also use old-style C<Type>s which end with "Single" or "Multiple", for +example: SelectSingle, SelectMultiple, FreeformSingle, etc. + +=item C<Values> + +C<Values> should be an array ref (never a single value!) of hashrefs +representing new L<RT::CustomFieldValue> objects to create for the new custom +field. This only makes sense for "Select" CFs. An example: + + my $i = 1; + push @CustomFields, { + Queue => 0, # Globally applied + LookupType => 'RT::Queue-RT::Ticket', # for Tickets + Name => 'Type of food', + Type => 'SelectSingle', # SelectSingle is the same as: Type => 'Select', MaxValues => 1 + RenderType => 'Dropdown', + Values => [ + { Name => 'Fruit', Description => 'Berries, peaches, tomatos, etc', SortOrder => $i++ }, + { Name => 'Vegetable', Description => 'Asparagus, peas, lettuce, etc', SortOrder => $i++ }, + # more values as such... + ], + }; + +In order to ensure the same sorting of C<Values>, set C<SortOrder> inside each +value. A clever way to do this easily is with a simple variable you increment +each time (as above with C<$i>). You can use the same variable throughout the +whole file, and don't need one per CF. + +=item C<BasedOn> + +Name or ID of another Select Custom Field. This makes the named CF the source +of categories for your values. + +=item C<Pattern> + +The regular expression text (not C<qr//>!) used to validate values. + +=back + +Refer to the documentation and implementation of L<RT::CustomField/Create> and +L<RT::CustomFieldValue/Create> for the full list of available fields and +allowed values. + +=head2 C<@ACL> + +C<@ACL> is very useful for granting rights on your newly created records or +setting up a standard system configuration. It is one of the most complex +initialdata structures. + +=head3 Pick a Right + +All ACL definitions expect a key named C<Right> with the internal right name +you want to grant. The internal right names are visible in RT's admin +interface in grey next to the longer descriptions. + +=head3 Pick a level: on a queue, on a CF, or globally + +After picking a C<Right>, you need to specify on what object the right is +granted. This is B<different> than the user/group/role receiving the right. + +=over 4 + +=item Granted on a custom field by name (or ID), potentially a global or queue + + CF => 'Name', + +=item Granted on a queue + + Queue => 'Name', + +=item Granted on a custom field applied to a specific queue + + CF => 'Name', + Queue => 'Name', + +=item Granted globally + +Specifying none of the above will get you a global right. + +=back + +There is currently no way to grant rights on a group or article class level. +Note that you can grant rights B<to> a group; see below. If you need to grants +rights on a group or article class level, you'll need to write an C<@Final> +subref to handle it using the RT Perl API. + +=head3 Pick a Principal: User or Group or Role + +Finally you need to specify to what system group, system/queue role, +user defined group, or user you want to grant the right B<to>. + +=over 4 + +=item An internal user group + + GroupDomain => 'SystemInternal', + GroupType => 'Everyone, Privileged, or Unprivileged' + +=item A system-level role + + GroupDomain => 'RT::System-Role', + GroupType => 'Requestor, Owner, AdminCc, or Cc' + +=item A queue-level role + + GroupDomain => 'RT::Queue-Role', + Queue => 'Name', + GroupType => 'Requestor, Owner, AdminCc, or Cc', + +=item A group you created + + GroupDomain => 'UserDefined', + GroupId => 'Name' + +=item Individual user + + UserId => 'Name or email or ID' + +=back + +=head3 Common cases + +You're probably looking for definitions like these most of the time. + +=over 4 + +=item Grant a global right to a group you created + + { Right => '...', + GroupDomain => 'UserDefined', + GroupId => 'Name' } + +=item Grant a queue-level right to a group you created + + { Queue => 'Name', + Right => '...', + GroupDomain => 'UserDefined', + GroupId => 'Name' } + +=item Grant a CF-level right to a group you created + + { CF => 'Name', + Right => '...', + GroupDomain => 'UserDefined', + GroupId => 'Name' } + +=back + +Since you often want to grant a list of rights on the same object/level to the +same role/group/user, we generally use Perl loops and operators to aid in the +generation of C<@ACL> without repeating ourselves. + + # Give Requestors globally the right to see tickets, reply, and see the + # queue their ticket is in + push @ACL, map { + { + Right => $_, + GroupDomain => 'RT::System-Role', + GroupType => 'Requestor', + } + } qw(ShowTicket ReplyToTicket SeeQueue); + +=head3 Troubleshooting + +The best troubleshooting is often to see how the rights you define in C<@ACL> +show up in the RT admin interface. + +=head2 C<@Scrips> + +Creates a new L<RT::Scrip> for each hashref. Refer to the documentation of +L<RT::Scrip/Create> for the fields you can use. + +Additionally, the C<Queue> field is specially handled to make it easier to +setup the same Scrip on multiple queues: + +=over 4 + +=item Globally + + Queue => 0, + +=item Single queue + + Queue => 'General', # Name or ID + +=item Multiple queues + + Queue => ['General', 'Helpdesk', 13], # Array ref of Name or ID + +=back + +=head2 C<@ScripActions> + +Creates a new L<RT::ScripAction> for each hashref. Refer to the documentation +of L<RT::ScripAction/Create> for the fields you can use. + +=head2 C<@ScripConditions> + +Creates a new L<RT::ScripCondition> for each hashref. Refer to the +documentation of L<RT::ScripCondition/Create> for the fields you can use. + +=head2 C<@Templates> + +Creates a new L<RT::Template> for each hashref. Refer to the documentation of +L<RT::Template/Create> for the fields you can use. + +=head2 C<@Attributes> + +An array of L<RT::Attribute>s to create. You likely don't need to mess with +this. If you do, know that the key C<Object> is expected to be an +L<RT::Record> object on which to call C<AddAttribute>. If you don't provide +C<Object> or it's undefined, C<< RT->System >> will be used. + +=head2 C<@Initial> + +=head2 C<@Final> + +C<@Initial> and C<@Final> are special and let you write your own processing +code that runs before anything else or after everything else. They are +expected to be arrays of subrefs (usually anonymous) like so: + + our @Final = (sub { + RT->Logger->info("Finishing up!"); + }); + +You have the full power of RT's Perl libraries at your disposal. Be sure to do +error checking and log any errors with C<< RT->Logger->error("...") >>! + +=head1 What's missing? + +There is currently no way, short of writing code in C<@Final> or C<@Initial>, +to easily create B<Classes>, B<Topics>, or B<Articles> from initialdata files. + +=head1 Running an initialdata file + + sbin/rt-setup-database --action insert --datafile /path/to/your/initialdata + +This may prompt you for a database password. + +=head1 Implementation details + +All the handling of initialdata files is done in C<< RT::Handle->InsertData >>. +If you want to know B<exactly> what's happening with each array, your best bet +is to start reading the code there. + +RT takes care of the ordering so that your new queues are created before it +processes the new ACLs for those queues. This lets you refer to new queues you +just created by Name. |