Possible race condition with script listener. Any advice

Bryan Karsh
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.
July 23, 2016

Hi guys,

I have a script listener that will create and link a ticket if a ticket priority equals 'P1', or is modified to 'P1' from another priority. It seems to work flawlessly most of the time. It's designed to not create/link a ticket if a link already exists. Anyway, I got a complaint that my listener created 2 tickets and linked them to the source ticket – which is not desired. When I look at the history of the source ticket, I see that one user changes the source ticket from p2 to p1 (which triggers the listener). At the same time I see another user add an attachment to the ticket. I'm assuming this is happening before the check is made for an existing link. Below is my script. Any tips on how to prevent this from happening? By the way – I don't want to lock the script from running if it is already running – it is conceivable that this could be modifying more than one ticket at once. If a lock is needed, I need a way to lock changes from an existing ticket that is already being modified.

 

Tips much appreciated.

 

import com.atlassian.jira.event.issue.AbstractIssueEventListener
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.customfields.option.Option


public class LINKAssignmentListener extends AbstractIssueEventListener {

    @Override
    void workflowEvent(IssueEvent event) {
        // only one central way ...
        this.customEvent(event)

    }


    @Override
    void customEvent(IssueEvent event) {

        // Setting up the various Managers
        def issueManager = ComponentAccessor.getIssueManager()       //for testing in console
        def projectManager = ComponentAccessor.getProjectManager()
        def customFieldManager = ComponentAccessor.getCustomFieldManager()
        def optionsManager = ComponentAccessor.getOptionsManager()
        def issueLinkManager = ComponentAccessor.getIssueLinkManager()

        def results = ""
//def myIssue = 'FOOBAR-28234'      //for testing in console

        def myNewIssue = ''

//MutableIssue issue = issueManager.getIssueObject(myIssue)        //for testing in console


        def issue = event.getIssue()

        def priority = issue.getPriorityObject().getName()

// first -- we only run if ticket is a p1

//if (priority == 'P1')                                              //for testing in console
        List changeItems = []
        try {
            changeItems = event.getChangeLog().getRelated("ChildChangeItem")
        }

        catch (e) {

            results += "Skipping changeItems -- looks like issue created via workflow."

        }


        if ((priority == 'P1') ||
                (priority == 'P1' && changeItems.any {
                    it.get('field') == 'priority'
                })) {

            // Next -- we check if a link to a LINK ticket already exists, if one does, we stop script.


            String linkResult = '';
            Collection<IssueLink> olinks = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId());
            Collection<IssueLink> ilinks = ComponentAccessor.getIssueLinkManager().getInwardLinks(issue.getId());
            Collection<IssueLink> links = olinks + ilinks;

            for (IssueLink il : links) {
                linkResult += il.getSourceObject().getProjectObject().getKey()
                linkResult += il.getDestinationObject().getProjectObject().getKey()
            }
            if (linkResult.contains("LINK")) {

                results += "Link already exists. I shall stop now. "          //for testing in console
            } else {

                // Let's create a LINK ticket
                // .... but first we need to copy existing custom field values from the FOOBAR ticket

                // Classification field.
                CustomField classification = customFieldManager.getCustomFieldObjects(issue).find { it.name == "Classification" }

                def region = issue.getCustomFieldValue(classification)[0].toString()
                def customer = issue.getCustomFieldValue(classification)[1].toString()
                def country = issue.getCustomFieldValue(classification)[2].toString()

                //In order to set the above Classification field values in the target LINK ticket, I need to get the Option Ids that correspond to them.


                Long regionId = null
                Long customerId = null
                Long countryId = null

                def cfConfig = classification.getRelevantConfig(issue)

                def classificationOptions = optionsManager.getOptions(cfConfig)


                for (Option regionOption : classificationOptions) {
                    if (regionOption.getValue().equals(region)) {
                        regionId = regionOption.getOptionId()
                        def customers = regionOption.getChildOptions()
                        for (Option customerOption : customers) {
                            if (customerOption.getValue().equals(customer)) {
                                customerId = customerOption.getOptionId()
                                def countries = customerOption.getChildOptions()
                                for (Option countryOption : countries) {
                                    if (countryOption.getValue().equals(country)) {
                                        countryId = countryOption.getOptionId()
                                    }
                                }
                            }
                        }
                    }
                }

                // We have the needed data from the original FOOBAR ticket. Now we can create the LINK ticket:


                def projectName = "Linked Ticket Example" //Target project name

                def project = projectManager.getProjectObjByName(projectName)

                //  String currentUser = event.getUser().getName();
                User currentUserObj = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

                // This is used for creating the new LINK ticket
                def issueService = ComponentAccessor.getIssueService()

                def issueInputParameters = issueService.newIssueInputParameters();

// Setting System Fields
                issueInputParameters.setProjectId(project.id)
                issueInputParameters.setIssueTypeId('34')  // 'Problem' issue type in LINK
                issueInputParameters.setSummary(issue.summary)
                issueInputParameters.setAssigneeId('triage')
                issueInputParameters.setReporterId(issue.getReporterId())
                issueInputParameters.setDescription(issue.description)

// Setting Custom Fields

                // First we'll do the  classification field values, since we have that already defined from above.

                issueInputParameters.addCustomFieldValue(classification.getId() + ":0", regionId.toString())
                issueInputParameters.addCustomFieldValue(classification.getId() + ":1", customerId.toString())
                issueInputParameters.addCustomFieldValue(classification.getId() + ":2", countryId.toString())


                def customField = customFieldManager.getCustomFieldObject("customfield_11135") // Support Manager
                issueInputParameters.addCustomFieldValue(customField.getId(), 'support-guy')    // 'support-guy' option for Support Manager


                customField = customFieldManager.getCustomFieldObject("customfield_10891") // Action Required From
                issueInputParameters.addCustomFieldValue(customField.getId(), '13118')    // 'Foo Bar' option for Action Required From

                customField = customFieldManager.getCustomFieldObject("customfield_11124") // Director
                issueInputParameters.addCustomFieldValue(customField.getId(), '12902')    // 'Director Guy' option for Director

                customField = customFieldManager.getCustomFieldObject("customfield_13190") // LL Needed?
                issueInputParameters.addCustomFieldValue(customField.getId(), '16741')    // 'No' option for LL Needed?

                customField = customFieldManager.getCustomFieldObject("customfield_11812") // Is there a high probability this will happen again?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13920')   // 'Yes' option for  Is there a high probability this will happen again?

                customField = customFieldManager.getCustomFieldObject("customfield_11815") // Does the Customer perceive this as urgent?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13926')   // 'Yes' option for  Does the Customer perceive this as urgent?

                customField = customFieldManager.getCustomFieldObject("customfield_11816") // Does it create an outage?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13928')   // 'Yes' option for  Does it create an outage?

                customField = customFieldManager.getCustomFieldObject("customfield_11820") // Revenue Loss?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13936')   // 'Yes' option for  Revenue Loss?

                customField = customFieldManager.getCustomFieldObject("customfield_11813") // Are there any timeline commitments / dependencies?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13923')   // 'No' option for  Are there any timeline commitments / dependencies?

                customField = customFieldManager.getCustomFieldObject("customfield_11814") // Are there any SLA dependencies?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13925')   // 'No' option for  Are there any SLA dependencies?

                customField = customFieldManager.getCustomFieldObject("customfield_11819") // Does this problem create an additional ongoing workload?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13935')   // 'No' option for  Does this problem create an additional ongoing workload?

                customField = customFieldManager.getCustomFieldObject("customfield_11817") // Are either ALL users of one customer affected or ALL customers affected?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13931')   // 'No' option for  Are either ALL users of one customer affected or ALL customers affected?

                customField = customFieldManager.getCustomFieldObject("customfield_11818") // Would there be penalties?
                issueInputParameters.addCustomFieldValue(customField.getId(), '13932')   // 'Yes' option for  Would there be penalties?

                // Let's create the new LINK tiket


                def newIssueResult = issueService.validateCreate(currentUserObj, issueInputParameters);
                if (!newIssueResult.isValid()) {
                    results += "could not create target issue: " + newIssueResult.getErrorCollection()
                }

                def newIssue = issueService.create(currentUserObj, newIssueResult).getIssue()

                results += "Created issue ${newIssue.key} ."

                Long linkType = 10196  // 'Relates to' link type


                try {
                    issueLinkManager.createIssueLink(issue.getId(), newIssue.getId(), linkType, Long.valueOf(1), currentUserObj)
                    results += "Added link from  ${issue.key} of type \'Relates\'  to ${newIssue.key}"

                }
                catch (e) {

                    results += "PROBLEM adding link from ${issue.key} of type ${linkType} to ${newIssue.key} :: ${e}"

                }

            }

        } else {

            results += "Ticket is not P1"
        }

//return results -- for testing

    }
}

 

 

1 answer

0 votes
francis
Marketplace Partner
Marketplace Partners provide apps and integrations available on the Atlassian Marketplace that extend the power of Atlassian products.
January 25, 2017

We have a similar issue

We run a bulk transition on a set of issues which trigger an event which are catched by a script listener which generates a custom mail ...

What we have now is that some mails have the subject of the previous issue, which is strange to say the least.

 

JIRA 6.2.7
Script Listener 3.0.16

cc: @Jamie Echlin (Adaptavist) 

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events