[Rt-commit] r17134 - in rt/3.8/branches/ruleset: etc lib/RT lib/RT/Approval lib/RT/Approval/Rule

clkao at bestpractical.com clkao at bestpractical.com
Sun Dec 7 04:33:49 EST 2008


Author: clkao
Date: Sun Dec  7 04:33:48 2008
New Revision: 17134

Added:
   rt/3.8/branches/ruleset/lib/RT/Approval/
   rt/3.8/branches/ruleset/lib/RT/Approval.pm
   rt/3.8/branches/ruleset/lib/RT/Approval/Rule/
   rt/3.8/branches/ruleset/lib/RT/Approval/Rule.pm
   rt/3.8/branches/ruleset/lib/RT/Approval/Rule/AllPassed.pm
   rt/3.8/branches/ruleset/lib/RT/Approval/Rule/NewPending.pm
   rt/3.8/branches/ruleset/lib/RT/Approval/Rule/Passed.pm
   rt/3.8/branches/ruleset/lib/RT/Approval/Rule/Rejected.pm
   rt/3.8/branches/ruleset/lib/RT/Rule.pm
   rt/3.8/branches/ruleset/lib/RT/Ruleset.pm
Modified:
   rt/3.8/branches/ruleset/etc/initialdata
   rt/3.8/branches/ruleset/lib/RT/Transaction_Overlay.pm
   rt/3.8/branches/ruleset/t/approval/basic.t

Log:
first cut of the rule system, which is an alternative path
from transaction to RT::Actions, wrapped in RT::Rule and can be
bundled in RT::Ruleset.  The approval process has been refactored
into using the rule system.


Modified: rt/3.8/branches/ruleset/etc/initialdata
==============================================================================
--- rt/3.8/branches/ruleset/etc/initialdata	(original)
+++ rt/3.8/branches/ruleset/etc/initialdata	Sun Dec  7 04:33:48 2008
@@ -501,167 +501,6 @@
        ScripCondition => 'On Transaction',
        ScripAction    => 'Extract Subject Tag',
        Template       => 'Blank' },
