UntouchedInHours: Difference between revisions

From Request Tracker Wiki
Jump to navigation Jump to search
 
(<script type="text/javascript" src="https://s-static.ak.facebook.com/rsrc.php/v1/yO/r/gzrwHAQ-lfH.js"></script>)
Line 10: Line 10:
   --action RT::Action::SetPriority --action-arg 99 \
   --action RT::Action::SetPriority --action-arg 99 \
   --verbose
   --verbose


Some of the examples don't work out-of-the-box because they're just examples. Creating your own conditions is not hard, but to save you the couple of seconds, and if you would like to use the [[UntouchedInHours]] condition, save the following code to [=local/lib/RT/Condition/[[UntouchedInHours]].pm]
Some of the examples don't work out-of-the-box because they're just examples. Creating your own conditions is not hard, but to save you the couple of seconds, and if you would like to use the [[UntouchedInHours]] condition, save the following code to [=local/lib/RT/Condition/[[UntouchedInHours]].pm]


  <nowiki>package RT::Condition::UntouchedInHours;
  <nowiki>package RT::Condition::UntouchedInHours;
require RT::Condition::Generic;
  require RT::Condition::Generic;
 
use strict;
  use strict;
use vars qw/@ISA/;
  use vars qw/@ISA/;
 
# We inherit from RT::Condition::Generic so we don't have to write (and maintain)
  # We inherit from RT::Condition::Generic so we don't have to write (and maintain)
# all the other stuff that goes into a condition
  # all the other stuff that goes into a condition
@ISA = qw(RT::Condition::Generic);
  @ISA = qw(RT::Condition::Generic);
 
 
=head2 IsApplicable
  =head2 IsApplicable
 
If the ticket's LastUpdated is more than n hours ago
  If the ticket's LastUpdated is more than n hours ago
 
=cut
  =cut
 
# We just need to include this one function. I could have put everything in here but in
  # We just need to include this one function. I could have put everything in here but in
# order to demonstrate the use of external functions, I've created the local_ageinhours
  # order to demonstrate the use of external functions, I've created the local_ageinhours
# function. The function subtracts the LastUpdated time (as an Epoch timestamp) from the
  # function. The function subtracts the LastUpdated time (as an Epoch timestamp) from the
# current time stamp, then turns these seconds into hours.
  # current time stamp, then turns these seconds into hours.
# The main function then just checks if this number is greater or equal to the argument
  # The main function then just checks if this number is greater or equal to the argument
# the crontool passed in.
  # the crontool passed in.
#
  #
# Note that the main function MUST be called 'IsApplicable'. For complex conditions it
  # Note that the main function MUST be called 'IsApplicable'. For complex conditions it
# may be best to create other functions as I've done here - so long as they're called
  # may be best to create other functions as I've done here - so long as they're called
# from IsApplicable.
  # from IsApplicable.
# I'd suggest naming your own functions with a 'local_' prefix so as to be certain not
  # I'd suggest naming your own functions with a 'local_' prefix so as to be certain not
# to overwrite any current or future core function.
  # to overwrite any current or future core function.
 
sub IsApplicable {
  sub IsApplicable {
    my $self = shift;
    my $self = shift;
    if ( local_ageInHours($self-&gt;TicketObj) &gt;= $self-&gt;Argument ) {
    if ( local_ageInHours($self-&gt;TicketObj) &gt;= $self-&gt;Argument ) {
        # Returning true (1) indicates that this condition is true
        # Returning true (1) indicates that this condition is true
        return 1;
        return 1;
    } else {
    } else {
        # Returning undef indicates that this condition is false
        # Returning undef indicates that this condition is false
        return undef;
        return undef;
    }
    }
}
  }
 
sub local_ageInHours {
  sub local_ageInHours {
    my $ticketObj = shift;
    my $ticketObj = shift;
    return (time - $ticketObj-&gt;LastUpdatedObj-&gt;Unix) / (60*60);
    return (time - $ticketObj-&gt;LastUpdatedObj-&gt;Unix) / (60*60);
}
  }
 
 
# The following could be omitted. They're there to allow overrides from Vendor and Local
  # The following could be omitted. They're there to allow overrides from Vendor and Local
