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?
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:
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)
}
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".
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.IssueInputParameterslog.warn("----------- LAST TRANSITIONED STATUS -------------")
log.warn("issue: " + issue )
def issue = issue as Issuedef 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 = 41if (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.errorCollectionif (!errorCollection.hasAnyErrors()) {
issueService.transition(currentUser, validationResult)
}
else {
log.error(errorCollection)
}
}
}log.warn("----------- LAST TRANSITIONED STATUS END -------------")
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hello again @Nic Brough -Adaptavist-
And what is the solution if you want to comeback to different status from Waiting?
Best regards.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Create a transition from the other status from waiting and trigger that when you need it.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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".
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hello @Chris Shepherd
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)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Your code to read the custom field looks ok.
How are you populating that field?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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,
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 ?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.