Forums

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

Forced to run post-function twice for the listener to pick up the event and run its script.

Shane Labonte May 6, 2019

Hello. 

Hoping to get a bit of help here on some automation I set up via script listeners and post functions together.

I currently have a scripted Post-function to change the value of a custom field on a Task depending on what status transition last occurred. This seems to work fine.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder

def customFieldManager = ComponentAccessor.customFieldManager
def escalate = customFieldManager.getCustomFieldObjectsByName("MyCustomField").first()


def optionsManager = ComponentAccessor.getOptionsManager()
def config = escalate.getRelevantConfig(issue)
def options = optionsManager.getOptions(config)
def optionToSelect = options.find {
it.value == "MyCustomFieldValue"
}
escalate.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(escalate), optionToSelect), new DefaultIssueChangeHolder())

After that occurs, I have a script listener setup which will apply the value of that custom field on the previously mentioned Task to any issues it is linked to (Epic). This also seems to work fine.

import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.event.issue.IssueEvent

def issue = event.issue as Issue

if(issue.issueType.name=="My Issue Type")
{
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser.class)
def searchProvider = ComponentAccessor.getComponent(SearchProvider.class)
def issueManager = ComponentAccessor.getIssueManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def linkedQuery = jqlQueryParser.parseQuery("issue in linkedIssues(\"${issue.key}\")")
def linkedResults = searchProvider.search(linkedQuery, user, PagerFilter.getUnlimitedFilter())

log.error("List of My Issue Type linked issues : " + linkedResults.getIssues())

def customFieldManager = ComponentAccessor.getCustomFieldManager()

int cfmyIssueTypeId = 11111
def cfmyIssueType = customFieldManager.getCustomFieldObject(cfmyIssueTypeId)
def cfmyIssueTypeValue = issue.getCustomFieldValue(cfmyIssueType)
log.error("My Issue Type : " +cfmyIssueTypeValue)

def changeHistoryManager = com.atlassian.jira.component.ComponentAccessor.getChangeHistoryManager()
linkedResults.getIssues().each

{myIssue ->;
def docIssue = issueManager.getIssueObject(myIssue.id)

def cfmyIssueTypeCurrentValue = docIssue.getCustomFieldValue(cfmyIssueType)
log.error("My Issue Type to be replaced : " + cfmyIssueTypeCurrentValue)
cfmyIssueType.updateValue(null, docIssue, new ModifiedValue(cfmyIssueTypeCurrentValue, cfmyIssueTypeValue), new DefaultIssueChangeHolder())

}

}

 

The issue I am having with this process is that when I change the status of the Task, I can see the changes take place immediately to the custom field; when I check the custom field on the Epic it seems to be a step behind... or always display the previous value from the Task.

  • I currently have the post-function to change the custom field value on the Task set to occur before the "Issue Updated" event is fired.
  • I added an extra post-function to fire an "Issue Updated" event if the custom field is changed; also did not work.
  • I added an extra script listener to fire an "Issue Updated" event if that field is changed anywhere in the project; also did not work.

I am not really sure what to do in order to get this to function properly. I am not having any errors and can see it work but for some reason it seems that the changes are not being recorded on the initial post function?

Any help would be hugely appreciated.

Thanks in advance.

 

tl;dr.....

Simplified workflow....

  1. Change status on Task
  2. Post-function changes custom field on Task to defined value
  3. Script Listener takes that custom field value from Task and applies it to the same field in the Epic

Simplified problem....

The custom field on the Epic is always 1 step behind that of the Task. I need to update the Task a 2nd time (any field) in order to populate the proper values to the custom field within the linked Epic.

Intended outcome....

Changing the status of the Task, updates the custom field value on the task; then in-turn updates the custom field value on the linked Epic so that the fields are the same.

1 answer

1 accepted

0 votes
Answer accepted
Marc Minten (EVS)
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 7, 2019

