Update a field based on two others

Mihai Mihai March 26, 2019

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!

 

 

 

 

3 answers

1 accepted

1 vote
Answer accepted
Antoine Berry
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 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.

Mihai Mihai March 26, 2019

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
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 26, 2019

Have you identified which part is giving you troubles ?

Mihai Mihai April 10, 2019

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
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
April 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 ?

Mihai Mihai April 10, 2019

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
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
April 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
Mihai Mihai April 10, 2019

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
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
April 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
Mihai Mihai April 11, 2019

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
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
April 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
Mihai Mihai April 11, 2019

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
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
April 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

Mihai Mihai April 11, 2019

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)
Mihai Mihai April 11, 2019

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
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
April 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
Mihai Mihai April 11, 2019

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.

0 votes
Sysad December 11, 2019

Hi @Antoine Berry  

We have a requirement, as one of our internal team has moved from a "different system" to Jira ServiceDesk. In their different system, they have the following fields

1. Impact- a dropdown custom field with options I1 I2 I3 I4

2. Urgency - a dropdown custom field with options U1 U2 U3 U4

3. Priority - a dropdown system field with options P1 P2 P3 P4

4. Weight - Single-line textbox which holds a number(n1 n2 n3 n4 n5...n10)

I could do the following:

While creating issue users will choose impact and urgency. If Impact is I1 and urgency U2 I can update Priority(P3) and Weight(n3). 

I couldn't achieve:

I would like to develop the same functionality when Impact & Urgency is edited. If edited, priority & weight should get updated as well.

Any help is very much appreciated. I tried something similar with a text box but now with options.

Antoine Berry
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
December 11, 2019

Hi @Sysad ,

May you please open a new Question ? That way other members of the community will be able to answer. I will gladly look into it. Also please specify how you were able to achieve the update.

Antoine

Like Sysad likes this
Antoine Berry
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
December 11, 2019

Also you can link the new thread here so I can check it. :)

Sysad December 12, 2019

I used inbuilt automation in JSD to get this auto field update when an issue is created and need help on issue edit.

When issue's Impact or Urgency changes/edited then priority and weight should change

0 votes
Mihai Mihai April 11, 2019

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
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
April 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
Mihai Mihai April 11, 2019

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