J Tricks - Little JIRA Tricks
 
Hope you all enjoyed the custom field tutorial! If you haven't got a chance and is interested, have a look here.

Let us now look at workflow post functions. Workflow post functions are very effective and heavily used. They allow you to do a lot of things when you progress on the workflow on an issue. Lot of customizations and work arounds take this route! And hence I am hoping this tutorial will help you wee bit in getting there!!


More theory on the workflow post functions can be found here.

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 post function module in the plugin descriptor looks like this:
<workflow-function key="set-usercf" name="Set User CF Post Function"
                class="com.jtricks.SetUserCFFunctionFactory">
    <description>Set Defined User or Current User if not defined!</description>
    <function-class>com.jtricks.SetUserCFFunction</function-class>

    <orderable>true</orderable>
    <unique>false</unique>
    <deletable>true</deletable>

    <resource type="velocity" name="view" location="templates/com/jtricks/view-userCFFunction.vm"/>
    <resource type="velocity" name="input-parameters" location="templates/com/jtricks/edit-userCFFunction.vm"/>
    <resource type="velocity" name="edit-parameters" location="templates/com/jtricks/edit-userCFFunction.vm"/>
</workflow-function>

As usual, key should be a unique value. The class here is used to extract the input parameters that are used in defining the post function. To make it clear, the inputs here are not the input while performing the workflow action but the inputs in defining the post function. In our case, we are going to take an input value (user name) and hence we create a class SetUserCFFunctionFactory extending AbstractWorkflowPluginFactory. The class must implement WorkflowPluginFunctionFactory and that will give you some unimplemented methods which we will see soon.

In case if you don't need any input parameters in you post function, you can replace the class with the JIRA class: com.atlassian.jira.plugin.workflow.WorkflowNoInputPluginFactory . An example would be set the 'current user' to a user custom field (instead of taking the user input!).

Now we come to the next important class which is the function-class. This is the place where the actual function is executed and we need to extend AbstractJiraFunctionProvider here. We will more about both these classes shortly.

We also have 3 views in a post function depending on your requirement. 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.

The details on the 3 parameters defines the following:
  • orderable - (true/false) Specifies if this function can be re-ordered within the list of functions associated with a transition. The postion within the list determines when the function actually executes.
  • unique - (true/false) Specifies if this function is unique; i.e., if it is possible to add multiple instances of this post function on a single transition.
  • deletable - (true/false) Specifies if this function can be removed from a transition.
Let us now look a bit deeper.

SetUserCFFunctionFactory

As mentioned earlier, this is the class to define the inputs and set the velocity context. In out case we have a single input 'user'. Here is what the class look like:
public class SetUserCFFunctionFactory extends AbstractWorkflowPluginFactory implements
        WorkflowPluginFunctionFactory {

    private static final String USER_NAME = "user";
    private static final String CURRENT_USER = "Current User";

    @Override
    protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor) {
        velocityParams.put(USER_NAME, getUserName(descriptor));
    }

    @Override
    protected void getVelocityParamsForInput(Map velocityParams) {
        velocityParams.put(USER_NAME, CURRENT_USER);
    }

    @Override
    protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor) {
        velocityParams.put(USER_NAME, getUserName(descriptor));
    }

    @SuppressWarnings("unchecked")
    public Map getDescriptorParams(Map conditionParams) {
        if (conditionParams != null && conditionParams.containsKey(USER_NAME))  {
            return EasyMap.build(USER_NAME, extractSingleParam(conditionParams, USER_NAME));
        }
        // Create a 'hard coded' parameter
        return EasyMap.build(USER_NAME, CURRENT_USER);
    }  

    private String getUserName(AbstractDescriptor descriptor){
        //Extract the user Name from the decriptor and return!
    }
}

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 'user' variable.

getUserName(descriptor) is the method that retrieves the user from the descriptor and that is done as follows:
private String getUserName(AbstractDescriptor descriptor){

    if (!(descriptor instanceof FunctionDescriptor)) {
        throw new IllegalArgumentException("Descriptor must be a FunctionDescriptor.");
    }

    FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
    String user = (String) functionDescriptor.getArgs().get(USER_NAME);

    if (user!=null && user.trim().length()>0)
        return user;
    else
        return CURRENT_USER;
}

