Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Next challenges

Recent achievements

  • Global
  • Personal

Recognition

  • Give kudos
  • Received
  • Given

Leaderboard

  • Global

Trophy case

Kudos (beta program)

Kudos logo

You've been invited into the Kudos (beta program) private group. Chat with others in the program, or give feedback to Atlassian.

View group

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Scriptrunner return to previous status

I have a global transition that clones an issue,  when the clone is complete I want the workflow to return to where it was.  I seem to be able to get the previous status but when I try to transition to it it fails with:

 

def issueManager = ComponentAccessor.getIssueManager()
def issue = issueManager.getIssueObject("INS-4557")

def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
def changeHistories = changeHistoryManager.getChangeHistories(issue)

def changeItem = ComponentAccessor.getChangeHistoryManager().getChangeItemsForField(issue, 'status')?.last()
def lastStatus = changeItem.getFromString();

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

def workflow = ComponentAccessor.getWorkflowManager().getWorkflow(issue)
def status=workflow.getLinkedStatusObjects().find{item -> item.name==lastStatus}
def lastStatusId = status.id;


IssueService issueService = ComponentAccessor.getIssueService();
IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();
issueInputParameters.setComment("Transition Returned to " + lastStatus)


IssueService.TransitionValidationResult validationResult = issueService.validateTransition(currentUser, issue.id, lastStatusId as Integer, issueInputParameters)
def errorCollection = validationResult.errorCollection
log.debug(errorCollection)
if (! errorCollection.hasAnyErrors()) {
issueService.transition(currentUser, validationResult)
}
else {
// log errors etc
}

 

At the line

issueService.transition(currentUser, validationResult)

I get the error The workflow operation with action id '10875' does not exist in the workflow.

I figure that I need to pass a transition ID not a status ID. 
So I think my question is how do I get the transition ID I need for a given status?

 

 

6 answers

1 accepted

2 votes
Answer accepted

Just putting my answer here for those that might come across this. 

First I created a custom number field (LastTransitionId).

Then on each transition I added a post function that set this field to the ID of the current transition (you could probably do this in a function but I just hard coded the number in the transition).

The IDs are on the workflow text edit screen here:

31-10-2017 11-37-49.png

Then you can use a script post function like this to rexeecute the transition:

import com.atlassian.jira.component.ComponentAccessor
import org.apache.log4j.Logger
import org.apache.log4j.Level
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.issue.IssueInputParameters

def log = Logger.getLogger("daiwa.TransitiontoPrevious")
log.setLevel(Level.DEBUG)

def issueManager = ComponentAccessor.getIssueManager()

def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
def changeHistories = changeHistoryManager.getChangeHistories(issue)

def changeItem = ComponentAccessor.getChangeHistoryManager().getChangeItemsForField(issue, 'status')?.last()
def lastStatus = changeItem.getFromString();

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cField = customFieldManager.getCustomFieldObject("customfield_17940")
def lastTransitionId = issue.getCustomFieldValue(cField)

IssueService issueService = ComponentAccessor.getIssueService();
IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();

IssueService.TransitionValidationResult validationResult = issueService.validateTransition(currentUser, issue.id, lastTransitionId as Integer, issueInputParameters)
def errorCollection = validationResult.errorCollection

if (! errorCollection.hasAnyErrors()) {
issueService.transition(currentUser, validationResult)
}
else {
log.error(errorCollection)
}


0 votes

You are right, you need to pass in a transition ID to the update, as you don't just set a Status, you have to transition to it.

Your workflow needs to have the transition "back" to the status you want from the current status. 

Manually, you can just look at the workflow and get the transition ID from that, and then hard code it into the script.

If you want to do this programatically, you'll need code that can look at the workflow and work out the transition from current status to desired (I've never bothered to do this, it's too much fuss, but it is possible)

Thanks Nic,

"Your workflow needs to have the transition "back" to the status you want from the current status. "

As they are all global transitions then I guess they all have one available.

you'll need code that can look at the workflow and work out the transition from current status to desired (I've never bothered to do this, it's too much fuss, but it is possible)

Yeah they want the transition to always return from wherever it was called so I guess that is what I need.


The requirement (Clone the issue, wherever it is in the project),  seems quite a common thing to want to do.  Is a post function the best way to achieve this or should I be looking at some other way of doing it ?

