Difference between revisions of "WriteCustomAction"

From Request Tracker Wiki
Jump to navigation Jump to search
(phlebotomy training|phlebotomy certification|phlebotomy classes)
(2 intermediate revisions by the same user not shown)
Line 9: Line 9:
== Basics ==
== Basics ==


Let's start with Web UI. Goto Configuration -> Globals -> Scrips -> New Scrip.
[[File:Example1ScripUI.png|thumb|left|325px|Modify a scrip UI]]Let's start with Web UI. Goto Configuration -> Globals -> Scripts -> New Script.


The Scrip UI will display the following selectors: Description, Condition, Action, Template and Stage. In the action selector you can pick actions based on files (modules). An outstanding action from this list is "User Defined". The rest of this document describes this action. So in the Action field, select "User Defined" from the menu.
The Script UI will display the following selectors: Description, Condition, Action, Template and Stage. In the action selector you can pick actions based on files (modules). An outstanding action from this list is "User Defined". The rest of this document describes this action. So in the Action field, select "User Defined" from the menu.


Once you have selected "User Defined" from the Action Field, the following two Action code areas are enabled and used by RT:
Once you have selected "User Defined" from the Action Field, the following two Action code areas are enabled and used by RT:
* Custom action preparation code
* Custom action preparation code
* Custom action cleanup code
* Custom action cleanup code


When a [[Transaction]] is created, RT does the following steps to enable all this magic and give you a chance to be a small God:
When a [[Transaction]] is created, RT does the following steps to enable all this magic and give you a chance to be a small God:


* Select [[Scrip]]s
* Select [[Scrip]]ts
* Check applicability of scrips by executing [[Condition]]s
* Check applicability of scripts by executing [[Condition]]s
* Execute preparation code scrip by scrip
* Execute preparation code script by script
* Scrips that fail will be thrown away
* Scripts that fail will be thrown away
* Execute commit code of survived scrips proceeding scrip by scrip
* Execute commit code of survived scripts proceeding script by script


When RT executes your perl Action code within this scrip, your code can become an actor upon the ticket. RT has already defined the variable $self. This variable represents an instance of class [=RT::Action::[[UserDefined]]], a subclass of <code>RT::Action::Generic</code>. You can get more info from perldoc and from the source code.
When RT executes your perl Action code within this script, your code can become an actor upon the ticket. RT has already defined the variable $self. This variable represents an instance of class [=RT::Action::[[UserDefined]]], a subclass of <code>RT::Action::Generic</code>. You can get more info from perldoc and from the source code.


Instance <code>$self</code> provides your Action code with very useful methods:
Instance <code>$self</code> provides your Action code with very useful methods:
Line 40: Line 40:
  $self-&gt;TemplateObj
  $self-&gt;TemplateObj


Returns the template (<code>RT::Template</code>) which was selected for this scrip.
Returns the template (<code>RT::Template</code>) which was selected for this script.


=== Getting more info about these objects ===
=== Getting more info about these objects ===
Line 55: Line 55:
  perldoc /opt/rt3/lib/RT/Transactions_Overlay.pm
  perldoc /opt/rt3/lib/RT/Transactions_Overlay.pm
  ...
  ...
