Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
4,295,776
Community Members
 
Community Events
165
Community Groups

Need help with creating custom script for scriptrunner jobs

Edited

I have a requirement where I want to archive certain EPICS across my JIRA instance. I know there is an inbuilt scriptrunner job to archive, but we don't want to use it due to certain very very bad experience in the past with it.

So, I want to automatically archive my EPICS based on the below criteria,

* ISSUE TYPE = Epic

* WORK SEGMENT (cascading down custom field value) = ABC SECTOR (parent value) & 2020 Work; 2021 Work; 2022 Work (child values)

* DUE DATE = Over 30days 

* No issues must be be linked to the Epic. (No stories or issues must be associated with the this Epic)

I want a create a custom script to checking the below things,

  • Count number of hits from the JQL and send an e-mail to my email ID 4 days before Archiving
  • And the script must limit the number of rows to update to max. 500. It should not archive more than 500

Can I please get some help with this.

Thank you !

 

5 answers

2 accepted

2 votes
Answer accepted

Hi @Aisha M 

Thanks for summoning me :) 

Did you try to filter those issues and archive each of them using Groovy script? I can't help you with the entire code but this might be a good starting point.

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.archiving.ArchivedIssueService

@StandardModule
ArchivedIssueService archivedIssueService

def NOTIFY_USERS = false // if you want to notify users change to true

def user = //.... the user who will archive the issue

def issues = [...] // filter those issues which are less than 500

issues.eachWithIndex {it, index ->
if (index == 500) return // another control for exceeding 500 limit

def validationResult = archivedIssueService.validateArchiveIssue(user, it.getKey(), NOTIFY_USERS);
if (validationResult.isValid()) {
archivedIssueService.archiveIssue(validationResult);
}
}

 

I did not test the code, I just code it here, so please be aware ;)

I hope I clearly understood your request, and this might help you well.

Please let me know if you have questions.

Cheers 

 

EDIT: Here is the latest working code, thanks @Aisha M 

 

import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.archiving.ArchivedIssueService
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter


ArchivedIssueService archivedIssueService = ComponentAccessor.getComponent(ArchivedIssueService)

def NOTIFY_USERS = false // if you want to notify users change to true

def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.getIssueManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def query = jqlQueryParser.parseQuery("""project = ALTY2 AND issuetype = Epic""")
def search = searchService.search(user, query, PagerFilter.getUnlimitedFilter())

int counter = 0
search.results.each { documentIssue ->
if (++counter >= 500) return

def issue = issueManager.getIssueObject(documentIssue.id)
def validationResult = archivedIssueService.validateArchiveIssue(user, issue.getKey(), NOTIFY_USERS);

if (validationResult.isValid()) {
archivedIssueService.archiveIssue(validationResult);
}
}
return null

 

@Tuncay Senturk Thank you for responding :) No I haven’t used a groovy script for archiving issues . . But I am looking for a groovy script job to achieve this. . .

I had initially set this up (almost) using the inbuilt archive functions available within scriptrunner , but it was given the red flag due to the probability of an error happening in production . .   

And to be honest , I’m not good with scripting at all . . So, looking for help here  . .  

Is this script something that is to be used in the script runner job ? Also, is it possible to get the issues through a query in the script ? :) 

I will not be notifying the users , so I ll skip that . . I have an user AUTOMATION that ll perform this. . And the issue must be filtered based on my query . . So that’s okay I guess

But do I need another job if I want an email id to be notified that “this many issues are gonna be archived” in the next 4 days ? 

Tuncay Senturk Community Leader May 24, 2022

Sorry, it wasn't clear. 

The below code might help you even more.

It has the script to fetch those issues, control exceeding 500, and the code archiving the issue.

import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.archiving.ArchivedIssueService
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter

@StandardModule
ArchivedIssueService archivedIssueService

def NOTIFY_USERS = false // if you want to notify users change to true

def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.getIssueManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def query = jqlQueryParser.parseQuery("YOUR JQL HERE to get those issues")
def search = searchService.search(user, query, PagerFilter.getUnlimitedFilter())

int counter = 0
search.results.each { documentIssue ->
if (counter++ > 500) return

def issue = issueManager.getIssueObject(documentIssue.id)

def validationResult = archivedIssueService.validateArchiveIssue(user, it.getKey(), NOTIFY_USERS);
if (validationResult.isValid()) {
archivedIssueService.archiveIssue(validationResult);
}
}


 Please let me know if you are getting error and where!