I think you only update a value of a custom field in your listener, but you do not save the issue, nor re-index the issue...

Shane Labonte May 7, 2019

Thanks for the reply Mark
I have a couple of questions for added details:

1) How would I save the issue and re-index the issue in the function?

2) Would I need to apply those changes to the listener, post-function, or both?

I already have the post-function occurring after the "Update Change History" and before the "re-index" steps of the transition.

The final step of the workflow transition is to fire an "Issue Updated" event; this is what i have the listener set to fire on.

Pretty new to Groovy here so any detailed help or corrections would be highly appreciated.

I assumed that running a post-function which occurs after the change history update, and prior to the re-index and the issue updated event would have those changes applied and updated before the listeners are triggered (since that is the last thing to occur in the workflow transition) but clearly I am missing something very important.

Marc Minten (EVS)
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 8, 2019

I think you should put your post-function BEFORE the "Update change history for an issue and store the issue in the database.", that way the changed value is stored automatically and the issue is re-indexed (= "Re-index an issue to keep indexes in sync with the database.." step).

For listener functions however, you need to do those things (store the issue and re-index the issue) yourself, something like

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.index.IssueIndexingService

ApplicationUser userId = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
IssueManager issueManager = ComponentAccessor.getIssueManager()

issueManager.updateIssue(userId, <your issue, class mutableIssue!>, EventDispatchOption.DO_NOT_DISPATCH, false)

boolean wasIndexing = ImportUtils.isIndexIssues()
IssueIndexingService indexing = (IssueIndexingService) ComponentAccessor.getComponent(IssueIndexingService.class)
indexing.reIndex(issue)
ImportUtils.setIndexIssues(wasIndexing)
Shane Labonte May 10, 2019

Thank you for once again getting back to me on this one Marc.

I went ahead and added your suggested lines, initially receiving some errors in the code when tied in with mine.

I altered it slightly to fit with my work without displaying any errors as follows:

import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Category

MutableIssue issue = (MutableIssue) event.issue
def issueManager = ComponentAccessor.getIssueManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

if(issue.issueType.name=="EABR Task")
{
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser.class)
def searchProvider = ComponentAccessor.getComponent(SearchProvider.class)

def linkedQuery = jqlQueryParser.parseQuery("issue in linkedIssues(\"${issue.key}\")")
def linkedResults = searchProvider.search(linkedQuery, user, PagerFilter.getUnlimitedFilter())

log.info("List of EABR Task linked Epic issues : " + linkedResults.getIssues())

def customFieldManager = ComponentAccessor.getCustomFieldManager()

int cfTestCaseStatusId = 12407
def cfTestCaseStatus = customFieldManager.getCustomFieldObject(cfTestCaseStatusId)
def cfTestCaseStatusValue = issue.getCustomFieldValue(cfTestCaseStatus)
log.info("Source to use for update : ${issue.key} ${issue.issueType.name} ${issue.id} ${cfTestCaseStatusValue}")

def changeHistoryManager = com.atlassian.jira.component.ComponentAccessor.getChangeHistoryManager()
linkedResults.getIssues().each

{myIssue ->;
def linkedIssue = issueManager.getIssueObject(myIssue.id)

def cfTestCaseStatusCurrentValue = linkedIssue.getCustomFieldValue(cfTestCaseStatus)
log.info("Needs update : ${linkedIssue.key} ${issue.issueType.name} ${linkedIssue.id} ${cfTestCaseStatusCurrentValue}")
cfTestCaseStatus.updateValue(null, linkedIssue, new ModifiedValue(cfTestCaseStatusCurrentValue, cfTestCaseStatusValue), new DefaultIssueChangeHolder())

issueManager.updateIssue(user, linkedIssue, EventDispatchOption.DO_NOT_DISPATCH, false)

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)

boolean wasIndexing = ImportUtils.isIndexIssues()
ImportUtils.setIndexIssues(true)
issueIndexingService.reIndex(linkedIssue)
ImportUtils.setIndexIssues(wasIndexing)
log.info("Linked Epic Issue Re-Indexed? ${wasIndexing}")

}