Hello @Chris Shepherd

@Nic Brough _Adaptavist_ 

 

I'm trying to run this script but I have doubts, maybe you can help me.

 

Is exactly with this part of the code.

cField is the field LastTransitionedId but is null (no information) and checking the code, actually I'm not seen where the information should be set in this field.

Also, in my case, the objective is "comeback from waiting for user" depend on the previous status. With this script I have the previous status and I have the transition in my workflow, but I don't understand how to implement it.

Could you help me? At least to understand it.

Best regards.

 

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cField = customFieldManager.getCustomFieldObject("customfield_11310")

def lastTransitionId = issue.getCustomFieldValue(cField)

Your code to read the custom field looks ok. 

How are you populating that field?

Hello @Nic Brough _Adaptavist_ ,

First I'm checking in Console of ScriptRunner with an specific issue.

Second I'm adding as a post function in an specific workflow.

Best regards,

That does not tell us what you are putting into the custom field 11310 (what you've got that tries to read it later seems good, but we need to know what you're putting into it)

The script is exactly this:

import com.atlassian.jira.component.ComponentAccessor
import org.apache.log4j.Logger
import org.apache.log4j.Level
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.issue.IssueInputParameters

//def log = Logger.getLogger("daiwa.TransitiontoPrevious")
//log.setLevel(Level.DEBUG)

def issueManager = ComponentAccessor.getIssueManager()

def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
def changeHistories = changeHistoryManager.getChangeHistories(issue)

def changeItem = ComponentAccessor.getChangeHistoryManager().getChangeItemsForField(issue, 'status')?.last()
def lastStatus = changeItem.getFromString();

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cField = customFieldManager.getCustomFieldObject("customfield_11310")
def lastTransitionId = issue.getCustomFieldValue(cField)

log.error("customfield_11310 : " + lastTransitionId)

IssueService issueService = ComponentAccessor.getIssueService();
IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();

IssueService.TransitionValidationResult validationResult = issueService.validateTransition(currentUser, issue.id, lastTransitionId as Integer, issueInputParameters)
def errorCollection = validationResult.errorCollection

if (! errorCollection.hasAnyErrors()) {
issueService.transition(currentUser, validationResult)
}
else {
log.error(errorCollection)
}

 

When I is runned in transition the error that appears is:

 

Time (on server): Wed Dec 04 2019 09:57:29 GMT+0100 (Central European Standard Time)

The following log information was produced by this execution. Use statements like:log.info("...") to record logging information.

2019-12-04 09:57:29,344 ERROR [workflow.AbstractScriptWorkflowFunction]: customfield_11310 : null
2019-12-04 09:57:29,487 ERROR [workflow.AbstractScriptWorkflowFunction]: *************************************************************************************
2019-12-04 09:57:29,491 ERROR [workflow.AbstractScriptWorkflowFunction]: Script function failed on issue: EMDEPP-3, actionId: 161, file: null
java.lang.IllegalArgumentException
 at Script2.run(Script2.groovy:32)

 

I hope that it will be useful.

I'm starting with ScriptRunner these days, I'm very beginner.

Apologize for inconveniences.

Thanks in advance.

Hello @Nic Brough _Adaptavist_ 

no I have a little bit more knowledge in your plugin so I will understand a little bit how to run this plugin.

Thanks.

Sorry I was too busy to pick this up, glad you worked out that you needed to populate the custom field before you could read any data out of it!

Hello,

I have updates for who need to do this and was a beginner.

Steps:

1) Create the script in ScriptRunner Console. To do this you need some "tips" in order to not die in the attemp...

1.1) Use some issue to check the behavior. 

For example some like this:

//Test para Console
def issueKey = "IESD-14862"
def issueManager = ComponentAccessor.getIssueManager()
def issue = issueManager.getIssueObject(issueKey)
//def issue = issue as Issue
log.info("issue: "+ issue)

 

1.2) Use predefined values to check that works.

 

For example some like this:

//convert to ID
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cField = customFieldManager.getCustomFieldObject("customfield_11310")
def LastTransitionId = issue.getCustomFieldValue(cField)

LastTransitionId = 141 

Where 141 is the value of the transition that you want to run.

 

