RT is built like a layer cake. Each layer has no innate knowledge of what sits on top of it.[11] This chapter takes a tour of RT’s structure and then a more detailed look at RT’s database and object models.
Before we get into the nitty gritty details of how RT is put together, let’s take a quick look at the RT layer cake and see what we get out of each layer. Figure 8-1 shows the layers and how they fit together.
Behind everything that RT does, there’s an SQL database. This database stores all of RT’s data and metadata. Some SQL databases provide their own procedural languages that allow developers to implement large applications entirely inside the database. This has the disadvantage of tying an application tightly to a specific database engine, such as MySQL or Oracle. RT takes a different tack, using the database as a datastore. Because of this, RT is able to run on top of a number of different database engines, each of which has different strengths. RT works to make sure that data stored in SQL is relatively easy to query using other tools, though we strongly recommend that you not alter the database by hand.
Perl’s DBI is the standard SQL Database Interface for Perl programs. It defines an interface that various and sundry database drivers (DBDs) implement in a largely consistent manner. In addition to MySQL, Postgres, and Oracle, you’ll find DBD modules that let you talk to LDAP, flat text files, and even Google as if
they were SQL databases. If you come from the Java world, you can think of DBI as Perl’s equivalent to JDBC.
DBIx::SearchBuilder
DBIx::SearchBuilder
is the secret sauce that lets an object-oriented application like RT talk to a table-oriented relational database. SearchBuilder
presents a simple object-oriented API to Perl programs and translates those API calls into SQL statements tuned for the specific database. A row from any given database table can be accessed via a subclass of DBIx::SearchBuilder::Record
. To search for and work with sets of records all at once, RT provides a Collection
class for each database table, which is a subclass of DBIx::SearchBuilder
itself.
Until now, we’ve only talked about RT as a ticketing system. Once you start looking at the architecture and APIs, you’ll quickly discover that there’s a whole application platform under the hood in addition to the ticketing system.
RT’s application platform libraries are the guts of RT. They provide database connectivity, logging infrastructure, users, groups, access control, links, and a few other bits. While the ticketing system uses the RT application platform, it’s not the only application that does so. RTFM[12] is a knowledge-base tool that uses the RT application platform to implement an entirely different application.
RT’s ticketing system libraries use the RT application platform as a base to provide tickets, transactions, attachments, custom fields, and queues. The ticketing system defines groups and access control rights and a type of link that teaches the application platform how to deal with links between ticket objects.
The Mason handler is one of many possible applications that runs on top of the RT Core libraries. It provides a wrapper around the Mason templating system.[13] Mason is conceptually similar to ASP, PHP, or JSP, but for Perl.
RT comes with two main sets of Mason templates out of the box: user interface templates, which are designed for end users to interact with their browsers, and REST templates, which are designed to be easy for other software to interact with. As of version 3.4, the REST templates are designed to present information in RFC822 format, which anything can parse.
Right now, the Mason templates for the RT application platform and RT ticketing system are very tightly integrated, but moving forward, the templates are changing to improve how they are broken out.
RT has three HTTP clients: a web browser, the email gateway, and the command-line interface. Most users interact with RT using their browsers. RT’s email gateway is a tiny Perl script that your mail server runs every time an email message destined for RT comes in. The email gateway, in turn, dispatches the message to your RT server over HTTP or HTTPS. It waits around for the RT server to confirm receipt of the message and then exits.
RT’s command-line interface is a web browser. But not like you’re imagining. The CLI communicates with RT over the web, using the same authentication system as the rest of RT, so you can use it from anywhere that you can use RT’s web interface. Like any other web client, it uses GET and POST to download from and upload to your RT server.
In the next sections and chapters, we’ll dive into RT’s internals to get a better sense of how RT is put together. As you’re reading, it may be useful to refer to RT’s source code. All the paths we talk about will be relative to your RT installation path. By default, RT is installed into /opt/rt3, but you or your system administrator may have installed it elsewhere. Some flavors of Unix—such as Debian Linux and FreeBSD—package RT and install its code and configuration files into locations that fit system policy.
Standard Perl libraries live in lib/. RT.pm contains RT’s basic initialization routines. It reads RT’s configuration file, sets up connections to the database and logging system, and loads system-internal objects.
RT’s other Perl libraries live inside the RT/ subdirectory of lib. To add RT-specific behavior, both RT/Record.pm and RT/SearchBuilder.pm subclass DBIx::SearchBuilder
and DBIx::SearchBuilder::Record::Cachable
, respectively. RT/Interface/Web.pm handles many of the details of initialization of RT’s Mason handler and provides a number of helper functions for the Mason Interface. RT/Interface/REST.pm provides helper functions for RT’s REST-based web services framework. RT/Interface/Email.pm provides routines for handling incoming email messages and converting them into RT’s preferred formats and encodings.
Standard lexicon files live in lib/RT/I18N/. Lexicon files contain translations of RT into languages other than English. They’re in the extended GNU Gettext format that Locale::Maketext::Lexicon
supports.
Standard Mason components live in share/html/.
Executable RT programs live in bin/. rt is the RT command-line tool. It talks to your RT server via HTTP or HTTPS. You can copy it to other servers or your desktop to run. rt-crontool
lets you script actions like escalations or reminder email using your system’s cron facility. You can use rt-mailgate
to funnel incoming email into RT. Like rt
, it can run on any server (such as your mail server) that has Perl installed. webmux.pl
is the mod_perl handler for RT. mason_handler.fcgi
is the FastCGI handler for RT. standalone_httpd
is a full RT webserver in a single tiny Perl script. It’s useful for testing out RT or for your development environment. Because it’s a non-forking, non-threaded Perl script, it might not stand up to full-scale production use.
RT system programs live in sbin/. These tools are used during initial installation of RT. They should almost never be needed during regular use of RT. rt-test-dependencies
is a tool to help verify that you’ve installed all the Perl modules RT needs to run and to help you install any that you happen to be missing. rt-setup-database
is the RT helper script that sets up a database schema, ACLs, and data for RT or any RT extensions that touch the database.
Mason cache data lives in var/mason_data/. Under some database backends, session data and locks may be stored under var/session_data/. If you are using the SQLite backend, its database is under var/ by default, such as the file var/rt3.
Configuration files live in etc/. RT_Config.pm lists out all of RT’s default configuration parameters. Database configuration parameters are set automatically by RT’s configure script, but just about everything else is the factory default.
When RT starts up, it looks in RT_Config.pm and then overrides the defaults it finds there with local configuration from RT_SiteConfig.pm. This way, you can add any RT configuration you want to RT_SiteConfig.pm, rather than hand-editing the RT_Config.pm file and merging changes every time you upgrade.
The initial database schema also lives in etc/, in multiple different schema.* files customized for each database platform.
Site-local Perl libraries live in local/lib/. Site-local Mason components live in local/html/. Site-local lexicon files live in local/po/.
Before RT 3.0, RT was language-agnostic. The user interface was in English,[14] and RT didn’t do anything special with the text entered by users. As long as you only used the web interface, it worked OK. Users in Japan could enter text in Japanese. Users in Spain could enter text in Spanish. When RT tried to send mail, the recipient wouldn’t be able to read it, since RT didn’t know enough about the content of the message to correctly label it.
RT 3.0 changed all that. The interface has been fully internationalized,[15] and we’ve standardized on Unicode and the 8-bit UTF-8 encoding. This means that you can use English, Spanish, Russian, Japanese, Korean, and Chinese in the same message, and RT will store and display them just fine.
When a user connects to RT’s web interface, RT tells his browser that it should speak to RT in UTF-8. Because the web came into being after the advent of Unicode, most every web browser knows how to speak UTF-8.
Sadly, the situation isn’t so rosy in the world of email. Every language has its own encoding, sometimes even two or three of them. For email, RT has to do all of the hard work. When it receives an email message, it has a look at the message headers to see if the sender told us what encoding they were using. If the sender didn’t tell RT, it uses the Perl module Encode::Guess
to look at the message’s content and guess what the most likely encoding is. Once RT knows what encoding an incoming message is in, it converts the message to UTF-8. When sending messages back out, RT looks at a site’s list of preferred encodings, picks the most apropriate one, and re-encodes the message.
This section takes a tour of RT’s logical model , shown in Figure 8-2. RT maps its logical model closely to its object model , so we’ve condensed the details of the object and logical models throughout the section.
Every RT object has a field called ID. This field is an integer that’s automatically set to a unique value when an object is created and never changes. RT uses these values internally as keys to objects. Additionally, many of RT’s objects have fields called Creator, Created, LastUpdated, and LastUpdatedBy. These fields automatically track the users who created and most recently updated these objects and the times of those actions. For clarity, we’ve skipped over those fields in the schema listings below.
Each of the database tables we discuss here corresponds to two RT objects, a record class and a collection class. The methods for each of these classes are split up into several files; see Overlays in Chapter 10 for more details. Method-level documentation for each class is available in Perl’s perldoc format. Each collection class is named the same as RT’s database table for that sort of object. Each record class is named for the singular version of the collection. So if you’re interested in users, for example, you want to look at both RT::Users
and RT::User
. By default, the primary file for each of these classes lives in /opt/rt3/lib/RT/.
The documentation in both the record and the collection modules gives an overview of the database schema for the class and provides pointers to other files that contain class methods and documentation:
perldoc /opt/rt3/lib/RT/User.pm
and
perldoc /opt/rt3/lib/RT/Users.pm
Each of these files will give you an overview of the database schema for the class, as well as provide pointers to other files that contain class methods and documentation.
In RT, a user is any individual who can perform actions within the system. Any time you create, modify, look at, or delete an object, you need to do it as a user.
Out of the box, RT comes with three special users :
RT uses the RT_System user to perform actions as the system itself. This includes looking up data for internal use—when you really don’t want access control checks to deny the system access to its own data—as well as automatically reopening a closed ticket when a user sends in email about that ticket. Inside RT, you always can access RT_System as $RT::SystemUser
.
RT uses the Nobody user primarily to mark tickets that have no owner. For consistency, it’s important that tickets always be owned by somebody, even if that somebody is a dummy user. Inside RT, you can access Nobody as $RT::Nobody
.
Out of the box, RT comes standard with a single user account called root whose password is password. On Unix systems, root is the superuser. Long, long ago, RT used unix system accounts instead of its own account system. The Unix-y name of this account is a holdover from those days. Once you’ve got an RT system up and running, nothing internal depends on the name root. There isn’t a global object in RT for the root user.
Users have the following fields:
Every user in RT has a Name, which is guaranteed to be unique. While RT doesn’t reference users directly by Name, users authenticate to RT and search for other users by Name.
A user’s EmailAddress is used by RT to attach incoming email messages to a user account upon receipt. It’s also used by RT to figure out where to send outgoing mail when tickets are updated. No two users may have the same email address, although it’s fine to have many users with no email address.
While RT supports external access control mechanisms, many sites don’t have a universal authentication framework, so RT provides its own password-based system. Internally, RT stores an MD5 hash of a user’s password in the Password field but never exposes it directly to end-users. Unlike most other fields, Password is write-only. RT provides SetPassword
and IsPassword
methods but not a Password method. RT treats passwords such as *NO-PASSWORD
and *LOCK*
as locked passwords and never allows password-based access to accounts with such passwords.
RT is often used in a customer-service scenario, where it’s important to be able to keep notes about users. The Comments field is a scratch-pad about a user, but it isn’t visible to that user unless they’re part of the Privileged group.
When composing mail from within the web interface, RT automatically will append a user’s Signature to the message.
In a number of situations, RT will display a user’s RealName, rather than their Name or EmailAddress.
RT’s web interface provides browser-based language negotiation features, but it’s sometimes useful to override that with a user’s preferred language. The Lang field stores an RFC3066-style language tag.
RT provides functionality to allow command-line tools written around the RT framework to map the current Unix username to an RT user’s Gecos field.
RT provides these fields to allow sites to use it as a contact management system. They’re not used internally.
RT doesn’t currently use these fields.
A collection of users and other groups can be assigned rights, made watchers of tickets, and so on. Groups can contain users and groups. Groups can’t contain themselves.
Groups have the following fields:
Every group in RT has a name, purely for human convenience. Internally, RT doesn’t care about group names.
Likewise, the Description is provided so that users know what a particular group is for.
Many parts of RT use the groups system for a wide variety of different purposes. Domain is a high level way to mark what type each group is.
Internally, RT’s access control system only grants rights to groups. When first created, every user has an ACL equivalence group created with only that user as a member. Whenever RT’s API is used to grant rights to an individual user, the right is really granted to that user’s ACL equivalence group.
RT keeps track of certain user attributes with system-wide meta-groups. Upon installation, RT creates three of these SystemInternal metagroups: Everyone, Privileged, and Unprivileged. Every single user is added as a member of the Everyone group and either the Privileged or Unprivileged group. If a user is Unprivileged, they can’t be granted rights directly, and RT’s web frontend automatically shunts them to a restricted self-service frontend upon login.
UserDefined groups are system-wide groups created by local staff. Generally, they’re used as a system managment convenience. Rights are granted to groups, rather than individual users, and groups are made Ccs or AdminCcs of Tickets or Queues. This makes it easier for administrators to track who can perform actions or who will receive email on a specific topic.
Personal groups are defined by individual users for their own personal use. Currently, they’re used only by the Delegation system to allow users to delegate rights to other users or groups.
RT::System-Role
, RT::Queue-Role
and RT::Ticket-Role
Role groups are designed to allow administrators to grant rights to a role and configure mailing rules for a role at either the System or Queue level and have that configuration apply to every user who is operating in that role for a given ticket or queue. As of RT 3.0, the possible roles are Requestor, Cc, AdminCc, and Owner.
For ACLEquivalence
groups, the group’s type is 'UserEquiv
‘.
For SystemInternal
groups, the group’s type is one of 'Everyone
', 'Privileged
', or 'Unprivileged
‘.
For each of the role groups, the type is one of Owner
, Requestor
, Cc
, or AdminCc
.
For Personal
groups, the type is always “” (an empty string).
For ACLEquivalence
and Personal
groups, the group’s instance is the user’s ID.
For SystemInternal
and UserDefined
groups, the group’s instance is always 0.
For RT::Ticket-Role
groups, the group’s instance is the Ticket’s ID.
For RT::Queue-Role
groups, the group’s instance is the Queue’s ID.
For RT::System-Role
groups, the group’s instance is always 1.
In RT, each user or group is a type of Principal. Principals are an abstraction that RT uses internally so that rights can be granted to both users and groups and so that both users and groups can be members of a group.
Principals have the following fields:
A principal’s type is always one of User or Group. It tells RT which sort of object this Principal is. Because a Principal’s ID is always the same as the ID of the associated user or group object, it would be possible (but slow) to deduce this information on the fly.
Sometimes a user or group is no longer needed. If you simply deleted it from the database, you’d lose information about what that user or group did in the past. RT provides the Disabled field, which lets you specify that a Principal and its associated user or group is no longer in play.
A principal’s ObjectId is always the same as its ID. At one point, a User’s ID wasn’t the same as the ID of the Principal object. You should never be looking at or using a Principal’s ObjectId.
The GroupMembers table keeps track of all the users and groups that are members of a group. Generally, developers will interact with the GroupMembers table through the API provided by group objects.
GroupId refers to the group where the member belongs. It points to the ID of a group object.
MemberId is the ID of a Principal that is a member of the group referenced by the GroupId.
RT allows both users and groups to be members of groups. Because SQL databases don’t generally provide a means to ask a database for all rows that refer to this row recursively, we have to build up a more complex mapping table.
Generally, everything you ever want to do with CachedGroupMembers is encapsulated in the interface to group objects. RT’s CachedGroupMembers algorithm is quite complex and isn’t designed to be modified by user code. Details of its schema are provided here for completeness.
CachedGroupMembers have the following fields:
As in the GroupMembers table, MemberId is a reference to the ID of a Principal that is a member of the group referenced by the GroupId.
In cases where a user or group is a member of a (parent) group, Via will point to the ID of the row in the CachedGroupMembers table for the parent group.
When the group in question is the top-level group, Via points to this row’s ID.[16]
ImmediateParentId is the ID of the Group that this row’s MemberId is explicitly a member of. It corresponds directly to the GroupId in the GroupMembers table.
If this cached group member is a member of this group by way of a disabled group or if this group is disabled, this will be set to 1.
This prevents us from finding members of disabled subgroups when listing off group members recursively. Also, it allows the access control system to elide members of disabled groups.
A single row in an access control list is an Access Control Entry or ACE. The Access Control List (ACL) table details which rights each Principal has for any ACLed object in RT. ACLed objects include: tickets, queues, and groups.
ACLs have the following fields:
PrincipalType captures which sort of object a given ACE applies to. For each of the roles—Owner, Requestor, Cc, and AdminCc—the PrincipalType is the name of that role.
For regular groups, the PrincipalType is simply Group.
For an ACE granting rights to a user, the PrincipalType is Group. Behind the scenes, rights are never granted directly to users, but to their ACL equivalence groups.
PrincipalId is a pointer to the principal to which this ACE applies. In the case of regular groups and role groups, it points to the group in question’s Principal. If the ACE grants rights to a specific user, the PrincipalId points to that user’s ACL equivalence group.
The RightName is a simple textual identifier for the right being granted. For any object that supports access control, you can get a complete list of what rights it supports with the AvailableRights()
method.
Each ACE applies to a specific object. ObjectType is the class of that object. For example, if you granted a right on Queue 1, the class would be RT::Queue
.
ObjectId is the ID of the object to which this ACE refers. For example, if you granted a right on Queue 1, the ObjectId would be 1.
RT’s ACL delegation system allows individual users to delegate rights that had been granted to them and automatically removes the delegated rights when the grantor’s rights are revoked. DelegatedFrom is a pointer to the ID of the ACE on which this right is based.
DelegatedBy is the Principal ID of the user who delegated this right.
The Links table keeps track of relationships between entities. The object classes wrapping Links have built-in intelligence to do clever things with RT tickets, but you can think of the Links system as a simple store for RDF-style Subject-Predicate-Object triples (Base-Type-Target in RT’s lexicon).[17]
Links have the following fields:
Base is the URI of the left-hand side of this link relation. It can contain any URI. By default, RT ships with classes that can handle http:, https:, file:, ftp: and fsck.com-rt: URIs.
If this Link’s base is a local ticket, LocalBase is a pointer to the ID of that ticket. This field is a convenience that makes it easier for RT to find links related to a particular ticket.
Type describes what sort of relationship the Base has to the Target. Out of the box, RT supports three simple types of relationships:
The Base refers to the Target. This is a simple weak reference that lets you tie related objects together. For example, “The purchase of new servers refers to the estimates we got from these six vendors.”
The Base in some way depends on the Target. In the ticketing system side of RT, we use DependsOn to track cases when one Ticket must be completed before another one can be completed such as “The purchase of new servers depends on budget approval for the server migration project.”
The Target contains the Base. In the ticketing system side of RT, we use MemberOf to track parent-child relationships such as “The purchase of new servers is a member of the server migration project.”
Target is the URI of the right-hand side of this link relation. It can contain any URI.
If this Link’s target is a local ticket, LocalTarget is a pointer to the ID of that ticket. This field is a convenience that makes it easier for RT to find links related to a particular ticket.
RT’s Attributes table , new in 3.2, allows you to store arbitrary metadata about any RT object. It’s quite useful for extending objects in ways that we haven’t envisioned. Bear in mind, though, that attributes aren’t searchable. You can’t for example, ask RT to find you all the Users who have an attribute called “LikesUnix.”
ObjectType refers to what sort of RT object to which this attribute is attached. If you want to store an attribute for the user whose ID is 4, the attribute’s ObjectType would be RT::User
.
ObjectId is the ID of the object for which this attribute is being stored. If you want to store an attribute for the user whose ID is 4, the attribute’s ObjectId would be 4.
An attribute’s Name is the machine-readable name for this attribute. RT uses attributes to store persistent searches. All saved searches have the name SavedSearch.
An attribute’s Description is the human readable explanation of the purpose of this particular attribute. For saved searches, this would be the user’s hand-entered description for what’s being searched.
Content contains the actual value of this attribute. It can store at least 4,000 characters of text or binary data. Complex data structures are stored in the Perl “storable” format.
Transactions record everything that happens to a ticket, from its creation through its eventual resolution. Each transaction can have one or more Attachments. Unlike most objects in RT, transactions are immutable once created.
Transactions have the following fields:
ObjectType describes the class of record to which this transaction refers. Most often, you’ll see RT::Ticket
here. (In previous versions, RT only tracked transactions on tickets). As of RT 3.4, RT also records changes to RT::User
and RT::Group
objects.
ObjectId is a pointer to the ID of the record to which this transaction refers.
A transaction’s Type describes what sort of update it is. For most simple updates to individual ticket fields, the type is Set. For adding a comment to a ticket, it’s Comment. For updates to Custom Fields, it’s CustomField.
For some types of transactions, like Comment and Correspond, users can specify how long they spent on the update. TimeTaken tracks that value in minutes.
For updates that alter a field or custom field, Field tracks what was changed.
OldValue tracks what a field was changed from. In the case of deletion, OldValue is the value that was deleted.
NewValue tracks what a field was changed to. In the case of a new value being added, NewValue tracks the new value.
Some transactions have a bit more data than can be encapsulated easily in the previous fields. Data stores the subjects of incoming correspondence and occasionally other data.
Some transactions store changes to custom fields for things like images, files, or large text blocks. When RT records one of those updates as a transaction, it sets ReferenceType to RT::ObjectCustomFieldValue
and fills in OldReference and/or NewReference. Because storing two copies of these big objects can bloat the database, RT stores changes to large values like this by reference instead of just copying the values like it does for smaller fields.
NewReference contains a pointer to an added value of the type stored in ReferenceType.
OldReference contains a pointer to a deleted value of the type stored in ReferenceType.
The Attachment table stores any message body or attachment for a transaction in a structured manner, so MIME email messages can be rebuilt.
Attachments are arbitrarily complex MIME entities that can be tied to tickets using transactions. Every email message that RT associates with a ticket will be stored in the database as one or more attachments. If an attachment is made up of multiple MIME parts, RT will split those parts into a tree of related attachments and store each one in the database.
Attachments have the following fields:
Each Attachment is associated with a Transaction. TransactionId is a reference to the ID of the Transaction associated with this attachment.
If this attachment is part of a complex MIME structure, Parent points to another Attachment representing the parent MIME section.
If an attachment is an email message, MessageId stores its Message-Id:
header. In the future, this will make it easier to build message threads.
If an attachment is an email message, Subject stores its Subject:
header.
If an attachment is a named file, Filename stores what it was called when it was submitted to RT.
ContentType stores this attachment’s MIME type.
When possible, RT undoes all of the encoding that attachments are wrapped in for transport by email, so it can store them in their raw forms in the database. Some databases, like PostgreSQL and Oracle don’t support binary data in the same sorts of fields that they use to store character data, like the bodies of email messages. On these databases, RT transparently encodes stored attachments into Base64 or QuotedPrintable encoding when stuffing them into the database and transparently undoes it when pulling them back out.
Data about what sort of encoding is used is stored in the ContentEncoding header.
Content is the actual body of the attachment, stored as UTF-8 text, binary data, or encoded data.
RT stores the attachment’s message headers and MIME headers in the Headers field. RT also stores additional data, including the attachment’s length and original text encoding, if it was converted into UTF-8 by RT.
RT allows sites to track custom metadata per ticket using a facility known as Custom Fields. These custom fields can be applied to tickets globally or per-queue. Custom fields can be of several types: Select from a list, free-form data entry in a text field, free-form data entry in a textarea field (with either plain or wiki text), file upload, and image upload. Additionally, each custom field can accept either a single value or multiple values. For all intents and purposes, users interacting with RT can treat custom fields the same as regular fields like Status and Subject. Users can see, edit, and search on custom field values through the same interfaces that they use to work with RT. At the API level, however, they’re treated somewhat differently.
CustomFields have the following fields:
Each custom field has a name displayed to users editing or viewing it. Names aren’t guaranteed to be unique.
Description is a human-readable summary of a custom field.
A custom field’s Type describes what sort of values it accepts. As of RT 3.4, acceptable values are: Select, Freeform, Text, Image, and Binary. Select fields let the user pick values from a list. Freeform fields let the user enter individual lines of text. Text fields let the user enter large (up to a couple megabytes) blocks of text. Image fields let the user upload images as custom field values. Binary fields let the user attach files as values.
MaxValues tells RT how many values this custom field can have for a given object. Out of the box, RT 3.4 lets users pick 0, which allows unlimited possible values, or 1, which limits users to a single selection.
SortOrder is an integer field used by RT to automatically list custom fields in the order you want to present them. RT lists custom fields in ascending order sorted by SortOrder.
Disabled is a boolean field that specifies whether RT should let users see and modify a custom field and its values. It’s important not to lose historical data about custom fields and their values, so disable custom fields instead of deleting them.
RT uses LookupType to describe what sorts of objects to which a custom field applies. RT::Queue-RT::Ticket
means that the custom field applies to tickets in a given queue. RT::Queue-RT::Ticket-RT::Transaction
means that the custom field applies to transactions on tickets in a given queue. RT::Group
tells RT that this custom field should apply to groups. Similarly, RT::User
makes sure the custom field only shows up on user objects.
RT 3.4 doesn’t do anything with a custom field’s Pattern attribute, but future releases or plugins will add the ability to validate users’ input against a regular expression stored there.
The Repeated attribute is reserved for future use.
The CustomFieldValues table holds the list of acceptable values for Select custom fields.
CustomFieldValues have the following fields:
CustomField is a pointer to the ID of the custom field for which this row holds a valid value.
Name is the actual value of the custom field to be listed.
Description is a longer explanation of the meaning of this value’s Name. It’s displayed in the administrative interface and some user-visible contexts.
SortOrder is an integer field used by RT to automatically list custom field values in the order you want to present them. Values fields are listed in ascending order.
ObjectCustomFields is a simple linking table that RT uses to decide which custom fields apply to a given object.
CustomField is a reference to the ID of a record in the CustomFields table.
Using the custom field’s LookupType, RT can figure out the type of objects to which the custom field can apply. ObjectId tells RT the exact object of that type to which a custom field applies. A value of 0 means that this custom field applies to all objects of that type.
ObjectCustomFieldValues holds all current custom field values for all tickets within RT. Values are stored by value, rather than by reference. This enables users to switch custom field types from Select to Freeform and back again without loss of data.
CustomField is a pointer to the ID of the custom field associated with this value.
ObjectType describes what sort of record to which this transaction refers. Its values are RT::Record
subtypes. In RT, most transactions are on tickets, so the most common value you’ll see is RT::Ticket
, but others such as RT::Group
and RT::User
are also valid.
ObjectId is a pointer to the ID of the record (usually a ticket) to which this value applies. In previous versions of RT, this column was called Ticket.
Content is the actual textual value of the custom field for the ticket. The content is stored as a 255 character text field.
LargeContent stores larger text values for custom fields, as well as image or binary custom field values. As of RT 3.4, it’s used by the Image, Text, and Binary custom field types.
ContentType stores the MIME type of any LargeContent. If there is no LargeContent for this record, it’s blank. When searching custom field content, RT is careful to search only values that have a ContentType that begins with text
.
Because some databases handle binary data differently, ContentEncoding tells RT whether it needs to do anything special to decode and present the LargeContent.
RT is a ticketing sytem. It should come as no suprise that one of the core objects is the Ticket. Every ticket has a number of things associated with it, including role groups for Owner, Requestors, Cc, and AdminCc. Owner is quite special in that it can contain only one user, but the others can contain multiple users and groups. By default, RT is configured to treat each of the groups specially, but internally they are quite similar. Additionally, a ticket can have a number of attached Transactions, CustomFields, and Links.
Tickets have the following fields:
By default, a ticket’s EffectiveId is the same as its ID. RT supports the ability to merge tickets together. When you merge a ticket into another one, RT sets the first ticket’s EffectiveId to the second ticket’s ID. RT uses this data to quickly look up which ticket you’re really talking about when you reference a merged ticket.
Tickets are grouped into queues. Each ticket can have one and only one queue. Queue is a pointer to the ID of the queue for this ticket.
Type is a simple textual identifer that describes what sort of ticket this is. By default, all tickets are of Type ticket
. Generally, there’s no reason to change that.
Owner is a pointer to the ID of the Principal who owns this ticket. This information is also stored in the ticket’s Owner role group, but it is cached in the Ticket record for performance reasons.
Subject is a simple human-readable description of what this ticket is about.
When a ticket is first created, its Priority is copied into its InitialPriority. An external tool can move a ticket’s Priority up (or down) toward its FinalPriority.
RT allows users to track how much time, in minutes, a given ticket is estimated to take. Over time, users can add to the TimeWorked and (hopefully) subtract from the TimeLeft.
Each ticket has a single current Status, which can be one of new
, open
, stalled
, resolved
, rejected
, or deleted
. Deleted tickets are treated specially in that they can’t be displayed in ticket listings.
Told tracks the last time a ticket update was sent to the requestor.
Starts is primarily intended for users’ project management needs. RT doesn’t handle it specially.
Started tracks the last time the ticket’s status was flipped from new
to another status.
Due tracks the ticket’s due date.
The name Resolved is a bit misleading. This field tracks the last time that this ticket’s status was set to resolved
, rejected
, or deleted
.
These fields are not currently used by RT.
The Queue is RT’s basic administrative unit. Every ticket is categorized into a Queue. Access control, business logic, notifications, and custom fields can all be configured at the queue level.
Queues have the following fields:
Each queue has a unique Name. This name is displayed to users and used by tools such as RT’s mail gateway to determine in which queue a new ticket should be created.
Description is a human-readable explanation of a queue’s purpose that is displayed to administrative staff and sometimes included in outgoing email messages about tickets in this queue.
When RT sends out public email correspondence about a ticket, it automatically sets the message’s From:
and Reply-To:
headers to the queue’s CorrespondAddress. It’s up to local staff to make sure that both this and the queue’s CommentAddress are set to values that will be properly routed back into RT.
When RT sends out private email comments about a ticket, it automatically sets the message’s From:
and Reply-To:
headers to the queue’s CommentAddress.
InitialPriority specifies a default priority for a newly created ticket in this queue.
FinalPriority specifies a default ending priority for a newly created ticket in this queue. RT ships with a small tool that you can insert into cron to automatically move tickets toward their FinalPriority as they approach their due dates.
DefaultDueIn, if set, will set the due dates of newly created tickets in this queue to DefaultDueIn days in the future.
Sometimes, you don’t want a queue to appear in listings anymore. Deleting the queue outright would lose data about every ticket that had ever been in that queue. Instead, RT provides the Disabled flag, which simply masks the queues you don’t want to see anymore.
Scrips are one of RT’s key bits of extensibility. Scrips are bits of custom logic that users can install into RT without changing any system code. Scrips are composed of a Condition and an Action. If the Condition returns success, then RT will prepare the Action. If the action is prepared cleanly, it is Committed and all relevant email is sent out and data written to the database.
Scrips have the following fields:
The Description is a simple human-readable explanation for this Scrip.
ScripCondition is a pointer to the ID of a ScripCondition object.
ScripAction is a pointer to the ID of a ScripAction object.
CustomIsApplicableCode is a field used primarily by user-defined conditions. Its content is used as the source code for an anonymous subroutine, which can return true or false. If it returns true, the Scrip’s Action is prepared. Otherwise, processing of this scrip ceases.
CustomPrepareCode is a field used primarily by user-defined actions. Its content is used as the source code for an anonymous subroutine, which can return true or false. If it returns true, the CustomCommitCode is evaluated. Otherwise, processing of this scrip ceases.
CustomCommitCode is a field used primarily by user-defined actions. Its content is used as the source code for an anonymous subroutine, which should perform whatever action the ScripAction needs to perform and return true on success or false on failure.
Stages are the places in RT that call out to fire user-specified scrips . Valid values include:
TransactionCreate is the standard scrip call-point from RT 2.0. Immediately after a transaction has been posted to the database, RT executes the scrip.
RT collects updates to a ticket into a single batch and executes all TransactionBatch scrips once the current RT::Ticket
object has been marked for destruction.
Queue is the ID of the queue to which this scrip applies. If it’s set to 0, this scrip applies to all queues.
Template is a reference to the name of the template this scrip should use. If the template is a global template and there’s a queue-specific template with the same name in this scrip’s queue, RT will use the queue template.
These fields are not currently used by RT.
Templates are simple text templates
which Scrips use to build outgoing email messages. Their content is parsed with the Text::Template
module.
Templates have the following fields:
Queue is a pointer to the ID of the queue with which this template is associated. If this template is a global template that should be available to scrips in all queues, Queue is 0.
Name is a simple human-readable name for this template. Unlike most Names, it is sometimes used by RT to find templates.
Description is a simple human-readable description for this template.
Generally, a template’s Content is an email message template that a Scrip interprets and sends off. RT uses a simple heuristic to determine whether a template’s content contains email message headers; if the content’s first line contains a colon (:), it’s assumed to be a message header. RT treats each line as a header until it finds a blank line.
These fields are not used by RT.
ScripActions are references to chunks of code on disk, which are used by Scrips to perform some action. Examples include: Autoreply to Requestor, Notify Owner, and Page Systems Administrators.
ScripActions have the following fields:
Name is a simple human-readable name for this ScripAction. It’s not used internally by RT.
Description is a longer human-readable description for this ScripAction. It’s not used internally by RT.
ExecModule is the name of the Perl module in lib/RT/Action/ that should be called by this ScripAction. That Perl module must be a subclass of RT::Action::Generic
.
Argument contains an optional argument to be passed to the Action module during evaluation. RT’s Notify ScripAction uses this argument to define which roles should receive mail from a particular scrip.
ScripConditions are references to chunks of code on disk, which scrips use to perform some check or validation. Examples include: On Create, On Correspond, or If the smoke alarm is going off.
ScripConditions have the following fields:
Name is a simple human-readable name for this ScripAction. It’s not used internally by RT.
Description is a longer human-readable description for this ScripAction. It’s not used internally by RT.
ExecModule is the name of the Perl module in lib/RT/Action/ that should be called by this ScripAction. That Perl module must be a subclass of RT::Condition::Generic
.
Argument contains an optional argument to be passed to the Action module during evaluation. RT’s “Notify” ScripAction uses this argument to define which roles should be receiving mail from a particular scrip.
ApplicableTransTypes specifies a comma-delimited list of transaction types to which this ScripCondition applies. “Any” means that the ScripAction will run on any transaction. This serves as a first line of defense to cut down on the amount of processing that RT needs to do on any ticket update.
[11] There are a couple of places where this isn’t quite true yet, but it’s being improved with each new version.
[12] The RT FAQ Manager. It doesn’t mean anything else, really!
[13] Dave Rolsky and Ken Williams’s Embedding Perl in HTML with Mason (O’Reilly) is the canonical source for everything about Mason.
[14] End users painstakingly translated the entire application into three or four different languages.
[15] Well, almost fully, it doesn’t deal quite right with right-to-left languages like Hebrew and Arabic yet.
[16] To make the access control system work, it’s important that a group be listed as a member of itself.
[17] For more information about RDF, see Practical RDF (O’Reilly).