Forums

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

Groovy Script Unsuccessful Workflow Transition Block if Custom Field Aerial Value is "Yes"

Daniel McCollum October 18, 2022

Hi All,

Newbie here, struggling to putting together working script after several hours of attempted learning and forum research.

Jira Software Server 9.0.0 (On-Premise) and Groovy Scripting.

Goal: prevent user from transitioning an issue forward in workflow if selected value from among aerial options ("Yes", "No" or the default "None") for custom field is set to "Yes".

Here's the code block so far... bolded/italicized code is where I've been unsuccessful.

import com.atlassian.jira.issue.Issue
import com.atlassian.jira.component.ComponentAccessor
import com.opensymphony.workflow.InvalidInputException
import com.atlassian.jira.issue.fields.CustomField

def CUSTOM_FIELD_ID = 10405L
def MESSAGE_ERROR = "Field ${getCustomFieldObject(CUSTOM_FIELD_ID)} is required"

if(getCustomFieldValue(issue, CUSTOM_FIELD_ID).name == "Yes"){
throw new InvalidInputException("Response cannot be "Yes", click Cancel")
}

if(!getCustomFieldValue(issue, CUSTOM_FIELD_ID)){
throw new InvalidInputException(MESSAGE_ERROR)
}

def getCustomFieldValue(issue, Long fieldId) {
issue.getCustomFieldValue(getCustomFieldObject(fieldId))
}

def getCustomFieldObject(Long fieldId) {
ComponentAccessor.customFieldManager.getCustomFieldObject(fieldId)
}

 

Above script is successful in blocking transition when there is not a custom field value entered by user (i.e., "None"), but again, having huge time in attempting to solve for also blocking transition if user chooses/selects "Yes" and tries to move issue forward in workflow.

Any help or input is very appreciated!

2 answers

2 accepted

3 votes
Answer accepted
Florian Bonniec
Community Champion
October 18, 2022

Hi @Daniel McCollum 

Have you check what is returned by logging it ?

getCustomFieldValue(issue, CUSTOM_FIELD_ID).name

 

Have you try 

getCustomFieldValue(issue, CUSTOM_FIELD_ID).get value()

 

Regards 

Daniel McCollum October 18, 2022

Thanks for the reply @Florian Bonniec .

Did you mean try replacing 

if(getCustomFieldValue(issue, CUSTOM_FIELD_ID).name == "Yes")

with

