WorkFlow2

From Request Tracker Wiki
Jump to: navigation, search

Another kind of workflow for RT

This method draws heavily on the other workflow plugin and from many contributions here in general, so I definitely don't claim ownership of all the code/methods (thx guys :)) In the original workflow plugin, customfields were defined for each task and initiated manually, with (potentially) all the tasks running concurrently (as subtickets). In this model, one master ticket initiates a child ticket for the first task, when the first child is resolved the second child ticket is initiated etc. In addition, this method allows a name, description, queue, and owner for each subticket (stage) of the process. Also, this reduces the number of scrips+templates+customfields to a constant number, regardless of how many workflows you have.

Custom Field Setup

Add 5 (ticket) custom fields and activate them globally using the following names:

WorkflowName

WorkflowStage

WorkflowStageDesc

WorkflowStageOwner

WorkflowstageQueue

All of these should be "enter a value" fields with the exception of WorkflowName, which should be a "select one value" field.

Template Setup

Add a single global template called "WorkflowTemplate" that consists of the following:

  ===Create-Ticket:  {$Tickets{'TOP'}->FirstCustomFieldValue('WorkflowName');}::{$Tickets{'TOP'}->FirstCustomFieldValue('WorkflowStage');}
  Subject: Workflow::{$Tickets{'TOP'}->Subject;}
  Parents: TOP
  Queue: {$Tickets{'TOP'}->FirstCustomFieldValue('WorkflowStageQueue');}
  Owner: {$Tickets{'TOP'}->FirstCustomFieldValue('WorkflowStageOwner');}
  Content: {$Tickets{'TOP'}->FirstCustomFieldValue('WorkflowStageDesc');}
  ENDOFCONTENT

Main Scrip Setup

Create a global scrip;

Condition: User Defined

Action: Create Tickets

Template: WorkflowTemplate

Stage: TransactionCreate

Beware, this scrip is pretty ugly. Basically, it's a ticket creation scrip that performs a lot more than condition checking in the condition section (i didnt wanna mess around with making a new ticket from a template in the scrip, and this was easy).

