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

Version 1 Next »

This documentation describe a solution to migration from TGNG Server / Datacenter to TGNG Cloud.

In order to migrate from TGNG Server / Datacenter, we build a script to help migrate issues data as for now. We plan to build an in-app solution to help migrating our app to the Atlassian Cloud, this feature is in our roadmap for 2024.

Groovy Script

 Expand to see the script....
import groovy.json.JsonSlurper;
import groovy.json.StreamingJsonBuilder;
import com.atlassian.jira.issue.CustomFieldManager;
import com.atlassian.jira.issue.fields.CustomField;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.MutableIssue;
import com.atlassian.jira.user.util.UserManager;
import com.atlassian.jira.bc.issue.search.SearchService;
import com.atlassian.jira.jql.parser.JqlQueryParser;
import com.atlassian.jira.web.bean.PagerFilter;
import org.apache.commons.codec.binary.Base64;
import groovy.json.*;


////////////////////////////////////////////
// ***** CHANGE VARIABLES HERE *****
////////////////////////////////////////////

// Authorization 
String authorizationStringServer = "Basic ="
String authorizationStringCloud = "Basic ="
String authorizationStringCloudAccountID = "Basic =="
// Grid IDs 
String gridCustomFieldIdServer = 1
String gridIdCloud = "-"
//Jira BASE URL
String jiraBaseURLServer = "http://---"
String jqlQuery = '------'

////////////////////////////////////////////
// ***** CHANGE VARIABLES HERE *****
////////////////////////////////////////////

//IssueManager issueManager = ComponentAccessor.getOSGiComponentInstanceOfType(IssueManager.class);
//Collection<Long> ids = issueManager.getIssueIdsForProject(projectId); // Get all issue IDs by the Project ID 
//Collection<Issue> issues = issueManager.getIssueObjects(ids);  

StringBuilder responses = new StringBuilder()

def searchService = ComponentAccessor.getComponent(SearchService)
def loggedInUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def queryParser = ComponentAccessor.getComponent(JqlQueryParser)
def sb = new StringBuffer()

//your query goes here
def query = queryParser.parseQuery(jqlQuery)

//gets results of query
def search = searchService.search(loggedInUser, query, PagerFilter.getUnlimitedFilter())

//iterate over each returned issue
Collection<Issue> issues = search.results.key