Now there is an
  alternative that you can ingest get to get [http://phlebotomycoursesinfo.com/phlebotomy-certification/#phlebotomy_certification phlebotomy certification]with the aid of online educational institutions.
  This can be in fact recommended since you can not waste time as well as if
  you will take this approach than to go to Phlebotomy institution
  professionally. Phlebotomy will be the research involving extracting body in
  the spider vein. This is one of the vouchers that exist in order to be in
  advance with your profession. A phlebotomist could be the individual who is
  licensed in getting taste blood to a person that is going to go through blood
  transfusion. The thing that you have to get ready if you are interested in
  online phlebotomy college can be your senior high school diploma or degree as
  well as GED. For you to definitely always be known as phlebotomist you'll
  need to be acknowledged with a Phlebotomy university. Fortunately that you
  can now be approved in the using online institution associated with
  phlebotomy. There are lots of websites in the Internet wherein you'll find
  about [http://phlebotomycoursesinfo.com/#phlebotomy_classes phlebotomy classes] and also
  [http://phlebotomycoursesinfo.com/phlebotomy-training/#phlebotomy_training phlebotomy training] . The actual Online phlebotomy certificate along with
  degree packages may be carried out in since three months. But when you want
  to take the affiliate levels inside phlebotomy may take 18 months as the
  bachelor levels will require longer time. In order to take some internship in
  the hospital whilst learning online phlebotomy this might take you a year.
  You might need to get ready qualifications throughout CPR if you are
  considering internships. Almost all of the phlebotomists are located in
  medical workplaces, hospitals, and even with a labradors. They are giving
  services that will deal with body assortment. If you're fascinated it is
  advisable if you'll try to find the dependable online phlebotomy institution
  online. You'll want to get the built to be verified and contains a healthy
  standing. An advanced regimented man or woman so you are aware that learn
  inside online universities and then attempt the utilization this online
  qualification.


== Simple example ==
== Simple example ==
Line 91: Line 62:
Requirement: There is a support queue for special customers where each request must have high priority on ticket creation.
Requirement: There is a support queue for special customers where each request must have high priority on ticket creation.


Preparation code:
[[File:Example1ScripBehavior.png|thumb|275px]]Preparation code:


  <nowiki># we don't need any preparation yet.
  <nowiki># we don't need any preparation yet.
return 1;
  return 1;
 
</nowiki>
  </nowiki>


Commit code:
Commit code:
Line 102: Line 73:
  $self-&gt;TicketObj-&gt;SetPriority( 100 );
  $self-&gt;TicketObj-&gt;SetPriority( 100 );
  return 1;
  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 administrator define default final priority per queue, our code should reflect that.
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 administrator define default final priority per queue, our code should reflect that.
Line 109: Line 79:
  $self-&gt;TicketObj-&gt;SetPriority( int( $qfp * 0.9 ) );
  $self-&gt;TicketObj-&gt;SetPriority( int( $qfp * 0.9 ) );
  return 1;
  return 1;


This change to the logic first retrieves the [[FinalPriority]] of the current Queue or 100 if no [[FinalPriority]] is set for the Queue. Then it sets the Priority of this ticket to 90% of the retrieved Priority. The final 10% of the priority is reserved for very exclusive, super high priority requests.
This change to the logic first retrieves the [[FinalPriority]] of the current Queue or 100 if no [[FinalPriority]] is set for the Queue. Then it sets the Priority of this ticket to 90% of the retrieved Priority. The final 10% of the priority is reserved for very exclusive, super high priority requests.
Line 124: Line 93:
  }
  }
  return 1;
  return 1;


== What you can (not) do with scrips ==
== What you can (not) do with scrips ==
Line 144: Line 112:


  $TicketObj-&gt;_Set(Field =&gt; 'Priority', Value =&gt; 90, RecordTransaction =&gt; 0);
  $TicketObj-&gt;_Set(Field =&gt; 'Priority', Value =&gt; 90, RecordTransaction =&gt; 0);


The zero in the [[RecordTransaction]] argument informs RT not to record the change as a new transaction.
The zero in the [[RecordTransaction]] argument informs RT not to record the change as a new transaction.
Line 153: Line 120:


  <nowiki>...
  <nowiki>...
my $CFName = 'MyCustomField';
  my $CFName = 'MyCustomField';
my $CF = RT::CustomField-&gt;new( $RT::SystemUser );
  my $CF = RT::CustomField-&gt;new( $RT::SystemUser );
$CF-&gt;LoadByNameAndQueue( Name =&gt; $CFName, Queue =&gt; $Ticket-&gt;Queue );
  $CF-&gt;LoadByNameAndQueue( Name =&gt; $CFName, Queue =&gt; $Ticket-&gt;Queue );
# RT has bug/feature until 3.0.10, you should load global CF yourself
  # RT has bug/feature until 3.0.10, you should load global CF yourself
unless( $CF-&gt;id ) {
  unless( $CF-&gt;id ) {
  # queue 0 is special case and is a synonym for global queue
    # queue 0 is special case and is a synonym for global queue
  $CF-&gt;LoadByNameAndQueue( Name =&gt; $CFName, Queue =&gt; '0' );
    $CF-&gt;LoadByNameAndQueue( Name =&gt; $CFName, Queue =&gt; '0' );
}
  }
 
unless( $CF-&gt;id ) {
  unless( $CF-&gt;id ) {
  $RT::Logger-&gt;error( "No field $CFName in queue ". $Ticket-&gt;QueueObj-&gt;Name );
    $RT::Logger-&gt;error( "No field $CFName in queue ". $Ticket-&gt;QueueObj-&gt;Name );
  return undef;
    return undef;
}
  }
...
  ...
 
</nowiki>
  </nowiki>


Now we could add value to ticket:
Now we could add value to ticket:
Line 174: Line 141:
  my $Value = 'MyValue';
  my $Value = 'MyValue';
  $Ticket-&gt;AddCustomFieldValue( Field =&gt; $CF, Value =&gt; $Value );
  $Ticket-&gt;AddCustomFieldValue( Field =&gt; $CF, Value =&gt; $Value );


or
or


  $Ticket-&gt;AddCustomFieldValue( Field =&gt; $CF, Value =&gt; $Value, RecordTransaction =&gt; 0 );
  $Ticket-&gt;AddCustomFieldValue( Field =&gt; $CF, Value =&gt; $Value, RecordTransaction =&gt; 0 );


you also could use custom field id instead of object.
you also could use custom field id instead of object.
Line 190: Line 155:


  <nowiki># init empty instance of RT::Ticket class
  <nowiki># init empty instance of RT::Ticket class
my $TicketObj = RT::Ticket-&gt;new( $session{'CurrentUser'} );
  my $TicketObj = RT::Ticket-&gt;new( $session{'CurrentUser'} );
 
# create new record
  # create new record
# if you more familiar with SQL then it's INSERT
  # if you more familiar with SQL then it's INSERT
# this call is inherited from SearchBuilder API
  # this call is inherited from SearchBuilder API
my $id = $TicketObj-&gt;Create( %ARGS );
  my $id = $TicketObj-&gt;Create( %ARGS );
 
</nowiki>
  </nowiki>


Ticket is created and it's time to record transaction into table. RT has all info for this: ticket's id that was recently created and transaction type - 'Create'.
Ticket is created and it's time to record transaction into table. RT has all info for this: ticket's id that was recently created and transaction type - 'Create'.


  <nowiki># this call creates new transaction record
  <nowiki># this call creates new transaction record
# this is very similar to situation with new ticket record
  # this is very similar to situation with new ticket record
my $TransactionObj = $Ticket-&gt;_RecordTransaction( %ARGS );
  my $TransactionObj = $Ticket-&gt;_RecordTransaction( %ARGS );
 
</nowiki>
  </nowiki>


Transactions in RT has an many-to-one mapping with ticket. One ticket =&gt; one or more transactions. You can get collection of transactions by calling Transactions method on a ticket object:
Transactions in RT has an many-to-one mapping with ticket. One ticket =&gt; one or more transactions. You can get collection of transactions by calling Transactions method on a ticket object:


  my $transactions = $TicketObj-&gt;Transactions;
  my $transactions = $TicketObj-&gt;Transactions;


Each transaction belongs to only one ticket and you get ticket object with the following code:
Each transaction belongs to only one ticket and you get ticket object with the following code:


  my $ticket = $TransactionObj-&gt;TicketObj;
  my $ticket = $TransactionObj-&gt;TicketObj;


As you can see RT still didn't use content that you wrote in the body. Content is an RT::Attachment object. You know how RT creates new record and know about Ticket-Transaction relation, the same relationship applies to Attachments and Transactions.
As you can see RT still didn't use content that you wrote in the body. Content is an RT::Attachment object. You know how RT creates new record and know about Ticket-Transaction relation, the same relationship applies to Attachments and Transactions.
Line 246: Line 209:
   
   
  1;
  1;


That's it. Save it as [=lib/RT/Action/MyAction.pm], fill in preparation and commit code then you can register your action in the DB. Use the following data file and [[AddDatabaseRecords]] instructions.
That's it. Save it as [=lib/RT/Action/MyAction.pm], fill in preparation and commit code then you can register your action in the DB. Use the following data file and [[AddDatabaseRecords]] instructions.
Line 258: Line 220:
   },
   },
  );
  );