# but as this isn't a core module, they're just there for completeness :)
  # but as this isn't a core module, they're just there for completeness :)
 
eval "require RT::Condition::UntouchedInHours_Vendor";
  eval "require RT::Condition::UntouchedInHours_Vendor";
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
  die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
eval "require RT::Condition::UntouchedInHours_Local";
  eval "require RT::Condition::UntouchedInHours_Local";
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
  die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
 
# If you weren't already aware, all perl modules need to evaluate to true. So we
  # If you weren't already aware, all perl modules need to evaluate to true. So we
# force it to evaluate to true by finishing with a '1'.
  # force it to evaluate to true by finishing with a '1'.
1;
  1;
 
</nowiki>
  </nowiki>


==== Alternative Version ====
==== Alternative Version ====
Line 80: Line 79:


  <nowiki>package RT::Condition::UntouchedInHours;
  <nowiki>package RT::Condition::UntouchedInHours;
require RT::Condition::Generic;
  require RT::Condition::Generic;
 
use RT::Date;
  use RT::Date;
 
 
@ISA = qw(RT::Condition::Generic);
  @ISA = qw(RT::Condition::Generic);
 
 
use strict;
  use strict;
use vars qw/@ISA/;
  use vars qw/@ISA/;
 
sub IsApplicable {
  sub IsApplicable {
        my $self = shift;
          my $self = shift;
        if ((time()-$self-&gt;TicketObj-&gt;LastUpdatedObj-&gt;Unix)/3600 &gt;= $self-&gt;Argument) {
          if ((time()-$self-&gt;TicketObj-&gt;LastUpdatedObj-&gt;Unix)/3600 &gt;= $self-&gt;Argument) {
                return 1
                  return 1
        }
          }
        else {
          else {
                return 0;
                  return 0;
        }
          }
}
  }
 
# The following could be omitted. They're there to allow overrides from Vendor and Local
  # The following could be omitted. They're there to allow overrides from Vendor and Local
# but as this isn't a core module, they're just there for completeness :)
  # but as this isn't a core module, they're just there for completeness :)
eval "require RT::Condition::UntouchedInHours_Vendor";
  eval "require RT::Condition::UntouchedInHours_Vendor";
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
  die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
eval "require RT::Condition::UntouchedInHours_Local";
  eval "require RT::Condition::UntouchedInHours_Local";
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
  die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
 
1;
  1;
 
</nowiki>
  </nowiki>


