2nd workaround


Table of contents



Working principle

The purpose is to retrieve a piece of data which is common to across both Jira instances, migrate this data to the target instance and update the Connect fields accordingly:

  • For Issue IDs, the corresponding Issue keys are retrieved
  • For Option IDs (such as a Select List), the corresponding Option names are retrieved

Once this common piece of data is migrated, the Connect fields are updated accordingly.

For example, a ticket has id "10000" and key "TEST-1" in the source instance. Once migrated, this ticket has id "10001" and key "TEST-1" in the target instance. The "TEST-1" key is common to both instances and can be used for matching. Based on this key, you can update the Connect field in the target instance by following the procedure below.

Steps to follow

From the source Jira instance

A. Create a custom Text field

First, you need to create a custom Text Field (single line):

Add this custom field to the projects where the Connect field is used.

(info) Create one custom Text field for each Connect field. Each Connect field affected by the problem must have its own custom field.

B. Run a script to copy the Connect field value to the custom Text field

Then, you need to run a script (via the Console tab of Scriptrunner for Jira):

  1. If the Connect field stores Issue IDs, this is the script to execute to populate the custom Text field with the corresponding Issue keys:
    log.warn('EC SCRIPT - Start')
    import com.atlassian.jira.component.ComponentAccessor
    import com.atlassian.jira.bc.issue.search.SearchService
    import com.atlassian.jira.jql.parser.JqlQueryParser
    import com.atlassian.jira.web.bean.PagerFilter
    import com.atlassian.jira.issue.ModifiedValue
    import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
    import com.atlassian.jira.issue.MutableIssue
       
    def pluginAccessor = ComponentAccessor.getPluginAccessor();
    def plugin = pluginAccessor.getPlugin("com.valiantys.jira.plugins.SQLFeed");
    def serviceValueClass = plugin.getClassLoader().loadClass("com.valiantys.nfeed.api.IFieldValueService");
    def fieldValueService = ComponentAccessor.getOSGiComponentInstanceOfType(serviceValueClass);
    
    def customFieldManager = ComponentAccessor.getCustomFieldManager();
    def issueManager = ComponentAccessor.getIssueManager()
       
    def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
    def searchService = ComponentAccessor.getComponent(SearchService)
    def user = ComponentAccessor.getJiraAuthenticationContext().getUser()
       
    def query = jqlQueryParser.parseQuery("A_VALID_JQL_QUERY")
    def connectFieldId = "customfield_XXXXX"
    def customFieldId = "customfield_YYYYY"
    
    def customField = customFieldManager.getCustomFieldObject(customFieldId);
       
    def results = searchService.search(user, query, PagerFilter.getUnlimitedFilter())
       
    log.warn("EC SCRIPT - Total issues: ${results.total}")
    log.warn('EC SCRIPT ----------------------------------------------------------------')
       
    results.getResults().each { myIssue ->
    
        def connectFieldValue = fieldValueService.getFieldValues(myIssue.key, connectFieldId)
        if(connectFieldValue){
            log.warn("EC SCRIPT - "+connectFieldId+" is not empty in "+myIssue.key)
            String customFieldValue = ""
            log.warn("EC SCRIPT - Connect field values:"+connectFieldValue)
            connectFieldValue.each { value ->
                MutableIssue selectedIssue = issueManager.getIssueObject(value.toLong())
                
                if(customFieldValue.length() <= 0){
                    customFieldValue = selectedIssue.getKey()
                } else {
                    customFieldValue += ','+selectedIssue.getKey()
                }
            }    
            customField.updateValue(null, myIssue, new ModifiedValue(myIssue.getCustomFieldValue(customField), customFieldValue), new DefaultIssueChangeHolder())
            log.warn("EC SCRIPT - issue "+myIssue.key+" has been updated with:'"+customFieldValue+"'")
        } else {
            log.warn("EC SCRIPT - "+connectFieldId+" is empty in "+myIssue.key)
        }
        log.warn('EC SCRIPT ----------------------------------------------------------------')
    }
    log.warn('EC SCRIPT - End')

    Please replace:

    • A_VALID_JQL_QUERY with a JQL query returning all the issues to be updated

    • customfield_XXXXX with the Connect field ID

    • customfield_YYYYY with the Custom Text field ID

  2. If the Connect field stores Option IDs, this is the script to execute to populate the custom Text field with the corresponding Option names:
    log.warn('EC SCRIPT - Start')
    import com.atlassian.jira.component.ComponentAccessor
    import com.atlassian.jira.bc.issue.search.SearchService
    import com.atlassian.jira.jql.parser.JqlQueryParser
    import com.atlassian.jira.web.bean.PagerFilter
    import com.atlassian.jira.issue.ModifiedValue
    import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
       
    def pluginAccessor = ComponentAccessor.getPluginAccessor();
    def plugin = pluginAccessor.getPlugin("com.valiantys.jira.plugins.SQLFeed");
    def serviceValueClass = plugin.getClassLoader().loadClass("com.valiantys.nfeed.api.IFieldValueService");
    def fieldValueService = ComponentAccessor.getOSGiComponentInstanceOfType(serviceValueClass);
    
    def customFieldManager = ComponentAccessor.getCustomFieldManager();
    def issueManager = ComponentAccessor.getIssueManager()
    def optionsManager = ComponentAccessor.getOptionsManager()
       
    def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
    def searchService = ComponentAccessor.getComponent(SearchService)
    def user = ComponentAccessor.getJiraAuthenticationContext().getUser()
    
    def query = jqlQueryParser.parseQuery("A_VALID_JQL_QUERY")
    def connectFieldId = "customfield_XXXXX"
    def customFieldId = "customfield_YYYYY"
    
    def customField = customFieldManager.getCustomFieldObject(customFieldId);
       
    def results = searchService.search(user, query, PagerFilter.getUnlimitedFilter())
       
    log.warn("EC SCRIPT - Total issues: ${results.total}")
    log.warn('EC SCRIPT ----------------------------------------------------------------')
       
    results.getResults().each { myIssue ->
    
        def connectFieldValue = fieldValueService.getFieldValues(myIssue.key, connectFieldId)
        if(connectFieldValue){
            log.warn("EC SCRIPT - "+connectFieldId+" is not empty in "+myIssue.key)
            String customFieldValue = ""
            log.warn("EC SCRIPT - Connect field values:"+connectFieldValue)
            connectFieldValue.each { value ->          
                def optionItem = optionsManager.getAllOptions().find{
                    (String) it.optionId.toString() == value
                } 
    
                if(customFieldValue.length() <= 0){
                    customFieldValue = optionItem
                } else {
                    customFieldValue += ','+optionItem
                }
            }    
            customField.updateValue(null, myIssue, new ModifiedValue(myIssue.getCustomFieldValue(customField), customFieldValue), new DefaultIssueChangeHolder())
            log.warn("EC SCRIPT - issue "+myIssue.key+" has been updated with:'"+customFieldValue+"'")
        } else {
            log.warn("EC SCRIPT - "+connectFieldId+" is empty in "+myIssue.key)
        }
        log.warn('EC SCRIPT ----------------------------------------------------------------')
    }
    log.warn('EC SCRIPT - End')

    Please replace:

    • A_VALID_JQL_QUERY with a JQL query returning all the issues to be updated

    • customfield_XXXXX with the Connect field ID

    • customfield_YYYYY with the Custom Text field ID