Now your action in the DB and you can pick it for a scrip through the Web UI instead of picking "User Defined".
Now your action in the DB and you can pick it for a scrip through the Web UI instead of picking "User Defined".

Revision as of 16:59, 26 March 2013

This text in Portuguese in WriteCustomActionBR

How to write custom action code

Introduction

Included in RT3 is the ability to create your own custom ticket actions via the Web UI. The RT3 Custom Scrip capability lets you access the RT API and is a very powerful tool for customising RT.

Basics

File:Example1ScripUI.png
Modify a scrip UI

Let's start with Web UI. Goto Configuration -> Globals -> Scripts -> New Script.

The Script UI will display the following selectors: Description, Condition, Action, Template and Stage. In the action selector you can pick actions based on files (modules). An outstanding action from this list is "User Defined". The rest of this document describes this action. So in the Action field, select "User Defined" from the menu.

Once you have selected "User Defined" from the Action Field, the following two Action code areas are enabled and used by RT:

  • Custom action preparation code
  • Custom action cleanup code


When a Transaction is created, RT does the following steps to enable all this magic and give you a chance to be a small God:

  • Select Scripts
  • Check applicability of scripts by executing Conditions
  • Execute preparation code script by script
  • Scripts that fail will be thrown away
  • Execute commit code of survived scripts proceeding script by script