@Tuncay Senturk  Wow, let me try  . .  :)

Update - I get an error in the line below

@StandardModule

error - unable to resolve class. Unable to find class for annotation

 

 

To put in in simpler terms, I want to do two things,

JOB 1

Run the JQL query

calculate the number results

send a mail to my email about the result count 

 

(after 4 days)

JOB 2

run the JQL query

get the first 500 list

if due >30  then automatically archive 

 

Not sure about how much this makes sense :D

Sorry, I don't have DC instance either, that's why I can't try the code.

I changed the related part as below, pls try that one.

Btw, I only tried to help with the archiving issue part. Not the sending email job.

 

import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.archiving.ArchivedIssueService
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter

ArchivedIssueService archivedIssueService = ComponentAccessor.getComponent(ArchivedIssueService)

def NOTIFY_USERS = false // if you want to notify users change to true

def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.getIssueManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def query = jqlQueryParser.parseQuery("YOUR JQL HERE to get those issues")
def search = searchService.search(user, query, PagerFilter.getUnlimitedFilter())

int counter = 0
search.results.each { documentIssue ->
if (counter++ > 500) return

def issue = issueManager.getIssueObject(documentIssue.id)

def validationResult = archivedIssueService.validateArchiveIssue(user, issue.getKey(), NOTIFY_USERS);
if (validationResult.isValid()) {
archivedIssueService.archiveIssue(validationResult);
}
}

Thanks

Tuncay 

Thank you @Tuncay Senturk :) That’s alright, I ll figure something out for the mail part later . . Will try and test this code to see if the archiving works :) Will update you 

