[Bps-public-commit] RT-Extension-SLA branch, master, updated. 0.05_02-10-g9e5da31
Thomas Sibley
trs at bestpractical.com
Fri Jun 29 14:59:49 EDT 2012
The branch, master has been updated
via 9e5da31afc34554c0083683858503dc03ff7a408 (commit)
via 80a2c5d8e8636a5fc41da2b8ea6b7bb6ea9d08b0 (commit)
via 8957dd0d22701ef388cd4477f29800a40bbd3dac (commit)
via 5d05bb2789f9e33da4d00110930ca4b1615eba96 (commit)
via 2e9acee6e0d72cf6052bdf5081d9ed78b22d9424 (commit)
via 67d0095f46d5e9e1de4e22b4f1833e74ae934113 (commit)
via da2f26fe9f5ac4156de55db0ccc437ed04ac996e (commit)
via 8ae6b607fe083c6cb21e23cceab68e55faae5bed (commit)
via c5e14573a37fa02da0ec5ccd02e12ca89372e0e4 (commit)
via e9b63eb1dfa8e32e82660179e2a04b78e52307d2 (commit)
from c929225f866acec10bdd45ccdcdc2764ebbe9b3c (commit)
Summary of changes:
Changes | 6 +
MANIFEST | 4 +
META.yml | 2 +-
Makefile.PL | 2 +
README | 349 +++++++++++++++++++++++++++++++++
etc/initialdata | 2 +-
etc/upgrade/0.06/content | 16 ++
inc/Module/Install/ReadmeFromPod.pm | 138 +++++++++++++
lib/RT/Condition/SLA_RequireDueSet.pm | 4 +
lib/RT/Extension/SLA.pm | 42 +++-
lib/RT/Extension/SLA/Test.pm | 2 +-
t/ignore-on-statuses.t | 166 ++++++++++++++++
12 files changed, 728 insertions(+), 5 deletions(-)
create mode 100644 README
create mode 100644 etc/upgrade/0.06/content
create mode 100644 inc/Module/Install/ReadmeFromPod.pm
create mode 100644 t/ignore-on-statuses.t
- Log -----------------------------------------------------------------
commit 5d05bb2789f9e33da4d00110930ca4b1615eba96
Merge: c929225 2e9acee
Author: Thomas Sibley <trs at bestpractical.com>
Date: Fri Jun 29 11:38:34 2012 -0700
Merge branch 'ignore-deadline-on-statuses'
commit 8957dd0d22701ef388cd4477f29800a40bbd3dac
Author: Thomas Sibley <trs at bestpractical.com>
Date: Fri Jun 29 11:48:39 2012 -0700
Release prep for 0.06
diff --git a/Changes b/Changes
index 1436e21..b8a40c9 100644
--- a/Changes
+++ b/Changes
@@ -1,4 +1,4 @@
-0.06 Not released
+0.06 Fri Jun 29 11:47:50 PDT 2012
* IgnoreOnStatuses agreement modifier
diff --git a/META.yml b/META.yml
index 766c822..b301f42 100644
--- a/META.yml
+++ b/META.yml
@@ -25,4 +25,4 @@ requires:
perl: 5.8.0
resources:
license: http://opensource.org/licenses/gpl-2.0.php
-version: 0.05_02
+version: 0.06
diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index 34bbc37..a955dd2 100644
--- a/lib/RT/Extension/SLA.pm
+++ b/lib/RT/Extension/SLA.pm
@@ -4,7 +4,7 @@ use warnings;
package RT::Extension::SLA;
-our $VERSION = '0.05_02';
+our $VERSION = '0.06';
=head1 NAME
commit 80a2c5d8e8636a5fc41da2b8ea6b7bb6ea9d08b0
Author: Thomas Sibley <trs at bestpractical.com>
Date: Fri Jun 29 11:56:46 2012 -0700
Add upgrading doc to POD
diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index a955dd2..6a9c7db 100644
--- a/lib/RT/Extension/SLA.pm
+++ b/lib/RT/Extension/SLA.pm
@@ -35,6 +35,17 @@ to your @Plugins line (or create one) like:
=back
+=head1 UPGRADING
+
+=head2 From versions prior to 0.06
+
+You need to run an upgrade step on your RT database so this extension continues
+to work. Run the following from inside the source of this extension:
+
+ /opt/rt4/sbin/rt-setup-database --action insert --datafile etc/upgrade/0.06/content
+
+It will prompt you for your DBA password and should complete without error.
+
=head1 CONFIGURATION
Service level agreements of tickets is controlled by an SLA custom field (CF).
commit 9e5da31afc34554c0083683858503dc03ff7a408
Author: Thomas Sibley <trs at bestpractical.com>
Date: Fri Jun 29 11:57:18 2012 -0700
Generate a README from POD and point people to it from Changes
diff --git a/Changes b/Changes
index b8a40c9..3f5ec6b 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,7 @@
0.06 Fri Jun 29 11:47:50 PDT 2012
+ UPGRADING: There are database changes to make, please see the README.
+
* IgnoreOnStatuses agreement modifier
0.05_02 Wed Jun 27 15:44:36 PDT 2012
diff --git a/MANIFEST b/MANIFEST
index 28765bf..ff9d3a0 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -10,6 +10,7 @@ inc/Module/Install/Fetch.pm
inc/Module/Install/Include.pm
inc/Module/Install/Makefile.pm
inc/Module/Install/Metadata.pm
+inc/Module/Install/ReadmeFromPod.pm
inc/Module/Install/RTx.pm
inc/Module/Install/RTx/Factory.pm
inc/Module/Install/Substitute.pm
@@ -29,6 +30,7 @@ lib/RT/Queue_SLA.pm
Makefile.PL
MANIFEST This list of files
META.yml
+README
t/basics.t
t/business_hours.t
t/due.t
diff --git a/Makefile.PL b/Makefile.PL
index 99c9689..d01bc26 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -3,6 +3,7 @@ use inc::Module::Install;
abstract('Service Level Agreements for RT');
RTx ('RT-Extension-SLA');
all_from('lib/RT/Extension/SLA.pm');
+readme_from('lib/RT/Extension/SLA.pm');
license('gpl2');
build_requires('Test::More');
@@ -24,4 +25,5 @@ substitute(
qw(lib/RT/Extension/SLA/Test.pm),
);
+sign;
WriteAll();
diff --git a/README b/README
new file mode 100644
index 0000000..a5445a7
--- /dev/null
+++ b/README
@@ -0,0 +1,349 @@
+NAME
+ RT::Extension::SLA - Service Level Agreements for RT
+
+DESCRIPTION
+ RT extension to implement automated due dates using service levels.
+
+INSTALL
+ perl Makefile.PL
+ make
+ make install
+ make initdb (for the first time only)
+ Base configuration
+ In RT 3.8 and later, you must enable the plugin by adding
+ RT::Extension::SLA to your @Plugins line (or create one) like:
+
+ Set(@Plugins,(qw(RT::Extension::SLA)));
+
+UPGRADING
+ From versions prior to 0.06
+ You need to run an upgrade step on your RT database so this extension
+ continues to work. Run the following from inside the source of this
+ extension:
+
+ /opt/rt4/sbin/rt-setup-database --action insert --datafile etc/upgrade/0.06/content
+
+ It will prompt you for your DBA password and should complete without
+ error.
+
+CONFIGURATION
+ Service level agreements of tickets is controlled by an SLA custom field
+ (CF). This field is created during "make initdb" step (above) and
+ applied globally. This CF MUST be of "select one value" type. Values of
+ the CF define the service levels.
+
+ It's possible to define different set of levels for different queues.
+ You can create several CFs with the same name and different set of
+ values. But if you move tickets between queues a lot then it's going to
+ be a problem and it's preferred to use ONE SLA custom field.
+
+ There is no WebUI in the current version. Almost everything is
+ controlled in the RT's config using option %RT::ServiceAgreements and
+ %RT::ServiceBusinessHours. For example:
+
+ %RT::ServiceAgreements = (
+ Default => '4h',
+ QueueDefault => {
+ 'Incident' => '2h',
+ },
+ Levels => {
+ '2h' => { Resolve => { RealMinutes => 60*2 } },
+ '4h' => { Resolve => { RealMinutes => 60*4 } },
+ },
+ );
+
+ In this example *Incident* is the name of the queue, and *2h* is the
+ name of the SLA which will be applied to this queue by default.
+
+ Each service level can be described using several options: Starts,
+ Resolve, Response, KeepInLoop, OutOfHours and ServiceBusinessHours.
+
+ Starts (interval, first business minute)
+ By default when a ticket is created Starts date is set to first business
+ minute after time of creation. In other words if a ticket is created
+ during business hours then Starts will be equal to Created time,
+ otherwise Starts will be beginning of the next business day.
+
+ However, if you provide 24/7 support then you most probably would be
+ interested in Starts to be always equal to Created time.
+
+ Starts option can be used to adjust behaviour. Format of the option is
+ the same as format for deadlines which described later in details.
+ RealMinutes, BusinessMinutes options and OutOfHours modifiers can be
+ used here like for any other deadline. For example:
+
+ 'standard' => {
+ # give people 15 minutes
+ Starts => { BusinessMinutes => 15 },
+ },
+
+ You can still use old option StartImmediately to set Starts date equal
+ to Created date.
+
+ Example:
+
+ '24/7' => {
+ StartImmediately => 1,
+ Response => { RealMinutes => 30 },
+ },
+
+ But it's the same as:
+
+ '24/7' => {
+ Starts => { RealMinutes => 0 },
+ Response => { RealMinutes => 30 },
+ },
+
+ Resolve and Response (interval, no defaults)
+ These two options define deadlines for resolve of a ticket and reply to
+ customer(requestors) questions accordingly.
+
+ You can define them using real time, business or both. Read more about
+ the latter below.
+
+ The Due date field is used to store calculated deadlines.
+
+ Resolve
+ Defines deadline when a ticket should be resolved. This option is quite
+ simple and straightforward when used without "Response".
+
+ Example:
+
+ # 8 business hours
+ 'simple' => { Resolve => 60*8 },
+ ...
+ # one real week
+ 'hard' => { Resolve => { RealMinutes => 60*24*7 } },
+
+ Response
+ In many companies providing support service(s) resolve time of a ticket
+ is less important than time of response to requestors from stuff
+ members.
+
+ You can use Response option to define such deadlines. When you're using
+ this option Due time "flips" when requestors and non-requestors reply to
+ a ticket. We set Due date when a ticket is created, unset when
+ non-requestor replies... until ticket is closed when ticket's Due date
+ is also unset.
+
+ NOTE that behaviour changes when Resolve and Response options are
+ combined, read below.
+
+ As response deadlines are calculated using requestors' activity so
+ several rules applies to make things sane:
+
+ * If requestor(s) reply multiple times and are ignored then the
+ deadline is calculated using the oldest requestors' correspondence.
+
+ * If a ticket has no requestor(s) then it has no response deadline.
+
+ * If a ticket is created by non-requestor then due date is left unset.
+
+ * If owner of a ticket is its requestor then his actions are treated
+ as non-requestors'.
+
+ Using both Resolve and Response in the same level
+ Resolve and Response can be combined. In such case due date is set
+ according to the earliest of two deadlines and never is dropped to 'not
+ set'.
+
+ If a ticket met its Resolve deadline then due date stops "flipping", is
+ freezed and the ticket becomes overdue. Before that moment when
+ non-requestor replies to a ticket, due date is changed to Resolve
+ deadline instead of 'Not Set', as well this happens when a ticket is
+ closed. So all the time due date is defined.
+
+ Example:
+
+ 'standard delivery' => {
+ Response => { RealMinutes => 60*1 }, # one hour
+ Resolve => { RealMinutes => 60*24 }, # 24 real hours
+ },
+
+ A client orders goods and due date of the order is set to the next one
+ hour, you have this hour to process the order and write a reply. As soon
+ as goods are delivered you resolve tickets and usually meet Resolve
+ deadline, but if you don't resolve or user replies then most probably
+ there are problems with delivery of the goods. And if after a week you
+ keep replying to the client and always meeting one hour response
+ deadline that doesn't mean the ticket is not over due. Due date was
+ frozen 24 hours after creation of the order.
+
+ Using business and real time in one option
+ It's quite rare situation when people need it, but we've decided that
+ business is applied first and then real time when deadline described
+ using both types of time. For example:
+
+ 'delivery' => {
+ Resolve => { BusinessMinutes => 0, RealMinutes => 60*8 },
+ },
+ 'fast delivery' {
+ StartImmediately => 1,
+ Resolve => { RealMinutes => 60*8 },
+ },
+
+ For delivery requests which come into the system during business hours
+ these levels define the same deadlines, otherwise the first level set
+ deadline to 8 real hours starting from the next business day, when
+ tickets with the second level should be resolved in the next 8 hours
+ after creation.
+
+ Keep in loop (interval, no defaults)
+ If response deadline is used then Due date is changed to repsonse
+ deadline or to "Not Set" when staff replies to a ticket. In some cases
+ you want to keep requestors in loop and keed them up to date every few
+ hours. KeepInLoop option can be used to achieve this.
+
+ 'incident' => {
+ Response => { RealMinutes => 60*1 }, # one hour
+ KeepInLoop => { RealMinutes => 60*2 }, # two hours
+ Resolve => { RealMinutes => 60*24 }, # 24 real hours
+ },
+
+ In the above example Due is set to one hour after creation, reply of a
+ non-requestor moves Due date two hours forward, requestors' replies move
+ Due date to one hour and resolve deadine is 24 hours.
+
+ Modifying Agreements
+ OutOfHours (struct, no default)
+ Out of hours modifier. Adds more real or business minutes to resolve
+ and/or reply options if event happens out of business hours, read also
+ </"Configuring business hours"> below.
+
+ Example:
+
+ 'level x' => {
+ OutOfHours => { Resolve => { RealMinutes => +60*24 } },
+ Resolve => { RealMinutes => 60*24 },
+ },
+
+ If a request comes into the system during night then supporters have two
+ hours, otherwise only one.
+
+ 'level x' => {
+ OutOfHours => { Response => { BusinessMinutes => +60*2 } },
+ Resolve => { BusinessMinutes => 60 },
+ },
+
+ Supporters have two additional hours in the morning to deal with bunch
+ of requests that came into the system during the last night.
+
+ IgnoreOnStatuses (array, no default)
+ Allows you to ignore a deadline when ticket has certain status. Example:
+
+ 'level x' => {
+ KeepInLoop => { BusinessMinutes => 60, IgnoreOnStatuses => ['stalled'] },
+ },
+
+ In above example KeepInLoop deadline is ignored if ticket is stalled.
+
+ NOTE: When a ticket goes from an ignored status to a normal status, the
+ new Due date is calculated from the last action (reply, SLA change, etc)
+ which fits the SLA type (Response, Starts, KeepInLoop, etc). This means
+ if a ticket in the above example flips from stalled to open without a
+ reply, the ticket will probably be overdue. In most cases this shouldn't
+ be a problem since moving out of stalled-like statuses is often the
+ result of RT's auto-open on reply scrip, therefore ensuring there's a
+ new reply to calculate Due from. The overall effect is that ignored
+ statuses don't let the Due date drift arbitrarily, which could wreak
+ havoc on your SLA performance.
+
+ Configuring business hours
+ In the config you can set one or more work schedules. Use the following
+ format:
+
+ %RT::ServiceBusinessHours = (
+ 'Default' => {
+ ... description ...
+ },
+ 'Support' => {
+ ... description ...
+ },
+ 'Sales' => {
+ ... description ...
+ },
+ );
+
+ Read more about how to describe a schedule in Business::Hours.
+
+ Defining different business hours for service levels
+ Each level supports BusinessHours option to specify your own business
+ hours.
+
+ 'level x' => {
+ BusinessHours => 'work just in Monday',
+ Resolve => { BusinessMinutes => 60 },
+ },
+
+ then %RT::ServiceBusinessHours should have the corresponding definition:
+
+ %RT::ServiceBusinessHours = (
+ 'work just in Monday' => {
+ 1 => { Name => 'Monday', Start => '9:00', End => '18:00' },
+ },
+ );
+
+ Default Business Hours setting is in
+ $RT::ServiceBusinessHours{'Default'}.
+
+ Defining service levels per queue
+ In the config you can set per queue defaults, using:
+
+ %RT::ServiceAgreements = (
+ Default => 'global default level of service',
+ QueueDefault => {
+ 'queue name' => 'default value for this queue',
+ ...
+ },
+ ...
+ };
+
+ Access control
+ You can totally hide SLA custom field from users and use per queue
+ defaults, just revoke SeeCustomField and ModifyCustomField.
+
+ If you want people to see the current service level ticket is assigned
+ to then grant SeeCustomField right.
+
+ You may want to allow customers or managers to escalate thier tickets.
+ Just grant them ModifyCustomField right.
+
+TODO
+ * [implemented, TODO: tests for options in the config] default SLA for queues
+
+ * [implemented, TODO: tests] add support for multiple b-hours definitions,
+ this could be very helpfull when you have 24/7 mixed with 8/5 and/or
+ something like 8/5+4/2 for different tickets(by requestor, queue or
+ something else). So people would be able to handle tickets in the right
+ order using Due dates.
+
+ * [not implemented] WebUI
+
+DESIGN
+ Classes
+ Actions are subclasses of RT::Action::SLA class that is subclass of
+ RT::Extension::SLA and RT::Action classes.
+
+ Conditions are subclasses of RT::Condition::SLA class that is subclass
+ of RT::Extension::SLA and RT::Condition classes.
+
+ RT::Extension::SLA is a base class for all classes in the extension, it
+ provides access to config, generates Business::Hours objects, and other
+ things useful for whole extension. As this class is the base for all
+ actions and conditions then we MUST avoid adding methods which overload
+ methods in 'RT::{Condition,Action}' RT's modules.
+
+NOTES
+ If you run "make initdb" more than once you will create multiple SLA
+ CFs. You can remove these via RT's "Configuration->Global" menu, (both
+ Custom Fields and Scrips).
+
+AUTHOR
+ Ruslan Zakirov <ruz at bestpractical.com>
+
+COPYRIGHT
+ This extension is Copyright (C) 2007-2009 Best Practical Solutions, LLC.
+
+ It is freely redistributable under the terms of version 2 of the GNU
+ GPL.
+
diff --git a/inc/Module/Install/ReadmeFromPod.pm b/inc/Module/Install/ReadmeFromPod.pm
new file mode 100644
index 0000000..fb7075f
--- /dev/null
+++ b/inc/Module/Install/ReadmeFromPod.pm
@@ -0,0 +1,138 @@
+#line 1
+package Module::Install::ReadmeFromPod;
+
+use 5.006;
+use strict;
+use warnings;
+use base qw(Module::Install::Base);
+use vars qw($VERSION);
+
+$VERSION = '0.18';
+
+sub readme_from {
+ my $self = shift;
+ return unless $self->is_admin;
+
+ # Input file
+ my $in_file = shift || $self->_all_from
+ or die "Can't determine file to make readme_from";
+
+ # Get optional arguments
+ my ($clean, $format, $out_file, $options);
+ my $args = shift;
+ if ( ref $args ) {
+ # Arguments are in a hashref
+ if ( ref($args) ne 'HASH' ) {
+ die "Expected a hashref but got a ".ref($args)."\n";
+ } else {
+ $clean = $args->{'clean'};
+ $format = $args->{'format'};
+ $out_file = $args->{'output_file'};
+ $options = $args->{'options'};
+ }
+ } else {
+ # Arguments are in a list
+ $clean = $args;
+ $format = shift;
+ $out_file = shift;
+ $options = \@_;
+ }
+
+ # Default values;
+ $clean ||= 0;
+ $format ||= 'txt';
+
+ # Generate README
+ print "readme_from $in_file to $format\n";
+ if ($format =~ m/te?xt/) {
+ $out_file = $self->_readme_txt($in_file, $out_file, $options);
+ } elsif ($format =~ m/html?/) {
+ $out_file = $self->_readme_htm($in_file, $out_file, $options);
+ } elsif ($format eq 'man') {
+ $out_file = $self->_readme_man($in_file, $out_file, $options);
+ } elsif ($format eq 'pdf') {
+ $out_file = $self->_readme_pdf($in_file, $out_file, $options);
+ }
+
+ if ($clean) {
+ $self->clean_files($out_file);
+ }
+
+ return 1;
+}
+
+
+sub _readme_txt {
+ my ($self, $in_file, $out_file, $options) = @_;
+ $out_file ||= 'README';
+ require Pod::Text;
+ my $parser = Pod::Text->new( @$options );
+ open my $out_fh, '>', $out_file or die "Could not write file $out_file:\n$!\n";
+ $parser->output_fh( *$out_fh );
+ $parser->parse_file( $in_file );
+ close $out_fh;
+ return $out_file;
+}
+
+
+sub _readme_htm {
+ my ($self, $in_file, $out_file, $options) = @_;
+ $out_file ||= 'README.htm';
+ require Pod::Html;
+ Pod::Html::pod2html(
+ "--infile=$in_file",
+ "--outfile=$out_file",
+ @$options,
+ );
+ # Remove temporary files if needed
+ for my $file ('pod2htmd.tmp', 'pod2htmi.tmp') {
+ if (-e $file) {
+ unlink $file or warn "Warning: Could not remove file '$file'.\n$!\n";
+ }
+ }
+ return $out_file;
+}
+
+
+sub _readme_man {
+ my ($self, $in_file, $out_file, $options) = @_;
+ $out_file ||= 'README.1';
+ require Pod::Man;
+ my $parser = Pod::Man->new( @$options );
+ $parser->parse_from_file($in_file, $out_file);
+ return $out_file;
+}
+
+
+sub _readme_pdf {
+ my ($self, $in_file, $out_file, $options) = @_;
+ $out_file ||= 'README.pdf';
+ eval { require App::pod2pdf; }
+ or die "Could not generate $out_file because pod2pdf could not be found\n";
+ my $parser = App::pod2pdf->new( @$options );
+ $parser->parse_from_file($in_file);
+ open my $out_fh, '>', $out_file or die "Could not write file $out_file:\n$!\n";
+ select $out_fh;
+ $parser->output;
+ select STDOUT;
+ close $out_fh;
+ return $out_file;
+}
+
+
+sub _all_from {
+ my $self = shift;
+ return unless $self->admin->{extensions};
+ my ($metadata) = grep {
+ ref($_) eq 'Module::Install::Metadata';
+ } @{$self->admin->{extensions}};
+ return unless $metadata;
+ return $metadata->{values}{all_from} || '';
+}
+
+'Readme!';
+
+__END__
+
+#line 254
+
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list