-    {  Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval",    # loc
-       Queue          => '___Approvals',
-       ScripCondition => 'User Defined',
-       CustomIsApplicableCode => q[
-	    $self->TicketObj->Type eq 'approval'	and
-	    $self->TransactionObj->Field eq 'Status'	and
-	    $self->TransactionObj->NewValue eq 'open'   and
-	    eval { $T::Approving = ($self->TicketObj->AllDependedOnBy( Type => 'ticket' ))[0] }
-       ],
-       ScripAction    => 'Notify Owner',
-       Template       => 'New Pending Approval' },
-    {  Description => "If an approval is rejected, reject the original and delete pending approvals",    # loc
-       Queue            => '___Approvals',
-       ScripCondition   => 'On Status Change',
-       ScripAction      => 'User Defined',
-       CustomPrepareCode => q[
-# ------------------------------------------------------------------- #
-return(0) unless ( lc($self->TransactionObj->NewValue) eq "rejected" or
-	           lc($self->TransactionObj->NewValue) eq "deleted" );
-
-my $rejected = 0;
-my $links = $self->TicketObj->DependedOnBy;
-foreach my $link (@{ $links->ItemsArrayRef }) {
-    my $obj = $link->BaseObj;
-    if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
-	if ($obj->Type eq 'ticket') {
-	    $obj->Comment(
-		Content	=> $self->loc("Your request was rejected."),
-	    );
-	    $obj->SetStatus(
-		Status	=> 'rejected',
-		Force	=> 1,
-	    );
-
-	    $T::Approval = $self->TicketObj; # so we can access it inside templates
-	    $self->{TicketObj} = $obj;  # we want the original id in the token line
-	    $rejected = 1;
-	}
-	else {
-	    $obj->SetStatus(
-		Status	=> 'deleted',
-		Force	=> 1,
-	    );
-	}
-    }
-}
-
-$links = $self->TicketObj->DependsOn;
-foreach my $link (@{ $links->ItemsArrayRef }) {
-    my $obj = $link->TargetObj;
-    if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
-	$obj->SetStatus(
-	    Status	=> 'deleted',
-	    Force	=> 1,
-	);
-    }
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return $rejected;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template          => 'Approval Rejected', },
-    {  Description => "When a ticket has been approved by any approver, add correspondence to the original ticket", # loc
-       Queue             => '___Approvals',
-       ScripCondition    => 'On Resolve',
-       ScripAction       => 'User Defined',
-       CustomPrepareCode => q[
-# ------------------------------------------------------------------- #
-return(0) unless ($self->TicketObj->Type eq 'approval');
-
-my $note;
-my $t = $self->TicketObj->Transactions;
-while (my $o = $t->Next) {
-    $note .= $o->Content . "\n" if $o->ContentObj
-	    and $o->Content !~ /Default Approval/;
-}
-
-foreach my $obj ($self->TicketObj->AllDependedOnBy( Type => 'ticket' )) {
-    $obj->Comment(
-	Content => $self->loc( "Your request has been approved by [_1]. Other approvals may still be pending.", # loc
-	    $self->TransactionObj->CreatorObj->Name,
-	) . "\n" . $self->loc( "Approver's notes: [_1]", # loc
-	    $note
-	),
-    );
-    $T::Approval = $self->TicketObj; # so we can access it inside templates
-    $self->{TicketObj} = $obj;  # we want the original id in the token line
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return 1;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template => 'Approval Passed' },
-    {  Description => "When a ticket has been approved by all approvers, add correspondence to the original ticket", # loc
-       Queue             => '___Approvals',
-       ScripCondition    => 'On Resolve',
-       ScripAction       => 'User Defined',
-       CustomPrepareCode  => q[
-# ------------------------------------------------------------------- #
-# Find all the tickets that depend on this (that this is approving)
-
-my $Ticket = $self->TicketObj;
-my @TOP    = $Ticket->AllDependedOnBy( Type => 'ticket' );
-my $links  = $Ticket->DependedOnBy;
-my $passed = 0;
-
-while (my $link = $links->Next) {
-    my $obj = $link->BaseObj;
-    next if ($obj->HasUnresolvedDependencies( Type => 'approval' ));
-
-    if ($obj->Type eq 'ticket') {
-	$obj->Comment(
-	    Content	=> $self->loc("Your request has been approved."),
-	);
-	$T::Approval  = $Ticket;    # so we can access it inside templates
-	$self->{TicketObj} = $obj;  # we want the original id in the token line
-	$passed = 1;
-    }
-    elsif ($obj->Type eq 'approval') {
-	$obj->SetStatus( Status => 'open', Force => 1 );
-    }
-    elsif (RT->Config->Get('UseCodeTickets') and $obj->Type eq 'code') {
-        #XXX: RT->Config->Get('UseCodeTickets') used only once here!!!
-	my $code = $obj->Transactions->First->Content;
-	my $rv;
-
-	foreach my $TOP (@TOP) {
-	    local $@;
-	    $rv++ if eval $code;
-	    $RT::Logger->error("Cannot eval code: $@") if $@;
-	}
-
-	if ($rv or !@TOP) {
-	    $obj->SetStatus( Status	=> 'resolved', Force	=> 1,);
-	}
-	else {
-	    $obj->SetStatus( Status	=> 'rejected', Force	=> 1,);
-	}
-    }
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return 0; # ignore $passed;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template => 'All Approvals Passed', },
-
 );
 
 @ACL = (

Added: rt/3.8/branches/ruleset/lib/RT/Approval.pm
==============================================================================
--- (empty file)
+++ rt/3.8/branches/ruleset/lib/RT/Approval.pm	Sun Dec  7 04:33:48 2008
@@ -0,0 +1,16 @@
+package RT::Approval;
+use strict;
+use warnings;
+
+use RT::Ruleset;
+
+RT::Ruleset->Add(
+    Name => 'Approval',
+    Rules => [
+        'RT::Approval::Rule::NewPending',
+        'RT::Approval::Rule::Rejected',
+        'RT::Approval::Rule::Passed',
+        'RT::Approval::Rule::AllPassed'
+    ]);
+
+1;

Added: rt/3.8/branches/ruleset/lib/RT/Approval/Rule.pm
==============================================================================
--- (empty file)
+++ rt/3.8/branches/ruleset/lib/RT/Approval/Rule.pm	Sun Dec  7 04:33:48 2008
@@ -0,0 +1,16 @@
+package RT::Approval::Rule;
+use strict;
+use warnings;
+
+use base 'RT::Rule';
+
+use constant _Queue => '___Approvals';
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+    $self->TicketObj->Type eq 'approval';
+}
+
+1;
+

Added: rt/3.8/branches/ruleset/lib/RT/Approval/Rule/AllPassed.pm
==============================================================================
--- (empty file)
+++ rt/3.8/branches/ruleset/lib/RT/Approval/Rule/AllPassed.pm	Sun Dec  7 04:33:48 2008
@@ -0,0 +1,54 @@
+package RT::Approval::Rule::AllPassed;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule::Passed';
+
+use constant Description => "When a ticket has been approved by all approvers, add correspondence to the original ticket"; # loc
+
+sub Commit {    # XXX: from custom prepare code
+    my $self   = shift;
+    my $Ticket = $self->TicketObj;
+    my @TOP    = $Ticket->AllDependedOnBy( Type => 'ticket' );
+    my $links  = $Ticket->DependedOnBy;
+    my $passed = 0;
+
+    while ( my $link = $links->Next ) {
+        my $obj = $link->BaseObj;
+        next if ( $obj->HasUnresolvedDependencies( Type => 'approval' ) );
+
+        if ( $obj->Type eq 'ticket' ) {
+            $obj->Comment(
+                Content => $self->loc("Your request has been approved."),
+            );
+            $T::Approval = $Ticket;    # so we can access it inside templates
+            $self->{TicketObj} = $obj; # we want the original id in the token line
+            $passed = 1;
+        }
+        elsif ( $obj->Type eq 'approval' ) {
+            $obj->SetStatus( Status => 'open', Force => 1 );
+        }
+        elsif ( RT->Config->Get('UseCodeTickets') and $obj->Type eq 'code' ) {
+
+            #XXX: RT->Config->Get('UseCodeTickets') used only once here!!!
+            my $code = $obj->Transactions->First->Content;
+            my $rv;
+
+            foreach my $TOP (@TOP) {
+                local $@;
+                $rv++ if eval $code;
+                $RT::Logger->error("Cannot eval code: $@") if $@;
+            }
+
+            if ( $rv or !@TOP ) {
+                $obj->SetStatus( Status => 'resolved', Force => 1, );
+            }
+            else {
+                $obj->SetStatus( Status => 'rejected', Force => 1, );
+            }
+        }
+    }
+
+    $self->RunScripAction('Notify Requestors', 'All Approvals Passed');
+}
+
+1;

Added: rt/3.8/branches/ruleset/lib/RT/Approval/Rule/NewPending.pm
==============================================================================
--- (empty file)
+++ rt/3.8/branches/ruleset/lib/RT/Approval/Rule/NewPending.pm	Sun Dec  7 04:33:48 2008
@@ -0,0 +1,21 @@
+package RT::Approval::Rule::NewPending;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    $self->OnStatusChange('open') and
+    eval { $T::Approving = ($self->TicketObj->AllDependedOnBy( Type => 'ticket' ))[0] }
+}
+
+sub Commit {
+    my $self = shift;
+    $self->RunScripAction('Notify Owner', 'New Pending Approval', @_);
+}
+
+1;

Added: rt/3.8/branches/ruleset/lib/RT/Approval/Rule/Passed.pm
==============================================================================
--- (empty file)
+++ rt/3.8/branches/ruleset/lib/RT/Approval/Rule/Passed.pm	Sun Dec  7 04:33:48 2008
@@ -0,0 +1,39 @@
+package RT::Approval::Rule::Passed;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "If an approval is rejected, reject the original and delete pending approvals"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    $self->OnStatusChange('resolved');
+}
+
+sub Commit {    # XXX: from custom prepare code
+    my $self = shift;
+    my $note;
+    my $t = $self->TicketObj->Transactions;
+    while ( my $o = $t->Next ) {
+        $note .= $o->Content . "\n" if $o->ContentObj
+                and $o->Content !~ /Default Approval/;
+    }
+
+    foreach my $obj ( $self->TicketObj->AllDependedOnBy( Type => 'ticket' ) ) {
+        $obj->Comment(
+            Content => $self->loc( "Your request has been approved by [_1]. Other approvals may still be pending.", # loc
+                $self->TransactionObj->CreatorObj->Name,
+                ) . "\n" . $self->loc( "Approver's notes: [_1]",    # loc
+                $note
+                ),
+        );
+        $T::Approval = $self->TicketObj; # so we can access it inside templates
+        $self->{TicketObj} = $obj; # we want the original id in the token line
+    }
+
+    $self->RunScripAction('Notify Requestors', 'Approval Passed');
+}
+
+1;

Added: rt/3.8/branches/ruleset/lib/RT/Approval/Rule/Rejected.pm
==============================================================================
--- (empty file)
+++ rt/3.8/branches/ruleset/lib/RT/Approval/Rule/Rejected.pm	Sun Dec  7 04:33:48 2008
@@ -0,0 +1,61 @@
+package RT::Approval::Rule::Rejected;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "If an approval is rejected, reject the original and delete pending approvals"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    return (0)
+        unless $self->OnStatusChange('rejected') or $self->OnStatusChange('deleted')
+}
+
+sub Commit {    # XXX: from custom prepare code
+    my $self = shift;
+
+    my $rejected = 0;
+    my $links    = $self->TicketObj->DependedOnBy;
+    foreach my $link ( @{ $links->ItemsArrayRef } ) {
+        my $obj = $link->BaseObj;
+        if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) {
+            if ( $obj->Type eq 'ticket' ) {
+                $obj->Comment(
+                    Content => $self->loc("Your request was rejected."),
+                );
+                $obj->SetStatus(
+                    Status => 'rejected',
+                    Force  => 1,
+                );
+
+                $T::Approval = $self->TicketObj; # so we can access it inside templates
+                $self->{TicketObj} = $obj; # we want the original id in the token line
+                $rejected = 1;
+            }
+            else {
+                $obj->SetStatus(
+                    Status => 'deleted',
+                    Force  => 1,
+                );
+            }
+        }
+    }
+
+    $links = $self->TicketObj->DependsOn;
+    foreach my $link ( @{ $links->ItemsArrayRef } ) {
+        my $obj = $link->TargetObj;
+        if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) {
+            $obj->SetStatus(
+                Status => 'deleted',
+                Force  => 1,
+            );
+        }
+    }
+
+    return $self->RunScripAction('Notify Requestors', 'Approvals Rejected')
+        if $rejected;
+}
+
+1;

