Optimizing 50+ Post Function groovy script calls on issue create

flaimo February 4, 2020

We are using a Jira project to manage all our onboarding tasks whenever a new employee joins our department. The information about the teams and their leads are implemented using the components and component leads (and relabelling the field using behaviours). All other data is implemented as custom fields.

Whenever a new employee joins, the manager creates a new "Onboarding" issue and fills out the required fields. After he clicks "Create" a bunch of sub-tasks are created automatically and assigned to different users with different due dates based on the information entered in the parent issue. Also, some of the sub-tasks are linked together using a "depends on" linking type.

I have implemented this using Scriptrunner. For each sub-task I have created a Post Function script call, which is triggered in the Create transition. Those calls are in a particular order, so sub-tasks created later on can link to sub-tasks created earlier.

The problem is, that creating a new onboarding ticket takes a long time and sometimes I run into timeout issues, where the create issue dialog switches back from an inactive state back to an active state, because I have hit some kind of timeout limit, even though the post functions are still running in the background (they all finish successfully).

I have attached one of the scripts, which mostly all look the same. Maybe someone can give me feedback on how to optimize the script, so it might run faster.

 

/* ========== change values here ============ */

def subtaskComponent = issue.getParentObject().components.first().name /* lead of component (team) from paren issue should be used as sub-task assignee */
def subtaskAssignee = "" /* jira username; overrides component lead if needed */
def subtaskRelDueDate = 14 /* in days relative to main issue due date */
def subtaskLabelIDString = "hrst_0052" /* us this label for the sub-task to identify issue later on in other sub-task create scripts*/
def dependsOnLabelIDs = ["hrst_0004"] /* list of labels from tasks that should later be linked together */
def checklistURL = "https://wiki.karriereservice.at/x/FgJwAw" /* link to specific checklist in confluence for this sub-task */
def subtaskDescription = """Checkliste: $checklistURL""" /* also write link to confluence page into subject for this sub-task */

/* ========================================== */

import com.atlassian.jira.component.ComponentAccessor
import java.sql.Timestamp
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.user.util.DefaultUserManager
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.project.Project
import com.atlassian.jira.issue.label.LabelManager
import com.atlassian.jira.config.IssueTypeManager
import com.atlassian.jira.exception.CreateException
import com.atlassian.jira.issue.IssueFactory
import com.atlassian.jira.issue.link.RemoteIssueLinkBuilder
import com.atlassian.jira.bc.issue.link.RemoteIssueLinkService

if (subtaskComponent?.trim()) {
def projectComponentManager = ComponentAccessor.getProjectComponentManager()
def issueProject = issue.getProjectObject()
def foundComponents = projectComponentManager.findByComponentName(issueProject.getId(), subtaskComponent)
issue.setComponent([foundComponents])

def componentLead = issue.components.first().getComponentLead()
ApplicationUser customAssigneeUserObject = null

if (subtaskAssignee?.trim()) {
def userManager = ComponentAccessor.getUserManager() as UserManager
customAssigneeUserObject = userManager.getUserByName(subtaskAssignee)
} // end if

if (customAssigneeUserObject) {
issue.setAssignee(customAssigneeUserObject)
} else if (componentLead) {
issue.setAssignee(componentLead)
}// end if
} // end if

if (subtaskDescription?.trim()) {
issue.description = subtaskDescription.replaceAll(/ /, '')
} // end if

def dateOfEntry = (Date) issue.getParentObject().getCustomFieldValue(customFieldManager.getCustomFieldObject(12830)) // ID of "First Work Day" field

if (dateOfEntry) {
def newDueDate = dateOfEntry + subtaskRelDueDate
issue.setDueDate(new Timestamp((newDueDate).time))
} // end if

doAfterCreate = {
def labelManager = ComponentAccessor.getComponent(LabelManager)
labelManager.addLabel(currentUser, issue.getId(), subtaskLabelIDString, false)

/* Add link to checklist in confluence */
def linkBuilder = new RemoteIssueLinkBuilder()
def serviceUser = ComponentAccessor.userManager.getUserByKey("jenkins")
linkBuilder.issueId(issue.getId())
linkBuilder.relationship("Wiki Page")
linkBuilder.title("Checkliste")
linkBuilder.url(checklistURL)

def remoteIssueLinkService = ComponentAccessor.getComponent(RemoteIssueLinkService)
def validationResult = remoteIssueLinkService.validateCreate(currentUser, linkBuilder.build())

if (validationResult.isValid()) {
remoteIssueLinkService.create(serviceUser, validationResult)
log.debug "Remote link created"
}
else {
log.error validationResult.errorCollection.errorMessages
}

/* link to dependency tickets */
def parentIssue = issue.getParentObject()
def subTaskManager = ComponentAccessor.getSubTaskManager()
def subTasks = subTaskManager.getSubTaskObjects(parentIssue)

subTasks.each() {
def subtaskLabels = it.getLabels()
def dependsOnLabelIDexists = dependsOnLabelIDs.any { subtaskLabels*.label.contains(it) }

if (dependsOnLabelIDexists) {
//10105: 0 = depended on by, 1 = depends on
issueLinkManager.createIssueLink(issue.getId(), it.getId(), Long.parseLong("10105"),Long.valueOf(1), currentUser);
}
} // end each
}

 

1 answer

0 votes
Leonard Chew
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.
February 4, 2020

Here are two individual ideas to solve the problem:

  1. Increase the session timeout. I'm not sure, but the timeout responsible for your problem, might be the default session timeout of the web-app:
    https://confluence.atlassian.com/jirakb/change-the-default-session-timeout-to-avoid-user-logout-in-jira-server-604209887.html

  2. Instead of creating the sub-tasks synchronously, you might want to do that asynchronously. This could probably be done with a custom event.
    - Create a cusom event, e.g. Event "Create Onboarding Tasks"
    - Fire your "Create Onboarding Tasks" Event as a postfunction after the main issue is created (then the screen will be available again for the user)
    - Delete your 50 postfunctions and implement them as Listeners, listening on your "Create Onboarding Tasks" Event.
Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 4, 2020

I'd also simplify

50 slightly different scripts doing creates is quite an overhead.  If you could take (say) 5 of them that create the first (say) 10 subtasks combine them into one so you're only running one script and can optimise that.

The second layer of optimisation might also to pre-calculate and loop.  Rather than create st-1, st-2, st-3, etc, build a collection of data that varies between them, then loop the creation (using the same issue-create object repeatedly and altering it on each create).  More obscure than the other three options above, and probably less effective in improvement, but "every little helps"

Marc Minten _EVS_
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.
February 4, 2020

...unfortunately, I think all events in Jira are by default synchronous events :-( (and neither multi-threaded)

If time is not critical, maybe you can implement next work-around:

Do you need it to be real time? Could you write a scheduled job that runs every 5minutes and just checks for updated issues (specific fields etc) using JQL. Or have you event listener create an AO entry in the database with the issue key. Then through a scheduled service you can submit the updated issues

flaimo February 6, 2020

managers go through the created subtasks immediately after creation, so asynchronous is not an option.

 

since the prerequisites are quite different for each script it would lead to a myriad of if/else branches in on script.

Suggest an answer

Log in or Sign up to answer