Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

issueService.Transition() is breaking for loop

Deleted user May 21, 2020

Hi all,

 

We are experiencing an issue with transitioning an issue using a for loop in a post function script. Somehow when an issue that we want to transition using the script has satisfied the condition we set up, it successfully transitions it. But if there are multiple issues that is needed to be transitioned, what happens is that only the first issue it sees is transitioned and then the script stops. We have checked and verified that the loop works as without the line containing the "issueService.Transition", it completes the loop but without the transition.

Adding our script here for reference:

import com.atlassian.jira.bc.issue.search.SearchService import com.atlassian.jira.bc.issue.IssueService import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.search.SearchException import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.link.IssueLink import com.atlassian.jira.issue.link.IssueLinkManager import com.atlassian.jira.issue.CustomFieldManager import com.atlassian.jira.web.bean.PagerFilter import com.atlassian.jira.user.ApplicationUser import com.atlassian.jira.workflow.TransitionOptions 

final String jqlSearch = "issue in linkedIssues(${issue})" // the jql query for the linked issue
log.warn("issue: " + issue) def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def searchService = ComponentAccessor.getComponentOfType(SearchService) SearchService.ParseResult parseResult = searchService.parseQuery(user, jqlSearch)

if (parseResult.isValid()) {
//try {
def results = searchService.search(user, parseResult.query, PagerFilter.unlimitedFilter) def parentIssues = results.results // jql query result
log.warn("Number of parent issues: " + parentIssues.size())
log.warn("Checking parent issue(es) and status")

for ( def i = 0; i < parentIssues.size(); i++) {
def parentIssueStatus = parentIssues[i].getStatus().getName()
log.warn("Parent issue and status: " + parentIssues[i].key + " : " + parentIssues[i].getStatus().getName())
log.warn("Checking linked issue(es) and status")

if ( parentIssues[i].issueType.name == "Test-Case" ) {
final String jqlQuery = "issue in linkedIssues(${parentIssues[i].key})" // the jql query for the parent issue
SearchService.ParseResult parseNewResult = searchService.parseQuery(user, jqlQuery)
if (parseNewResult.isValid()) {
def linkedResults = searchService.search(user, parseNewResult.query, PagerFilter.unlimitedFilter)
def linkedIssues = linkedResults.results // jql query result for linked issues on parent issue
for (link in linkedIssues) {
def linkedIssueStatus = link.getStatus().getName()
log.warn("Linked issue and status: " + link.key + " : " + link.getStatus().getName())
}
def statusCheckerDefect = linkedIssues.count { it.getStatus().getName().contains("Closed") } // Number of linked defect issues that has Passed Status
log.warn("Total number of linked issues: " + statusCheckerDefect)
def linkedIssuesCount = linkedIssues.size() - 1 // Number of linked issues
log.warn("Number of linked issues associated with parent ticket: " + linkedIssuesCount)
if ( linkedIssuesCount == statusCheckerDefect) { // this block for transition for blocked to unblocked and failed to for retest
IssueService issueService = ComponentAccessor.getIssueService()
if (parentIssueStatus == "Blocked") {
IssueService.TransitionValidationResult result = issueService.validateTransition(user, parentIssues[i].getId(), 201, issueService.newIssueInputParameters())
if (result.isValid()) {
log.warn("Moving ticket from Blocked to Unblocked")
issueService.transition(user, result)
log.warn("Continuing loop")
} else {
log.warn result.getErrorCollection().getErrors()
}
log.warn("Continuing loop2")
}
if (parentIssueStatus == "Failed") {
IssueService.TransitionValidationResult result = issueService.validateTransition(user, parentIssues[i].getId(), 241, issueService.newIssueInputParameters())
if (result.isValid()) {
log.warn("Moving ticket from Failed to For Retest")
issueService.transition(user, result)
log.warn("Continuing loop")
} else {
log.warn result.getErrorCollection().getErrors()
} log.warn("Continuing loop2")
} log.warn("Continuing loop3")
} log.warn("Continuing loop4")
} log.warn("Continuing loop5")
} log.warn("Continuing loop6")
}
} else {
log.warn("Invalid query")
return null
}

 