Check if the descriptor is an instance of FunctionDescriptor and if it is, functionDescriptor.getArgs() gets you a Map with all the variables in it. In our case, we retrieve 'user'. Returns 'Current User' 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.

SetUserCFFunction

Time to move to the actual function class. Here is how it looks like:
public class SetUserCFFunction extends AbstractJiraFunctionProvider {

    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException {
        MutableIssue issue = getIssue(transientVars);
        String userName = (String) args.get("user");      

        // Do your business Logic now!

    }
}

When you extend the AbstractJiraFunctionProvider, you will have to implement the execute method and that is what will be executed once the workflow action is over. In our case, we retrieve the user name from the map and set it on the 'Test User' customfield on the issue (pure hard coding for the sake of this tutorial!). You can find the code for this purticular example at the end of the tutorial but this is in essence is what you do in the function class.

Templates

Before I warp up, let us quickly look at the velocity templates. I am using the same template 'edit-userCFFunction.vm' for both input-paramers and edit-parameters.
<tr bgcolor="#ffffff">
    <td align="right" valign="top" bgcolor="#fffff0">
        <span class="label">UserName:</span>
    </td>
    <td bgcolor="#ffffff" nowrap>
        <input type="text" name="user" value="$user"/>
        <br><font size="1">Enter the userName to be set on the Test User CustomField</font>
    </td>
</tr>

$user is what we populated in the getVelocityParamsForInput and getVelocityParamsForEdit earlier!

view-userCFFunction.vm looks like this:
The 'Test User' CF will be set with value : $user

This will appear in the post functions tab on the workflow tranisition once added succesfully.

We now have our post function ready. Create a user customfield 'Test User', deploy the plugin into jira-home/plugins/installed-plugins (WEB-INF/lib if you created plugin-1 version), Create the Post Function in a valid workflow and test it!!

More details on post function module can be found here! Njoy!!

Download the full source code below. And feel free to post your comments/feedback!

Note: The tutorial is just to explain the basic concepts of Post Functions. Please don't put too much thought into the business logic! And don't forget to create a user Custom Field 'Test User' if you want to get the tutorial working.

set-usercf-postfunction.zip
File Size: 6 kb
File Type: zip
Download File

 


Comments

Jeff D
09/21/2010 12:12am

Blimey! Exactly what I was looking for. Thanks.

Reply
J-Tricks
09/21/2010 10:30am

Glad it helped :)

Reply
eva
06/21/2011 10:30am

I did the same steps but I am not seeing the new function on the post-function tab. Help?

Reply
J-Tricks
06/22/2011 7:21am

eva, You mean you are not seeing it when you try to add a post function?

If so, can you check if the plugin is loaded or not bu going to Administration > Plugins?

Reply
Joe C
08/12/2013 6:40pm

I am trying to add the post function to a workflow.
i can see my plugin loaded and enabled under (Admin-> Plugin) .
For the post functions available for the transition are"Events" Do i need to add an event first? how do i associate an event with the post function from the plugin?
Any help would be great. Thanks

Reply
J-Tricks
08/27/2013 12:57pm

Sorry, I didn't understand. Throwing an event is just another post function. If your custom post function is enabled in the plugin, you should be able to see it while adding a post function.

See https://confluence.atlassian.com/display/JIRA/Advanced+Workflow+Configuration#AdvancedWorkflowConfiguration-Postfunctions on how to add a post function.

Reply
Joe c
08/12/2013 7:46pm

OK So i found the link on the "Edit transition" popup page.
I think previously i was trying "Update parameters of the Fire Event Function for this transition."

Reply
J-Tricks
08/27/2013 12:59pm

Ah ok. Didn't notice that you already figured it out ;)

Reply
Iryna
11/14/2013 1:41am

Hi.
Do you know how to set a text description that is shown in the list of post-functions (when you look in you workflow), if i don't have any parameters to get. My Function is like this:

<workflow-function key="my-key" name="someText" class="com.atlassian.jira.plugin.workflow.WorkflowNoInputPluginFactory">
<description>descText</description>
<function-class>PostFuctionClass</function-class>
</workflow-function>
and my PostFuctionClass implements FunctionProvider

Reply
J-Tricks
11/19/2013 7:27pm

The description is usually what you see in the <description> tag. Is that not working?

Reply

Your comment will be posted after it is approved.


Leave a Reply


J tricks - Little JIRA Tricks