(For more resources on this topic, see here.)
Workflows are one standout feature which help users to transform JIRA into a user-friendly system. It helps users to define a lifecycle for the issues, depending on the issue type, the purpose for which they are using JIRA, and so on. As the Atlassian documentation says at http://confluence.atlassian.com/display/JIRA/Configuring+Workflow:
A JIRA workflow is the set of steps and transitions an issue goes through during its lifecycle. Workflows typically represent business processes.
JIRA uses Opensymphony’s OSWorkflow which is highly configurable, and more importantly pluggable, to cater for the various requirements. JIRA uses three different plugin modules to add extra functionalities into its workflow, which we will see in detail through this chapter.
To make things easier, JIRA ships with a default workflow. We can’t modify the default workflow, but can copy it into a new workflow and amend it to suit our needs. Before we go into the development aspect of a workflow, it makes sense to understand the various components of a workflow.
The two most important components of a JIRA workflow are Step and Transition. At any point of time, an Issue will be in a step. Each step in the workflow is linked to a workflow Status (http://confluence.atlassian.com/display/JIRA/Defining+%27Status%27+F ield+Values) and it is this status that you will see on the issue at every stage. A transition, on the other hand, is a link between two steps. It allows the user to move an issue from one step to another (which essentially moves the issue from one status to another).
Few key points to remember or understand about a workflow:
OSWorkflow, and hence JIRA, provides us with the option of adding various elements into a workflow transition which can be summarized as follows:
These three elements give us the flexibility of handling the various use cases when an issue is moved from one status to another. JIRA ships with a few built-in conditions, validators, and post functions. There are plugins out there which also provide a wide variety of useful workflow elements. And if you still don’t find the one you are looking for, JIRA lets us write them as plugins. We will see how to do it in the various recipes in this chapter. Hopefully, that gives you a fair idea about the various workflow elements. A lot more on JIRA workflows can be found in the JIRA documentation at http://confluence.atlassian.com/display/JIRA/Configuring+Workflow.
What are workflow conditions? They determine whether a workflow action is available or not. Considering the importance of a workflow in installations and how there is a need to restrict the actions either to a set of people, roles, and so on, or based on some criteria (for example, the field is not empty!), writing workflow conditions is inevitable.
Workflow conditions are created with the help of the workflow-condition module. The following are the key attributes and elements supported. See http://confluence.atlassian.com/display/JIRADEV/Workflow+Plugin+Modules#WorkflowPluginModules-Conditions for more details.
Attributes:
Name | Description |
key | This should be unique within the plugin. |
class | Class to provide contexts for rendered velocity templates. Must implement the com.atlassian.jira.plugin.workflow.WorkflowPluginConditionFactory interface. |
i18n-name-key | The localization key for the human-readable name of the plugin module. |
name | Human-readable name of the workflow condition. |
Elements:
Name | Description |
description | Description of the workflow condition. |
condition-class | Class to determine whether the user can see the workflow transition. Must implement com.opensymphony.workflow.Condition. Recommended to extend the com.atlassian.jira.workflow.condition.AbstractJiraCondition class. |
resource type=”velocity” | Velocity templates for the workflow condition views. |
As usual, create a skeleton plugin. Create an eclipse project using the skeleton plugin and we are good to go!
In this recipe, let’s assume we are going to develop a workflow condition that limits a transition only to the users belonging to a specific project role. The following are the steps to write our condition:
private static final String ROLE_NAME = "role";
private static final String ROLES = "roles";
.......
@Override
protected void getVelocityParamsForEdit(Map<String, Object>
velocityParams, AbstractDescriptor descriptor) {
velocityParams.put(ROLE, getRole(descriptor));
velocityParams.put(ROLES, getProjectRoles());
} @Override
protected void getVelocityParamsForInput(Map<String, Object>
velocityParams) {
velocityParams.put(ROLES, getProjectRoles());
} @Override
protected void getVelocityParamsForView(Map<String, Object>
velocityParams, AbstractDescriptor descriptor) {
velocityParams.put(ROLE, getRole(descriptor));
}
Let’s look at the methods in detail:
private ProjectRole getRole(AbstractDescriptor descriptor){
if (!(descriptor instanceof ConditionDescriptor)) {
throw new IllegalArgumentException("Descriptor must be a
ConditionDescriptor.");
}
ConditionDescriptor functionDescriptor = (ConditionDescriptor)
descriptor;
String role = (String) functionDescriptor.getArgs().get(ROLE);
if (role!=null && role.trim().length()>0)
return getProjectRole(role);
else
return null;
}
Just check if the descriptor is a condition descriptor or not, and then extract the role as shown in the preceding snippet.
<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.id}"
#if ($role && (${field.id}==${role.id}))
SELECTED
#end
>$field.name</option>
#end
</select>
<br><font size="1">Select the role in which the user
should be present!</font>
</td>
</tr>
#if ($role)
User should have ${role.name} Role!
#else
Role Not Defined
#end
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 role = (String)args.get(ROLE);
Long roleId = new Long(role); return projectRoleManager.isUserInProjectRole(user,
projectRoleManager.getProjectRole(roleId), project);
}
The issue on which the condition is checked can be retrieved using the getIssue method implemented in the AbstractJiraCondition class. Similarly, the user can be retrieved using the getCaller method. In the preceding method, projectRoleManager is injected in the constructor, as we have seen before.
public Map<String, String> getDescriptorParams(Map<String, Object>
conditionParams) {
if (conditionParams != null &&
conditionParams.containsKey(ROLE))
{
return EasyMap.build(ROLE,
extractSingleParam(conditionParams, ROLE));
}
// Create a 'hard coded' parameter
return EasyMap.build();
}
The method here builds a map of the key:value pair, where key is ROLE and the value is the role value entered in the input configuration page. The extractSingleParam method is implemented in the AbstractWorkflowPluginFactory class. The extractMultipleParams method can be used if there is more than one parameter to be extracted!
<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>
After the plugin is deployed, we need to modify the workflow to include the condition. The following screenshot is how the condition looks when it is added initially. This, as you now know, is rendered using the input template:
After the condition is added (that is, after selecting the Developers role), the view is rendered using the view template and looks as shown in the following screenshot:
(Move the mouse over the image to enlarge.)
If you try to edit it, the screen will be rendered using the edit template, as shown in the following screenshot:
Note that the Developers role is already selected.
After the workflow is configured, when the user goes to an issue, he/she will be presented with the transition only if he/she is a member of the project role where the issue belongs. It is while viewing the issue that the passesCondition method in the condition class is executed.
Workflow validators are specific validators that check 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 some of them are not satisfied. A typical example would be to check if a particular field is present or not before the issue is moved to a different status.
Workflow validators are created with the help of the workflow- validator module. The following are the key attributes and elements supported.
Attributes:
Name | Description |
key | This should be unique within the plugin. |
class | Class to provide contexts for rendered velocity templates. Must implement the com.atlassian.jira.plugin.workflow.WorkflowPluginValidatorFactory interface. |
i18n-name-key | The localization key for the human-readable name of the plugin module. |
name | Human-readable name of the workflow validator. |
Elements:
Name | Description |
description | Description of the workflow validator. |
validator-class | Class which does the validation. Must implement com.opensymphony.workflow.Validator. |
resource type=”velocity” | Velocity templates for the workflow validator views. |
See http://confluence.atlassian.com/display/JIRADEV/Workflow+Plugin+Modules#WorkflowPluginModules-Validators for more details.
As usual, create a skeleton plugin. Create an eclipse project using the skeleton plugin and we are good to go!
Let us consider writing a validator that checks whether a particular field has a value entered on the issue or not! We can do this using the following steps:
@Override
protected void getVelocityParamsForEdit(Map velocityParams,
AbstractDescriptor descriptor) {
velocityParams.put(FIELD_NAME, getFieldName(descriptor));
velocityParams.put(FIELDS, getCFFields());
} @Override
protected void getVelocityParamsForInput(Map velocityParams) {
velocityParams.put(FIELDS, getCFFields());
} @Override
protected void getVelocityParamsForView(Map velocityParams,
AbstractDescriptor descriptor) {
velocityParams.put(FIELD_NAME, getFieldName(descriptor));
}
You may have noticed that the methods look quite similar to the ones in a workflow condition, except for the business logic! Let us look at the methods in detail:
private String getFieldName(AbstractDescriptor descriptor){
if (!(descriptor instanceof ValidatorDescriptor)) {
throw new IllegalArgumentException('Descriptor must be a
ValidatorDescriptor.');
}
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;
}
Just check if the descriptor is a validator descriptor or not and then extract the field as shown in the preceding snippet.
<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>
#if ($field)
Field '$field' is Required!
#end
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!"); }
}
}
The issue on which the validation is done can be retrieved from the transientVars map. customFieldManager is injected in the constructor as usual.
<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>
Note that we have stored the role name instead of the ID in the workflow, unlike what we did in the workflow condition. However, it is safe to use the ID because administrators can rename the roles, which would then need changes in the workflows.
After the plugin is deployed, we need to modify the workflow to include the validator. The following screenshot is how the validator looks when it is added initially. This, as you now know, is rendered using the input template:
After the validator is added (after selecting the Test Number field), it is rendered using the view template and looks as follows:
If you try to edit it, the screen will be rendered using the edit template, as shown in the following screenshot:
Note that the Test Number field is already selected.
After the workflow is configured, when the user goes to an issue and tries to progress it, the validator will check if the Test Number field has a value or not. It is at this point that the validate method in the FieldValidator class is executed.
If the value is missing, you will see an error, as shown in the following screenshot:
I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…
Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…
Once we learn how to deploy an Ubuntu server, how to manage users, and how…
Key-takeaways: Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…
While developing a web application, or setting dynamic pages and meta tags we need to deal with…
Software architecture is one of the most discussed topics in the software industry today, and…