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:
- Sprints are synced bi-directionally with all the meta data
- Epics and its child objects are synced across while ensuring that the relationships among the Epic and child tickets are maintained.
- Adding any tickets to a sprint should reflect on the remote board as well.
- Starting and completing a sprint should sync over and reflect on the remote board.
- Updating issues, changing swim lanes and creating new issues should all reflect on the remote board.
- Dynamically sync across versions and components if they are not present on the remote side.
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:
- Cloud Outgoing: Epic.sendInAnyOrder()
- Cloud Incoming: Epic.receive()
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: