ScripRunner Issue get dueDate of linked Epic

kaeser_m May 19, 2021

Hi

I am struggling with me first script.

What I want to achieve is that all issues return by a JQL will update they dueDate if there was an update on the linked epics dueDate.

For this I was reading script runner documentation and many similar questions in this community, but I did not find a matching solution.

Here is me Code but I get a error on the variable “issue”. “The variable issue is undeclared”

Can someone help me?

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.search.SearchQuery
import com.atlassian.jira.issue.Issue
import com.opensymphony.workflow.InvalidInputException

// The issues returned from that JQL will get altered
final searchQuery = "project = MN0001 and issuetype = Aufgabe"

// Get some components
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issueService = ComponentAccessor.issueService

//Addet Code
def issue = issue as Issue
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def epicLinkCf = customFieldManager.getCustomFieldObjectsByName("Epic Link")
def epicLink = issue.getCustomFieldValue(epicLinkCf) as Issue
def epicDueDate = epicLink.dueDate?.dateString

// Perform the search
def query = jqlQueryParser.parseQuery(searchQuery)
def searchResults = searchProvider.search(SearchQuery.create(query, loggedInUser), PagerFilter.unlimitedFilter)

// Iterate all the results to update each issue
searchResults.results.each { documentIssue ->
// Define the new params (the dueDate of linked Epic)
def issueInputParameters = issueService.newIssueInputParameters()
issueInputParameters.setDueDate(epicDueDate)

// Update the issue
def issueId = documentIssue.document.fields.find { it.name() == "issue_id" }.stringValue().toLong()
def updateValidationResult = issueService.validateUpdate(loggedInUser, issueId, issueInputParameters)
assert updateValidationResult.isValid() : updateValidationResult.errorCollection

// Validate the update
def issueResult = issueService.update(loggedInUser, updateValidationResult)
assert issueResult.isValid() : issueResult.errorCollection
}

2 answers

1 accepted

0 votes
Answer accepted
Erik Buchholz
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 19, 2021

Hi @kaeser_m ,

this is quite enthusiastic for your first script.

Where do you want to use the script? In a listener for updates of the epic?

If you try to use the script at the wrong place like the script console the undefined issue error is clear. The way you use the variable issue it may be undefined in this places.

To develop your script it's handy to use the console and log to test your script. You can start with the following script. I used log.warn because this log level is enabled by default.

import com.atlassian.jira.issue.Issue
import com.onresolve.scriptrunner.runner.customisers.ContextBaseScript
import groovy.transform.BaseScript

@BaseScript ContextBaseScript contextBaseScript
// Get default issue in script console
final Issue issue = getIssueOrDefault('MN0001-1')
log.warn("Resolved issue '${issue}'. We can go to next step.")

Try to break down the task.

  1. Get the epic (e.g. as issue variable from listener)
  2. Get the issues in this epic. (You will need to adjust your JQL for that or use the epic link e.g. with IssueLinkManager like in https://community.atlassian.com/t5/Jira-discussions/Get-Issues-In-Epic-in-Scripted-Field/td-p/862123)
  3. Update the child issues. (This part looks promissing so far)

Regards
Erik

kaeser_m May 19, 2021

Hi Erik

The higher the goal, the harder the climb, but after that the bigger the muscle the, smarter the mind. ;)


Yes, I use it as a listener on "Issue Updated" to catch all updates that are done to issues. i think all epics are issues as well. Since the script was fired (with error) i think i have the correct event,

Thanks for the hint with the IssueLinkManger and the Console I will try this.

Regards

Michael

Like Erik Buchholz likes this
Erik Buchholz
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 19, 2021

Hi Michael,

I haven't worked so much with listeners so far.

If issue is undefined in case of the issue Updated event, you can try to use

event.getIssue() 

to resolve the issue from the event.

Take a look at the documentation for the API: https://docs.atlassian.com/software/jira/docs/api/8.16.0/com/atlassian/jira/event/issue/IssueEvent.html

And check the bindings of scriptrunner at the ? icon below the scripting window.

Grüße
Erik

kaeser_m May 26, 2021

Hi erik

still, I’m struggling with this script.

I managed to get the script to work only if the update was done on an epic. this I’m doing with a simple check that the eventissuetype name is equal to "Epic" before updating the issues given back from jql.

now I’m struggling with the updating process. for some reason, the assert seams to file all the time.

after me understanding the assert updateValidationResult.isValid() should return "true". but I do not know why this does not behave like I expect.

do you have an idea?

 

Here is me current Code:

