Groovy : Set Cascading Field Value

Deleted user November 17, 2013

Hi everybody,

After looking for a solution myslef, I'm kind out of idea right now. Here's the situation :

Environment : Jira 5.2.11 / Script Runner 2.1.15

When creating an issue, (in post-function) I want to :

- Extract values from a cascading select field (Nature of the Request - cf id:17000)

- Check if the word 'QlikView' is in the first value

- Update another cascading field (System and Sub-System - cf id 11201) with values (existing in the field configuration, let's say Data - QlikView)

So far, I have this script I modified to fit my needs, BUT the updating process doesn't manage to update the cf 11201 value.

When checking the script with the script console, I don't have any error message and it gives me back the correct value (Data - QlikView).

But when I put the script in the workflow, the value isn't updated in the cf 11201

// and if I look at the logs, i have the following message :

// Caused by: java.lang.NoClassDefFoundError: com/opensymphony/user/User

// --> caused by a plugin old version

Do you have any clues about what's wrong?

Thanks!

import org.apache.log4j.Category
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.IssueChangeHolder
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.layout.field.FieldLayoutItem
import com.atlassian.jira.issue.customfields.manager.OptionsManager
import com.atlassian.jira.issue.customfields.view.CustomFieldParams
import com.atlassian.jira.issue.customfields.view.CustomFieldParamsImpl 
import com.atlassian.jira.issue.fields.layout.field.FieldLayoutStorageException
import com.atlassian.jira.issue.customfields.impl.CascadingSelectCFType 
import com.atlassian.jira.issue.customfields.option.Option
import com.atlassian.jira.component.ComponentAccessor
import com.opensymphony.workflow.WorkflowContext;


MutableIssue issue = issue

CustomFieldManager cfManager2 = ComponentManager.getInstance().getCustomFieldManager()
// name of the first cascading field I will extract the value in order to check it
CustomField cf2 = cfManager2.getCustomFieldObject(17000)
String first = issue.getCustomFieldValue(cf2)?.values()*.value[0]
String second = issue.getCustomFieldValue(cf2)?.values()*.value[1]

// name of the cascading field where I want to put my new value
CustomField categoryCF = ComponentManager.getInstance().getCustomFieldManager().getCustomFieldObjectByName("Système et sous-système"); 
CustomFieldParams cfVal = issue.getCustomFieldValue(categoryCF) as CustomFieldParams

if (categoryCF == null || !(categoryCF.getCustomFieldType() instanceof CascadingSelectCFType)) {

log.error("Expected customfield doesn't exist or doesn't have the expected type.");

}
   
OptionsManager optionsManager = ComponentAccessor.getOptionsManager();

Option parentOptionObj = optionsManager.findByOptionId(11124); // value to put in first list     

Option childOptionObj = optionsManager.findByOptionId(11211);  // value to put in second list    

Map<String,Object> newValues = new HashMap<String, String>();

// checking if the value in the first cascading field is correct
if (first.contains('QlikView')) {

newValues.put(null, parentOptionObj); 
newValues.put("1", childOptionObj); 
// then updating the second cascading field with new values
categoryCF.updateValue(null, issue, new ModifiedValue(cfVal,newValues), new DefaultIssueChangeHolder());

issue.store()
}

11 answers

1 accepted

0 votes
Answer accepted
Deleted user November 17, 2013

Ok, if you want to update a blank cascading select field, you just have to put in comment the following lines :

// Object cValue = categoryCF.getValue(cIssue);

// if (cValue == null) {
// log.error("Expected customfield doesn't have any value.");
// }

and replace the last line of the script with :

categoryCF.updateValue(null, cIssue, new ModifiedValue(null, newValues), new DefaultIssueChangeHolder());

2 votes
Robert Mota January 4, 2015

Hi,

Using ComponentAccessor now

import com.atlassian.jira.component.ComponentAccessor
//(...)

CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager()
//(...)

def CUSTOM_FIELD_ID = 11201; // set your Cascading Select custom field Id

//Get your Cascading Select CF Object
def categoryCF = customFieldManager.getCustomFieldObject(CUSTOM_FIELD_ID);

 

If we want to find the Parent and the child Options Object from the string of their context label :

// if you know or you find these strings
String stParentOption = "Targeted parent option";
String stChildOption = "Targeted child option of the selected parent";

// you'll be able to find their Options objects as below
// null as parentId to find the string into the parent options list of the context
Option parentOptionObj = optionsManager.getOptions(categoryCF.getRelevantConfig(cIssue)).getOptionForValue(stParentOption,null)

// knowing the parent Option object, you'll find the children on its Id
Option childOptionObj = optionsManager.getOptions(myCascadingCf.getRelevantConfig(cIssue)).getOptionForValue(stChildOption,parentOptionObj.optionId)

and I add how I store it

def newValues = new HashMap();
newValues.put(null, parentOptionObj);
newValues.put("1", childOptionObj);

classificationObj.updateValue(null, cIssue, new ModifiedValue(null, newValues), changeHolder);

// deprecated way to store the issue 
cIssue.store()
// to be updated with something like
// issueManager.updateIssue(cUser, cIssue, EventDispatchOption.DO_NOT_DISPATCH, false);

 

have fun smile

0 votes
Andy Keyworth May 28, 2015

Which of the scripts above could be used to copy the contents of a select list (single) to a select list (cascading), changing from a single entry to a map form?

0 votes
Robert Mota January 5, 2015

A script below I used to copy values from a source Cascading Select CF, named "Classification Incident", to a target one, named "Classification", using a JQL query filter

/**
 * Created by robert_mota on 18/12/2014.
 * Script to use one shot to copy "Classification Incident" CF
 * into "Classification"
 *
 * Prerequisite : all the values of the source Cascading Select CF 
 * must be in context configuration of the target CF
 */
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.customfields.manager.OptionsManager
import com.atlassian.jira.issue.customfields.option.Option
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.util.IssueChangeHolder
import com.atlassian.jira.web.bean.PagerFilter
import org.apache.log4j.Level
import org.apache.log4j.Logger
import com.atlassian.jira.issue.ModifiedValue

CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager()
IssueChangeHolder changeHolder = new DefaultIssueChangeHolder();

Logger log = Logger.getLogger("com.onresolve.jira.groovy")
log.setLevel(Level.DEBUG)

log.debug("Begin: Fix Classification ****************************");

OptionsManager optionsManager = ComponentAccessor.getOptionsManager();

// to test on 1 issue
//jqlSearch = "project = CSI AND issuetype = incident AND \"Classification Incident\" is not EMPTY and Classification is not EMPTY and key in (CSI-23015)"
// to run on all issues of the search
jqlSearch = "project = CSI AND issuetype = incident AND \"Classification Incident\" is not EMPTY and Classification is not EMPTY"

SearchService searchService = ComponentAccessor.getComponent(SearchService.class)

// ComponentAccessor.getJiraAuthenticationContext().getUser() returns an ApplicationUser
// To get the User needed for SearchService below, use getDirectoryUser() method
User user = ComponentAccessor.getJiraAuthenticationContext().getUser().getDirectoryUser()

IssueManager issueManager = ComponentAccessor.getIssueManager()

if (!user) {
    log.debug("User not found ****************************");
    return false;
}

SearchService.ParseResult parseResult =  searchService.parseQuery(user, jqlSearch)
if (parseResult.isValid()) {
    def searchResult = searchService.search(user, parseResult.getQuery(), PagerFilter.getUnlimitedFilter())
    // Transform issues from DocumentIssueImpl to the "pure" form IssueImpl (some methods don't work with DocumentIssueImps)
    def issues = searchResult.issues.collect {issueManager.getIssueObject(it.id)}
    issues.each {
        MutableIssue incident = it as MutableIssue;

        // Source CF named "Classification Incident" id = 10490
        def classificationIncidentObj = customFieldManager.getCustomFieldObjectByName("Classification Incident");
        // to use ID
        //def classificationIncidentObj = customFieldManager.getCustomFieldObject(10490);

        // Target CF cible Classification id = 10367
        def classificationObj = customFieldManager.getCustomFieldObjectByName("Classification");
        //def classificationObj = customFieldManager.getCustomFieldObject(10367);

        def classificationIncident = classificationIncidentObj.getValue(incident);
        //log.debug ("\n      Incident: Classification Incident Parent: " + classificationIncident.get(null));
        //log.debug ("\n      Incident: Classification Incident Child: " + classificationIncident.get("1"));

        String stParentOption = classificationIncident.get(null);
        String stChildOption = classificationIncident.get("1");
        //log.debug ("\n      Incident: stParentOption = " + stParentOption);
        //log.debug ("\n      Incident: stChildOption = " + stChildOption);

        Option parentOptionObj = optionsManager.getOptions(classificationObj.getRelevantConfig(incident)).getOptionForValue(stParentOption,null)
        //log.debug ("\n      Target Parent Classification = " + parentOptionObj);

        Option childOptionObj = optionsManager.getOptions(classificationObj.getRelevantConfig(incident)).getOptionForValue(stChildOption,parentOptionObj.optionId)
        //log.debug ("\n      Target Classification child ("+ parentOptionObj + ") = " + childOptionObj);

        def newValues = new HashMap();
        newValues.put(null, parentOptionObj);
        newValues.put("1", childOptionObj);

        //log.debug ("\n      Target Classification Parent new value: " + newValues.get(null));
        //log.debug ("\n      Target Classification Child new value: " + newValues.get("1"));

        // To store it
        classificationObj.updateValue(null, incident, new ModifiedValue(null, newValues), changeHolder);

        // deprecated method to store issues
        //incident.store()

        // new way to store issues
        issueManager.updateIssue(user, incident, EventDispatchOption.DO_NOT_DISPATCH, false);
    }
} else {
    log.debug("Invalid JQL: " + jqlSearch);
    return false;
}
log.debug("End: Fix Classification ****************************");
return true;

Regards

0 votes
Deleted user November 17, 2013

Rambanam, because you proposed some solutions for the previous problems, I give you the karma points.

Thanks again! (thanks also to Bharadwaj!)

0 votes
Deleted user November 17, 2013

Ok, I made some progress. Right now, this script can modify the value of a cascading select field already containing values. I have now to make it work with an empty field.

Hope this first script can help some people :

import org.apache.log4j.Category
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.IssueChangeHolder
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.layout.field.FieldLayoutItem
import com.atlassian.jira.issue.customfields.manager.OptionsManager
import com.atlassian.jira.issue.customfields.view.CustomFieldParams
import com.atlassian.jira.issue.customfields.view.CustomFieldParamsImpl 
import com.atlassian.jira.issue.fields.layout.field.FieldLayoutStorageException
import com.atlassian.jira.issue.customfields.impl.CascadingSelectCFType 
import com.atlassian.jira.issue.customfields.option.Option
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.crowd.embedded.api.User;


MutableIssue cIssue = issue

// get customfield
CustomField categoryCF = ComponentManager.getInstance().getCustomFieldManager().getCustomFieldObject(11201);
 
if (categoryCF == null || !(categoryCF.getCustomFieldType() instanceof CascadingSelectCFType)) {
log.error("Expected customfield doesn't exist or doesn't have the expected type.");
}
 
OptionsManager optionsManager = (OptionsManager)ComponentManager.getComponentInstanceOfType(OptionsManager.class);
// get customfield value
Object cValue = categoryCF.getValue(cIssue);

if (cValue == null) {
log.error("Expected customfield doesn't have any value.");
}

Option parentOptionObj = optionsManager.findByOptionId(11124); // get first value to input    

Option childOptionObj = optionsManager.findByOptionId(11211);  // get second value to input     

Map<String,Object> newValues = new HashMap<String, String>();

newValues.put(null, parentOptionObj); //input value in first dropdown

newValues.put("1", childOptionObj); // input value in second dropdown

//update issue and save modifications in database
categoryCF.updateValue(null, cIssue, new ModifiedValue(cValue, newValues), new DefaultIssueChangeHolder()); 
issue.store()

0 votes
RambanamP
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.
November 17, 2013

check the "setFieldValue" method on following class, i hope it will help you

https://github.com/atlassian/jira-suite-utilities/blob/master/src/main/java/com/googlecode/jsu/util/WorkflowUtils.java

Deleted user November 17, 2013

Thanks Rambanam, i'll check and let you know! :)

Cheers!

0 votes
Deleted user November 17, 2013

Thanks Bharadwaj, I followed your advice, but still having troubles to make my script working.

In the logs, I have now a NullPointerException, which I don't get because it works in the Script Console.

Any ideas?

0 votes
Deleted user November 17, 2013

Hi, thanks to both of you.

The opensymphony error was generated by one plugin (condition validator) which was in an old version (because I don't use it, I've dactivated it) BUT, my script still doesn't update the cascading select field.

Bharadwaj Jannu
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.
November 17, 2013

yes in the workflow transition you check any other validators or postfunctions present.

Eventhough you disabled, those were not removed from the transition. You need to manually remove them.

0 votes
Bharadwaj Jannu
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.
November 17, 2013

The above script appear to be correct for me.

You check any validator script running in the transition which uses com/opensymphony/user/User object.

Bharadwaj Jannu
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.
November 17, 2013

it should be import com.atlassian.crowd.embedded.api.User; as prasad suggested

0 votes
RambanamP
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.
November 17, 2013

you have to use following import

import com.atlassian.crowd.embedded.api.User;

opensymphony user has deprecated!!

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events