Compare commits

...

10 commits

Author SHA1 Message Date
Brett Smith
8216ff4071 CheckTransactionFlags: New Condition. 2018-04-27 14:27:13 -04:00
Brett Smith
8514ae62d5 NotifyOwnerOrQueueAdminCcsAsComent: New ScripAction. 2018-04-26 09:56:55 -04:00
Brett Smith
727d9d5659 OneTicketPerRequestor: Handle more YAML errors.
This avoids dying in cases where the YAML file is empty or malformed.
2018-04-26 09:46:11 -04:00
Brett Smith
325c5ceaf6 upgrade: Add actions for adding correspondence/comments.
I don't know why this doesn't ship with RT…
2018-04-19 10:27:30 -04:00
Brett Smith
43e51ac66c Extension: Fix typo in rt-setup-database instructions. 2018-04-19 10:27:02 -04:00
Brett Smith
3f58ff316d StatusChangeWithDependents: Initial condition. 2018-04-19 09:50:38 -04:00
Brett Smith
83243abf2c Extension: Add initial documentation. 2018-04-11 17:32:21 -04:00
Brett Smith
50db9059bd NotifyOwnerOrQueueAdminCcs: Initial Scrip action. 2018-04-10 11:33:38 -04:00
Brett Smith
50e684e755 OneTicketPerRequestor: Add the search query to the debug log. 2018-04-10 10:51:21 -04:00
Brett Smith
d7a24b0f97 OneTicketPerRequestor: Catch and report errors in the ticket search. 2018-04-10 10:44:29 -04:00
12 changed files with 311 additions and 25 deletions

View file

@ -0,0 +1,7 @@
---
# Keys are queue names.
# Values are either a string, or an array of strings, with
# TicketSQL ordering like "Created DESC" or "id ASC".
# For each queue in this file, the OneTicketPerRequestor mail plug-in
# will redirect incoming mail to the first ticket from the requestor
# that matches with the ordering you specified.

8
etc/upgrade/0.2/content Normal file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env perl
@ScripActions = (
{ Name => 'Notify Owner or Queue AdminCcs',
Description => 'Sends mail to ticket AdminCCs, plus the Owner if set, otherwise queue AdminCCs',
ExecModule => 'NotifyOwnerOrQueueAdminCcs',
},
);

28
etc/upgrade/0.3/content Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env perl
@ScripConditions = (
{ Name => 'On Close With Dependents',
Description => 'Whenever a ticket is closed and other tickets depend on it',
ApplicableTransTypes => 'Status',
ExecModule => 'StatusChangeWithDependents',
Argument => 'dependents > 0; old: initial, active; new: inactive',
},
{ Name => 'On Close Without Dependents',
Description => 'Whenever a ticket is closed and no other tickets depend on it',
ApplicableTransTypes => 'Status',
ExecModule => 'StatusChangeWithDependents',
Argument => 'dependents == 0; old: initial, active; new: inactive',
},
{ Name => 'On Approved With Dependents',
Description => 'Whenever a ticket is an approval and other tickets depend on it',
ApplicableTransTypes => 'Status',
ExecModule => 'StatusChangeWithDependents',
Argument => 'dependents > 0; new: approved',
},
{ Name => 'On Approved Without Dependents',
Description => 'Whenever a ticket is an approval and no other tickets depend on it',
ApplicableTransTypes => 'Status',
ExecModule => 'StatusChangeWithDependents',
Argument => 'dependents == 0; new: approved',
},
);

12
etc/upgrade/0.4/content Normal file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env perl
@ScripActions = (
{ Name => 'Add a Comment',
Description => 'Add a comment to the ticket',
ExecModule => 'RecordComment',
},
{ Name => 'Add Correspondence',
Description => 'Add correspondence to the ticket',
ExecModule => 'RecordCorrespondence',
},
);

8
etc/upgrade/0.5/content Normal file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env perl
@ScripActions = (
{ Name => 'Notify Owner or Queue AdminCcs as Comment',
Description => 'Sends comment mail to ticket AdminCCs, plus the Owner if set, otherwise queue AdminCCs',
ExecModule => 'NotifyOwnerOrQueueAdminCcsAsComment',
},
);

28
etc/upgrade/0.6/content Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env perl
@ScripConditions = (
{ Name => 'On Create Inbound',
Description => 'When a ticket is created by its requestor',
ApplicableTransTypes => 'Create',
ExecModule => 'CheckTransactionFlags',
Argument => 'IsInbound',
},
{ Name => 'On Create Not Inbound',
Description => 'When a ticket is created for the requestor by someone else',
ApplicableTransTypes => 'Create',
ExecModule => 'CheckTransactionFlags',
Argument => '!IsInbound',
},
{ Name => 'On Create Not Inbound With Content',
Description => 'When a ticket with content is created for the requestor by someone else',
ApplicableTransTypes => 'Create',
ExecModule => 'CheckTransactionFlags',
Argument => '!IsInbound HasContent',
},
{ Name => 'On Create Not Inbound Without Content',
Description => 'When an empty ticket is created for the requestor by someone else',
ApplicableTransTypes => 'Create',
ExecModule => 'CheckTransactionFlags',
Argument => '!IsInbound !HasContent',
},
);

View file

@ -0,0 +1,23 @@
package RT::Action::NotifyOwnerOrQueueAdminCcs;
use strict;
use warnings;
use base qw(RT::Action::NotifyOwnerOrAdminCc);
sub SetRecipients {
my $self = shift;
$self->SUPER::SetRecipients();
my $ticket = $self->TicketObj;
# If the ticket has an owner, that person was set as the recipient.
# Now add ticket AdminCcs to the Bcc list.
if ($ticket->Owner != RT->Nobody->id) {
push(@{$self->{Bcc}}, $ticket->AdminCc->MemberEmailAddresses);
}
# If the ticket has no owner, both queue and ticket AdminCCs were set as
# recipients. There's nothing else to do.
}
RT::Base->_ImportOverlays();
1;

View file

@ -0,0 +1,15 @@
package RT::Action::NotifyOwnerOrQueueAdminCcsAsComment;
use strict;
use warnings;
use RT::Action::NotifyAsComment;
use mro "c3";
use base qw(
RT::Action::NotifyOwnerOrQueueAdminCcs
RT::Action::NotifyAsComment
);
RT::Base->_ImportOverlays();
1;

View file

@ -0,0 +1,29 @@
package RT::Condition::CheckTransactionFlags;
use strict;
use warnings;
use base qw(RT::Condition);
sub IsApplicable {
my $self = shift;
my $txn = $self->TransactionObj;
my @flags = split(" ", $self->Argument || "");
foreach my $flag (@flags) {
unless ($flag =~ /^(!?)(\w+)$/) {
$RT::Logger->Error("Argument '$flag' is malformed");
return 0;
}
my $method = $txn->can($2);
unless (defined($method)) {
$RT::Logger->Error("Argument '$flag' refers to nonexistent transaction method");
return 0;
}
return 0 unless (!!$txn->$method == !$1);
}
return 1;
}
RT::Base->_ImportOverlays();
1;

View file

@ -0,0 +1,34 @@
package RT::Condition::StatusChangeWithDependents;
use strict;
use warnings;
use base qw(RT::Condition::StatusChange);
sub Argument {
# Take the dependents setting out of the argument, so StatusChange gets
# everything else.
my $self = shift;
$self->SUPER::Argument =~ s/^\s*dependents\s*(<|>|[=!<>]=)\s*(\d+)\s*;?//r;
}
sub IsApplicable {
my $self = shift;
my $argument = $self->SUPER::Argument;
my ($op, $operand);
if ($argument =~ /^\s*dependents\s*(<|>|[=!<>]=)\s*(\d+)\s*;?/) {
$op = $1;
$operand = $2;
} else {
$RT::Logger->error("Argument '$argument' is incorrect");
return 0;
}
return 0 unless $self->SUPER::IsApplicable;
my $dep_count = $self->TicketObj->DependedOnBy->Count;
return 0 unless (eval "\$dep_count $op $operand;");
return 1;
}
RT::Base->_ImportOverlays();
1;

View file