This script may take a long time to run based on the scope of affected issues. We thus recommend you to schedule this maintenance operation during non-business hours in order to avoid performance issues

 C. Migrate with CMJ

Once the custom Text field has been populated with Issue keys, perform the migration using Configuration Manager and make sure sure that the custom Text field is embedded/included in the migration.

D. Delete the custom Text field

Once the migration has been carried out, delete the custom Text field.

From the target Jira instance

A. Run a script to copy the custom Text field value to the Connect field

Once the migration has been carried out, you need to run the following script in the target instance (via the Console tab of Scriptrunner for Jira):

  1. If the Connect field stores Issue IDs:
    log.warn('EC SCRIPT - Start')
    import com.atlassian.jira.component.ComponentAccessor
    import com.atlassian.jira.bc.issue.search.SearchService
    import com.atlassian.jira.jql.parser.JqlQueryParser
    import com.atlassian.jira.web.bean.PagerFilter
    import com.atlassian.jira.issue.MutableIssue
    import com.atlassian.jira.event.type.EventDispatchOption
       
    def pluginAccessor = ComponentAccessor.getPluginAccessor();
    
    def customFieldManager = ComponentAccessor.getCustomFieldManager();
    def issueManager = ComponentAccessor.getIssueManager()
       
    def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
    def searchService = ComponentAccessor.getComponent(SearchService)
    def user = ComponentAccessor.getJiraAuthenticationContext().getUser()
       
    def query = jqlQueryParser.parseQuery("A_VALID_JQL_QUERY")
    def connectFieldId = "customfield_XXXXX"
    def customFieldId = "customfield_YYYYY"
    
    def connectField = customFieldManager.getCustomFieldObject(connectFieldId);
    def customField = customFieldManager.getCustomFieldObject(customFieldId);
       
    def results = searchService.search(user, query, PagerFilter.getUnlimitedFilter())
       
    log.warn("EC SCRIPT - Total issues: ${results.total}")
    log.warn('EC SCRIPT ----------------------------------------------------------------')
       
    results.getResults().each { myIssue ->
        if(myIssue.getCustomFieldValue(customField)){
            def customFieldValues = myIssue.getCustomFieldValue(customField).toString().split(',')
            log.warn("EC SCRIPT - "+customFieldId+" is not empty in "+myIssue.key)
            log.warn("EC SCRIPT - Custom field values:'"+myIssue.getCustomFieldValue(customField)+"'")
            def connectFieldValues = []
            customFieldValues.each { value -> 
                MutableIssue selectedIssue = issueManager.getIssueObject(value)
                
                if(selectedIssue){
                    connectFieldValues.add((String) selectedIssue.id.toString())
                }
            }
            if(connectFieldValues){
                log.warn("EC SCRIPT - Connect field values:'"+connectFieldValues+"'")
                def issueToUpdate = issueManager.getIssueObject(myIssue.key)
                issueToUpdate.setCustomFieldValue(connectField, '{"keys":'+connectFieldValues+'}');
                issueManager.updateIssue(user, issueToUpdate, EventDispatchOption.DO_NOT_DISPATCH, false);
                log.warn("EC SCRIPT - "+connectFieldId+" has been updated in "+myIssue.key)
            }
        } else {
            log.warn("EC SCRIPT - "+customFieldId+" is emtpy in "+myIssue.key)
        }
        log.warn('EC SCRIPT ----------------------------------------------------------------')
    }
    log.warn('EC SCRIPT - End')

    Please replace:

    • A_VALID_JQL_QUERY with a JQL query returning all the issues to be updated

    • customfield_XXXXX with the Connect field ID

    • customfield_YYYYY with the Custom Text field ID

  2. If the Connect field stores Option IDs:
    log.warn('EC SCRIPT - Start')
    import com.atlassian.jira.component.ComponentAccessor
    import com.atlassian.jira.bc.issue.search.SearchService
    import com.atlassian.jira.jql.parser.JqlQueryParser
    import com.atlassian.jira.web.bean.PagerFilter
    import com.atlassian.jira.issue.MutableIssue
    import com.atlassian.jira.event.type.EventDispatchOption
       
    def pluginAccessor = ComponentAccessor.getPluginAccessor();
    
    def customFieldManager = ComponentAccessor.getCustomFieldManager();
    def issueManager = ComponentAccessor.getIssueManager()
    def optionsManager = ComponentAccessor.getOptionsManager()
       
    def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
    def searchService = ComponentAccessor.getComponent(SearchService)
    def user = ComponentAccessor.getJiraAuthenticationContext().getUser()
       
    def query = jqlQueryParser.parseQuery("A_VALID_JQL_QUERY")
    def connectFieldId = "customfield_XXXXX"
    def customFieldId = "customfield_YYYYY" // the field used to migrate Connect field values
    def selectListId = "customfield_ZZZZZ" // the field whose Options IDs are stored by the Connect field
    
    def connectField = customFieldManager.getCustomFieldObject(connectFieldId);
    def customField = customFieldManager.getCustomFieldObject(customFieldId);
    def selectListField = customFieldManager.getCustomFieldObject(selectListId);
       
    def results = searchService.search(user, query, PagerFilter.getUnlimitedFilter())
       
    log.warn("EC SCRIPT - Total issues: ${results.total}")
    log.warn('EC SCRIPT ----------------------------------------------------------------')
       
    results.getResults().each { myIssue ->
        if(myIssue.getCustomFieldValue(customField)){
            def customFieldValues = myIssue.getCustomFieldValue(customField).toString().split(',')
            log.warn("EC SCRIPT - "+customFieldId+" is not empty in "+myIssue.key)
            log.warn("EC SCRIPT - Custom field values:'"+myIssue.getCustomFieldValue(customField)+"'")
            def connectFieldValues = []
            customFieldValues.each { value -> 
                def fieldConfig = selectListField.getRelevantConfig(myIssue)
                def optionItem = optionsManager.getOptions(fieldConfig)?.find {
                    it.toString() == value
                }
                
                if(optionItem){
                    connectFieldValues.add((String) optionItem.optionId.toString())
                }
            }
            if(connectFieldValues){
                log.warn("EC SCRIPT - Connect field values:'"+connectFieldValues+"'")
                def issueToUpdate = issueManager.getIssueObject(myIssue.key)
                issueToUpdate.setCustomFieldValue(connectField, '{"keys":'+connectFieldValues+'}');
                issueManager.updateIssue(user, issueToUpdate, EventDispatchOption.DO_NOT_DISPATCH, false);
                log.warn("EC SCRIPT - "+connectFieldId+" has been updated in "+myIssue.key)
            }
        } else {
            log.warn("EC SCRIPT - "+customFieldId+" is empty in "+myIssue.key)
        }
        log.warn('EC SCRIPT ----------------------------------------------------------------')
    }
    log.warn('EC SCRIPT - End')

    Please replace:

    • A_VALID_JQL_QUERY with a JQL query returning all the issues to be updated

    • customfield_XXXXX with the Connect field ID

    • customfield_YYYYY with the Custom Text field ID (the field used to migrate Connect field values)

    • customfield_ZZZZZ with the Custom field ID used by the Connect field (the field whose Options IDs are stored by the Connect field).

This script may take a long time to run based on the scope of affected issues. We thus recommend you to schedule this maintenance operation during non-business hours in order to avoid performance issues

B. Delete the custom Text field

Once the Connect fields have been updated, delete the corresponding custom Text field.