Hi
There we use this incoming:
def projKey = "XXXXX"
// =====================
// UNEXALATE "INTERNES"
// =====================
if (replica.type?.name == "Internes") {
// stop sync if the issue type has changed to "Internes"
if (!firstSync) {
syncHelper.unExalateAfterProcessing()
}
return
}
// =====================
// PROJECT & ISSUE TYPE (First Sync Only)
// =====================
if (firstSync) {
issue.projectKey = projKey
}
issue.type = nodeHelper.getIssueType(replica.type?.name)
// =====================
// STANDARD FIELDS
// =====================
issue.summary = replica.summary
issue.resolution = replica.resolution
issue.labels = replica.labels
issue.description = replica.description
issue.parentId = replica.parentId
issue.project = replica.project
issue.resolution = replica.resolution
issue.components = replica.components
issue.changedComments = replica.changedComments
issue.removedComments = replica.removedComments
issue.changeHistory = replica.changeHistory
issue.watches = replica.watches
issue.created = replica.created
issue.updated = replica.updated
issue.due = replica.due
//issue.entityProperties = replica.entityProperties
issue.securityLevel = replica.securityLevel
issue.issueLinks = replica.issueLinks
// =====================
// NVX AUTOR
// =====================
def autor = replica.reporter
if (autor) {
def displayName = autor.displayName ?: autor.name ?: autor.key ?: "Unbekannt"
def email = autor.email
// If email is present, put it on the second line in parentheses
def authorText = email
? "${displayName}\n(${email})"
: displayName
issue.customFields."Ticket-Autor".value = authorText as String
}
// =====================
// TICKETNUMMER
// =====================
if (replica.key) {
issue.customFields."Ticketnummer".value = replica.key.toString()
//syncHelper.syncBackAfterProcessing()
}
// =====================
// BEARBEITER
// =====================
def b = replica.assignee
if (b) {
def displayName = b.displayName ?: b.name ?: b.key ?: "Unbekannt"
def email = b.email
// If email is present, put it on the second line in parentheses
def assigneeText = email
? "${displayName}\n(${email})"
: displayName
issue.customFields."Ticket-Bearbeiter".value = assigneeText as String
}
// =====================
// KEY USER
// =====================
if (replica.customFields."Key User"?.value) {
def rawValue = replica.customFields."Key User".value
// Value often comes as a list with one entry for single-user pickers
def userRef = (rawValue instanceof List) ? rawValue[0] : rawValue
// userRef may be a Map with an "@key" entry, or already a resolved user-like object
def userKey = userRef instanceof Map ? userRef."@key" : userRef?.key
def resolvedUser = userKey ? nodeHelper.getUser(userKey) : null
def displayText = resolvedUser?.displayName ?: resolvedUser?.name ?: userKey ?: "Unbekannt"
issue.customFields."Key User".value = displayText as String
}
/*def keyUser = replica.customFields."Key User"?.value
def fallbackUser = nodeHelper.getUserByEmail("disp_kunde@domain.com")
if (firstSync) {
if (keyUser) {
def destinationUser = nodeHelper.getUserByEmail(keyUser.email)
issue.customFields."Key User".value = destinationUser ?: fallbackUser
} else {
issue.customFields."Key User".value = fallbackUser
}}
*/
// =====================
// SUB-TASKS
// =====================
/*if(firstSync && replica.parentId && replica.issueType.name == "Sub-task"){
issue.typeName = "Sub-task" //Make sure to use the right subtask type here.
def localParent = nodeHelper.getLocalIssueFromRemoteId(replica.parentId.toLong())
if(localParent){
issue.parentId = localParent.id
} else {
throw new com.exalate.api.exception.IssueTrackerException("Subtask cannot be created: parent issue with remote id " + replica.parentId + " was not found. Please make sure the parent issue is synchronized before resolving this error" )
}
}
*/
// =====================
// STATUS
// =====================
issue.status = replica.status
// =====================
// PRIORITÄT
// =====================
//def defaultPriority = nodeHelper.getPriority("Trivial")
issue.priority = nodeHelper.getPriority(replica.priority?.name) //?: defaultPriority
// =====================
// RESOLUTION
// =====================
issue.resolution = replica.resolution
// =====================
// REPORTER
// =====================
if (firstSync) {
def destReporter = null
// 1. Try by email
try {
destReporter = nodeHelper.getUserByEmail(replica.reporter?.email)
} catch (Exception e) {
// email hidden or not found
}
// 2. Try by key
if (destReporter == null) {
try {
destReporter = nodeHelper.getUser(replica.reporter?.key)
} catch (Exception e) {
// key not found in destination
}
}
// 3. Try by full name
if (destReporter == null) {
try {
destReporter = nodeHelper.getUserByFullName(replica.reporter?.displayName)
} catch (Exception e) {
// full name not found
}
}
// 4. Fallback to disp_kunde@domain.com if still not found
if (destReporter == null) {
try {
destReporter = nodeHelper.getUserByEmail("disp_kunde@domain.com")
} catch (Exception e) {
// fallback account not found — Exalate proxy user will be used as last resort
}
}
if (destReporter != null) {
issue.reporter = destReporter
}
}
// =====================
// ASSIGNEE
// =====================
if (firstSync) {
if (replica.assignee != null) {
def destUser = null
// 1. Try by email
try {
destUser = nodeHelper.getUserByEmail(replica.assignee?.email)
} catch (Exception e) {}
// 2. Try by key
if (destUser == null) {
try {
destUser = nodeHelper.getUser(replica.assignee?.key)
} catch (Exception e) {}
}
// 3. Try by full name
if (destUser == null) {
try {
destUser = nodeHelper.getUserByFullName(replica.assignee?.displayName)
} catch (Exception e) {}
}
// 4. Try to assign — if user has assignable permission, assign; else leave as is (unassigned or current assignee)
if (destUser != null) {
try {
if (nodeHelper.isUserAssignable(issue.projectKey, destUser)) {
issue.assignee = destUser
} // else: leave as is (unassigned or current assignee)
} catch (Exception e) {
// Suppress error, leave as is
}
} // else: leave as is (unassigned or current assignee)
}
}
// =====================
// CUSTOM FIELDS
// =====================
issue.customFields."Gewünschtes Lieferdatum".value = replica.customFields."Gewünschtes Lieferdatum"?.value
issue.customFields."Externe Vorgangsnummer".value = replica.customFields."Externe Vorgangsnummer"?.value
issue.customFields."Auftragsklarheit".value = replica.customFields."Auftragsklarheit"?.value?.value?.toString()
issue.customFields."Releasemanagement".value = replica.customFields."Releasemanagement".value
// =====================
// ERHÖHT SUPPORTGRUNDLAGE
// =====================
if (replica.customFields."Erhöht Supportgrundlage"?.value?.value) {
issue.customFields."Erhöht Supportgrundlage".value =
replica.customFields."Erhöht Supportgrundlage".value.value.toString()
}
// =====================
// ERHÖHT SUPPORTGRUNDLAGE AB
// =====================
if(replica.customFields."Erhöht Supportgrundlage ab"){
issue.customFields."Erhöht Supportgrundlage ab".value = replica.customFields."Erhöht Supportgrundlage ab".value
}
// =====================
// AUFWAND
// =====================
def formatAufwand = { rawValue ->
if (rawValue == null) return null
def d = rawValue.toDouble()
def germanFormat = new java.text.DecimalFormat("#.##", new java.text.DecimalFormatSymbols(Locale.GERMANY))
germanFormat.setGroupingUsed(false) // avoid thousands separator dots, e.g. "1.234,25"
return germanFormat.format(d)
}
if (replica.customFields."Aufwand (angefragt)") {
def rawValue = replica.customFields."Aufwand (angefragt)".value
issue.customFields."Aufwand (angefragt)".value = formatAufwand(rawValue)
}
if (replica.customFields."Aufwand (Std.)") {
def rawValue = replica.customFields."Aufwand (Std.)".value
issue.customFields."Aufwand (Std.)".value = formatAufwand(rawValue)
}
// =====================
// ANSPRECHPARTNER
// =====================
if (replica.customFields."Ansprechpartner"?.value) {
def rawValue = replica.customFields."Ansprechpartner".value
// Value often comes as a list with one entry for single-user pickers
def userRef = (rawValue instanceof List) ? rawValue[0] : rawValue
// userRef may be a Map with an "@key" entry, or already a resolved user-like object
def userKey = userRef instanceof Map ? userRef."@key" : userRef?.key
def resolvedUser = userKey ? nodeHelper.getUser(userKey) : null
def displayText = resolvedUser?.displayName ?: resolvedUser?.name ?: userKey ?: "Unbekannt"
issue.customFields."Ansprechpartner".value = displayText as String
}
// =====================
// ORIGINAL ERSTELLUNGSDATUM
// =====================
issue.customFields."Erstellungsdatum".value = replica.created?.toString() // Original Erstellungsdatum
// =====================
// ORIGINAL ÄNDERUNGSDATUM
// =====================
issue.customFields."Änderungsdatum".value = replica.updated?.toString()
// =====================
// KUNDE BEARBEITER
// =====================
// This will override the above if present
// Only assign if user is assignable, else leave as is
/*
def assigneeDisplayName = replica.customFields."Externer Bearbeiter (Kunde)"?.value?.asString
if (assigneeDisplayName) {
def assignee = nodeHelper.getUserByFullName(assigneeDisplayName)
if (assignee && nodeHelper.isUserAssignable(issue.projectKey, assignee)) {
issue.assignee = assignee
} // else: leave as is
}
*/
// =====================
// FIX VERSIONS
// =====================
def fixVersionNames = replica.fixVersions?.collect { it.name }?.join(", ") ?: ""
issue.customFields."Lösungsversion (Fix Version)".value = fixVersionNames
// =====================
// ATTACHMENTS + UPLOAD COMMENT
// =====================
def dateFormat = new java.text.SimpleDateFormat("dd.MM.yyyy HH:mm")
dateFormat.setTimeZone(java.util.TimeZone.getTimeZone("Europe/Vienna"))
// Snapshot existing filenames before merge
def existingNames = issue.attachments?.collect { it.filename }?.toSet() ?:
as Set
// Merge attachments
issue.attachments = attachmentHelper.mergeAttachments(issue, replica)
// Only add upload comments after the first sync
if (!firstSync) {
def newAttachments = issue.attachments?.findAll { a ->
!existingNames.contains(a.filename)
} ?:
newAttachments.each { a ->
def uploaderName = a.author?.displayName ?: replica.reporter?.displayName ?: "Unknown User"
def uploadDate = a.created ? dateFormat.format(a.created) : "unbekanntes Datum"
def comment = new com.exalate.basic.domain.hubobject.v1.BasicHubComment()
comment.body = "${uploaderName} hat am ${uploadDate} ein Dokument hochgeladen: ${a.filename}"
comment.author = a.author
comment.internal = false
comment.restrictSync = true // never sync this comment to the other side
issue.comments << comment
}
}
// =====================================================
// COMMENTS (Merge + Format)
// =====================================================
if (replica.comments) {
def dateFormatter = new java.text.SimpleDateFormat("dd.MM.yyyy HH:mm")
dateFormatter.setTimeZone(java.util.TimeZone.getTimeZone("Europe/Vienna"))
def formatDate = { rawDate ->
if (!rawDate) return ""
try {
if (rawDate instanceof java.util.Date) {
return dateFormatter.format(rawDate)
}
return dateFormatter.format(new java.util.Date(rawDate.toString().isLong() ? rawDate.toString().toLong() : java.sql.Timestamp.valueOf(rawDate.toString()).time))
} catch (Exception e) {
return rawDate.toString()
}
}
issue.comments = commentHelper.mergeComments(issue, replica, { comment ->
if ((comment.body ?: "").contains("uploaded attachment:")) {
return null
}
def email = comment.author?.email
def localUser = email ? nodeHelper.getUserByEmail(email) : null
def authorName = comment.author?.displayName ?: comment.author?.name ?: "Jemand"
def commentDate = formatDate(comment.created)
def prefix = "${authorName} hat am ${commentDate} kommentiert:"
if (!(comment.body ?: "").startsWith("${authorName} hat")) {
comment.body = "${prefix}\n${comment.body ?: ""}\n"
}
return comment
})
}
When syncing from Source issues with lots (35+) Issue Links, the Destination throws “Issue Tracker Error”, but “Resolve all” for the error populates the Destination, until the error comes again, and then we need to ignore it.
When updating the same issue, the same scenario, but then we need to ignore the error => not Field updates happening.
In a earlier script we had at the end, for the comments this return statement which might have caused the problem.
if (!replica.comments) {
return
}
But its removed now - see end of attached script
// ===================================================== // COMMENTS (Merge + Format) // =====================================================
.............
Any Ideas anybody ? we hit a dead end…
Thanks & BR