1 answer

0 votes
PD Sheehan
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.
May 23, 2020

I can't see anything inherently wrong. So maybe something is going on during the transition.

A few things to try:

1) assign the issueService.transition result to some variable and examine the results

2) Don't put the log.warn() statements on the same line as the closing brace or separate them with ;

3) change the for loops for groovy each construct

2) send the issue service into a separate thread so that it can't interact with the current thread (will also cause the transitions to happen asynchronously... useful if you have many).

Here is a rewritten script (untested) that applies all of this along with some groovyfication ;)

import com.atlassian.jira.bc.issue.search.SearchService 
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.web.bean.PagerFilter

def issueService = ComponentAccessor.issueService
def searchService = ComponentAccessor.getComponentOfType(SearchService)
def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def statusTransitionMap = [Blocked: 201, Failed: 241]

final String jqlSearch = "issue in linkedIssues(${issue.key})" // the jql query for the linked issue
log.warn("issue: " + issue)

def parseResult = searchService.parseQuery(user, jqlSearch)

if (parseResult.isValid()) {

def results = searchService.search(user, parseResult.query, PagerFilter.unlimitedFilter)
def linkedIssues = results.results // jql query result
log.warn("Number of linked issues: " + linkedIssues .size())
log.warn("Checking linked issue(es) and status")
linkedIssues.
findAll{it.issueType.name == 'Test-Case'}.
each{ testCaseIssue ->
Thread.start{
def jqlQuery = "issue in linkedIssues($testCaseIssue.key)"
def parseNewResult = searchService.parseQuery(user, jqlQuery)
if (parseNewResult.isValid()) {
def linkedResults = searchService.search(user, parseNewResult.query, PagerFilter.unlimitedFilter)
//for jira 8.3
/*linkedResults.results*.document.collect{
ComponentAccessor.issueManager.getIssueObject(it.getValues('issue_id').first() as Long)
}*/
def subLinkedIssues = linkedResults.results
def closedLinkedIssues = subLinkedIssues.findAll{it.status.name.contains('Closed')}
log.warn "Linked Issues and status associated with test case issue ($testCaseIssue.key): \n${subLinkedIssues.collect{ " $it.key : $it.status.name"}.join('\n')}"
if( subLinkedIssues.size() == closedLinkedIssues.size()) {
def transitionId = statusTransitionMap[testCaseIssue .status.name]
def validationResult = issueService.validateTransition(user, testCaseIssue.id, transitionId, issueService.newIssueInputParameters())
if(validationResult.isValid()){
log.warn "Moving ticket from $testCaseIssue.status.name using transition id= $transitionId"
def result = issueService.transition(user, validationResult)
} else {
log.error validationResult.errorCollection.errors
}
} else {
log.warn "Some linked issues are still blocked"
}
}
}
}
} else {
log.warn("Invalid query")
return null
}
Deleted user May 25, 2020

Hi Peter, thanks for the insights we will try it out and let you know.

Deleted user May 28, 2020

After applying the changes you recommended, the same issue still happens. Somehow there seems to be some issue with the issueService.transition line. Would you know of any other way to perform transition aside from using issueService.transition?

PD Sheehan
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.
May 28, 2020

Maybe you can try it on a single issue in the console and try to narrow down the source of the issue.

For example, I took the script I suggested and pared it down to just the transition piece and added a manual issue definition (and added a statu/transition map item that matches my workflow):

import com.atlassian.jira.component.ComponentAccessor

def issueService = ComponentAccessor.issueService
def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def statusTransitionMap = [Blocked: 201, Failed: 241, Closed: 61]
def issue = ComponentAccessor.issueManager.getIssueObject('JSP-1922')

log.warn("issue: " + issue)

def transitionId = statusTransitionMap[issue.status.name]
def validationResult = issueService.validateTransition(user, issue.id, transitionId, issueService.newIssueInputParameters())
if(validationResult.isValid()){
log.warn "Moving ticket from $issue.status.name using transition id= $transitionId"
def result = issueService.transition(user, validationResult)
} else {
log.error validationResult.errorCollection.errors
}

