WriteCustomActionBR
Como escrever código de ação customizadas
Introdução
O RT3 possui a habilidade para criar ações customizadas nos tiquetes via a interface Web. A habilidade CustomScrip do RT3 permite o acesso a API do RT e é uma ferramenta poderosa para customizar o RT.
Básico
Vamos iniciar com a interface Web. Vá em Configurações -> Globais -> Scrips -> Novo Scrip
Esta tela mostrará cinco espaços: Descrição, Condição, Ação, Template e Estágio. O restante deste documento descreve as ações definidas pelo usuário. No campo Ação, selecione "User Defined".
Uma vez selecionada essa opção no campo Ação, as seguintes áreas serão habilitadas e utilizadas pelo RT:
* Custom action preparation code * Custom action cleanup code
O RT segue os seguintes passos para permitir esta mágica e dar a chance a você de ser um Deus:
* Cria uma transação * Encontra padrões de condição * Executa o código de preparação scrip por scrip * Scrips que falharem irão ser descartados * Executa código commit dos scrips restantes, scrip por scrip
Quando o RT executa o código perl Ação dentro deste scrip, seu código pode tornar-se um ator sobre o tiquete. RT já define uma variável $self. Esta variável representa uma instância da classe RT:Action::UserDefined, uma subclasse de RT::Action::Generic. Você pode obter mais informações no perldoc ou no código fonte.
Instâncias $self fornecem ao código Ação com alguns métodos de acesso ao objeto muito úteis:
* TransactionObj - retorna uma instância RT::Transaction, a transação que foi aplicada. * TicketObj - retorna uma instância RT::Ticket que representa o tíquete. A transação será aplicada sobre ele. * TemplateObj - representa o modelo que foi selecionado para essa ação;
Você pode obter informações completas sobre esses objetos em perlpod.
Exemplo Simples
Ok, vamos tentar mudar algo.
Requisito: existe uma fila de suporte para clientes especiais onde cada requisição precisa ter alta prioridade na criação do tíquete.
Código de preparação:
- we don't need any preparation yet. return 1;
Código Commit:
$self->TicketObj->SetPriority( 100 ); return 1;
I hope that this example is understandable enough, but it has at least one weakness. I've hardcoded the priority value. Since RT lets the Queue administrator define the final default priority, our code shoudl reflect that understanding.
my $qfp = $self->TicketObj->QueueObj->FinalPriority || 100; $self->TicketObj->SetPriority( int( $qfp * 0.9 ) ); return 1;
Esta mudança primeiro recupera o valor de FinalPriority na fila atual ou 100 se FinalPriority não está definida para a fila. Então, setamos a prioridade deste tíquete para 90% do valor da prioridade recuperada. Os 10% restantes da prioridade é reservada para requisições com super altas prioridades e exceções. O que você pode (ou não) fazer com scrips:
Você pode manipular a maioria dos objetos do RT com um scrip:
* atualizar propriedade dos tíquetes, por exemplo definir propriedades de tíquetes com comandos de emails * mundar ligações de tíquetes, por exemplo OpenTicketOnAllMemberResolve e OpenDependantsOnResolve * extrair informações das mensagens, implementar seu próprio workflow, criar aprovações e muitas outras ações
Vamos ver agora coisas impossíveis que você realmente não irá querer tentar fazer com scrips:
* você não poderá negar ações de scrips, por exemplo você deseja não permitir que usuários abram tíquetes a não ser que eles sejam os donos, você pode pensar em criar um scrip "quando tíquete abrir bloqueie a ação se não for o dono", mas isso é impossível. Eu disse impossível? não... realmente você pode criar este scrip, mas ao invés de prevenir a ação você pode reverte-la, defininado o status com o valor anterior. Sim, isto funciona mas não se esqueça que serão duas transações "definir status aberto" e "definir status anterior". * executar mais de um scrip ao mesmo tempo, lembre-se RT executa scrips após a criação da transação, mas é claro que existe uma solução para esta situação também - rt-crontool
Fragmentos de códigos:
Recuperar o nome da fila onde o tíquete está:
my $QueueName = $TicketObj->QueueObj->Name;
Mudar a fila do tíquete:
my $TargetQueueName = 'MyQueue'; my ($status, $msg) = $TicketObj->SetQueue( $TargetQueueName ); unless( $status ) {
die "Error: $msg";
}
Verificar se um usuário é um observador:
if ( $TicketObj->IsWatcher( Type => 'Requestor', Email => 'foo@bar.com' ) ) { ... }
Configurar o ator atual como o proprietário do tíquete:
my $Actor = $TransactionObj->CreatorObj->Id; if( $Actor != $self->TicketObj->OwnerObj->Id ) {
$RT::Logger->info("Auto assign ticket #". $TicketObj->id ." to user #". $Actor ); my ($status, $msg) = $TicketObj->SetOwner( $Actor ); unless( $status ) { die "Error: $msg"; }
} return( 1 );
We want to be able to assign cases to unprivileged customers, once they have replied the case should be released:
my $Actor = $self->TransactionObj->CreatorObj; return 1 if $Actor->Privileged;
my $Owner = $self->TicketObj->Owner; return 1 unless $Actor->id == $Owner;
$self->TicketObj->SetOwner( $RT::Nobody->id ); return 1;
How to be silent
Now you put $TicketObj->Set* calls all over the places in RT and suddenly note that strange transactions appear in tickets. They have creator RT_System and describe what you've done with your scrips. Sometimes it's better to be silent and not mislead users. These transactions also go through the steps described earlier and could trigger some conditions too. Just use the long form of Set* functions:
$TicketObj->_Set(Field => 'Priority', Value => 90, RecordTransaction => 0);
The zero in the RecordTransaction argument informs RT not to record the change as a transaction. How to change ticket custom field values
Step 1, get the custom field (CF) object or ID. Don't use the hardcoded CF ID from the database. Step 2, get the CF object by ticket object by ticket object (I hope you remember how to get a ticket object) and CF name.
... my $CFName = 'MyCustomField'; my $CF = RT::CustomField->new( $RT::SystemUser ); $CF->LoadByNameAndQueue( Name => $CFName, Queue => $Ticket->Queue ); # RT has bug/feature until 3.0.10, you should load global CF yourself unless( $CF->id ) {
# queue 0 is special case and is a synonim for global queue $CF->LoadByNameAndQueue( Name => $CFName, Queue => '0' );
}
unless( $CF->id ) {
$RT::Logger->error( "No field $CFName in queue ". $Ticket->QueueObj->Name ); return undef;
} ...
Now we could add value to ticket:
my $Value = 'MyValue'; $Ticket->AddCustomFieldValue( Field => $CF, Value => $Value );
or
$Ticket->AddCustomFieldValue( Field => $CF, Value => $Value, RecordTransaction => 0 );
you also could use custom field id instead of object.
$Ticket->AddCustomFieldValue( Field => NN , Value => $Value );
Step by step
Lets look what happens with RT from the beginning when a user clicks on create button in their browser. Web server got request where stored: queue id, ticket subject and body, owner and etc. RT fetches info from request which is needed for Ticket record: its Queue id, Status, Subject, CurrentUser(Creator), and then runs the next code
- init empty instance of RT::Ticket class my $TicketObj = RT::Ticket->new( $session{'CurrentUser'} );
- create new record # if you more familiar with SQL then it's INSERT # this call is inherited from SearchBuilder API my $id = $Ticket->Create( %ARGS );
Ticket is created and time record transaction into table, RT has all info for this: ticket's id that was recently created and transaction type - 'Create'.
- this call creates new transaction record # this is very similar to situation with new ticket record # but this call is part of RT API and return object, but not an id. my $TrObj = $Ticket->_RecordTransaction( %ARGS );
Transactions in RT has an many-to-one mapping with ticket. One ticket => one or more transactions. You can get collection of transactions with $TicketObj->Transactions. Each transaction has only one ticket that it belongs to and you get ticket object with $TransactionObj->TicketObj. As you can see RT still didn't use content that you wrote in the body. Content is an attachment. You know how RT creates new record and know about Ticket-Transaction relation, same relationship between Attachments and Transactions.
...
How to send note to folk
I've decided skip this chapter, because I don't need this one :). Also I looked into RT code and realize that this task is more for action module then embeded custom action code. Look into autoreply and sendmail actions in RT code base, it will help you a lot. How to eviscerate fish
TODO:
Other documentation on this wiki that may help
GlobalObjects, ObjectModel Special thanks
* TimWilson, who was/is main editor and reviewer
TODO
* Continue....! * Write chapter "How to eviscerate fish" (in other words: How to get info from incoming email)