linkedResults.getIssues().each
{finishedIssue ->;
def changedIssue = issueManager.getIssueObject(finishedIssue.id)
def cfPostIndexValue = changedIssue.getCustomFieldValue(cfTestCaseStatus)

log.info("New values for updated issue : ${changedIssue.key} ${issue.issueType.name} ${changedIssue.id} ${cfPostIndexValue}")

issueManager.updateIssue(user, changedIssue, EventDispatchOption.DO_NOT_DISPATCH, false)

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)

boolean wasIndexing = ImportUtils.isIndexIssues()
ImportUtils.setIndexIssues(true)
log.info("Updating and Reindexing this issue 1 more time for assurance... ${changedIssue.key} ${issue.issueType.name} ${changedIssue.id}")
issueIndexingService.reIndex(changedIssue)
ImportUtils.setIndexIssues(wasIndexing)

}

}

Nothing seems to have changed...

It still runs successfully each time it is triggered, but the Epic still does not update/re-index to display the synced value for the custom field in the Task (always 1 step behind).

Here are the logs for info....

 

2019-05-10 00:34:03,610 INFO [runner.ScriptRunnerImpl]: List of EABR Task linked Epic issues : [DocumentIssueImpl[issueKey=QAT-424]]
2019-05-10 00:34:03,610 INFO [runner.ScriptRunnerImpl]: EABR Task Issue & Test Case Status to be used for update : QAT-425 100188 Review Ready
2019-05-10 00:34:03,610 INFO [runner.ScriptRunnerImpl]: Epic & Test Case Status to be updated : QAT-424 100187 Not Started
2019-05-10 00:34:03,610 INFO [runner.ScriptRunnerImpl]: Linked Epic Issue Re-Indexed? true
2019-05-10 00:34:03,610 INFO [runner.ScriptRunnerImpl]: New Epic Test Case Status After Re-Index : QAT-424 100187 Review Ready
2019-05-10 00:34:03,610 INFO [runner.ScriptRunnerImpl]: Updating and Reindexing this issue 1 more time for assurance... QAT-424 100187


Am I using something out of order or incorrectly?

I am currently using JIRA v7.9.0 server 

Marc Minten (EVS)
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 13, 2019

The log extract does not align with the code in your script ? There are lines in your logs that do not seem to come from your script ??? Other lines are missing ?

Is this the right script that did produce your log ??

Shane Labonte May 13, 2019

That's on me. Sorry for that. I changed the text which prints the description I think between posting either of them, but the results should be the same. I have posted both again below to avoid the confusion.

import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Category

MutableIssue issue = (MutableIssue) event.issue
def issueManager = ComponentAccessor.getIssueManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

if(issue.issueType.name=="EABR Task")
{
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser.class)
def searchProvider = ComponentAccessor.getComponent(SearchProvider.class)

def linkedQuery = jqlQueryParser.parseQuery("issue in linkedIssues(\"${issue.key}\")")
def linkedResults = searchProvider.search(linkedQuery, user, PagerFilter.getUnlimitedFilter())

log.info("List of EABR Task linked Epic issues : " + linkedResults.getIssues())

def customFieldManager = ComponentAccessor.getCustomFieldManager()

int cfTestCaseStatusId = 12407
def cfTestCaseStatus = customFieldManager.getCustomFieldObject(cfTestCaseStatusId)
def cfTestCaseStatusValue = issue.getCustomFieldValue(cfTestCaseStatus)
log.info("Source to use for update : ${issue.key} ${issue.issueType.name} ${issue.id} ${cfTestCaseStatusValue}")

def changeHistoryManager = com.atlassian.jira.component.ComponentAccessor.getChangeHistoryManager()
linkedResults.getIssues().each

