JIRA provides the ability to create customized workflows, which gives the user full control over the life cycle of a JIRA issue. This allows the workflow designer to specify:
This tutorial focuses on the condition and post function elements of a workflow. We'll show you how to create a custom condition and post function, and how they are integrated with JIRA through the add-on system. We've also included an addition sub-routine that shows you how to keep an issue open until everything blocking it is closed.
In order to make a custom workflow element (e.g condition, post function) available within JIRA, you'll need to create an add-on with a workflow module. The add-on will consist of the following components (all contained in a single JAR):
These are described in more detail below.
A condition restricts the execution of a workflow transition until certain criteria are met. If the condition fails, the transition link will not be available on the 'View Issue' page.
This section of the tutorial focuses on the condition element and provides an example custom condition which can be plugged into JIRA.
JIRA provides a number of system conditions available on setup - DisallowIfInStepCondition, AllowOnlyAssignee, IssueAssignedCondition, etc, each allowing the user to define when a worklfow transition becomes available.
The SubTaskBlockingCondition (another system condition) determines if a transition is available for an issue based on the status of its associated sub-tasks. The user specifies a list of statuses that will permit the transition to be available.
For example, the 'Close Issue' workflow transition link for an issue can be conditioned to be only available if all related sub-tasks are associated with the 'Closed' status. In effect, this transition link is not available for the parent issue until all sub-tasks are closed.
For developers designing a custom workflow condition, we recommend that the custom condition class extend the AbstractJiraCondition
class. In order to avoid multiple database calls to retrieve the original issue object for the condition check, there are two possibilities available to the condition designer. Firstly, the custom condition class can overwrite the following method:
1 2Issue getIssue(Map transientVars)
The logic within this method should retrieve the original issue object as required.
Alternatively, if the getIssue
method is not overwritten, it is possible to pass the original issue object to the transientVars
map, for example:
1 2GenericValue origianlIssueGV = ComponentManager.getInstance().getIssueManager().getIssue(issue.getId()); fields.put(AbstractJiraCondition.ORIGNAL_ISSUE_KEY, IssueImpl.getIssueObject(origianlIssueGV));
In this instance, the fields
object will be passed to the getIssue method as the transientVars map.
This ensures that the original issue is examined during the condition check and minimal database calls are made.
This example provides the reverse condition of the SubTaskBlockingFunction,
in that it determines if a transition is available for a sub-task based on the status of its associated parent issue.
In this example, the condition has been configured to display the workflow transition 'Reopen' for a sub-task, only if the parent issue is associated with an unresolved status (e.g. 'Open', 'In Progress' , Unresolved').
The condition is applied to the 'Reopen' transition in a copy of the default JIRA workflow associated with the 'Sub-Task' issue type.
In effect, the condition will prevent the transition for any sub-task from the 'Closed' to the 'Reopened' status, if the parent issue is not associated with an unresolved status.
The condition logic is contained in the class ParentIssueBlockingCondition
class that implements the interface Condition
.
The only method requiring implementation is the passesCondition(...)
method. Within this example, this method retrieves the parent issue and then determines if its associated status is contained in the user specified list of statuses. The condition passes if the specified list of statuses contains the status associated with the parent issue.
The list of statuses is specified when adding the workflow condition to a transition.
The class WorkflowParentIssueBlockingConditionFactoryImpl
is also included - this class manages passing the required parameters to the resource templates.
The workflow condition requires a number of resources in order to display the input, edit and view screens.
In this example, a velocity template is provided for each screen:
allowing the user to initially specify the statuses which will result in a 'pass', to edit these statuses and also a screen displaying the selected statuses.
As with all plugins, the workflow condition must be defined in a file named atlassian-plugin.xml and be located in the root of the JAR file.
The definition of the ParentIssueBlockingCondition
condition is as follows:
1 2<atlassian-plugin key="com.atlassian.jira.plugin.workflow.example" name="Workflow Examples Plugin"> <plugin-info> <description>Example JIRA Workflow Elements</description> <version>1.0</version> <application-version min="3.0" max="3.0"/> <vendor name="Atlassian Software Systems Pty Ltd" url="http://www.atlassian.com"/> </plugin-info> <workflow-condition key="issueblocking-condition" name="Parent Issue Blocking Condition" class="com.atlassian.jira.plugin.workflow.example.WorkflowParentIssueBlockingConditionFactoryImpl"> <description>Condition to block sub-task issue transition depending on parent issue status.</description> <condition-class> com.atlassian.jira.plugin.workflow.example.condition.ParentIssueBlockingCondition</condition-class> <resource type="velocity" name="view" location="templates/issueblockingcondition/issueblocking-condition-view.vm"/> <resource type="velocity" name="input-parameters" location="templates/issueblockingcondition/issueblocking-condition-input-params.vm"/> <resource type="velocity" name="edit-parameters" location="templates/issueblockingcondition/issueblocking-condition-edit-params.vm"/> </workflow-condition> </atlassian-plugin>
The workflow condition entry specifies the key, name and the Workflow Condition Factory for this condition. The factory class provides methods for passing input, edit and view parameters to the view templates. The condition description is also specified.
The class containing the condition logic, ParentIssueBlockingCondition
, is specified next in the <condition-class>
tag.
Finally, the location of the resource templates are specified, with an individual template for input, edit and view screens.
Once the workflow example JAR file has been placed in the JIRA lib directory, the Parent Issue Blocking Condition will be available as a condition within the workflow editor.
A post function executes specified actions immediately after a transition is executed (hence the name post function). Example possible actions include updating issue fields, generating change history, adding a comment, generating an event that signals that an issue has been progressed through workflow, etc.
This section of the tutorial focuses on the post function element and provides an example post function which can be plugged into JIRA.
JIRA provides a number of system post functions available on setup - UpdateIssueStatusFunction, CreateCommentFunction, etc - each allowing the user to specify that certain actions should be executed following a specific workflow transition.
Note, certain JIRA system post functions cannot be edited, deleted or ordered, as they must be executed during every transition. These post functions are essential for JIRA's issue life cycle, and would compromise other functionality if not executed.
This example post function will close the parent issue once the final sub-task is closed (all other associated sub-tasks are already closed).
The post function will ensure that the parent issue is still open and that all other associated sub-tasks are also closed before attempting to close the parent issue.
The post function can be applied to the 'Close Issue' transition in a copy of the default JIRA workflow associated with the 'Sub-Task' issue type.
The post function logic is contained in the class CloseParentIssueFunction
class that implements the interface FunctionProvider.
The execute
method retrieves the sub-task from the parameters. From this, the parent issue is determined and a check is made as to whether the parent issue is closed or not.
If the parent issue is not closed, the statuses of the rest of the associated sub-tasks are also checked. If all sub-tasks are closed, the parent issue can be closed.
This function does not require any input or configuration - the action to be executed is defined within the post function logic.
The only user input required is to associate the post function with a particular transition within a workflow.
This post function only requires a view template as there is no configuration or editing required.
The velocity template is provided for the view screen:
As with all add-ons, the post function condition must be defined in a file named atlassian-plugin.xml and be located in the root of the JAR file.
The definition of the CloseParentIssueFunction condition is as follows:
1 2... <workflow-function key="closeparentissue-function" name="Close Parent Issue Function" class="com.atlassian.jira.plugin.workflow.WorkflowNoInputPluginFactory"> <description>Closes the parent issue on closing final associated sub-task (all other sub-tasks are closed).</description> <function-class> com.atlassian.jira.plugin.workflow.example.function.CloseParentIssueFunction</function-class> <orderable>true</orderable> <unique>true</unique> <deletable>true</deletable> <default>true</default> <resource type="velocity" name="view" location="templates/closeparentfunction/closeparentissue-function-view.vm"/> </workflow-function> ...
Here, the WorkflowNoInputPluginFactory
class must implement WorkflowPluginFunctionFactory
.
The post function entry specifies the key, name and the Post Function Factory for this condition. The factory class provides methods for passing parameters to the view templates - in this case, no parameter passing is required. The post function description is also specified.
The class containing the post function logic, CloseParentIssueFunction
, is specified next in the <function-class>
tag.
It is also possible to configure the post function as it appears in the workflow editor. The following options can be specified:
orderable
- specifies if this post function can be re-ordered within the list of post functions associated with a transition. The postion within the list determines when the post function actually executes.unique
- specifies if this post function is unique or not - i.e. if it is possible to add multiple instances of this post function on a single transition.deleteable
- specifies if the post function can be removed from a transition.default
- specifies if this post function is automatically associated with any new transitions created.It is also possible to specify a weight
configuration parameter - however this is mainly intended for JIRA system post function elements. This parameter is used in conjunction with the default
parameter - if the post function is to be added to all new transitions, the weight
parameter is used to determine the post function position within the post function list.
Finally, the location of the resource view template is specified.
Once the workflow example JAR file has been placed in the JIRA lib directory, the Close Parent Issue Post Function will be available as a post function within the workflow editor.
This custom subroutine can be used to not allow users to transition an issue which is linked via linktype 'Blocking' to issues not in a user specified state. (you could easily extend this routine to allow the linktype to be user-specified as well)
When using the condition in a JIRA workflow the user must supply a single parameter closedstate
which contains the 'id' primary key value (1 or more, comma separated) of the issue state(s) the linked issues must be in to allow the workflow transition.
There is one drawback to this simple task: it is not possible to override the condition. In itself, this is ok as you can just have it in a 'or' clause with an additional check for the groups the user is a member of. If however you wish to have more complicated logic - i.e.:
if (the user is a member of project-admin group AND all subtasks are closed) OR (user is a jira-administrator)
you have to create a 'meta-condition' class which contains the logic for the 'and' conditions. There is supposed to be a supported syntax with the JIRA 3.0.3 opensymphony osworkflow library but I haven't determined what this is yet.
How to use
Every transition that used this condition required complex if/else logic so I had to create a 'meta-condition' which contained calls to multiple conditionals. You environment might differ. Regardless, to use this condition call it from a java class as shown in the skeleton at the linked page shown below.
The Custom workflow condition skeleton section later on this page has the frame required to use following subroutine:
blockingLinksClosed subroutine
1 2public boolean blockingLinksClosed( GenericValue issue , String linkValidStates ) { try { // Inward links are links made from other issues to the passed 'issue' List inwardLinks = ComponentManager.getInstance().getIssueLinkManager().getInwardLinks(issue.getLong("id")); // Make sure all linked issues of link type equal to passed link name for (int i = 0; i < inwardLinks.size(); i++) { IssueLink link = (IssueLink) inwardLinks.get(i); //log.error("issueLinkName: " + link.getIssueLinkType().getName()); if("Blocking".equals(link.getIssueLinkType().getName())) { String issueStatus = ((GenericValue ) link.getSource()).getString("status"); //log.error("issueStatus: " + issueStatus); boolean isClosed = false; String[] validStates = linkValidStates.split(","); for (int j = 0; j < validStates.length; j++) { String validState = validStates[j]; if(issueStatus.equals(validState)) { //log.error(" validState: " + validState); isClosed = true; break; } } //log.error(" returning: " + isClosed); return isClosed; } } return true; } catch(Exception e) { log.error("Exception verifying all blockingLinks are closed: " + e, e); return false; } }
You can deploy this subroutine into your instance by wrapping it in a custom workflow element add-on.
This is a 'general' workflow condition skeleton which can be used to call the example condition subroutines which link to this page.
Some conditions will use different parameters than what's used in this example.
blockingLinksClosed subroutine
1 2package com.newisys.jira.workflow.condition; import org.apache.log4j.Category; import org.ofbiz.core.entity.GenericValue; import org.ofbiz.core.entity.GenericEntityException; import com.opensymphony.workflow.Condition; import com.opensymphony.workflow.spi.WorkflowEntry; import com.opensymphony.module.propertyset.PropertySet; import com.atlassian.jira.ManagerFactory; import com.atlassian.jira.ComponentManager; import com.atlassian.jira.issue.link.IssueLink; import com.atlassian.jira.issue.link.IssueLinkType; import java.util.Map; import java.util.List; import java.util.Iterator; import java.util.Collection; /** * Passes if all subtask issues have current status equal to passed status id * * required argument 'closedstate' */ public class BlockingLinksClosedCondition implements Condition { private static final Category log = Category.getInstance(BlockingLinksClosedCondition.class); public boolean passesCondition(Map transientVars, Map args, PropertySet ps) { try { WorkflowEntry entry = (WorkflowEntry) transientVars.get("entry"); GenericValue issue = null; try { issue = ManagerFactory.getIssueManager().getIssueByWorkflow(new Long(entry.getId())); } catch (GenericEntityException e) { log.error("Exception: " + e, e); return false; } // Get the id of the workflow String closedState = (String) args.get("closedstate"); if (closedState == null) throw new IllegalArgumentException("Must specify a 'closedstate' arg specifying ids of valid states"); RETURN RESULT OF CALL TO WORKFLOW CONDITION SUBROUTINE } catch(Exception e) { log.error("Exception: " + e, e); return false; } return true; } }
You may also want to read the tutorial, which expands on working with the JIRA add-on system:
Rate this page: