import org.slf4j.Logger import com.exalate.basic.domain.hubobject.v1.BasicHubIssue /** Usage: Add the snippet below to the end of your "Outgoing sync": SubTask.send() -------------------------------- Add the snippet below to the end of your "Incoming sync": SubTask.receive() -------------------------------- * */ class SubTask { static log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.SubTask") static com.exalate.basic.domain.hubobject.v1.BasicHubIssue send() { final def nservInternal = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) final def im = com.atlassian.jira.component.ComponentAccessor.getIssueManager() final def stm = com.atlassian.jira.component.ComponentAccessor.getSubTaskManager() def context = null if (com.exalate.processor.jira.JiraCreateReplicaProcessor.dataFilterContext != null && com.exalate.processor.jira.JiraCreateReplicaProcessor.dataFilterContext.get() == true) { context = com.exalate.processor.jira.JiraCreateReplicaProcessor.threadLocalContext.get() } else { throw new com.exalate.api.exception.IssueTrackerException(""" No context for executing external script SubTask.groovy. Please contact Exalate Support.""".toString()) } final def replica = context.replica final def issue = context.issue def getChildren = { Long parentIssueId -> def parentIssue = im.getIssueObject(parentIssueId) if (parentIssue == null) { return [] } def children = stm.getSubTaskObjects(parentIssue) return children.collect { story -> [ "id" : story.id, "key": story.key ] } } replica.id = issue.id replica.parentId = issue.parentId if (issue.parentId != null) { def localIssueKey = nservInternal.getLocalKey(issue.parentId as Long)?.URN replica.customKeys."parentContext" = ["key": localIssueKey] replica.customKeys."subTaskContext" = [ "parent" : [ "id" : issue.parentId as Long, "key": localIssueKey ], "children": getChildren(issue.parentId as Long) ] log.debug("Sending parentContext`" + replica.customKeys."parentContext" + "`") log.debug("Sending subTaskContext`" + replica.customKeys."subTaskContext" + "`") } else { def children = getChildren(issue.id as Long) if (children != null && !children.empty) { replica.customKeys."subTaskContext" = [ "parent" : [ "id" : issue.id as Long, "key": issue.key ], "children": children ] } log.debug("Sending subTaskContext`" + replica.customKeys."subTaskContext" + "`") } replica } static BasicHubIssue receive() { def context = null def remoteReplica = null if (com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext != null && com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext.get() == true) { context = com.exalate.processor.jira.JiraCreateIssueProcessor.threadLocalContext.get() remoteReplica = context.remoteReplica } else if (com.exalate.processor.jira.JiraChangeIssueProcessor.changeProcessorContext != null && com.exalate.processor.jira.JiraChangeIssueProcessor.changeProcessorContext.get() == true) { context = com.exalate.processor.jira.JiraChangeIssueProcessor.threadLocalContext.get() remoteReplica = context.currentReplica } else { throw new com.exalate.api.exception.IssueTrackerException(""" No context for executing external script SubTask.groovy. Please contact Exalate Support.""".toString()) } final def replica = context.replica final def issue = context.issue final def nodeHelper = context.nodeHelper final def connection = context.connection final def syncRequest = context.syncRequest final def issueBeforeScript = context.issueBeforeScript final def traces = context.traces final def blobMetadataList = context.blobMetadataList def projectKey = issue.project?.key ?: issue.projectKey def issueType = issue.type ?: ({ nodeHelper.getIssueType(issue.typeName) })() if (projectKey == null) { throw new com.exalate.api.exception.IssueTrackerException(""" Project key is not found. Please fill issue.projectKey or issue.project parameter in script """.toString()) } if (issueType == null) { throw new com.exalate.api.exception.IssueTrackerException(""" Issue type is not found. Please fill issue.typeName or issue.type parameter in script """.toString()) } receiveBeforeCreation(replica, issue, nodeHelper) if(issue.id != null){ receiveAfterCreation(replica, issue, nodeHelper) ProgressLog.logIssueChange("CREATED ISSUE", replica) return issue } CreateIssue.create( replica, issue, syncRequest, nodeHelper, issueBeforeScript, remoteReplica, traces, blobMetadataList) { // the issue has been created do any crazy things you would normally do after the issue has been created receiveAfterCreation(replica, issue, nodeHelper) // Log sucessful creation of issue ProgressLog.logIssueChange("CREATED ISSUE", replica) return null } return issue; } static com.exalate.basic.domain.hubobject.v1.BasicHubIssue receiveBeforeCreation(com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper) { log.debug("Executing receiveBeforeCreation, subTaskContext is " + replica.customKeys."subTaskContext") if (replica.parentId != null) { log.debug("Remote issue `" + replica.key + "` is a sub-task of `" + replica.parentId + "`") def parentIssue = ExalateApi.getLocalIssueFromRemoteId(replica.parentId as Long, nodeHelper) if (parentIssue == null) { log.debug("Can not find parent issue `" + replica.customKeys."parentContext"?.key + "` (" + replica.parentId + "). This issue can not be created until the parent issue is successfully synchronized.") return issue } def parentIssueId = parentIssue.id as Long log.debug("Local parent issue found `" + parentIssue.key + "` (" + parentIssueId + ")") issue.parentId = parentIssueId as String } issue } static com.exalate.basic.domain.hubobject.v1.BasicHubIssue receiveAfterCreation(com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper) { log.debug("Executing receiveAfterCreation, subTaskContext is " + replica.customKeys."subTaskContext") if (replica.customKeys."subTaskContext" == null) { return issue } final def im = com.atlassian.jira.component.ComponentAccessor.getIssueManager() def createdJiraIssue = im.getIssueObject(issue.id as Long) if (createdJiraIssue == null) { def jiraHome = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.config.util.JiraHome.class) throw new com.exalate.api.exception.IssueTrackerException(""" It seems, that the issue has not been created while running SubTask.groovy. Please contact Exalate Support.""".toString()) } def getLocalIssue = { remoteSuspectId, jiraIssue -> if (replica.id.equals((remoteSuspectId as Long) as String)) { return jiraIssue } def localHubIssue = ExalateApi.getLocalIssueFromRemoteId(remoteSuspectId as Long, nodeHelper) if (localHubIssue == null) { return null } return im.getIssueObject(localHubIssue.id as Long) } def getLocalParentIssue = { jiraIssue -> def subTaskContext = replica.customKeys."subTaskContext" if (subTaskContext == null) { return null } def remoteParentId = subTaskContext.parent.id getLocalIssue(remoteParentId, jiraIssue) } // try to link all the children to the parent: def localParentJissue = getLocalParentIssue(createdJiraIssue) log.debug("Local parent issue is " + localParentJissue) if (localParentJissue != null) { final def stm = com.atlassian.jira.component.ComponentAccessor.getSubTaskManager() final def proxyUser = LogIn.getProxyUser() log.debug("Starting to proceed children `" + replica .customKeys ."subTaskContext" .children + "`") LogIn.tryLogInFinallyLogOut { replica .customKeys ."subTaskContext" .children .collect { child -> getLocalIssue(child.id, createdJiraIssue) } // find all synced children .findAll() // find all synced children which have no parent id set .findAll { child -> child.parentId == null } .each { child -> log.debug("linking the child `" + child.key + "` to parent `" + localParentJissue.key + "` for the issue `" + createdJiraIssue.key + "` for remote issue `" + replica.key + "`") def mi = (child as com.atlassian.jira.issue.MutableIssue) mi.setParentObject(localParentJissue) try{ im .updateIssue( proxyUser, mi, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_UPDATED, true) stm.createSubTaskIssueLink(localParentJissue, child, proxyUser) } catch (groovy.lang.MissingMethodException ignore) { im .updateIssue( proxyUser.getDirectoryUser(), mi, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_UPDATED, true) stm.createSubTaskIssueLink(localParentJissue, child, proxyUser.getDirectoryUser()) } } } } issue } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~CREATE ISSUE EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ static class CreateIssue { private static def log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.SubTask") private static def doCreate = { com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.api.domain.request.ISyncRequest syncRequest, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issueBeforeScript, com.exalate.api.domain.INonPersistentReplica remoteReplica, List traces, List blobMetadataList, Logger log -> def firstSync = com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext.get() == true def issueLevelError = { String msg -> new com.exalate.api.exception.IssueTrackerException(msg) } def issueLevelError2 = { String msg, Throwable c -> new com.exalate.api.exception.IssueTrackerException(msg, c) } def toExIssueKey = { com.atlassian.jira.issue.MutableIssue i -> new com.exalate.basic.domain.BasicIssueKey(i.id, i.key) } final def authCtxInternal = com.atlassian.jira.component.ComponentAccessor.getJiraAuthenticationContext() final def imInternal = com.atlassian.jira.component.ComponentAccessor.issueManager final def umInternal = com.atlassian.jira.component.ComponentAccessor.userManager final def nservInternal = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) final def hohfInternal2 = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.hubobject.IHubObjectHelperFactory.class) //noinspection GroovyAssignabilityCheck final def hohInternal2 = hohfInternal2.get(remoteReplica.payload.version) if (issue.id != null) { def existingIssue = imInternal.getIssueObject(issue.id as Long) if (existingIssue != null) { return [existingIssue, toExIssueKey(existingIssue)] } } def proxyAppUserInternal = nservInternal.getProxyUser() def loggedInUser = authCtxInternal.getLoggedInUser() log.debug("Logged user is " + loggedInUser) def reporterAppUser = null if (issue.reporter != null) { reporterAppUser = umInternal.getUserByKey(issue.reporter?.key) } reporterAppUser = reporterAppUser ?: proxyAppUserInternal issue.project = issue.project ?: ({ nodeHelper.getProject(issue.projectKey) })() issue.type = issue.type ?: ({ nodeHelper.getIssueType(issue.typeName) })() def jIssueInternal = null try { LogIn.logIn(reporterAppUser) if (issue.id != null) { def existingIssue = imInternal.getIssueObject(issue.id as Long) if (existingIssue != null) { issue.id = existingIssue.id issue.key = existingIssue.key return [existingIssue, toExIssueKey(existingIssue)] } } def cir = null try{ cir = hohInternal2.createNodeIssueWith(issue, hohInternal2.createHubIssueTemplate(), null, [:], blobMetadataList, syncRequest) } catch (MissingMethodException e){ cir = hohInternal2.createNodeIssueWith(issue, hohInternal2.createHubIssueTemplate(), null, [:], blobMetadataList, syncRequest.getConnection()) } def createdIssueKey = cir.getIssueKey(); jIssueInternal = imInternal.getIssueObject(createdIssueKey.id) if (issue.id != null) { def oldIssueKey = jIssueInternal.key def oldIssueId = jIssueInternal.id try { jIssueInternal.key = issue.key jIssueInternal.store() } catch (Exception e) { log.error("""Failed to sync issue key: ${e.message}. Please contact Exalate Support. Deleting issue `$oldIssueKey` ($oldIssueId)""".toString(), e) imInternal.deleteIssue(proxyAppUserInternal, jIssueInternal as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } } issue.id = jIssueInternal.id issue.key = jIssueInternal.key return [jIssueInternal, toExIssueKey(jIssueInternal)] } catch (com.exalate.api.exception.TransitionException te) { if (firstSync && te.issueKey && te.issueKey.id) { def jIssueInternal2 = imInternal.getIssueObject(te.issueKey.id) imInternal.deleteIssue(proxyAppUserInternal, jIssueInternal2 as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } throw te } catch (com.exalate.api.exception.IssueTrackerException ite) { if (firstSync && jIssueInternal != null) { imInternal.deleteIssue(proxyAppUserInternal, jIssueInternal as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } throw ite } catch (Exception e) { if (firstSync && jIssueInternal != null) { imInternal.deleteIssue(proxyAppUserInternal, jIssueInternal as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } throw issueLevelError2("""Failed to create issue: ${ e.message }. Please review the script or contact Exalate Support""".toString(), e) } finally { LogIn.logIn(loggedInUser) } } /** * @param whenIssueCreatedFn - a callback closure executed after the issue has been created * */ static com.exalate.basic.domain.BasicIssueKey create( com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.api.domain.request.ISyncRequest syncRequest, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issueBeforeScript, com.exalate.api.domain.INonPersistentReplica remoteReplica, List traces, List blobMetadataList, Closure whenIssueCreatedFn) { def firstSync = com.exalate.processor.jira.JiraCreateIssueProcessor.createProcessorContext.get() == true def (_jIssue, _exIssueKey) = doCreate(replica, issue, syncRequest, nodeHelper, issueBeforeScript, remoteReplica, traces, blobMetadataList, log) com.atlassian.jira.issue.MutableIssue jIssue = _jIssue as com.atlassian.jira.issue.MutableIssue com.exalate.basic.domain.BasicIssueKey exIssueKey = _exIssueKey as com.exalate.basic.domain.BasicIssueKey try { whenIssueCreatedFn() UpdateIssue.update(replica, issue, syncRequest, nodeHelper, issueBeforeScript, traces, blobMetadataList, jIssue, exIssueKey) } catch (Exception e3) { final def imInternal = com.atlassian.jira.component.ComponentAccessor.issueManager final def nservInternal2 = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) def proxyAppUserInternal = nservInternal2.getProxyUser() if (firstSync && _jIssue != null) { imInternal.deleteIssue(proxyAppUserInternal, _jIssue as com.atlassian.jira.issue.Issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_DELETED, false) } throw e3 } return exIssueKey } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~UPDATE ISSUE EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ static class UpdateIssue { private static def log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.SubTask") private static def doUpdate = { com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.api.domain.request.ISyncRequest syncRequest, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issueBeforeScript, List traces, List blobMetadataList, com.atlassian.jira.issue.MutableIssue jIssue, com.exalate.basic.domain.BasicIssueKey exIssueKey -> try { final def hohfInternal2 = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.hubobject.IHubObjectHelperFactory.class) //noinspection GroovyAssignabilityCheck final def hohInternal2 = hohfInternal2.get("1.2.0") final def nservInternal2 = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) def proxyAppUserInternal2 = nservInternal2.getProxyUser() log.info("performing the update for the issue `" + jIssue.key + "` for remote issue `" + replica.key + "`") //finally create all def fakeTraces2 = com.exalate.util.TraceUtils.indexFakeTraces(traces) def preparedIssue2 = hohInternal2.prepareLocalHubIssueForApplication(issueBeforeScript, issue, fakeTraces2) //@Nonnull IIssueKey issueKey, @Nonnull IHubIssueReplica hubIssueAfterScripts, @Nullable String proxyUser, @Nonnull IHubIssueReplica hubIssueBeforeScripts, @Nonnull Map> traces, @Nonnull List blobMetadataList, IRelation relation def resultTraces2 = null try{ resultTraces2 = hohInternal2.updateNodeIssueWith(exIssueKey, preparedIssue2, proxyAppUserInternal2.key, issueBeforeScript, fakeTraces2, blobMetadataList, syncRequest) } catch (MissingMethodException e){ resultTraces2 = hohInternal2.updateNodeIssueWith(exIssueKey, preparedIssue2, proxyAppUserInternal2.key, issueBeforeScript, fakeTraces2, blobMetadataList, syncRequest.getConnection()) } traces.clear() traces.addAll(resultTraces2 ?: []) new Result(issue, traces) } catch (com.exalate.api.exception.IssueTrackerException ite2) { throw ite2 } catch (Exception e2) { throw new com.exalate.api.exception.IssueTrackerException(e2.message, e2) } } static Result update(com.exalate.basic.domain.hubobject.v1.BasicHubIssue replica, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, com.exalate.api.domain.request.ISyncRequest syncRequest, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper, com.exalate.basic.domain.hubobject.v1.BasicHubIssue issueBeforeScript, List traces, List blobMetadataList, com.atlassian.jira.issue.MutableIssue jIssue, com.exalate.basic.domain.BasicIssueKey exIssueKey) { doUpdate(replica, issue, syncRequest, nodeHelper, issueBeforeScript, traces, blobMetadataList, jIssue, exIssueKey) } static class Result { com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue List traces Result(com.exalate.basic.domain.hubobject.v1.BasicHubIssue issue, java.util.List traces) { this.issue = issue this.traces = traces } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~LOG IN EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private static class LogIn { static logIn(u) { def authCtx = com.atlassian.jira.component.ComponentAccessor.getJiraAuthenticationContext() try { //Jira 7 authCtx.setLoggedInUser(u) } catch (Exception ignore) { // Jira 6 //noinspection GroovyAssignabilityCheck authCtx.setLoggedInUser(u.getDirectoryUser()) } } static R tryLogInFinallyLogOut(Closure fn) { def authCtx = com.atlassian.jira.component.ComponentAccessor.getJiraAuthenticationContext() def proxyAppUser = getProxyUser() def loggedInUser = authCtx.getLoggedInUser() try { logIn(proxyAppUser) fn() } finally { logIn(loggedInUser) } } static getProxyUser() { def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) nserv.proxyUser } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~PROGRESS LOG EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private static class ProgressLog { private static progresslog = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.SubTask") static void logIssueChange(String event, BasicHubIssue issue) { def created_by = issue.creator?.getUsername() def key = issue.getKey() progresslog.trace("${event}: '$key' '$created_by'") } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~EXALATE API EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private static class ExalateApi { static com.exalate.basic.domain.hubobject.v1.BasicHubIssue getLocalIssueFromRemoteId(remoteIssueId, com.exalate.node.hubobject.v1_3.NodeHelper nodeHelper) { if (remoteIssueId == null) { return null } def im = com.atlassian.jira.component.ComponentAccessor.getIssueManager() def tt = nodeHelper.twinTraceRepository.getTwinTraceByRemoteIssueId(nodeHelper.connection, (remoteIssueId as String)) //SubTaskSync.log.debug("Trying to get issue object by id `" + tt.localReplica.issueKey.id + "`") def _jIssue = tt != null && tt.localReplica != null ? im.getIssueObject(tt.localReplica.issueKey.id as Long) : null if (_jIssue == null) { SubTask.log.debug("Issue Object for remote issue id '" + remoteIssueId + "' is null") return null } def localIssue = nodeHelper.nodeHubObjectConverter.getHubIssue(_jIssue) if (localIssue == null) { SubTask.log.debug("Hub Issue for remote issue id '" + remoteIssueId + "' is null") return null } return localIssue } } }