This code works...in console, now step2.

 

import com.atlassian.jira.component.ComponentAccessor
import org.apache.log4j.Logger
import org.apache.log4j.Level
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.issue.IssueInputParameters

//Test para Console
def issueKey = "IESD-14862"
def issueManager = ComponentAccessor.getIssueManager()
def issue = issueManager.getIssueObject(issueKey)
//def issue = issue as Issue
log.info("issue: "+ issue)

//Reading the "memory of the issue"
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
def changeHistories = changeHistoryManager.getChangeHistories(issue)

//selecting the "last status"
def changeItem = ComponentAccessor.getChangeHistoryManager().getChangeItemsForField(issue, 'status')?.last()
def lastStatus = changeItem.getFromString();

log.info("last status: " + lastStatus)

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

//convert to ID
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cField = customFieldManager.getCustomFieldObject("customfield_11310")
def LastTransitionId = issue.getCustomFieldValue(cField)

LastTransitionId = 141

log.info(cField)

log.info("customfield_11310: " + LastTransitionId)

IssueService issueService = ComponentAccessor.getIssueService();
IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();

IssueService.TransitionValidationResult validationResult = issueService.validateTransition(currentUser, issue.id, LastTransitionId as Integer, issueInputParameters)
def errorCollection = validationResult.errorCollection

if (! errorCollection.hasAnyErrors())
{
issueService.transition(currentUser, validationResult)
}
else {
log.error(errorCollection)
}

 

2) Create post function in each status to save the transition in variable LastTransitionID.

3) Do the listened were you "need it".

@Nic Brough _Adaptavist_ , in point 3 of this scenario.

Do you listened where you "need it".

If the only that you want is "comeback to the previous status" when the issue is in an status called "waiting for user" and the transition will be "triggered" when the user comment public what is the best place to prepare the code to comeback to "lastTransitionID"?

 

In serviceDesk automation I don't have the option of "scriptRunner".

How can I do it? or maybe "where I should do it"?

 

Thanks in advance.

0 votes

@Juan José Marchal Gómez  Yes, you need to have a transition back to the status you want to go back to, or there is nothing for your listener to trigger.

Problem is here, your "last transition id" is not going to be the transition you want to trigger.  It tells you what you did, not what you want to do.

As an example, let's say you run a transition from "support investigating" to "waiting for data".  That may have a transition if of say 42.  Your listener will pick up an incoming comment and will need to transition from "waiting for data" to "support investigating".  That will not have an id of 42, it will have its own unique id, which you'll need to use.

Your listener is either going to have to have an understanding of which transition ids "back" match a "forward" transition, or it's going to have to extract it from the workflow definition

ScriptRunner is an app for Jira, it's not part of the Automation stuff.

Hello @Nic Brough _Adaptavist_ ,

 

I will research about:

1) Create listened that will be triggered when commend added and this comment is public and this comment is from "waiting for data".

2) How to read the variable that stores for each issue the information about "last status"...

I will feedback about it...

thanks for the support.

Best regards and happy holidays.

Hello @Nic Brough _Adaptavist_ ,

at the end, in my case, the code is easily because I used "FastTrack in listener".

 

I created one listener for each status that I want to comeback from "Waiting status". 

The listeners check the "LastStatus" and return TRUE if it is the condition. In this case, use Action Transition of Fast track.

import com.atlassian.jira.component.ComponentAccessor
import org.apache.log4j.Logger
import org.apache.log4j.Level
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.issue.IssueInputParameters

def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
def changeHistories = changeHistoryManager.getChangeHistories(issue)
def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

log.info("---------- GET LAST STATUS ----------")
def changeItem = ComponentAccessor.getChangeHistoryManager().getChangeItemsForField(issue,'status')?.last()
def lastStatus = changeItem.getFromString()
log.info("Last Status: " + lastStatus)

if (lastStatus == "GEOS")
{
log.info("LAST STATUS IS ANALYTICS")
return true
}
else
{
log.info("DO NOTHING")
return false
}

 

@Nick, is it true if I'm creating "custom listened" with a case can I do all the transitions with only one script?

 

and using this part of your code?

 

issueService.validateTransition(currentUser, issue.id, LastTransitionId as Integer, issueInputParameters)
def errorCollection = validationResult.errorCollection

