[Bps-public-commit] RT-Extension-SLA branch, performance-stats, updated. 0.05-36-g8bd55be

Ruslan Zakirov ruz at bestpractical.com
Mon Mar 12 19:15:19 EDT 2012


The branch, performance-stats has been updated
       via  8bd55be3d8e07e9cc11d095a202bc5c77599ac0c (commit)
       via  7323bbf8fd70f3ba37888be21c6430ea81487ab1 (commit)
       via  376263e5d68b15840f41c4e91d220bb0656b382e (commit)
       via  cb9005220cb5e2a05f622e146ffb1f326f72eb08 (commit)
       via  5f5f363e6b10c275e61288cef299e64a2ca6f419 (commit)
       via  62e517869aa3eeb9213e6a9790ef07bc8fbcc5b6 (commit)
       via  21419ce22e13c70376a7a5460f791107fef424fd (commit)
       via  ee73ace809c92ccb500fb0af4ddbee9c0871bed1 (commit)
       via  f2d456cf3075f3d8def8156a8cb4b1ea8a5d93c5 (commit)
       via  79d4a13ac02aca536165069f83f2527d237ddad4 (commit)
      from  54ba321367ea3c2da470ce5ecd5b741c521f7d1a (commit)

Summary of changes:
 MANIFEST                                           |    9 +-
 META.yml                                           |    2 +-
 Makefile.PL                                        |    1 +
 README                                             |  351 ++++++++++++++++++++
 .../RT-Extension-SLA/Elements/Tabs/Privileged      |   30 +-
 html/Tools/Reports/SLA.html                        |    4 +-
 inc/Module/AutoInstall.pm                          |    2 +-
 inc/Module/Install.pm                              |    2 +-
 inc/Module/Install/AutoInstall.pm                  |    2 +-
 inc/Module/Install/Base.pm                         |    2 +-
 inc/Module/Install/Can.pm                          |   85 +++++-
 inc/Module/Install/Fetch.pm                        |    2 +-
 inc/Module/Install/Include.pm                      |    2 +-
 inc/Module/Install/Makefile.pm                     |    2 +-
 inc/Module/Install/Metadata.pm                     |    2 +-
 inc/Module/Install/ReadmeFromPod.pm                |  138 ++++++++
 inc/Module/Install/Win32.pm                        |    2 +-
 inc/Module/Install/WriteAll.pm                     |    2 +-
 lib/RT/Extension/SLA.pm                            |   35 ++
 lib/RT/Extension/SLA/Report.pm                     |    7 +-
 lib/RT/Extension/SLA/Summary.pm                    |    7 +-
 21 files changed, 649 insertions(+), 40 deletions(-)
 create mode 100644 README
 create mode 100644 inc/Module/Install/ReadmeFromPod.pm

- Log -----------------------------------------------------------------
commit 79d4a13ac02aca536165069f83f2527d237ddad4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 02:05:00 2012 +0400

    make sure $field is empty before we fall into if block
    
    Open/Close paren don't work well when there is no limit
    call in between.

diff --git a/lib/RT/Extension/SLA/Report.pm b/lib/RT/Extension/SLA/Report.pm
index 5ce96a8..f15ea7a 100644
--- a/lib/RT/Extension/SLA/Report.pm
+++ b/lib/RT/Extension/SLA/Report.pm
@@ -278,11 +278,12 @@ sub Transactions {
             FIELD           => 'Type',
             VALUE           => $type,
         );
