I want to migrate TimePiece data from one Jira to another. Would it be possible to do using Exalate?
Exalate can be used for migration scenarios, including moving issue data between Jira instances. However, there are some important considerations:
- Exalate is primarily designed for synchronization, not bulk migration, so the process may be slower than native Jira import/export tools.
- Some fields (like certain dates, issue keys, and creators) cannot be migrated due to Jira API limitations.
- Custom fields and data from plugins (like Timepiece) may require custom sync rules. If Timepiece stores its data in custom fields or standard Jira fields, you can map and sync those fields using Exalate’s scripting capabilities.
- You may need to develop and test custom synchronization rules to ensure all relevant Timepiece data is transferred.
For more details, see the official documentation:
Can I Use Exalate for Migration?
If Timepiece data is stored in custom fields, you can refer to guides like:
How to Sync Date and DateTime Custom Fields in Jira On-premise
How to Sync Time Tracking Fields in Jira On-premise
If you need to migrate large volumes or have complex requirements, Exalate recommends reaching out to their support or professional services for guidance.
It would be possible indeed, but it would need you to customize the scripts in order to achieve your exact use case. Here is what I tried as a quick POC:
- I tried to migrate the calendars from source to destination, and used the Get, Post and Put APIs from here: Create Calendar
- The authentication is hardcoded in scripts, but you can always abstract that to the backend.
I have attached a video demo here of what I was able to do:
The full scripts (minus the tokens) are here:
Outgoing on Source
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
def token = "__add your token here__"
def url = "https://tis.obss.io/rest/calendar?tisjwt=${token}"
def connection = new URL(url).openConnection()
connection.setRequestMethod("GET")
def response = connection.getInputStream().getText("UTF-8")
def calendars = new JsonSlurper().parseText(response)
replica.customKeys.all_calendars = new JsonBuilder(calendars).toString()
Incoming on Destination
if (replica.customKeys.all_calendars) {
def sourceCalendars = new JsonSlurper().parseText(replica.customKeys.all_calendars)
def targetToken = "__your token here__"
def targetUrl = "https://tis.obss.io/rest/calendar?tisjwt=${targetToken}"
def targetConnection = new URL(targetUrl).openConnection()
targetConnection.setRequestMethod("GET")
def targetResponse = targetConnection.getInputStream().getText("UTF-8")
def targetCalendars = new JsonSlurper().parseText(targetResponse)
sourceCalendars.each { sourceCalendar ->
// Skip built-in calendars
if (sourceCalendar.name == "normalHours" || sourceCalendar.isBuiltIn == true) {
return
}
def targetCalendar = targetCalendars.find { it.name == sourceCalendar.name }
def payload = [
name: sourceCalendar.name,
timeZone: sourceCalendar.timeZone,
workingTimes: sourceCalendar.workingTimes,
holidays: sourceCalendar.holidays ?: []
]
def jsonPayload = new JsonBuilder(payload).toString()
if (targetCalendar && targetCalendar.id) {
// UPDATE existing calendar
def updateUrl = "https://tis.obss.io/rest/calendar/${targetCalendar.id}?tisjwt=${targetToken}"
def connection = new URL(updateUrl).openConnection()
connection.setRequestMethod("PUT")
connection.setDoOutput(true)
connection.setRequestProperty("Content-Type", "application/json")
connection.outputStream.withWriter { writer ->
writer << jsonPayload
}
def responseCode = connection.getResponseCode()
if (responseCode != 200) {
def errorText = connection.getErrorStream()?.getText("UTF-8") ?: "No error"
debug.error("Update failed ${sourceCalendar.name}: ${responseCode} - ${errorText}")
}
} else {
// CREATE new calendar
def createUrl = "https://tis.obss.io/rest/calendar?tisjwt=${targetToken}"
def connection = new URL(createUrl).openConnection()
connection.setRequestMethod("POST")
connection.setDoOutput(true)
connection.setRequestProperty("Content-Type", "application/json")
connection.outputStream.withWriter { writer ->
writer << jsonPayload
}
def responseCode = connection.getResponseCode()
if (responseCode != 200 && responseCode != 201) {
def errorText = connection.getErrorStream()?.getText("UTF-8") ?: "No error"
debug.error("Create failed ${sourceCalendar.name}: ${responseCode} - ${errorText}")
}
}
}
}
Hope it helps you!
Thanks
Majid