Compare commits
10 commits
daeabc1aa6
...
8216ff4071
Author | SHA1 | Date | |
---|---|---|---|
|
8216ff4071 | ||
|
8514ae62d5 | ||
|
727d9d5659 | ||
|
325c5ceaf6 | ||
|
43e51ac66c | ||
|
3f58ff316d | ||
|
83243abf2c | ||
|
50db9059bd | ||
|
50e684e755 | ||
|
d7a24b0f97 |
12 changed files with 311 additions and 25 deletions
7
etc/OneTicketPerRequestor.yml
Normal file
7
etc/OneTicketPerRequestor.yml
Normal 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
8
etc/upgrade/0.2/content
Normal 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
28
etc/upgrade/0.3/content
Normal 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
12
etc/upgrade/0.4/content
Normal 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
8
etc/upgrade/0.5/content
Normal 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
28
etc/upgrade/0.6/content
Normal 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',
|
||||
},
|
||||
);
|
23
lib/RT/Action/NotifyOwnerOrQueueAdminCcs.pm
Normal file
23
lib/RT/Action/NotifyOwnerOrQueueAdminCcs.pm
Normal 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;
|
15
lib/RT/Action/NotifyOwnerOrQueueAdminCcsAsComment.pm
Normal file
15
lib/RT/Action/NotifyOwnerOrQueueAdminCcsAsComment.pm
Normal 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;
|
29
lib/RT/Condition/CheckTransactionFlags.pm
Normal file
29
lib/RT/Condition/CheckTransactionFlags.pm
Normal 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;
|
34
lib/RT/Condition/StatusChangeWithDependents.pm
Normal file
34
lib/RT/Condition/StatusChangeWithDependents.pm
Normal 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;
|
|
@ -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 warnings;
|
||||
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;
|
||||
|
|
|
@ -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) {
|
||||
next unless (defined($confdir) && $confdir);
|
||||
my $conf_path = "$confdir/OneTicketPerRequestor.yml";
|
||||
if (-r $conf_path) {
|
||||
%QUEUES = %{YAML::Tiny->read($conf_path)->[0]};
|
||||
last if (%QUEUES);
|
||||
}
|
||||
eval { %QUEUES = %{YAML::Tiny->read($conf_path)->[0]} };
|
||||
last if (%QUEUES);
|
||||
}
|
||||
|
||||
# Compile the user's configuration into arguments for RT::Tickets->OrderBy.
|
||||
|
@ -77,22 +75,28 @@ sub BeforeDecrypt {
|
|||
return;
|
||||
}
|
||||
|
||||
my $ticket_search = RT::Tickets->new($RT::SystemUser);
|
||||
$ticket_search->LimitQueue(
|
||||
OPERATOR => "=",
|
||||
VALUE => $queue_name,
|
||||
);
|
||||
$ticket_search->LimitWatcher(
|
||||
TYPE => "Requestor",
|
||||
OPERATOR => "=",
|
||||
VALUE => $from_address,
|
||||
);
|
||||
foreach my $sort_args (@$sort_orders) {
|
||||
$ticket_search->OrderBy(@$sort_args);
|
||||
my $dest_ticket = eval {
|
||||
my $ticket_search = RT::Tickets->new($RT::SystemUser);
|
||||
$ticket_search->LimitQueue(
|
||||
OPERATOR => "=",
|
||||
VALUE => $queue_name,
|
||||
);
|
||||
$ticket_search->LimitWatcher(
|
||||
TYPE => "Requestor",
|
||||
OPERATOR => "=",
|
||||
VALUE => $from_address,
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
my $dest_ticket = $ticket_search->First;
|
||||
if (!defined($dest_ticket)) {
|
||||
elsif (!defined($dest_ticket)) {
|
||||
$RT::Logger->debug(sprintf("OTPR stopping: No ticket found from <%s> in queue %s",
|
||||
$from_address, $queue_name));
|
||||
return;
|
||||
|
|
Loading…
Reference in a new issue