Skip to end of metadata
Go to start of metadata

You are viewing an old version of this content. View the current version.

Compare with Current View Version History

« Previous Version 26 Current »

Tested on September 12, 2024, Jira 9.12.8, ScriptRunner 8.33.0, Multi-Level Cascading Select 7.0.2.

If you are coming from the JCMA process, read this!

Error [JCMA 104] JCMA default values migration limitations

Before you begin the migration process, it is important to understand the limitations of Jira Cloud Migration Assistant (JCMA) regarding default values of third-party custom fields.

Due to Atlassian’s limitations, you are required to set your MLCS default value to none in Jira Data Center to complete the migration with JCMA.

if this error screen is displayed during the pre-migration checks:

Screenshot 2025-04-30 alle 11.04.03.png

please check the ZIP file to review the logs. If you encounter the following error: 2025-04-30 07:19:00.022383 ERROR ITS project-export Custom field type: com.sourcesense.jira.plugin.cascadingselect:multi-level-cascading-select for Custom Field 'AAA-mlcs-4-fm-dc' is not supported. [JCMA 104], it means that default values are set in your Jira Data Center instance. To proceed with the process and make sure to migrate your custom field data, Please unset the default values in your custom field page (Configure contextEdit default values). You can set them up again in the Cloud instance once the migration is performed.

For more information on this, visit the related Jira issue: MIG-2180.

This information is also available in our JCMA documentation article: https://sourcesense.atlassian.net/wiki/spaces/SOUS/pages/138379265/Jira+Cloud+Migration+Assistant?atl_f=PAGETREE.


After migrating the projects and issues, you can transfer MLCS field values from Server/Data Center to Cloud using the ScriptRunner console. This article outlines the steps to complete the migration:

Configuration Steps:

  1. Set Up Jira Cloud API Access:

    • Find your Jira Cloud REST API Base URL. Typically, it follows the format https://[YourInstance].atlassian.net.

    • Create an API token from your Atlassian account. Follow Atlassian's guide for creating an API token.

    • Combine your email and API token in the format email:token. This will be used for basic authentication.

  2. Identify the Custom Field:

    • Find the name of the MLCS custom field in your Jira Server instance that you wish to migrate.

    • Obtain the ID of the corresponding custom field in your Jira Cloud instance.

  3. Update the Script:

    • Replace jiraCloudApiBaseUrl with your Jira Cloud instance's base URL.

    • Update encodedAuthString with your email and API token.

    • Update customFieldName with the name of your MLCS custom field in the Jira Data Center instance.

    • Replace cloudCustomFieldId with the ID of the corresponding custom field in your Jira Cloud instance.

import com.atlassian.jira.issue.customfields.option.LazyLoadedOption
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.security.JiraAuthenticationContext
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.issue.search.SearchException
import com.atlassian.jira.web.bean.PagerFilter
import groovy.json.JsonOutput
import groovyx.net.http.RESTClient
import groovyx.net.http.ContentType
 
// Jira Cloud REST API base URL
final String jiraCloudApiBaseUrl = "https://[YourInstance].atlassian.net"
 
// Basic Authentication Encoded String
def encodedAuthString = "your_email:token".bytes.encodeBase64().toString()
 
// Get necessary components
def issueManager = ComponentAccessor.getIssueManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def authenticationContext = ComponentAccessor.getJiraAuthenticationContext()
def searchService = ComponentAccessor.getComponent(SearchService.class)
 
// Define and find the custom field
final String customFieldName = "mlcs employee"
final String cloudCustomFieldId = "customfield_10101"
def customField = customFieldManager.getCustomFieldObjectsByName(customFieldName)?.first()
 
if (customField == null) {
    log.error "Custom field not found: $customFieldName"
    return
}
 
// Fetch issues
def user = authenticationContext.getLoggedInUser()
def query = "\"${customFieldName}\" is not EMPTY"
 
// Parse the JQL query
def parseResult = searchService.parseQuery(user, query)
 
def transformElement(LazyLoadedOption element) {
  String label = element.getValue()
  String optionId = element.getOptionId().toString()
  return [label: label, value: optionId]
}
 
if (!parseResult.isValid()) {
    log.error "Invalid JQL Query: ${query}"
    return
}
 
try {
    def results = searchService.search(user, parseResult.getQuery(), PagerFilter.getUnlimitedFilter())
 
    results.getResults().each { Issue issue ->
       List serverValue = issue.getCustomFieldValue(customField)
        if (serverValue) {
 
           def transformedArray = [:]
 
        // Populate the map
        serverValue.eachWithIndex { element, index ->
            transformedArray["lv$index"] = transformElement(element as LazyLoadedOption)
        }
            
 
            def payload = [
                fields: [
                    (cloudCustomFieldId):  JsonOutput.toJson(transformedArray)
                ]
            ]
 
            def client = new RESTClient(jiraCloudApiBaseUrl)
 
            client.setHeaders([
                'Content-Type' : ContentType.JSON,
                'Authorization': "Basic $encodedAuthString"
            ])
 
            try {
                def response = client.put(
                    path: "/rest/api/3/issue/$issue.key",
                    contentType: ContentType.JSON,
                    body: payload
                )
 
                if (response.status != 204) {
                    log.error("Failed to update issue ${issue.key}: ${response.data}")
                } else {
                    log.info("Issue updated successfully: ${issue.key}")
                }
            } catch(Exception e) {
                log.error("Error updating issue ${issue.key}: ${e.message}")
            }
        }
    }
} catch (SearchException e) {
    log.error("Error executing search: ${e.message}")
}

Execution:

  1. Run the Script:

    • Open the ScriptRunner Console in your Jira Server instance.

    • Paste the updated script into the console.

    • Execute the script.

Troubleshooting

If issues fail to update, check the log for error messages. Ensure that all instance URLs, field names, and IDs are correct. Verify that your API token has appropriate permissions. Also, be aware of the following HTTP status codes and their implications:

  • 400 Bad Request

    • This error occurs if the user lacks the necessary permissions to edit the issue or to view it, or if the mlcs field is not found in the cloud or not associated with the issue's edit screen.

  • 401 Unauthorized

    • This status is returned if the token or the email is not valid.

  • 403 Forbidden

    • This can happen if the user does not have the necessary permissions to edit the issue or to view it.

  • 404 Not Found

    • Returned if the issue is not found or the user does not have permission to view it.

  • Connection refused

    • This error indicates network issues. Please check the connectivity between the On Premises instance and the Cloud instance.

  • No labels