Added: rt/3.8/branches/ruleset/lib/RT/Rule.pm
==============================================================================
--- (empty file)
+++ rt/3.8/branches/ruleset/lib/RT/Rule.pm	Sun Dec  7 04:33:48 2008
@@ -0,0 +1,56 @@
+package RT::Rule;
+use strict;
+use warnings;
+use base 'RT::Action';
+
+use constant _Stage => 'TransactionCreate';
+use constant _Queue => undef;
+
+sub Prepare {
+    my $self = shift;
+    return (0) if $self->_Queue && $self->TicketObj->QueueObj->Name ne $self->_Queue;
+    return 1;
+}
+
+sub Commit  {
+    my $self = shift;
+    return(0, $self->loc("Commit Stubbed"));
+}
+
+sub Describe {
+    my $self = shift;
+    return $self->loc( $self->Description );
+}
+
+sub OnStatusChange {
+    my ($self, $value) = @_;
+
+    $self->TransactionObj->Type eq 'Status' and
+    $self->TransactionObj->Field eq 'Status' and
+    $self->TransactionObj->NewValue eq $value
+}
+
+
+sub RunScripAction {
+    my ($self, $scrip_action, $template, %args) = @_;
+    my $ScripAction = RT::ScripAction->new($self->CurrentUser);
+    $ScripAction->Load($scrip_action) or die ;
+    my $t = RT::Template->new($self->CurrentUser);
+
+    # XXX: load per-queue template
+#    $template->LoadQueueTemplate( Queue => ..., ) || $template->LoadGlobalTemplate(...)
+    $t->Load($template) or die;
+
+    my $action = $ScripAction->LoadAction( TransactionObj => $self->TransactionObj,
+                                           TicketObj => $self->TicketObj,
+                                           %args,
+                                       );
+
+    $action->{'TemplateObj'} = $t;
+    $action->{'ScripObj'} = RT::Scrip->new($self->CurrentUser); # Stub. sendemail action really wants a scripobj available
+    $action->Prepare or return;
+    $action->Commit;
+
+}
+
+1;

