import com.exalate.api.hubobject.jira.INodeHelper import com.exalate.basic.domain.hubobject.v1.BasicHubIssue import org.slf4j.Logger /** Usage: Add the snippet below to the end of your "Outgoing sync": Status.send() -------------------------------- Add the snippet below to the end of your "Incoming sync": Status.receive( workflowMapping = [ "Source Status A" : "Dest Status A", "Source Status B" : "Dest Status B", "Source Status C" : "Dest Status C", ] ) -------------------------------- * */ class Status { private static def log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.Status") static com.exalate.basic.domain.hubobject.v1.BasicHubIssue send() { sendStatus() } static com.exalate.basic.domain.hubobject.v1.BasicHubIssue receive(useRemoteStatusByDefault = true, Map workflowMapping = [:], Map resolutionMapping = [:]) { receiveStatus(useRemoteStatusByDefault, workflowMapping, resolutionMapping) } static com.exalate.basic.domain.hubobject.v1.BasicHubIssue sendStatus() { 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 Status.groovy. Please contact Exalate Support.""".toString()) } final BasicHubIssue replica = context.replica final BasicHubIssue issue = context.issue replica.status = issue.status replica.resolution = issue.resolution replica } static com.exalate.basic.domain.hubobject.v1.BasicHubIssue receiveStatus(boolean useRemoteStatusByDefault, Map workflowMapping, Map resolutionMapping) { def context = null def remoteReplica = null if (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.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 Status.groovy. Please contact Exalate Support.""".toString()) } final BasicHubIssue replica = context.replica final BasicHubIssue issue = context.issue final INodeHelper 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()) } if(issue.id != null){ receiveStatusAfterCreateIssue(useRemoteStatusByDefault, workflowMapping, resolutionMapping, replica, issue, nodeHelper) return issue } CreateIssue.create( replica, issue, syncRequest, nodeHelper, issueBeforeScript, remoteReplica, traces, blobMetadataList) { receiveStatusAfterCreateIssue(useRemoteStatusByDefault, workflowMapping, resolutionMapping, replica, issue, nodeHelper) return null } issue } static com.exalate.basic.domain.hubobject.v1.BasicHubIssue setResolution(Map resolutionMapping, BasicHubIssue replica, BasicHubIssue issue, INodeHelper nodeHelper) { if (replica.resolution != null && issue.resolution == null) { // the remote issue is resolved, but the local isn't - look up the correct local resolution object. // use 'replica.resolution.name' as resolution if the remote resolution is not found def targetResolutionName = resolutionMapping[replica.resolution.name] ?: replica.resolution.name // nodeHelper.getResolution looks up the local resolution object based on the provided name def targetResolution = nodeHelper.getResolution(targetResolutionName) if(targetResolution != null) { issue.resolution = targetResolution issue } else { throw new com.exalate.api.exception.IssueTrackerException( "Resolution with name " + targetResolutionName + " was not found." + " Please open your sync rules and edit resolutionMapping parameter 'Status.receive(statusMapping, resolutionMapping)'." ) } } } static com.exalate.basic.domain.hubobject.v1.BasicHubIssue receiveStatusAfterCreateIssue(boolean useRemoteStatusByDefault, Map workflowMapping, Map resolutionMapping, BasicHubIssue replica, BasicHubIssue issue, INodeHelper nodeHelper) { setResolution(resolutionMapping, replica, issue, nodeHelper) def issueLevelError = { String msg -> new com.exalate.api.exception.IssueTrackerException(msg) } final def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class) final def cfm = com.atlassian.jira.component.ComponentAccessor.customFieldManager final def optm = com.atlassian.jira.component.ComponentAccessor.optionsManager final def imInternal = com.atlassian.jira.component.ComponentAccessor.issueManager com.atlassian.jira.issue.MutableIssue jIssueInternal = imInternal.getIssueObject(issue.id as Long) if (jIssueInternal == null) { throw issueLevelError(""" It seems, that the issue has not been created yet. Please change you create processor to create the issue and populate the `issue.id` property before using the `StatusSync.receiveStatus(...)` """.toString()) } com.exalate.basic.domain.BasicIssueKey exIssueKeyInternal = nserv.getLocalKey(issue.id as Long) as com.exalate.basic.domain.BasicIssueKey final def wfmInternal = com.atlassian.jira.component.ComponentAccessor.workflowManager final def authCtxInternal = com.atlassian.jira.component.ComponentAccessor.getJiraAuthenticationContext() final def umInternal = com.atlassian.jira.component.ComponentAccessor.userManager final def isInternal = com.atlassian.jira.component.ComponentAccessor.issueService def proxyAppUser = nserv.getProxyUser() def wfInternal = wfmInternal.getWorkflow(jIssueInternal) def resultToStatus = { com.opensymphony.workflow.loader.ResultDescriptor rd, com.atlassian.jira.workflow.JiraWorkflow wf -> def step = wf.descriptor.getStep(rd.step) if (step == null) { return null } wf.getLinkedStatus(step) } def getWorkflow = { def globalTsInternal = wfInternal.allActions .findAll { tInternal -> wfInternal.isGlobalAction(tInternal) } .collect { tInternal -> def toInternal = resultToStatus(tInternal.unconditionalResult, wfInternal) if (toInternal == null) { return null } [ "id" : tInternal.id as String, "name" : tInternal.name as String, "to" : [ "id" : toInternal.id as String, "name" : toInternal.name as String ], "global": true ] } .findAll {it != null} def getTransitionsInternal = { com.opensymphony.workflow.loader.StepDescriptor fromStepInternal -> def fromInternal = wfInternal.getLinkedStatus(fromStepInternal) def tsFromStepInternal = fromStepInternal.actions.collect { com.opensymphony.workflow.loader.ActionDescriptor tInternal -> def toInternal = resultToStatus(tInternal.unconditionalResult, wfInternal) if (toInternal == null) { return null } [ "id": tInternal.id, "name" : tInternal.name, "from" : [ "id" : fromInternal.id, "name" : fromInternal.name, ], "to" : [ "id" : toInternal.id, "name" : toInternal.name, ], "global": wfInternal.isGlobalAction(tInternal) ] } .findAll { it != null } tsFromStepInternal } def tsInternal = wfInternal.descriptor .steps .collect(getTransitionsInternal) .flatten() // find unique transitions .inject([] as List>) { List> result, Map tInternal -> def existingTInternal = result.find { Map candidateInternal -> ((candidateInternal.id as Long) == (tInternal.id as Long)) } if (existingTInternal == null) { result += tInternal } result } [ "name" : wfInternal.name, "transitions" : tsInternal + globalTsInternal, "steps" : wfInternal.descriptor .steps .collect { com.opensymphony.workflow.loader.StepDescriptor fromStepInternal -> def fromInternal = wfInternal.getLinkedStatus(fromStepInternal) def stepTsInternal = getTransitionsInternal(fromStepInternal) [ "id" : fromInternal.id, "name" : fromInternal.name, "transitions" : stepTsInternal ] } ] } // def validateValidator = { com.atlassian.jira.workflow.JiraWorkflow wf, com.opensymphony.workflow.loader.ActionDescriptor t, v -> // if (!(v instanceof com.opensymphony.workflow.loader.ValidatorDescriptor)) { // throw new com.exalate.api.exception.IssueTrackerException( // "Please review the Create processor: " + // "Failed to read descriptor for validator `"+ v + "` of transition `"+ t.name +"` ("+ t.id +") from work flow `"+ wf.name +"`." // ) // } // v as com.opensymphony.workflow.loader.ValidatorDescriptor // } // def isCustomFieldRequiredFn = { com.atlassian.jira.workflow.JiraWorkflow wf, com.opensymphony.workflow.loader.ActionDescriptor t, String projectKey, String issueTypeName -> // { v -> // def vd = validateValidator(wf, t, v) // // vd.type == "class" && // vd.args."class.name" == "com.googlecode.jsu.workflow.validator.FieldsRequiredValidator" && // (vd.args."hidFieldsList" =~ /customfield_(\d+)@@/) // } // } // def isAssigneeRequiredFn = { com.atlassian.jira.workflow.JiraWorkflow wf, com.opensymphony.workflow.loader.ActionDescriptor t -> // { v -> // def vd = validateValidator(wf, t, v) // // vd.type == "class" && // vd.args."class.name" == "com.googlecode.jsu.workflow.validator.FieldsRequiredValidator" && // (vd.args."hidFieldsList" == "assignee@@") // } // } def transition = { com.opensymphony.workflow.loader.ActionDescriptor t, String transitionExecUserKey, com.atlassian.jira.issue.IssueInputParameters issueInputParams, com.atlassian.jira.workflow.JiraWorkflow wf -> def transitionName = t.name def loggedInUser = authCtxInternal.getLoggedInUser() try { def execAppUser = umInternal.getUserByKey(transitionExecUserKey) ?: loggedInUser LogIn.logIn(execAppUser) def inputParams = issueInputParams ?: new com.atlassian.jira.issue.IssueInputParametersImpl() // t.validators // .findAll(isCustomFieldRequiredFn(wf, t, issue.project?.key ?: issue.projectKey, issue.type?.name ?: issue.typeName)) // .each { v -> // def vd = validateValidator(wf, t, v) // def match = (vd.args."hidFieldsList" =~ /customfield_(\d+)@@/) // if (!(match.hasGroup() && match.size() == 1)) { // //FIXME: the validator requires a field, but it's not a custom field // return // } // def firstGroup = match[0] as List; // if (firstGroup.size() != 2) { // //FIXME: the validator requires a field, but it's not a custom field // return // } // def cfId = (firstGroup[1] as Long) // def cf = cfm.getCustomFieldObject(cfId) // if (cf == null) { // return // } // // def cft = cf.customFieldType // if (cft.getKey() == "com.atlassian.jira.plugin.system.customfieldtypes:select") { // // if the custom field is a select list, take the remote custom field value or the first value as default // // def localOptionId = ({ // def localCfHasBeenSet = issue.customFields.keySet().contains(cf.name) // def localCf = issue.customFields[cf.name] // if (localCf == null) { // //TODO figure out how to react - there is no such custom field! // return null // } // // def result = null // if (localCf.value == null) { // //TODO the value has not been set on the issue // } else { // result = localCf.value.id // } // if (!localCfHasBeenSet) { // issue.customFields.remove(cf.name) // } // result // })() // def resultOptId = localOptionId ?: ({ // def opts = optm.getOptions(cf.getRelevantConfig(jIssueInternal)) // def optList = opts.collect() // optList.find().optionId // })() // if (resultOptId == null) { // // TODO figure out how to react: there is no values in the custom field - add a comment ? // return // } // inputParams = inputParams.addCustomFieldValue(cfId, resultOptId as String) // } // } // // t.validators // .findAll(isAssigneeRequiredFn(wf, t)) // .each { v -> // if (issue.assignee == null) { // // TODO: assignee has not been set, although it's required // return // } // inputParams = inputParams.setAssigneeId(issue.assignee.username) // } com.atlassian.jira.bc.issue.IssueService.TransitionValidationResult tvr = isInternal.validateTransition( execAppUser, exIssueKeyInternal.id, t.id, inputParams, new com.atlassian.jira.workflow.TransitionOptions(true, true, true, false) ) if (!tvr.valid) { def customFieldInvalidValueErrorEntry = tvr.errorCollection.errors.find { k, v -> k.matches("^customfield_(\\d+)\$") && v.matches("^Invalid value '(\\d+)' passed to custom field '(.+)'\$") } if (customFieldInvalidValueErrorEntry) { def customFieldInvalidValueErrorCustomFieldKeyStr = customFieldInvalidValueErrorEntry?.key def customFieldInvalidValueErrorStr = customFieldInvalidValueErrorEntry?.value def cfMatcher = (customFieldInvalidValueErrorCustomFieldKeyStr =~ "^customfield_(\\d+)\$") def optionMatcher = (customFieldInvalidValueErrorStr =~ "^Invalid value '(\\d+)' passed to custom field '(.+)'\$") if (cfMatcher.size() > 0 && cfMatcher[0] instanceof Iterable && (cfMatcher[0] as List).size() >= 2 && optionMatcher.size() > 0 && optionMatcher[0] instanceof Iterable && (optionMatcher[0] as List).size() >= 3) { def cfId = (cfMatcher[0] as List)[1] as Long def optionId = (optionMatcher[0] as List)[1] as Long def cfName = (optionMatcher[0] as List)[2] as String def cf = cfm.getCustomFieldObject(cfId) def relevantConfig = cf?.getRelevantConfig(jIssueInternal) if (cf && relevantConfig) { def opts = optm.getOptions(relevantConfig) def optList = opts ? opts.collect() : [] def optionValueStr = optList.find { it.optionId == optionId }?.value throw new com.exalate.api.exception.IssueTrackerException( "Validation failed to transition "+ jIssueInternal.key + " via transition "+ transitionName + " as user "+ transitionExecUserKey + " because option `${optionValueStr}` (${optionId}) can not be selected for custom field ${cfName}".toString() + " due to: "+ tvr.errorCollection + "\nthe reasons were: "+ tvr.errorCollection.reasons ) } } } else { throw new com.exalate.api.exception.IssueTrackerException( "Validation failed to transition "+ jIssueInternal.key + " via transition "+ transitionName + " as user "+ transitionExecUserKey + " due to: "+ tvr.errorCollection + "\nthe reasons were: "+ tvr.errorCollection.reasons ) } } def tr = null try { try{ com.exalate.replication.in.RequestWorker.exalateTransitionChange.set(true) } catch(e){ //Ignore, previous to 4.6.8 } tr = isInternal.transition(execAppUser, tvr) } finally { try { com.exalate.replication.in.RequestWorker.exalateTransitionChange.set(false) } catch(e) { //Ignore, previous to 4.6.8 } } if (!tr || !tr.valid) { throw new com.exalate.api.exception.IssueTrackerException( "Failed to transition "+ jIssueInternal.key + " via transition "+ transitionName + " as user "+ transitionExecUserKey + " due to: "+ tr.errorCollection + "\nthe reasons were: "+ tr.errorCollection.reasons ) } def _IssueChangedEventImplClazz = null try { _IssueChangedEventImplClazz = com.atlassian.jira.component.ComponentAccessor.classLoader.loadClass("com.atlassian.jira.event.issue.IssueChangedEventImpl") } catch (Exception ignore) { // ... } if (_IssueChangedEventImplClazz) { def jiraIssue = imInternal.getIssueObject(jIssueInternal.id) def chm = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.issue.changehistory.ChangeHistoryManager.class) def allChangeItems = chm.getChangeHistories(jiraIssue); def ep = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.event.api.EventPublisher.class) def lastChangeGroup = allChangeItems.max { it.timePerformed.time } def issueChangedEvent = ({ try { _IssueChangedEventImplClazz.newInstance( jiraIssue, Optional.of(proxyAppUser), lastChangeGroup?.changeItemBeans ?: [], Optional. empty(), lastChangeGroup ? new Date(lastChangeGroup.timePerformed.time) : new Date(), true ) } catch (Exception ignore) { try { _IssueChangedEventImplClazz.newInstance( jiraIssue, Optional.of(proxyAppUser), lastChangeGroup?.changeItemBeans ?: [], Optional. empty(), lastChangeGroup ? new Date(lastChangeGroup.timePerformed.time) : new Date() ) } catch (Exception ignoreException) { _IssueChangedEventImplClazz.newInstance( jiraIssue, Optional.of(proxyAppUser), lastChangeGroup?.changeItemBeans ?: [], Optional. empty(), lastChangeGroup ? new Date(lastChangeGroup.timePerformed.time) : new Date(), true, null ) } } })() ep.publish(issueChangedEvent) } tr.issue } catch (com.exalate.api.exception.IssueTrackerException e) { throw e } catch (Exception e) { throw new com.exalate.api.exception.IssueTrackerException( "Failed to transition "+ jIssueInternal.key + " via transition "+ transitionName + " as user "+ transitionExecUserKey + " Due to Exception: "+ e.getMessage(), e ) } finally { LogIn.logIn(loggedInUser) } } def getTransitions = { String statusName, Map wf -> def step = wf.steps.find { Map s -> (s.name as String).equalsIgnoreCase(statusName) } if (step == null) { throw issueLevelError("Can not find step `"+ statusName +"`. Available steps: `"+ wf.steps.collect { Map s -> s.name + " ("+s.id+")" } +"`. Create it or review the transitioning script.") } step.transitions } def getGlobalTransitions = { Map wf -> wf .transitions .findAll { t -> t.global } } def getAllTransitions = { String statusName, List visitedStates, Map wf -> (getGlobalTransitions(wf) + getTransitions(statusName, wf)) .findAll { t -> !visitedStates.any { it.equalsIgnoreCase(t.to.name) } } .findAll {t -> !t.to.name.equalsIgnoreCase(statusName)} } def getAllTransitionsSkipGlobalTransitions = { String statusName, List visitedStates, Map wf -> getTransitions(statusName, wf) .findAll { t -> !visitedStates.any { it.equalsIgnoreCase(t.to.name) } } .findAll {t -> !t.to.name.equalsIgnoreCase(statusName)} } def getTransitionPath getTransitionPath = { transitionWithParent -> if(!transitionWithParent) return null; if(transitionWithParent.parentTransition == null){ return [transitionWithParent.transition] } getTransitionPath(transitionWithParent.parentTransition) + [transitionWithParent.transition] } def getPathsFromAtoB = { String currentStatusName, String targetStatusName, Map wf -> if(currentStatusName.equalsIgnoreCase(targetStatusName)){ return null } def q = [] as Queue>; def visitedStates = [] as List; def allFirstTransitions = getAllTransitions(currentStatusName, visitedStates, wf) as List>; visitedStates.add(currentStatusName as String) for(aTransition in allFirstTransitions){ def nextStatus = aTransition.to.name as String def transitionWithParent = ["transition": aTransition, "parentTransition": null] if(nextStatus.equalsIgnoreCase(targetStatusName)){ return transitionWithParent } q.add(transitionWithParent) } //solution -> every state has a from transition property while(!q.isEmpty()){ def transitionWithParent = q.remove() def nextStatus = transitionWithParent.transition.to.name as String def childTransitions = getAllTransitionsSkipGlobalTransitions(nextStatus, visitedStates, wf) as List>; visitedStates.add(nextStatus as String) for(childTransition in childTransitions){ def nextChildStatus = childTransition.to.name as String if(targetStatusName.equalsIgnoreCase(nextChildStatus)){ return ["transition": childTransition, "parentTransition": transitionWithParent] } q.add(["transition": childTransition, "parentTransition": transitionWithParent]) } } } // transition - find shortest path from current step to the required one def desiredStatusName = (workflowMapping.find { k,_ -> k.equalsIgnoreCase(replica.status?.name) }?.value) if (useRemoteStatusByDefault && desiredStatusName == null) { desiredStatusName = replica?.status?.name } if (desiredStatusName != null && !jIssueInternal.status.name.equalsIgnoreCase(desiredStatusName)) { def wf = getWorkflow() if (!wf.steps?.any { step -> desiredStatusName.equalsIgnoreCase(step?.name) }) { throw issueLevelError( "Can not find status `" + desiredStatusName + "` " + "in workflow `" + wf.name + ". " + "Please review whether `" + wf.name + "` is the correct workflow for the issue `" + jIssueInternal.key + "`." ) } def shortestPath = getTransitionPath(getPathsFromAtoB(jIssueInternal.status.name, desiredStatusName as String, wf)) if (shortestPath == null) { log.warn( "Can not find path from `" + jIssueInternal.status.name + "` to `" + desiredStatusName + "` " + "in workflow `" + wf.name +". " + "Please review whether `" + wf.name + "` isInternal the correct workflow for the issue `" + jIssueInternal.key + "`." ) return } def transitions = "" shortestPath.each { exTInternal -> def jiraTInternal = wfInternal.allActions.find { (it.id as Long) == (exTInternal.id as Long) } transition(jiraTInternal, proxyAppUser.key, null, wfInternal) // Sucessfull transition def nextTransition = exTInternal.name transitions += "$nextTransition -> " } issue.setStatus(desiredStatusName) ProgressLog.logIssueStatusChange("CHANGED STATUS", issue, jIssueInternal.status.name, desiredStatusName, transitions[0..-5]) } issue } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~CREATE ISSUE EXTERNAL SCRIPT~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class CreateIssue { private static def log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.Status") 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~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class UpdateIssue { private static def log = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.Status") 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 } } } private static class ProgressLog { private static progresslog = org.slf4j.LoggerFactory.getLogger("com.exalate.scripts.SubTask") static void logIssueStatusChange(String event, BasicHubIssue issue, String fromStatus, String toStatus, String stepsTaken) { def key = issue.getKey() if (stepsTaken.contains("->")) { progresslog.trace("${event}: '$key' '$fromStatus' '$toStatus' '$stepsTaken'") } else { progresslog.trace("${event}: '$key' '$fromStatus' '$toStatus'") } } static void logIssueChange(String event, BasicHubIssue issue) { def created_by = issue.creator?.getUsername() def key = issue.getKey() progresslog.trace("${event}: '$key' '$created_by'") } } //~LogIn EXTERNAL SCRIPT~ private static class LogIn { //can be com.atlassian.jira.user.ApplicationUser or com.atlassian.jira.crowd.embedded.ofbiz.OfBizUser 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 } } }