When RT executes your perl Action code within this script, your code can become an actor upon the ticket. RT has already defined the variable $self. This variable represents an instance of class [=RT::Action::UserDefined], a subclass of RT::Action::Generic. You can get more info from perldoc and from the source code.

Instance $self provides your Action code with very useful methods:

$self->TransactionObj

Returns RT::Transaction instance, the transaction that has been created and caused all this.

$self->TicketObj

Returns RT::Ticket instance which represents the ticket. Our transaction was applied to it.

$self->TemplateObj

Returns the template (RT::Template) which was selected for this script.

Getting more info about these objects

You can get complete information about these objects from their POD (embedded documentation).

perldoc /opt/rt3/lib/RT/Ticket.pm
perldoc /opt/rt3/lib/RT/Ticket_Overlay.pm
perldoc /opt/rt3/lib/RT/Tickets.pm
perldoc /opt/rt3/lib/RT/Tickets_Overlay.pm
perldoc /opt/rt3/lib/RT/Transaction.pm
perldoc /opt/rt3/lib/RT/Transaction_Overlay.pm
perldoc /opt/rt3/lib/RT/Transactions.pm
perldoc /opt/rt3/lib/RT/Transactions_Overlay.pm
...

Simple example

Ok, let's try to change something.

Requirement: There is a support queue for special customers where each request must have high priority on ticket creation.

Preparation code:

# we don't need any preparation yet.
  return 1;
  
  

Commit code:

$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 administrator define default final priority per queue, our code should reflect that.

my $qfp = $self->TicketObj->QueueObj->FinalPriority || 100;
$self->TicketObj->SetPriority( int( $qfp * 0.9 ) );
return 1;

