[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