Step-by-step migration guide

Here are the key steps to follow to successfully migrate your Elements Connect configuration from Jira Server/Data Center to Jira Cloud: 

Step 1: Evaluate the current feature parity on Cloud

First, verify that all the features you need exist on Jira Cloud by checking out the Feature Comparison table.
Awareness of the feature parity between the hosting environments is essential for you to understand if your use case can be reproduced on Cloud. Some of your configurations may not yet be available, or you need to implement another solution to achieve the same that you have done in your Jira Server / Data Center environment.

What's coming next

We are getting closer and closer to bridging the functionality gap between the Server / Data Center product and the Cloud version, and are dedicated to providing you with the same value no matter which hosting you use. Join our Early Access Program to stay updated.

Step 2: Install Elements Connect on your Jira Cloud instance

Install Elements Connect on your Jira Cloud instance following this tutorial.
We recommend you take some time to evaluate the Cloud app to better understand how it works. Feel free to take a look at our documentation for configuration help and how-tos, or to reach out to our support team.


Step 3: Manually create new configurations

Manually create your configuration (datasources and connected items) in Elements Connect for Jira Cloud.

Don’t forget to uncheck the option "Create associated mirror field", when creating connected items.

Step 4: Prepare Connect fields on Server/DC for migration

Step 4.1. Create target custom fields

On your Server / Data Center instance, create as many Jira Text Custom fields as you have Elements Connect fields.

If your Connect field stores multiple values, or if it stores single values with many characters, then you should create Jira Text Area custom fields (as Text single-line fields are limited to a maximum of 255 characters).

Step 4.2. Copy Connect fields display to these target custom fields

Now that your target custom fields are created, you can now copy your Connect fields display to these. You can do so in two different ways: 

  • by using the "Copy field display to another field" post-function, or
  • by running a ScriptRunner script to copy the field display to another field.

HTML in display template

For every Elements Connect custom fields using HTML/CSS in the display template, make sure there are no reference to HTML/CSS. To do so, you need to edit the display template for each Elements Connect custom fields and remove HTML/CSS references.

Option 1: using the "Copy field display to another field" post-function

  • For each workflow linked to an issue associated with an Elements Connect Custom Field, add a global transition with our post-function "Copy field display to another field"
  • Configure as many post-functions as needed in order to have a 1-to-1 mapping between Elements Connect Custom Fields and Jira Text Custom fields
  • Use a Bulk operation to execute the previous transition on all the relevant issues

User type fields

For Elements Connect User type custom fields, use the "Copy field key to another field" post-function. You will copy the user's username in the text field.

Option 2: running a ScriptRunner script to copy the field display to another field 

You can also decide to use a Groovy script to copy Elements Connect field display to other custom fields. For this to work, you'll need to have Script Runner installed.

The following script will copy the field display of Elements Connect fields that are not empty which match the JQL query specified at line 21.


import org.apache.log4j.Logger
import org.apache.log4j.Level
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLinkTypeManager
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.security.groups.GroupManager
log.setLevel(Level.INFO)
log.info("SCRIPT - START")
log.info("-----------------------------------------")
def pluginAccessor = ComponentAccessor.getPluginAccessor();
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def user = ComponentAccessor.getJiraAuthenticationContext().getUser()
// -----------------------------------------------------
// TO EDIT: please write a valid JQL query returning the issues to be updated
def jqlQuery = "project = PM"
// TO EDIT: Please populate the table with the following information:
// - In the 1st column, the Connect field ID to be copied (source)
// - In the 2nd column, the Text Area field ID to be populated (target)
// This must be a 1 to 1 relationship, and one Connect field must correspond to one Text Area field.
def fieldsMapping =
    [
        ['customfield_10200','customfield_10305'],
        ['customfield_10302','customfield_10306'],
        ['customfield_10203','customfield_10307'],
        ['customfield_10303','customfield_10308'],
        ['customfield_10304','customfield_10309'],
    ]
