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 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....
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.
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...
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 ??
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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..." ?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
To clarify:
log.info("New values for updated issue : ${changedIssue.key} ${issue.issueType.name} ${changedIssue.id} ${cfPostIndexValue}")
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 ?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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:
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:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Ok. Where did you put your post function (change of custom field) ? Before and after what other Post functions?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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:
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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:
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.
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.