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:

Workflow screen - Post functions
Workflow screen - Post functions

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.

Workflow screen - conditions
Workflow screen - conditions

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