J tricks - Little JIRA Tricks
  • Home
  • Plugins ↓
    • JQL Tricks Plugin
      • JQL Tricks Plugin - Cloud
        • JQLT Cloud Installation
        • JQLT Cloud Configuration
        • JQLT Cloud Usage
        • JQLT Cloud License
        • JQLT Cloud FAQ
      • JQL Tricks Plugin - DC
        • JQLT DC Installation
        • JQLT DC Configuration
        • JQLT DC Usage
          • JQLT Issue Functions
          • JQLT Subtask Functions
          • JQLT Links Functions
          • JQLT Development Functions
          • JQLT Worklog Functions
          • JQLT Project Functions
          • JQLT Component Functions
          • JQLT Version Functions
          • JQLT Group Functions
          • JQLT User Functions
          • JQLT Date Functions
        • JQLT DC License
        • JQLT DC FAQ
        • JQLT DC Known Issues
        • JQLT DC Performance
      • JQL Tricks Cloud Migration
    • Simplified Planner
      • J-Planner Installation
      • J-Planner Configuration
      • J-Planner Usage
        • Creating a plan
        • Editing a plan
        • Deleting a plan
        • Viewing a plan
        • Modifying a plan
      • J-Planner FAQ
    • Atla-Search Plugin
      • Atla-Search Installation
      • Atla-Search Configuration
      • Atla-Search Usage
      • Atla-Search License
      • Atla-Search FAQ
    • Heroku for Compass App
      • Heroku for Compass Installation
      • Heroku for Compass Configuration
      • Heroku for Compass Usage
    • Copy to subtask Plugin
    • All Plugins
  • Tutorials
  • The Book
  • Contact Us
  • Home
  • Plugins ↓
    • JQL Tricks Plugin
      • JQL Tricks Plugin - Cloud
        • JQLT Cloud Installation
        • JQLT Cloud Configuration
        • JQLT Cloud Usage
        • JQLT Cloud License
        • JQLT Cloud FAQ
      • JQL Tricks Plugin - DC
        • JQLT DC Installation
        • JQLT DC Configuration
        • JQLT DC Usage
          • JQLT Issue Functions
          • JQLT Subtask Functions
          • JQLT Links Functions
          • JQLT Development Functions
          • JQLT Worklog Functions
          • JQLT Project Functions
          • JQLT Component Functions
          • JQLT Version Functions
          • JQLT Group Functions
          • JQLT User Functions
          • JQLT Date Functions
        • JQLT DC License
        • JQLT DC FAQ
        • JQLT DC Known Issues
        • JQLT DC Performance
      • JQL Tricks Cloud Migration
    • Simplified Planner
      • J-Planner Installation
      • J-Planner Configuration
      • J-Planner Usage
        • Creating a plan
        • Editing a plan
        • Deleting a plan
        • Viewing a plan
        • Modifying a plan
      • J-Planner FAQ
    • Atla-Search Plugin
      • Atla-Search Installation
      • Atla-Search Configuration
      • Atla-Search Usage
      • Atla-Search License
      • Atla-Search FAQ
    • Heroku for Compass App
      • Heroku for Compass Installation
      • Heroku for Compass Configuration
      • Heroku for Compass Usage
    • Copy to subtask Plugin
    • All Plugins
  • Tutorials
  • The Book
  • Contact Us

Writing a Workflow Validator!

8/17/2010

17 Comments

 
In this tutorial, let us have a look at workflow validators. If you have gone through our workflow post function and condition tutorials, this should be damn easy!

Workflow validators are specific validators that checks whether some pre-defined constraints are satisfied or not while progressing on a workflow.The constraints are configured in the workflow and the user will get an error if the same is not satisfied. A typical example would be to check if a particular field is present or not before the status of the issue is moved to a different status.
Have a look here for more theory on workflow validators.

As usual, have your skeleton plugin ready. Let us start with the atlassian-plugin.xml which you can find in your skeleton plugin. 

The workflow validator module in the plugin descriptor looks like this:
<workflow-validator key="field-validator" name="Field Validator"
    class="com.jtricks.FieldValidatorFactory">
    <description>Field Not Empty Workflow Validator</description>

    <validator-class>
        com.jtricks.FieldValidator
    </validator-class>

    <resource type="velocity" name="view"
        location="templates/com/jtricks/view-fieldValidator.vm"/>
    <resource type="velocity" name="input-parameters"
        location="templates/com/jtricks/edit-fieldValidator.vm"/>
    <resource type="velocity" name="edit-parameters"
        location="templates/com/jtricks/edit-fieldValidator.vm"/>