for (String issue : issues){

    String issueKey = issue
    
    //String issueKey = issue.getKey()
    
    def baseURL = jiraBaseURLServer + "/rest/idalko-grid/1.0/api/grid/" + gridCustomFieldIdServer + "/issue/" + issueKey;
    def baseURL2 =  "https://databuckets.net/rest/tgc/api/v1/grids/" + gridIdCloud + "/issue/" + issueKey;
    def instanceName = "----"

    //Getting the Grid information from the Data Center Jira instance
    URL url;
    url = new URL(baseURL);

    URLConnection connection = url.openConnection();
    connection.setRequestMethod("GET")
    connection.setRequestProperty("Authorization", authorizationStringServer)
    connection.doOutput = true
    connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8")
    connection.connect();

    String serverData = connection.getInputStream().getText()
    def jsonSlurper = new JsonSlurper()
    def object = jsonSlurper.parseText(serverData)

    Long gridRowsinCloud = -1; //create variable with invalid value just to signal exception and skip condition if failed

    try {

        // Geting Grid info from Cloud to see if it was already uploaded (avoid duplicates)

        URL url3;
        url3 = new URL(baseURL2);
        URLConnection connection3 = url3.openConnection();
        connection3.setRequestMethod("GET")
        connection3.doOutput = true
        connection3.setRequestProperty("Authorization", authorizationStringCloud)
        connection3.connect();

        responseCode = connection3.getResponseCode()

        if(responseCode != 200){
            responseMessage = connection3.getErrorStream().text;
        }else{
            String cloudData = connection3.getInputStream().getText();
            def jsonSlurperAccount = new JsonSlurper()
            def cloudjson = jsonSlurperAccount.parseText(cloudData)
            //Long gridRowsinServer = object.rows.size();
            gridRowsinCloud = cloudjson.rows.size(); //if it works, change value of variable
        }


    } catch(java.io.IOException ex) {

        responses.append("!! exception !!");

    }
   
    // check if data was already uploaded    
    if(gridRowsinCloud == 0){
    //if(gridRowsinServer > 0 && gridRowsinCloud == 0){

        // Formatting the output so that it fits the POST request body formatting requirements
        object.remove("customFieldId")         //Removing Grid ID since does not need to be passed in the request body
        object.rows.each { e ->         //Going through all of the rows from the response


            e.remove("order")           // Removing order index
            e.remove("rowId")           // Removing Row ID since it will be automatically assigned
            e.columns.each{ key, value ->   // Unpacking the "columns" section into the previous level of JSON structure
                
                if(value != null || !value.toString().equals("{}") || value != ""){

                    if(value instanceof String){
                        e.put(key, value)
                    }else if(value instanceof org.apache.groovy.json.internal.LazyMap){

                        if(value.keySet().contains("username")){
                        
                            // Get user email to match with user in Cloud
                            def userManager = ComponentAccessor.getUserManager()
                            def user = userManager.getUserByName(value.username)

                            if(user != null){
                                String email = user.getEmailAddress()

                                // Getting new user info from Cloud based on email
                                URL urlAccountID
                                urlAccountID = new URL("https://${instanceName}.atlassian.net/rest/api/3/user/search?query=" + email.replace(" ","%20"));
                                URLConnection connection4 = urlAccountID.openConnection();
                                connection4.setRequestMethod("GET")
                                connection4.setRequestProperty("Authorization", authorizationStringCloudAccountID)
                                connection4.doOutput = true
                                connection4.connect();

                                String cloudData1 = connection4.getInputStream().getText()
                                def jsonSlurperAccount1 = new JsonSlurper()
                                def objectAccount1 = jsonSlurperAccount1.parseText(cloudData1)
                                
                                if(objectAccount1[0] != null){
                                    e.put(key, jsonSlurperAccount1.parseText('{"accountId":"' + objectAccount1[0].accountId+'"}'))
                                }
                            }
                        } 
                        if(value.keySet().contains("label")){
                            e.put(key, value.label)
                        }
                    }else if(value.toString().length() != 2){
                        e.put(key, value.value)
                    }
                }
            }
            e.remove("columns")             // Removing the packed "columns" section as it is redundant and breaks the formatting
            e.remove("Item Number")         // Remove SEQUENCE type column from the data load 
        }


        try {

            // Uploading the grid data to the Cloud
            URL url2;
            url2 = new URL(baseURL2);
            URLConnection connection2 = url2.openConnection();
            connection2.setRequestMethod("POST")
            connection2.doOutput = true
            connection2.setRequestProperty("Authorization", authorizationStringCloud)
            connection2.setRequestProperty("Content-Type", "application/json;charset=UTF-8")
            connection2.outputStream.withWriter("UTF-8") { new StreamingJsonBuilder(it, object) }
            connection2.connect();

            responseCode = connection2.getResponseCode()

            if(responseCode != 201){
                responseMessage = connection2.getErrorStream().text + JsonOutput.toJson(object);
            }else{
                responseMessage = connection2.getResponseMessage();
            }

            responses.append(   "  ||  IssueKey: " + issueKey + 
                                " ResponseCode:" + responseCode + 
                                " ResponseMessage:" + responseMessage)

        } catch(java.io.IOException ex) {

            responses.append("!! exception !!");
        
        }

        responses.append("\n")

    }
}

return responses

Prerequisites

  • A plugin/tool for running Groovy code is present on the instance from which the script is being run

  • TGNG apps installed on both Server and Cloud instances

  • The project from the source instance was already migrated to the target instance

  • All Columns in the Grid Config are made editable before running the script (can be changed after)

  • Please note that the script was written to be used as a sample and require you to change the variables values

Disclaimers

  • The script was developed to be run from the source Server instance.

  • The script is not an official solution for migration but it’s a workaround solution and should be used with care and precaution.

Guide

  1. Make sure that all of the prerequisites above are met

  2. Verify whether the grid fields on your Server instance have a multi-context setup

    1. If not, configure identical Grids on the Cloud instance (+ Scopes)

    2. If so, create an identical Grid configuration for each distinct context

  3. Obtain valid authentication strings from both instances

  4. Replace the placeholder authentication values as well as instance URL's with valid ones

  5. Replace the gridCustomFieldIdServer and gridIdCloud for the values corresponding to the first grid or Grid context that is being migrated

  6. Prepare the script to verification by adding a logic gate in the "for" loop or by hard-coding a single issue KEY into the issueKey variable

  7. Change projectId to a project that is relevant for the current grid field

  8. Verify whether object.rows.each { } loop does not contain any mentions of columns that are not present in the Grid configuration and add e.remove("COLUMN_NAME") for all sequence columns

  9. Run the script and verify data, for each Grid or Grid context:

    1. Change the gridCustomFieldIdServer, gridIdCloud, projected or any custom issue-pulling logic to relevant values

    2. Re-do step 11

    3. Run the script

    4. Verify whether the data was migrated correctly

  • No labels