Need feedback on this script to convert subtasks to standard issues

I have several hundred issues with sub-tasks that need to be converted to Epics with issues. It’s too much to do by hand, so I’ve written this helper script. All the issues are first bulk converted to Epics, so the starting condition is a bunch of Epics with Sub-tasks. The Subtasks are converted to a specific standard issue type, the Epic link is added, and the parent link is removed. I’m not sure I have properly removed the existing parent-child link. At one point during testing I had issues appearing as both child issues and sub-tasks, which was clearly wrong. I think I’ve got this cleaned up but I may be missing some element of the parent-child relationship. Any input is welcome.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.query.Query
import com.atlassian.jira.jql.parser.JqlParseException
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder

def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def searchQuery = "project = JAM AND type = Epic"
def destinationIssueType = "11600" // Desired new issue type ID
def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issueManager = ComponentAccessor.issueManager
def issueLinkManager = ComponentAccessor.issueLinkManager

// Change subtasks of an Epic to child issues of an Epic
Query query
try { query = jqlQueryParser.parseQuery(searchQuery) } catch (JqlParseException e) { return e }
def searchResults =, user), PagerFilter.unlimitedFilter, (Set<String>)["key"])
log.warn("Query: '${searchQuery}' Results: ${searchResults.results.size()}")

// Iterate over each subtask in each Epic
searchResults.results.each { documentIssue ->
    def epicKey = documentIssue.document.fields.find { == "key" }.stringValue()
    def epic = issueManager.getIssueObject(epicKey)
    Collection subTasks = epic.getSubTaskObjects()
    subTasks.each { it ->
        log.warn("processing subtask " + it.key)
        // Remove the parent-child link and change the issue type
        def inwardLinks = issueLinkManager.getInwardLinks(
        inwardLinks.findAll { it.issueLinkType.isSubTaskLinkType() }.each {
            issueLinkManager.removeIssueLink(it, user)
        ((MutableIssue) it).setIssueTypeId(destinationIssueType)
        // set Epic link
        def epicLinkCF = ComponentAccessor.getCustomFieldManager().getCustomFieldObjects(it).findByName('Epic Link')
        if (epicLinkCF)
            epicLinkCF.updateValue(null, it, new ModifiedValue(it.getCustomFieldValue(epicLinkCF), epic), new DefaultIssueChangeHolder())
            log.error("Epic custom field not found for issue: " + it.key)
        //Update the moved issue
        issueManager.updateIssue(user, (MutableIssue) it, EventDispatchOption.ISSUE_UPDATED, false)
    // Reorder the epic's links as its sub-task links have been removed

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.