ticket escalation, part 2, RT#8254
authormark <mark>
Tue, 25 Jan 2011 10:13:16 +0000 (10:13 +0000)
committermark <mark>
Tue, 25 Jan 2011 10:13:16 +0000 (10:13 +0000)
FS/FS/Cron/rt_tasks.pm
FS/FS/TicketSystem.pm
rt/FREESIDE_MODIFIED
rt/etc/RT_SiteConfig.pm
rt/lib/RT/Action/EscalateQueue.pm [new file with mode: 0755]
rt/lib/RT/Action/SetPriority_Local.pm [new file with mode: 0644]
rt/lib/RT/CustomFieldValues/Queues.pm [new file with mode: 0644]

index 066aeeb..26e305d 100644 (file)
@@ -42,6 +42,7 @@ sub rt_escalate {
   foreach (qw(
     Search::ActiveTicketsInQueue 
     Action::EscalatePriority
+    Action::EscalateQueue
     )) {
     eval "use RT::$_";
     die $@ if $@;
@@ -51,7 +52,7 @@ sub rt_escalate {
   # Mechanics:
   # We're using EscalatePriority, so search in all queues that have a 
   # priority range defined. Select all active tickets in those queues and
-  # LinearEscalate them.
+  # EscalatePriority, then EscalateQueue them.
 
   # to make some actions work without complaining
   %void = map { $_ => "RT::$_"->new($CurrentUser) }
@@ -61,6 +62,8 @@ sub rt_escalate {
   # we might want to do, but escalation is the only one we do now.
   my $queues = RT::Queues->new($CurrentUser);
   $queues->UnLimit;
+  my @actions = ();
+  my @active_tickets = ();
   while (my $queue = $queues->Next) {
     if ( $queue->InitialPriority == $queue->FinalPriority ) {
       warn "Queue '".$queue->Name."' (skipped)\n" if $DEBUG;
@@ -76,8 +79,24 @@ sub rt_escalate {
     $search->Prepare;
     while (my $ticket = $tickets->Next) {
       warn 'Ticket #'.$ticket->Id()."\n" if $DEBUG;
-      # We don't need transaction stuff from rt-crontool here
-      action($ticket, 'EscalatePriority', "CurrentTime:$time");
+      my @a = (
+        action($ticket, 'EscalatePriority', "CurrentTime:$time"),
+        action($ticket, 'EscalateQueue')
+      );
+      next if !@a;
+      push @actions, @a;
+      push @active_tickets, $ticket; # avoid RT's overzealous garbage collector
+    }
+  }
+  foreach (grep {$_} @actions) {
+    my ($val, $msg) = $_->Commit;
+    if ( $DEBUG ) {
+      if ($val) {
+        warn "Action committed: ".ref($_)." #".$_->TicketObj->Id."\n";
+      }
+      else {
+        warn "Action returned $msg: #".$_->TicketObj->Id."\n";
+      }
     }
   }
   return;
@@ -98,11 +117,13 @@ sub action {
     ScripAction   => $void{'ScripAction'},
     CurrentUser   => $CurrentUser,
   );
-  return unless $action_obj->Prepare;
-  warn "Action prepared: $action\n" if $DEBUG;
-  $action_obj->Commit;
-  warn "Action committed: $action\n" if $DEBUG;
-  return;
+  if ( $action_obj->Prepare ) {
+    warn "Action prepared: $action\n" if $DEBUG;
+    return $action_obj;
+  }
+  else {
+    return;
+  }
 }
 
 1;
index d53d2f6..f5c8e7d 100644 (file)
@@ -29,23 +29,61 @@ sub AUTOLOAD {
 
 sub _upgrade_data {
   return if $system ne 'RT_Internal';
-
   my ($class, %opts) = @_;
-  my ($t, $exec, @fields) = map { driver_name =~ /^mysql/i ? $_ : lc($_) }
-  (qw( ScripConditions ExecModule
-    Name Description ExecModule ApplicableTransTypes
-    Creator Created LastUpdatedBy LastUpdated));
-  my $count_sql = "SELECT COUNT(*) FROM $t WHERE $exec = 'CustomFieldChange'";
-  my $sth = dbh->prepare($count_sql) or die dbh->errstr;
-  $sth->execute or die $sth->errstr;
-  my $total = $sth->fetchrow_arrayref->[0];
-  return if $total > 0;
-
-  my $insert_sql = "INSERT INTO $t (".join(',',@fields).") VALUES (".
-  "'On Custom Field Change', 'When a custom field is changed to some value',
-  'CustomFieldChange', 'Any', 1, CURRENT_DATE, 1, CURRENT_DATE )";
-  $sth = dbh->prepare($insert_sql) or die dbh->errstr;
-  $sth->execute or die $sth->errstr;
+
+  # go ahead and use the RT API for this
+  
+  FS::TicketSystem->init;
+  my $session = FS::TicketSystem->session();
+  my $CurrentUser = $session->{'CurrentUser'}
+    or die 'freeside-upgrade must run as a valid RT user';
+
+  # CustomFieldChange scrip condition
+  my $ScripCondition = RT::ScripCondition->new($CurrentUser);
+  $ScripCondition->LoadByCols('ExecModule' => 'CustomFieldChange');
+  if (!defined($ScripCondition->Id)) {
+    my ($val, $msg) = $ScripCondition->Create(
+      'Name' => 'On Custom Field Change',
+      'Description' => 'When a custom field is changed to some value',
+      'ExecModule' => 'CustomFieldChange',
+      'ApplicableTransTypes' => 'Any',
+    );
+    die $msg if !$val;
+  }
+
+  # SetPriority scrip action
+  my $ScripAction = RT::ScripAction->new($CurrentUser);
+  $ScripAction->LoadByCols('ExecModule' => 'SetPriority');
+  if (!defined($ScripAction->Id)) {
+    my ($val, $msg) = $ScripAction->Create(
+      'Name' => 'Set Priority',
+      'Description' => 'Set ticket priority',
+      'ExecModule' => 'SetPriority',
+      'Argument' => '',
+    );
+    die $msg if !$val;
+  }
+
+  # EscalateQueue custom field and friends
+  my $CF = RT::CustomField->new($CurrentUser);
+  $CF->Load('EscalateQueue');
+  if (!defined($CF->Id)) {
+    my ($val, $msg) = $CF->Create(
+      'Name' => 'EscalateQueue',
+      'Type' => 'Select',
+      'MaxValues' => 1,
+      'LookupType' => 'RT::Queue',
+      'Description' => 'Escalate to Queue',
+      'ValuesClass' => 'RT::CustomFieldValues::Queues', #magic!
+    );
+    die $msg if !$val;
+    my $OCF = RT::ObjectCustomField->new($CurrentUser);
+    ($val, $msg) = $OCF->Create(
+      'CustomField' => $CF->Id,
+      'ObjectId' => 0,
+    );
+    die $msg if !$val;
+  }
   return;
 }
 
index 70801d5..5bef0bf 100644 (file)
@@ -17,6 +17,7 @@ lib/RT/Scrip_Overlay.pm #create ticket on custom field change
 lib/RT/Action/CreateTickets.pm #create ticket on custom field change
 lib/RT/Action/EscalatePriority.pm #ticket escalation
 lib/RT/Action/EscalateQueue.pm #ticket escalation
+lib/RT/Action/SetPriority_Local.pm #ticket escalation
 lib/RT/CustomFieldValues/Queues.pm #ticket escalation
 lib/RT/Condition/CustomFieldChange.pm #create ticket on custom field change
 lib/RT/Interface/Web_Vendor.pm
index f833f0d..872b8c3 100644 (file)
@@ -53,4 +53,8 @@ Set($MessageBoxRichTextHeight, 368);
 #Set($QuickCreateRedirect, 1);
 
 #Set(@Plugins,(qw(Extension::QuickDelete RT::FM)));
+
+#used in ticket escalation
+Set(@CustomFieldValuesSources, ('RT::CustomFieldValues::Queues'));
+
 1;
diff --git a/rt/lib/RT/Action/EscalateQueue.pm b/rt/lib/RT/Action/EscalateQueue.pm
new file mode 100755 (executable)
index 0000000..adafbdf
--- /dev/null
@@ -0,0 +1,141 @@
+=head1 NAME
+
+RT::Action::EscalateQueue - move a ticket to a different queue when it reaches its final priority
+
+=head1 DESCRIPTION
+
+EscalateQueue is a ScripAction that will move a ticket to a new 
+queue when its priority equals its final priority.  It is designed 
+to be used with LinearEscalate or another action that increments
+ticket priority on some schedule.  Like those actions, it is intended 
+to be called from an escalation tool.
+
+=head1 CONFIGURATION
+
+FinalPriority is a ticket property, defaulting to the queue property.
+
+EscalateQueue is a queue custom field using RT::CustomFieldValues::Queue 
+as its data source (that is, it refers to another queue).  Tickets at 
+FinalPriority will be moved to that queue.
+
+From a shell you can use the following command:
+
+    rt-crontool --search RT::Search::FromSQL --search-arg \
+    "(Status='new' OR Status='open' OR Status = 'stalled')" \
+    --action RT::Action::EscalateQueue
+
+No action argument is needed.  Each ticket will be escalated based on the
+EscalateQueue property of its current queue.
+
+=cut
+
+package RT::Action::EscalateQueue;
+
+use strict;
+use warnings;
+use base qw(RT::Action);
+
+our $VERSION = '0.01';
+
+#What does this type of Action does
+
+sub Describe {
+    my $self = shift;
+    my $class = ref($self) || $self;
+    return "$class will move a ticket to its escalation queue when it reaches its final priority."
+}
+
+#This Prepare only returns 1 if the ticket will be escalated.
+
+sub Prepare {
+    my $self = shift;
+
+    my $ticket = $self->TicketObj;
+    my $queue = $ticket->QueueObj;
+    my $new_queue = $queue->FirstCustomFieldValue('EscalateQueue');
+
+    my $ticketid = 'Ticket #'.$ticket->Id; #for debug messages
+    if ( $ticket->InitialPriority == $ticket->FinalPriority ) {
+        $RT::Logger->debug("$ticketid has no priority range.  Not escalating.");
+        return 0;
+    }
+
+    if ( $ticket->Priority == $ticket->FinalPriority ) {
+        if (!$new_queue) {
+            $RT::Logger->debug("$ticketid has no escalation queue.  Not escalating.");
+            return 0;
+        }
+        if ($new_queue eq $queue->Name) {
+            $RT::Logger->debug("$ticketid would be escalated to its current queue.");
+            return 0;
+        }
+        $self->{'new_queue'} = $new_queue;
+        return 1;
+    }
+    return 0;
+}
+
+# whereas Commit returns 1 if it succeeds at whatever it's doing
+sub Commit {
+    my $self = shift;
+
+    return 1 if !exists($self->{'new_queue'});
+
+    my $ticket = $self->TicketObj;
+    my $ticketid = 'Ticket #'.$ticket->Id;
+    my $new_queue = RT::Queue->new($ticket->CurrentUser);
+    $new_queue->Load($self->{'new_queue'});
+    if ( ! $new_queue ) {
+        $RT::Logger->debug("Escalation queue ".$self->{'new_queue'}." not found.");
+        return 0;
+    }
+    $RT::Logger->debug("Escalating $ticket from ".$ticket->QueueObj->Name .
+        ' to '.  $new_queue->Name . ', FinalPriority '.$new_queue->FinalPriority);
+
+    my ( $val, $msg ) = $ticket->SetQueue($self->{'new_queue'});
+    if (! $val) {
+        $RT::Logger->error( "Couldn't set queue: $msg" );
+        return (0, $msg);
+    }
+
+    # Set properties of the ticket according to its new queue, so that 
+    # escalation Does What You Expect.  Don't record transactions for this;
+    # the queue change should be enough.
+
+    ( $val, $msg ) = $ticket->_Set(
+      Field => 'FinalPriority',
+      Value => $new_queue->FinalPriority,
+      RecordTransaction => 0,
+    );
+    if (! $val) {
+        $RT::Logger->error( "Couldn't set new final priority: $msg" );
+        return (0, $msg);
+    }
+    my $Due = new RT::Date( $ticket->CurrentUser );
+    if ( my $due_in = $new_queue->DefaultDueIn ) {
+        $Due->SetToNow;
+        $Due->AddDays( $due_in );
+    }
+    ( $val, $msg ) = $ticket->_Set(
+      Field => 'Due',
+      Value => $Due->ISO,
+      RecordTransaction => 0,
+    );
+    if (! $val) {
+        $RT::Logger->error( "Couldn't set new due date: $msg" );
+        return (0, $msg);
+    }
+    return 1;
+}
+
+1;
+
+=head1 AUTHOR
+
+Mark Wells E<lt>mark@freeside.bizE<gt>
+
+Based on in part LinearEscalate by Kevin Riggle E<lt>kevinr@bestpractical.comE<gt>
+and Ruslan Zakirov E<lt>ruz@bestpractical.comE<gt> .
+
+=cut
diff --git a/rt/lib/RT/Action/SetPriority_Local.pm b/rt/lib/RT/Action/SetPriority_Local.pm
new file mode 100644 (file)
index 0000000..efaadc9
--- /dev/null
@@ -0,0 +1,47 @@
+package RT::Action::SetPriority;
+use strict;
+no warnings 'redefine';
+
+# Extension to allow relative priority changes:
+# if Argument is "R" followed by a value, it's 
+# relative to current priority.
+sub Commit {
+    my $self = shift;
+    my ($rel, $val);
+    my $arg = $self->Argument;
+    if ( $arg ) {
+      ($rel, $val) = ( $arg =~ /^(r?)(-?\d+)$/i );
+      if (!length($val)) {
+        warn "Bad argument to SetPriority: '$arg'\n";
+        return 0;
+      }
+    }
+    else {
+      my %Rules = $self->Rules;
+      $rel = length($Rules{'inc'}) ? 1 : 0;
+      $val = $Rules{'inc'} || $Rules{'set'};
+      if ($val !~ /^[+-]?\d+$/) {
+        warn "Bad argument to SetPriority: '$val'\n";
+        return 0;
+      }
+    }
+    $val += $self->TicketObj->Priority if $rel;
+    $self->TicketObj->SetPriority($val);
+}
+
+sub Options {
+  (
+    {
+      'name'    => 'set',
+      'label'   => 'Set to value',
+      'type'    => 'text',
+    },
+    {
+      'name'    => 'inc',
+      'label'   => 'Increment by',
+      'type'    => 'text',
+    },
+  )
+}
+
+1;
diff --git a/rt/lib/RT/CustomFieldValues/Queues.pm b/rt/lib/RT/CustomFieldValues/Queues.pm
new file mode 100644 (file)
index 0000000..59529b6
--- /dev/null
@@ -0,0 +1,30 @@
+package RT::CustomFieldValues::Queues;
+
+use strict;
+use warnings;
+
+use base qw(RT::CustomFieldValues::External);
+
+sub SourceDescription {
+    return 'RT ticket queues';
+}
+
+sub ExternalValues {
+    my $self = shift;
+
+    my @res;
+    my $i = 0;
+    my $queues = RT::Queues->new( $self->CurrentUser );
+    $queues->UnLimit;
+    $queues->OrderByCols( { FIELD => 'Name' } );
+    while( my $queue = $queues->Next ) {
+        push @res, {
+            name        => $queue->Name,
+            description => $queue->Description,
+            sortorder   => $i++,
+        };
+    }
+    return \@res;
+}
+
+1;