In this tutorial, We attempt to take you through creating workflow conditions. If you have gone through our workflow post function tutorial, it going to be a breeze!
So what are workflow conditions? They determine whether a workflow action is available or not. Considering the importance of workflow in installations and how there is a need to restrict the actions either to a set of people, roles etc or based on some criteria (Eg: the field is not empty!), writing workflow conditions is inevitable.
Have a look here for more theory on workflow conditions.
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 condition module in the plugin descriptor looks like this:
<workflow-condition key="role-condition" name="Role Based Condition" class="com.jtricks.RoleConditionFactory"> <description>Role Based Workflow Condition</description> <condition-class> com.jtricks.RoleCondition </condition-class> <resource type="velocity" name="view" location="templates/com/jtricks/view-roleCondition.vm"/> <resource type="velocity" name="input-parameters" location="templates/com/jtricks/edit-roleCondition.vm"/> <resource type="velocity" name="edit-parameters" location="templates/com/jtricks/edit-roleCondition.vm"/> </workflow-condition>
Similar to 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 condition. To make it clear, the inputs here are not the input while performing the workflow action but the inputs in defining the condition. In our case, we are going to take an input value (project role) and hence we create a class RoleConditionFactory extending AbstractWorkflowPluginFactory. The class must implement WorkflowPluginConditionFactory and that will give you some unimplemented methods which we will see soon.
Now we come to the next important class which is the condition-class. This is the place where the actual condition is checked and we need to extend AbstractJiraCondition here. We will more about both these classes shortly. We also have 3 views in a condition depending on your requirement (Much similar to post functions!). 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 look a bit deeper. RoleConditionFactory As mentioned earlier, this is the class to define the inputs and set the velocity context. In out case we have a single input 'project role'. Here is what the class look like:
public class RoleConditionFactory extends AbstractWorkflowPluginFactory implements WorkflowPluginConditionFactory{ private static final String ROLE_NAME = 'role'; private static final String ROLES = 'roles'; private static final String NOT_DEFINED = 'Not Defined'; private final ProjectRoleManager projectRoleManager; public RoleConditionFactory(ProjectRoleManager projectRoleManager) { this.projectRoleManager = projectRoleManager; } @Override protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor) { velocityParams.put(ROLE_NAME, getRoleName(descriptor)); velocityParams.put(ROLES, getProjectRoles()); } @Override protected void getVelocityParamsForInput(Map velocityParams) { velocityParams.put(ROLES, getProjectRoles()); } @Override protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor) { velocityParams.put(ROLE_NAME, getRoleName(descriptor)); } @SuppressWarnings('unchecked') public Map getDescriptorParams(Map conditionParams) { if (conditionParams != null && conditionParams.containsKey(ROLE_NAME)) { return EasyMap.build(ROLE_NAME, extractSingleParam(conditionParams, ROLE_NAME)); } // Create a 'hard coded' parameter return EasyMap.build(); } private String getRoleName(AbstractDescriptor descriptor){ //Extract Role from the workflow } private Collection getProjectRoles(){ //Get list of project Roles } }
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 'role' variable and 'roles' (the list of all project roles) for Edit.
getRoleName(descriptor) is the method that retrieves the role from the descriptor and that is done as follows:
private String getRoleName(AbstractDescriptor descriptor){ if (!(descriptor instanceof ConditionDescriptor)) { throw new IllegalArgumentException('Descriptor must be a ConditionDescriptor.'); } ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor; String role = (String) conditionDescriptor.getArgs().get(ROLE_NAME); if (role!=null && role.trim().length()>0) return role; else return NOT_DEFINED; }
Check if the descriptor is an instance of ConditionDescriptor and if it is, conditionDescriptor.getArgs() gets you a Map with all the variables in it. In our case, we retrieve 'role'. 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. getProjectRoles() does nothing but retrieves all the project roles:
Collection projRoles = projectRoleManager.getProjectRoles(); return Collections.unmodifiableCollection(projRoles);
RoleCondition
Let us now move to the actual condition class. Here is how it looks like:
public class RoleCondition extends AbstractJiraCondition{ private static final String ROLE_NAME = "role"; public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException { Issue issue = getIssue(transientVars); User user = getCaller(transientVars, args); Project project = issue.getProjectObject(); String roleName = (String) args.get(ROLE_NAME); return projectRoleManager.isUserInProjectRole(user, projectRoleManager.getProjectRole(roleName), project); } }
When you extend the AbstractJiraCondition, you will have to implement the passesCondition method and that is what will be checked when the Issue is viewed!. In our case, we retrieve the role name from the map and checks if the user belongs to that role. You can find the full code at the end of the tutorial.
Templates Before We wrap up, let us quickly look at the velocity templates. We are using the same template 'edit-roleCondition.vm' for both input-parameters and edit-parameters.
<tr bgcolor="#ffffff"> <td align="right" valign="top" bgcolor="#fffff0"> <span class="label">Project Role:</span> </td> <td bgcolor="#ffffff" nowrap> <select name="role" id="role"> #foreach ($field in $roles) <option value="$field.name" #if ($field.name.equals($role)) SELECTED #end >$field.name</option> #end </select> <br><font size="1">Select the role in which the user should be present!</font> </td> </tr>
$roles is populated both in the getVelocityParamsForInput and getVelocityParamsForEdit earlier! We also had $role in the getVelocityParamsForEdit context which is the role already selected. In the template, we populate the select list options from $roles and keep the option matching $role as selected.
view-roleCondition.vm looks like this:
#if ($role) User should have $role Role! #end
This will appear in the conditions tab on the workflow tranisition once added succesfully.
We now have our condition ready. Deploy the plugin into jira-home/plugins/installed-plugins (WEB-INF/lib if you created plugin-1 version), Create the Condition in a valid workflow, Add yourself to the relevant project roles and test it!! More details on workflow condition module can be found here! Njoy!! Download the full source code at the end of the page. And let us know your comments/feedback! Note: The tutorial is just to explain the basic concepts of Conditions. Modify the buisness logic accordingly!
6 Comments
MULKI MOIES HUSSAIN
5/29/2011 06:33:27 pm
hi
Reply
7/8/2013 12:15:52 am
You have helped a lot for helping me understand the basics of JIRA and now workflow condition. I believe in DIY and learning techniques rather than classroom teaching. Thanks Jobin, for the excellent article.
Reply
Aleksandr
5/21/2014 10:00:08 pm
Method getUserName(AbstractDescriptor descriptor) must be
Reply
J-Tricks
5/22/2014 01:31:21 am
Thanks, I corrected it. Glad the tutorial was useful!
Reply
J-Tricks
7/3/2014 03:55:17 am
You can just implement the "WorkflowPluginConditionFactory' interface and have just the "view" template. Both getVelocityParams and getDescriptorParams can return empy Map.
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
|