Scriptrunner listener returning a log for every field in an issue

Pierre Ibrahim November 7, 2024

Currently working on a listener that listens to issue updated events, when an issue updated event happens, it looks through a list of fields to see if they changed, if they have, it then updates a counter by 1.

What I noticed is whenever I make even one change to an issue, I get at least 15 logs. I was able to see the fields changing, and it's looking like a log per field on the issue, even though none of the fields are actually changed. Is this proper behavior? Is there any way to get the listener to run as many times as there are actual issue updated events?

This is one of the logs I get. I get 15 each time I update one field, difference between each log and the next is the field changing, but they all have fromString null and toString null.

scriptrunner listener.png

Some additional information that I've been able to get out of this:

The issue I'm running this script on has three subtasks. the script seems to be running 10 times per issue in the issue hierarchy, so 10 times for each subtask, please twice for the issue where the actual change happens. Not sure why this is happening, or why 10 specifically.

For reference, this is my script:

 

 

import com.atlassian.jira.issue.Issue
import com.atlassian.jira.component.ComponentAccessor

// Initial issue setup and dependencies
def issueToCompare = event.issue as Issue
def issueService = ComponentAccessor.getIssueService()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def listenFields = ['List of fields']

def acceptedIssueTypes = ["List of issue types"]

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cField = customFieldManager.getCustomFieldObjectsByName("Field Updated Counter")[0]
def counter = issueToCompare.getCustomFieldValue(cField) as Double
def counterIncrement = ((counter != null ? counter : 0) + 1).doubleValue()

// Check if issue type is accepted and there are change histories
if(issueToCompare.getIssueType().name in (acceptedIssueTypes)){
    def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
    def changeHistories = changeHistoryManager.getChangeHistories(event.issue)
    log.warn(changeHistories.last().getChangeItemBeans().find {it.field})
    if (changeHistories) {
        def changeUser = changeHistories.last().getAuthorDisplayName()
        if (changeUser != 'CP - Admin'){
            log.warn(changeUser)
            def changeItem = changeHistories.last().getChangeItemBeans().find {
                // Check if Source field value changed
                (it.field in listenFields) && it.fromString != it.toString
            }
            log.warn(changeItem)
            if (changeItem) {
                // Use IssueService to update the issue and trigger an issue updated event
                def issueInputParameters = issueService.newIssueInputParameters()
                if (issueInputParameters) {
                    issueInputParameters.with {
                        setSkipScreenCheck(true)
                        setRetainExistingValuesWhenParameterNotProvided(true)
                        addCustomFieldValue(cField.idAsLong, counterIncrement.toString())
                    }

                    def validationResult = issueService.validateUpdate(user, issueToCompare.id, issueInputParameters)

                    if (validationResult.isValid()) {
                        issueService.update(user, validationResult)
                    } else {
                        log.warn("Issue update validation failed: ${validationResult.errorCollection}")
                    }
                } else {
                    log.error("IssueInputParameters could not be instantiated. Cannot proceed with issue update.")
                }
            }
        }
    }
}

1 answer

1 accepted

1 vote
Answer accepted
Bobby Bailey
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.
November 7, 2024

Hi @Pierre Ibrahim , 

 

I am not 100% sure this is the problem, but I am a little weary that you are triggering this script on an Issue Updated event, and the script it self then fires the event: 


// Use IssueService to update the issue and trigger an issue updated event

 

You could also simplify this area of code using HAPI, so this: 

 

if (changeItem) {
    // Use IssueService to update the issue and trigger an issue updated event
    def issueInputParameters = issueService.newIssueInputParameters()

    if (issueInputParameters) {
        issueInputParameters.with {
            setSkipScreenCheck(true)
            setRetainExistingValuesWhenParameterNotProvided(true)
            addCustomFieldValue(cField.idAsLong, counterIncrement.toString())
        }

        def validationResult = issueService.validateUpdate(user, issueToCompare.id, issueInputParameters)

        if (validationResult.isValid()) {
            issueService.update(user, validationResult)
        } else {
            log.warn("Issue update validation failed: ${validationResult.errorCollection}")
        }
    } else {
        log.error("IssueInputParameters could not be instantiated. Cannot proceed with issue update.")
    }
}

 