if (! errorCollection.hasAnyErrors())
{
issueService.transition(currentUser, validationResult)
}
else {
log.error(errorCollection)
}

I'm waiting your feedback.

Best regards.

Hello @Nic Brough _Adaptavist_ ,

could you lead me?

Best regards.

I would not try to do more than one transition on an issue in one script.

Transitions are done by a service, and you run the risk of the first transition not being complete by the time the second one starts. That can damage the data (worst case, I think, is fixable with the integrity checker, but until you ran that, your issue could not be transitioned anywhere. That's the worst I've seen from trying it)

Hello again @Nic Brough _Adaptavist_ 

And what is the solution if you want to comeback to different status from Waiting?

Best regards.

Create a transition from the other status from waiting and trigger that when you need it.

Hello @Nic Brough _Adaptavist_ ,

I have each transition from each status to waiting.

My current script basically is doing:

When the user set a comment and the status is waiting, depends on previous status (checking getChangeHistoryManager) comebacks to the status using the transition created previously.

Best regards. 

Finally, this is my code. Works.

The transition is "hardCode".

I created a listener that is triggered only when a customer comment is added.

This listener check the "last status" and transitioned the issue to it.

Thanks for all.

import com.atlassian.jira.component.ComponentAccessor
import org.apache.log4j.Logger
import org.apache.log4j.Level
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.issue.IssueInputParameters

log.warn("----------- LAST TRANSITIONED STATUS -------------")
log.warn("issue: " + issue )
def issue = issue as Issue

def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
def changeHistories = changeHistoryManager.getChangeHistories(issue)
def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();

log.warn("Current Status: " + issue.getStatus().name)
if (issue.getStatus().name == "Waiting for customer")
{
log.warn("Automation to transitioned ready to be done")
//Get last comment to know if it is from reporter to transition or not
def commentManager = ComponentAccessor.getCommentManager()
def commentList = commentManager.getComments(issue).sort{it.getCreated()}

log.info("--------- GET COMMENTS ---------")
log.info("Comments: " + commentList)
log.info("Last comment: " + commentList.last().body + " Created by: " + commentList.last().getAuthorApplicationUser())
def lastTransitionId = 41

if (commentList.last().getAuthorApplicationUser()==issue.getReporter())
{
log.info("Last comment has been created by Reporter, the issue should be transitioned")
log.info("---------- GET LAST STATUS ----------")
def changeItem = ComponentAccessor.getChangeHistoryManager().getChangeItemsForField(issue,'status')?.last()
log.warn(ComponentAccessor.getChangeHistoryManager().getChangeItemsForField(issue,'status'))
def lastStatus = changeItem.getFromString()
log.info("Last Status: " + lastStatus)

switch(lastStatus)
{
//There is case statement defined for cases
// Each case statement section has a break condition to exit the loop
case "GEOS":
lastTransitionId = 21
break;
case "Analytics":
lastTransitionId = 11
break;
case "EDS":
lastTransitionId = 171
break;
case "3LEVEL/EEHQ":
lastTransitionId = 81
break;
case "2LEVEL/EEIN":
lastTransitionId = 71
default:
lastTransitionId = 81
break;
}
log.warn("lastTransitionId: " + lastTransitionId)

IssueService issueService = ComponentAccessor.getIssueService()
IssueInputParameters issueInputParameters = issueService.newIssueInputParameters()

IssueService.TransitionValidationResult validationResult = issueService.validateTransition(currentUser, issue.id, lastTransitionId as Integer, issueInputParameters)
def errorCollection = validationResult.errorCollection

if (!errorCollection.hasAnyErrors()) {
issueService.transition(currentUser, validationResult)
}
else {
log.error(errorCollection)
}
}
}

log.warn("----------- LAST TRANSITIONED STATUS END -------------")

Suggest an answer

Log in or Sign up to answer
TAGS
Community showcase
Published in Marketplace Apps & Integrations

Bitbucket Smart Commits vs. Genius Commits - What's the difference?

If you already heard about Smart Commits in Bitbucket, know that you just stumbled upon something even better (and smarter!): Genius Commits by Better DevOps Automation for Jira Data Center (+ Server...

106 views 0 2
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you