5. Workflow extensions
Are you interested in using Project Configurator to migrate workflows that contain conditions, validators, or post-functions defined by a third-party app? Let's discover how easily this can be achieved.
Is the workflow app already supported?
Navigate to the list of supported workflow plugins. This list includes the most popular Jira apps for workflows, and there are new additions to it from time to time. If your third-party app is already supported, then you do not need to create a new extension and can start moving workflows that use this app immediately. If the app is not supported, follow the next steps with the help of an example based on the app Workflow Essentials for Jira.
Step 1: Create the workflow feature and export its XML descriptor
Install the third-party app in the Jira instance you will use to develop and test the extension, then create a workflow that has the conditions, validators, or post-functions (collectively, workflow functions) that you want to support with Project Configurator. In this guide, we focus on:
Add them to a workflow:
Depending on the complexity of the chosen workflow function, it may be a good idea to create more than one instance of each to cover cases when the function can be configured in different ways. In the case of the Date Compare condition, you can see that it can work both with system or custom fields and that it can handle a time expression or the current date and time. It is, therefore, better to create two instances of this condition to cover those cases.
Once you have the workflow with the desired functions, export it from Jira as XML.
Open the XML file with your preferred editor and navigate to the section of the XML where the functions are defined.
Step 2: Implement interface HookPointCollection
Unless your extension already has another implementation of com.awnaba.projectconfigurator.extensionpoints.common.HookPointCollection
, to which you can add the extensions for these workflow functions, create a new instance of this interface:
ImplementingHookPointCollection
@Profile("pc4j-extensions") @Component
public class WES4JExtensionModule implements HookPointCollection {
}
Step 3: Implement the workflow extensions
DateCompareCondition
Analyze
Examine the workflow functions in the exported XML file. For example, start with the Date Compare condition. Looking in the XML, you will find the two occurrences of this condition:
First occurrence of the "Date Compare condition"
<condition type="class">
<arg name="EVALUATION_EXPRESSION">+7d</arg>
<arg name="OPERATOR">></arg>
<arg name="FIELD_ID">duedate</arg>
<arg name="SELECT_DATE_COMPARE_OPTION">VARIABLE_EXPRESSION</arg>
<arg name="class.name">de.codecentric.jira.condition.DateComparisonCondition</arg>
</condition
Second occurrence of the "Date Compare condition"
<condition type="class">
<arg name="EVALUATION_EXPRESSION"></arg>
<arg name="OPERATOR">></arg>
<arg name="FIELD_ID">10101</arg>
<arg name="SELECT_DATE_COMPARE_OPTION">CURRENT_DATETIME</arg>
<arg name="class.name">de.codecentric.jira.condition.DateComparisonCondition</arg>
</condition>
Looking at this part of the workflow descriptor, you see that the argument "FIELD_ID" can contain either the name of a system field ("duedate") or the ID of a custom field ("10101"), so it refers to another object in Jira. Then you have to create for this argument an implementation of the com.awnaba.projectconfigurator.extensionpoints.workflow.WorkflowHookPoint
interface. Create a method with this return type in the class created in step 2.
Implementing HookPointCollection
Why are references to other objects important? (1 of 2)
To start with, it occurs very frequently that a reference will need to be changed for a successful migration. In the above example, whenever a custom field is referenced, it will likely have a different ID at a different Jira instance, so this argument has to be rewritten with the ID of the equivalent custom field at the destination instance.
The other possibility is that this arg refers to a system field, like "duedate". A system object is completely transparent from the point of view of moving this workflow to another instance, as we expect all Jira instances to have that system field. This means you do not have to consider whether the system field exists or not or if it has to be created before the workflow.
There are no other references to other entities in Jira, so when you implement support for the "FIELD_ID" argument, you are finished with this condition. The default action for PCJ, when a workflow is migrated, is to transfer it to the other instance as it is; for all other elements in the condition that do not reference entities in Jira, you do not need to do anything.
Define location
In order to create the WorkflowHookPoint
, you have to provide information about the location within the workflow descriptor of the string that this extension is dealing with. In this case, the location of this string may be described as "the text node under an 'arg' element that has an attribute called 'name', equal to 'FIELD_ID' under a 'function' element that has another 'arg' with attribute 'name' equal to 'class.name' that is equal to 'de.codecentric.jira.condition.DateComparisonCondition
'. This description must be provided as an XPath v1 expression that identifies this location:
XPath expression
Don't know XPath? No problem!
If you are not familiar with XPath, don't worry! You will have to learn yet another arcane piece of technology. As you will see, all the XPath expressions that you need to cover all your workflows are essentially the same, just with different values for the 'class.name' or 'name' attributes for the args, and then applying them for condition, validator, or function elements.
Wait until you see another example!
Define the content of the reference
You must specify the content of the string. We know the string contains either the internal name of a system field or the numeric ID of a custom field. As discussed before, when a system field is present, we can simply ignore it, as it is not necessary to do anything with it in the migration.
The content of the string handled by this extension point is specified by the method ReferenceProcessor<String> getReferenceProcessor()
in interface HookPoint.
A ReferenceProcessor
is the object that is able to interpret and handle the content of a reference.
Except for very specific cases, you do not need to create a ReferenceProcessor
yourself, as there are two services, SimpleReferenceProcessorFactory
and ReferenceProcessorComposer
, that create these objects. The former is used to create a ReferenceProcessor from
a set of built-in ones, both for Jira "out of the box" objects or objects defined in extensions to PCJ (see section 7 of this guide). The ReferenceProcessorComposer
can be used when you want to compose a more complex ReferenceProcessor
based on another already available ReferenceProcessor.
There are two possible alternatives for the content of the reference in this case:
A numeric custom field ID
A system field identifier. No special action is required; the default behaviour of copying the descriptor "as is" will be enough.
Looking at the Javadoc for SimpleReferenceProcessorFactory,
you will find it has a method ReferenceProcessor<String> fromOption(ReferenceOption option)
that creates one from a set of predefined ReferenceProcessor
depending on the value of an enum passed to that method. Among the possible values of ReferenceOption
you will find these two:
CUSTOM_FIELD_ID
that handles a numeric custom field ID (this would cover the case of custom fields).VOID_TRANSLATOR
does nothing; it treats the content as if it were not a reference. This would serve in the case of system fields.
Finally, it is necessary to specify that these references should be treated in one of these ways, depending on whether they are about custom or system fields. In the Javadoc for ReferenceProcessorComposer
you will find the method:
<W> ReferenceProcessor<W> choiceOf(List<ReferenceProcessor<W>> processors, Function<W, Integer> selector)
This method can be used to build a ReferenceProcessor
that is able to pick one among a list of ReferenceProcessor
at runtime, depending on the reference values.
Note
Many workflow plugins reference fields in a slightly different way to the one seen here, using the ID string provided by Field.getId()
. This produces the same strings shown here, with the only difference being that a custom field would be identified by "custom field_10101" instead of "10101". For those cases, there is already a built-in translator that handles the overall case, both for system and custom fields: simpleReferenceProcessorFactory.fromOption(FIELD_STRING_ID)
.
Assembling everything, the result would be something like this:
Implementing WorkflowHookPoint
Congratulations! You have created your first extension point for workflows. With it, any instance of the Date Compare Condition from Workflow Essentials for Jira can now be migrated in an easy and safe way to another Jira instance.
Assign specific user post-function
Let's look at this post-function, which will be the second extension to implement.
Analyze
This is an occurrence of this post-function inside a workflow descriptor:
"User in group validator” in the workflow descriptor
You can see that the only reference to other entities in Jira is a username.
This is similar to what we did with the Date Compare condition, so let's dive in.
Define location
As in any workflow extension, you have to specify the location of the reference string within the descriptor as an XPath:
Notice how the structure of this XPath location is similar to the one used for the previous condition.
Define the content of the reference
In this case, the reference consists of a single username. As in the previous extension, if you look at the Javadoc for SimpleReferenceProcessorFactory
you will notice there is an option to retrieve a built-in ReferenceProcessor
that handles plain user names.
Combining both things in the extension code, you should add the following method to WES4JExtensionModule
class:
Implementing another WorkflowHookPoint