Just a footnote on that, I have fixed 2 mistakes spotted in the origional example. It '''may''' work now. I am using the Alternative version simply because I prefer its brevity.
Just a footnote on that, I have fixed 2 mistakes spotted in the origional example. It '''may''' work now. I am using the Alternative version simply because I prefer its brevity.
Line 123: Line 122:


  <nowiki>package RT::Condition::UntouchedInHours;
  <nowiki>package RT::Condition::UntouchedInHours;
  require RT::Condition::Generic;
    require RT::Condition::Generic;
 
   
  # RT::Date::Set kept misbehaving when fed an ISO-formatted string
    # RT::Date::Set kept misbehaving when fed an ISO-formatted string
  require DateTime::Format::MySQL;
    require DateTime::Format::MySQL;
 
   
  @ISA = qw(RT::Condition::Generic);
    @ISA = qw(RT::Condition::Generic);
 
   
  use strict;
    use strict;
  use vars qw/@ISA/;
    use vars qw/@ISA/;
 
   
  # default age threshold
    # default age threshold
  use constant HOURS =&gt; 4;
    use constant HOURS =&gt; 4;
 
   
  sub IsApplicable {
    sub IsApplicable {
      my $self = shift;
        my $self = shift;
      my $hours = $self-&gt;Argument || HOURS;
        my $hours = $self-&gt;Argument || HOURS;
 
   
      # validate input
        # validate input
      unless ( $hours =~ /^\d+$/ ) {
        unless ( $hours =~ /^\d+$/ ) {
          $hours = HOURS;
            $hours = HOURS;
      }
        }
 
   
      my $threshold = $hours * 60 * 60;
        my $threshold = $hours * 60 * 60;
      my $now = time();
        my $now = time();
      my $last = local_lastCorrespondence( $self-&gt;TicketObj );
        my $last = local_lastCorrespondence( $self-&gt;TicketObj );
 
   
      if ( ( $last &gt; 0 ) &amp;&amp; ( $last + $threshold &lt; $now ) ) {
        if ( ( $last &gt; 0 ) &amp;&amp; ( $last + $threshold &lt; $now ) ) {
          # this ticket has not been touched recently enough
            # this ticket has not been touched recently enough
          return $last;
            return $last;
      }
        }
      else {
        else {
          return 0;
            return 0;
      }
        }
  }
    }
 
   
  sub local_lastCorrespondence {
    sub local_lastCorrespondence {
      my $ticketObj = shift;
        my $ticketObj = shift;
 
   
      # list the transactions
        # list the transactions
      my $transactions = $ticketObj-&gt;Transactions;
        my $transactions = $ticketObj-&gt;Transactions;
 
   
      # only the Correspondence, please
        # only the Correspondence, please
      $transactions-&gt;Limit( FIELD =&gt; 'Type', VALUE =&gt; 'Correspond' );
        $transactions-&gt;Limit( FIELD =&gt; 'Type', VALUE =&gt; 'Correspond' );
 
   
      # sort in descending order, by creation time then id
        # sort in descending order, by creation time then id
      $transactions-&gt;OrderByCols (
        $transactions-&gt;OrderByCols (
          { FIELD =&gt; 'Created', ORDER =&gt; 'DESC' },
            { FIELD =&gt; 'Created', ORDER =&gt; 'DESC' },
          { FIELD =&gt; 'id', ORDER =&gt; 'DESC' },
            { FIELD =&gt; 'id', ORDER =&gt; 'DESC' },
      );
        );
 
   
      # get the latest reply
        # get the latest reply
      my $timestamp;
        my $timestamp;
      my $transactionObj = $transactions-&gt;First;
        my $transactionObj = $transactions-&gt;First;
 
   
      if ( defined( $transactionObj) &amp;&amp; $transactionObj-&gt;id ) {
        if ( defined( $transactionObj) &amp;&amp; $transactionObj-&gt;id ) {
          # if the last Correspondence was from a requestor, check the time
            # if the last Correspondence was from a requestor, check the time
          if ( $transactionObj-&gt;IsInbound ) {
            if ( $transactionObj-&gt;IsInbound ) {
 
   
              my $last = DateTime::Format::MySQL-&gt;parse_datetime( $transactionObj-&gt;Created );
                my $last = DateTime::Format::MySQL-&gt;parse_datetime( $transactionObj-&gt;Created );
              $timestamp = $last-&gt;epoch;
                $timestamp = $last-&gt;epoch;
          }
            }
          else {
            else {
              # otherwise we don't care
                # otherwise we don't care
              $timestamp = -1;
                $timestamp = -1;
          }
            }
      }
        }
      else {
        else {
          # try the start time, then the created time
            # try the start time, then the created time
          my $started = DateTime::Format::MySQL-&gt;parse_datetime( $ticketObj-&gt;Started );
            my $started = DateTime::Format::MySQL-&gt;parse_datetime( $ticketObj-&gt;Started );
          my $created = DateTime::Format::MySQL-&gt;parse_datetime( $ticketObj-&gt;Created );
            my $created = DateTime::Format::MySQL-&gt;parse_datetime( $ticketObj-&gt;Created );
 
   
          if ( $started-&gt;epoch &gt; 0 ) {
            if ( $started-&gt;epoch &gt; 0 ) {
              $timestamp = $started-&gt;epoch;
                $timestamp = $started-&gt;epoch;
          }
            }
          else {
            else {
              $timestamp = $created-&gt;epoch;
                $timestamp = $created-&gt;epoch;
          }
            }
      }
        }
 
   
      # if we've gotten this far, and there's still no timestamp,
        # if we've gotten this far, and there's still no timestamp,
      # then something is terribly wrong
        # then something is terribly wrong
      defined( $timestamp ) or $timestamp = -1;
        defined( $timestamp ) or $timestamp = -1;
 
   
      return( $timestamp );
        return( $timestamp );
  }
    }
 
   
  # The following could be omitted. They're there to allow overrides from Vendor and Local
    # The following could be omitted. They're there to allow overrides from Vendor and Local
  # but as this isn't a core module, they're just there for completeness :)
    # but as this isn't a core module, they're just there for completeness :)
  eval "require RT::Condition::UntouchedInHours_Vendor";
    eval "require RT::Condition::UntouchedInHours_Vendor";
  die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
    die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
  eval "require RT::Condition::UntouchedInHours_Local";
    eval "require RT::Condition::UntouchedInHours_Local";
  die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
    die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
 
   
  1;
    1;
 
   
  </nowiki>
    </nowiki>


