[Rt-commit] r17204 - in rt/3.8/trunk: . etc etc/upgrade/3.8.2 lib/RT lib/RT/Approval lib/RT/Approval/Rule t/approval
clkao at bestpractical.com
clkao at bestpractical.com
Sat Dec 13 02:19:24 EST 2008
Author: clkao
Date: Sat Dec 13 02:19:23 2008
New Revision: 17204
Added:
rt/3.8/trunk/lib/RT/Approval/
rt/3.8/trunk/lib/RT/Approval.pm
rt/3.8/trunk/lib/RT/Approval/Rule/
rt/3.8/trunk/lib/RT/Approval/Rule.pm
rt/3.8/trunk/lib/RT/Approval/Rule/Created.pm
rt/3.8/trunk/lib/RT/Approval/Rule/NewPending.pm
rt/3.8/trunk/lib/RT/Approval/Rule/Passed.pm
rt/3.8/trunk/lib/RT/Approval/Rule/Rejected.pm
rt/3.8/trunk/lib/RT/Rule.pm
rt/3.8/trunk/lib/RT/Ruleset.pm
Modified:
rt/3.8/trunk/ (props changed)
rt/3.8/trunk/etc/RT_Config.pm.in
rt/3.8/trunk/etc/initialdata
rt/3.8/trunk/etc/upgrade/3.8.2/content
rt/3.8/trunk/lib/RT/Action/SendEmail.pm
rt/3.8/trunk/lib/RT/Template_Overlay.pm
rt/3.8/trunk/lib/RT/Ticket_Overlay.pm
rt/3.8/trunk/lib/RT/Transaction_Overlay.pm
rt/3.8/trunk/t/approval/basic.t
Log:
Merge ruleset branch to trunk.
r37974 at mtl (orig r17133): clkao | 2008-12-07 12:53:04 +0800
- Create branch ruleset
r37981 at mtl (orig r17134): clkao | 2008-12-07 17:33:51 +0800
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.
r37982 at mtl (orig r17135): clkao | 2008-12-07 21:28:17 +0800
merge allpassed rule with passed for rt::approval.
r37983 at mtl (orig r17136): clkao | 2008-12-07 22:26:58 +0800
make sendemail action log originating scrip as #rule if does
not have scripobj.
r37984 at mtl (orig r17137): clkao | 2008-12-07 22:39:01 +0800
- only correspond txn content should be approver's note.
- when approval passed, use the associated template for sending
correspond rather than calling notify action and comment.
- no longer needs the T:: hack in template.
- put $Notes into the approval templates.
r37985 at mtl (orig r17138): clkao | 2008-12-07 23:02:35 +0800
test for approval all passed template.
r37989 at mtl (orig r17142): clkao | 2008-12-07 23:39:36 +0800
- change rejection logic so the first rejection triggers
the original ticket to be rejected (and notified by the
correct actor.)
r37990 at mtl (orig r17143): clkao | 2008-12-08 00:12:52 +0800
improve newpending notification to include the content of the approval
ticket we created from the workflow template.
r38023 at mtl (orig r17153): clkao | 2008-12-09 23:46:57 +0800
allow template object to be passed directly to RunScripAction.
r38024 at mtl (orig r17154): clkao | 2008-12-09 23:59:16 +0800
automatically open leaves of the approval tree with
transactionbatch.
r38077 at mtl (orig r17171): ruz | 2008-12-11 17:56:48 +0800
* add content upgrader for approvals
r38135 at mtl (orig r17182): clkao | 2008-12-12 19:21:20 +0800
make UseTransactionBatch default.
r38136 at mtl (orig r17183): clkao | 2008-12-12 19:32:19 +0800
refactor common approval templating code.
r38137 at mtl (orig r17184): clkao | 2008-12-12 19:38:39 +0800
prefix the approval templates with "RT ", so it's easier for the upgrade
to process, since the semantics of the templates have also changed.
r38139 at mtl (orig r17186): clkao | 2008-12-12 20:06:06 +0800
when all approvals are passed, also notify the owner that the request can now be acted on.
r38156 at mtl (orig r17187): ruz | 2008-12-13 01:21:33 +0800
* delete prefix from templates, we'll add prefix to OLD templates instead
r38157 at mtl (orig r17188): ruz | 2008-12-13 01:22:12 +0800
* rename old and add new templates for approvals
r38158 at mtl (orig r17189): ruz | 2008-12-13 01:32:25 +0800
* remove 'RT ' templates prefix in the code
Modified: rt/3.8/trunk/etc/RT_Config.pm.in
==============================================================================
--- rt/3.8/trunk/etc/RT_Config.pm.in (original)
+++ rt/3.8/trunk/etc/RT_Config.pm.in Sat Dec 13 02:19:23 2008
@@ -1375,7 +1375,7 @@
=cut
-Set($UseTransactionBatch, 0);
+Set($UseTransactionBatch, 1);
=item C<@CustomFieldValuesSources>
Modified: rt/3.8/trunk/etc/initialdata
==============================================================================
--- rt/3.8/trunk/etc/initialdata (original)
+++ rt/3.8/trunk/etc/initialdata Sat Dec 13 02:19:23 2008
@@ -319,24 +319,29 @@
{ Queue => '___Approvals',
Name => "Approval Passed", # loc
Description =>
- "Notify Owner of their ticket has been approved by some approver", # loc
+ "Notify Requestor of their ticket has been approved by some approver", # loc
Content => 'Subject: Ticket Approved: {$Ticket->Subject}
Greetings,
Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
Other approvals may be pending.
+
+Approver\'s notes: { $Notes }
'
},
{ Queue => '___Approvals',
Name => "All Approvals Passed", # loc
Description =>
- "Notify Owner of their ticket has been approved by all approvers", # loc
+ "Notify Requestor of their ticket has been approved by all approvers", # loc
Content => 'Subject: Ticket Approved: {$Ticket->Subject}
Greetings,
-Your ticket has been approved. Its Owner may now start to act on it.
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Its Owner may now start to act on it.
+
+Approver\'s notes: { $Notes }
'
},
{ Queue => '___Approvals',
@@ -348,6 +353,20 @@
Greetings,
Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+
+Approver\'s notes: { $Notes }
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Ready for Owner", # loc
+ Description =>
+ "Notify Owner of their ticket has been approved and is ready to be acted on", # loc
+ Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+The ticket has been approved, you may now start to act on it.
+
'
},
{ Queue => 0,
@@ -501,167 +520,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 = (
Modified: rt/3.8/trunk/etc/upgrade/3.8.2/content
==============================================================================
--- rt/3.8/trunk/etc/upgrade/3.8.2/content (original)
+++ rt/3.8/trunk/etc/upgrade/3.8.2/content Sat Dec 13 02:19:23 2008
@@ -1,9 +1,111 @@
+ at Initial = (
+ sub {
+ $RT::Logger->warning(
+ "Going to add [OLD] prefix to all temlates in approvals queue."
+ ." If you never used approvals then you can delete all these"
+ ." templates with [OLD] prefix. Leave new there may be you will"
+ ." want to use approvals some time."
+ );
+
+ my $approvals_q = RT::Queue->new( $RT::SystemUser );
+ $approvals_q->Load('___Approvals');
+ unless ( $approvals_q->id ) {
+ $RT::Logger->error("You have no approvals queue.");
+ return 1;
+ }
+
+ my $templates = RT::Templates->new( $RT::SystemUser );
+ $templates->LimitToQueue( $approvals_q->id );
+ while ( my $tmpl = $templates->Next ) {
+ my ($status, $msg) = $tmpl->SetName( "[OLD] ". $tmpl->Name );
+ unless ( $status ) {
+ $RT::Logger->error("Couldn't rename template #". $tmpl->id .": $msg");
+ }
+ }
+ return 1;
+ },
+);
@ACL = (
{ GroupDomain => 'SystemInternal',
GroupType => 'privileged',
Right => 'ShowApprovalsTab', },
);
+ at Templates = (
+ { Queue => '___Approvals',
+ Name => "New Pending Approval", # loc
+ Description =>
+ "Notify Owners and AdminCcs of new items pending their approval", # loc
+ Content => 'Subject: New Pending Approval: {$Ticket->Subject}
+
+Greetings,
+
+There is a new item pending your approval: "{$Ticket->Subject()}",
+a summary of which appears below.
+
+Please visit {RT->Config->Get(\'WebURL\')}Approvals/Display.html?id={$Ticket->id}
+to approve or reject this ticket, or {RT->Config->Get(\'WebURL\')}Approvals/ to
+batch-process all your pending approvals.
+
+-------------------------------------------------------------------------
+{$Transaction->Content()}
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Passed", # loc
+ Description =>
+ "Notify Requestor of their ticket has been approved by some approver", # loc
+ Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Other approvals may be pending.
+
+Approver\'s notes: { $Notes }
+'
+ },
+ { Queue => '___Approvals',
+ Name => "All Approvals Passed", # loc
+ Description =>
+ "Notify Requestor of their ticket has been approved by all approvers", # loc
+ Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Its Owner may now start to act on it.
+
+Approver\'s notes: { $Notes }
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Rejected", # loc
+ Description =>
+ "Notify Owner of their rejected ticket", # loc
+ Content => 'Subject: Ticket Rejected: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+
+Approver\'s notes: { $Notes }
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Ready for Owner", # loc
+ Description =>
+ "Notify Owner of their ticket has been approved and is ready to be acted on", # loc
+ Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+The ticket has been approved, you may now start to act on it.
+
+'
+ },
+);
+
@Final = (
sub {
$RT::Logger->debug("Going to adjust dashboards");
@@ -44,4 +146,41 @@
$RT::Logger->debug("Fixed.");
return 1;
},
+ sub {
+ my $approvals_q = RT::Queue->new( $RT::SystemUser );
+ $approvals_q->Load('___Approvals');
+ unless ( $approvals_q->id ) {
+ $RT::Logger->error("You have no approvals queue.");
+ return 1;
+ }
+
+ require File::Temp;
+ my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( 'rt-approvals-scrips-XXXX', CLEANUP => 0 );
+ unless ( $tmp_fh ) {
+ $RT::Logger->error("Couldn't create temporary file.");
+ return 0;
+ }
+
+ $RT::Logger->warning(
+ "IMPORTANT: We're going to delete all scrips in Approvals queue"
+ ." and save them in '$tmp_fn' file."
+ );
+
+ require Data::Dumper;
+
+ my $scrips = RT::Scrips->new( $RT::SystemUser );
+ $scrips->LimitToQueue( $approvals_q->id );
+ while ( my $scrip = $scrips->Next ) {
+ my %tmp =
+ map { $tmp->{ $_ } = $scrip->_Value( $_ ) }
+ $scrip->ReadableAttributes;
+
+ print $tmp_fh Data::Dumper::Dumper( \%tmp );
+
+ my ($status, $msg) = $scrip->Delete;
+ unless ( $status ) {
+ $RT::Logger->error( "Couldn't delete scrip: $msg");
+ }
+ }
+ },
);
Modified: rt/3.8/trunk/lib/RT/Action/SendEmail.pm
==============================================================================
--- rt/3.8/trunk/lib/RT/Action/SendEmail.pm (original)
+++ rt/3.8/trunk/lib/RT/Action/SendEmail.pm Sat Dec 13 02:19:23 2008
@@ -302,7 +302,7 @@
. $self->TicketObj->id . "/"
. $self->TransactionObj->id
. " - Scrip "
- . $self->ScripObj->id . " "
+ . ($self->ScripObj->id || '#rule'). " "
. ( $self->ScripObj->Description || '' ) );
my $status = RT::Interface::Email::SendEmail(
Added: rt/3.8/trunk/lib/RT/Approval.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval.pm Sat Dec 13 02:19:23 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::Created',
+ ]);
+
+1;
Added: rt/3.8/trunk/lib/RT/Approval/Rule.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule.pm Sat Dec 13 02:19:23 2008
@@ -0,0 +1,27 @@
+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';
+}
+
+sub GetTemplate {
+ my ($self, $template_name, %args) = @_;
+ my $template = RT::Template->new($self->CurrentUser);
+ $template->Load($template_name) or return;
+ my ($result, $msg) = $template->Parse(%args);
+
+ # XXX: error handling
+
+ return $template;
+}
+
+1;
+
Added: rt/3.8/trunk/lib/RT/Approval/Rule/Created.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule/Created.pm Sat Dec 13 02:19:23 2008
@@ -0,0 +1,23 @@
+package RT::Approval::Rule::Created;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant _Stage => 'TransactionBatch';
+
+use constant Description => "Notify Owner of their ticket has been approved by some or all approvers"; # loc
+
+sub Prepare {
+ my $self = shift;
+ return unless $self->SUPER::Prepare();
+
+ $self->TransactionObj->Type eq 'Create' &&
+ !$self->TicketObj->HasUnresolvedDependencies( Type => 'approval' );
+}
+
+sub Commit {
+ my $self = shift;
+ $self->RunScripAction('Open Tickets' => 'Blank');
+}
+
+1;
Added: rt/3.8/trunk/lib/RT/Approval/Rule/NewPending.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule/NewPending.pm Sat Dec 13 02:19:23 2008
@@ -0,0 +1,49 @@
+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;
+ my ($top) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' );
+ my $t = $self->TicketObj->Transactions;
+ my $to;
+ while ( my $o = $t->Next ) {
+ $to = $o, last if $o->Type eq 'Create';
+ }
+
+ # XXX: this makes the owner incorrect so notify owner won't work
+ # local $self->{TicketObj} = $top;
+
+ # first txn entry of the approval ticket
+ local $self->{TransactionObj} = $to;
+ $self->RunScripAction('Notify Owner', 'New Pending Approval', @_);
+
+ return;
+
+ # this generates more correct content of the message, but not sure
+ # if ccmessageto is the right way to do this.
+ my $template = $self->GetTemplate('New Pending Approval',
+ TicketObj => $top,
+ TransactionObj => $to)
+ or return;
+
+ my ( $result, $msg ) = $template->Parse(
+ TicketObj => $top,
+ );
+ $self->TicketObj->Comment( CcMessageTo => $self->TicketObj->OwnerObj->EmailAddress,
+ MIMEObj => $template->MIMEObj );
+
+}
+
+1;
Added: rt/3.8/trunk/lib/RT/Approval/Rule/Passed.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule/Passed.pm Sat Dec 13 02:19:23 2008
@@ -0,0 +1,53 @@
+package RT::Approval::Rule::Passed;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "Notify Owner of their ticket has been approved by some or all approvers"; # loc
+
+sub Prepare {
+ my $self = shift;
+ return unless $self->SUPER::Prepare();
+
+ $self->OnStatusChange('resolved');
+}
+
+sub Commit {
+ my $self = shift;
+ my $note;
+ my $t = $self->TicketObj->Transactions;
+
+ while ( my $o = $t->Next ) {
+ next unless $o->Type eq 'Correspond';
+ $note .= $o->Content . "\n" if $o->ContentObj;
+ }
+ my ($top) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' );
+ my $links = $self->TicketObj->DependedOnBy;
+
+ while ( my $link = $links->Next ) {
+ my $obj = $link->BaseObj;
+ next unless $obj->Type eq 'approval';
+ next if $obj->HasUnresolvedDependencies( Type => 'approval' );
+
+ $obj->SetStatus( Status => 'open', Force => 1 );
+ }
+
+ my $passed = !$top->HasUnresolvedDependencies( Type => 'approval' );
+ my $template = $self->GetTemplate(
+ $passed ? 'All Approvals Passed' : 'Approval Passed',
+ TicketObj => $top,
+ Approval => $self->TicketObj,
+ Notes => $note,
+ ) or die;
+
+ $top->Correspond( MIMEObj => $template->MIMEObj );
+
+ if ($passed) {
+ $self->RunScripAction('Notify Owner', 'Approval Ready for Owner',
+ TicketObj => $top);
+ }
+
+ return;
+}
+
+1;
Added: rt/3.8/trunk/lib/RT/Approval/Rule/Rejected.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule/Rejected.pm Sat Dec 13 02:19:23 2008
@@ -0,0 +1,57 @@
+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;
+ if ( my ($rejected) =
+ $self->TicketObj->AllDependedOnBy( Type => 'ticket' ) ) {
+ my $template = $self->GetTemplate('Approval Rejected',
+ TicketObj => $rejected,
+ Approval => $self->TicketObj,
+ Notes => '');
+
+ $rejected->Correspond( MIMEObj => $template->MIMEObj );
+ $rejected->SetStatus(
+ Status => 'rejected',
+ Force => 1,
+ );
+ }
+ my $links = $self->TicketObj->DependedOnBy;
+ foreach my $link ( @{ $links->ItemsArrayRef } ) {
+ my $obj = $link->BaseObj;
+ if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) {
+ if ( $obj->Type eq 'approval' ) {
+ $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,
+ );
+ }
+ }
+
+}
+
+1;
Added: rt/3.8/trunk/lib/RT/Rule.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Rule.pm Sat Dec 13 02:19:23 2008
@@ -0,0 +1,60 @@
+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 ;
+
+ unless (ref($template)) {
+ # XXX: load per-queue template
+ # $template->LoadQueueTemplate( Queue => ..., ) || $template->LoadGlobalTemplate(...)
+
+ my $t = RT::Template->new($self->CurrentUser);
+ $t->Load($template) or die;
+ $template = $t;
+ }
+
+ my $action = $ScripAction->LoadAction( TransactionObj => $self->TransactionObj,
+ TicketObj => $self->TicketObj,
+ %args,
+ );
+
+ # XXX: fix template to allow additional arguments to be passed from here
+ $action->{'TemplateObj'} = $template;
+ $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/trunk/lib/RT/Ruleset.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Ruleset.pm Sat Dec 13 02:19:23 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/trunk/lib/RT/Template_Overlay.pm
==============================================================================
--- rt/3.8/trunk/lib/RT/Template_Overlay.pm (original)
+++ rt/3.8/trunk/lib/RT/Template_Overlay.pm Sat Dec 13 02:19:23 2008
@@ -414,7 +414,6 @@
my $is_broken = 0;
my $retval = $template->fill_in(
- PACKAGE => 'T',
HASH => \%args,
BROKEN => sub {
my (%args) = @_;
Modified: rt/3.8/trunk/lib/RT/Ticket_Overlay.pm
==============================================================================
--- rt/3.8/trunk/lib/RT/Ticket_Overlay.pm (original)
+++ rt/3.8/trunk/lib/RT/Ticket_Overlay.pm Sat Dec 13 02:19:23 2008
@@ -3164,6 +3164,15 @@
TransactionObj => $batch->[0],
Type => join( ',', map $_->Type, grep defined, @{$batch} )
);
+
+ # Entry point of the rule system
+ my $rules = RT::Ruleset->FindAllRules(
+ Stage => 'TransactionBatch',
+ TicketObj => $self,
+ TransactionObj => $batch->[0],
+ Type => join( ',', map $_->Type, grep defined, @{$batch} )
+ );
+ RT::Ruleset->CommitRules($rules);
}
# }}}
Modified: rt/3.8/trunk/lib/RT/Transaction_Overlay.pm
==============================================================================
--- rt/3.8/trunk/lib/RT/Transaction_Overlay.pm (original)
+++ rt/3.8/trunk/lib/RT/Transaction_Overlay.pm Sat Dec 13 02:19:23 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/trunk/t/approval/basic.t
==============================================================================
--- rt/3.8/trunk/t/approval/basic.t (original)
+++ rt/3.8/trunk/t/approval/basic.t Sat Dec 13 02:19:23 2008
@@ -8,33 +8,30 @@
or plan skip_all => 'require Email::Abstract and Test::Email';
}
-plan tests => 28;
+plan tests => 38;
use RT;
use RT::Test;
use RT::Test::Email;
RT->Config->Set( LogToScreen => 'debug' );
-
+RT->Config->Set( UseTransactionBatch => 1 );
my ($baseurl, $m) = RT::Test->started_ok;
-my ($user_a, $user_b) = (RT::User->new($RT::SystemUser), RT::User->new($RT::SystemUser));
-my ($user_c) = RT::User->new($RT::SystemUser);
-
-$user_a->Create( Name => 'CFO', Privileged => 1, EmailAddress => 'cfo at company.com');
-$user_b->Create( Name => 'CEO', Privileged => 1, EmailAddress => 'ceo at company.com');
-$user_c->Create( Name => 'minion', Privileged => 1, EmailAddress => 'minion at company.com');
-
my $q = RT::Queue->new($RT::SystemUser);
$q->Load('___Approvals');
-
$q->SetDisabled(0);
-my ($val, $msg);
-($val, $msg) = $user_a->PrincipalObj->GrantRight(Object =>$q, Right => $_)
- for qw(ModifyTicket OwnTicket ShowTicket);
-($val, $msg) = $user_b->PrincipalObj->GrantRight(Object =>$q, Right => $_)
- for qw(ModifyTicket OwnTicket ShowTicket);
+my %users;
+for my $user_name (qw(minion cfo ceo )) {
+ my $user = $users{$user_name} = RT::User->new($RT::SystemUser);
+ $user->Create( Name => uc($user_name),
+ Privileged => 1,
+ EmailAddress => $user_name.'@company.com');
+ my ($val, $msg);
+ ($val, $msg) = $user->PrincipalObj->GrantRight(Object =>$q, Right => $_)
+ for qw(ModifyTicket OwnTicket ShowTicket);
+}
# XXX: we need to make the first approval ticket open so notification is sent.
my $approvals =
@@ -94,7 +91,11 @@
$t->Create(Subject => "PO for stationary",
Owner => "root", Requestor => 'minion',
Queue => $q->Id);
-} { from => qr/PO via RT/,
+} { from => qr/RT System/,
+ to => 'cfo at company.com',
+ subject => qr/New Pending Approval: CFO Approval/,
+ body => qr/pending your approval.*Your approval is requested.*Blah/s
+},{ from => qr/PO via RT/,
to => 'minion at company.com',
subject => qr/PO for stationary/,
body => qr/automatically generated in response/
@@ -104,19 +105,6 @@
is ($t->ReferredToBy->Count,2, "referred to by the two tickets");
-# open the approval tickets that are ready for approval
-mail_ok {
- for my $ticket ($t->AllDependsOn) {
- next if $ticket->Type ne 'approval' && $ticket->Status ne 'new';
- next if $ticket->HasUnresolvedDependencies( Type => 'approval' );
- $ticket->SetStatus('open');
- }
-} { from => qr/RT System/,
- to => 'cfo at company.com',
- subject => qr/New Pending Approval: CFO Approval/,
- body => qr/pending your approval/
-};
-
my $deps = $t->DependsOn;
is ($deps->Count, 1, "The ticket we created depends on one other ticket");
my $dependson_ceo= $deps->First->TargetObj;
@@ -135,20 +123,28 @@
mail_ok {
my $cfo = RT::CurrentUser->new;
- $cfo->Load( $user_a );
+ $cfo->Load( $users{cfo} );
$dependson_cfo->CurrentUser($cfo);
+ my $notes = MIME::Entity->build(
+ Data => [ 'Resources exist to be consumed.' ]
+ );
+ RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8
+
+ my ( $notesval, $notesmsg ) = $dependson_cfo->Correspond( MIMEObj => $notes );
+ ok($notesval, $notesmsg);
+
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/,
+ body => qr/pending your approval.*CFO approved.*ok with that\?/s
+},{ from => qr/RT System/,
to => 'minion at company.com',
subject => qr/Ticket Approved:/,
- body => qr/approved by CFO/
+ body => qr/approved by CFO.*notes: Resources exist to be consumed/s
};
is ($t->DependsOn->Count, 1, "still depends only on the CEO approval");
@@ -156,3 +152,68 @@
is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ],
[ 'new', 'resolved', 'open'], 'ticket state after cfo approval');
+
+mail_ok {
+ my $ceo = RT::CurrentUser->new;
+ $ceo->Load( $users{ceo} );
+
+ $dependson_ceo->CurrentUser($ceo);
+ my $notes = MIME::Entity->build(
+ Data => [ 'And consumed they will be.' ]
+ );
+ RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8
+
+ my ( $notesval, $notesmsg ) = $dependson_ceo->Correspond( MIMEObj => $notes );
+ ok($notesval, $notesmsg);
+
+ my ($ok, $msg) = $dependson_ceo->SetStatus( Status => 'resolved' );
+ ok($ok, "ceo can approve - $msg");
+
+} { from => qr/RT System/,
+ to => 'minion at company.com',
+ subject => qr/Ticket Approved:/,
+ body => qr/approved by CEO.*Its Owner may now start to act on it.*notes: And consumed they will be/s,
+}, { from => qr'CEO via RT',
+ to => 'root at localhost',
+ subject => qr/Ticket Approved/,
+ body => qr/The ticket has been approved, you may now start to act on it/,
+};
+
+
+is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ],
+ [ 'new', 'resolved', 'resolved'], 'ticket state after ceo approval');
+
+$dependson_cfo->_Set(
+ Field => 'Status',
+ Value => 'open');
+
+$dependson_ceo->_Set(
+ Field => 'Status',
+ Value => 'new');
+
+mail_ok {
+ my $cfo = RT::CurrentUser->new;
+ $cfo->Load( $users{cfo} );
+
+ $dependson_cfo->CurrentUser($cfo);
+ my $notes = MIME::Entity->build(
+ Data => [ 'sorry, out of resources.' ]
+ );
+ RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8
+
+# my ( $notesval, $notesmsg ) = $dependson_cfo->Correspond( MIMEObj => $notes );
+# ok($notesval, $notesmsg);
+
+ my ($ok, $msg) = $dependson_cfo->SetStatus( Status => 'rejected' );
+ ok($ok, "cfo can approve - $msg");
+
+} { from => qr/RT System/,
+ to => 'minion at company.com',
+ subject => qr/Ticket Rejected: PO for stationary/,
+ body => qr/rejected by CFO/
+};
+
+$t->Load($t->id);$dependson_ceo->Load($dependson_ceo->id);
+is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ],
+ [ 'rejected', 'rejected', 'deleted'], 'ticket state after cfo rejection');
+
More information about the Rt-commit
mailing list