Keeping Resolved tickets Resolved

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!)

Maybe you could replace:

my $cutoff_time = 7 * 86400;

with:

my $cutoff_time = RT->Config->Get('CutOffTime') or 7 * 86400;

Or something similar? I haven’t tried it myself so make sure the the handling of default values is correct. See: RT::Config - RT 4.4.2 Documentation - Best Practical

Thank you! I will definitely check that out.

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! :slight_smile:

Hi Sir,

image

Is this correct for Extension;:ActionsandConditions?

Regards,
Karen

That’s it!

Stephen Cena
Operational IT Administrator
Quality Vision International, Inc.
Phone: (585) 544-0450 x300
To notify helpdesk: http://helpdesk.ogp.qvii.com or email: hd-general@qvii.commailto:hd-general@qvii.com
To report email issues: postmaster@qvii.commailto:postmaster@qvii.com

Hello, please write the above script to RT 4.4.x
Thank you

I wrote the script for our install of RT 4.4.2. Did I use some language constructs that are being deprecated? Or is there some other issue?

I did not know how to install the script.

it works already thank you

FYI,

We have been using the extension:

RT-Extension-RepliesToResolved

Which seems to have most of this functionality.

Regards,
Ken

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?

Well, I’m attempting to try this extension on RT 5 - looks as if little or no minimal will be needed, I’ll post an update once I’ve done testing.

@jjohnson

Did you manage to keep resolved tickets resolved with RT 5 ?

regards
Filip

any updates on this?