-        if ( $field ) {
-            my $tmp = ref $field? $field : [$field];
+
+        my @fields = (grep defined && length, ref $field? @$field : ($field));
+        if ( @fields ) {
             $txns->_OpenParen( $clause );
             my $first = 1;
-            foreach my $value ( @$tmp ) {
+            foreach my $value ( @fields ) {
                 $txns->Limit(
                     SUBCLAUSE       => $clause,
                     ENTRYAGGREGATOR => $first? 'AND' : 'OR',

commit f2d456cf3075f3d8def8156a8cb4b1ea8a5d93c5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 02:43:57 2012 +0400

    fix merging results
    
    we were passing incorrect values into MergeCountMinMaxSum

diff --git a/lib/RT/Extension/SLA/Summary.pm b/lib/RT/Extension/SLA/Summary.pm
index 112f42a..3b0cfaf 100644
--- a/lib/RT/Extension/SLA/Summary.pm
+++ b/lib/RT/Extension/SLA/Summary.pm
@@ -99,13 +99,16 @@ sub MergeResults {
     my $dst = shift || $self->Result;
 
 
-    while ( my ($k, $v) = each %$src ) {
+    foreach my $k ( keys %$src ) {
+        my $v = $src->{ $k };
+
         unless ( ref $v ) {
             $dst->{$k} += $v;
         }
         elsif ( ref $v eq 'HASH' ) {
+            $dst->{$k} ||= {};
             if ( exists $v->{'count'} ) {
-                $self->MergeCountMinMaxSum( $src, $dst );
+                $self->MergeCountMinMaxSum( $v, $dst->{$k} );
                 $self->MergeResults(
                     { map { $_ => $v->{$_} } grep !/^(?:count|min|max|sum)$/, keys %$v  },
                     $dst->{ $k }

commit ee73ace809c92ccb500fb0af4ddbee9c0871bed1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 02:45:21 2012 +0400

    protect all menu items with right check

diff --git a/html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged b/html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged
index 5bbcc46..6fc19d2 100644
--- a/html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged
+++ b/html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged
@@ -1,5 +1,9 @@
 <%INIT>
 
+return unless $session{'CurrentUser'}->PrincipalObj->HasRight(
+    Object => $RT::System, Right => 'SeeSLAReports',
+);
+
 my $request_path = $HTML::Mason::Commands::r->path_info;
 
 if ( $request_path =~ m{^/Ticket/} ) {

commit 21419ce22e13c70376a7a5460f791107fef424fd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 02:45:53 2012 +0400

    put menu under Tools and Feeds

diff --git a/html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged b/html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged
index 6fc19d2..4b1dab6 100644
--- a/html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged
+++ b/html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged
@@ -21,18 +21,18 @@ if ( $request_path =~ m{^/Ticket/} ) {
     }
 }
 
-#elsif ( $Query ||= $session{'CurrentSearchHash'}->{'Query'} ) {
-#    $tabs->{"m"} = {
-#        path  => "Tools/Reports/SLA.html?". $m->comp( '/Elements/QueryString', Query => $Query ),
-#        title => loc('Report SLA'),
-#    };
-#}
+my $Query = $m->request_args->{'Query'} || $session{"CurrentSearchHash"}{'Query'};
+if ( $Query && PageMenu->child('more') ) {
+    PageMenu->child('more')->child(
+        sla =>
+        path => "/Tools/Reports/SLA.html?". $m->comp( '/Elements/QueryString', Query => $Query ),
+        title => loc('SLA Report'),
+    );
+}
 
-#return unless $session{'CurrentUser'}->PrincipalObj->HasRight(
-#    Object => $RT::System, Right => 'SeeSLAReports',
-#);
-#$tabs->{'s'} = {
-#    title => loc('Service Level Aggreements'),
-#    path  => 'Tools/Reports/SLA.html',
-#};
+Menu->child('tools')->child(
+    sla =>
+    path => "/Tools/Reports/SLA.html",
+    title => loc('SLA Report'),
+);
 </%INIT>
\ No newline at end of file

commit 62e517869aa3eeb9213e6a9790ef07bc8fbcc5b6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 02:46:40 2012 +0400

    incorrect box heading

diff --git a/html/Tools/Reports/SLA.html b/html/Tools/Reports/SLA.html
index 038d06e..360db0e 100644
--- a/html/Tools/Reports/SLA.html
+++ b/html/Tools/Reports/SLA.html
@@ -1,7 +1,7 @@
 <& /Elements/Header, Title => $title &>
 <& /Elements/Tabs &>
 
-<&| /Widgets/TitleBox, title => loc('Summary') &>
+<&| /Widgets/TitleBox, title => loc('Properties') &>
 <form method="post" action="SLA.html">
 <table>
     <tr>

commit 5f5f363e6b10c275e61288cef299e64a2ca6f419
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 02:46:59 2012 +0400

    make it clear that query is required field

diff --git a/html/Tools/Reports/SLA.html b/html/Tools/Reports/SLA.html
index 360db0e..dddc476 100644
--- a/html/Tools/Reports/SLA.html
+++ b/html/Tools/Reports/SLA.html
@@ -5,7 +5,7 @@
 <form method="post" action="SLA.html">
 <table>
     <tr>
-        <td class="label"><&|/l&>Query</&>:</td>
+        <td class="label"><&|/l&>Query</&><&|/l&>(required)</&>:</td>
         <td class="value"><textarea cols="60" rows="20" name="Query"><% $Query %></textarea></td>
     </tr>
 </table>

commit cb9005220cb5e2a05f622e146ffb1f326f72eb08
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 03:11:49 2012 +0400

    update POD

diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index 2b78673..cb6d7fd 100644
--- a/lib/RT/Extension/SLA.pm
+++ b/lib/RT/Extension/SLA.pm
@@ -322,6 +322,41 @@ to then grant SeeCustomField right.
 You may want to allow customers or managers to escalate thier tickets.
 Just grant them ModifyCustomField right.
 
+=head1 REPORTING
+
+Since version 0.06 extension supports reporting. It works only with RT
+4.0+. Reports accessible in the UI. Each ticket has 'SLA Report' element
+in the page menu under 'Actions'. Search results also has element in the
+menu under 'Feeds'. Also, 'SLA Reports' are under 'Tools' in the main
+menu. This interface is protected by a new right 'SeeSLAReports'.
+
+For purpose of statistics actors are splitted into three groups: requestors,
+owner and other. Any user who was not requestor or owner at the moment is
+assigned to 'other group'.
+
+All time intervals are calculated in business hours.
+
+The following statistics are collected:
+
+=over 4
+
+=item * count of replies splitted by above groups
+
+=item * first response to requestor
+
+Tickets where first act was not by requestor are ignored. This happens
+when somebody on the staff created ticket for client.
+
+=item * response times
+
+=item * number of deadlines met
+
+=item * failed deadlines and timing of such
+
+=back
+
+Note that changing configuration changes stats.
+
 =cut
 
 push @{ scalar RT->Config->Get('CSSFiles') }, 'base/sla-table.css';

commit 376263e5d68b15840f41c4e91d220bb0656b382e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 03:12:01 2012 +0400

    update M::I

diff --git a/META.yml b/META.yml
index f7f0230..36d6250 100644
--- a/META.yml
+++ b/META.yml
@@ -9,7 +9,7 @@ configure_requires:
   ExtUtils::MakeMaker: 6.59
 distribution_type: module
 dynamic_config: 1
-generated_by: 'Module::Install version 1.05'
+generated_by: 'Module::Install version 1.06'
 license: gpl2
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
diff --git a/inc/Module/AutoInstall.pm b/inc/Module/AutoInstall.pm
index d516bae..aa7aa92 100644
--- a/inc/Module/AutoInstall.pm
+++ b/inc/Module/AutoInstall.pm
@@ -8,7 +8,7 @@ use ExtUtils::MakeMaker ();
 
 use vars qw{$VERSION};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 }
 
 # special map on pre-defined feature sets
diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
index 4508d97..4ecf46b 100644
--- a/inc/Module/Install.pm
+++ b/inc/Module/Install.pm
@@ -31,7 +31,7 @@ BEGIN {
 	# This is not enforced yet, but will be some time in the next few
 	# releases once we can make sure it won't clash with custom
 	# Module::Install extensions.
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 
 	# Storage for the pseudo-singleton
 	$MAIN    = undef;
diff --git a/inc/Module/Install/AutoInstall.pm b/inc/Module/Install/AutoInstall.pm
index 405f161..6efe4fe 100644
--- a/inc/Module/Install/AutoInstall.pm
+++ b/inc/Module/Install/AutoInstall.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
index 683088b..802844a 100644
--- a/inc/Module/Install/Base.pm
+++ b/inc/Module/Install/Base.pm
@@ -4,7 +4,7 @@ package Module::Install::Base;
 use strict 'vars';
 use vars qw{$VERSION};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 }
 
 # Suspend handler for "redefined" warnings
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
index 79932c5..22167b8 100644
--- a/inc/Module/Install/Can.pm
+++ b/inc/Module/Install/Can.pm
@@ -3,13 +3,12 @@ package Module::Install::Can;
 
 use strict;
 use Config                ();
-use File::Spec            ();
 use ExtUtils::MakeMaker   ();
 use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -29,7 +28,7 @@ sub can_use {
 	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
 }
 
-# check if we can run some command
+# Check if we can run some command
 sub can_run {
 	my ($self, $cmd) = @_;
 
@@ -38,14 +37,88 @@ sub can_run {
 
 	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
 		next if $dir eq '';
-		my $abs = File::Spec->catfile($dir, $_[1]);
+		require File::Spec;
+		my $abs = File::Spec->catfile($dir, $cmd);
 		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
 	}
 
 	return;
 }
 
-# can we locate a (the) C compiler
+# Can our C compiler environment build XS files
+sub can_xs {
+	my $self = shift;
+
+	# Ensure we have the CBuilder module
+	$self->configure_requires( 'ExtUtils::CBuilder' => 0.27 );
+
+	# Do we have the configure_requires checker?
+	local $@;
+	eval "require ExtUtils::CBuilder;";
+	if ( $@ ) {
+		# They don't obey configure_requires, so it is
+		# someone old and delicate. Try to avoid hurting
+		# them by falling back to an older simpler test.
+		return $self->can_cc();
+	}
+
+	# Do we have a working C compiler
+	my $builder = ExtUtils::CBuilder->new(
+		quiet => 1,
+	);
+	unless ( $builder->have_compiler ) {
+		# No working C compiler
+		return 0;
+	}
+
+	# Write a C file representative of what XS becomes
+	require File::Temp;
+	my ( $FH, $tmpfile ) = File::Temp::tempfile(
+		"compilexs-XXXXX",
+		SUFFIX => '.c',
+	);
+	binmode $FH;
+	print $FH <<'END_C';
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+int main(int argc, char **argv) {
+    return 0;
+}
+
+int boot_sanexs() {
+    return 1;
+}
+
+END_C
+	close $FH;
+
+	# Can the C compiler access the same headers XS does
+	my @libs   = ();
+	my $object = undef;
+	eval {
+		local $^W = 0;
+		$object = $builder->compile(
+			source => $tmpfile,
+		);
+		@libs = $builder->link(
+			objects     => $object,
+			module_name => 'sanexs',
+		);
+	};
+	my $result = $@ ? 0 : 1;
+
+	# Clean up all the build files
+	foreach ( $tmpfile, $object, @libs ) {
+		next unless defined $_;
+		1 while unlink;
+	}
+
+	return $result;
+}
+
+# Can we locate a (the) C compiler
 sub can_cc {
 	my $self   = shift;
 	my @chunks = split(/ /, $Config::Config{cc}) or return;
@@ -78,4 +151,4 @@ if ( $^O eq 'cygwin' ) {
 
 __END__
 
-#line 156
+#line 236
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
index 085a253..bee0c4f 100644
--- a/inc/Module/Install/Fetch.pm
+++ b/inc/Module/Install/Fetch.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Include.pm b/inc/Module/Install/Include.pm
index 2dd69c9..8310e4c 100644
--- a/inc/Module/Install/Include.pm
+++ b/inc/Module/Install/Include.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
index c3cbb2b..7052f36 100644
--- a/inc/Module/Install/Makefile.pm
+++ b/inc/Module/Install/Makefile.pm
@@ -8,7 +8,7 @@ use Fcntl qw/:flock :seek/;
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
index c863124..58430f3 100644
--- a/inc/Module/Install/Metadata.pm
+++ b/inc/Module/Install/Metadata.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
index 22c409a..eeaa3fe 100644
--- a/inc/Module/Install/Win32.pm
+++ b/inc/Module/Install/Win32.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
index 6cd27da..85d8018 100644
--- a/inc/Module/Install/WriteAll.pm
+++ b/inc/Module/Install/WriteAll.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.05';
+	$VERSION = '1.06';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }

commit 7323bbf8fd70f3ba37888be21c6430ea81487ab1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 03:13:43 2012 +0400

    generate README from POD

diff --git a/MANIFEST b/MANIFEST
index 093a485..f254f8f 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -40,3 +40,4 @@ t/due.t
 t/queue.t
 t/reporting/basic.t
 t/starts.t
+README
diff --git a/Makefile.PL b/Makefile.PL
index 99c9689..cc49b5b 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');
diff --git a/README b/README
new file mode 100644
index 0000000..96b94dc
--- /dev/null
+++ b/README
@@ -0,0 +1,351 @@
+NAME
+    RT::Extension::SLA - Service Level Agreements for RT
+
+DESCRIPTION
+    RT extension to implement automated due dates using service levels.
+
+UPGRADING
+    On upgrade you shouldn't run 'make initdb'.
+
+    If you were using 0.02 or older version of this extension with RT 3.8.1
+    then you have to uninstall that manually. List of files you can find in
+    the MANIFEST.
+
+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)));
+
+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:
+
+        Set( %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:
+    StartImmediately, Resolve, Response, KeepInLoop, OutOfHours and
+    ServiceBusinessHours.
+
+  StartImmediately (boolean, false)
+    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. In this case
+    you can set option StartImmediately to a true value.
+
+    Example:
+
+        '24/7' => {
+            StartImmediately => 1,
+            Response => { RealMinutes => 30 },
+        },
+        'standard' => {
+            StartImmediately => 0, # can be ommited as it's default
+            Response => { BusinessMinutes => 2*60 },
+        },
+
+  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.
+
+  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.
+
+  Configuring business hours
+    In the config you can set one or more work schedules. Use the following
+    format:
+
+        Set( %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:
+
+        Set( %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:
+
+        Set( %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.
+
+REPORTING
+    Since version 0.06 extension supports reporting. It works only with RT
+    4.0+. Reports accessible in the UI. Each ticket has 'SLA Report' element
+    in the page menu under 'Actions'. Search results also has element in the
+    menu under 'Feeds'. Also, 'SLA Reports' are under 'Tools' in the main
+    menu. This interface is protected by a new right 'SeeSLAReports'.
+
+    For purpose of statistics actors are splitted into three groups:
+    requestors, owner and other. Any user who was not requestor or owner at
+    the moment is assigned to 'other group'.
+
+    All time intervals are calculated in business hours.
+
+    The following statistics are collected:
+
+    *   count of replies splitted by above groups
+
+    *   first response to requestor
+
+        Tickets where first act was not by requestor are ignored. This
+        happens when somebody on the staff created ticket for client.
+
+    *   response times
+
+    *   number of deadlines met
+
+    *   failed deadlines and timing of such
+
+    Note that changing configuration changes stats.
+
+TODO and CAVEATS
+        * [not implemented] KeepInLoop and Response deadlines need adjusting. For example
+          KeepInLoop is 2h and Response is 2h as well. Owner replies at point 0, deadline
+          is 2h, at 1h requestor replies with anything -> deadline is moved according to
+          response deadline to 3h when it must stay at 2h waiting for KeepInLoop follow up
+          from owner and then move to another KeepInLoop deadline at 4h.
+
+        * [not implemented] Manually entered Due date should be treated as Resolve deadline.
+          We should store it and use later, so this module can be used for projects. For
+          example: Response 4 hours, KeepInLoop 1 day, Resolve 5 b.days; these are defaults,
+          but any manual change to Due date changes Resolve deadline.
+
+        * [not implemented] WebUI
+
+        * [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.
+
+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
+

commit 8bd55be3d8e07e9cc11d095a202bc5c77599ac0c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 13 03:15:02 2012 +0400

    update manifest

diff --git a/MANIFEST b/MANIFEST
index f254f8f..81064af 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,7 +1,8 @@
 Changes
 etc/initialdata
-html/Callbacks/RT-Extension-SLA/Ticket/Elements/Tabs/Default
-html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default
+html/Callbacks/RT-Extension-SLA/Elements/Tabs/Privileged
+html/Elements/SLA/ShowReportSummary
+html/NoAuth/css/base/sla-table.css
 html/Ticket/SLA.html
 html/Tools/Reports/SLA.html
 inc/Module/AutoInstall.pm
@@ -13,6 +14,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
@@ -28,16 +30,16 @@ lib/RT/Condition/SLA_RequireDueSet.pm
 lib/RT/Condition/SLA_RequireStartsSet.pm
 lib/RT/Extension/SLA.pm
 lib/RT/Extension/SLA/Report.pm
-lib/RT/Extension/SLA/Test.pm
 lib/RT/Extension/SLA/Summary.pm
+lib/RT/Extension/SLA/Test.pm
 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
 t/queue.t
 t/reporting/basic.t
 t/starts.t
-README

-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list