@Tuncay Senturk I tried the script with a simple query . .  If I run gives the below error ;(

groovy.lang.MissingPropertyException: No such property: it for class: Script94 at Script94$_run_closure1.doCall(Script94.groovy:26) at Script94.run(Script94.groovy:21)

The last potion of the code shows some error (sneaked in a pic, lol)

def validationResult = archivedIssueService.validateArchiveIssue(user, it.getKey(), NOTIFY_USERS);
if (validationResult.isValid()) {
archivedIssueService.archiveIssue(validationResult);
Tuncay Senturk Community Leader May 25, 2022

Hi @Aisha M 

It is because of your DC version. In previous versions of DC, there is no third parameter (NOTIFY_USERS) which is the decision to notify users about the archiving.

 

Please change that part as below

def validationResult = archivedIssueService.validateArchiveIssue(user, it.getKey());
Tuncay Senturk Community Leader May 25, 2022

But, please keep in mind that if you upgrade your Jira, you will need to change that again :( 

@Tuncay Senturk The DC got upgraded recently, so there won’t be an upgrade in the near future :)

I modified the script , but it shows the same error as before . . I have added a pic of the full script from the console . . :o

E8DDCA1E-D130-4BDC-ADCB-4A8B6C8D0477.jpeg

Tuncay Senturk Community Leader May 25, 2022

This will be tricky!

After typing below which code assisted methods do you see? You can get the list by clicking Ctrl + Space, I believe.

archivedIssueService.

@Tuncay Senturk I see the below selections for that, (Sorry for the horrible pic quality, as you know we can’t take screenshots or use the snipping tool)

D19BEE3F-3EC3-439E-9A36-50A6C80682A5.jpeg

Tuncay Senturk Community Leader May 25, 2022

Ok, so the first one should definitely work.

As you can see in the screenshot you shared there is a method

validateArchiveIssue(ApplicationUser, String, boolean)

So, please make sure you use :


def validationResult = archivedIssueService.validateArchiveIssue(user, it.getKey(), false);

@Tuncay Senturk I changed, it still shows the same error , not sure why ;( *aargh*

76492E91-FD25-458D-9470-8910417345A4.jpeg2CF605E3-364B-42ED-A3FE-A1E932A4EC3E.jpeg

Tuncay Senturk Community Leader May 25, 2022

Oh! sorry! Thanks for the screenshot.

Please use issue.getKey() instead of it.getKey()

By the way, I recommend testing with a specific issue, providing JQL like key = ABC-2334

@Tuncay Senturk I made the said changes and modified the JQL to one key. Now the script is all green, but getting this error when I run . . 

B253E223-3FA5-4EBE-8E20-B2B187E95F94.jpeg

And when I tried to run the script from the console, get the below result:

DocumentIssueImpl[issueKey=AMT1-12]
And nothing happens, the issue doesn't get archived.
Tuncay Senturk Community Leader May 26, 2022

Hi @Aisha M 

Are there any other log messages? This error message did not ring the bell.

@Tuncay Senturk  When I tried to run the job to test, the stackoverflow error is all I got. But the script looks green.

Someone had mentioned about it here  - https://productsupport.adaptavist.com/browse/SRJIRA-4220  (Not sure what they mean)

And then I tried to run the script in the console and all it says in the result was "documentissueimpl{issue key} . . No logs or errors here too

Overall, the issue doesnt get archived. 

Tuncay Senturk Community Leader May 26, 2022

Ok,

can you simply put the below line at the end of the script?

return null

@Tuncay Senturk  I did actually . .  But the script runs without any error & result says "null" , but the issue did not get archived . . Not sure why

Tuncay Senturk Community Leader May 26, 2022

Now the fun begins...

What I'd suggest is to add some logs so that we can see whether the API calls are done successfully.

def validationResult = archivedIssueService.validateArchiveIssue(user, issue.getKey(), NOTIFY_USERS);
logger.error ("validationResult: $validationResult")
if (validationResult.isValid()) {
logger.error ("Calling archive service for $issue.key")
archivedIssueService.archiveIssue(validationResult);
logger.error ("Called archive service for $issue.key")
}

let's see the results

@Tuncay Senturk  Lol, yer making it sound fun :D

I get the red checks that state variable [logger] is undeclared. 

Tuncay Senturk Community Leader May 26, 2022

Aaah sorry! please use log instead of logger :) 

@Tuncay Senturk  No problem :D

The logs show something like below . . Do you want me to share the entire log ?

B8ECC9A6-4555-48CE-97B9-AAEBC543896D.jpeg

 

UPDATE - I just checked & looks like that particular issue got archived . . lol . .  Not sure how the script decided to work suddenly :D 

Tuncay Senturk Community Leader May 26, 2022

These logs are not the ones I needed actually. Are you sure the issue was archived? So, no need to add any log.

So, please make sure and change the code accordingly, I mean please do not forget to change the JQL to whatever you want to get the appropriate list of issues which will be archived.

if all is sorted, then happy end :) 

@Tuncay Senturk YESSS it worked ! I’ m shocked !! Yayyyy :D

I tried with a different issue key and that got archived as well . . 
But I see a huge log, not sure what it means and if it’s any cause for concern . . Have attached the image for you anyway,

255B9ED0-D783-4DCC-AF0D-8066189D9C80.jpeg

Tuncay Senturk Community Leader May 26, 2022

Those are info level logs, don't worry about them.

I'm glad it worked :) 

@Tuncay Senturk  I can’t contain my happiness, so happy it worked , that too you don’t have a DC to try it out , super amazing !! 

Also, this job won’t work for more than 500 issues at a time right ? :o I will be testing that out though :) 

Like Antoine Berry likes this
Tuncay Senturk Community Leader May 26, 2022

I'm glad it worked.

@Aisha M you can test it with 3 issues, changing 500 to 3 and testing with a JQL which will return 5 issues. You know where to change in code to test, after a successful result  you can change back to 500.

Like # people like this

@Tuncay Senturk  Thank you, I was on the way to test with 500 issues :D

I tried with 3 out of 11 issues . .  It archived 4 . .  Not sure why it picked +1, but thats okay I guess :)

Like Tuncay Senturk likes this
Tuncay Senturk Community Leader Jun 01, 2022

Oh, I see, my bad!

If you change this line

if (counter++ > 500)

to this

if (++counter > 500)

it will not pick +1 :) 

Like # people like this

@Tuncay Senturk  I have an issue :(

When I provide the query in the script, its says unexpected token for some of the query snippets (though the JQL works fine in the search issue)

project = KTTL9 AND issuetype = Epic AND "Work Segment" = "ABC Sector" AND duedate <= -30d AND issueFunction not in hasLinkType("Epic-Story Link") AND issueLinkType not in ("Child of Custom Link")

The highlighted ones are being said as unexpected token, basically, its not picking the query inside the brackets or having an apostrophe

Tuncay Senturk Community Leader Jun 03, 2022

Hi @Aisha M 

It must have been the quotes. Put your jql within three quotes, like this 

parseQuery(""" query """ )

Like Antoine Berry likes this

@Tuncay Senturk Thank you for getting back :) I had tried with single quote (‘) and it seemed to work . . :o 

