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.
17 Comments
Hanno
6/13/2011 10:34:21 pm
Hi,
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.
Reply
J-Tricks
6/13/2011 11:39:03 pm
Sorry, I meant *Remove productDataVersion in the earlier comment.
Reply
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?
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!
Reply
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? 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
6/18/2012 06:57:55 am
Better yet, I can throw both conditions this way...
Reply
6/18/2012 07:26:55 am
Correction: the throw bock needs to be inside the test for the field value in NULL
Reply
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.
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.
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. |
AuthorJobin Kuruvilla - Works in Adaptavist as Head of DevOps Professional Services. Categories
All
Archives
October 2016
|