And I executed this in the console. My issue was initially Closed, and it was transitioned back to New using the "Reopen task" transition (id=61).

And it generated the following expected logs:

2020-05-28 08:37:35,738 WARN [runner.ScriptBindingsManager]: issue: JSP-1922
2020-05-28 08:37:35,886 WARN [runner.ScriptBindingsManager]: Moving ticket from Closed using transition id= 61
Deleted user May 29, 2020

After trying it out, we have the following weird findings:

1. Somehow, trying it out in script console when all the issues are in the proper status (for example, the defect that should trigger the script is in Closed status while the test-case(s) that are supposed to be affected by this script is in Blocked status) causes it to work on more than one test-case but only one transition shows up in the logs.

2. Converting the script as a listener also has this weird thing. We used "All Events" trigger for the listener with the same issue status condition as #1 (We made some flags for it to make sure it works only for the intended purpose). The problem with this is that when the listener is triggered from a transition event (we use a generic event here), it only works for the first transition. But when triggered by adding a comment on the defect with "Closed" status that should trigger the script, somehow it is able to execute transition on all the test-case that are in the "Blocked" status.

PD Sheehan
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.
May 29, 2020

I got terribly confused while reading your code and trying to understand the relationship between issues and which issues you needed to transition and why. 
I suspect there is something weird happening in there.

Can you explain those relationships and your requirements?

  • How are the issues linked. Which issues must have a transition.
  • Are there exception where some issues should not be transitioned?
  • What is the issue/transition that triggers the others to transition?

Maybe I can help suggest a different way to identify the issues to act on rather than the double-nesting of JQL search that's currently happening.

Deleted user June 1, 2020

Apologies for the confusion.

The requirement we have is to automatically transition all issue(s) in our test board from Failed status and/or Blocked status to For Retest status and/or Unblocked status when all their linked issues is in Closed status. 

Since we were troubled on how to start, we decided to instead use the defect board issues as it would trigger the automatic transition when it is transitioned to Closed status. This is the reason for using the 2 layered JQL search.

We hope this clears some things.

We are grateful for your assistance.

PD Sheehan
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.
June 1, 2020

So something like this in JQL?

status in ( Failed, Blocked) and issueFunction not in linkedIssuesOf("status != Closed")

This should list all the Failed/Blocked issues that do not have any open linked issues.

Then you should only have a single layer to deal with.

Deleted user June 2, 2020

Thanks for the insight, I think this approach is cleaner than what we have.

I think I am seeing the issue now. Somehow when the script executes if the linked issue associated with an issue from the test board is transitioned to Closed status, JQL search shows the status of said issue to be its pre-transition status instead of Closed status.

Example:
ABC-1 (from defect board) is a defect linked to both ABC-2 and ABC-3 which are in the test board. When ABC-1 is transitioned to "Closed" status from "In Progress" status, the script triggers to check if the issue(s) linked to the test board issues (ABC-2 and ABC-3 in this case). Somehow we could see that for some reason, the JQL search returns the status of ABC-1 as "In Progress" instead of "Closed".

Have you encountered this by any chance?

PD Sheehan
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.
June 2, 2020

The JQL search are based on the index.

So if you've updated the status of one issue, but the corresponding index was not refreshed, then the JQL search will be based on the old status.

But if you get an issue object and actually validate the status, you should be able to find the correct/current status.

The documentation for transition method does indicate that re-indexing should be taking place:

This method will store the provided issue to the JIRA datastore and will transition it through workflow. Use this method to perform the default behavior for updating and transitioning an issue in JIRA. The issue will be saved and re-indexed, the comment, if provided, will be added and the transition will be made.

If you've implemented by threading suggestion, that might explain it, the thread/indexing may not be complete when you re-evaluate the jql. Which is why having a single JQL would be better: get all the issues you want to change in one search, then act on each of them. The action on one issue should not impact the others.

If you didn't implement the threading, then I really don't know why your JQL data is inconsistent.

Deleted user June 8, 2020

I see, thanks for the assistance. We will be reaching out to atlassian support in case they know a way to resolve this.

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events