</workflow-validator>

Just like other plugin modules, key should be a unique value. The class here is used to extract the input parameters that are used in defining the validator. To make it clear, the inputs here are not the input while performing the workflow action but the inputs in defining the validator. In our case, we are going to take an input value (custom field) and hence we create a class FieldValidatorFactory extending AbstractWorkflowPluginFactory. The class must implement WorkflowPluginValidatorFactory and that will give you some unimplemented methods which we will see soon.

Now we come to the next important class which is the validator-class. This is the place where the actual validation is done and we need to implement Validator here. We will more about both these classes shortly.

We also have 3 views in a validator depending on your requirement (Much similar to post functions and conditions!). view, input-parameters and edit-parameters. As you might have guessed already, input-parameters and edit-parameters are required only if there are user inputs while setting the post function.

Let us now have a look in detail.

FieldValidatorFactory

As mentioned earlier, this is the class to define the inputs and set the velocity context. In out case we have a single input 'field' which is the name of a custom field. Here is what the class look like:
public class FieldValidatorFactory extends AbstractWorkflowPluginFactory implements WorkflowPluginValidatorFactory {
    private static final String FIELD_NAME = "field";
    private static final String FIELDS = "fields";
    private static final String NOT_DEFINED = "Not Defined";

    private final CustomFieldManager customFieldManager;

    public FieldValidatorFactory(CustomFieldManager customFieldManager) {
        this.customFieldManager = customFieldManager;
    }

    @Override
    protected void getVelocityParamsForEdit(Map<String, Object> velocityParams, AbstractDescriptor descriptor) {
        velocityParams.put(FIELD_NAME, getFieldName(descriptor));
        velocityParams.put(FIELDS, getCFFields());
    }

    @Override
    protected void getVelocityParamsForInput(Map<String, Object> velocityParams) {
        velocityParams.put(FIELDS, getCFFields());
    }

    @Override
    protected void getVelocityParamsForView(Map<String, Object> velocityParams, AbstractDescriptor descriptor) {
        velocityParams.put(FIELD_NAME, getFieldName(descriptor));
    }

    @SuppressWarnings("unchecked")
    public Map<String, String> getDescriptorParams(Map<String, Object> conditionParams) {
        if (conditionParams != null && conditionParams.containsKey(FIELD_NAME)) {
            return EasyMap.build(FIELD_NAME, extractSingleParam(conditionParams, FIELD_NAME));
        }

        // Create a 'hard coded' parameter
        return EasyMap.build();
    }

    private String getFieldName(AbstractDescriptor descriptor) {
        //Extract field from the workflow
    }

    private Collection<CustomField> getCFFields() {
        //Get list of custom fields
    }
}

Let us take it one by one. The 3 methods, as the name suggests, getVelocityParamsForInput, getVelocityParamsForEdit and getVelocityParamsForView are for populating the velocity parameters for the 3 diferent scenarios. In our case, we populate the params with the 'field' variable and 'fields' (the list of all customfields) for Edit.

getFieldName(descriptor) is the method that retrieves the field from the descriptor and that is done as follows:
private String getFieldName(AbstractDescriptor descriptor) {
    if (!(descriptor instanceof ValidatorDescriptor)) {
        throw new IllegalArgumentException("Descriptor must be a ConditionDescriptor.");
    }

    ValidatorDescriptor validatorDescriptor = (ValidatorDescriptor) descriptor;

    String field = (String) validatorDescriptor.getArgs().get(FIELD_NAME);
    if (field != null && field.trim().length() > 0)
        return field;
    else
        return NOT_DEFINED;
}

Check if the descriptor is an instance of ValidatorDescriptor and if it is, validatorDescriptor.getArgs() gets you a Map with all the variables in it. In our case, we retrieve 'field'. Returns 'NOT_DEFINED' if the retrieved value is null or Empty.

We have one more method getDescriptorParams in the class and this just return a map of sanitized parameters which will be passed into workflow plugin instances from the values in array form submitted by velocity. More info on that here.

getCFFields() does nothing but retrieves all the customfields:
List<CustomField> customFields = customFieldManager.getCustomFieldObjects();
return customFields;

FieldValidator
 
Let us now move to the actual validator class. Here is how it looks like:
public class FieldValidator implements Validator{   
    private final CustomFieldManager customFieldManager;    
    private static final String FIELD_NAME = "field";    

    public FieldValidator(CustomFieldManager customFieldManager) {
        this.customFieldManager = customFieldManager;
    }

