It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Update a field based on two others

Hello,

We have the following use-case:

 - text field called "DoD"

- text field called "Acceptance Criteria"

- checklist field called "Story DoD" (from here: https://marketplace.atlassian.com/apps/1211562/checklist-for-jira?hosting=server&tab=overview

When "acceptance criteria" is filled in, then the text field "DoD" should get the text:

"Acceptance Criteria defined" to it.

When all the checkboxes in "Story DoD" are filled in, the text field "DoD" (same one as above), should get the text "All checkboxes checked" appended to it.

First I tried with two listeners, but then I realized that the update of one of them can trigger the other and vice-versa.

I am having problems putting them together in just on script.

What I managed so far is below:

 

import com.atlassian.jira.component.ComponentAccessor
import com.opensymphony.workflow.InvalidInputException
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.security.JiraAuthenticationContext


def customFieldManager = ComponentAccessor.getCustomFieldManager()
def optionsManager = ComponentAccessor.getOptionsManager()
def fieldConfigSchemeManager = ComponentAccessor.getFieldConfigSchemeManager()
def issue = event.issue as MutableIssue
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def DoD= ComponentAccessor.customFieldManager.getCustomFieldObjectByName("DoD")
def DoDValue = issue.getCustomFieldValue(DoD)
String appendedmessage1 = 'All the checkboxes in the Story DoD field are checked'

def StoryCheckbox = customFieldManager.getCustomFieldObjectByName("Story DoD")
def config = fieldConfigSchemeManager.getRelevantConfig(issue, StoryCheckbox)
def allOptions = optionsManager.getOptions(config)

def criteria = ComponentAccessor.customFieldManager.getCustomFieldObjectByName("Acceptance Criteria")
def criteriavalue = issue.getCustomFieldValue(criteria)
String appendedmessage2 = 'Acceptance Criteria defined'

Boolean alreadyAddedMessage1 = criteriavalue.indexOf(appendedmessage1) != -1
Boolean alreadyAddedMessage2 = criteriavalue.indexOf(appendedmessage2) != -1

 

 

I am having problems putting the condition in place.

Any help is appreciated.

 

Thank you!

 

 

 

 

2 answers

1 accepted

1 vote
Answer accepted
Antoine Berry Community Leader Mar 26, 2019

Hi,

What happens if "Acceptance criteria" is filled and all the checkboxes in "Story DoD" are checked ?

I'm pretty sure you should handle this in one listener.

Hello,

Yes, it should be one listener, because in two different ones the message posted by one could trigger the other. However, I'm having troubles putting it together in just one script.

Antoine Berry Community Leader Mar 26, 2019

Have you identified which part is giving you troubles ?

Hi @Antoine Berry 

Sorry for the late reply, I was off some days.

I am having problems updating the issue when is has all the checkboxes checked. When "Acceptance Criteria" is filled in, it works. The bad part is that it replaces instead of appending, but I can figure that out later.

Please check what I have below and let me know if you have any suggestions.

 

Thank you!

switch (!alreadyAddedMessage1) {

case true:
if (!alreadyAddedMessage2)

{
if (criteriavalue) {

issue.setCustomFieldValue(
customFieldManager.getCustomFieldObjectByName("DoD")
, appendedmessage2,
)
}

else if (issue.getCustomFieldValue(StoryCheckbox) == allOptions) {
issue.setCustomFieldValue(
customFieldManager.getCustomFieldObjectByName("DoD")
, appendedmessage1
)
}

};
break

case false:
if (!alreadyAddedMessage2)

{
if (criteriavalue) {

issue.setCustomFieldValue(
customFieldManager.getCustomFieldObjectByName("DoD")
, appendedmessage2,
)
}

};
break
}

ComponentAccessor.getIssueManager().updateIssue(user,issue, EventDispatchOption.DO_NOT_DISPATCH, false)

Antoine Berry Community Leader Apr 10, 2019

Hi @Mihai Mihai ,

re-reading this, I have a few questions. Why do you need to fill this acceptance criteria field ? Why use a text-field over standard checkbox field ?

Hi @Antoine Berry 

We could not get everybody to use the same values for the checkboxes.

Some product owners also wanted a free text field where they could add items specific to them. 

We have a very large instance with over 140 projects, and we tried to satisfy many different projects with the same schema, so stuff like this appeared. 

Changing the way things are right now would not be a possibility for us.

Antoine Berry Community Leader Apr 10, 2019

I see, it might be tough to change something used among that many users. I checked the code and this is how I would do it : 

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

int cfDoDId = 11001
int cfAcceptanceCriteriaId = 11002
int cfStoryDoDId = 11003

def cfDoD = customFieldManager.getCustomFieldObject(cfDoDId)
def cfAcceptanceCriteria = customFieldManager.getCustomFieldObject(cfAcceptanceCriteria)
def cfStoryDoD = customFieldManager.getCustomFieldObject(cfStoryDoDId)


def cfAcceptanceCriteriaChanged = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Acceptance Criteria"}
if (cfAcceptanceCriteriaChanged){
def cfAcceptanceCriteriaValue = issue.getCustomFieldValue(cfAcceptanceCriteria)
def cfDoDValue = issue.getCustomFieldValue(cfDoD)
if (cfAcceptanceCriteriaValue != null && cfAcceptanceCriteriaValue != "" && cfDoDValue.indexOf("Acceptance Criteria defined") == -1){
def cfDoDNewValue = (cfDoDValue == "") ? "Acceptance Criteria defined" : cfDoDValue + " & Acceptance Criteria defined"
cfDoD.updateValue(null, issue, new ModifiedValue(cfDoDValue, cfDoDNewValue), new DefaultIssueChangeHolder())
}
}

def cfStoryDoDChanged = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Story DoD"}
if (cfStoryDoDChanged){
def cfStoryDoDValue = issue.getCustomFieldValue(cfStoryDoD)
log.error("cfStoryDoDValue : " + cfStoryDoDValue)
log.error("cfStoryDoDValue class : " + cfStoryDoDValue.getClass())

def config = ComponentAccessor.getFieldConfigSchemeManager().getRelevantConfig(issue, cfStoryDoD)
def allOptions = optionsManager.getOptions(config)
def cfDoDValue = issue.getCustomFieldValue(cfDoD)

if (cfStoryDoDValue != null && cfStoryDoDValue == allOptions && cfDoDValue.indexOf("All checkboxes checked") == -1){
def cfDoDNewValue = (cfDoDValue == "") ? "All checkboxes checked" : cfDoDValue + " & All checkboxes checked"
cfDoD.updateValue(null, issue, new ModifiedValue(cfDoDValue, cfDoDNewValue), new DefaultIssueChangeHolder())
}
}

assuming your "allOptions" variable indeed is equal to the custom field value when all the checkboxes are checked. I do not have the plug-in so you have to make sure this is the case (that is why I put some logs there).

Also, ensure that this listener is triggered by each event where the user can change these values (I guess Issue Updated, but you should care also for transitions).

Antoine

Like Mihai Mihai likes this

Hi @Antoine Berry 

Thank you very much for your reply.

I tried it, but get one error, and the updates are not done, probably because of it:

java.lang.NullPointerException: Cannot invoke method indexOf() on null object

 

"Acceptance Criteria" has some values in it, the checkboxes are also all checked.

Do you have any suggestion for this?

Thank you!

Antoine Berry Community Leader Apr 11, 2019

Hi,

That is my bad, I had in mind that empty text field would return "", but it returns null.

Try :

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

int cfDoDId = 11001
int cfAcceptanceCriteriaId = 11002
int cfStoryDoDId = 11003

def cfDoD = customFieldManager.getCustomFieldObject(cfDoDId)
def cfAcceptanceCriteria = customFieldManager.getCustomFieldObject(cfAcceptanceCriteria)
def cfStoryDoD = customFieldManager.getCustomFieldObject(cfStoryDoDId)


def cfAcceptanceCriteriaChanged = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Acceptance Criteria"}
if (cfAcceptanceCriteriaChanged){
def cfAcceptanceCriteriaValue = issue.getCustomFieldValue(cfAcceptanceCriteria)
def cfDoDValue = issue.getCustomFieldValue(cfDoD)
if (cfAcceptanceCriteriaValue != null && cfAcceptanceCriteriaValue != "" && (cfDoDValue == null || cfDoDValue.indexOf("Acceptance Criteria defined") == -1)){
def cfDoDNewValue = (cfDoDValue == null) ? "Acceptance Criteria defined" : cfDoDValue + " & Acceptance Criteria defined"
cfDoD.updateValue(null, issue, new ModifiedValue(cfDoDValue, cfDoDNewValue), new DefaultIssueChangeHolder())
}
}

def cfStoryDoDChanged = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Story DoD"}
if (cfStoryDoDChanged){
def cfStoryDoDValue = issue.getCustomFieldValue(cfStoryDoD)
log.error("cfStoryDoDValue : " + cfStoryDoDValue)
log.error("cfStoryDoDValue class : " + cfStoryDoDValue.getClass())

def config = ComponentAccessor.getFieldConfigSchemeManager().getRelevantConfig(issue, cfStoryDoD)
def allOptions = optionsManager.getOptions(config)
def cfDoDValue = issue.getCustomFieldValue(cfDoD)

if (cfStoryDoDValue != null && cfStoryDoDValue == allOptions && (cfDoDValue == null || cfDoDValue.indexOf("All checkboxes checked") == -1)){
def cfDoDNewValue = (cfDoDValue == null) ? "All checkboxes checked" : cfDoDValue + " & All checkboxes checked"
cfDoD.updateValue(null, issue, new ModifiedValue(cfDoDValue, cfDoDNewValue), new DefaultIssueChangeHolder())
}
}

Antoine

Like Mihai Mihai likes this

Hi @Antoine Berry 

Thank you very much. That solves that error, and now the listeners themselves have a check-mark, but the change is only actually being made for the acceptance criteria field, not also when the checkboxes field has the checkboxes checked, like you thought might happen.

 

Looking in the logs, I see this:

2019-04-11 12:25:36,840 ERROR [runner.ScriptRunnerImpl]: cfStoryDoDValue : [Story description understood and complete, Impact and risk are analyzed and understood, Feasible in time and cost, No impediment identified, Can be verified]
2019-04-11 12:25:36,841 ERROR [runner.ScriptRunnerImpl]: cfStoryDoDValue class : class java.util.ArrayList

Do you have any suggestions for this?

Thank you!

Antoine Berry Community Leader Apr 11, 2019

Hi, 

Indeed we need to figure what value your checkbox field is expecting. Please add : 

log.error("cfStoryDoDValue[0] class : " + cfStoryDoDValue[0].getClass())
log.error("allOptions : " + allOptions)
log.error("allOptions class : " + allOptions.getClass())
log.error("allOptions[0] class : " + allOptions.getClass())

Towards the end where these variables are valued. Also try to update the Story DoD custom field only to test it, and not the acceptance criteria, because I am not sure how the listener would react in that case.

Antoine

Like Mihai Mihai likes this

Hi @Antoine Berry 

I added those and these are the errors:

2019-04-11 14:08:52,006 ERROR [runner.ScriptRunnerImpl]: cfStoryDoDValue : [Story description understood and complete, Impact and risk are analyzed and understood, Feasible in time and cost, No impediment identified, Can be verified]
2019-04-11 14:08:52,006 ERROR [runner.ScriptRunnerImpl]: cfStoryDoDValue class : class java.util.ArrayList
2019-04-11 14:08:52,008 ERROR [runner.ScriptRunnerImpl]: allOptions : [Story description understood and complete, Impact and risk are analyzed and understood, Feasible in time and cost, No impediment identified, Can be verified]
2019-04-11 14:08:52,008 ERROR [runner.ScriptRunnerImpl]: allOptions class : class com.atlassian.jira.issue.customfields.option.OptionsImpl
2019-04-11 14:08:52,011 ERROR [runner.AbstractScriptListener]: *************************************************************************************
2019-04-11 14:08:52,011 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: com/listeners/IIPDoRUpdate_V3.groovy
groovy.lang.MissingMethodException: No signature of method: org.apache.log4j.Logger.error() is applicable for argument types: (java.lang.String, IIPDoRUpdate_V3$_run_closure3) values: [allOptions[0] class : class com.atlassian.jira.issue.customfields.option.OptionsImpl, ...]
Possible solutions: error(java.lang.Object), error(java.lang.Object, java.lang.Throwable), grep(), iterator(), every(), getRoot()
 at IIPDoRUpdate_V3.run(IIPDoRUpdate_V3.groovy:48)
Antoine Berry Community Leader Apr 11, 2019

Please add this one : 

log.error("cfStoryDoDValue[0] class : " + cfStoryDoDValue[0].getClass())

and replace this 

log.error("allOptions[0] class : " + allOptions.getClass())

by this :

log.error("allOptions[0] class : " + allOptions.get(0).getClass())

Thanks

Hi @Antoine Berry 

I did the changes. These are the results:

2019-04-11 14:23:55,627 ERROR [runner.AbstractScriptListener]: *************************************************************************************
2019-04-11 14:23:55,628 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: com/listeners/IIPDoRUpdate_V3.groovy
groovy.lang.MissingMethodException: No signature of method: org.apache.log4j.Logger.error() is applicable for argument types: (java.lang.String, IIPDoRUpdate_V3$_run_closure3) values: [allOptions[0] class : class com.atlassian.jira.issue.customfields.option.LazyLoadedOption, ...]
Possible solutions: error(java.lang.Object), error(java.lang.Object, java.lang.Throwable), grep(), iterator(), every(), getRoot()
 at IIPDoRUpdate_V3.run(IIPDoRUpdate_V3.groovy:45)

Update: changed their place and I think this is what you were looking for:

2019-04-11 14:26:37,925 ERROR [runner.ScriptRunnerImpl]: cfStoryDoDValue[0] class : class com.okapya.jira.customfields.ChecklistItem
2019-04-11 14:26:37,925 ERROR [runner.ScriptRunnerImpl]: allOptions[0] class : class com.atlassian.jira.issue.customfields.option.LazyLoadedOption
Antoine Berry Community Leader Apr 11, 2019

I think the best and only way to check then is to compare the size then, try to use : 

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

int cfDoDId = 11001
int cfAcceptanceCriteriaId = 11002
int cfStoryDoDId = 11003

def cfDoD = customFieldManager.getCustomFieldObject(cfDoDId)
def cfAcceptanceCriteria = customFieldManager.getCustomFieldObject(cfAcceptanceCriteria)
def cfStoryDoD = customFieldManager.getCustomFieldObject(cfStoryDoDId)


def cfAcceptanceCriteriaChanged = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Acceptance Criteria"}
if (cfAcceptanceCriteriaChanged){
def cfAcceptanceCriteriaValue = issue.getCustomFieldValue(cfAcceptanceCriteria)
def cfDoDValue = issue.getCustomFieldValue(cfDoD)
if (cfAcceptanceCriteriaValue != null && cfAcceptanceCriteriaValue != "" && (cfDoDValue == null || cfDoDValue.indexOf("Acceptance Criteria defined") == -1)){
def cfDoDNewValue = (cfDoDValue == null) ? "Acceptance Criteria defined" : cfDoDValue + " & Acceptance Criteria defined"
cfDoD.updateValue(null, issue, new ModifiedValue(cfDoDValue, cfDoDNewValue), new DefaultIssueChangeHolder())
}
}

def cfStoryDoDChanged = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Story DoD"}
if (cfStoryDoDChanged){
def cfStoryDoDValue = issue.getCustomFieldValue(cfStoryDoD)
log.error("cfStoryDoDValue : " + cfStoryDoDValue)
log.error("cfStoryDoDValue class : " + cfStoryDoDValue.getClass())

def config = ComponentAccessor.getFieldConfigSchemeManager().getRelevantConfig(issue, cfStoryDoD)
def allOptions = optionsManager.getOptions(config)
def cfDoDValue = issue.getCustomFieldValue(cfDoD)

if (cfStoryDoDValue != null && cfStoryDoDValue.size() == allOptions.size() && (cfDoDValue == null || cfDoDValue.indexOf("All checkboxes checked") == -1)){
def cfDoDNewValue = (cfDoDValue == null) ? "All checkboxes checked" : cfDoDValue + " & All checkboxes checked"
cfDoD.updateValue(null, issue, new ModifiedValue(cfDoDValue, cfDoDNewValue), new DefaultIssueChangeHolder())
}
}

Antoine

Like Mihai Mihai likes this

Hi @Antoine Berry 

This unfortunately adds the "All checkboxes checked" message with every update of the checkbox field, no matter if it has 1 checkbox checked out of 5, 2 out of 5, 3 out of 5 , 4 out of 5 or actually all 5. 

Thank you.

Hi @Antoine Berry 

I think I got lucky and found something useful:

 

cfStoryDoDValue.every({ it.checked })

 

seems to do the trick.

 

My last condition is now: 

 

if (cfStoryDoDValue != null && cfStoryDoDValue.every({ it.checked }) && (cfDoDValue == null || cfDoDValue.indexOf("All checkboxes checked") == -1)){
def cfDoDNewValue = (cfDoDValue == null) ? "All checkboxes checked" : cfDoDValue + " & All checkboxes checked"
cfDoD.updateValue(null, issue, new ModifiedValue(cfDoDValue, cfDoDNewValue), new DefaultIssueChangeHolder())

 

and it only adds the text if actually all the items are checked.

 

However, I did see that if I update "Acceptance Criteria" and not also the checkbox field, the proper message for acceptance criteria is added.

 

If I do not update acceptance criteria and I do check the boxes (all of them), the proper message is added.

 

If I update both acceptance criteria and the checkbox field, only the message for the checkbox field is added.

Thank you!

Antoine Berry Community Leader Apr 11, 2019

Good job ! I was doing some testing on my part but it is harder without having the plugin.

For the last issue, try adding 

def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)
issue.store()
issueIndexingService.reIndex(issue)

just before 

def cfStoryDoDChanged = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Story DoD"}

and 

import com.atlassian.jira.issue.index.IssueIndexingService

to the imports.

If that does not work, you will need to check if both fields have been updated, i.e. 

if (cfAcceptanceCriteriaChanged && cfStoryDoDChanged){ ...

Antoine

Like Mihai Mihai likes this

Thanks for the tip again @Antoine Berry 

I needed the 

if (cfAcceptanceCriteriaChanged && cfStoryDoDChanged){

 part that you suggested. Now it works. 

Again, thanks for all your help!

Like Antoine Berry likes this

Suggest an answer

Log in or Sign up to answer
Community showcase
Posted in Jira

Calling all Jira Cloud users! Give us feedback on our exploration of a new navigation.

Hi everyone! My name’s Matt and I’m a product manager at Atlassian. I work in the navigation & findability space for all our Jira Cloud products. We’ve been working on trying to improve the exp...

902 views 14 12
Join discussion

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you