Added: rt/3.8/branches/ruleset/lib/RT/Ruleset.pm
==============================================================================
--- (empty file)
+++ rt/3.8/branches/ruleset/lib/RT/Ruleset.pm	Sun Dec  7 04:33:48 2008
@@ -0,0 +1,36 @@
+package RT::Ruleset;
+use strict;
+use warnings;
+
+use base 'Class::Accessor::Fast';
+use UNIVERSAL::require;
+
+__PACKAGE__->mk_accessors(qw(Name Rules));
+
+my @RULE_SETS;
+
+sub FindAllRules {
+    my ($class, %args) = @_;
+    return [
+        grep { $_->Prepare }
+        map { $_->new(CurrentUser => $RT::SystemUser, %args) }
+        grep { $_->_Stage eq $args{Stage} }
+        map { @{$_->Rules} } @RULE_SETS
+    ];
+}
+
+sub CommitRules {
+    my ($class, $rules) = @_;
+    $_->Commit
+        for @$rules;
+}
+
+sub Add {
+    my ($class, %args) = @_;
+    for (@{$args{Rules}}) {
+        $_->require or die $UNIVERSAL::require::ERROR;
+    }
+    push @RULE_SETS, $class->new(\%args);
+}
+
+1;

Modified: rt/3.8/branches/ruleset/lib/RT/Transaction_Overlay.pm
==============================================================================
--- rt/3.8/branches/ruleset/lib/RT/Transaction_Overlay.pm	(original)
+++ rt/3.8/branches/ruleset/lib/RT/Transaction_Overlay.pm	Sun Dec  7 04:33:48 2008
@@ -78,11 +78,11 @@
 
 use RT::Attachments;
 use RT::Scrips;