    public void validate(Map transientVars, Map args, PropertySet ps) throws InvalidInputException, WorkflowException {
        Issue issue = (Issue) transientVars.get("issue");
        String field = (String) args.get(FIELD_NAME);    
        
        CustomField customField = customFieldManager.getCustomFieldObjectByName(field);
        
        if (customField!=null){
            //Check if the custom field value is NULL
            if (issue.getCustomFieldValue(customField) == null){
                throw new InvalidInputException("The field:"+field+" is required!");
            }
        }
    }
}

When you implement the Validator, you will have to implement the validate method and that is what will be executed when the workflow action is performed!. In our case, we retrieve the field name from the map and checks issue is populated with that customfield. You can find the full code at the end of the tutorial.

Note: Please note that some fields may not be configured for that issue and you can take it into account when you actually implement it!

Templates

Before We wrap up, let us quickly look at the velocity templates. We are using the same template 'edit-fieldValidator.vm' for both input-paramers and edit-parameters.
<tr bgcolor="#ffffff">
    <td align="right" valign="top" bgcolor="#fffff0">
        <span class="label">Custom Fields:</span>
    </td>
    <td bgcolor="#ffffff" nowrap>
        <select name="field" id="field">
        #foreach ($cf in $fields)
            <option value="$cf.name"
            #if ($cf.name.equals($field))
                SELECTED
            #end
            >$cf.name</option>
        #end
        </select>
        <br><font size="1">Select the Custom Field to be validated for NULL</font>
    </td>
</tr>

$fields is populated both in the getVelocityParamsForInput and getVelocityParamsForEdit earlier! We also had $field in the getVelocityParamsForEdit context which is the field already selected. In the template, we populate the select list options from $fields and keep the option matching $field as selected.

view-fieldValidator.vm looks like this:
#if ($field)
    Field '$field' is Required.
#end

This will appear in the validators tab on the workflow tranisition once added succesfully.

We now have our validator ready. Deploy the plugin into jira-home/plugins/installed-plugins (WEB-INF/lib if you created plugin-1 version), Create the Validator to check for a valid custom field in a valid workflow and test it!!

More details on workflow validator module can be found here! Njoy!!

Download the full source code at the end of the tutorial. And don't forget to post your comments/feedback!

Note: The tutorial is just to explain the basic concepts of Validators. Modify the buisness logic accordingly! And one tip on the custom fields - Custom field names are not unique and so it is advised to use the id for storing the the descriptor. You can anytime retrieve the name for displaying in the velocity etc.
field-not-empty-validator.zip
File Size: 6 kb
File Type: zip
Download File

17 Comments
Hanno
6/13/2011 10:34:21 pm

Hi,

I tried to use the sample in Jira 4.3.4 and get the error "Could not load validator class".

Is this due to incomaptibilities between 4.0.2 and 4.3.4 ? If so, what is the correct version for jira.data.version that fits <jira.version>4.3.4</jira.version> ?

Are there any known issues?

Reply
J-Tricks
6/13/2011 11:36:39 pm

Yes, it should be due to compatibility. Try the data version as 4.3 (major version). That should work.

Alternatively, you can remove the data version all-together if you are not doing any testing. Remote productDataVersion attribute from maven-jira-plugin configuration as well!

Reply
J-Tricks
6/13/2011 11:39:03 pm

Sorry, I meant *Remove productDataVersion in the earlier comment.

Try 4.3 as data version first as that should work.

Reply
David Jellison link
6/17/2012 10:38:25 pm

How do I update the help text of the field with validation instructions under the field when the validation fails?

if (customField!=null){
//Check if the custom field value is NULL
if (issue.getCustomFieldValue(customField) == null){
// ***** update help text for field here *****
throw new InvalidInputException("The field:"+field+" is required!");
}
}

Reply
J-Tricks
6/18/2012 01:42:19 am

Personally, I don't think it is a good idea because the field description is not for that particular screen and user session alone. It is for all screens where the custom field us used and also for all user session. So if you change the field description, it is going to confuse a few for sure!

Why don't you add the details in the error message itself?

Reply
David Jellison link
6/18/2012 01:52:03 am

I want to call attention to the field the message refers to. I'm validating the system Priority field and want to apply text in red under the field like field validation in most applications. The exception banner appears at the top of the page, but the submit for the form is at the bottom of the page. Both visual treatments would be nice. The problem with just the exception is that the error message banner is not seen while scrolled to the bottom of the page, even though the Priority field is visible. Do you have any ideas about another way to accomplish this easily? I see the Behaviours Plugin does this, but seems quite complex to implement it they way they did.