// Loop to browse all Connect fields to be copied.
for(fields in fieldsMapping){
    def connectFieldId = fields[0]
    log.info("SCRIPT - Connect field ID => "+connectFieldId)
    def textFieldId = fields[1]
    def textField = customFieldManager.getCustomFieldObject(textFieldId);
    // We get the Connect field type (Live Text, Live User, Snapshot Text, Snapshot Date or Snapshot Datetime)
    def connectFieldType = customFieldManager.getCustomFieldObject(connectFieldId).getCustomFieldType().getName();
    log.info("SCRIPT - Connect field type => "+customFieldManager.getCustomFieldObject(connectFieldId).getCustomFieldType().getName())
    // Improvement of the JQL query with a check of the Connect field in each ticket
    def jqlQueryOptimized;
    if(jqlQuery){
        jqlQueryOptimized = jqlQuery+" AND cf["+connectFieldId.replace('customfield_','')+"] IS NOT EMPTY"
    }
    else{
        jqlQueryOptimized = "cf["+connectFieldId.replace('customfield_','')+"] IS NOT EMPTY"
    }
    log.info("SCRIPT - JQL Query => "+jqlQueryOptimized)
    // Processing of the JQL query
    def results = searchService.search(user, jqlQueryParser.parseQuery(jqlQueryOptimized), PagerFilter.getUnlimitedFilter())
    def issueManager = ComponentAccessor.issueManager
    def issueLinkTypeManager = ComponentAccessor.getComponent(IssueLinkTypeManager)
    final Long sequence = 1L
    def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
    log.info("SCRIPT - Total issues to be updated => ${results.total}")
    // Different value copy actions are performed depending on the Connect field type
    if(connectFieldType == "Elements Connect - Live - Text"){    
        // We get the classes related to the Elements Connect API
        def plugin = pluginAccessor.getPlugin("com.valiantys.jira.plugins.SQLFeed");
        def serviceClass = plugin.getClassLoader().loadClass("com.valiantys.nfeed.api.IFieldDisplayService");
        def fieldDisplayService = ComponentAccessor.getOSGiComponentInstanceOfType(serviceClass);
        // Loop  to browse all the issues returned by the JQL Query
        results.getResults().eachWithIndex { issue , it ->
            String displayValue = "";
            Object displayResult = fieldDisplayService.getDisplayResult(issue.key, connectFieldId);
            if (displayResult && displayResult.getDisplay()) {
                def textValues = displayResult.getDisplay().replace('<BR/>','\n');
                // We copy the Connect field value to the Text Area field
                textField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(textField), textValues), new DefaultIssueChangeHolder())  
                log.info("SCRIPT - issue ${issue.key} has been updated ("+(it+1)+" out of ${results.total})");
            }    
        }
    }
    else if(connectFieldType == "Elements Connect - Live - User") {
        // We get the classes related to the Elements Connect API
        def plugin = pluginAccessor.getPlugin("com.valiantys.jira.plugins.SQLFeed");
        def serviceClass = plugin.getClassLoader().loadClass("com.valiantys.nfeed.api.IFieldValueService");
        def fieldValueService = ComponentAccessor.getOSGiComponentInstanceOfType(serviceClass);
        // Loop  to browse all the issues returned by the JQL Query
        results.getResults().eachWithIndex { issue , it ->
            def userValues = fieldValueService.getFieldValues(issue.key, connectFieldId);
            if(userValues){
                // We copy the Connect field value to the Text Area field
                textField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(textField), userValues.join('\n')), new DefaultIssueChangeHolder())
                log.info("SCRIPT - issue "+issue.key+" has been updated ("+(it+1)+" out of ${results.total})");
            }
        }
    }
    else if(connectFieldType == "Elements Connect - Snapshot - Text" || connectFieldType == "Elements Connect - Snapshot - Datetime") {
        // Loop  to browse all the issues returned by the JQL Query
        results.getResults().eachWithIndex { issue , it ->
            def snapshotField = customFieldManager.getCustomFieldObject(connectFieldId)
            def snapshotValues = issue.getCustomFieldValue(snapshotField)
            if(snapshotValues){
                // We copy the Connect field value to the Text Area field
                textField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(textField), snapshotValues.toString()), new DefaultIssueChangeHolder())
                log.info("SCRIPT - issue "+issue.key+" has been updated ("+(it+1)+" out of ${results.total})");
            }
        }
    }
    else if(connectFieldType == "Elements Connect - Snapshot - Date") {
        // Loop  to browse all the issues returned by the JQL Query
        results.getResults().eachWithIndex { issue , it ->
            def snapshotField = customFieldManager.getCustomFieldObject(connectFieldId)
            def snapshotValues = issue.getCustomFieldValue(snapshotField)
            if(snapshotValues){
                // We copy the Connect field value to the Text Area field               
                textField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(textField), snapshotValues.toString().split(" ")[0]), new DefaultIssueChangeHolder())
                log.info("SCRIPT - issue "+issue.key+" has been updated ("+(it+1)+" out of ${results.total})");
            }
        }
    }
    else {
        log.info("SCRIPT - Connect Field Type => Unknown")
    }
    log.info("-----------------------------------------")
}
log.info("SCRIPT - END")
CODE


Or download it here:

How to run this script?

In order for this script to run, you will need to:

1/ Specify a JQL query to narrow down the scope of this field display copy job. (line 21)


We recommend you to start with a restrictive JQL query for testing purposes. If tests are successful, then you can extend the scope of your JQL query.

2/ Specify the Elements Connect field IDs you want to copy (source) and the field IDs of the Text Area custom fields where you want the data to be copied to (target) (start line 28). There should be as many field mappings as there are Elements Connect fields to copy.


If your JQL query returns over 1000 results, make sure to increase the default maximum limit of 1000 in Jira. Alternatively, you can run the script over as many batches as required.


If you wish to run this script over a large number of issues, we recommend you to do that during non-business hours for performance reasons.

(lightbulb) We recommend that you verify that all Elements Connect Custom Fields have their display value copied in their dedicated Jira Text Custom Field.

Step 5: Export fields from your Server/DC instance

You have two ways of exporting your Jira issues with their values:

Or

Some specificities to take into account:

HTML in display template

For every Elements Connect custom fields using HTML/CSS in the display template, make sure there are no reference to HTML/CSS in the CSV document:

  • Edit the display template for each Elements Connect custom fields and remove HTML/CSS references
    OR

  • Make sure there are no HTML/CSS references directly in the CSV file

User type fields
For Elements Connect User type custom fields, use the "Copy field key to another field" post-function. You will copy the user's username in the text field.

Step 6: Import the fields on Cloud

Depending on the solution you used in step 5:

    • If you used the CSV exporter:
      • On your Jira Cloud instance, create as many Jira Text Custom fields as previously created on Server/Data Center
      • Import the previously generated CSV file.
  •  

Step 7: Set the mirror fields

For each Connected item, you need to set the mirror field accordingly. To do so, map the custom fields created in Step 4 to the right Connected item using the mirror fields.

(star) Congrats! That’s it!

This migration path allows you to migrate data to Jira Cloud only. Configurations need to be performed manually again and Connected items need to be manually added to projects as well.

(question) We're here to help

We’re committed to making your migration to Elements Connect for Jira Cloud as smooth as possible, don’t hesitate to contact our support team should you have any questions or concerns. We recommend that you attach your support dump file to your support request, it'll help our team provide more accurate answers about your specific case.