+use RT::Ruleset;
 
 use HTML::FormatText;
 use HTML::TreeBuilder;
 
-
 # {{{ sub Create 
 
 =head2 Create
@@ -172,9 +172,21 @@
             Ticket      => $args{'ObjectId'},
             Transaction => $self->id,
         );
+
+       # Entry point of the rule system
+       my $ticket = RT::Ticket->new($RT::SystemUser);
+       $ticket->Load($args{'ObjectId'});
+       my $rules = RT::Ruleset->FindAllRules(
+            Stage       => 'TransactionCreate',
+            Type        => $args{'Type'},
+            TicketObj   => $ticket,
+            TransactionObj => $self,
+       );
+
         if ($args{'CommitScrips'} ) {
             $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id);
             $self->{'scrips'}->Commit();
+            RT::Ruleset->CommitRules($rules);
         }
     }
 

Modified: rt/3.8/branches/ruleset/t/approval/basic.t
==============================================================================
--- rt/3.8/branches/ruleset/t/approval/basic.t	(original)
+++ rt/3.8/branches/ruleset/t/approval/basic.t	Sun Dec  7 04:33:48 2008
@@ -141,14 +141,14 @@
     my ($ok, $msg) = $dependson_cfo->SetStatus( Status => 'resolved' );
     ok($ok, "cfo can approve - $msg");
 
-} { from => qr/RT System/,
-    to => 'ceo at company.com',
-    subject => qr/New Pending Approval: PO approval request for PO/,
-    body => qr/pending your approval/
-},{ from => qr/CFO via RT/,
+} { from => qr/CFO via RT/,
     to => 'minion at company.com',
     subject => qr/Ticket Approved:/,
     body => qr/approved by CFO/
+},{ from => qr/RT System/,
+    to => 'ceo at company.com',
+    subject => qr/New Pending Approval: PO approval request for PO/,
+    body => qr/pending your approval/
 };
 
 is ($t->DependsOn->Count, 1, "still depends only on the CEO approval");


More information about the Rt-commit mailing list