Delete jira issues moved to Deleted status using post function script in workflow with a delay

Sreedevi Raman Pisharody June 29, 2023

Hello Team,

 

We have a requirement to setup a feature in jira workflow at a status named Deleted where in after the jira ticket is moved to Deleted Status, after 10 mins only the jira ticket should be deleted from Jira Project completely.

 

I found the below script from one of the Atlassian community

import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.component.ComponentAccessor

IssueService issueService = ComponentAccessor.getIssueService()
def userManager = ComponentAccessor.getUserManager()
String userkey = transientVars.userKey
def userDelete = userManager.getUserByKey(userkey)

log.debug("Delete issue $issue with user $userDelete")
if (issue) {
def validationResult = issueService.validateDelete(userDelete, issue.id)
if (validationResult.errorCollection.hasAnyErrors()) {
log.warn("Can't delete issue")
}
else {
issueService.delete(userDelete, validationResult)
}
}

 However, i am not able to understand if this would help since i have seen a comment stating this gives illegal operation command.... in the same community discussion.

I also need to ensure there is a delay of say approximately 10 mins before the Deletion after the issue is moved to Deleted status.

In the script above, i am not clear why is the getUserManager() used and how will it be validated.

Please help to get this fixed.

1 answer

1 accepted

0 votes
Answer accepted
Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 1, 2023

Hi @Sreedevi Raman Pisharody

It would be best to use ScriptRunner's Custom Scheduled Jobs for your requirement. I am suggesting this because if you try doing this via the Workflow, you will encounter exceptions when trying to delete the issue.

Below is a sample working code for your reference:-

import com.adaptavist.hapi.jira.users.Users
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.event.type.EventDispatchOption

def loggedInUser = Users.loggedInUser
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.issueManager
def changeHistoryManager = ComponentAccessor.changeHistoryManager

def query = jqlQueryParser.parseQuery("project = 'MOCK' and type = 'Story' and status = 'Done'")
def search = searchService.search(loggedInUser, query, PagerFilter.unlimitedFilter)

final statusName = 'Done'
def timeDiff

search.results.each {

def issue = issueManager.getIssueObject(it.id) as Issue

def changeItems = changeHistoryManager.getChangeItemsForField(issue, 'status')

changeItems.reverse().each { ChangeItemBean item ->
if(item.fromString == statusName) {
timeDiff = System.currentTimeMillis() - item.created.time
}
}

//Convert the time value from milisecond to minutes
def inMinutes = Math.round( ((timeDiff / 1000) / 60) as Double)

//if the value is greater than or equals to 20 minutes delete the issue
if(inMinutes >= 20) {

issueManager.deleteIssue(loggedInUser, issue, EventDispatchOption.ISSUE_DELETED, false)
}

}
return null

Please note that the sample working code above is not 100% exact to your environment. Hence you will need to make the required modifications.

To configure a Custom scheduled job, first, select the Jobs option on the left, and then select the Custom scheduled job option as shown in the screenshot below:-

config1.png

Once you are on the Custom scheduled job page, you can add the sample code provided above and set the Note, User and the time you want to trigger the job as shown in the screenshot below:-

configuration.png

In the example above, the Custom scheduled job has been configured to trigger every minute. In executing the job every minute, it will be deleted if any issue has been transitioned to that particular status you want for 20 minutes or more.

I hope that this helps to solve your question. :-)

Thank you and Kind regards,

Ram

Sreedevi Raman Pisharody July 2, 2023

I will give a try and get back to you, Thank you so much for your time and suggestions.

Sreedevi Raman Pisharody July 2, 2023

Hello Ram,

I agree with your comment that adding in the workflow transition can cause issues. I did encounter by my earlier code when i moved a jira ticket into the Deleted status, it kept on circling back and comes out with time out message, and does not get deleted either.

Will adding this into schedule jobs my concern is whether it would impact the performance? As we would need to run this deletion on multiple projects. 

My thought was if we run the deletion action after few mins of moving the ticket into Deleted status, may not cause impact with performance. Correct me if i am wrong.

Thanks and Regards,

Sreedevi

Sreedevi Raman Pisharody July 2, 2023

Hello Ram,

I also tried to run the job with the code you provided, however it fails with below error