J-Tricks
6/18/2012 02:07:40 am

Can you try passing the field name as the first argument when the validation error is thrown?

throw new InvalidInputException(customfield_10000, "The field:"+field+" is required!");

where customfield_10000 is the customfield id!

David Jellison link
6/18/2012 05:53:17 am

Yes, that works! Thank you. :) You may want to add this as a tip in your book.

Reply
J-Ticks
6/18/2012 07:09:59 am

It is tough adding things to book but hey, you have my blog ;)

Reply
David Jellison link
6/18/2012 06:57:55 am

Better yet, I can throw both conditions this way...

InvalidInputException ex = new InvalidInputException();
String errorMsg = "The field:"+field+" is required!";
if (customField!=null){
//Check if the custom field value is NULL
if (issue.getCustomFieldValue(customField) == null){
ex.addError(errorMsg);
ex.addError(customField,errorMsg);
}
if ( ex.getErrors() != null ) {
throw ex;
}
}

Reply
David Jellison link
6/18/2012 07:26:55 am

Correction: the throw bock needs to be inside the test for the field value in NULL

InvalidInputException ex = new InvalidInputException();
String errorMsg = "The field:"+field+" is required!";
if (customField!=null){
//Check if the custom field value is NULL
if (issue.getCustomFieldValue(customField) == null){
ex.addError(errorMsg);
ex.addError(customField,errorMsg);
if ( ex.getErrors() != null ) {
throw ex;
}
}
}

Reply
Kalynn link
7/23/2012 10:09:20 am

I use the workflow transition validator options as they provide many of these options using the UI. And, given that we are an OnDemand customer, that is all we can use.

However, I was wondering, if you have used the UI or if there is a way to set as a property for the step, if you can require a field on save when it is in that specific state. For example, on Create, I cannot require it because one of our processes is to create by cloning and fields cannot be set; same reason I cannot set it in the field configuration to make it required.

However, when the user edits the issue going forward, I want to make sure certain fields are required.

Any suggestions are much appreciated!

Reply
J-Tricks
7/24/2012 03:22:28 am

You won't be able to do that. One option you might want to look is to create a workflow transition that lets you edit those mandatory fields and add a "Field mandatory" validator for those fields.

Put the fields only on the transition screen and not on the edit screen.

Reply
Mayuresh
12/19/2014 09:43:20 am

Hello. I am new to JIRA and am trying to configure JIRA to make the best use of its features in my company. I came across this trick and I need to use it. But I am not sure if it can be used for a Cloud version of JIRA. If yes could you give me a small starting point so that I can look deeper into it. Thanks in advance! :-)

Reply
J-Tricks
12/21/2014 01:57:31 am

Unfortunately, you can not this in Cloud version. Check out Atlassian Connect to see what you can do with the cloud version.

Reply
Achyuth ram
7/10/2017 07:20:22 am

Hello, I am trying to develop this plugin but couldnt able to find the ui for custom fields populating and only one button add is found which is redirecting to error 500.Can you please help me.

Reply
J-Tricks
7/14/2017 11:20:15 am

You mean, you do not have the UI for the validator? Did you add all the templates and include it in the definition in atlassian-plugin.xml?

Reply

Your comment will be posted after it is approved.


Leave a Reply.

    Enter your email address:

    Author

    Jobin Kuruvilla - Works in Adaptavist as Head of DevOps Professional Services. 

    Author of JIRA Development Cookbook and JIRA 5.x Development Cookbook.


    RSS Feed

    Categories

    All
    Acive Objects
    Ajs
    Book
    Components
    Condition
    Custom Fields
    Customization
    Events
    Gadgets
    Javascript
    Jql
    Listener
    Mail
    Permissions
    Plugin Framework
    Post Function
    Properties
    Remote Invocation
    Reporting
    Rest
    Scheduled Tasks
    Search
    Services
    Soap
    Summit
    User Interface
    Validator
    Webwork Actions
    Workflow

    Archives

    October 2016
    August 2016
    March 2016
    January 2016
    December 2015
    May 2014
    December 2013
    November 2013
    July 2013
    June 2013
    April 2013
    October 2012
    September 2012
    August 2012
    July 2012
    May 2012
    March 2012
    February 2012
    January 2012
    December 2011
    November 2011
    June 2011
    May 2011
    April 2011
    March 2011
    February 2011
    January 2011
    November 2010
    October 2010
    September 2010
    August 2010

SUPPORT
APPS
TUTORIALS
THE BOOK
© J-Tricks