1st workaround
Working Principle
When a value is selected in a Connect field, the key defined in the field configuration is stored in Jira:
It's this data that is migrated by Configuration Manager.
The purpose is to update the Connect field key with a value common to both Jira instances:
- For a Connect field storing Issue IDs, the field key should be changed to Issue keys.
- For a Connect field storing Option IDs (such as a Select List), the field key should be changed to Option names.
For example, let's suppose that the source instance contains an issue whose id is "10000" and key is "TEST-1", while the target instance contains an issue whose id is "10001" and the key is "TEST-1". The Issue key is common to both instances and can be used for matching. By updating the Connect field key from Issue ID to Issue key in the source instance, the Connect field values can be migrated correctly by Configuration Manager.
Special case: JQL fields
In the case of a field connected to the "Jira issues (JQL)" datasource, the Issue ID is stored automatically and it's not possible to change the field key. You need to switch the datasource to "Jira database (SQL)" and then convert the JQL query into an SQL query.
Steps to follow
Case 1 - when the field key is an Issue ID
Context
Let's say the Connect field that stores Issue IDs is using the following query:
SELECT id, summary FROM jiraissue
As follows:
A. Edit the Connect field with a temporary configuration
First, you need to update the Connect field configuration and change its key from Issue ID to Issue key. To do so, you need to use $currentCustomfieldValue to retrieve the old keys, as this variable contains the key currently stored in Jira.
Please update your Connect field query as follows (and keep your initial query somewhere):
#if($currentCustomfieldValue.size() > 0)
SELECT CONCAT(p.pkey,'-',j.issuenum), j.summary
FROM jiraissue j
JOIN project p ON j.project = p.id
WHERE j.id IN ($currentCustomfieldValue.stringList())
#else
$query.abort()
#end
This field configuration is temporary until the script below is executed. Its purpose is to retrieve the Issue key corresponding to the returned Issue ID.
If the field is connected to the "Jira issues (JQL)" datasource, switch the datasource to "Jira database (SQL)"
B. Run a script to update the Connect field keys
You then need to run a script to update the Connect field key in all affected issues, based on the temporary Connect field query configured above.
Thus, the Issue ID will be changed to the Issue key in all affected issues:
log.warn("EC SCRIPT - Script Starts")
log.warn("EC SCRIPT --------------------------------------------------")
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 connectFieldsToBeUpdated = ["customfield_XXXXX","customfield_YYYYY"]
log.warn("EC SCRIPT --------------------------------------------------")
def results = searchService.search(user, query, PagerFilter.getUnlimitedFilter())
results.getResults().each { myIssue ->
log.warn("EC SCRIPT - Updating issue "+myIssue.key)
def issueToUpdate = issueManager.getIssueObject(myIssue.key)
connectFieldsToBeUpdated.each{connectFieldId ->
def connectField = customFieldManager.getCustomFieldObject(connectFieldId)
log.warn("EC SCRIPT - connect field: "+connectFieldId)
def connectFieldValue = myIssue.getCustomFieldValue(connectField)
if(connectFieldValue){
log.warn("EC SCRIPT - => "+connectFieldId+" contains the following value:"+connectFieldValue)
connectFieldValue = extracValueFromJson(connectFieldValue)
log.warn("EC SCRIPT - => extracted value:"+connectFieldValue)
def issueKeys = getIssueKeys(connectFieldValue, issueManager)
if(issueKeys){
log.warn("EC SCRIPT - => "+connectFieldId+" has been updated with these issue-keys:"+issueKeys)
issueToUpdate.setCustomFieldValue(connectField, '{"keys":['+issueKeys+']}')
} else {
log.warn("EC SCRIPT - => "+connectFieldId+" has not been updated")
}
} else {
log.warn("EC SCRIPT - => "+connectFieldId+" is empty")
}
}
issueManager.updateIssue(user, issueToUpdate, EventDispatchOption.DO_NOT_DISPATCH, false)
log.warn("EC SCRIPT --------------------------------------------------")
}
log.warn("EC SCRIPT - Script End")
def extracValueFromJson(jsonValue){
log.warn("EC SCRIPT - ==> extracValueFromJson - Start")
def match = (jsonValue =~ /\[.*?\]/)[0] // extract value thanks to a regex
def valueString = match.substring(0, match.length() - 1).substring(1) // remove square brackets
valueString = valueString.replace('"','') // remove double quotes
log.warn("EC SCRIPT - ==> extracValueFromJson - End")
return valueString.tokenize(',')
}
def getIssueKeys(connectFieldValue, issueManager){
log.warn("EC SCRIPT - ==> getIssueKeys - Start")
String issueKeys = ""
connectFieldValue.each{ value ->
if(value.toString().contains('-') && value.toString().tokenize('-')[1].isNumber()){
log.warn("EC SCRIPT - ===> "+value+" is an issue-key => we don't do anything")
}
else if(value.toString().isNumber()){
log.warn("EC SCRIPT - ===> "+value+' is an issue-id => we convert the issue-id to issue-key')
MutableIssue selectedIssue = issueManager.getIssueObject(value.toLong())
if(selectedIssue){
if(issueKeys.length() <= 0){
issueKeys = '"'+selectedIssue.getKey()+'"'
} else {
issueKeys += ',"'+selectedIssue.getKey()+'"'
}
} else {
log.warn("EC SCRIPT - ===> The issue '"+value+"' has not been found => we don't do anything")
}
}
else {
log.warn("EC SCRIPT - ===> '"+value+"' is not an issue-id or an issue key => we don't do anything")
}
}
log.warn("EC SCRIPT - ==> getIssueKeys - End")
return issueKeys
}
Please replace:
A_VALID_JQL_QUERY with a JQL query returning all the issues to be updated
customfield_XXXXX and customfield_YYYYY with the Connect fields ID
You can update multiple Connect fields at the same time, but be aware that this script may cause slowdowns depending on the number of Connect fields being updated and the number of issues returned by the JQL query.
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. Edit the Connect field with a permanent configuration
Once the key has been updated in all affected issues, you can replace your temporary query with a permanent one.
Different cases are possibles:
Case 01 - SQL field:
Assuming your initial Connect field query (storing Issue IDs) was the following:
CODESELECT id, summary FROM jiraissue
The new Connect field query (storing Issue keys) should be:
CODESELECT CONCAT(p.pkey,'-',ji.issuenum), ji.summary FROM jiraissue ji INNER JOIN project p ON p.id = ji.project
Case 02 - JQL field:
Assuming your field was connected to the "Jira issues (JQL)" and the query was:
CODEPROJECT = PM AND ISSUETYPE = Task
The new datasource should be "Jira database (SQL)" and the query should be:
CODESELECT CONCAT(p.pkey,'-',ji.issuenum), ji.summary FROM jiraissue ji INNER JOIN project p ON p.id = ji.project INNER JOIN issuetype it ON it.id = ji.issuetype WHERE p.pkey = 'PM' AND it.pname = 'Task'
D. Migrate with CMJ
Your Elements Connect configuration is then ready to be migrated with Configuration Manager.
Case 2 - when the field key is an Option ID
Context
Let's assume that the Connect field stores option IDs from a custom Select List (whose ID is "10300") and uses the following query:
SELECT DISTINCT cfo.id, cfo.customvalue FROM customfieldvalue cfv
INNER JOIN customfieldoption cfo ON cfv.stringvalue = cfo.id
WHERE cfv.customfield = 10300
As follows:
A. Edit the Connect field with a temporary configuration
First, you need to update the Connect field configuration and change its key from Option ID to Option Name. To do so, you need to use $currentCustomfieldValue to retrieve the old keys, as this variable contains the Option ID currently stored in Jira.
Please update your Connect field query as follows:
#if($currentCustomfieldValue.size() > 0)
SELECT DISTINCT cfo.customvalue FROM customfieldvalue cfv
INNER JOIN customfieldoption cfo ON cfv.stringvalue = cfo.id
WHERE cfv.customfield = 10300
AND cfo.id IN ($currentCustomfieldValue.stringList())
#else
$query.abort()
#end
Please replace 10300 according to your own custom field ID.
This field configuration is temporary until the script below is executed. Its purpose is to retrieve the Option Name corresponding to the returned Option ID.
B. Run a script to update the Connect field keys
You then need to run a script to update the Connect field key in all affected issues, based on the temporary Connect field query configured above.
Thus, the Option ID will be changed to the Option Name key in all affected issues:
log.warn("EC SCRIPT - Script Starts")
log.warn("EC SCRIPT --------------------------------------------------")
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.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 connectFieldsToBeUpdated = ["customfield_XXXXX","customfield_YYYYY"]
def customfieldFieldId = "customfield_10300"
def customfield = customFieldManager.getCustomFieldObject(customfieldFieldId)
log.warn("EC SCRIPT --------------------------------------------------")
def results = searchService.search(user, query, PagerFilter.getUnlimitedFilter())
results.getResults().each { myIssue ->
log.warn("EC SCRIPT - Updating issue "+myIssue.key)
def issueToUpdate = issueManager.getIssueObject(myIssue.key)
connectFieldsToBeUpdated.each{connectFieldId ->
def connectField = customFieldManager.getCustomFieldObject(connectFieldId)
log.warn("EC SCRIPT - connect field: "+connectFieldId)
def connectFieldValue = myIssue.getCustomFieldValue(connectField)
if(connectFieldValue){
log.warn("EC SCRIPT - => "+connectFieldId+" contains the following value:"+connectFieldValue)
connectFieldValue = extracValueFromJson(connectFieldValue)
log.warn("EC SCRIPT - => extracted value:"+connectFieldValue)
def fieldConfig = customfield.getRelevantConfig(myIssue)
def optionNames = getOptionNames(connectFieldValue, fieldConfig, optionsManager)
if(optionNames){
log.warn("EC SCRIPT - => "+connectFieldId+" has been updated with these Option Names:"+optionNames)
issueToUpdate.setCustomFieldValue(connectField, '{"keys":['+optionNames+']}')
} else {
log.warn("EC SCRIPT - => "+connectFieldId+" has not been updated")
}
} else {
log.warn("EC SCRIPT - => "+connectFieldId+" is empty")
}
}
issueManager.updateIssue(user, issueToUpdate, EventDispatchOption.DO_NOT_DISPATCH, false)
log.warn("EC SCRIPT --------------------------------------------------")
}
log.warn("EC SCRIPT - Script End")
def extracValueFromJson(jsonValue){
log.warn("EC SCRIPT - ==> extracValueFromJson - Start")
def match = (jsonValue =~ /\[.*?\]/)[0] // extract value thanks to a regex
def valueString = match.substring(0, match.length() - 1).substring(1) // remove square brackets
valueString = valueString.replace('"','') // remove double quotes
log.warn("EC SCRIPT - ==> extracValueFromJson - End")
return valueString.tokenize(',')
}
def getOptionNames(connectFieldValue, fieldConfig, optionsManager){
log.warn("EC SCRIPT - ==> getOptionNames - Start")
String optionNames = ""
connectFieldValue.each{ value ->
def name = optionsManager.findByOptionId((Long) value.toLong())
if(optionNames.length() <= 0){
optionNames = '"'+name+'"'
} else {
optionNames += ',"'+name+'"'
}
}
log.warn("EC SCRIPT - ==> getOptionNames - End")
return optionNames
}
Please replace:
A_VALID_JQL_QUERY with a JQL query returning all the issues to be updated
customfield_XXXXX and customfield_YYYYY with the Connect fields ID
- customfield_10300 with the custom Select List ID
You can update multiple Connect fields at the same time, but be aware that this script may cause slowdowns depending on the number of Connect fields being updated and the number of issues returned by the JQL query.
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. Edit the Connect field with a permanent configuration
Once the key has been updated in all affected issues, you can replace your temporary query with a permanent one:
SELECT DISTINCT cfo.customvalue FROM customfieldvalue cfv
INNER JOIN customfieldoption cfo ON cfv.stringvalue = cfo.id
WHERE cfv.customfield = 10300
Please replace 10300 according to your own custom field ID.
D. Migrate with CMJ
Your Elements Connect configuration is then ready to be migrated with Configuration Manager.