{myIssue ->;
def linkedIssue = issueManager.getIssueObject(myIssue.id)

def cfTestCaseStatusCurrentValue = linkedIssue.getCustomFieldValue(cfTestCaseStatus)
log.info("Needs update : ${linkedIssue.key} ${issue.issueType.name} ${linkedIssue.id} ${cfTestCaseStatusCurrentValue}")
cfTestCaseStatus.updateValue(null, linkedIssue, new ModifiedValue(cfTestCaseStatusCurrentValue, cfTestCaseStatusValue), new DefaultIssueChangeHolder())

issueManager.updateIssue(user, linkedIssue, EventDispatchOption.DO_NOT_DISPATCH, false)

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)

boolean wasIndexing = ImportUtils.isIndexIssues()
ImportUtils.setIndexIssues(true)
issueIndexingService.reIndex(linkedIssue)
ImportUtils.setIndexIssues(wasIndexing)
log.info("Linked Epic Issue Re-Indexed? ${wasIndexing}")

}

linkedResults.getIssues().each
{finishedIssue ->;
def changedIssue = issueManager.getIssueObject(finishedIssue.id)
def cfPostIndexValue = changedIssue.getCustomFieldValue(cfTestCaseStatus)

log.info("New values for updated issue : ${changedIssue.key} ${issue.issueType.name} ${changedIssue.id} ${cfPostIndexValue}")

issueManager.updateIssue(user, changedIssue, EventDispatchOption.DO_NOT_DISPATCH, false)

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)

boolean wasIndexing = ImportUtils.isIndexIssues()
ImportUtils.setIndexIssues(true)
log.info("Updating and Reindexing this issue 1 more time for assurance... ${changedIssue.key} ${issue.issueType.name} ${changedIssue.id}")
issueIndexingService.reIndex(changedIssue)
ImportUtils.setIndexIssues(wasIndexing)

}

}

 

Output for info logs:

2019-05-13 06:35:22,004 INFO [runner.ScriptRunnerImpl]: List of EABR Task linked Epic issues : [DocumentIssueImpl[issueKey=QAT-424]]
2019-05-13 06:35:22,004 INFO [runner.ScriptRunnerImpl]: Source to use for update : QAT-425 EABR Task 100188 Review Ready
2019-05-13 06:35:22,004 INFO [runner.ScriptRunnerImpl]: Needs update : QAT-424 EABR Task 100187 Not Started
2019-05-13 06:35:22,004 INFO [runner.ScriptRunnerImpl]: Linked Epic Issue Re-Indexed? true
2019-05-13 06:35:22,004 INFO [runner.ScriptRunnerImpl]: New values for updated issue : QAT-424 EABR Task 100187 Review Ready
2019-05-13 06:35:22,004 INFO [runner.ScriptRunnerImpl]: Updating and Reindexing this issue 1 more time for assurance... QAT-424 EABR Task 100187

Cancel

Marc Minten (EVS)
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 13, 2019