Can become this: 

if(changeItem){
    issueToCompare.update {
        setCustomFieldValue(cField.idAsLong, counterIncrement.toString())
        setEventDispatchOption(EventDispatchOption.DO_NOT_DISPATCH)
        setSendEmail(false)
     }
}

 

Could you try this and let me know if this helps?

Kind regards, 

Bobby

 

Pierre Ibrahim November 7, 2024

That worked amazingly well! Would you mind explaining a bit what the setEventDispatchOption does?

Also to address your concern, I'm using the resulting field from the listener (the counter getting updated) to use in an automation. In one of my earlier iterations in the script, it wasn't auto firing issue updated events that the automation would need to trigger. So I'm very curious how it works now!

Thanks again so much!

Quick edit: the setEventDispatchOption as DO_NOT_DISPATCH looks to (as the name implies) not fire an issue updated event. if I comment that out, I get the many logs again but my automation is able to trigger.

Bobby Bailey
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.
November 7, 2024

That is exactly correct. Previously, the code you were using was calling the original APIs to update an issue. To change that you would have changed this:

issueService.update(user, validationResult)

To this:

issueService.update(user, update, EventDispatchOption.DO_NOT_DISPATCH, false)

Where the last "false" tells it to not send emails either. You can read more about these functions in the official docs here

 

The reduction in code comes from HAPI, which is a feature of ScriptRunner designed to act as a buffer between you and the complexities for the JAVA API. Which is why the new code looks much simpler :-) 

 

In regards to what it was doing previously, that would depend on what your automation was doing. If that was then going to perform an action where down the road the issue was being updated somehow, then it was creating one big loop. To understand exactly how we would need to follow the trail of the automations.

 

I am glad this was able to solve your problem!

Like Pierre Ibrahim likes this
Pierre Ibrahim November 8, 2024

Hi Bobby!

Thanks again for the explanation, greatly appreciated!

While this didn't directly solve the issue and I'll explain why, you made me realize what my actual problem is :)

So the listener is supposed to listen for multiple fields to change, once it finds that, it increments a counter, that counter is then used in a JIRA automation to trigger a transition on the ticket that had the original field changed.

Reason I'm going this roundabout way was to try to get around the service limit of JIRA Automation on Data Center without directly upping the threshold limit. The automation I have in place listened to 65 different custom fields, and the problem with JIRA automation rules is that I can't specify the issue type first THEN listen to fields changing, which resulted in thousands of runs as these fields are not limited in scope to the issue types I'm targeting. Those runs would eventually cause the automation rule to throttle after reaching 3600 seconds of runtime within a 12 hour cycle.

With all that said, the listener (with your help!) alleviates that load off of the automation rule, but the additional updates now make sense to me.

The listener fires on issue updated events, if the issue is one of four issue types, it'll then check all 65 fields for changes, if it finds a change and it wasn't made by a service account, it'll then increment the counter (and fire another issue updated event).

At this point the automation rule would take over as it listening to the counter changing on issue updated events, it would then trigger a transition on the triggering issue.

The transition would then use a post function to copy all 65 fields across the issues subtasks, or siblings and parent tasks.

That update then re-triggers the listener, which checks for issue type, passes, then checks for service account and exits the listener without incrementing the counter again.

That is what causes the many logs I'm seeing. If I make the counter set not fire an event, then only one log generates, but the automation does not trigger because it's waiting for an issue updated event.

With all that said, I marked your answer as the accepted solution because it most definitely is, and my use case is just not a very realistic one :)

Suggest an answer

Log in or Sign up to answer