@ -1,12 +1,102 @@
# This extension is free software.
# You may distribute it under the terms of the GNU General Public License,
# version 3.0, or (at your option) any later version published by the
# Free Software Foundation.
use strict; use strict;
use warnings; use warnings;
package RT::Extension::Conservancy; package RT::Extension::Conservancy;
our $VERSION = '0.1'; our $VERSION = '0.6';
=pod
=head1 RT::Extension::Conservancy
A collection of RT extensions developed for Software Freedom Conservancy.
=head2 Contents
=head3 RT::Action::NotifyOwnerOrQueueAdminCcs
A Scrip action that sends notice to the ticket owner if that's set, or queue
AdminCCs if not. Ticket AdminCCs always get the notice too, which is how
this is different from the built-in "Notify Owner or AdminCCs".
=head3 RT::Action::NotifyOwnerOrQueueAdminCcsAsComment
A variant of the above to send notice from the comment address.
=head3 RT::Condition::CheckTransactionFlags
A Scrip condition that calls configured boolean methods on the transaction
object and passes or fails depending on whether they match the configured
state.
=head3 RT::Condition::StatusChangeWithDependents
A Scrip condition that extends RT::Condition::StatusChange. The affected
ticket must have a configured number of other tickets that Depend On it for
this condition to be true.
=head3 RT::Interface::Email::OneTicketPerRequestor
A mail plugin that examines incoming mail to see if it would create a new
ticket. If so, and the requestor already has a ticket in the destination
queue, the mail is turned into correspondence on the existing ticket
instead.
You can configure which queues this works for, and what ticket the mail gets
merged into (in case there's more than one from ticket history, or admins
creating multiple through the web interface, etc.). Edit
C<etc/OneTicketPerRequestor.yml> following the instructions in the comments.
=head2 Requirements
Works with RT 4.4.
=head2 Installation
=over 4
=item Check out this repository in your RT plugins directory.
=item Edit your RT site configuration to include the line
C<Plugin("RT-Extension-Conservancy")>.
=item If you want to use the OneTicketPerRequestor mail plugin, make sure
your site configuration sets C<@MailPlugins>, and the list includes
C<"OneTicketPerRequestor">.
=item Run in a shell from your checkout:
C<for f in etc/upgrade/*/content; do rt-setup-database --action insert --datafile "$(realpath -s "$f")" || break; done>
=item Restart your RT server.
=back
=head2 Upgrading
=over 4
=item Before you upgrade anything, note the C<$VERSION> of this extension
you're currently running.
=item Update your plugin checkout to the version you want with C<git pull>,
C<git checkout>, etc.
=item Repeat the C<rt-setup-database> step from the Installation step,
running it only on the versions that are later than the C<$VERSION> you
noted two steps ago.
=item Restart your RT server.
=back
=head2 Copyright and License
Copyright © 2018 Brett Smith <brettcsmith@brettcsmith.org>
This extension is free software.
You may distribute it under the terms of the GNU General Public License,
version 3.0, or (at your option) any later version published by the
Free Software Foundation.
=cut
1; 1;

View file

@ -18,10 +18,8 @@ my $plugin_self = RT::Plugin->new(name => "RT-Extension-Conservancy");
foreach my $confdir ($plugin_self->Path("etc"), $RT::LocalEtcPath, $RT::EtcPath) { foreach my $confdir ($plugin_self->Path("etc"), $RT::LocalEtcPath, $RT::EtcPath) {
next unless (defined($confdir) && $confdir); next unless (defined($confdir) && $confdir);
my $conf_path = "$confdir/OneTicketPerRequestor.yml"; my $conf_path = "$confdir/OneTicketPerRequestor.yml";
if (-r $conf_path) { eval { %QUEUES = %{YAML::Tiny->read($conf_path)->[0]} };
%QUEUES = %{YAML::Tiny->read($conf_path)->[0]}; last if (%QUEUES);
last if (%QUEUES);
}
} }
# Compile the user's configuration into arguments for RT::Tickets->OrderBy. # Compile the user's configuration into arguments for RT::Tickets->OrderBy.
@ -77,22 +75,28 @@ sub BeforeDecrypt {
return; return;
} }
my $ticket_search = RT::Tickets->new($RT::SystemUser); my $dest_ticket = eval {
$ticket_search->LimitQueue( my $ticket_search = RT::Tickets->new($RT::SystemUser);
OPERATOR => "=", $ticket_search->LimitQueue(
VALUE => $queue_name, OPERATOR => "=",
); VALUE => $queue_name,
$ticket_search->LimitWatcher( );
TYPE => "Requestor", $ticket_search->LimitWatcher(
OPERATOR => "=", TYPE => "Requestor",
VALUE => $from_address, OPERATOR => "=",
); VALUE => $from_address,
foreach my $sort_args (@$sort_orders) { );
$ticket_search->OrderBy(@$sort_args); foreach my $sort_args (@$sort_orders) {
$ticket_search->OrderBy(@$sort_args);
}
$RT::Logger->debug("OTPR search: " . $ticket_search->BuildSelectQuery());
$ticket_search->First;
};
if ($@) {
$RT::Logger->debug("OTPR stopping: Ticket search died: $@");
return;
} }
elsif (!defined($dest_ticket)) {
my $dest_ticket = $ticket_search->First;
if (!defined($dest_ticket)) {
$RT::Logger->debug(sprintf("OTPR stopping: No ticket found from <%s> in queue %s", $RT::Logger->debug(sprintf("OTPR stopping: No ticket found from <%s> in queue %s",
$from_address, $queue_name)); $from_address, $queue_name));
return; return;