This change to the logic first retrieves the FinalPriority of the current Queue or 100 if no FinalPriority is set for the Queue. Then it sets the Priority of this ticket to 90% of the retrieved Priority. The final 10% of the priority is reserved for very exclusive, super high priority requests.

Errors handling

It's important to check errors to protect you from headache. Most methods in RT that change something return a tuple ($status, $msg). If $status is not true value then something failed and $msg is error text describing the reason. Let's extend our code:

my $qfp = $self->TicketObj->QueueObj->FinalPriority || 100;
my ($status, $msg) = $self->TicketObj->SetPriority( int( $qfp * 0.9 ) );
unless ( $status ) {
    $RT::Logger->error("Coudln't change priority: $msg");
    return 0;
}
return 1;

What you can (not) do with scrips

You can manipulate almost any object in RT within a scrip.

  • update properties of tickets, for example set properties of tickets with commands in email
  • change linked tickets, for example OpenTicketOnAllMemberResolve and OpenDependantsOnResolve
  • extract info from messages, implement own workflow, create approvals and many-many more actions

Let's talk about impossible things you don't even want to try to do with a scrip:

  • you couldn't deny action scrip's triggered, for example you don't want to allow users to open ticket unless it has owner, you may think that you can create scrip "on ticket open block action if ticket has no owner", but it's impossible. I said impossible? no... sure you can create such scrip, but instead of preventing action you can revert it by setting status back to old value. yeah, this works but don't forget that it would be two transactions 'set open status' and 'set previouse status'. Each action could be applied to other scrips, so really it's not preventing action.
  • you couldn't run scrip at some time, remember RT runs scrips after creating transactions, but of course we have solution for this situation - rt-crontool.

How to be silent

Now you put SetXxxx 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 SetXxx functions:

$TicketObj->_Set(Field => 'Priority', Value => 90, RecordTransaction => 0);

The zero in the RecordTransaction argument informs RT not to record the change as a new 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 synonym 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 or "Tickets, transactions and attachments"

Lets look what happens with RT from the beginning when a user clicks on create button in their browser. Web server gets request with queue id, ticket subject and body, owner and etc. RT fetches info from the request that 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 = $TicketObj->Create( %ARGS );
  
  

Ticket is created and it's time to 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
  my $TransactionObj = $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 by calling Transactions method on a ticket object:

my $transactions = $TicketObj->Transactions;

Each transaction belongs to only one ticket and you get ticket object with the following code:

my $ticket = $TransactionObj->TicketObj;

As you can see RT still didn't use content that you wrote in the body. Content is an RT::Attachment object. You know how RT creates new record and know about Ticket-Transaction relation, the same relationship applies to Attachments and Transactions.

From "User Defined" to a module

User defined actions you write in the web UI are handy and quick way to write an action. However, at some point you want to re-use your action, make it configurable, edit it in more suitable editor rather than text area, make it more complex and consist of additional methods. So it's time to move from "User Defined" action to your first module file.

use strict;
use warnings;

package RT::Action::MyAction;
use base qw(RT::Action::Generic);

sub Prepare {
  my $self = shift;

  ... here goes preparation code ...

  return 1;
}

sub Commit {
  my $self = shift;

  ... here goes commit code ...

  return 1;
}

1;

That's it. Save it as [=lib/RT/Action/MyAction.pm], fill in preparation and commit code then you can register your action in the DB. Use the following data file and AddDatabaseRecords instructions.

@ScripActions = (
  {
    Name        => 'My super duper action',
    Description => 'Super-puper action that does hell of a job' ,
    ExecModule => 'MyAction',
    Argument   => 'some argument if needed'
  },
);

Now your action in the DB and you can pick it for a scrip through the Web UI instead of picking "User Defined".

See also

Other documentation on this wiki that may help

GlobalObjects, ObjectModel

Special thanks

  • TimWilson, who was the main editor and reviewer