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:
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.
22 Comments
Jeff D
9/20/2010 08:12:07 pm
Blimey! Exactly what I was looking for. Thanks.
Reply
J-Tricks
9/21/2010 06:30:05 am
Glad it helped :)
Reply
eva
6/21/2011 06:30:43 am
I did the same steps but I am not seeing the new function on the post-function tab. Help?
Reply
J-Tricks
6/22/2011 03:21:56 am
eva, You mean you are not seeing it when you try to add a post function?
Reply
Joe C
8/12/2013 02:40:05 pm
I am trying to add the post function to a workflow.
Reply
J-Tricks
8/27/2013 08:57:48 am
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.
Reply
Joe c
8/12/2013 03:46:49 pm
OK So i found the link on the "Edit transition" popup page.
Reply
J-Tricks
8/27/2013 08:59:15 am
Ah ok. Didn't notice that you already figured it out ;)
Reply
Iryna
11/13/2013 08:41:10 pm
Hi.
Reply
J-Tricks
11/19/2013 02:27:11 pm
The description is usually what you see in the <description> tag. Is that not working?
Reply
Faz
9/1/2014 06:10:54 am
Hi.
Reply
J-Tricks
9/2/2014 05:30:04 am
Yes, it is the same thing for post functions too. Never tried injecting the js to UI. Should be possible for sure. Similar to what create issue does. Maybe one for another tutorial.
Reply
Terry
9/4/2014 09:51:10 am
Hi Jobin,
Reply
j-Tricks
9/4/2014 04:22:45 pm
Glad to know you like the book :)
Reply
Terry
9/5/2014 04:02:21 am
Yes, I would like to capture user input after a transition. The problem with the transition screens is that they act upon the current issue, and I need to create and link a new issue. I was hoping that I was missing something obvious. :-)
J-Tricks
9/7/2014 01:35:33 am
Unfortunately, that is not possible. You will have to capture any inputs as part of the current issue and then use it to create the new one.
agung
3/17/2015 12:26:30 am
Hello,
Reply
Georges
5/11/2015 12:05:40 am
Hi,
Reply
J-Tricks
5/11/2015 01:19:59 am
Almost everything will be the same. You just need to write the code to add a comment inside the function class. Look for the following comment.
Reply
Georges
5/11/2015 01:57:56 am
Is there any more detailed document that gives step by step directions from start to end?
J-Tricks
5/11/2015 10:10:33 am
None that I have seen! 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
|