I’ve finally been able to spend some time and start coding for RT. One issue we’ve had here is users re-opening tickets that were closed. Ordinarily it’s not a problem. However if they re-open a months old ticket, our metrics get thrown off. So, I wrote this Action: CreateChildTicketIfTooOld. We opted for a 7 day “re-open” period. After that, a new ticket gets created & the original ticket referenced. I retain (as best I can) the original Resolved time so that the stats will look correct. I’m sharing the code I’ve written in hopes that a) people find it useful and b) i get suggestions on how to improve it. One I would love to do is to somehow put the day duration into my RT_SiteConfig.pm rather than having to edit the .pm file directly if I want to change the day duration.
To deploy:
Condition: On Resolve
Action: Create Child Ticket If Too Old
Template: Blank
Mode: Batch
package RT::Action::CreateChildTicketIfTooOld;
use strict;
use base qw(RT::Action);
sub Describe {
my $self = shift;
return(ref $self . " Fork the last comment/correspondance into a new ticket.");
}
sub Prepare {
# Nothing to prepare
return 1;
}
sub Commit {
my $self = shift;
my $ticket = $self->TicketObj;
my $transaction = $self->TransactionObj;
my $cutoff_time = 7 * 86400; # DAYS * 86400 seconds
my $time_difference = 0;
#==================================================
# Determine if the ticket has been resolved
my $transactions = $ticket->Transactions;
my $resolved_str = "to 'resolved'";
my $resolved_date = undef;
my $current_date = undef;
$transactions->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Status%');
while (my $txn = $transactions->Next)
{
if (index($txn->Description,$resolved_str) != -1)
{
if ($resolved_date == undef)
{
$resolved_date = RT::Date->new(RT->SystemUser);
}
$resolved_date->Set(Format => 'ISO', Value => $txn->Created, Timezone => 'user');
}
}
if ($resolved_date == undef)
{
# There's nothing to do
return 1;
}
$current_date = RT::Date->new(RT->SystemUser);
$current_date->SetToNow;
$time_difference = $current_date->Diff($resolved_date);
if ($time_difference <= $cutoff_time)
{
# There is nothing to do
return 1;
}
# We need to create the child ticket and reset the parents resolved time
# Mute the messages
$ticket->SquelchMailTo($ticket->RequestorAddresses);
$ticket->SquelchMailTo($ticket->AdminCcAddresses);
$ticket->SquelchMailTo($ticket->CcAddresses);
# Reset the ticket data
$ticket->SetStatus(Status => 'resolved');
$ticket->SetResolved($resolved_date->Get);
# Unmute
$ticket->UnsquelchMailTo($ticket->RequestorAddresses);
$ticket->UnsquelchMailTo($ticket->AdminCcAddresses);
$ticket->UnsquelchMailTo($ticket->CcAddresses);
# Get the ticket entry prepared
my $MIMEObj = MIME::Entity->build(To => $transaction->Attachments->First->GetHeader('To'),
From => $transaction->Attachments->First->GetHeader('From'),
Subject => $transaction->Subject,
Date => $transaction->Attachments->First->GetHeader('Date'),
Encoding => '-SUGGEST',
Data => 'Response received regarding ticket #' . $ticket->Id . "\n\n" . $transaction->Content
);
RT::I18N::SetMIMEEntityToUTF8($MIMEObj);
# Add any file attachments to the ticket
my $transaction_content_obj = $transaction->ContentObj;
my $attachments = $transaction->Attachments;
while (my $attachment = $attachments->Next)
{
next unless ($attachment->ContentLength || $attachment->Filename);
next if
(
$transaction_content_obj &&
$transaction_content_obj->Id == $attachment->Id &&
($transaction_content_obj->ContentType =~ qr{text/plain}i ||
$transaction_content_obj->ContentType =~ qr{text/html}i)
);
$MIMEObj->attach( Type => $attachment->ContentType,
CharSet => $attachment->OriginalEncoding,
Data => $attachment->OriginalContent,
Filename => Encode::decode_utf8($attachment->Filename),
Encoding => '-SUGGEST'
);
}
# Create the child ticket & establish the link
my $child_ticket = RT::Ticket->new(RT->SystemUser);
my $user = RT::User->new(RT->SystemUser);
$user->Load($transaction->Creator);
my $mail = $user->EmailAddress;
my $requestors = $ticket->RequestorAddresses;
# Check the ticket to see if the transcation was done by someone on the ticket
if ((index($ticket->RequestorAddresses,$mail) == -1) &&
(index($ticket->CcAddresses,$mail) == -1) &&
(index($ticket->AdminCcAddresses,$mail) == -1) )
{
# The email wasn't part of the ticket
if ($requestors == undef)
{
$requestors = $mail;
} else {
$requestors = $ticket->RequestorAddresses . ', ' . $mail;
}
}
my ($child_id, $child_TransObj, $errormsg) =
$child_ticket->Create( Queue => $ticket->Queue,
Subject => 'Not Resolved: #'. $ticket->Id . ':' . $ticket->Subject,
RefersTo => $ticket->Id,
MIMEObj => $MIMEObj,
Requestor => $requestors,
Cc => $ticket->CcAddresses,
AdminCc => $ticket->AdminCcAddresses
);
# Document the ticket link was made by the system
$ticket->Comment( Content => 'Following the previous correspondence, a new ticket was opened: Ticket #' . $child_ticket->Id );
}
1;
I just finished reworking this code a little bit. This is the Action we’re putting into production. Some of the code may be optimized, but this all appears to work. Please comment/etc! Thanks!
package RT::Action::CreateChildTicketIfTooOld;
use strict;
use base qw(RT::Action);
sub Describe {
my $self = shift;
return(ref $self . " Fork the last comment/correspondance into a new ticket.");
}
sub Prepare {
# Nothing to prepare
return 1;
}
sub Commit {
my $self = shift;
my $ticket = $self->TicketObj;
my $transaction = $self->TransactionObj;
my $user = RT::User->new(RT->SystemUser);
my $cutoff_time = 7 * 86400; # DAYS * 86400 seconds
my $time_difference = 0;
#==================================================
# Determine if the ticket has been resolved
my $transactions = $ticket->Transactions;
my $resolved_str = "to 'resolved'";
my $resolved_date = undef;
my $current_date = undef;
# Identify who caused the re-open to happen
$user->Load($transaction->Creator);
$transactions->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Status%');
while (my $txn = $transactions->Next)
{
if (index($txn->Description,$resolved_str) != -1)
{
if ($resolved_date == undef)
{
$resolved_date = RT::Date->new(RT->SystemUser);
}
$resolved_date->Set(Format => 'ISO', Value => $txn->Created, Timezone => 'user');
}
}
if ($resolved_date == undef)
{
# There's nothing to do
return 1;
}
$current_date = RT::Date->new(RT->SystemUser);
$current_date->SetToNow;
$time_difference = $current_date->Diff($resolved_date);
if ($time_difference <= $cutoff_time)
{
# There is nothing to do
return 1;
}
# We need to create the child ticket and reset the parents resolved time
# Mute the messages
$ticket->SquelchMailTo($ticket->RequestorAddresses);
$ticket->SquelchMailTo($ticket->AdminCcAddresses);
$ticket->SquelchMailTo($ticket->CcAddresses);
# Reset the ticket data
$ticket->SetStatus(Status => 'resolved');
$ticket->SetResolved($resolved_date->Get);
# Unmute
$ticket->UnsquelchMailTo($ticket->RequestorAddresses);
$ticket->UnsquelchMailTo($ticket->AdminCcAddresses);
$ticket->UnsquelchMailTo($ticket->CcAddresses);
# Compose the response string
my $response_string = "User ";
if (defined $user->RealName)
{
$response_string = $response_string . $user->RealName . " ";
} else {
$response_string = $response_string . "NAME REDACTED ";
}
if (defined $user->EmailAddress)
{
$response_string = $response_string . "(" . $user->EmailAddress . ") ";
} else {
$response_string = $response_string . "(EMAIL REDACTED) ";
}
$response_string = $response_string . " has responded to closed ticket #" . $ticket->Id;
$response_string = $response_string . "\n\n" . $transaction->Content;
# Get the ticket entry prepared
my $MIMEObj = MIME::Entity->build(To => $transaction->Attachments->First->GetHeader('To'),
From => $transaction->Attachments->First->GetHeader('From'),
Subject => $transaction->Subject,
Date => $transaction->Attachments->First->GetHeader('Date'),
Encoding => '-SUGGEST',
Data => $response_string
);
RT::I18N::SetMIMEEntityToUTF8($MIMEObj);
# Add any file attachments to the ticket
my $transaction_content_obj = $transaction->ContentObj;
my $attachments = $transaction->Attachments;
while (my $attachment = $attachments->Next)
{
next unless ($attachment->ContentLength || $attachment->Filename);
next if
(
$transaction_content_obj &&
$transaction_content_obj->Id == $attachment->Id &&
($transaction_content_obj->ContentType =~ qr{text/plain}i ||
$transaction_content_obj->ContentType =~ qr{text/html}i)
);
$MIMEObj->attach( Type => $attachment->ContentType,
CharSet => $attachment->OriginalEncoding,
Data => $attachment->OriginalContent,
Filename => Encode::decode_utf8($attachment->Filename),
Encoding => '-SUGGEST'
);
}
# Create the child ticket & establish the link
my $child_ticket = RT::Ticket->new(RT->SystemUser);
my ($child_id, $child_TransObj, $errormsg) =
$child_ticket->Create( Queue => $ticket->Queue,
Subject => 'Not Resolved: #'. $ticket->Id . ':' . $ticket->Subject,
RefersTo => $ticket->Id,
MIMEObj => $MIMEObj,
Requestor => $ticket->RequestorAddresses,
Cc => $ticket->CcAddresses,
AdminCc => $ticket->AdminCcAddresses
);
# Document the ticket link was made by the system
$ticket->Comment( Content => 'Following the previous correspondence, a new ticket was opened: Ticket #' . $child_ticket->Id );
}
1;
Need to tinker some more LOL. Just found a “bug”: A ticket that is re-opened but was closed outside the window causes the ticket to get Linked and then resolved with the earlier date. If the above Action is deployed at server deployment, then there shouldn’t be an issue. Adding this Action to a production system with live data could see anomalies. I’m going to try to account for it now. Stay tuned! (Help appreciated too!)
Right now, it’s looking like my solution to the “already open previously closed” tickets is just to announce a policy change. All the tickets that were reopened outside the window will get split going forward. It appears to be the simplest solution.
My other though is ti introduce a “cut off” so ticket after a certain value get affected then deal with all the previously opened tickets and start clean.
Barring that one issue, the Action works very well. I’m debating expanding it to include Rejected tickets but it’s straight forward enough to modify on your own.
That worked brilliantly! I’ve tightened up the string concatenation a little and have added the ability to configure the cut off period using the RT_SiteConfig file. I’m trying to settle on the parameter name to use there. I’m also looking into how to accommodate for Rejected tickets in the same code without making things too complicated. I’ll publish the morst recent version of my code later today. Thank you!
Ok; I think this is the final revision I’m making. I was able to move some configuration options into RT_Siteconfig.pm. I tried using arrays for the statuses, but I kept crashing the system. I know the error is on my part, but didn’t want to deal with it. So I fell back & used an 0/1 to decide what to do. So now inside my RT_SiteConfig.pm:
Set($MaxDaysToRepoen, INT); – The maximum number of days that can pass before a ticket can’t be re-opened.
Set($IncludeRejected, 0/1); – 0 = Only process ‘resolved’ tickets. 1 = process both ‘resolved’ and ‘rejected’.
“Sometimes the straightest pass is through the mud” - To deal with any active tickets that are outside the date, we are simply going to let the users know of the upcoming change and what that will mean to them. It’s becoming too tricky to accommodate everything. Anyways, this is the final bit of code I have. Please review & make comments as that’s how we make software better!
package RT::Action::CreateChildTicketIfTooOld;
use strict;
use base qw(RT::Action);
sub Describe {
my $self = shift;
return(ref $self . " Create a new linked ticket if the response is passed the Resolved cut off period");
}
sub Prepare {
# Nothing to prepare
return 1;
}
sub Commit {
my $self = shift;
my $ticket = $self->TicketObj;
my $transaction = $self->TransactionObj;
my $user = RT::User->new(RT->SystemUser);
my $cutoff_time = (RT->Config->Get('MaxDaysToReopen') or 7) * 86400; # DAYS * 86400 seconds, default=7
my $inc_rejected = RT->Config->Get('IncludeRejected') or 0; # Default - only resolved 1 = resolved/rejected
my $time_difference = 0;
#==================================================
# Determine if the ticket has been resolved
my $transactions = $ticket->Transactions;
my $resolved = "resolved";
my $rejected = "rejected";
my $resolved_str = "to '" . $resolved . "'";
my $rejected_str = "to '" . $rejected . "'";
my $status = $resolved;
my $status_date = undef;
my $current_date = undef;
# Identify who caused the re-open to happen
$user->Load($transaction->Creator);
$transactions->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Status%');
while (my $txn = $transactions->Next)
{
if (index($txn->Description,$resolved_str) != -1)
{
if ($status_date == undef)
{
$status_date = RT::Date->new(RT->SystemUser);
}
$status_date->Set(Format => 'ISO', Value => $txn->Created, Timezone => 'user');
$status = $resolved;
}
if ($inc_rejected == 1)
{
if (index($txn->Description,$rejected_str) != -1)
{
if ($status_date == undef)
{
$status_date = RT::Date->new(RT->SystemUser);
}
$status_date->Set(Format => 'ISO', Value => $txn->Created, Timezone => 'user');
$status = $rejected;
}
}
}
if ($status_date == undef)
{
# There's nothing to do
return 1;
}
$current_date = RT::Date->new(RT->SystemUser);
$current_date->SetToNow;
$time_difference = $current_date->Diff($status_date);
if ($time_difference <= $cutoff_time)
{
# There is nothing to do
return 1;
}
# We need to create the child ticket and reset the parents resolved time
# Mute the messages
$ticket->SquelchMailTo($ticket->RequestorAddresses);
$ticket->SquelchMailTo($ticket->AdminCcAddresses);
$ticket->SquelchMailTo($ticket->CcAddresses);
# Reset the ticket data
$ticket->SetStatus(Status => $status);
$ticket->SetResolved($status_date->Get);
# Unmute
$ticket->UnsquelchMailTo($ticket->RequestorAddresses);
$ticket->UnsquelchMailTo($ticket->AdminCcAddresses);
$ticket->UnsquelchMailTo($ticket->CcAddresses);
# Compose the response string
my $response_string = "User ";
if (defined $user->RealName)
{
$response_string .= $user->RealName . " ";
} else {
$response_string .= "NAME REDACTED ";
}
if (defined $user->EmailAddress)
{
$response_string .= "(" . $user->EmailAddress . ") ";
} else {
$response_string .= "(EMAIL REDACTED) ";
}
$response_string .= " has responded to " . $status . " ticket #" . $ticket->Id;
$response_string .= "\n\n" . $transaction->Content;
# Get the ticket entry prepared
my $MIMEObj = MIME::Entity->build(To => $transaction->Attachments->First->GetHeader('To'),
From => $transaction->Attachments->First->GetHeader('From'),
Subject => $transaction->Subject,
Date => $transaction->Attachments->First->GetHeader('Date'),
Encoding => '-SUGGEST',
Data => $response_string
);
RT::I18N::SetMIMEEntityToUTF8($MIMEObj);
# Add any file attachments to the ticket
my $transaction_content_obj = $transaction->ContentObj;
my $attachments = $transaction->Attachments;
while (my $attachment = $attachments->Next)
{
next unless ($attachment->ContentLength || $attachment->Filename);
next if
(
$transaction_content_obj &&
$transaction_content_obj->Id == $attachment->Id &&
($transaction_content_obj->ContentType =~ qr{text/plain}i ||
$transaction_content_obj->ContentType =~ qr{text/html}i)
);
$MIMEObj->attach( Type => $attachment->ContentType,
CharSet => $attachment->OriginalEncoding,
Data => $attachment->OriginalContent,
Filename => Encode::decode_utf8($attachment->Filename),
Encoding => '-SUGGEST'
);
}
# Create the child ticket & establish the link
my $child_ticket = RT::Ticket->new(RT->SystemUser);
my ($child_id, $child_TransObj, $errormsg) =
$child_ticket->Create( Queue => $ticket->Queue,
Subject => 'Not Resolved: #'. $ticket->Id . ':' . $ticket->Subject,
RefersTo => $ticket->Id,
MIMEObj => $MIMEObj,
Requestor => $ticket->RequestorAddresses,
Cc => $ticket->CcAddresses,
AdminCc => $ticket->AdminCcAddresses
);
# Document the ticket link was made by the system
my $last_comment = "Following the previous correspondence, a new ticket was opened: Ticket #" . $child_ticket->Id;
#$ticket->Comment( Content => 'Following the previous correspondence, a new ticket was opened: Ticket #' . $child_ticket->Id );
$ticket->Comment( Content => $last_comment);
}
1;
Figures, this is what I get for going to sleep at night LOL. This Action works, however if a ticket does get reopened within the ‘grace period’ it will STILL get split/linked once the cut off date hits. I’m going to work on this today & see if I can get it addressed. I’m close at least!
The solution in this thread looks about equal to RTx::RepliesToResolved, but it hasn’t been updated for RT 5.0.0 yet, or so it claims. Before I take that upon myself, is this a working RT 5 solution?