Webhook Event Tasks
Webhook event tasks are extensible automated tasks that wait for events from webhooks endpoints and complete when the incoming event matches the specified condition.
Using the Wait For Json Event task
In Digital.ai Release there is a supplied generic webhook task Webhook: Wait for Json event.
The input options for the Webhook: Wait for Json event task are:
Option | Description |
---|---|
Endpoint | Configured HTTP Endpoint for webhooks to listen on. |
Condition | The condition that must be satisfied to transition the task from in-progress to complete. |
Received event | Output property bound to a text variable that will store the JSON of the webhook event (optional). |
The task will listen for incoming events only while it is in the In progress state.
Condition script
The webhook task condition is a Jython script that works similar to the Webhook event trigger filter rule:
If the script returns True
, the task will complete.
If the script returns False
, the task will stay in progress.
If there is an error with the script, the task will fail.
In the script, you can access the Release Jython API, and the following properties: event
, headers
, parameters
.
event
is the parsed JSON body of the webhook event, while headers
and parameters
are the HTTP request headers and parameters.
The event
, headers
, parameters
data is also accessible using dot access, e.g. event.commits[0].author
, but the regular dict access is still available.
Define a custom webhook event task
In order to reduce repetition of event matching logic, you can define your own webhook task type in the synthetic.xml
. All webhook event tasks are a sub-type of Custom script task.
All rules concerning custom script tasks are valid for webhook event tasks.
To define a custom webhook event task, you need to:
- Define new task type which extends
webhook.ReactiveTask
- Define a
webhookScriptLocation
property which points to the condition script file
Example of a custom webhook event task:
The synthetic.xml
type definition:
<synthetic xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.xebialabs.com/deployit/synthetic"
xsi:schemaLocation="http://www.xebialabs.com/deployit/synthetic synthetic.xsd">
<type type="jira.WaitForIssueStatus" extends="webhook.ReactiveTask">
<property category="input" name="issueKey" kind="string"/>
<property category="input" name="issueStatus" kind="string" default="Closed"/>
<property category="output" name="resolution" kind="string"/>
<property category="output" name="resolutionDate" kind="date"/>
<property hidden="true" transient="true" name="webhookScriptLocation" default="jira/WaitForIssueStatusWebhook.py" />
</type>
</synthetic>
The WaitForIssueStatusWebhook.py
script:
global issueKey, issueStatus, resolution, resolutionDate
if not issueKey:
raise Exception("Issue key must be defined")
if not issueStatus:
raise Exception("Expected issue status to wait for must be defined")
print "Matching new event: %s" % payload
result = event.webhookEvent == "jira:issue_updated" and event.issue.key == issueKey and event.issue.fields.status.name == issueStatus
if result:
print "Issue %s has successfully transitioned to status %s" % (issueKey, issueStatus)
resolution = event.issue.fields.resolution.name
resolutionDate = event.issue.fields.resolutiondate
else:
print "Event did not match expected conditions"
Further, it is possible to design a custom webhook event task which, when starting, does an initial pull of the external system. If the condition has already been satisfied, the task will transition directly into the Completed state. Otherwise, it will stay In progress until a matching JSON event gets received on the configured HTTP endpoint event source.
We can quickly update the example jira.WaitForIssueStatus
task and reuse some JIRA plugin base types and utility classes:
- Make
jira.WaitForIssueStatus
type extendjira.JiraScript
- Add back the properties previously provided by the
webhook.ReactiveTask
- theendpoint
andwaitForSignal
properties - Add a new
WaitForIssueStatus.py
script which will perform the initial poll
Example of the updated task:
The synthetic.xml
type definition:
<synthetic xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.xebialabs.com/deployit/synthetic"
xsi:schemaLocation="http://www.xebialabs.com/deployit/synthetic synthetic.xsd">
<type type="jira.WaitForIssueStatus" extends="jira.JiraScript">
<property category="input" name="endpoint" kind="ci" referenced-type="events.WebhookEndpoint" />
<property category="input" name="issueKey" kind="string"/>
<property category="input" name="issueStatus" kind="string" default="Closed"/>
<property category="output" name="resolution" kind="string"/>
<property category="output" name="resolutionDate" kind="date"/>
<property category="script" name="waitForSignal" kind="boolean" default="false"/>
<property hidden="true" transient="true" name="scriptLocation" default="jira/WaitForIssueStatus.py" />
<property hidden="true" transient="true" name="webhookScriptLocation" default="jira/WaitForIssueStatusWebhook.py" />
</type>
</synthetic>
The WaitForIssueStatus.py
script:
global issueKey, issueStatus, resolution, resolutionDate
import sys
import com.xhaus.jyson.JysonCodec as json
from jira import JiraServer
if not jiraServer:
raise Exception("JIRA server must be defined")
if not issueKey:
raise Exception("Issue key must be defined")
if not issueStatus:
raise Exception("Expected issue status to wait for must be defined")
ISSUE_RETREIVED_STATUS = 200
jira = JiraServer(jiraServer, username, password, apiToken)
request = jira._createRequest()
statusTask = jira._getVersionUri() + '/issue/' + issueKey
response = request.get(statusTask, contentType='application/json', headers=jira._createApiTokenHeader())
# if response received from Jira
if response.getStatus() == ISSUE_RETREIVED_STATUS:
# retrieve issue status
issueData = json.loads(response.getResponse())
currentStatus = issueData['fields']['status']['name']
print "\nThe status of issue %s is %s" % (issueKey, currentStatus)
if currentStatus == issueStatus:
print "\nIssue is in expected status, completing task now"
resolution = issueData['fields']['resolution']['name']
resolutionDate = issueData['fields']['resolutiondate']
waitForSignal = False
else:
print "\nIssue is not in expected status yet, will wait for matching event"
waitForSignal = True
else:
print "Error from JIRA, HTTP Return: %s" % (response.getStatus())
response.errorDump()
sys.exit(1)
Depending on the status of the ticket, the WaitForIssueStatus.py
script will either complete the task by setting the waitForSignal
flag to False
or will stay in progress and pass the control to the WaitForIssueStatusWebhook.py
script by setting the waitForSignal
flag to True
.