Can I let that be, or three quotes is the way to go . . ?

Like Antoine Berry likes this
Tuncay Senturk Community Leader Jun 03, 2022

Both are OK.

But please bear in mind that if you use single quote, you can't use single wuote within the jql. It's same for the double quotes. But if you use three double quotes, you are free to use any :)

Like Antoine Berry likes this
Tuncay Senturk Community Leader Jun 13, 2022

Hi @Aisha M 

If possible, can you please attach the latest code here, and I will add that to the accepted answer. This way, any community member will benefit directly without needing to read every comments :) 

Thanks

Like # people like this

@Tuncay Senturk  Absolutely ! Yes ! That’s d make more sense :) I’m sorry, have been sick the past, so couldn’t respond promptly :o

Like Antoine Berry likes this

FINAL SCRIPT :

import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.archiving.ArchivedIssueService
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter

ArchivedIssueService archivedIssueService = ComponentAccessor.getComponent(ArchivedIssueService)

def NOTIFY_USERS = false

def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.getIssueManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def query = jqlQueryParser.parseQuery("""project = ALTY2 AND issuetype = Epic""")
def search = searchService.search(user, query, PagerFilter.getUnlimitedFilter())

int counter = 0
search.results.each { documentIssue ->
if (++counter > 3) return

def issue = issueManager.getIssueObject(documentIssue.id)
def validationResult = archivedIssueService.validateArchiveIssue(user, issue.getKey(), NOTIFY_USERS);
log.error ("validationResult: $validationResult")
if (validationResult.isValid()) {
log.error ("Calling archive service for $issue.key")
archivedIssueService.archiveIssue(validationResult);
log.error ("Called archive service for $issue.key")
}
}
return null

Like Tuncay Senturk likes this
Tuncay Senturk Community Leader Jun 14, 2022

Thanks for the final script @Aisha M 

I hope you feel better now. 

Like Antoine Berry likes this
1 vote
Answer accepted
Antoine Berry Community Leader May 23, 2022

Hello @Aisha M ,

What do you understand by "archiving" the issues ? Do you mean to move them to another project ?

@Antoine Berry  We archive issues just we archive projects right  . .  It takes it out of the jira index. Then you can restore it went needed. 

https://confluence.atlassian.com/adminjiraserver/archiving-an-issue-968669980.html 

Like Antoine Berry likes this
Antoine Berry Community Leader May 23, 2022

Hi @Aisha M unfortunately this feature is available for DataCenter only (as you can see in this KB). I am afraid you are running server ?

Looks like we have Data center . . . Just verified now 

I am able to archive issue in my instance using the scriptrunner job, but I want to set that up using a script instead of the inbuild job  . .  But we had a bad incident of when every issue in the instance got archived & it became a huge mess . .  so don't want to use that 

Or you think if there is any way of archiving those epics with the conditions using a script ?

Antoine Berry Community Leader May 23, 2022

Well I use Server and there is no "Archive" feature that I know of. Could you share a screenshot of the scriptrunner job ?

Though I cannot screenshot anything from my system , below is  exactly what I did initially, in the JQL field I used my query (mentioned in the question)

https://scriptrunner.adaptavist.com/latest/jira/issue-archiving-job.html 

This is exactly what I had used earlier . . . But they want regular job script with the conditions . .  so completely clueless

Like Antoine Berry likes this
Antoine Berry Community Leader May 23, 2022

Well... again this feature is available on Data Center only ! 

" The Issue Archiving Job is only available on Data Center 8.1 and above. "

You might be on Data Center without knowing ?

I am on Server still and I am positive this feature does not exist. :)

Yes, I'm on Data center . . Checked now. *ahhh* 