Ok, but your case is still confusing: you talk about "Epic"s, but the issue you are updating and re-indexing (QAT-424) is of type "EABR Task" ??? (the same issue type of the issue the listener is responding to (QAT-424).

How do you validate the value in QAT-424 is (not)correct ?

What do you exactly mean with "...always 1 step behind..." ?

Shane Labonte May 13, 2019

To clarify:

  • The Epic is linked to the EABR Task issue type. 
  • I am trying to update the custom field for all issues linked to the EABR Task (which is the Epic).
  • The listener should respond to the changes made in the EABR Task and then update that custom field to the same value on the linked Epic issue.
  • QAT-424 is the Epic issue to be updated
  • QAT-425 is the EABR Task which has the value to base the epic's custom field off of.
  • I can validate that the changes are applied to the linked issues (Epic) via the line:
    • log.info("New values for updated issue : ${changedIssue.key} ${issue.issueType.name} ${changedIssue.id} ${cfPostIndexValue}")
    • which shows the Epic issue and its custom field value correctly after the first re-index.
  • "...always 1 step behind" is referring to my overall issue that I need to update the EABR Task 2 times before the changes in the custom field are applied to the Epic via the listener.

My apologies for the confusion, you can imagine that this is not easy for me either. 

I really do appreciate the consistent help and replies you have provided thus far. Thank you for your time and effort Marc.

Marc Minten (EVS)
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 14, 2019

Hi, ok.

I think the script is "correct" now.

The behavior being "1 step behind" sounds very strange: the value is updated, or it is not I guess..  So there must be something else ? Can you specify even more in detail how you validate your Epic has the right value ? Do you view the Epic in the Jira User Interface "view issue" ? Do you refresh your browser ?

Shane Labonte May 14, 2019
  • Let's say I transition the status of the EABR Task to Status A.
  • The post-function will then change the custom field value on the EABR Task to CFvalue_A.
  • I then navigate to the linked Epic issue and the custom field value will either be set as the default, or have nothing there.
  • I return to the EABR Task issue in the previous step.
  • I change any field in the EABR Task to any value (doesn't matter which field or what the value is).
  • I once again return to the linked Epic issue and refresh the page.
  • I can now see the that the custom field value is updated to CFvalue_A as intended.

This poses a problem in that if the next change made to the EABR Task is changing the status (as opposed to any of the other fields) the custom field in the Epic will have it's value updated, but it will be to the value previously displayed in the EABR Task, rather than the currently set value.

example below:

  • I transition the status of the EABR Task to Status A.
  • The post-function updates the custom field value on the EABR Task to CFvalue_A.
  • I navigate to the linked Epic issue and the custom field value will either be set as the default, or have nothing there.
  • I return to the EABR Task issue in the previous step.
  • I transition the status of the EABR Task to Status X.
  • The post-function updates the custom field value on the EABR Task to CFvalue_X.
  • I navigate to the linked Epic issue and the custom field value will display CFvalue_A.
  • This persists.......
  • I then once again return to the EABR Task and transition the status to Status B.
  • The post-function updates the custom field value on the EABR Task to CFvalue_B.
  • I once again navigate to the linked Epic issue and the custom field value will now display CFvalue_X (which was set as the previous value on the EABR Task).

This continues even if I change the EABR Task and re-index the entire project. The value will still not be copied properly to the Epic unless I run a 2nd update of any kind on the EABR Task issue.

If it helps to have this visually, I made a flowchart of the process:Jira Script listener Issue.jpg

Marc Minten (EVS)
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 15, 2019

Ok. Where did you put your post function (change of custom field) ? Before and after what other Post functions?

Shane Labonte May 15, 2019

I currently have the post function to change the custom field on status transition, just before the "Update Change History for an issue and store the issue in the database" step. So, the whole process of a status transition is as follows:

  1. Set issue status to the linked status of the destination workflow step.
  2. Add a comment to an issue if one is entered during a transition.
  3. Custom script post-function (inline script) {my post function}
  4. Update change history for an issue and store the issue in the database.
  5. Re-index an issue to keep indexes in sync with the database.
  6. Fire an Issue Updated event that can be processed by the listeners.

This being said I have tried moving it around to a couple different places within those steps to see if that made a difference, and had no luck.

Marc Minten (EVS)
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 16, 2019

Ok. Your post-function should be before the update and re-index post functions!

Then I think you should validate and debug step by step:

  • transition the Task
  • validate the values in your log file. Was the listener executed ? Correctly ?
  • check manually the values in the Epic (UI View issue screen)
  • if not ok, manually execute a re-index (of the project) and re-validate values in Epic : this will tell you if there is an indexing issue
  • if still not ok, add more logging ?

One thing I would do different in the script is : in stead of executing a JQL query searching for linked issues, find the linked issues directly using the API (IssueLinkManager.getOutwardLinks() and check the link type. But this shouldn't make a difference I guess.

Suggest an answer

Log in or Sign up to answer