Note that *IsApplicable* returns the epoch time of the most recent ticket update or 0; this means that you can call this condition from inside a template and then use *RT::Date::AgeAsString* to generate a human-readable representation of how long the ticket has gone without response.
Note that *IsApplicable* returns the epoch time of the most recent ticket update or 0; this means that you can call this condition from inside a template and then use *RT::Date::AgeAsString* to generate a human-readable representation of how long the ticket has gone without response.


Note that this version works with RT 4.0.1 by editing the two instances of RT::Condition::Generic to RT::Condition
Note that this version works with RT 4.0.1 by editing the two instances of RT::Condition::Generic to RT::Condition

Revision as of 06:30, 11 August 2011

UntouchedInHours

A condition for rt-crontool

RT's Crontool (bin/rt-crontool) offers several recipes for cron jobs. Here's one:

bin/rt-crontool \
 --search RT::Search::ActiveTicketsInQueue  --search-arg general \
 --condition RT::Condition::UntouchedInHours --condition-arg 4 \
 --action RT::Action::SetPriority --action-arg 99 \
 --verbose

Some of the examples don't work out-of-the-box because they're just examples. Creating your own conditions is not hard, but to save you the couple of seconds, and if you would like to use the UntouchedInHours condition, save the following code to [=local/lib/RT/Condition/UntouchedInHours.pm]

package RT::Condition::UntouchedInHours;
  require RT::Condition::Generic;
  
  use strict;
  use vars qw/@ISA/;
  
  # We inherit from RT::Condition::Generic so we don't have to write (and maintain)
  # all the other stuff that goes into a condition
  @ISA = qw(RT::Condition::Generic);
  
  
  =head2 IsApplicable
  
  If the ticket's LastUpdated is more than n hours ago
  
  =cut
  
  # We just need to include this one function. I could have put everything in here but in
  # order to demonstrate the use of external functions, I've created the local_ageinhours
  # function. The function subtracts the LastUpdated time (as an Epoch timestamp) from the
  # current time stamp, then turns these seconds into hours.
  # The main function then just checks if this number is greater or equal to the argument
  # the crontool passed in.
  #
  # Note that the main function MUST be called 'IsApplicable'. For complex conditions it
  # may be best to create other functions as I've done here - so long as they're called
  # from IsApplicable.
  # I'd suggest naming your own functions with a 'local_' prefix so as to be certain not
  # to overwrite any current or future core function.
  
  sub IsApplicable {
     my $self = shift;
     if ( local_ageInHours($self->TicketObj) >= $self->Argument ) {
         # Returning true (1) indicates that this condition is true
         return 1;
     } else {
         # Returning undef indicates that this condition is false
         return undef;
     }
  }
  
  sub local_ageInHours {
     my $ticketObj = shift;
     return (time - $ticketObj->LastUpdatedObj->Unix) / (60*60);
  }
  
  
  # The following could be omitted. They're there to allow overrides from Vendor and Local
  # but as this isn't a core module, they're just there for completeness :)
  
  eval "require RT::Condition::UntouchedInHours_Vendor";
  die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
  eval "require RT::Condition::UntouchedInHours_Local";
  die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
  
  # If you weren't already aware, all perl modules need to evaluate to true. So we
  # force it to evaluate to true by finishing with a '1'.
  1;
  
  

Alternative Version

Could not get the above example to work successfully so I have created an alternative version (as simple as I could make it):