Oh noooo, I'm doomed . . now I'm super clueless on how to create this script  ;( 

Antoine Berry Community Leader May 23, 2022

My advice would be to still use the scriptrunner archiving job as it exactly fits your need.

But you could create a subscription to this filter : 

project = "My Test 1" 
AND issuetype = Epic
AND ("WORK SEGMENT" in cascadeOption("ABC SECTOR","2019 Work") OR "Initiative Type" in cascadeOption("ABC SECTOR","2020 Work") OR "Initiative Type" in cascadeOption("ABC SECTOR","2021 Work"))
AND duedate <= -26d
AND issueFunction not in hasLinkType("Epic-Story Link")
AND issueLinkType not in ("Child of Custom Link")

I changed the due date condition so you get notified 4 days prior.

If there is a problem it seems that you can restore archived issues.

If you run the job daily you should not have more than 500 issues.

Also, I would strongly advise to use "resolutiondate" instead of "duedate". This will ensure the issue is resolved and can be archived whereas an issue might still be open eventhough the due date is passed.

@Antoine Berry 

Actually there was an incident where accidently this job archived every issue in the dev environment. And it became a huge escalation. . So, my management doesn't want to use this inbuilt job ever again . .  LOL :D . .  coz even a minor error could archive the wrong things in prod . . . Though we have bulk archive options in Jira, we hav to manually restore individual issues if wrong things get archived . . So, thats why I'm looking for a script . . 

Antoine, can this job be written as a script instead ? Also add another job to send me a mail to me with the number of issues that is going to be archived for that query. And the original archive job can be run in the next 4 days for >=500 issues.

Antoine Berry Community Leader May 23, 2022

I do not know if this can be written as a script. Probably, but there is no resource online yet. And I cannot test it since I do not have a DataCenter instance.

But what I can tell you is that the scriptrunner job was probably not at fault, but rather your configuration. 

I would suggest you test it on dev with my proposal to have a subscription for your filter which will notify you 4 days before. 

@Antoine Berry  Yes understandable . . Also, is it possible to limit the job to run for not more than 500 at a time ?

Also, like you mentioned regarding the due date, how to set up the filter to calculate the due date to 30 days from a different date ? We have a PI date . .  So I want to filter the epics that are 30 days older from that PI date 

Example:

PL date is 05/23/2022 means, epics with due dates 30 days older from the end of the PI date 

 

Antoine Berry Community Leader May 23, 2022

Looking at the documentation, it is not possible to limit the job at 500 issues. That is why I suggest a subscription which will send you the issues that will be archived by the job 4 days prior.

Also, it is not possible to substract dates in a JQL query. A workaround would be to create a scripted field that would calculate the difference between the dates.

@Antoine Berry  I just spoke to my manager about the possibility of going with the archive job, they don't want the in-built job at all . . . We have many jobs in the instance using scripts, so they want to use scripts . . 

I'm assuming they wan the script to be something like,

JOB 1

Run query

calculate the results

send a mail to a DL about the result count 

(after 4 days)

JOB 2

run query

get the first 500 list

if due >30 - then archive 

 

I'm like so confused at this point, coz I suck at scripting . . :D 

Antoine Berry Community Leader May 23, 2022

Well I cannot really help with the scripting because I do not have DataCenter, so I could not try it at all.

My best advice would be for you to contact Adaptavist, and ask either to provide a script for archiving or a proof that the archive feature is working well.

I am almost certain that this feature should work without issue, and that the solution I proposed would fit the need. But Adaptavist will know better so I suggest you open a ticket on their support portal. :)

Thank you so much @Antoine Berry . .  I will try seeking help from them . . Thank you for patiently to listening to my queries and providing the best possible answers :)

Like Antoine Berry likes this

@Antoine Berry I have accepted your answer too, since it’s makes sense from the inbuilt job perspective:) Thank you so much for always helping out !!! >_<

Like Antoine Berry likes this

@Tuncay Senturk One quick question pleaseeeee 

is it possible to use cron expression on different unique dates, example

6 July 2022

16 Oct 2022

23 Dec 2022

 

i tried, 

0 0 5 6,16 JUL,OCT ? 2022 but realised this might run on 6,16 of JULY & 6,16 of Oct . . Or is it right 

Tuncay Senturk Community Leader Jun 17, 2022

No, I'm afraid you can't do that. You can define recurrent dates but not multiple particular dates.

You can use this website to generate cron expressions. 

https://www.freeformatter.com/cron-expression-generator-quartz.html

@Tuncay Senturk Yea, that’s what I thought so too. . Actually, I had been using that site for it . . So wanted to know if it’s possible 

Calling another script expert @Tuncay Senturk to please pitch in & share some thoughts :):) At this point, I'm super desperate for any help ;(

@Antoine Berry  Hello !!! Hope you are well :):)

I'm so sorry for bothering, but can you please me with this question . .  I have run my wits end to figure out script . . . I know you the only one who is an  expert on scripts :):)

Suggest an answer

Log in or Sign up to answer
TAGS

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you