





















































(For more resources related to this topic, see here.)
What are workflow conditions? They determine whether a workflow action is available or not. Considering the importance of a workflow in business processes and how there is a need to restrict the actions, either to a set of people (groups, roles, and so on) or based on some criteria (for example, the field is not empty), writing workflow conditions is almost inevitable.
Workflow conditions are created with the help of the workflow-condition module. The following are the key attributes and elements supported. Visit https://developer.atlassian.com/jiradev/jira-platform/building-jira-add-ons/jira-plugins2-overview/jira-plugin-module-types/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. It 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. It must implement com.opensymphony.workflow.Condition. It is 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. Then 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 = "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>
<td class="fieldLabelArea">Project Role: </td>
<td 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) {
Issue issue = getIssue(transientVars);
ApplicationUser user = getCallerUser(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 thegetIssuemethod implemented in theAbstractJiraConditionclass. Similarly, the user can be retrieved using the getCallerUsermethod. In the preceding method, projectRoleManageris injected in the constructor, as we have seen before.
Make sure you are using the appropriate scanner annotations for constructor injection, if the Atlassian Spring Scanner is defined in the pom.xml. See https://bitbucket.org/atlassian/atlassian-spring-scannerfor more details.
public Map<String, String> getDescriptorParams(Map<String, Object> conditionParams) {
if (conditionParams != null && conditionParams.containsKey(ROLE)) {
return MapBuilder.build(ROLE, extractSingleParam(conditionParams, ROLE));
}
// Create a 'hard coded' parameter
return MapBuilder.emptyMap();
}
The method here builds a map of thekey:valuepair, where key isROLEand the value is the role value entered in the input configuration page. TheextractSingleParammethod is implemented in theAbstractWorkflowPluginFactoryclass. TheextractMultipleParamsmethod can be used if there is more than one parameter to be extracted!
<workflow-condition key="role-condition" name="Role Based Condition" i18n-name-key="role-condition.name" class="com.jtricks.jira.workflow.RoleConditionFactory">
<description key="role-condition.description">Role Based Workflow Condition</description>
<condition-class>com.jtricks.jira.workflow.RoleCondition</condition-class>
<resource type="velocity" name="view" location="templates/conditions/role-condition.vm"/>
<resource type="velocity" name="input-parameters" location="templates/conditions/role-condition-input.vm"/>
<resource type="velocity" name="edit-parameters" location="templates/conditions/role-condition-input.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 Administratorsrole[N1] ), the view is rendered using the view template and looks as shown in the following screenshot:
If you try to edit it, the screen will be rendered using the same input template and the Administrators role, or whichever role was selected earlier, will be pre-selected.
After the workflow is configured, when the user goes to an issue, they will be presented with the transition only if they are 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.
Active Objects represent a technology used by JIRA to allow per-plugin storage. This gives the plugin developers a real protected database where they can store the data belonging to their plugin and which other plugins won't be able to access. In this recipe, we will see how we can store an address entity in the database using Active Objects.
You can read more about Active Objects at:
http://java.net/projects/activeobjects/pages/Home
Create a skeleton plugin using the Atlassian Plugin SDK[SafisEd2] .
Following are the steps to use Active Objects in the plugin:
<dependency>
<groupId>com.atlassian.activeobjects</groupId>
<artifactId>activeobjects-plugin</artifactId>
<version>${ao.version}</version>
<scope>provided</scope>
</dependency>
<ao key="ao-module">
<description>The configuration of the Active Objects service</description>
<entity>com.jtricks.entity.AddressEntity</entity>
</ao>
As you can see, the module has a unique key and it points to an entity we are going to define [SafisEd3] later, AddressEntity in this case.
<component-import key="ao" name="Active Objects components" interface="com.atlassian.activeobjects.external.ActiveObjects">
<description>Access to the Active Objects service</description>
</component-import>
Note that this step is not required if you are using the Atlassian Spring Scanner. Instead, you can use the @ComponentImport annotation, while injecting ActiveObjects in the constructor.
public interface AddressEntity extends Entity {
public String getName();
public void setName(String name);
public String getState();
public void setState(String state);
public String getCountry();
public void setCountry(String country);
}
By doing this, we have set up the entity to facilitate the storage of all the three attributes.
We can now create, modify, or delete the data using the ActiveObjects component. The component can be instantiated by injecting it into the constructor:
private ActiveObjects ao;
@Inject
public ManageActiveObjects(@ComponentImport ActiveObjects ao) {
this.ao = ao;
}
AddressEntity addressEntity = ao.create(AddressEntity.class);
addressEntity.setName(name);
addressEntity.setState(state);
addressEntity.setCountry(country);
addressEntity.save();
Details can be read either using the ID, which is the primary key, or by querying the data using a net.java.ao.Queryobject. Using the ID is as simple as is shown in the following code line:
AddressEntity addressEntity = ao.get(AddressEntity.class, id);
The Query object can be used as follows:
AddressEntity[] addressEntities = ao.find(AddressEntity.class, Query.select().where("name = ?", name));
for (AddressEntity addressEntity : addressEntities) {
System.out.println("Name:"+addressEntity.getName()+", State:"+addressEntity.getState()+", Country:"+addressEntity.getCountry());
}
Here, we are querying for all records with a given name.
Once you get hold of an entity by either means, we can edit the contents simply by using the setter method:
addressEntity.setState(newState);
addressEntity.save();
Deleting is even simpler!
ao.delete(addressEntity);
Behind the scenes, separate tables are created in the JIRA database for every entity that we add. The Active Objects service interacts with these tables to do the work.
If you look at the database, a table of the name AO_{SOME_HEX}_MY_OBJECT is created for every entity named MyObject belonging to a plugin with the key com.example.ao.myplugin, where:
For every attribute with the getter method, getSomeAttribute, defined in the entity interface, a column is created in the table with the name SOME_ATTRIBUTE using the Java Beans naming convention—separating the two words by an underscore and keeping them both in upper case.
In our AddressEntity example, we have the following table, ao_a2a665_address_entity, as follows:
If you navigate to Administration | System | Advanced | Plugin Data Storage, you can find out all the tables created using Active Objects, as shown here:
As you can see, the table created using our example plugin is listed along with the tables created by other standard JIRA plugins.
Lots more about Active Objects can be read at:
https://developer.atlassian.com/docs/atlassian-platform-common-components/active-objects
In this article, just a couple of JIRA functionalities are explained. For more information you can refer to Jira 7 Development Cookbook, Third Edition. This book is your one-stop resource for mastering JIRA extension and customization. You will learn how to create your own JIRA plugins, customize the look-and-feel of your JIRA UI, work with workflows, issues, custom fields, and much more.