// All issues return by a JQL will automatically update they dueDate if there was an update on the linked epics dueDate

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.search.SearchQuery
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.issuetype.IssueType
import com.onresolve.scriptrunner.runner.customisers.ContextBaseScript
import groovy.transform.BaseScript

// The issues returned from that JQL will get altered
final searchQuery = "project = MN0001 and issuetype = Aufgabe"
log.warn("searchQuery'${searchQuery}'")

// Get some components
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
log.warn("loggedInUser'${loggedInUser}'")
def issueService = ComponentAccessor.issueService

//get issueTypeName and DueDate of updated issue
def IssueTypeName = event.issue.getIssueType().name
log.warn("IssueTypeName'${IssueTypeName}'")
def DueDate = event.issue.getDueDate().toString()
log.warn("DueDate'${DueDate}'")

// Perform the search
def query = jqlQueryParser.parseQuery(searchQuery)
log.warn("query'${query}'")
def searchResults = searchProvider.search(SearchQuery.create(query, loggedInUser), PagerFilter.unlimitedFilter)

// If the updated issueType is a epic..
if (IssueTypeName == "Epic") {
// Iterate all the results to update each issue
searchResults.results.each { documentIssue ->
// Define the new params
def issueInputParameters = issueService.newIssueInputParameters()
issueInputParameters.setDueDate(DueDate)
log.warn("InputParamter'${issueInputParameters}'")

// Update the issue
def issueId = documentIssue.document.fields.find { it.name() == "issue_id" }.stringValue().toLong()
log.warn("issueId'${issueId}'")
def updateValidationResult = issueService.validateUpdate(loggedInUser, issueId, issueInputParameters)
log.warn("updateValidationResult'${updateValidationResult}'")
assert updateValidationResult.isValid() : updateValidationResult.errorCollection

//Validate the update
def issueResult = issueService.update(loggedInUser, updateValidationResult)
assert issueResult.isValid() : issueResult.errorCollection
}
}
Erik Buchholz
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 26, 2021

Hi @kaeser_m ,

I changed your script a little. The most important part is, that date fields need a special input format. I used DateFieldFormat for that.

Additional I changed your JQL. Please check if it finds the right issues.

Grüße
Erik

package com.acme.listener

/**
* This script updates the due date of all issues in an epic if the epic is updated.
* Use this script in an listener with all or any issue events.
*
* @author kaeser_m with help in https://community.atlassian.com/t5/dummy/dummy/qaq-p/1696961
*/
import org.apache.log4j.Logger
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.search.SearchQuery
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.issuetype.IssueType
import com.onresolve.scriptrunner.runner.customisers.ContextBaseScript
import groovy.transform.BaseScript

import com.atlassian.jira.util.DateFieldFormat

/*
* Initialize my logger.
* You need to set the log level for your packages like 'com.acme'. I recommend debug in development and warn in productive systems.
* For more information see https://docs.adaptavist.com/sfj/set-logging-to-help-debug-your-scripts-11993359.html
* I used getClass() to use the package name here.
*/
Logger mLog = Logger.getLogger(getClass())

//get issue, issueTypeName and DueDate of updated issue
final Issue myIssue = event.issue
/*
* BTW: Don't start variables with uppercase. Normally only classes start with uppercase.
*/
def issueTypeName = myIssue.getIssueType().name
log.warn("IssueTypeName '${issueTypeName}'")
/*
* I would move the if with negated condition here.
*
* if (issueTypeName != "Epic") {
* mLog.debug("Issue '${myIssue}' is no epic.")
* return
* }
*/

final Date DueDate = myIssue.getDueDate()
log.warn("DueDate '${DueDate}'")

// The issues returned from that JQL will get altered
/*
* I altered your JQL as I think this is what you want to get.
* You may have to alter the name of the epic link custom field or use the customfield id to match your system.
*/
final searchQuery = "project = MN0001 and Epos-Verknüpfung = ${myIssue.key}"
log.warn("searchQuery '${searchQuery}'")

// Get some components
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
log.warn("loggedInUser '${loggedInUser}'")
def issueService = ComponentAccessor.issueService

// Perform the search
def query = jqlQueryParser.parseQuery(searchQuery)
log.warn("query '${query}'")
def searchResults = searchProvider.search(SearchQuery.create(query, loggedInUser), PagerFilter.unlimitedFilter)

final DateFieldFormat dateFieldFormat = ComponentAccessor.getComponent(DateFieldFormat)

