Platforms: any → Jira Data Center
The problem
By default, the first sync of a remote item creates a new issue in Jira. Often the issue already exists: it was created by hand before the integration, or several remote items belong to the same Jira issue. Creating a new issue then gives duplicates, and the team has to merge or close them by hand.
The approach
- The remote side sends the Jira reference. Its outgoing script puts the Jira ID (
12345) or key (PROJ-123) inreplica.jiraId. - The Jira DC incoming script finds that issue on first sync. A numeric value is looked up with
getIssueObject, a key withgetIssueByCurrentKey. If a reference is sent but the issue doesn’t exist, the sync stops with a clear error instead of creating a duplicate. - The script connects to the existing issue instead of creating one. It loads the issue as Exalate sees it, fills
issueBeforeScriptandissuewith its current values, and setsissue.idandissue.keyto the existing issue. - Nothing sent: normal behavior. Without a
jiraId, a new issue is created in the default project, as usual.
The result
- One to one: a remote item connects to the Jira issue it points to, no duplicate is created.
- Many to one: when another remote item arrives later with the same Jira ID or key, it connects to that same Jira issue as well.
- From then on, the items sync like any other pair.
Notes
- Replace
PROJwith your default project key, and filltypemapwith your remote type names and the matching Jira issue types. - The remote outgoing script must send the reference, for example
replica.jiraId = <the field that holds the Jira ID or key>. getIssueByCurrentKeyonly accepts keys. A numeric Jira ID needsgetIssueObject(id as Long), which is why the script checksisLong()first.- Maps are copied into
issue(new LinkedHashMap(v)), so the changes made by the rest of your incoming script are detected as changes againstissueBeforeScript. - With many to one, all connected remote items update the same Jira issue. Keep that in mind for fields like summary or status, where the last update wins.
- Don’t have the ID or key on the remote side, only a value in a custom field? Find the issue with a JQL search first: [link to JQL post].
- Put your normal incoming mappings (summary, description, comments) below the connect block.
The Scripts
1. Jira DC incoming script: connect to an existing issue
// Exalate | Jira Data Center | Incoming sync
// On first sync, connect the remote item to an EXISTING Jira issue instead of creating a new one.
// Works one to one and many to one: every remote item that sends the same Jira ID or key
// is connected to that same Jira issue.
//
// The remote side sends the Jira ID (12345) or key (PROJ-123) in replica.jiraId.
// Nothing sent: a new issue is created in the default project.
//
// Configure:
// PROJECT_KEY default project for new issues
// typemap remote type name -> Jira issue type name
//
// Notes:
// - getIssueByCurrentKey() takes keys only; use getIssueObject(id as Long) for numeric IDs.
// - Maps are copied into "issue" so the rule's changes show up as diffs against issueBeforeScript.
import com.atlassian.jira.component.ComponentAccessor
final String PROJECT_KEY = "PROJ"
Map typemap = [
"Bug": "Bug"
]
String typeName = typemap[replica.type?.name] ?: debug.error("Value ${replica.type?.name} Not found in type map.")
def issueManager = ComponentAccessor.issueManager
def mainIssue = null
// Find the existing Jira issue by the ID or key sent by the remote side
if (firstSync && replica.jiraId) {
def ref = replica.jiraId.toString().trim() // Jira ID (12345) or key (PROJ-123)
mainIssue = ref.isLong() ? issueManager.getIssueObject(ref as Long) : issueManager.getIssueByCurrentKey(ref)
if (!mainIssue) {
debug.error("Jira issue '${ref}' not found, cannot connect.")
}
}
if (firstSync && mainIssue) {
// Use the existing issue's project/type; fall back to defaults
issue.projectKey = mainIssue.project?.key ?: PROJECT_KEY
issue.typeName = mainIssue.issueType?.name ?: typeName
// Load the existing Jira issue as Exalate sees it
def issueKey = new com.exalate.basic.domain.BasicIssueKey(mainIssue.id, mainIssue.key)
def hubObjectHelper = ComponentAccessor
.getOSGiComponentInstanceOfType(com.exalate.api.hubobject.IHubObjectHelperFactory.class)
.get(new com.exalate.basic.domain.BasicSemanticVersion(1, 0, 0))
def hubIssue = hubObjectHelper.getHubIssueOnOutgoingProcessor(issueKey)
// Fill "before" and "after"; Maps are copied so the rule's changes are detected as diffs
hubIssue.getProperties().each { k, v ->
issueBeforeScript[k] = v
issue[k] = (v instanceof Map) ? new LinkedHashMap(v) : v
}
// Connect
issueBeforeScript.id = null
issue.id = mainIssue.id
issue.key = mainIssue.key
issue.customKeys.somethingIsChanged = true
} else if (firstSync) {
issue.projectKey = PROJECT_KEY
issue.typeName = typeName
}
// Your normal incoming mappings go below (summary, description, comments, ...)