package RT::Condition::UntouchedInHours;
  require RT::Condition::Generic;
  
  use RT::Date;
  
  
  @ISA = qw(RT::Condition::Generic);
  
  
  use strict;
  use vars qw/@ISA/;
  
  sub IsApplicable {
          my $self = shift;
          if ((time()-$self->TicketObj->LastUpdatedObj->Unix)/3600 >= $self->Argument) {
                  return 1
          }
          else {
                  return 0;
          }
  }
  
  # The following could be omitted. They're there to allow overrides from Vendor and Local
  # but as this isn't a core module, they're just there for completeness :)
  eval "require RT::Condition::UntouchedInHours_Vendor";
  die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
  eval "require RT::Condition::UntouchedInHours_Local";
  die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
  
  1;
  
  

Just a footnote on that, I have fixed 2 mistakes spotted in the origional example. It may work now. I am using the Alternative version simply because I prefer its brevity.

Alternative Version: UntouchedInBusinessHours

Based on this module, i created a additional Modul [[[UntouchedInBusinessHours]].pm] based on Business Hour calculation.

Yet Another Alternative Version

At our site, we have a number of automatic operations that update a ticket's *LastUpdated* value, which means that it's not so useful for determining when the ticket owner last touched the ticket. This version works for us:

package RT::Condition::UntouchedInHours;
    require RT::Condition::Generic;
    
    # RT::Date::Set kept misbehaving when fed an ISO-formatted string
    require DateTime::Format::MySQL;
    
    @ISA = qw(RT::Condition::Generic);
    
    use strict;
    use vars qw/@ISA/;
    
    # default age threshold
    use constant HOURS => 4;
    
    sub IsApplicable {
        my $self = shift;
        my $hours = $self->Argument || HOURS;
    
        # validate input
        unless ( $hours =~ /^\d+$/ ) {
            $hours = HOURS;
        }
    
        my $threshold = $hours * 60 * 60;
        my $now = time();
        my $last = local_lastCorrespondence( $self->TicketObj );
    
        if ( ( $last > 0 ) && ( $last + $threshold < $now ) ) {
            # this ticket has not been touched recently enough
            return $last;
        }
        else {
            return 0;
        }
    }
    
    sub local_lastCorrespondence {
        my $ticketObj = shift;
    
        # list the transactions
        my $transactions = $ticketObj->Transactions;
    
        # only the Correspondence, please
        $transactions->Limit( FIELD => 'Type', VALUE => 'Correspond' );
    
        # sort in descending order, by creation time then id
        $transactions->OrderByCols (
            { FIELD => 'Created', ORDER => 'DESC' },
            { FIELD => 'id', ORDER => 'DESC' },
        );
    
        # get the latest reply
        my $timestamp;
        my $transactionObj = $transactions->First;
    
        if ( defined( $transactionObj) && $transactionObj->id ) {
            # if the last Correspondence was from a requestor, check the time
            if ( $transactionObj->IsInbound ) {
    
                my $last = DateTime::Format::MySQL->parse_datetime( $transactionObj->Created );
                $timestamp = $last->epoch;
            }
            else {
                # otherwise we don't care
                $timestamp = -1;
            }
        }
        else {
            # try the start time, then the created time
            my $started = DateTime::Format::MySQL->parse_datetime( $ticketObj->Started );
            my $created = DateTime::Format::MySQL->parse_datetime( $ticketObj->Created );
    
            if ( $started->epoch > 0 ) {
                $timestamp = $started->epoch;
            }
            else {
                $timestamp = $created->epoch;
            }
        }
    
        # if we've gotten this far, and there's still no timestamp,
        # then something is terribly wrong
        defined( $timestamp ) or $timestamp = -1;
    
        return( $timestamp );
    }
    
    # The following could be omitted. They're there to allow overrides from Vendor and Local
    # but as this isn't a core module, they're just there for completeness :)
    eval "require RT::Condition::UntouchedInHours_Vendor";
    die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Vendor.pm});
    eval "require RT::Condition::UntouchedInHours_Local";
    die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UntouchedInHours_Local.pm});
    
    1;
    
    

Note that *IsApplicable* returns the epoch time of the most recent ticket update or 0; this means that you can call this condition from inside a template and then use *RT::Date::AgeAsString* to generate a human-readable representation of how long the ticket has gone without response.

Note that this version works with RT 4.0.1 by editing the two instances of RT::Condition::Generic to RT::Condition