2023-07-02 09:29:33,428 ERROR [jobs.AbstractCustomScheduledJob]: ************************************************************************************* 2023-07-02 09:29:33,428 ERROR [jobs.AbstractCustomScheduledJob]: Script job: 'Delete Jira tickets in Deleted status - Jira Align' failed java.lang.NullPointerException: Cannot invoke method div() on null object at Script104$_run_closure1.doCall(Script104.groovy:35) at Script104.run(Script104.groovy:22)

There is no error on the code it seems to be valid, but on the line number 35 it isn't invoking the division operation which you have mentioned as below:-

def inMinutes = Math.round( ((timeDiff / 1000)/ 60) as Double)

Any thoughts on this error, please help.

 

Thanks and Regards,

Sreedevi

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 2, 2023

Hi @Sreedevi Raman Pisharody

I have run the test in my environment and don't seem to be encountering any issues.

Could you please share the exact code you tested with and a screenshot of your Custom Scheduled Job configuration? I am requesting this so I can get a better understanding as to why it is working fine in my environment and failing in yours.

Please also clarify the version of Jira and ScriptRunner that you are currently using.

I am looking forward to your feedback.

Thank you and Kind regards,

Ram

Sreedevi Raman Pisharody July 3, 2023

Hello Ram

Below is the code I used, replaced the status name to Deleted :-

import com.adaptavist.hapi.jira.users.Users
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.event.type.EventDispatchOption

def loggedInUser = Users.loggedInUser
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.issueManager
def changeHistoryManager = ComponentAccessor.changeHistoryManager

def query = jqlQueryParser.parseQuery("project = 'JATL' and status = 'Deleted'")
def search = searchService.search(loggedInUser, query, PagerFilter.unlimitedFilter)

final statusName = 'Deleted'
def timeDiff

search.results.each {

def issue = issueManager.getIssueObject(it.id) as Issue

def changeItems = changeHistoryManager.getChangeItemsForField(issue, 'status')

changeItems.reverse().each { ChangeItemBean item ->
if(item.fromString == statusName) {
timeDiff = System.currentTimeMillis() - item.created.time
}
}

//Convert the time value from milisecond to minutes
def inMinutes = Math.round( ((timeDiff / 1000) / 60) as Double)

//if the value is greater than or equals to 20 minutes delete the issue
if(inMinutes >= 20) {

issueManager.deleteIssue(loggedInUser, issue, EventDispatchOption.ISSUE_DELETED, false)
}

}
return null
I have my Jira server running on version 8.13.9 and script runner on version 7.11.0
Please let me know if you need any more information.
Thanks and Regards,
Sreedevi
Sreedevi Raman Pisharody July 3, 2023

Here is what I see on the schedule jobs:-

023-07-02 09:29:33,428 ERROR [jobs.AbstractCustomScheduledJob]: Script job: 'Delete Jira tickets in Deleted status - Jira Align' failed java.lang.NullPointerException: Cannot invoke method div() on null object at Script104$_run_closure1.doCall(Script104.groovy:35) at Script104.run(Script104.groovy:22)

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 4, 2023

Hi @Sreedevi Raman Pisharody

After looking at your updated code and comparing it against the error message that you are receiving, it appears that the error is coming from this line of the code:-