// If the updated issueType is a epic..
if (issueTypeName == "Epos") {
log.warn("Issue '${myIssue}' is an epic.")
// Iterate all the results to update each issue
searchResults.results.each { documentIssue ->
log.warn("Start to update issue '${documentIssue}'.")
// Define the new params
def issueInputParameters = issueService.newIssueInputParameters()
/*
* The input parameters needs a special string format for dates. Since this could change from one system to another,
* I used DateFieldFormat to format the Date right.
*/
issueInputParameters.setDueDate(dateFieldFormat.formatDatePicker(DueDate))
log.warn("InputParamter '${issueInputParameters}'")

// Update the issue
def issueId = documentIssue.document.fields.find { it.name() == "issue_id" }.stringValue().toLong()
log.warn("issueId '${issueId}'")
def updateValidationResult = issueService.validateUpdate(loggedInUser, issueId, issueInputParameters)
log.warn("updateValidationResult'${updateValidationResult}'")
assert updateValidationResult.isValid() : updateValidationResult.errorCollection

//Validate the update
def issueResult = issueService.update(loggedInUser, updateValidationResult)
assert issueResult.isValid() : issueResult.errorCollection
}
}
Like kaeser_m likes this
kaeser_m May 26, 2021

Erik, you are THE MAN! :)

It is doing what I want it to do.
If I ever meet you, I will buy you a beer! or two ;)

I had to change the JQL, because it did not match our JIRA-configurations.
Unfortunately, the previous JIRA-Admin dint know about translation functions, so we do have a wild mix between German and English naming on our index language. E.g. the index name of issuetype "Task" is "Aufgabe" but index name of "Epic" is "Epic".

0 votes
kaeser_m May 28, 2021

Final Code in me Script:

package com.acme.listener

/**
* This script updates the due date of all JQL-matching issues in an epic if this epic due date is updated.
* Use this script in an listener with all or any issue events.
*
* @author michael kaeser with help in https://community.atlassian.com/t5/dummy/dummy/qaq-p/1696961
*/
import org.apache.log4j.Logger
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.search.SearchQuery
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.issuetype.IssueType
import com.onresolve.scriptrunner.runner.customisers.ContextBaseScript
import groovy.transform.BaseScript
import com.atlassian.jira.util.DateFieldFormat

//Initialize my logger.
Logger mLog = Logger.getLogger(getClass())

// Get some components
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
log.warn("loggedInUser '${loggedInUser}'")
def issueService = ComponentAccessor.issueService
final DateFieldFormat dateFieldFormat = ComponentAccessor.getComponent(DateFieldFormat)

//get issueTypeName and DueDate of updated issue
final Issue myIssue = event.issue
def issueTypeName = myIssue.getIssueType().name
log.warn("IssueTypeName '${issueTypeName}'")
final Date DueDate = myIssue.getDueDate()
log.warn("DueDate '${DueDate}'")

// If updated issueType is an epic and match the JQL, update the DueDate of all issues linked to this epic
if (issueTypeName == "Epic") {
log.warn("Issue '${myIssue}' is an epic.")

// The issues returned from that JQL will get altered
final searchQuery = "category = \"2 - Kundenmaschinen-Projekte\" and Epic-Verknüpfung = ${myIssue.key}"
log.warn("searchQuery '${searchQuery}'")

// Perform the search
def query = jqlQueryParser.parseQuery(searchQuery)
log.warn("query '${query}'")
def searchResults = searchProvider.search(SearchQuery.create(query, loggedInUser), PagerFilter.unlimitedFilter)

// Iterate all the results to update each issue
searchResults.results.each { documentIssue ->
log.warn("Start to update issue '${documentIssue}'.")

// Define the new params
def issueInputParameters = issueService.newIssueInputParameters()
issueInputParameters.setDueDate(dateFieldFormat.formatDatePicker(DueDate))
log.warn("InputParamter '${issueInputParameters}'")

// Update the issue
def issueId = documentIssue.document.fields.find { it.name() == "issue_id" }.stringValue().toLong()
log.warn("issueId '${issueId}'")
def updateValidationResult = issueService.validateUpdate(loggedInUser, issueId, issueInputParameters)
log.warn("updateValidationResult'${updateValidationResult}'")
assert updateValidationResult.isValid() : updateValidationResult.errorCollection

//Validate the update
def issueResult = issueService.update(loggedInUser, updateValidationResult)
assert issueResult.isValid() : issueResult.errorCollection
}
} else {
//if updatet issue is no epic, send crate log.
mLog.debug("Issue '${myIssue}' is no epic.")
return
}

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events