This use case is very often encountered when companies need to collaborate on a certain project but would prefer to continue to use their own systems. Exalate can bridge the gap by syncing all the agile objects and maintain the relationships among items. All items are linked bi-directionally of course and the Jira boards also remain synced in real time. 

We use an example of a Jira Cloud and Jira Server synchronization covering the following:


The following graphic shows some of the objects and relationships we are syncing across:

There are many facets to this use case. Let us look at how each object is synced across:

Epics

We can easily sync Epics in Jira Cloud by including the following lines of script:


We can include the exact same lines on the Server side to sync Epics, but please ensure that you have placed the Epics.groovy script in the correct Jira directory. 

Sprints

In order to send over the sprints from the Cloud side, we used the following script in the Outgoing side of Jira Cloud:

def boardIds = ["5"] //Boards which sprints will get synced
if(entityType == "sprint" && boardIds.find{it == sprint.originBoardId}){
    replica.name = sprint.name
    replica.goal = sprint.goal
    replica.state = sprint.state
    replica.startDate = sprint.startDate
    replica.endDate = sprint.endDate
    replica.originBoardId = sprint.originBoardId
}

Note: Please remember to configure the board id that you need to sync in the first line. 


On the Jira Server side, we included the following script to receive and process the sprint correctly:


if(entityType == "sprint"){
    //Executed when receiving a sprint sync from the remote side
    def sprintMap = ["5":"37"] //[remoteBoardId: localBoardId]
    sprint.name = replica.name
    sprint.goal = replica.goal
    sprint.state = replica.state?.toUpperCase()
    sprint.startDate = replica.startDate
    sprint.endDate = replica.endDate
    def localBoardId = sprintMap[replica.originBoardId]

    if(localBoardId == null){
       throw new com.exalate.api.exception.IssueTrackerException("No board mapping for remote board id "+replica.originBoardId)
    }
    sprint.originBoardId = localBoardId //Set the board ID where the sprint will be created
}


Note: The only things that you need to configure is the sprintMap to include the board mappings between the two systems. 


Also, please remember to create a trigger to sync the sprints!


The last piece of the puzzle is to assign the correct Sprint field value to the issues under sync. On the server side, you will need to include the following in the Incoming side:

def sprintV = replica.customFields.Sprint.value?.id.collect{ 
    remoteSprintId ->
    return nodeHelper.getLocalIssueKeyFromRemoteId(remoteSprintId, "sprint")?.id?.toString()
    }.findAll{it != null}
issue.customFields."Sprint".value = sprintV


Versions

In order to dynamically sync Versions between Cloud and Server side, we include the following script on the Outgoing side of both Server and Cloud:


replica.fixVersions = issue.fixVersions
replica.affectedVersions = issue.affectedVersions

and the following on the Incoming script on Server and Cloud to create any missing sprints:


issue.fixVersions = replica
  .fixVersions
  .collect { v -> nodeHelper.createVersion(issue, v.name, v.description) }
issue.affectedVersions = replica
  .affectedVersions
  .collect { v -> nodeHelper.createVersion(issue, v.name, v.description) }

Components

In order to dynamically sync Components between Cloud and Server side, we include the following script on the Outgoing side of both Server and Cloud:

replica.components       = issue.components

In order to dynamically create the components on the Cloud side, add the following script on the Incoming side of the Cloud:

issue.components = replica.components.collect{
	nodeHelper.createComponent(
		issue, 
		it.name, 
		it.description, 
		it.leadKey, 
		it.assigneeType.name()
	)
} 

In order to select the correct component on the Server side, please include the following script on the Incoming side of the Server:

issue.components = replica.components
    .collect { remoteComponent ->
        nodeHelper.getComponent(
            remoteComponent.name,
            nodeHelper.getProject(issue.projectKey)
   )
}.findAll()


You can download the entire code used in this use case here


A short video demonstration of the use case in action is as follows: