Hello - We seem to have an ongoing problem where if the original Jira issue is closed with a specific resolution, then Exalate sync fails with the following error:
Could not perform a transition `{“transition”:{“id”:“41”}}` for work item with id `129,567`: Please select a Resolution.
We are syncing the resolution as far as I can tell but I am wondering if the screen on transition is causing issues. Any help would be appreciated.
Just to confirm my understanding: you have a sync configured between two Jira instances, and when a ticket is closed in one Jira, you want it to also close in the other. However, Exalate is failing to do this because the mandatory field “Resolution” is required, is that correct?
Could you please share the synchronization script from both sides so I can review it?
Hi Sonal - It’s actually a sync between the same instance but I don’t think that matters but figured i’d let you know. Otherwise everything is true, when we close a Jira in the source project, it fails to transition the destination item because of the resolution.
Here are the scripts:
Outgoing
replica.key = issue.key
replica.type = issue.type
//replica.assignee = issue.assignee
replica.reporter = issue.reporter
replica.summary = issue.summary
replica.description = issue.description
replica.labels = issue.labels
replica.comments = issue.comments
replica.resolution = issue.resolution
replica.status = issue.status
replica.parentId = issue.parentId
replica.priority = issue.priority
replica.attachments = issue.attachments
replica.project = issue.project
replica.issueType = issue.issueType
//replica.due = issue.due
replica.resolved = issue.resolved
/*
Uncomment these lines if you want to send the full list of versions and components of the source project.
replica.project.versions = []
replica.project.components = []
*/
/*
Custom Fields (CF)
How to send any field value from the source side to the destination side.
1/ Add the value to the replica object using the Display Name of the specific field.
2/ Uncomment this next statement out and change accordingly:
replica.customFields."CF Name" = issue.customFields."CF Name"
*/
//for bug only
if (issue.type.name == "Bug") {
replica.customFields."Total Clients Reporting" = issue.customFields."10305"
replica.customFields."Total Associated ARR" = issue.customFields."10306"
replica.customFields."Severity" = issue.customFields."10049"
}
//Added on 17 Jan 2025
if(issue.type.name == "Story" || issue.type.name == "Task" || issue.type.name == "Bug"){
replica.customFields."Allocation" = issue.customFields."10168"
}
replica.customFields."Product" = issue.customFields."10165"
replica.customFields."Team" = issue.customFields."10170"
replica.customFields."Salesforce - Record Owner" = issue.customFields."10472"
//v8 backend edits only
if (issue.type.name == "VEdits") {
replica.customFields."Sub Issue Type" = issue.customFields."10269"
}
// Exalate API Reference Documentation: https://docs.exalate.com/docs/exalate-api-reference-documentation
Incoming
if (firstSync && replica.project.key == "CLONEEVA") {
issue.projectKey = "EVA"
issue.typeName = nodeHelper.getIssueType(replica.type?.name, issue.projectKey)?.name ?: "Task"
}
if (firstSync && replica.project.key == "EVA") {
issue.projectKey = "CLONEEVA"
issue.typeName = nodeHelper.getIssueType(replica.type?.name, issue.projectKey)?.name ?: "Task"
}
issue.summary = replica.summary
issue.description = replica.description
issue.comments = commentHelper.mergeComments(issue, replica)
issue.attachments = attachmentHelper.mergeAttachments(issue, replica)
issue.labels = replica.labels
issue.status = replica.status.name
//issue.priority = replica.priority
//issue.due = replica.due
def tempUser = nodeHelper.getUser(replica.reporter?.email)
if ((issue.status.name == "Canceled" || replica.status.name == "Canceled") &&
(replica.issueType.name == "Bug" || issue.issueType?.name == "Bug" ||
replica.issueType.name == "Story" || issue.issueType?.name == "Story")) {
def resolutionMap = [
"Done" : "Done",
"Won\'t Do" : "Won\'t Do",
"Duplicate" : "Duplicate",
"Declined" : "Declined",
"Canceled" : "Canceled",
"Cannot Reproduce" : "Cannot Reproduce",
"Not A Bug" : "Not A Bug" ]
def targetResolutionName = resolutionMap[replica.resolution?.name] ?: "Done"
workflowHelper.transition(issue, replica.status.name, tempUser)
issue.resolution = nodeHelper.getResolution(targetResolutionName)
}
if(replica.issueType.name == "Bug"){
issue.customFields."10305".value = replica.customFields."Total Clients Reporting"?.value
issue.customFields."10306".value = replica.customFields."Total Associated ARR"?.value
issue.customFields."10049".value = replica.customFields."Severity"?.value?.value
issue.resolved = replica.resolved
}
//populate remote jira key
if(issue.projectKey == "CLONEEVA" && (replica.issueType.name == "V8 Backend Edits" || replica.issueType.name == "Story" || replica.issueType.name == "Task" || replica.issueType.name == "Bug")){
issue.customFields."10505".value = replica.key
}
if(replica.issueType.name == "V8 Backend Edits"){
issue.customFields."10269".value = replica.customFields."Sub Issue Type"?.value?.value ?: "Root Folder"
}
//Allocation checks added on 17 Jan 2025
if(replica.issueType.name == "Story" || replica.issueType.name == "Task" || replica.issueType.name == "Bug"){
issue.customFields."10168".value = replica.customFields."Allocation"?.value?.value
}
//Multi-select pickliest
issue.customFields."10165".value = nodeHelper.getOptions(issue, 10165,
replica.customFields."Product".value.collect {it -> it.value})
issue.customFields."10170".value = replica.customFields."Team"?.value?.value
/*
User Synchronization (Assignee/Reporter)
Set a Reporter/Assignee from the source side. If the user can't be found, set a default user.
You can also use this approach for custom fields of the type User
*/
def defaultUser = nodeHelper.getUserByEmail("jira_randdautomationsapi@scrubbed.com")
issue.reporter = nodeHelper.getUserByEmail(replica.reporter?.email) ?: defaultUser
//issue.assignee = nodeHelper.getUserByEmail(replica.assignee?.email)
issue.customFields."10472".value = replica.customFields."Salesforce - Record Owner"?.value
/*
Comment Synchronization
Impersonate comments with the original author. The sync will work if the user already exists
in the local instance.
Note: Don’t forget to remove the original comments sync line if you are using this approach.
issue.comments = commentHelper.mergeComments(issue, replica) {
it.executor = nodeHelper.getUserByEmail(it.author?.email)
}
*/
issue.issueType = replica.issueType
// Exalate API Reference Documentation: https://docs.exalate.com/docs/exalate-api-reference-documentation
Looks like that did not solve the problem. I tested and got the same error. It doesn’t seem to matter if it is the local work or remote work item that is transitioned
replica.key = issue.key
replica.type = issue.type
//replica.assignee = issue.assignee
replica.reporter = issue.reporter
replica.summary = issue.summary
replica.description = issue.description
replica.labels = issue.labels
replica.comments = issue.comments
replica.resolution = issue.resolution
replica.status = issue.status
replica.parentId = issue.parentId
replica.priority = issue.priority
replica.attachments = issue.attachments
replica.project = issue.project
replica.issueType = issue.issueType
//replica.due = issue.due
replica.resolution = issue.resolution
/*
Uncomment these lines if you want to send the full list of versions and components of the source project.
replica.project.versions = []
replica.project.components = []
*/
/*
Custom Fields (CF)
How to send any field value from the source side to the destination side.
1/ Add the value to the replica object using the Display Name of the specific field.
2/ Uncomment this next statement out and change accordingly:
replica.customFields."CF Name" = issue.customFields."CF Name"
*/
//for bug only
if (issue.type.name == "Bug") {
replica.customFields."Total Clients Reporting" = issue.customFields."10305"
replica.customFields."Total Associated ARR" = issue.customFields."10306"
replica.customFields."Severity" = issue.customFields."10049"
}
//Added on 17 Jan 2025
if(issue.type.name == "Story" || issue.type.name == "Task" || issue.type.name == "Bug"){
replica.customFields."Allocation" = issue.customFields."10168"
}
replica.customFields."Product" = issue.customFields."10165"
replica.customFields."Team" = issue.customFields."10170"
replica.customFields."Salesforce - Record Owner" = issue.customFields."10472"
//v8 backend edits only
if (issue.type.name == "V8 Backend Edits") {
replica.customFields."Sub Issue Type" = issue.customFields."10269"
}
// Exalate API Reference Documentation: https://docs.exalate.com/docs/exalate-api-reference-documentation
if (firstSync && replica.project.key == "CLONEEVA") {
issue.projectKey = "EVA"
issue.typeName = nodeHelper.getIssueType(replica.type?.name, issue.projectKey)?.name ?: "Task"
}
if (firstSync && replica.project.key == "EVA") {
issue.projectKey = "CLONEEVA"
issue.typeName = nodeHelper.getIssueType(replica.type?.name, issue.projectKey)?.name ?: "Task"
}
issue.summary = replica.summary
issue.description = replica.description
issue.comments = commentHelper.mergeComments(issue, replica)
issue.attachments = attachmentHelper.mergeAttachments(issue, replica)
issue.labels = replica.labels
issue.status = replica.status.name
//issue.priority = replica.priority
//issue.due = replica.due
def tempUser = nodeHelper.getUser(replica.reporter?.email)
if ((issue.status.name == "Canceled" || replica.status.name == "Canceled") &&
(replica.issueType.name == "Bug" || issue.issueType?.name == "Bug" ||
replica.issueType.name == "Story" || issue.issueType?.name == "Story")) {
def resolutionMap = [
"Done" : "Done",
"Won\'t Do" : "Won\'t Do",
"Duplicate" : "Duplicate",
"Declined" : "Declined",
"Canceled" : "Canceled",
"Cannot Reproduce" : "Cannot Reproduce",
"Not A Bug" : "Not A Bug" ]
def targetResolutionName = resolutionMap[replica.resolution?.name] ?: "Done"
workflowHelper.transition(issue, replica.status.name, tempUser)
issue.resolution = nodeHelper.getResolution(targetResolutionName)
}
if(replica.issueType.name == "Bug"){
issue.customFields."10305".value = replica.customFields."Total Clients Reporting"?.value
issue.customFields."10306".value = replica.customFields."Total Associated ARR"?.value
issue.customFields."10049".value = replica.customFields."Severity"?.value?.value
issue.resolved = replica.resolved
}
//populate remote jira key
if(issue.projectKey == "CLONEEVA" && (replica.issueType.name == "V8 Backend Edits" || replica.issueType.name == "Story" || replica.issueType.name == "Task" || replica.issueType.name == "Bug")){
issue.customFields."10505".value = replica.key
}
if(replica.issueType.name == "V8 Backend Edits"){
issue.customFields."10269".value = replica.customFields."Sub Issue Type"?.value?.value ?: "Root Folder"
}
//Allocation checks added on 17 Jan 2025
if(replica.issueType.name == "Story" || replica.issueType.name == "Task" || replica.issueType.name == "Bug"){
issue.customFields."10168".value = replica.customFields."Allocation"?.value?.value
}
//Multi-select pickliest
issue.customFields."10165".value = nodeHelper.getOptions(issue, 10165,
replica.customFields."Product".value.collect {it -> it.value})
issue.customFields."10170".value = replica.customFields."Team"?.value?.value
/*
User Synchronization (Assignee/Reporter)
Set a Reporter/Assignee from the source side. If the user can't be found, set a default user.
You can also use this approach for custom fields of the type User
*/
def defaultUser = nodeHelper.getUserByEmail("jira_randdautomationsapi@scrubbed.com")
issue.reporter = nodeHelper.getUserByEmail(replica.reporter?.email) ?: defaultUser
//issue.assignee = nodeHelper.getUserByEmail(replica.assignee?.email)
issue.customFields."10472".value = replica.customFields."Salesforce - Record Owner"?.value
/*
Comment Synchronization
Impersonate comments with the original author. The sync will work if the user already exists
in the local instance.
Note: Don’t forget to remove the original comments sync line if you are using this approach.
issue.comments = commentHelper.mergeComments(issue, replica) {
it.executor = nodeHelper.getUserByEmail(it.author?.email)
}
*/
issue.issueType = replica.issueType
// Exalate API Reference Documentation: https://docs.exalate.com/docs/exalate-api-reference-documentation