[Bps-public-commit] RT-Extension-SLA branch, performance-stats, created. 0.03-39-gafb655c

Ruslan Zakirov ruz at bestpractical.com
Thu Jun 2 18:26:58 EDT 2011


The branch, performance-stats has been created
        at  afb655c376a8816af5f4c85de10ff9aad9a766c8 (commit)

- Log -----------------------------------------------------------------
commit 2e7e0eb36361c9bab4e42998ef0890caef37ad8b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Apr 27 19:04:02 2009 +0000

    fist pass on a reporting

diff --git a/lib/RT/Extension/SLA/Report.pm b/lib/RT/Extension/SLA/Report.pm
new file mode 100644
index 0000000..afbae9e
--- /dev/null
+++ b/lib/RT/Extension/SLA/Report.pm
@@ -0,0 +1,98 @@
+use 5.8.0;
+use strict;
+use warnings;
+
+package RT::Extension::SLA::Report;
+
+sub new {}
+
+sub init {}
+
+sub State {
+    my $self = shift;
+    return $self->{State} ||= {};
+}
+
+{ my $cache;
+sub Handlers {
+    my $self = shift;
+
+    return $cache if $cache;
+    
+    $cache = {
+        Create => 'OnCreate',
+        Set    => {
+            Owner => 'OnOwnerChange',
+        },
+        Correpond => 'OnResponse',
+        CustomField => { map $_ => 'OnServiceLevelChange', $self->ServiceLevelCustomFields },
+    };
+
+    return $cache;
+}
+
+sub Drive {
+    my $self = shift;
+    my $txns = shift;
+
+    my $state = $self->State;
+    my $handler = $self->Handlers;
+
+    while ( my $txn = $txns->Next ) {
+        my ($type, $field) = ($txn->Type, $txn->Field);
+
+        my $h = $handler->{ $type };
+        unless ( $h ) {
+            $RT::Logger->debug( "No handler for $type transaction, skipping" );
+        } elsif ( ref $h ) {
+            unless ( $h = $h->{ $field } ) {
+                $RT::Logger->debug( "No handler for ($type, $field) transaction, skipping" );
+            }
+        }
+        next unless $h;
+
+        $self->$h( Transaction => $txn, State => $state );
+    }
+}
+
+sub InitialServiceLevel {
+    my $self = shift;
+    my $ticket = shift;
+
+    my $txns = $ticket->Transactions;
+    foreach my $cf ( $self->ServiceLevelCustomFields ) {
+        $txns->_OpenParen('ServiceLevelCustomFields');
+        $txns->Limit(
+            SUBCLAUSE       => 'ServiceLevelCustomFields',
+            ENTRYAGGREGATOR => 'OR',
+            FIELD           => 'Type',
+            VALUE           => 'CustomField',
+        );
+        $txns->Limit(
+            SUBCLAUSE       => 'ServiceLevelCustomFields',
+            ENTRYAGGREGATOR => 'AND',
+            FIELD           => 'Field',
+            VALUE           => $cf->id,
+        );
+        $txns->_CloseParen('ServiceLevelCustomFields');
+    }
+
+    return $self;
+}
+
+{ my @cache = ();
+sub ServiceLevelCustomFields {
+    my $self = shift;
+    return @cache if @cache;
+
+    my $cfs = RT::CustomFields->new( $RT::SystemUser );
+    $cfs->Limit( FIELD => 'Name', VALUE => 'SLA' );
+    $cfs->Limit( FIELD => 'LookupType', VALUE => RT::Ticket->CustomFieldLookupType );
+    # XXX: limit to applied custom fields only
+
+    push @cache, $_ while $_ = $cfs->Next;
+
+    return @cache;
+} }
+
+1;

commit 7f1f29c234beb02013dad72959767d94c78c1906
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Apr 29 20:54:43 2009 +0000

    another pass on reporting

diff --git a/lib/RT/Extension/SLA/Report.pm b/lib/RT/Extension/SLA/Report.pm
index afbae9e..52a254e 100644
--- a/lib/RT/Extension/SLA/Report.pm
+++ b/lib/RT/Extension/SLA/Report.pm
@@ -24,12 +24,14 @@ sub Handlers {
         Set    => {
             Owner => 'OnOwnerChange',
         },
-        Correpond => 'OnResponse',
+        Correspond => 'OnResponse',
         CustomField => { map $_ => 'OnServiceLevelChange', $self->ServiceLevelCustomFields },
+        AddWatcher => { Requestor => 'OnRequestorChange' },
+        DelWatcher => { Requestor => 'OnRequestorChange' },
     };
 
     return $cache;
-}
+} }
 
 sub Drive {
     my $self = shift;
@@ -55,29 +57,157 @@ sub Drive {
     }
 }
 
+sub OnCreate {
+    my $self = shift;
+    my %args = ( Ticket => undef, Transaction => undef, State => undef, @_);
+
+    my $level = $self->InitialServiceLevel( $args{'Ticket'} );
+
+    my $state = $args{'State'};
+    %$state = ();
+    $state->{'level'} = $level;
+    $state->{'transaction'} = $args{'Transaction'};
+    $state->{'requestors'} = [ $self->InitialRequestors( $args{'Ticket'} ) ];
+    $state->{'owner'} = $self->InitialOwner( $args{'Ticket'} );
+    return;
+}
+
+sub OnRequestorChange {
+    my $self = shift;
+    my %args = ( Ticket => undef, Transaction => undef, State => undef, @_);
+
+    my $requestors = $self->State->{'requestors'};
+    if ( $args{'Transaction'}->Type eq 'AddWatcher' ) {
+        push @$requestors, $args{'Transaction'}->NewValue;
+    }
+    else {
+        my $id = $args{'Transaction'}->OldValue;
+        @$requestors = grep $_ != $id, @$requestors;
+    }
+}
+
+sub OnResponse {
+    my $self = shift;
+    my $self
+}
+
+sub IsRequestorsAct {
+    my $self = shift;
+    my $txn = shift;
+
+    my $actor = $txn->Creator;
+
+    # owner is always treated as non-requestor
+    return 0 if $actor == $self->State->{'owner'};
+    return 1 if grep $_ == $actor, @{ $self->State->{'requestors'} };
+
+    # in case requestor is a group
+    foreach my $id ( @{ $self->State->{'requestors'} } ){
+        my $cgm = RT::CachedGroupMember->new( $RT::SystemUser );
+        $cgm->LoadByCols( GroupId => $id, MemberId => $actor, Disabled => 0 );
+        return 1 if $cgm->id;
+    }
+    return 1;
+}
+
 sub InitialServiceLevel {
     my $self = shift;
     my $ticket = shift;
 
+    return $self->InitialValue(
+        Ticket   => $ticket,
+        Current  => $ticket->FirstCustomFieldValue('SLA'),
+        Criteria => { CustomField => [ map $_->id, $self->ServiceLevelCustomFields ] },
+    );
+}
+
+sub InitialRequestors {
+    my $self = shift;
+    my $ticket = shift;
+
+    my @current = map $_->Member, @{ $ticket->Requestors->MembersObj->ItemsArrayRef };
+
+    my $txns = $self->Transactions(
+        Ticket => $ticket,
+        Order => 'DESC',
+        Criteria => { 'AddWatcher' => 'Requestor', DelWatcher => 'Requestor' },
+    );
+    while ( my $txn = $txns->Next ) {
+        if ( $txn->Type eq 'AddWatcher' ) {
+            my $id = $txn->NewValue;
+            @current = grep $_ != $id, @current;
+        }
+        else {
+            push @current, $txn->OldValue;
+        }
+    }
+
+    return @current;
+}
+
+sub InitialOwner {
+    my $self = shift;
+    my $ticket = shift;
+
+    return $self->InitialValue(
+        %args,
+        Current => $ticket->Owner,
+        Criteria => { 'Set', 'Owner' },
+    );
+}
+
+sub InitialValue {
+    my $self = shift;
+    my %args = ( Ticket => undef, Current => undef, Criteria => {}, @_ );
+
+    my $txns = $self->Transactions( %args );
+    if ( my $first_change = $txns->First ) {
+        # intial value is old value of the first change
+        return $first_change->OldValue;
+    }
+
+    # no change -> initial value is the current
+    return $args{'Current'};
+}
+
+sub Transactions {
+    my $self = shift;
+    my %args = (Ticket => undef, Criteria => undef, Order => 'ASC', @_);
+
     my $txns = $ticket->Transactions;
-    foreach my $cf ( $self->ServiceLevelCustomFields ) {
-        $txns->_OpenParen('ServiceLevelCustomFields');
+
+    my $clause = 'ByTypeAndField';
+    while ( my ($type, $field) = each %{ $args{'Criteria'} } ) {
+        $txns->_OpenParen( $clause );
         $txns->Limit(
-            SUBCLAUSE       => 'ServiceLevelCustomFields',
             ENTRYAGGREGATOR => 'OR',
+            SUBCLAUSE       => $clause,
             FIELD           => 'Type',
-            VALUE           => 'CustomField',
-        );
-        $txns->Limit(
-            SUBCLAUSE       => 'ServiceLevelCustomFields',
-            ENTRYAGGREGATOR => 'AND',
-            FIELD           => 'Field',
-            VALUE           => $cf->id,
+            VALUE           => $type,
         );
-        $txns->_CloseParen('ServiceLevelCustomFields');
+        if ( $field ) {
+            my $tmp = ref $field? $field : [$field];
+            $txns->_OpenParen( $clause );
+            my $first = 1;
+            foreach my $value ( @$tmp ) {
+                $txns->Limit(
+                    SUBCLAUSE       => $clause,
+                    ENTRYAGGREGATOR => $first? 'AND' : 'OR',
+                    FIELD           => 'Field',
+                    VALUE           => $value,
+                );
+                $first = 0;
+            }
+            $txns->_CloseParen( $clause );
+        }
+        $txns->_CloseParen( $clause );
     }
+    $txns->OrderByCols(
+        { FIELD => 'Created', ORDER => $args{'Order'} },
+        { FIELD => 'id',      ORDER => $args{'Order'} },
+    );
 
-    return $self;
+    return $txns;
 }
 
 { my @cache = ();

commit 2d62d176c8d8c4c3cfeb84c62638208e3951ad2c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 2 22:49:34 2009 +0000

    another round, close to something testable

diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index 1b51c6b..6a98ce2 100644
--- a/lib/RT/Extension/SLA.pm
+++ b/lib/RT/Extension/SLA.pm
@@ -14,6 +14,14 @@ RT::Extension::SLA - Service Level Agreements for RT
 
 RT extension to implement automated due dates using service levels.
 
+=head1 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.
+
 =head1 INSTALL
 
 =over 4
@@ -45,7 +53,7 @@ There is no WebUI in the current version. Almost everything is
 controlled in the RT's config using option C<%RT::ServiceAgreements>
 and C<%RT::ServiceBusinessHours>. For example:
 
-    %RT::ServiceAgreements = (
+    Set( %ServiceAgreements,
         Default => '4h',
         QueueDefault => {
             'Incident' => '2h',
@@ -248,7 +256,7 @@ of requests that came into the system during the last night.
 In the config you can set one or more work schedules. Use the following
 format:
 
-    %RT::ServiceBusinessHours = (
+    Set( %ServiceBusinessHours,
         'Default' => {
             ... description ...
         },
@@ -274,7 +282,7 @@ hours.
 
 then %RT::ServiceBusinessHours should have the corresponding definition:
 
-    %RT::ServiceBusinessHours = (
+    Set( %ServiceBusinessHours,
         'work just in Monday' => {
             1 => { Name => 'Monday', Start => '9:00', End => '18:00' },
         },
@@ -286,14 +294,14 @@ Default Business Hours setting is in $RT::ServiceBusinessHours{'Default'}.
 
 In the config you can set per queue defaults, using:
 
-    %RT::ServiceAgreements = (
+    Set( %ServiceAgreements,
         Default => 'global default level of service',
         QueueDefault => {
             'queue name' => 'default value for this queue',
             ...
         },
         ...
-    };
+    );
 
 =head2 Access control
 
@@ -427,6 +435,12 @@ sub GetDefaultServiceLevel {
     return $RT::ServiceAgreements{'Default'};
 }
 
+sub ReportOnTicket {
+    my $self = shift;
+    my $id = shift;
+
+}
+
 =head1 TODO
 
     * [implemented, TODO: tests for options in the config] default SLA for queues
diff --git a/lib/RT/Extension/SLA/Report.pm b/lib/RT/Extension/SLA/Report.pm
index 52a254e..65ee23d 100644
--- a/lib/RT/Extension/SLA/Report.pm
+++ b/lib/RT/Extension/SLA/Report.pm
@@ -4,13 +4,29 @@ use warnings;
 
 package RT::Extension::SLA::Report;
 
-sub new {}
+sub new {
+    my $proto = shift;
+    my $self = bless {}, ref($proto)||$proto;
+    return $self->init( @_ );
+}
 
-sub init {}
+sub init {
+    my $self = shift;
+    my %args = (Ticket => undef, @_);
+    $self->{'Ticket'} = $args{'Ticket'} || die "boo";
+    $self->{'State'} = {};
+    $self->{'Stats'} = [];
+    return $self;
+}
 
 sub State {
     my $self = shift;
-    return $self->{State} ||= {};
+    return $self->{State};
+}
+
+sub Stats {
+    my $self = shift;
+    return $self->{Stats};
 }
 
 { my $cache;
@@ -33,9 +49,9 @@ sub Handlers {
     return $cache;
 } }
 
-sub Drive {
+sub Run {
     my $self = shift;
-    my $txns = shift;
+    my $txns = shift || $self->{'Ticket'}->Transactions;
 
     my $state = $self->State;
     my $handler = $self->Handlers;
@@ -53,20 +69,18 @@ sub Drive {
         }
         next unless $h;
 
-        $self->$h( Transaction => $txn, State => $state );
+        $self->$h( Ticket => $self->{'Ticket'}, Transaction => $txn, State => $state );
     }
+    return $self;
 }
 
 sub OnCreate {
     my $self = shift;
     my %args = ( Ticket => undef, Transaction => undef, State => undef, @_);
 
-    my $level = $self->InitialServiceLevel( $args{'Ticket'} );
-
     my $state = $args{'State'};
     %$state = ();
-    $state->{'level'} = $level;
-    $state->{'transaction'} = $args{'Transaction'};
+    $state->{'level'} = $self->InitialServiceLevel( $args{'Ticket'} );
     $state->{'requestors'} = [ $self->InitialRequestors( $args{'Ticket'} ) ];
     $state->{'owner'} = $self->InitialOwner( $args{'Ticket'} );
     return;
@@ -88,7 +102,76 @@ sub OnRequestorChange {
 
 sub OnResponse {
     my $self = shift;
-    my $self
+    my %args = ( Ticket => undef, Transaction => undef, State => undef, @_);
+
+    my $txn = $args{'Transaction'};
+    unless ( $args{'State'}->{'level'} ) {
+        $RT::Logger->debug('No service level -> ignore txn #'. $txn->id );
+        return;
+    }
+
+    my $act = $args{'State'}->{'act'};
+    if ( $self->IsRequestorsAct( $txn ) ) {
+        if ( $act && $act->{'requestor'} ) {
+            # several requestors' acts in a row don't move deadlines
+            return;
+        }
+        $act ||= $args{'State'}->{'act'} = {};
+
+        $act->{'requestor'} = 1;
+        $act->{'acted'} = $txn->CreatedObj->Unix;
+    } else {
+        unless ( $act ) {
+            die "not yet implemented";
+        }
+        unless ( $act->{'requestor'} ) {
+            # check keep in loop
+            my $deadline = RT::Extension::SLA->Due(
+                Type  => 'KeepInLoop',
+                Level => $args{'State'}->{'level'},
+                Time  => $args{'State'}->{'acted'},
+            );
+            unless ( defined $deadline ) {
+                $RT::Logger->debug( "Multiple non-requestors replies in a raw, without keep in loop deadline");
+                return;
+            }
+            # keep in loop
+            my $failed = $txn->CreatedObj->Unix > $deadline? 1 : 0;
+            my $owner = $args{'State'}->{'owner'} == $txn->Creator? 1 : 0;
+            my $stat = {
+                type      => 'KeepInLoop',
+                owner     => $args{'State'}->{'owner'},
+                failed    => $failed,
+                owner_act => $owner,
+                shift     => $txn->CreatedObj->Unix - $deadline,                
+            };
+            push @{ $self->Stats }, $stat;
+        }
+        else {
+            # check response
+            my $deadline = RT::Extension::SLA->Due(
+                Type  => 'Response',
+                Level => $args{'State'}->{'level'},
+                Time  => $args{'State'}->{'acted'},
+            );
+            unless ( defined $deadline ) {
+                $RT::Logger->debug( "Non-requestors' reply after requestors', without response deadline");
+                return;
+            }
+
+            # repsonse
+            my $failed = $txn->CreatedObj->Unix > $deadline? 1 : 0;
+            my $owner = $args{'State'}->{'owner'} == $txn->Creator? 1 : 0;
+            my $stat = {
+                type      => 'KeepInLoop',
+                owner     => $args{'State'}->{'owner'},
+                failed    => $failed,
+                owner_act => $owner,
+                shift     => $txn->CreatedObj->Unix - $deadline,                
+            };
+            push @{ $self->Stats }, $stat;
+        }
+    }
 }
 
 sub IsRequestorsAct {
@@ -107,7 +190,7 @@ sub IsRequestorsAct {
         $cgm->LoadByCols( GroupId => $id, MemberId => $actor, Disabled => 0 );
         return 1 if $cgm->id;
     }
-    return 1;
+    return 0;
 }
 
 sub InitialServiceLevel {
@@ -147,11 +230,10 @@ sub InitialRequestors {
 
 sub InitialOwner {
     my $self = shift;
-    my $ticket = shift;
-
+    my %args = (Ticket => undef, @_);
     return $self->InitialValue(
         %args,
-        Current => $ticket->Owner,
+        Current => $args{'Ticket'}->Owner,
         Criteria => { 'Set', 'Owner' },
     );
 }
@@ -174,7 +256,7 @@ sub Transactions {
     my $self = shift;
     my %args = (Ticket => undef, Criteria => undef, Order => 'ASC', @_);
 
-    my $txns = $ticket->Transactions;
+    my $txns = $args{'Ticket'}->Transactions;
 
     my $clause = 'ByTypeAndField';
     while ( my ($type, $field) = each %{ $args{'Criteria'} } ) {
diff --git a/t/basics.t b/t/basics.t
index 8d73e46..998b596 100644
--- a/t/basics.t
+++ b/t/basics.t
@@ -3,9 +3,10 @@
 use strict;
 use warnings;
 
-use Test::More tests => 1;
+use Test::More tests => 2;
 
 use_ok 'RT::Extension::SLA';
+use_ok 'RT::Extension::SLA::Report';
 
 
 1;

commit ef5d140e7594f7146a4983db5b9cbfcbaa6c124c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 4 19:13:52 2009 +0000

    update TODO with some ideas

diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index 6a98ce2..8642296 100644
--- a/lib/RT/Extension/SLA.pm
+++ b/lib/RT/Extension/SLA.pm
@@ -441,7 +441,20 @@ sub ReportOnTicket {
 
 }
 
-=head1 TODO
+=head1 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
 
@@ -451,8 +464,6 @@ sub ReportOnTicket {
       something else). So people would be able to handle tickets in the right
       order using Due dates.
 
-    * [not implemented] WebUI
-
 =head1 DESIGN
 
 =head2 Classes

commit 665cceb7b8a41e80e912b84aede37ce46b95a092
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon May 4 22:32:51 2009 +0000

    more changes here and there regarding reporting
    * new Summary.pm to help combine multiple reports
      together

diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index 8642296..aa4911c 100644
--- a/lib/RT/Extension/SLA.pm
+++ b/lib/RT/Extension/SLA.pm
@@ -435,10 +435,12 @@ sub GetDefaultServiceLevel {
     return $RT::ServiceAgreements{'Default'};
 }
 
-sub ReportOnTicket {
+sub Report {
     my $self = shift;
-    my $id = shift;
+    my $ticket = shift;
 
+    require RT::Extension::SLA::Report;
+    return RT::Extension::SLA::Report->new( Ticket => $ticket )->Run;
 }
 
 =head1 TODO and CAVEATS
diff --git a/lib/RT/Extension/SLA/Report.pm b/lib/RT/Extension/SLA/Report.pm
index 65ee23d..dbb10ff 100644
--- a/lib/RT/Extension/SLA/Report.pm
+++ b/lib/RT/Extension/SLA/Report.pm
@@ -19,6 +19,31 @@ sub init {
     return $self;
 }
 
+sub Run {
+    my $self = shift;
+    my $txns = shift || $self->{'Ticket'}->Transactions;
+
+    my $state = $self->State;
+    my $handler = $self->Handlers;
+
+    while ( my $txn = $txns->Next ) {
+        my ($type, $field) = ($txn->Type, $txn->Field);
+
+        my $h = $handler->{ $type };
+        unless ( $h ) {
+            $RT::Logger->debug( "No handler for $type transaction, skipping" );
+        } elsif ( ref $h ) {
+            unless ( $h = $h->{ $field } ) {
+                $RT::Logger->debug( "No handler for ($type, $field) transaction, skipping" );
+            }
+        }
+        next unless $h;
+
+        $self->$h( Ticket => $self->{'Ticket'}, Transaction => $txn, State => $state );
+    }
+    return $self;
+}
+
 sub State {
     my $self = shift;
     return $self->{State};
@@ -41,49 +66,27 @@ sub Handlers {
             Owner => 'OnOwnerChange',
         },
         Correspond => 'OnResponse',
-        CustomField => { map $_ => 'OnServiceLevelChange', $self->ServiceLevelCustomFields },
+        CustomField => { map { $_->id => 'OnServiceLevelChange' } $self->ServiceLevelCustomFields },
         AddWatcher => { Requestor => 'OnRequestorChange' },
         DelWatcher => { Requestor => 'OnRequestorChange' },
     };
 
+    use Data::Dumper;
+    Test::More::diag( Dumper $cache );
+
     return $cache;
 } }
 
-sub Run {
-    my $self = shift;
-    my $txns = shift || $self->{'Ticket'}->Transactions;
-
-    my $state = $self->State;
-    my $handler = $self->Handlers;
-
-    while ( my $txn = $txns->Next ) {
-        my ($type, $field) = ($txn->Type, $txn->Field);
-
-        my $h = $handler->{ $type };
-        unless ( $h ) {
-            $RT::Logger->debug( "No handler for $type transaction, skipping" );
-        } elsif ( ref $h ) {
-            unless ( $h = $h->{ $field } ) {
-                $RT::Logger->debug( "No handler for ($type, $field) transaction, skipping" );
-            }
-        }
-        next unless $h;
-
-        $self->$h( Ticket => $self->{'Ticket'}, Transaction => $txn, State => $state );
-    }
-    return $self;
-}
-
 sub OnCreate {
     my $self = shift;
     my %args = ( Ticket => undef, Transaction => undef, State => undef, @_);
 
     my $state = $args{'State'};
     %$state = ();
-    $state->{'level'} = $self->InitialServiceLevel( $args{'Ticket'} );
-    $state->{'requestors'} = [ $self->InitialRequestors( $args{'Ticket'} ) ];
-    $state->{'owner'} = $self->InitialOwner( $args{'Ticket'} );
-    return;
+    $state->{'level'} = $self->InitialServiceLevel( Ticket => $args{'Ticket'} );
+    $state->{'requestors'} = [ $self->InitialRequestors( Ticket => $args{'Ticket'} ) ];
+    $state->{'owner'} = $self->InitialOwner( Ticket => $args{'Ticket'} );
+    return $self->OnResponse( %args );
 }
 
 sub OnRequestorChange {
@@ -100,15 +103,21 @@ sub OnRequestorChange {
     }
 }
 
+sub OnServiceLevelChange {
+    my $self = shift;
+    my %args = ( Ticket => undef, Transaction => undef, State => undef, @_);
+    $self->State->{'level'} = $args{'Transaction'}->NewValue;
+}
+
 sub OnResponse {
     my $self = shift;
     my %args = ( Ticket => undef, Transaction => undef, State => undef, @_);
 
     my $txn = $args{'Transaction'};
-    unless ( $args{'State'}->{'level'} ) {
-        $RT::Logger->debug('No service level -> ignore txn #'. $txn->id );
-        return;
-    }
+#    unless ( $args{'State'}->{'level'} ) {
+#        $RT::Logger->debug('No service level -> ignore txn #'. $txn->id );
+#        return;
+#    }
 
     my $act = $args{'State'}->{'act'};
     if ( $self->IsRequestorsAct( $txn ) ) {
@@ -143,6 +152,7 @@ sub OnResponse {
                 owner     => $args{'State'}->{'owner'},
                 failed    => $failed,
                 owner_act => $owner,
+                actor     => $txn->Creator,
                 shift     => $txn->CreatedObj->Unix - $deadline,                
             };
             push @{ $self->Stats }, $stat;
@@ -152,22 +162,25 @@ sub OnResponse {
             my $deadline = RT::Extension::SLA->Due(
                 Type  => 'Response',
                 Level => $args{'State'}->{'level'},
-                Time  => $args{'State'}->{'acted'},
+                Time  => $args{'State'}->{'act'}->{'acted'},
             );
             unless ( defined $deadline ) {
                 $RT::Logger->debug( "Non-requestors' reply after requestors', without response deadline");
                 return;
             }
 
+            Test::More::diag( 'deadline '. $deadline .' '. Dumper( $args{'State'} ) );
+
             # repsonse
             my $failed = $txn->CreatedObj->Unix > $deadline? 1 : 0;
             my $owner = $args{'State'}->{'owner'} == $txn->Creator? 1 : 0;
             my $stat = {
-                type      => 'KeepInLoop',
+                type      => 'Response',
                 owner     => $args{'State'}->{'owner'},
                 failed    => $failed,
                 owner_act => $owner,
-                shift     => $txn->CreatedObj->Unix - $deadline,                
+                actor     => $txn->Creator,
+                shift     => ($txn->CreatedObj->Unix - $deadline),
             };
             push @{ $self->Stats }, $stat;
         }
@@ -195,23 +208,23 @@ sub IsRequestorsAct {
 
 sub InitialServiceLevel {
     my $self = shift;
-    my $ticket = shift;
+    my %args = @_;
 
     return $self->InitialValue(
-        Ticket   => $ticket,
-        Current  => $ticket->FirstCustomFieldValue('SLA'),
+        Ticket   => $args{'Ticket'},
+        Current  => $args{'Ticket'}->FirstCustomFieldValue('SLA'),
         Criteria => { CustomField => [ map $_->id, $self->ServiceLevelCustomFields ] },
     );
 }
 
 sub InitialRequestors {
     my $self = shift;
-    my $ticket = shift;
+    my %args = @_;
 
-    my @current = map $_->Member, @{ $ticket->Requestors->MembersObj->ItemsArrayRef };
+    my @current = map $_->MemberId, @{ $args{'Ticket'}->Requestors->MembersObj->ItemsArrayRef };
 
     my $txns = $self->Transactions(
-        Ticket => $ticket,
+        Ticket => $args{'Ticket'},
         Order => 'DESC',
         Criteria => { 'AddWatcher' => 'Requestor', DelWatcher => 'Requestor' },
     );
@@ -302,9 +315,7 @@ sub ServiceLevelCustomFields {
     $cfs->Limit( FIELD => 'LookupType', VALUE => RT::Ticket->CustomFieldLookupType );
     # XXX: limit to applied custom fields only
 
-    push @cache, $_ while $_ = $cfs->Next;
-
-    return @cache;
+    return @cache = @{ $cfs->ItemsArrayRef };
 } }
 
 1;
diff --git a/lib/RT/Extension/SLA/Summary.pm b/lib/RT/Extension/SLA/Summary.pm
new file mode 100644
index 0000000..b59ba9d
--- /dev/null
+++ b/lib/RT/Extension/SLA/Summary.pm
@@ -0,0 +1,68 @@
+use 5.8.0;
+use strict;
+use warnings;
+
+package RT::Extension::SLA::Summary;
+
+sub new {
+    my $proto = shift;
+    my $self = bless {}, ref($proto)||$proto;
+    return $self->init( @_ );
+}
+
+sub init {
+    my $self = shift;
+    return $self;
+}
+
+sub Result {
+    my $self = shift;
+    return $self->{'Result'} ||= { };
+}
+
+sub AddReport {
+    my $self = shift;
+    my $report = shift;
+
+    my $new = $self->OnReport( $report );
+
+    my $total = $self->Result;
+    while ( my ($user, $stat) = each %$new ) {
+        my $tmp = $total->{$user} ||= {};
+        while ( my ($action, $count) = each %$stat ) {
+            $tmp->{$action} += $count;
+        }
+    }
+
+    return $self;
+}
+
+sub OnReport {
+    my $self = shift;
+    my $report = shift;
+
+    my $res = {};
+    foreach my $stat ( @{ $report->Stats } ) {
+        if ( $stat->{'owner_act'} ) {
+            my $owner = $res->{ $stat->{'owner'} } ||= { };
+            if ( $stat->{'failed'} ) {
+                $owner->{'failed'}++;
+            } else {
+                $owner->{'passed'}++;
+            }
+        } else {
+            my $owner = $res->{ $stat->{'owner'} } ||= { };
+            my $actor = $res->{ $stat->{'actor'} } ||= { };
+            if ( $stat->{'failed'} ) {
+                $owner->{'failed'}++;
+                $actor->{'late help'}++;
+            } else {
+                $owner->{'got help'}++;
+                $actor->{'helped'}++;
+            }
+        }
+    }
+    return $res;
+}
+
+1;

commit 247c990a939bd152f77ad64cdb681742dde96b36
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 5 01:11:11 2009 +0000

    add first html, more simple tests and changes Summary

diff --git a/META.yml b/META.yml
index c96930b..89aa5fb 100644
--- a/META.yml
+++ b/META.yml
@@ -17,6 +17,7 @@ name: RT-Extension-SLA
 no_index:
   directory:
     - etc
+    - html
     - inc
     - t
 requires:
diff --git a/html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default b/html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default
new file mode 100644
index 0000000..73c310b
--- /dev/null
+++ b/html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default
@@ -0,0 +1,9 @@
+<%ARGS>
+$tabs => {}
+</%ARGS>
+<%INIT>
+$tabs->{'s'} = {
+    title => loc('Service Level Aggreements'),
+    path  => 'Tools/Reports/SLA.html',
+};
+</%INIT>
diff --git a/html/Tools/Reports/SLA.html b/html/Tools/Reports/SLA.html
new file mode 100644
index 0000000..1cdeb03
--- /dev/null
+++ b/html/Tools/Reports/SLA.html
@@ -0,0 +1,43 @@
+<& /Elements/Header, Title => $title &>
+<& /Tools/Reports/Elements/Tabs, current_tab => 'Tools/Reports/SLA.html', Title => $title &>
+
+<table>
+<tr>
+<th><% loc('Owner') %></th>
+% my @columns = $summary->Labels;
+% my $i = 0;
+% foreach ( map $_->[0], grep $i++%2, @columns ) {
+<th><% loc($_) %></th>
+% }
+</tr>
+
+% while ( my ($owner, $stats) = each %$result ) {
+    <tr><td><% $owner %><td>
+% my $i = 1;
+% foreach ( map $stats->{ $_ }, grep $i++%2, @columns ) {
+<td><% $_ || 0 %></td>
+% }
+    </tr>
+% }
+</table>
+
+<%ARGS>
+$Query => undef
+</%ARGS>
+<%INIT>
+my $title = loc("Report on Service Level Agreements");
+
+use RT::Extension::SLA::Summary;
+my $summary = new RT::Extension::SLA::Summary;
+
+my $tickets = RT::Tickets->new( $session{'CurrentUser'} );
+$tickets->FromSQL( $Query );
+$tickets->OrderByCols( {FIELD => 'id', ORDER => 'ASC'} );
+while ( my $ticket = $tickets->Next ) {
+    my $report = RT::Extension::SLA->Report( Ticket => $ticket );
+    $summary->AddReport( $report );
+}
+
+my $result = $summary->Result;
+
+</%INIT>
diff --git a/lib/RT/Extension/SLA/Summary.pm b/lib/RT/Extension/SLA/Summary.pm
index b59ba9d..a0dc52f 100644
--- a/lib/RT/Extension/SLA/Summary.pm
+++ b/lib/RT/Extension/SLA/Summary.pm
@@ -20,6 +20,18 @@ sub Result {
     return $self->{'Result'} ||= { };
 }
 
+our @known_stats = (
+    'passed' => ['Passed', 'Replied before a deadline'],
+    'failed' => ['Failed', 'Replied after a deadline or not replied at all'],
+    'helped' => ['Helped', 'Helped another user to reach a deadline'],
+    'late help' => ['Helped (late)', 'Helped another user, however failed a deadline'],
+    'got help'  => ['Got help', 'Got help from another user within a deadline'],
+);
+
+sub Labels {
+    return @known_stats;
+}
+
 sub AddReport {
     my $self = shift;
     my $report = shift;
diff --git a/t/basics.t b/t/basics.t
index 998b596..08d6b46 100644
--- a/t/basics.t
+++ b/t/basics.t
@@ -3,10 +3,11 @@
 use strict;
 use warnings;
 
-use Test::More tests => 2;
+use Test::More tests => 3;
 
 use_ok 'RT::Extension::SLA';
 use_ok 'RT::Extension::SLA::Report';
+use_ok 'RT::Extension::SLA::Summary';
 
 
 1;

commit a9834df159c55da4e98cb1288e31c00fceeeca62
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 5 01:50:43 2009 +0000

    cleanup debug code
    * fix some bugs

diff --git a/html/Tools/Reports/SLA.html b/html/Tools/Reports/SLA.html
index 1cdeb03..df07885 100644
--- a/html/Tools/Reports/SLA.html
+++ b/html/Tools/Reports/SLA.html
@@ -12,12 +12,13 @@
 </tr>
 
 % while ( my ($owner, $stats) = each %$result ) {
-    <tr><td><% $owner %><td>
+<tr>
+<td><% $owner %></td>
 % my $i = 1;
 % foreach ( map $stats->{ $_ }, grep $i++%2, @columns ) {
 <td><% $_ || 0 %></td>
 % }
-    </tr>
+</tr>
 % }
 </table>
 
@@ -34,10 +35,9 @@ my $tickets = RT::Tickets->new( $session{'CurrentUser'} );
 $tickets->FromSQL( $Query );
 $tickets->OrderByCols( {FIELD => 'id', ORDER => 'ASC'} );
 while ( my $ticket = $tickets->Next ) {
-    my $report = RT::Extension::SLA->Report( Ticket => $ticket );
+    my $report = RT::Extension::SLA->Report( $ticket );
     $summary->AddReport( $report );
 }
 
 my $result = $summary->Result;
-
 </%INIT>
diff --git a/lib/RT/Extension/SLA/Report.pm b/lib/RT/Extension/SLA/Report.pm
index dbb10ff..9d0d8c2 100644
--- a/lib/RT/Extension/SLA/Report.pm
+++ b/lib/RT/Extension/SLA/Report.pm
@@ -39,6 +39,8 @@ sub Run {
         }
         next unless $h;
 
+        $RT::Logger->debug( "Handling transaction #". $txn->id ." ($type, $field) of ticket #". $self->{'Ticket'}->id );
+
         $self->$h( Ticket => $self->{'Ticket'}, Transaction => $txn, State => $state );
     }
     return $self;
@@ -71,9 +73,6 @@ sub Handlers {
         DelWatcher => { Requestor => 'OnRequestorChange' },
     };
 
-    use Data::Dumper;
-    Test::More::diag( Dumper $cache );
-
     return $cache;
 } }
 
@@ -131,7 +130,10 @@ sub OnResponse {
         $act->{'acted'} = $txn->CreatedObj->Unix;
     } else {
         unless ( $act ) {
-            die "not yet implemented";
+            $act = $args{'State'}->{'act'} = {};
+            $act->{'requestor'} = 0;
+            $act->{'acted'} = $txn->CreatedObj->Unix;
+            return;
         }
         unless ( $act->{'requestor'} ) {
             # check keep in loop
@@ -169,8 +171,6 @@ sub OnResponse {
                 return;
             }
 
-            Test::More::diag( 'deadline '. $deadline .' '. Dumper( $args{'State'} ) );
-
             # repsonse
             my $failed = $txn->CreatedObj->Unix > $deadline? 1 : 0;
             my $owner = $args{'State'}->{'owner'} == $txn->Creator? 1 : 0;

commit 9cbc83669fb5e477c134b3eef510813077e86dbd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 5 14:46:40 2009 +0000

    show user using a component
    * show some simple form

diff --git a/html/Tools/Reports/SLA.html b/html/Tools/Reports/SLA.html
index df07885..d50bbc8 100644
--- a/html/Tools/Reports/SLA.html
+++ b/html/Tools/Reports/SLA.html
@@ -12,8 +12,10 @@
 </tr>
 
 % while ( my ($owner, $stats) = each %$result ) {
+% my $user = RT::User->new( $session{'CurrentUser'} );
+% $user->Load( $owner );
 <tr>
-<td><% $owner %></td>
+<td><& /Elements/ShowUser, User => $user &></td>
 % my $i = 1;
 % foreach ( map $stats->{ $_ }, grep $i++%2, @columns ) {
 <td><% $_ || 0 %></td>
@@ -22,6 +24,11 @@
 % }
 </table>
 
+<form method="post" action="SLA.html">
+<&|/l&>Query</&>:<textarea cols="60" rows="20" name="Query"><% $Query %></textarea>
+<& /Elements/Submit, Label => loc('Update report') &>
+</form>
+
 <%ARGS>
 $Query => undef
 </%ARGS>

commit 56dceb3fe9ff8aa0d990068794d460594818d86d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 5 15:36:23 2009 +0000

    new test file, basics of reporting

diff --git a/t/reporting/basic.t b/t/reporting/basic.t
new file mode 100644
index 0000000..55befef
--- /dev/null
+++ b/t/reporting/basic.t
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::MockTime qw(set_fixed_time);
+
+use Test::More tests => 72;
+
+require 't/utils.pl';
+
+use_ok 'RT';
+RT::LoadConfig();
+$RT::LogToScreen = $ENV{'TEST_VERBOSE'} ? 'debug': 'warning';
+RT::Init();
+
+use_ok 'RT::Ticket';
+use_ok 'RT::Extension::SLA::Report';
+
+my $root = RT::User->new( $RT::SystemUser );
+$root->LoadByEmail('root at localhost');
+ok $root->id, 'loaded root user';
+
+diag '';
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => { Response => { RealMinutes => 60*2 } },
+        },
+    );
+
+    set_fixed_time('2009-05-05T10:00:00Z');
+
+    my $time = time;
+
+    # requestor creates
+    my $id;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $root->id );
+        ok $id, "created ticket #$id";
+
+        is $ticket->FirstCustomFieldValue('SLA'), '2', 'default sla';
+
+        my $due = $ticket->DueObj->Unix;
+        is $due, $time + 2*60*60, 'Due date is two hours from "now"';
+    }
+
+    set_fixed_time('2009-05-05T11:00:00Z');
+
+    # non-requestor reply
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are working on this.' );
+    }
+
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    $ticket->Load( $id );
+    my $report = RT::Extension::SLA::Report->new( Ticket => $ticket )->Run;
+    is_deeply $report->Stats,
+        [ {type => 'Response', owner => $RT::Nobody->id, owner_act => 0, failed => 0, shift => -3600 } ],
+        'correct stats'
+    ;
+}
+
+
+diag '';
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => { Response => { RealMinutes => 60*2 } },
+        },
+    );
+
+    set_fixed_time('2009-05-05T10:00:00Z');
+
+    my $time = time;
+
+    # requestor creates
+    my $id;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx', Requestor => $root->id );
+        ok $id, "created ticket #$id";
+
+        is $ticket->FirstCustomFieldValue('SLA'), '2', 'default sla';
+
+        my $due = $ticket->DueObj->Unix;
+        is $due, $time + 2*60*60, 'Due date is two hours from "now"';
+    }
+
+    set_fixed_time('2009-05-05T11:00:00Z');
+
+    # non-requestor reply
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are working on this.' );
+    }
+
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    $ticket->Load( $id );
+    my $report = RT::Extension::SLA::Report->new( Ticket => $ticket )->Run;
+    is_deeply $report->Stats,
+        [ {type => 'Response', owner => $RT::Nobody->id, owner_act => 0, failed => 0, shift => -3600 } ],
+        'correct stats'
+    ;
+}
+
+

commit ea3e755479c5e577557530cc293f6e482893b72b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 5 15:37:19 2009 +0000

    bump dev. version, update manifest and meta

diff --git a/MANIFEST b/MANIFEST
index 22c271a..c76a77e 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,5 +1,7 @@
 Changes
 etc/initialdata
+html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default
+html/Tools/Reports/SLA.html
 inc/Module/AutoInstall.pm
 inc/Module/Install.pm
 inc/Module/Install/AutoInstall.pm
@@ -23,6 +25,8 @@ lib/RT/Condition/SLA_RequireDefault.pm
 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/Summary.pm
 lib/RT/Queue_SLA.pm
 Makefile.PL
 MANIFEST			This list of files
@@ -31,5 +35,6 @@ t/basics.t
 t/business_hours.t
 t/due.t
 t/queue.t
+t/reporting/basic.t
 t/starts.t
 t/utils.pl
diff --git a/META.yml b/META.yml
index 89aa5fb..4c8edd2 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.03
+version: 0.03_01
diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index aa4911c..a8af3eb 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.03';
+our $VERSION = '0.03_01';
 
 =head1 NAME
 

commit 2acbcef68d6bf9ffa82cc12e58998eb337ecce31
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 13 21:56:44 2009 +0000

    update M::I

diff --git a/inc/Module/AutoInstall.pm b/inc/Module/AutoInstall.pm
index 739bc85..38c98f5 100644
--- a/inc/Module/AutoInstall.pm
+++ b/inc/Module/AutoInstall.pm
@@ -18,7 +18,9 @@ my %FeatureMap = (
 
 # various lexical flags
 my ( @Missing, @Existing,  %DisabledTests, $UnderCPAN,     $HasCPANPLUS );
-my ( $Config,  $CheckOnly, $SkipInstall,   $AcceptDefault, $TestOnly );
+my (
+    $Config, $CheckOnly, $SkipInstall, $AcceptDefault, $TestOnly, $AllDeps
+);
 my ( $PostambleActions, $PostambleUsed );
 
 # See if it's a testing or non-interactive session
@@ -73,6 +75,9 @@ sub _init {
         elsif ( $arg =~ /^--test(?:only)?$/ ) {
             $TestOnly = 1;
         }
+        elsif ( $arg =~ /^--all(?:deps)?$/ ) {
+            $AllDeps = 1;
+        }
     }
 }
 
@@ -115,7 +120,12 @@ sub import {
         )[0]
     );
 
-    $UnderCPAN = _check_lock(1);    # check for $UnderCPAN
+    # We want to know if we're under CPAN early to avoid prompting, but
+    # if we aren't going to try and install anything anyway then skip the
+    # check entirely since we don't want to have to load (and configure)
+    # an old CPAN just for a cosmetic message
+
+    $UnderCPAN = _check_lock(1) unless $SkipInstall;
 
     while ( my ( $feature, $modules ) = splice( @args, 0, 2 ) ) {
         my ( @required, @tests, @skiptests );
@@ -187,6 +197,7 @@ sub import {
             and (
                 $CheckOnly
                 or ($mandatory and $UnderCPAN)
+                or $AllDeps
                 or _prompt(
                     qq{==> Auto-install the }
                       . ( @required / 2 )
@@ -235,21 +246,35 @@ sub import {
     *{'main::WriteMakefile'} = \&Write if caller(0) eq 'main';
 }
 
+sub _running_under {
+    my $thing = shift;
+    print <<"END_MESSAGE";
+*** Since we're running under ${thing}, I'll just let it take care
+    of the dependency's installation later.
+END_MESSAGE
+    return 1;
+}
+
 # Check to see if we are currently running under CPAN.pm and/or CPANPLUS;
 # if we are, then we simply let it taking care of our dependencies
 sub _check_lock {
     return unless @Missing or @_;
 
+    my $cpan_env = $ENV{PERL5_CPAN_IS_RUNNING};
+
     if ($ENV{PERL5_CPANPLUS_IS_RUNNING}) {
-        print <<'END_MESSAGE';
+        return _running_under($cpan_env ? 'CPAN' : 'CPANPLUS');
+    }
 
-*** Since we're running under CPANPLUS, I'll just let it take care
-    of the dependency's installation later.
-END_MESSAGE
-        return 1;
+    require CPAN;
+
+    if ($CPAN::VERSION > '1.89' && $cpan_env) {
+        return _running_under('CPAN');
     }
 
-    _load_cpan();
+    # last ditch attempt, this -will- configure CPAN, very sorry
+
+    _load_cpan(1); # force initialize even though it's already loaded
 
     # Find the CPAN lock-file
     my $lock = MM->catfile( $CPAN::Config->{cpan_home}, ".lock" );
@@ -633,7 +658,7 @@ sub _load {
 
 # Load CPAN.pm and it's configuration
 sub _load_cpan {
-    return if $CPAN::VERSION;
+    return if $CPAN::VERSION and not @_;
     require CPAN;
     if ( $CPAN::HandleConfig::VERSION ) {
         # Newer versions of CPAN have a HandleConfig module
@@ -766,4 +791,4 @@ END_MAKE
 
 __END__
 
-#line 1004
+#line 1045
diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
index 5b9ddbf..fadb682 100644
--- a/inc/Module/Install.pm
+++ b/inc/Module/Install.pm
@@ -28,7 +28,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 = '0.85';
+	$VERSION = '0.87';
 
 	# Storage for the pseudo-singleton
 	$MAIN    = undef;
diff --git a/inc/Module/Install/AutoInstall.pm b/inc/Module/Install/AutoInstall.pm
index b7e92a5..562bd0a 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 $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.85';
+	$VERSION = '0.87';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
index ac416c9..211585a 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 = '0.85';
+	$VERSION = '0.87';
 }
 
 # Suspend handler for "redefined" warnings
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
index 3e2d523..ef98ee4 100644
--- a/inc/Module/Install/Can.pm
+++ b/inc/Module/Install/Can.pm
@@ -9,7 +9,7 @@ use ExtUtils::MakeMaker ();
 
 use vars qw{$VERSION $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.85';
+	$VERSION = '0.87';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
index 0a62208..763732d 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 $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.85';
+	$VERSION = '0.87';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Include.pm b/inc/Module/Install/Include.pm
index 92aad58..406da54 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 $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.85';
+	$VERSION = '0.87';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
index 2b80f0f..b1854f6 100644
--- a/inc/Module/Install/Makefile.pm
+++ b/inc/Module/Install/Makefile.pm
@@ -7,7 +7,7 @@ use ExtUtils::MakeMaker ();
 
 use vars qw{$VERSION $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.85';
+	$VERSION = '0.87';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
index ca16db7..6852f9a 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 = '0.85';
+	$VERSION = '0.87';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
index c00da94..5d11a43 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 = '0.85';
+	$VERSION = '0.87';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
index df3900a..a73f351 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 = '0.85';
+	$VERSION = '0.87';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }

commit 2f1294f01335c2e1ea561a9771958e73d6578ed1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 13 21:58:49 2009 +0000

    add protection by a right

diff --git a/META.yml b/META.yml
index 4c8edd2..a510d6f 100644
--- a/META.yml
+++ b/META.yml
@@ -8,7 +8,7 @@ build_requires:
 configure_requires:
   ExtUtils::MakeMaker: 6.42
 distribution_type: module
-generated_by: 'Module::Install version 0.85'
+generated_by: 'Module::Install version 0.87'
 license: gpl2
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
diff --git a/html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default b/html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default
index 73c310b..d1f352c 100644
--- a/html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default
+++ b/html/Callbacks/RT-Extension-SLA/Tools/Reports/Elements/Tabs/Default
@@ -2,6 +2,9 @@
 $tabs => {}
 </%ARGS>
 <%INIT>
+return unless $session{'CurrentUser'}->PrincipalObj->HasRight(
+    Object => $RT::System, Right => 'SeeSLAReports',
+);
 $tabs->{'s'} = {
     title => loc('Service Level Aggreements'),
     path  => 'Tools/Reports/SLA.html',
diff --git a/html/Tools/Reports/SLA.html b/html/Tools/Reports/SLA.html
index d50bbc8..ee2f6ae 100644
--- a/html/Tools/Reports/SLA.html
+++ b/html/Tools/Reports/SLA.html
@@ -33,6 +33,14 @@
 $Query => undef
 </%ARGS>
 <%INIT>
+unless (
+    $session{'CurrentUser'}->PrincipalObj->HasRight(
+        Object => $RT::System, Right => 'SeeSLAReports',
+    )
+) {
+    Abort("You're not allowed to see SLA reports.");
+}
+
 my $title = loc("Report on Service Level Agreements");
 
 use RT::Extension::SLA::Summary;
diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index a8af3eb..8a021d3 100644
--- a/lib/RT/Extension/SLA.pm
+++ b/lib/RT/Extension/SLA.pm
@@ -316,6 +316,14 @@ Just grant them ModifyCustomField right.
 
 =cut
 
+{
+    my $right = 'SeeSLAReports';
+    use RT::System;
+    $RT::System::Rights->{$right} = 'See service level performance reports';
+    use RT::ACE;
+    $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
+}
+
 sub BusinessHours {
     my $self = shift;
     my $name = shift || 'Default';

commit 5548273e39c39d0bfb3c02e39e0e0a65d75c1bba
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 13 22:00:18 2009 +0000

    add tabs to tickets so you can jump to a report right after search

diff --git a/html/Callbacks/RT-Extension-SLA/Ticket/Elements/Tabs/Default b/html/Callbacks/RT-Extension-SLA/Ticket/Elements/Tabs/Default
new file mode 100644
index 0000000..d8bbb01
--- /dev/null
+++ b/html/Callbacks/RT-Extension-SLA/Ticket/Elements/Tabs/Default
@@ -0,0 +1,15 @@
+<%ARGS>
+$Query => undef
+$tabs => {}
+</%ARGS>
+<%INIT>
+
+$Query ||= $session{'CurrentSearchHash'}->{'Query'};
+
+return unless $Query;
+
+$tabs->{"m"} = {
+    path  => "Tools/Reports/SLA.html?". $m->comp( '/Elements/QueryString', Query => $Query ),
+    title => loc('Report SLA'),
+};
+</%INIT>

commit 50b61707c22e1970103500d3eb0d16a7337ff024
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 14 14:27:44 2009 +0000

    add report per ticket for debugging and analysis

diff --git a/html/Callbacks/RT-Extension-SLA/Ticket/Elements/Tabs/Default b/html/Callbacks/RT-Extension-SLA/Ticket/Elements/Tabs/Default
index d8bbb01..76253a0 100644
--- a/html/Callbacks/RT-Extension-SLA/Ticket/Elements/Tabs/Default
+++ b/html/Callbacks/RT-Extension-SLA/Ticket/Elements/Tabs/Default
@@ -1,15 +1,24 @@
 <%ARGS>
 $Query => undef
 $tabs => {}
+$Ticket => undef
 </%ARGS>
 <%INIT>
 
-$Query ||= $session{'CurrentSearchHash'}->{'Query'};
+return unless $session{'CurrentUser'}->PrincipalObj->HasRight(
+        Object => $RT::System, Right => 'SeeSLAReports',
+);
 
-return unless $Query;
-
-$tabs->{"m"} = {
-    path  => "Tools/Reports/SLA.html?". $m->comp( '/Elements/QueryString', Query => $Query ),
-    title => loc('Report SLA'),
-};
+if ( $Ticket ) {
+    $tabs->{'this'}->{"subtabs"}->{'_DA'} = {
+        path  => "Ticket/SLA.html?id=". $Ticket->id,
+        title => loc('Report SLA'),
+    };
+}
+elsif ( $Query ||= $session{'CurrentSearchHash'}->{'Query'} ) {
+    $tabs->{"m"} = {
+        path  => "Tools/Reports/SLA.html?". $m->comp( '/Elements/QueryString', Query => $Query ),
+        title => loc('Report SLA'),
+    };
+}
 </%INIT>
diff --git a/html/Ticket/SLA.html b/html/Ticket/SLA.html
new file mode 100644
index 0000000..0cde6da
--- /dev/null
+++ b/html/Ticket/SLA.html
@@ -0,0 +1,46 @@
+<& /Elements/Header, Title => $title &>
+<& /Ticket/Elements/Tabs, 
+    Ticket => $ticket,
+    current_tab => "Ticket/SLA.html?id=$id",
+    Title => $title,
+&>
+
+<table>
+<tr><th>#</th><th>Description</th><th>Type</th><th>Owner</th><th>Failed</th><th>Shift</th></tr>
+% foreach my $stat ( @{ $report->Stats } ) {
+<tr>
+<td><% $stat->{transaction}->id %></td>
+<td><% $stat->{transaction}->Description %></td>
+<td><% $stat->{owner_act}? 'yes' : 'no' %></td>
+<td><% $stat->{failed}? 'yes' : 'no' %></td>
+<td><% $stat->{shift} %></td>
+</tr>
+% }
+</table>
+
+<%ARGS>
+$id => undef
+</%ARGS>
+<%INIT>
+
+unless (
+    $session{'CurrentUser'}->PrincipalObj->HasRight(
+        Object => $RT::System, Right => 'SeeSLAReports',
+    )
+) {
+    Abort("You're not allowed to see SLA reports.");
+}
+
+my $ticket = LoadTicket($id);
+unless ($ticket->CurrentUserHasRight('ShowTicket')) {
+    Abort("No permission to view ticket");
+}
+$id = $ARGS{'id'} = $ticket->id;
+
+my $title = loc("SLA performance on ticket #[_1]", $id);
+
+use RT::Extension::SLA;
+my $report = RT::Extension::SLA->Report( $ticket );
+use Data::Dumper;
+$RT::Logger->crit( Dumper $report );
+</%INIT>

commit 3f16054f3f851d748c606c2772618a0736b73344
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 14 14:29:08 2009 +0000

    store txn in the stats

diff --git a/lib/RT/Extension/SLA/Report.pm b/lib/RT/Extension/SLA/Report.pm
index 9d0d8c2..94290cd 100644
--- a/lib/RT/Extension/SLA/Report.pm
+++ b/lib/RT/Extension/SLA/Report.pm
@@ -150,12 +150,13 @@ sub OnResponse {
             my $failed = $txn->CreatedObj->Unix > $deadline? 1 : 0;
             my $owner = $args{'State'}->{'owner'} == $txn->Creator? 1 : 0;
             my $stat = {
-                type      => 'KeepInLoop',
-                owner     => $args{'State'}->{'owner'},
-                failed    => $failed,
-                owner_act => $owner,
-                actor     => $txn->Creator,
-                shift     => $txn->CreatedObj->Unix - $deadline,                
+                type        => 'KeepInLoop',
+                owner       => $args{'State'}->{'owner'},
+                failed      => $failed,
+                owner_act   => $owner,
+                transaction => $txn,
+                actor       => $txn->Creator,
+                shift       => $txn->CreatedObj->Unix - $deadline,                
             };
             push @{ $self->Stats }, $stat;
         }
@@ -175,12 +176,13 @@ sub OnResponse {
             my $failed = $txn->CreatedObj->Unix > $deadline? 1 : 0;
             my $owner = $args{'State'}->{'owner'} == $txn->Creator? 1 : 0;
             my $stat = {
-                type      => 'Response',
-                owner     => $args{'State'}->{'owner'},
-                failed    => $failed,
-                owner_act => $owner,
-                actor     => $txn->Creator,
-                shift     => ($txn->CreatedObj->Unix - $deadline),
+                type        => 'Response',
+                owner       => $args{'State'}->{'owner'},
+                failed      => $failed,
+                owner_act   => $owner,
+                transaction => $txn,
+                actor       => $txn->Creator,
+                shift       => ($txn->CreatedObj->Unix - $deadline),
             };
             push @{ $self->Stats }, $stat;
         }

commit 260414ce190fe977775123e557364e4a8a657877
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 14 15:07:41 2009 +0000

    update manifest

diff --git a/MANIFEST b/MANIFEST
index c76a77e..ad65521 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,6 +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/Ticket/SLA.html
 html/Tools/Reports/SLA.html
 inc/Module/AutoInstall.pm
 inc/Module/Install.pm

commit f38dd47548d498e713e70a280bda0627c5c64cfc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 14 15:08:02 2009 +0000

    update M::I

diff --git a/META.yml b/META.yml
index a510d6f..0317c5a 100644
--- a/META.yml
+++ b/META.yml
@@ -8,7 +8,7 @@ build_requires:
 configure_requires:
   ExtUtils::MakeMaker: 6.42
 distribution_type: module
-generated_by: 'Module::Install version 0.87'
+generated_by: 'Module::Install version 0.88'
 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 38c98f5..807fa7b 100644
--- a/inc/Module/AutoInstall.pm
+++ b/inc/Module/AutoInstall.pm
@@ -175,15 +175,24 @@ sub import {
             }
 
             # XXX: check for conflicts and uninstalls(!) them.
-            if (
-                defined( my $cur = _version_check( _load($mod), $arg ||= 0 ) ) )
+            my $cur = _load($mod);
+            if (_version_cmp ($cur, $arg) >= 0)
             {
                 print "loaded. ($cur" . ( $arg ? " >= $arg" : '' ) . ")\n";
                 push @Existing, $mod => $arg;
                 $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
             }
             else {
-                print "missing." . ( $arg ? " (would need $arg)" : '' ) . "\n";
+                if (not defined $cur)   # indeed missing
+                {
+                    print "missing." . ( $arg ? " (would need $arg)" : '' ) . "\n";
+                }
+                else
+                {
+                    # no need to check $arg as _version_cmp ($cur, undef) would satisfy >= above
+                    print "too old. ($cur < $arg)\n";
+                }
+
                 push @required, $mod => $arg;
             }
         }
@@ -268,8 +277,11 @@ sub _check_lock {
 
     require CPAN;
 
-    if ($CPAN::VERSION > '1.89' && $cpan_env) {
-        return _running_under('CPAN');
+    if ($CPAN::VERSION > '1.89') {
+        if ($cpan_env) {
+            return _running_under('CPAN');
+        }
+        return; # CPAN.pm new enough, don't need to check further
     }
 
     # last ditch attempt, this -will- configure CPAN, very sorry
@@ -310,7 +322,7 @@ sub install {
     while ( my ( $pkg, $ver ) = splice( @_, 0, 2 ) ) {
 
         # grep out those already installed
-        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
+        if ( _version_cmp( _load($pkg), $ver ) >= 0 ) {
             push @installed, $pkg;
         }
         else {
@@ -349,7 +361,7 @@ sub install {
 
     # see if we have successfully installed them
     while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
-        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
+        if ( _version_cmp( _load($pkg), $ver ) >= 0 ) {
             push @installed, $pkg;
         }
         elsif ( $args{do_once} and open( FAILED, '>> .#autoinstall.failed' ) ) {
@@ -404,7 +416,7 @@ sub _install_cpanplus {
         my $success;
         my $obj = $modtree->{$pkg};
 
-        if ( $obj and defined( _version_check( $obj->{version}, $ver ) ) ) {
+        if ( $obj and _version_cmp( $obj->{version}, $ver ) >= 0 ) {
             my $pathname = $pkg;
             $pathname =~ s/::/\\W/;
 
@@ -497,7 +509,7 @@ sub _install_cpan {
         my $obj     = CPAN::Shell->expand( Module => $pkg );
         my $success = 0;
 
-        if ( $obj and defined( _version_check( $obj->cpan_version, $ver ) ) ) {
+        if ( $obj and _version_cmp( $obj->cpan_version, $ver ) >= 0 ) {
             my $pathname = $pkg;
             $pathname =~ s/::/\\W/;
 
@@ -561,7 +573,7 @@ sub _update_to {
     my $ver   = shift;
 
     return
-      if defined( _version_check( _load($class), $ver ) );  # no need to upgrade
+      if _version_cmp( _load($class), $ver ) >= 0;  # no need to upgrade
 
     if (
         _prompt( "==> A newer version of $class ($ver) is required. Install?",
@@ -670,9 +682,11 @@ sub _load_cpan {
 }
 
 # compare two versions, either use Sort::Versions or plain comparison
-sub _version_check {
+# return values same as <=>
+sub _version_cmp {
     my ( $cur, $min ) = @_;
-    return unless defined $cur;
+    return -1 unless defined $cur;  # if 0 keep comparing
+    return 1 unless $min;
 
     $cur =~ s/\s+$//;
 
@@ -683,16 +697,13 @@ sub _version_check {
             ) {
 
             # use version.pm if it is installed.
-            return (
-                ( version->new($cur) >= version->new($min) ) ? $cur : undef );
+            return version->new($cur) <=> version->new($min);
         }
         elsif ( $Sort::Versions::VERSION or defined( _load('Sort::Versions') ) )
         {
 
             # use Sort::Versions as the sorting algorithm for a.b.c versions
-            return ( ( Sort::Versions::versioncmp( $cur, $min ) != -1 )
-                ? $cur
-                : undef );
+            return Sort::Versions::versioncmp( $cur, $min );
         }
 
         warn "Cannot reliably compare non-decimal formatted versions.\n"
@@ -701,7 +712,7 @@ sub _version_check {
 
     # plain comparison
     local $^W = 0;    # shuts off 'not numeric' bugs
-    return ( $cur >= $min ? $cur : undef );
+    return $cur <=> $min;
 }
 
 # nothing; this usage is deprecated.
@@ -791,4 +802,4 @@ END_MAKE
 
 __END__
 
-#line 1045
+#line 1056
diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
index fadb682..d39e460 100644
--- a/inc/Module/Install.pm
+++ b/inc/Module/Install.pm
@@ -28,7 +28,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 = '0.87';
+	$VERSION = '0.88';
 
 	# Storage for the pseudo-singleton
 	$MAIN    = undef;
@@ -353,7 +353,7 @@ sub _read {
 	if ( $] >= 5.006 ) {
 		open( FH, '<', $_[0] ) or die "open($_[0]): $!";
 	} else {
-		open( FH, "< $_[0]"  ) or die "open($_[0]): $!";	
+		open( FH, "< $_[0]"  ) or die "open($_[0]): $!";
 	}
 	my $string = do { local $/; <FH> };
 	close FH or die "close($_[0]): $!";
@@ -384,7 +384,7 @@ sub _write {
 	if ( $] >= 5.006 ) {
 		open( FH, '>', $_[0] ) or die "open($_[0]): $!";
 	} else {
-		open( FH, "> $_[0]"  ) or die "open($_[0]): $!";	
+		open( FH, "> $_[0]"  ) or die "open($_[0]): $!";
 	}
 	foreach ( 1 .. $#_ ) {
 		print FH $_[$_] or die "print($_[0]): $!";
diff --git a/inc/Module/Install/AutoInstall.pm b/inc/Module/Install/AutoInstall.pm
index 562bd0a..32f1423 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 $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.87';
+	$VERSION = '0.88';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
index 211585a..c08b3f0 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 = '0.87';
+	$VERSION = '0.88';
 }
 
 # Suspend handler for "redefined" warnings
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
index ef98ee4..fd64344 100644
--- a/inc/Module/Install/Can.pm
+++ b/inc/Module/Install/Can.pm
@@ -9,7 +9,7 @@ use ExtUtils::MakeMaker ();
 
 use vars qw{$VERSION $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.87';
+	$VERSION = '0.88';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
index 763732d..e0acf6b 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 $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.87';
+	$VERSION = '0.88';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Include.pm b/inc/Module/Install/Include.pm
index 406da54..6324bd5 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 $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.87';
+	$VERSION = '0.88';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
index b1854f6..3d10124 100644
--- a/inc/Module/Install/Makefile.pm
+++ b/inc/Module/Install/Makefile.pm
@@ -7,7 +7,7 @@ use ExtUtils::MakeMaker ();
 
 use vars qw{$VERSION $ISCORE @ISA};
 BEGIN {
-	$VERSION = '0.87';
+	$VERSION = '0.88';
 	$ISCORE  = 1;
 	@ISA     = qw{Module::Install::Base};
 }
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
index 6852f9a..6fd221f 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 = '0.87';
+	$VERSION = '0.88';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
@@ -511,7 +511,7 @@ sub requires_from {
 # Also, convert double-part versions (eg, 5.8)
 sub _perl_version {
 	my $v = $_[-1];
-	$v =~ s/^([1-9])\.([1-9]\d?\d?)$/sprintf("%d.%03d",$1,$2)/e;	
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)$/sprintf("%d.%03d",$1,$2)/e;
 	$v =~ s/^([1-9])\.([1-9]\d?\d?)\.(0|[1-9]\d?\d?)$/sprintf("%d.%03d%03d",$1,$2,$3 || 0)/e;
 	$v =~ s/(\.\d\d\d)000$/$1/;
 	$v =~ s/_.+$//;
@@ -534,7 +534,7 @@ sub WriteMyMeta {
 
 sub write_mymeta {
 	my $self = shift;
-	
+
 	# If there's no existing META.yml there is nothing we can do
 	return unless -f 'META.yml';
 
@@ -574,7 +574,7 @@ sub write_mymeta {
 
 	# Save as the MYMETA.yml file
 	print "Writing MYMETA.yml\n";
-	YAML::Tiny::DumpFile('MYMETA.yml', $meta);	
+	YAML::Tiny::DumpFile('MYMETA.yml', $meta);
 }
 
 1;
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
index 5d11a43..d91b287 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 = '0.87';
+	$VERSION = '0.88';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
index a73f351..e82f5d3 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 = '0.87';
+	$VERSION = '0.88';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }

commit afb655c376a8816af5f4c85de10ff9aad9a766c8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 14 15:09:25 2009 +0000

    bump version

diff --git a/META.yml b/META.yml
index 0317c5a..a894f6d 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.03_01
+version: 0.03_02
diff --git a/lib/RT/Extension/SLA.pm b/lib/RT/Extension/SLA.pm
index 8a021d3..e909e27 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.03_01';
+our $VERSION = '0.03_02';
 
 =head1 NAME
 

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



More information about the Bps-public-commit mailing list