search.results.each {

If the error is to come from here, it's most likely that the JQL Query you are using, as shown below, is not returning any results. When causes the search object to be null as well.

def query = jqlQueryParser.parseQuery("project = 'JATL' and status = 'Deleted'")

def search = searchService.search(loggedInUser, query, PagerFilter.unlimitedFilter)

I suggest adding some logging parameters to confirm this. Please modify your code to:-

import com.adaptavist.hapi.jira.users.Users
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.event.type.EventDispatchOption

def loggedInUser = Users.loggedInUser
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.issueManager
def changeHistoryManager = ComponentAccessor.changeHistoryManager

def query = jqlQueryParser.parseQuery("project = 'JATL' and status = 'Deleted'")
def search = searchService.search(loggedInUser, query, PagerFilter.unlimitedFilter)
log.warn "=====================>>>>Total Results: ${search.results.size()}"

final statusName = 'Deleted'
def timeDiff

search.results.each {

def issue = issueManager.getIssueObject(it.id) as Issue

def changeItems = changeHistoryManager.getChangeItemsForField(issue, 'status')

changeItems.reverse().each { ChangeItemBean item ->
if(item.fromString == statusName) {
timeDiff = System.currentTimeMillis() - item.created.time
}
}

//Convert the time value from milisecond to minutes
def inMinutes = Math.round( ((timeDiff / 1000) / 60) as Double)

//if the value is greater than or equals to 20 minutes delete the issue
if(inMinutes >= 20) {

issueManager.deleteIssue(loggedInUser, issue, EventDispatchOption.ISSUE_DELETED, false)
}

}
null

and see the total number of results returned in the log.

If it returns 0, you must double-check your JQL query to see if the status Deleted is correct.

Please share the logs once you run the test.

Also, I suggest running a JQL Query directly on Jira using your query, i.e. 

project = 'JATL' and status = 'Deleted'

and see if any results are returned.

I am looking forward to your feedback.

Thank you and Kind regards,

Ram

Sreedevi Raman Pisharody July 4, 2023

@Ram Kumar Aravindakshan _Adaptavist_  I tried the query before as well as today and it returns the number of jira tickets in Deleted status on the project.

I ran the script you provided latest one, and it is failing still

2023-07-04 05:51:45,127 WARN [runner.ScriptBindingsManager]: =====================>>>>Total Results: 6

2023-07-04 05:51:45,139 ERROR [jobs.AbstractCustomScheduledJob]: ************************************************************************************* 2023-07-04 05:51:45,139 ERROR [jobs.AbstractCustomScheduledJob]: Script job: 'Delete Jira tickets in Deleted status - Jira Align' failed java.lang.NullPointerException: Cannot invoke method div() on null object at Script108$_run_closure1.doCall(Script108.groovy:36) at Script108.run(Script108.groovy:23)

I feel it is failing at :- since it states cannot invoke method div() so it fails division operation on time calculation.. may be I am wrong not sure.

//Convert the time value from milisecond to minutes
def inMinutes = Math.round( ((timeDiff / 1000) / 60) as Double)

  Please advise.

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 4, 2023

Hi @Sreedevi Raman Pisharody

The inMinutes calculation is not the cause of the problem.

 The problem is coming from this if/else condition:-

   if(item.fromString == statusName) {
timeDiff = System.currentTimeMillis() - item.created.time
}

I reran the test in my environment using fromString, but it failed. Upon changing it to toString to get the current status, that issue is in, and it works without any issues.

Below is the updated code for your reference:-

import com.adaptavist.hapi.jira.users.Users
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.event.type.EventDispatchOption

def loggedInUser = Users.loggedInUser
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.issueManager
def changeHistoryManager = ComponentAccessor.changeHistoryManager

def query = jqlQueryParser.parseQuery("project = 'JATL' and status = 'Deleted'")
def search = searchService.search(loggedInUser, query, PagerFilter.unlimitedFilter)

final statusName = 'Deleted'
def timeDiff

search.results.each {

def issue = issueManager.getIssueObject(it.id) as Issue
def changeItems = changeHistoryManager.getChangeItemsForField(issue, 'status')

changeItems.reverse().each { ChangeItemBean item ->
if(item.toString == statusName) {
timeDiff = System.currentTimeMillis() - item.created.time
}
}
def inMinutes = Math.round( ((timeDiff / 1000) / 60) as Double)

if(inMinutes >= 20) {
issueManager.deleteIssue(loggedInUser, issue, EventDispatchOption.ISSUE_DELETED, false)
}

}
null

Give the updated code a try and let me know how it goes.

Thank you and Kind regards,

Ram 

Sreedevi Raman Pisharody July 4, 2023

Hello @Ram Kumar Aravindakshan _Adaptavist_ ,

This was a great catch, I did not think about, was debugging on the time division based on the error message

I ran the script and was able to delete the issues moved to Delete status.

One question, will this scheduling of the jobs cause any performance related issues when we do have bulk deletions, and upon associating this schedule job to multiple jira projects?

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 4, 2023

Hi @Sreedevi Raman Pisharody

It shouldn't impact your performance.

However, if you feel it may cause a performance issue, you can modify the timing to every 30 minutes or an hour instead of setting it to execute every minute.

Thank you and Kind regards,

Ram

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 4, 2023

Hi @Sreedevi Raman Pisharody

If your question is solved, please don't forget to accept the Answer.

Thank you and Kind regards,

Ram

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events