if(getCustomFieldValue(issue, CUSTOM_FIELD_ID).get value()

 

image.png

Radek Dostál
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.
October 19, 2022

I think he meant '.getValue()' which should return the string representation of the option.

Option customfields normally return a LazyLoadedOption: https://docs.atlassian.com/software/jira/docs/api/8.20.12/com/atlassian/jira/issue/customfields/option/LazyLoadedOption.html#getValue--

Like • Daniel McCollum likes this
Florian Bonniec
Community Champion
October 19, 2022

@Daniel McCollum 

Replace .name by .get value()

After getCustomFieldValue(issue, CUSTOM_FIELD_ID)

Daniel McCollum October 19, 2022

@Florian Bonniec @Radek Dostál okay, got it - thanks for the clarification and help to this point!

Here's entire script including classes and methods with made changes highlighted in bold/italic:

import com.atlassian.jira.issue.Issue
import com.atlassian.jira.component.ComponentAccessor
import com.opensymphony.workflow.InvalidInputException
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.customfields.option.LazyLoadedOption

def CUSTOM_FIELD_ID = 10405L
def MESSAGE_ERROR = "Field ${getCustomFieldObject(CUSTOM_FIELD_ID)} is required"
def customFieldManager = ComponentAccessor.getCustomFieldManager()

//Custom field name called Require Design with options yes no none
def value = customFieldMananger.getCustomFieldObjectByName("Require Design")

//Block transition if "Yes" option returned
if(getCustomFieldValue(issue, CUSTOM_FIELD_ID).get value() == "Yes"){
throw new InvalidInputException("Response cannot be Yes, click Cancel")
}

//Block transition if "None" option returned
if(!getCustomFieldValue(issue, CUSTOM_FIELD_ID)){
throw new InvalidInputException(MESSAGE_ERROR)
}

def getCustomFieldValue(issue, Long fieldId) {
issue.getCustomFieldValue(getCustomFieldObject(fieldId))
}

def getCustomFieldObject(Long fieldId) {
ComponentAccessor.customFieldManager.getCustomFieldObject(fieldId)
}

 

Initially testing of script does not stop workflow transition when I choose "Yes" option, but will keep researching and testing more solutions.

image.png

 

Before testing script, there was this warning for what it's worth.

image.png

Florian Bonniec
Community Champion
October 19, 2022

Use .getValue(), 

You have a space and lower case v

 

Regards 

Like • Daniel McCollum likes this
Daniel McCollum October 19, 2022

Used .getValue() without comma behind.

getCustomFieldValue(issue, CUSTOM_FIELD_ID).getValue() 

Changing to .getValue() at the end did not show behaviour change on workflow, but did generate another couple errors prior to running script tests on an existing issue.

Script snippet with 1st error (bolded/italicized) "variable CustomFieldManager undeclared".

def CUSTOM_FIELD_ID = 10405L
def MESSAGE_ERROR = "Field ${getCustomFieldObject(CUSTOM_FIELD_ID)} is required"
def customFieldManager = ComponentAccessor.getCustomFieldManager()

//Custom field name called Require Design with options yes no none
def value = customFieldMananger.getCustomFieldObjectByName("Require Design")

Definition is there in script, which I believe I have appropriately declared:

def customFieldManager = ComponentAccessor.getCustomFieldManager()

Script snippet with 2nd error (bolded/italicized) "Cannot find matching method java.lang.Object#getvalue()"

def CUSTOM_FIELD_ID = 10405L
def MESSAGE_ERROR = "Field ${getCustomFieldObject(CUSTOM_FIELD_ID)} is required"
def customFieldManager = ComponentAccessor.getCustomFieldManager()

//Custom field name called Require Design with options yes no none
def value = customFieldMananger.getCustomFieldObjectByName("Require Design")

//Block transition if "Yes" option returned
if(getCustomFieldValue(issue, CUSTOM_FIELD_ID).getValue() == "Yes"){
throw new InvalidInputException("Response cannot be Yes, click Cancel")
}

After returning custom field's option, script checks if equal to "Yes" after the getValue().

 == "Yes"
Radek Dostál
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.
October 19, 2022 edited

1st - customFieldManager isn't the same as customFieldMananger

2nd - static type checking is not an error, it's just a preemptive way of warning you about potential problems, not that there necessarily are any

 

But here we are getting into more java related topics. In a nutshell, Issue#getCustomFieldValue() returns an Object. Everything in java extends Object, everything is an instance of Object.

What this also means, and what is happening, is that you can return any data type - because again, everything is an Object. If you return a String or an Integer - they are instances of Object.

However, Object does not have a #getValue() method. Hence the warning.

The LazyLoadedOption does - which is what is the data type you will get out of that issue.getCustomFieldValue() method. 

Static type checking however has no idea what the return object will be - it sees it simply as an Object, that is why you get the static type checking warning.

What will happen in reality is that the data type of issue.getCustomFieldValue() will be LazyLoadedOption, and so .getValue() will work on it - provided it's not null, because you cannot call methods on a null.

It's just that static type checking has no clue about what the "real" class is, it only knows it's an Object but that's all it can know, so from STC perspective you are trying to call a non-existing method on an Object.

 

You can tell STC what data type it is

import com.atlassian.jira.issue.customfields.option.LazyLoadedOption

..

def value = ((LazyLoadedOption) issue.getCustomFieldValue(myCustomField))?.getValue()

in which case 'value' will be either null (no option selected), or String - the string representation of that option

And yes I added ? before .getValue() because that is a nullsafe check, or whatever it's called. Basically it means getValue() will not be executed if the value is null. If it's not null - meaning the issue actually has a value in that custom field - then it will execute getValue() on it. Hence, either get the option name, or null. Without ? you will probably get runtime errors because it would be trying to call getValue() on a null - if the issue currently has no custom field value.

 

Of course, LazyLoadedOption will only be the data type for single select fields. The return data type of issue.getCustomFieldValue() will vary depending on the custom field type. It can be a Double (number field), String (text field), List (multi select), Map (cascading) - all of which is dependent on the custom field type.

Daniel McCollum October 19, 2022

@Radek Dostál thank you for that expanded explanation on STC, objects, instances, etc.

added lazyloadedoption into script like so (thinking this is sufficient syntax/placement):

def value = ((LazyLoadedOption) issue.getCustomFieldValue(myCustomField))?.getValue()
CustomField myCustomField = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(CUSTOM_FIELD_ID)

Above snippet within larger script:

import com.atlassian.jira.issue.Issue
import com.atlassian.jira.component.ComponentAccessor
import com.opensymphony.workflow.InvalidInputException
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.customfields.option.LazyLoadedOption

def CUSTOM_FIELD_ID = 10405L
def MESSAGE_ERROR = "Field ${getCustomFieldObject(CUSTOM_FIELD_ID)} is required"
def customFieldManager = ComponentAccessor.getCustomFieldManager()

//Custom field name called Require Design with options yes no none
def value = ((LazyLoadedOption) issue.getCustomFieldValue(myCustomField))?.getValue()
CustomField myCustomField = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(CUSTOM_FIELD_ID)

//Block transition if "Yes" option returned
if(getCustomFieldValue(issue, CUSTOM_FIELD_ID).getValue() == "Yes"){
throw new InvalidInputException("Response cannot be Yes, click Cancel")
}

//Block transition if "None" option returned
if(!getCustomFieldValue(issue, CUSTOM_FIELD_ID)){
throw new InvalidInputException(MESSAGE_ERROR)
}

def getCustomFieldValue(issue, Long fieldId) {
issue.getCustomFieldValue(getCustomFieldObject(fieldId))
}

def getCustomFieldObject(Long fieldId) {
ComponentAccessor.customFieldManager.getCustomFieldObject(fieldId)
Florian Bonniec
Community Champion
October 20, 2022

Hi @Daniel McCollum 

 

This script below should work.

import com.atlassian.jira.issue.Issue

import com.atlassian.jira.component.ComponentAccessor

import com.opensymphony.workflow.InvalidInputException

import com.atlassian.jira.issue.fields.CustomField

import com.atlassian.jira.issue.CustomFieldManager

import com.atlassian.jira.issue.customfields.option.LazyLoadedOption

def CUSTOM_FIELD_ID = 10405

def customFieldManager = ComponentAccessor.getCustomFieldManager()

//Custom field name called Require Design with options yes no none

CustomField myCustomField = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(CUSTOM_FIELD_ID)

def value = ((LazyLoadedOption) issue.getCustomFieldValue(myCustomField))?.getValue()

def MESSAGE_ERROR = "Field ${myCustomField.getName()} is required"

//Block transition if "Yes" option returned

if(value == "Yes"){

throw new InvalidInputException("Response cannot be Yes, click Cancel")

}

//Block transition if "None" option returned

if(!value){

throw new InvalidInputException(MESSAGE_ERROR)

}

Make sure to use Custom Script validator and not Simple Script Validator.

Also when creating a script make sure to define a variable before using it.

If you need more explanation about this script let me know.

 

Regards

Like • Daniel McCollum likes this
Daniel McCollum October 20, 2022

@Florian Bonniec , thanks will give above script a try and test, thank you very much for putting together and time committed!

Using mygroovy plugin for jira software, and chose inline script which looked like a simple script validator.  Seems main difference between custom and simple script validator, in layman terms, is one allows more complex and accommodates use of file - again, high-level view.

image.png

Daniel McCollum October 20, 2022

@Florian Bonniec , have not yet tested yet but would it make a difference to define custom field  (Require Design aka 10405L - note: added "L' to end since that's exactly how it shows in Jira instance) differently?

Considering use of getCustomFieldObject(String id), like so bolded/italicized:

CustomField myCustomField = ComponentAccessor.getCustomFieldManager().getCustomFieldObject('customfield_10405L')

And possibly, this line goes away if above is changed:

def CUSTOM_FIELD_ID = 10405
Florian Bonniec
Community Champion
October 20, 2022

It should work the same, not sure le L is required.

 

I was thinking you was using Scriptrunner, not MyGroovy app. This app is not compatible with Jira version > 8.25.2

 

Regards 

Florian Bonniec
Community Champion
October 20, 2022

@Daniel McCollum use Groovy Inline Script in your case.

It should work even if not compatible but you may have issue in the future as the app is not updated anymore to work with newest Jira version.

 

Regards 

0 votes
Answer accepted
Daniel McCollum October 21, 2022

Hi @Florian Bonniec ,

Your script listed on 10/20/2022 is more efficient, so will need to spend some time comparing against what was written initially on my end - really appreciate your efforts and input!

Script below (from @Florian Bonniec) in Groovy inline script for Jira is without warnings and works successfully, meeting intended purpose of what I was trying to accomplish - see screenshots below script...

import com.atlassian.jira.issue.Issue
import com.atlassian.jira.component.ComponentAccessor
import com.opensymphony.workflow.InvalidInputException
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.customfields.option.LazyLoadedOption

//custom field id (update "00000")
def CUSTOM_FIELD_ID = 00000
def customFieldManager = ComponentAccessor.getCustomFieldManager()

//Custom field name called Require Design with options yes no none
CustomField myCustomField = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(CUSTOM_FIELD_ID)

//Custom field value retrieved
def value = ((LazyLoadedOption) issue.getCustomFieldValue(myCustomField))?.getValue()

//Message error for "None"
def MESSAGE_ERROR = "Field ${myCustomField.getName()} is required"

//Block workflow transition if "Yes" option returned
if(value == "Yes"){
throw new InvalidInputException("Response cannot be Yes, click Cancel")
}

//Block transition if "None" option returned
if(!value){
throw new InvalidInputException(MESSAGE_ERROR)
}

image.png image.png

Suggest an answer

Log in or Sign up to answer