[Bps-public-commit] rt-extension-sla-utils branch, master, created. 784836ee0f0555601d4b874b5b2fb7dcc868b739
Dave Goehrig
dave at bestpractical.com
Thu Mar 16 13:54:27 EDT 2017
The branch, master has been created
at 784836ee0f0555601d4b874b5b2fb7dcc868b739 (commit)
- Log -----------------------------------------------------------------
commit 784836ee0f0555601d4b874b5b2fb7dcc868b739
Author: Dave Goehrig <dave at bestpractical.com>
Date: Thu Mar 16 13:53:24 2017 -0400
RT extension for sla overdue checking
This is an RT extension that updates SLA based on
a customfield and can update a SLA Overdue custom field
based on a periodic check.
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..c9e879f
--- /dev/null
+++ b/Changes
@@ -0,0 +1,4 @@
+Revision history for RT-Extension-SLA-Utils
+
+0.01 [Release Date]
+ - Initial version
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..430b493
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,32 @@
+use inc::Module::Install;
+
+RTx 'RT-Extension-SLA-Utils';
+license 'gplv2';
+repository 'https://github.com/bestpractical/rt-extension-sla-utils';
+
+requires_rt '4.0.0';
+
+use Config;
+
+
+my ($lp) = ($INC{'RT.pm'} =~ /^(.*)[\\\/]/);
+my $lib_path = join( ' ', "$RT::LocalPath/lib", $lp );
+my $secure_perl_path = $Config{perlpath};
+if ($^O ne 'VMS') {
+ $secure_perl_path .= $Config{_exe}
+ unless $secure_perl_path =~ m/$Config{_exe}$/i;
+}
+
+substitute(
+ {
+ RT_LIB_PATH => $lib_path,
+ PERL => $ENV{PERL} || $secure_perl_path,
+ },
+ {
+ sufix => '.in'
+ },
+ qw(lib/RT/Extension/SLA/Utils/Test.pm),
+);
+
+sign;
+WriteAll;
diff --git a/etc/initialdata b/etc/initialdata
new file mode 100644
index 0000000..b763c83
--- /dev/null
+++ b/etc/initialdata
@@ -0,0 +1,85 @@
+
+our @CustomFields = (
+ {
+ Name => 'Resolution Due',
+ Description => 'SLA final resolution date',
+ Type => 'DateTime',
+ LookupType => 'RT::Queue-RT::Ticket',
+ Disabled => 0,
+ MaxValues => 1,
+ },
+ {
+ Name => 'SLA Overdue',
+ Description => 'This field is set upon first violation of an SLA due date',
+ Type => 'Freeform',
+ LookupType => 'RT::Queue-RT::Ticket',
+ Disabled => 0,
+ MaxValues => 1,
+ },
+);
+
+our @ScripConditions = (
+ {
+ Name => 'Require Due set according to SLA',
+ Description => 'Detect a situation where we should set a Due date.',
+ ApplicableTransTypes => 'Create,Correspond,Set,Status,OwnerChange',
+ ExecModule => 'SLA::RequireDueSet',
+ },
+ {
+ Name => 'On SLA Custom Field Change',
+ Description => 'The SLA Custom Field has changed',
+ ApplicableTransTypes => 'CustomField',
+ ExecModule => 'SLA::CustomFieldChanged',
+ },
+);
+
+our @ScripActions = (
+ {
+ Name => 'Set Due date according to SLA',
+ Description => 'Set the due date according to an agreement.',
+ ExecModule => 'SLA::SetDue',
+ },
+ {
+ Name => 'Set SLA Overdue',
+ Description => 'Set SLA Overdue custom field',
+ ExecModule => 'SLA::SetOverdue',
+ },
+ {
+ Name => 'Set SLA Based on a Custom Field',
+ Description => 'Set the SLA based on the custom field value',
+ ExecModule => 'SLA::SetFromCustomField',
+ },
+);
+
+our @Scrips = (
+ {
+ Description => 'Set due date if needed according to SLA',
+ ScripCondition => 'Require Due set according to SLA',
+ ScripAction => 'Set Due date according to SLA',
+ Template => 'Blank',
+ },
+ {
+ Description => 'On Impact Change, Update SLA',
+ ScripCondition => 'On SLA Custom Field Change',
+ ScripAction => 'Set SLA Based on a Custom Field',
+ Template => 'Blank',
+ },
+);
+
+our @Templates = (
+ {
+ Queue => 0,
+ Name => '2-Hour Ticket Due Notice',
+ Description => 'Notice sent via ticket comment two hours before Due date.',
+ Content => 'Subject: Two-Hour Due Notification for {$Ticket->Subject}
+
+
+This message was automatically generated as notice that a ticket is Due
+within the next two hours:
+ Subject: {$Ticket->Subject}
+ Queue: {$Ticket->Queue}
+ ID: {$Ticket->id}
+ Due: {$Ticket->Due}
+',
+ },
+);
diff --git a/lib/RT/Action/SLA/SetDue.pm b/lib/RT/Action/SLA/SetDue.pm
new file mode 100644
index 0000000..502769e
--- /dev/null
+++ b/lib/RT/Action/SLA/SetDue.pm
@@ -0,0 +1,217 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Action::SLA::SetDue;
+
+use base qw(RT::Action::SLA);
+
+=head2 Prepare
+
+Checks if the ticket has service level defined.
+
+=cut
+
+sub Prepare {
+ my $self = shift;
+
+ unless ( $self->TicketObj->SLA ) {
+ $RT::Logger->error('SLA::SetDue scrip has been applied to ticket #'
+ . $self->TicketObj->id . ' that has no SLA defined');
+ return 0;
+ }
+
+ return 1;
+}
+
+=head2 Commit
+
+Set the Due date accordingly to SLA.
+
+=cut
+
+sub Commit {
+ my $self = shift;
+
+ my $ticket = $self->TicketObj;
+ my $txn = $self->TransactionObj;
+ my $level = $ticket->SLA;
+
+ my ($last_reply, $is_outside) = $self->LastEffectiveAct;
+ $RT::Logger->debug(
+ 'Last effective '. ($is_outside? '':'non-') .'outside actors\' reply'
+ .' to ticket #'. $ticket->id .' is txn #'. $last_reply->id
+ );
+
+ my $initial_due = $self->Due(
+ Ticket => $ticket,
+ Level => $level,
+ Type => 'Initial',
+ Time => $ticket->CreatedObj->Unix,
+ );
+
+ my $response_due;
+ if ( $self->NeedsFirstResponse ){
+ $response_due = $self->Due(
+ Ticket => $ticket,
+ Level => $level,
+ Type => 'FirstResponse',
+ Time => $last_reply->CreatedObj->Unix,
+ );
+ RT::Logger->debug("Setting to FirstResponse");
+ }
+ else{
+ $response_due = $self->Due(
+ Ticket => $ticket,
+ Level => $level,
+ Type => 'KeepInLoop',
+ Time => $last_reply->CreatedObj->Unix,
+ );
+ }
+
+ my $resolve_due = $self->Due(
+ Ticket => $ticket,
+ Level => $level,
+ Type => 'Resolve',
+ Time => $ticket->CreatedObj->Unix,
+ );
+
+ my $txns = $ticket->Transactions;
+ $txns->Limit( FIELD => 'Type', VALUE => 'Set' );
+ $txns->Limit( FIELD => 'Field', VALUE => 'Owner' );
+ my $has_initial_assignment = $txns->Count;
+ my $is_subsequent_assignment = $has_initial_assignment - 1;
+ return if $is_subsequent_assignment and $self->TransactionObj->Type eq 'Set' and $self->TransactionObj->Field eq 'Owner';
+
+ my $due;
+ $due = $initial_due unless $has_initial_assignment;
+ $due = $response_due if $has_initial_assignment and defined $response_due;
+ $due = $resolve_due unless defined $due;
+ $due = $resolve_due if defined $due && defined $resolve_due && $resolve_due < $due;
+
+ my $resolve_date = RT::Date->new( $self->CurrentUser );
+ $resolve_date->Set('Format'=>'unix','Value'=>$resolve_due);
+ $ticket->AddCustomFieldValue(
+ Field => 'Resolution Due',
+ Value => $resolve_date->ISO(Timezone => 'user'),
+ );
+
+ return $self->SetDateField( Due => $due );
+}
+
+sub IsOutsideActor {
+ my $self = shift;
+ my $txn = shift || $self->TransactionObj;
+
+ my $actor = $txn->CreatorObj->PrincipalObj;
+
+ # owner is always treated as inside actor
+ return 0 if $actor->id == $self->TicketObj->Owner;
+
+ if ( $RT::ServiceAgreements{'AssumeOutsideActor'} ) {
+ # All non-admincc users are outside actors
+ return 0 if $self->TicketObj ->AdminCc->HasMemberRecursively( $actor )
+ or $self->TicketObj->QueueObj->AdminCc->HasMemberRecursively( $actor );
+
+ return 1;
+ } else {
+ # Only requestors are outside actors
+ return 1 if $self->TicketObj->Requestors->HasMemberRecursively( $actor );
+ return 0;
+ }
+}
+
+sub LastEffectiveAct {
+ my $self = shift;
+
+ my $txns = $self->TicketObj->Transactions;
+ $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' );
+ $txns->Limit( FIELD => 'Type', VALUE => 'Create' );
+ $txns->OrderByCols(
+ { FIELD => 'Created', ORDER => 'DESC' },
+ { FIELD => 'id', ORDER => 'DESC' },
+ );
+
+ my $res;
+ while ( my $txn = $txns->Next ) {
+ unless ( $self->IsOutsideActor( $txn ) ) {
+ last if $res;
+ return ($txn);
+ }
+ $res = $txn;
+ }
+ return ($res, 1);
+}
+
+# Determine if we have replied to the customer yet. If the ticket is
+# owned but there is no correspondence, then we need to reply.
+
+sub NeedsFirstResponse {
+ my $self = shift;
+
+ my $txns = $self->TicketObj->Transactions;
+ # Look for corresponds
+ $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' );
+
+ # Remove any requestor correspondence
+ my $requestors = $self->TicketObj->Requestors->UserMembersObj; # Returns RT group object
+
+ while ( my $requestor = $requestors->Next ){
+ $txns->Limit( FIELD => 'Creator', OPERATOR => '!=', VALUE => $requestor->Id, ENTRYAGGREGATOR => 'AND' );
+ }
+
+ # If we've replied before or this transaction is our reply, first response is covered
+ return 0 if $txns->Count
+ or ($self->TransactionObj->Type eq 'Correspond'
+ and $self->TransactionObj->Creator eq $self->TicketObj->OwnerObj->Id);
+ return 1;
+}
+
+1;
diff --git a/lib/RT/Action/SLA/SetFromCustomField.pm b/lib/RT/Action/SLA/SetFromCustomField.pm
new file mode 100644
index 0000000..75fdc2d
--- /dev/null
+++ b/lib/RT/Action/SLA/SetFromCustomField.pm
@@ -0,0 +1,80 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Action::SLA::SetFromCustomField;
+
+use base qw(RT::Action);
+
+=head2 Prepare
+
+=cut
+
+sub Prepare {
+ return 1;
+}
+
+=head2 Commit
+
+Set SLA based on CF.
+
+=cut
+
+sub Commit {
+ my $self = shift;
+
+ my $ticket = $self->TicketObj;
+ my ($ret, $msg) = $ticket->SetSLA($self->TransactionObj->NewValue);
+ RT::Logger->error("Unable to update SLA: $msg") unless $ret;
+ return 1;
+
+}
+
+1;
diff --git a/lib/RT/Action/SLA/SetOverdue.pm b/lib/RT/Action/SLA/SetOverdue.pm
new file mode 100644
index 0000000..2c3c20a
--- /dev/null
+++ b/lib/RT/Action/SLA/SetOverdue.pm
@@ -0,0 +1,98 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Action::SLA::SetOverdue;
+
+use base qw(RT::Action::SLA);
+
+use RT::Condition::Overdue;
+
+=head2 Prepare
+
+Checks if the ticket has service level defined.
+
+=cut
+
+sub Prepare {
+ my $self = shift;
+
+ unless ( $self->TicketObj->SLA ) {
+ $RT::Logger->error('SLA::SetOverdue scrip has been applied to ticket #'
+ . $self->TicketObj->id . ' that has no SLA defined');
+ return 0;
+ }
+
+ return 1;
+}
+
+=head2 Commit
+
+Set Overdue custom field according to SLA.
+
+=cut
+
+sub Commit {
+ my $self = shift;
+
+ my $ticket = $self->TicketObj;
+ my $txn = $self->TransactionObj;
+ my $level = $ticket->SLA;
+
+ my $overdue = RT::Condition::Overdue->new(TicketObj => $ticket);
+ return 0 unless $overdue->IsApplicable;
+
+ $ticket->AddCustomFieldValue(
+ Field => 'SLA Overdue',
+ Value => 1,
+ );
+}
+
+1;
diff --git a/lib/RT/Condition/SLA/CustomFieldChanged.pm b/lib/RT/Condition/SLA/CustomFieldChanged.pm
new file mode 100644
index 0000000..2237bf7
--- /dev/null
+++ b/lib/RT/Condition/SLA/CustomFieldChanged.pm
@@ -0,0 +1,88 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Condition::SLA::CustomFieldChanged;
+
+use base qw(RT::Condition);
+
+=head1 NAME
+
+RT::Condition::SLA::CustomFieldChanged - Did the CF change?
+
+=head1 DESCRIPTION
+
+Checks if the configured CF has changed.
+
+To set the desired Custom Field for triggering the SLA change
+in your RT_SiteConfig.pm add the line:
+
+ Set($SLACustomField, 'your field name here');
+
+Then whenever this field is changed, this condition will be met.
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ my $txn = $self->TransactionObj;
+
+ return 0 unless $txn->Type eq 'CustomField';
+
+ my $cf = RT::CustomField->new(RT->SystemUser);
+ my ($ret, $msg) = $cf->Load($txn->Field);
+ RT::Logger->error("Unable to load CF for id: "
+ . $txn->Field . " Error: $msg" ) unless $ret;
+
+ return 1 if $cf->Id and $cf->Name eq RT->Config->Get('SLACustomField');
+ return 0;
+}
+
+1;
diff --git a/lib/RT/Condition/SLA/Overdue.pm b/lib/RT/Condition/SLA/Overdue.pm
new file mode 100644
index 0000000..20dd203
--- /dev/null
+++ b/lib/RT/Condition/SLA/Overdue.pm
@@ -0,0 +1,88 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Condition::SLA::Overdue
+
+=head1 DESCRIPTION
+
+Returns true if the ticket we're operating on is overdue, and if
+the C<SLA Overdue> custom field is not yet set. Used by rt-crontool
+to help set the SLA Overdue field.
+
+=cut
+
+package RT::Condition::SLA::Overdue;
+use base 'RT::Condition';
+use strict;
+use warnings;
+
+use RT::CustomField;
+
+=head2 IsApplicable
+
+If the due date is before "now" and C<SLA Overdue> CF not set, return true
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+
+ my $due = $self->TicketObj->DueObj;
+ my $overdue_cf = $self->TicketObj->FirstCustomFieldValue('SLA Overdue');
+
+ return undef unless $due->IsSet and $due->Unix < time();
+ return undef if defined $overdue_cf and $overdue_cf > 0;
+ return 1;
+}
+
+RT::Base->_ImportOverlays();
+
+1;
+
diff --git a/lib/RT/Condition/SLA/RequireDueSet.pm b/lib/RT/Condition/SLA/RequireDueSet.pm
new file mode 100644
index 0000000..8d70f15
--- /dev/null
+++ b/lib/RT/Condition/SLA/RequireDueSet.pm
@@ -0,0 +1,96 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Condition::SLA::RequireDueSet;
+
+use base qw(RT::Condition::SLA);
+
+=head1 NAME
+
+RT::Condition::SLA::RequireDueSet - checks if Due date require update
+
+=head1 DESCRIPTION
+
+Checks if Due date require update. This should be done when we create
+a ticket and it has service level value or when we set service level.
+
+You can ignore specific service levels by adding a
+
+ Set($SLAIgnoreLevels, ... );
+
+which will exclude these SLA levels from requiring a due date set.
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ return 0 unless $self->SLAIsApplied;
+ return 0 if $self->TicketObj->QueueObj->SLADisabled;
+
+ # This SLA level has no actual SLA times attached
+ return 0 if scalar( grep { $_ eq $self->TicketObj->SLA } @{RT->Config->Get(SLAIgnoreLevels)};
+
+ my $type = $self->TransactionObj->Type;
+ if ( $type eq 'Create' || $type eq 'Correspond' || ($type eq 'Set' && $self->TransactionObj->Field eq 'Owner') ) {
+ return 1 if $self->TicketObj->SLA;
+ return 0;
+ }
+ elsif ( $type eq 'Status' || ($type eq 'Set' && $self->TransactionObj->Field eq 'Status') ) {
+ return 1 if $self->TicketObj->SLA;
+ return 0;
+ }
+ elsif ( $type eq 'SLA' || ($type eq 'Set' && $self->TransactionObj->Field eq 'SLA') ) {
+ return 1;
+ }
+ return 0;
+}
+
+1;
diff --git a/lib/RT/Extension/SLA/Utils.pm b/lib/RT/Extension/SLA/Utils.pm
new file mode 100644
index 0000000..a3a6f9f
--- /dev/null
+++ b/lib/RT/Extension/SLA/Utils.pm
@@ -0,0 +1,101 @@
+use strict;
+use warnings;
+package RT::Extension::SLA::Utils;
+
+our $VERSION = '0.01';
+
+=head1 NAME
+
+RT-Extension-SLA-Utils - additional SLA utilities
+
+=head1 DESCRIPTION
+
+The RT::Extension::SLA::Utils plugin adds additional
+scrip actions and conditions to trigger SLA changes
+off of a Custom Field change, and support tracking
+which tickets SLAs are overdue.
+
+
+=head1 RT VERSION
+
+Works with RT 4.4.0 and newer.
+
+=head1 INSTALLATION
+
+=over
+
+=item C<perl Makefile.PL>
+
+=item C<make>
+
+=item C<make install>
+
+=item C<make initdb>
+
+May need root permissions
+
+=item Edit your F</opt/rt4/etc/RT_SiteConfig.pm>
+
+If you are using RT 4.2 or greater, add this line:
+
+ Plugin('RT::Extension::SLA::Utils');
+
+or add C<RT::Extension::SLA::Utils> to your existing C<@Plugins> line.
+
+You willl also want to set the Custom Field to use to trigger SLA
+change conditions:
+
+ Set($SLACustomField, 'my field name');
+
+And if you want any SLA levels to not trigger changes:
+
+ Set($SLAIgnoreLevels, 'level 1', 'some other level');
+
+
+=item Setup rt-crontool to run overdue checks
+
+To have the B<SLA Overdue> field set automatically, you can have rt-crontool
+run the checks periodically to update the field. You can use rt-crontool with cron
+such as:
+
+ rt-crontool --search RT::Search::ActiveTicketsInQueue --search-arg <queue_name> \
+ --condition RT::Condition::SLA::Overdue --action RT::Action::SLA::SetOverdue
+
+Replace the <queue_name> with the name of the queue you would like the check run on,
+such as 'General'. If you want to run the checks on multiple queues, you will need
+a line for each queue.
+
+
+=item Clear your mason cache
+
+ rm -rf /opt/rt4/var/mason_data/obj
+
+=item Restart your webserver
+
+=back
+
+=head1 AUTHOR
+
+Best Practical Solutions, LLC E<lt>modules at bestpractical.comE<gt>
+
+=head1 BUGS
+
+All bugs should be reported via email to
+
+ L<bug-RT-Extension-SLA-Utils at rt.cpan.org|mailto:bug-RT-Extension-SLA-Utils at rt.cpan.org>
+
+or via the web at
+
+ L<rt.cpan.org|http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-SLA-Utils>.
+
+=head1 LICENSE AND COPYRIGHT
+
+This software is Copyright (c) 2017 by Dave Goehrig
+
+This is free software, licensed under:
+
+ The GNU General Public License, Version 2, June 1991
+
+=cut
+
+1;
diff --git a/lib/RT/Extension/SLA/Utils/Test.pm.in b/lib/RT/Extension/SLA/Utils/Test.pm.in
new file mode 100644
index 0000000..d38a4c7
--- /dev/null
+++ b/lib/RT/Extension/SLA/Utils/Test.pm.in
@@ -0,0 +1,119 @@
+use strict;
+use warnings;
+
+### after: use lib qw(@RT_LIB_PATH@);
+use lib qw(/opt/rt4/local/lib /opt/rt4/lib);
+
+package RT::Extension::SLA::Utils::Test;
+
+our @ISA;
+BEGIN {
+ local $@;
+ eval { require RT::Test; 1 } or do {
+ require Test::More;
+ Test::More::BAIL_OUT(
+ "requires 3.8 to run tests. Error:\n$@\n"
+ ."You may need to set PERL5LIB=/path/to/rt/lib"
+ );
+ };
+ push @ISA, 'RT::Test';
+}
+
+sub import {
+ my $class = shift;
+ my %args = @_;
+
+ $args{'requires'} ||= [];
+ if ( $args{'testing'} ) {
+ unshift @{ $args{'requires'} }, 'RT::Extension::SLA::Utils';
+ } else {
+ $args{'testing'} = 'RT::Extension::SLA::Utils';
+ }
+
+ $class->SUPER::import( %args );
+ $class->export_to_level(1);
+
+ require RT::Extension::SLA::Utils;
+}
+
+sub bootstrap_more_config{
+ my $self = shift;
+ my $config = shift;
+ my $args_ref = shift;
+
+ my $sla = <<"CONFIG";
+Set(%ServiceAgreements, (
+ Default => 'Informational',
+ QueueDefault => {
+ 'General' => 'Informational',
+ },
+ Levels => {
+ # The "Initial" period handled by RT::Action::SLA::SetDue
+ 'Critical' => {
+ Starts => { RealMinutes => 0 },
+ Initial => { RealMinutes => 10 }, # Initial Response
+ FirstResponse => { RealMinutes => 30 }, # First Response
+ KeepInLoop => { RealMinutes => 60*1 }, # Updates
+ Resolve => { RealMinutes => 60*4 }, # Resolution
+ },
+ 'Major' => {
+ Starts => { RealMinutes => 0 },
+ Initial => { RealMinutes => 15 }, # Initial Response
+ FirstResponse => { RealMinutes => 30 }, # First Response
+ KeepInLoop => { RealMinutes => 60*24 }, # Updates
+ Resolve => { RealMinutes => 60*48 }, # Resolution
+ },
+ 'Minor' => {
+ Starts => { RealMinutes => 0 },
+ Initial => { RealMinutes => 60 }, # Initial Response
+ FirstResponse => { RealMinutes => 60*2 }, # First Response
+ KeepInLoop => { RealMinutes => 60*24 }, # Updates
+ Resolve => { RealMinutes => 60*48 }, # Resolution
+ },
+ 'Informational' => {
+ Starts => { RealMinutes => 0 },
+ Initial => { RealMinutes => 60 }, # Initial Response
+ FirstResponse => { RealMinutes => 60*8 }, # First Response
+ KeepInLoop => { RealMinutes => 60*24 }, # Updates
+ Resolve => { RealMinutes => 60*72 }, # Resolution
+ },
+ 'Trivial' => {
+ # all ignored; this level is N/A via SLAIgnoreLevels configuration value
+ Starts => { RealMinutes => 0 },
+ Initial => { RealMinutes => 1 },
+ FirstResponse => { RealMinutes => 2 },
+ KeepInLoop => { RealMinutes => 3 },
+ Resolve => { RealMinutes => 4 },
+ }
+ },
+));
+Set(\$SLACustomField, 'Impact');
+Set(\$SLAIngnoreLevels, 'Trivial');
+CONFIG
+
+ print $config $sla;
+
+ # Create the Impact custom field we will use for testing per the config
+ my $cf = CustomField->new(RT->SystemUser);
+ $cf->Create(
+ LookupType => 'RT::Queue-RT::Ticket', # for Tickets
+ Name => 'Impact',
+ Type => 'SelectSingle', # SelectSingle is the same as: Type => 'Select', MaxValues => 1
+ RenderType => 'Dropdown',
+ Values => [
+ { Name => 'Critical' },
+ { Name => 'Informational' },
+ { Name => 'Major' },
+ { Name => 'Minor' },
+ { Name => 'Order to Activation' },
+ ]
+ );
+
+ my $queue = RT::Queue->new(RT->SystemUser);
+ $queue->Load('General'); # apply Impact to General queue
+ $cf->SetContextObject($queue);
+
+ return;
+}
+
+1;
diff --git a/xt/basic.t b/xt/basic.t
new file mode 100644
index 0000000..2d85ea1
--- /dev/null
+++ b/xt/basic.t
@@ -0,0 +1,38 @@
+use strict;
+use warnings;
+
+use RT::Extension::SLA::Utils::Test tests => undef;
+
+use_ok('RT::Extension::SLA::Utils');
+use_ok('RT::CustomField');
+
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+ok( $queue->id, "loaded the General queue" );
+ok( $queue->SetSLADisabled(0), 'SLA enabled');
+
+
+# Create the Impact custom field we will use for testing per the config
+my $cf = RT::CustomField->new(RT->SystemUser);
+$cf->Create(
+ LookupType => 'RT::Queue-RT::Ticket', # for Tickets
+ Name => 'Impact',
+ Type => 'SelectSingle', # SelectSingle is the same as: Type => 'Select', MaxValues => 1
+ RenderType => 'Dropdown',
+ Values => [
+ { Name => 'Critical' },
+ { Name => 'Informational' },
+ { Name => 'Major' },
+ { Name => 'Minor' },
+ { Name => 'Order to Activation' },
+ ]
+);
+$cf->AddToObject($queue);
+
+RT::Test->set_rights(
+ Principal => 'Everyone',
+ Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+);
+
+
+done_testing;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list