Paste the code below into the "Custom Condition" text box

        #example workflow.  Whatever you do, do NOT make '|' (a pipe symbol) the first character in a stagename
         my @newcompworkflow = (
               #   stagename               stagedesc                                                    owner       queue
               ["AskUserForSoftware", "please ask the user what specialized software they would like", "person1",  "mainhelpqueue"],
               ["OrderNewComputer", "please order the user's new computer", "person1", "purchases"],
               ["ImageMachine","please image the user's new machine","person2", "mainhelpqueue"],
               ["blah","please blah blah blah","person2","someotherqueue"],
         );
         my %workflows = ();
         #Set workflow name to 'New Computer'
         #Make as many workflows you want like the example above and define them here.
         #The name here ('New Computer') will correspond to the possible values for the customfield WorkflowName, so add them  as values there.
         $workflows{'New Computer'} = \@newcompworkflow;
         my $stagename = 0;
         my $stagedesc = 1;
         my $stageowner = 2;
         my $stagequeue = 3;
         my $wfn = 'WorkflowName';
         my $wfs = 'WorkflowStage';
         my $wfsd = 'WorkflowStageDesc';
         my $wfso = 'WorkflowStageOwner';
         my $wfsq = 'WorkflowStageQueue';
         
          my $QueueObj = $self->TicketObj->QueueObj;
         my $wfnobj = RT::CustomField->new( $QueueObj->CurrentUser );
          $wfnobj->LoadByNameAndQueue( Name => $wfn, Queue => $QueueObj->id );
          unless( $wfnobj->id ) {
            $wfnobj->LoadByNameAndQueue( Name => $wfn, Queue => 0 );
            unless( $wfnobj->id ) {
              $RT::Logger->warning("custom field '$wfn' isn't global or defined for queue '". $QueueObj->Name ."'");
              return undef;
            }
          }
         
          my $wfsobj = RT::CustomField->new( $QueueObj->CurrentUser );
          $wfsobj->LoadByNameAndQueue( Name => $wfs, Queue => $QueueObj->id );
          unless( $wfsobj->id ) {
            $wfsobj->LoadByNameAndQueue( Name => $wfs, Queue => 0 );
            unless( $wfsobj->id ) {
              $RT::Logger->warning("custom field '$wfs' isn't global or defined for queue '". $QueueObj->Name ."'");
              return undef;
            }
          }
          my $wfsdobj = RT::CustomField->new( $QueueObj->CurrentUser );
         $wfsdobj->LoadByNameAndQueue( Name => $wfsd, Queue => $QueueObj->id );
          unless( $wfsdobj->id ) {
            $wfsdobj->LoadByNameAndQueue( Name => $wfsd, Queue => 0 );
            unless( $wfsdobj->id ) {
              $RT::Logger->warning("custom field '$wfsd' isn't global or defined for queue '". $QueueObj->Name ."'");
              return undef;
            }
          }
         
          my $wfsoobj = RT::CustomField->new( $QueueObj->CurrentUser );
         $wfsoobj->LoadByNameAndQueue( Name => $wfso, Queue => $QueueObj->id );
          unless( $wfsoobj->id ) {
            $wfsoobj->LoadByNameAndQueue( Name => $wfso, Queue => 0 );
            unless( $wfsoobj->id ) {
              $RT::Logger->warning("custom field '$wfso' isn't global or defined for queue '". $QueueObj->Name ."'");
              return undef;
            }
          }
          my $wfsqobj = RT::CustomField->new( $QueueObj->CurrentUser );
         $wfsqobj->LoadByNameAndQueue( Name => $wfsq, Queue => $QueueObj->id );
          unless( $wfsqobj->id ) {
            $wfsqobj->LoadByNameAndQueue( Name => $wfsq, Queue => 0 );
            unless( $wfsqobj->id ) {
              $RT::Logger->warning("custom field '$wfsq' isn't global or defined for queue '". $QueueObj->Name ."'");
              return undef;
            }
          }
         
         #See if this ticket is part of a workflow
         my $wfname = $self->TicketObj->FirstCustomFieldValue('WorkflowName');
         my @workflow;
         if ($wfname ne '') {
              @workflow = @{$workflows{$wfname}};
         }
         
         #If the customfield workflowname was just set, then set workflowstage to the first stage of that workflow
         if ( ( $self->TransactionObj->Type eq 'CustomField' ) &&
             ($self->TransactionObj->Field == $wfnobj->id) ) {
         
                 if ($workflows{$wfname}) {
                     my( $st, $msg ) = $self->TicketObj->AddCustomFieldValue(
                                                  Field => $wfsobj->id,
                                                  Value => $workflow[0][$stagename],
                                                  RecordTransaction => 1 );
                 }
                  return undef;
                #even though this did what we wanted it to, return undef because we have no email to send at this point
         }
         #if we dont have a workflow by now, bail
         if (!@workflow) {
            return undef;
         }
         #if the workflowstage is prefixed by a '|', advance to the next workflow stage in the sequence, or 'completed' if  it's done
         #because completion generates no notice, i'd recommend having the last item in your workflow be a notification of the responsible party
         
         if ( ($self->TransactionObj->Type eq "CustomField") &&
               ($self->TransactionObj->Field == $wfsobj->id) ) {
            if (substr($self->TicketObj->FirstCustomFieldValue('WorkflowStage'), 0, 1) eq '|') {
                my $x = 0;
                for ($x=0;$x<@workflow;$x++) {
                     if ($workflow[$x][$stagename] eq substr($self->TicketObj->FirstCustomFieldValue('WorkflowStage'), 1)) {
                            my( $st, $msg ) = $self->TicketObj->AddCustomFieldValue(
                                                  Field => $wfsobj->id,
                                                  Value => ($x == $#workflow) ? 'Completed' : $workflow[$x+1][$stagename],
                                                  RecordTransaction => 1 );
                     }
                }
                return undef;
            }
            #If the workflowstage was just set, update the corresponding description, owner, and queue, and send off the new ticket
            my $x = 0;
            for ($x =0;$x<@workflow;$x++) {
               if ($workflow[$x][$stagename] eq $self->TicketObj->FirstCustomFieldValue('WorkflowStage')) {
            my( $st, $msg ) = $self->TicketObj->AddCustomFieldValue(
                                                  Field => $wfsdobj->id,
                                                  Value => $workflow[$x][$stagedesc],
                                                  RecordTransaction => 0 );
         
                my( $st, $msg ) = $self->TicketObj->AddCustomFieldValue(
                                                  Field => $wfsoobj->id,
                                                  Value => $workflow[$x][$stageowner],
                                                  RecordTransaction => 0 );
                my( $st, $msg ) = $self->TicketObj->AddCustomFieldValue(
                                                  Field => $wfsqobj->id,
                                                  Value => $workflow[$x][$stagequeue],
                                                  RecordTransaction => 0 );
         
            return 1;
                }
            }
         
         }
         return undef;
         

I apologize for the terrible formatting, if someone has a perl formatter handy feel free to have at it ;)

Stage Advancer scrip

The second scrip (also global) is responsible for changing the workflowstage from "stagename" to "|stagename" on the resolution of a child ticket. This is all that is necessary for the main scrip to notice that it is time to advance to the next stage.

Readers Note: Condition? Action? Template? Stage?

  my $MemberOf = $self->TicketObj->MemberOf;
  my $l = $MemberOf->Next;
  my $parentObj = $l->TargetObj;
  
  my $QueueObj = $parentObj->QueueObj;
  my $wfs = 'WorkflowStage';
   my $wfsobj = RT::CustomField->new( $QueueObj->CurrentUser );
   $wfsobj->LoadByNameAndQueue( Name => $wfs, Queue => $QueueObj->id );
   unless( $wfsobj->id ) {
     $wfsobj->LoadByNameAndQueue( Name => $wfs, Queue => 0 );
     unless( $wfsobj->id ) {
       $RT::Logger->warning("custom field '$wfs' isn't global or defined for queue '". $QueueObj->Name ."'");
       return undef;
     }
   }
  my $curval = $parentObj->FirstCustomFieldValue('WorkflowStage');
  
  if ($curval ne '') {
    my( $st, $msg ) = $parentObj->AddCustomFieldValue(
                                           Field => $wfsobj->id,
                                           Value => '|' . $curval,
                                           RecordTransaction => 1 );
    return 1;
  }
  return undef;

Kicking it off

I don't know how to make it work on create because the object doesnt exist yet, but if you create a ticket and then update the workflowname, the scrip will automatically initiate the first stage and create the first child ticket. As explained in the code comments, I'd recommend having the last phase of a workflow be creating a ticket to notify the responsible party that the sequence is complete, because currently nothing happens after the last step.

Hope this helps someone