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.
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:-
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:-
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
I will give a try and get back to you, Thank you so much for your time and suggestions.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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:-
Any thoughts on this error, please help.
Thanks and Regards,
Sreedevi
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hello Ram
Below is the code I used, replaced the status name to Deleted :-
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
If your question is solved, please don't forget to accept the Answer.
Thank you and Kind regards,
Ram
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.