Scriptrunner return to previous status

Chris Shepherd October 30, 2017

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?

 

 

7 answers

1 accepted

3 votes
Answer accepted
Chris Shepherd October 31, 2017

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
aamelkin December 22, 2021

You don't actually need any ScriptRunner script to return to the previous status of a transition in progress. All you need is to modify the standard list of post-functions and REMOVE this: "Set issue status to the linked status of the destination workflow step".

Once you do so, your transition will do whatever you do there, but will NOT change the status of the issue, effectively "transitioning it back to the previous state".

Chris Shepherd December 22, 2021

err,  thats not actually what the question is about.  The example we are trying to solve would be something like:

You have a workflow with a load of different states,  all of which allow you to transition to "suspended" at some point you want to be able to transition from suspended to whatever the previous state was.

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
December 22, 2021

Plus, of course, it completely breaks the issue data and should never be done.  You really don't want to go about corrupting your system like this.

Like Azfar Masut likes this
0 votes
Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 28, 2020

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 -------------")

0 votes
Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
December 24, 2019

@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.

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 24, 2019

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.

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 23, 2020

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.

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 28, 2020

Hello @Nic Brough -Adaptavist- ,

could you lead me?

Best regards.

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 31, 2020

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)

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 3, 2020

Hello again @Nic Brough -Adaptavist- 

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

Best regards.

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 4, 2020

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

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 5, 2020

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. 

0 votes
Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 23, 2019

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".

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 24, 2019

@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
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 25, 2019

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)

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
December 2, 2019

Your code to read the custom field looks ok. 

How are you populating that field?

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 3, 2019

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,

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
December 3, 2019

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)

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 4, 2019

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.

Juan José Marchal Gómez
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 23, 2019

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.

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
December 23, 2019

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!

0 votes
Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 30, 2017

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)

Chris Shepherd October 30, 2017

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 ?

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events