Skip to main content
DevOps icon showing cogs

This post is from the XebiaLabs blog and has not been updated since the original publish date.

Last Updated Jul 27, 2015 — DevOps Expert

Data Driven Continuous Delivery with XL Release's Export Hooks


One of things in XL Release 4.7.0 that I'm most excited about is the new "export hook" feature. Export hooks allow you to populate your reporting/Business Intelligence/Big Data stores with as much or as little information about release, phases and tasks as you need. More importantly, you can "mix in" related data from external sources, such as retrieving information from process tools like JIRA or ServiceNow to allow you to link releases to service tickets, incidents, feature requests, epics, stories or the like. Best of all, implementing an export hook is extremely easy, certainly when compared to the effort required to get this kind of information out of most CI or release management tools. XL Release already provides a bunch of useful out-of-the-box reports, of course. Export hooks, however, allow you to pre-process the data in whichever way you need it for your reports - and, by making it easy to pull in information from additional sources, enable you to quickly put together reports that go beyond what XL Release can provide, giving you insight into your full end-to-end pipeline from concept to cash, as well as your full technical stack from your virtual machines to your business processes. The export hook documentation goes into more detail, but export hooks are essentially event hooks that are triggered whenever a release is completed or aborted. To implement an export hook, all you need to do is define any parameters that your export hook needs and provide a Jython script as the implementation. This Jython script is passed, amongst others, a release object containing all the information about the release that was just completed or aborted. You can do whatever you wish with this information: print it to the console, write it to a file, send it to a web service or message queue, process it in any way you need to, link it to information from other sources etc.. If you're planning to store the information in a database, there's also a JDBC export hook, which takes care of all the JDBC connection management and makes a connection object available that you can use to write to your database. You're not limited to one export hook, either: create as many as needed to distribute your XL Release information to all the relevant data stores. There are a couple of sample export hooks that push data to ElasticSearch and MySQL, respectively. Here, just as a flavour of what's possible, we look at a simple export hook that writes aggregated release information, and more detailed task information, to two text files in CSV format. Four lines of XML is all it takes to define the export hook itself. Compare that to the extension APIs for some other tools!

<type type="acme.CsvExportHook" extends="xlrelease.ExportHook">
  <property name="releaseDetailsFile" />
  <property name="taskDetailsFile" />

Here's an instance of the CSV export hook, configured via the Settings > Configuration menu in XL Release: export-hook-configuration The implementation of the hook is almost as simple. Here, for example, is the loop that writes information about each task to the task details file, one line per task:

task_details_file = open(exportHook.taskDetailsFile, 'a')
    if isEmpty(exportHook.taskDetailsFile):
        task_details_file.write('"Release ID","Template ID","Release title",
            "Release owner","Release start time","Release end time",
            "Release duration","Planned release duration","Release delayed?",
            "Release status","Release flag status","Release Tags","Phase title",
            "Task type","Automated task?","Task title","Task owner","Task team",
            "Task start time","Task end time","Task duration","Task status",
            "Task failure count","Task flag status"\n')
    for phase in release.phases:
        for taskOrGroup in phase.tasks:
            for task in expandGroup(taskOrGroup):
                startTaskMillis = task.startDate.getTime()
                endTaskMillis = task.endDate.getTime()
                task_details_file.write('%s,%s,"%s","%s",%d,%d,%d,%s,%d,%s,%s,"%s","%s",%s,%d,"%s","%s","%s",%d,%d,%d,%s,%d,%s\n' % (
                    stripApplication(, stripApplication(noneToEmpty(release.originTemplateId)), release.title, release.owner,
                    startReleaseMillis, endReleaseMillis, releaseDuration, noneToEmpty(release.plannedDuration),
                    oneIfOverrun(release.plannedDuration, releaseDuration), release.status, release.flagStatus, noneToEmpty(' '.join(release.tags)),
                    phase.title, task.type, oneIfAutomated(task.type), task.title, noneToEmpty(task.owner), noneToEmpty(,
                    startTaskMillis, endTaskMillis, (endTaskMillis - startTaskMillis) / 1000, task.status, task.failuresCount, task.flagStatus))

Here, the expandGroup function simply extracts the list of tasks contained in a parallel group. Writing information to the release details file is a little more interesting. Here, we are calculating some basic aggregated statistics for the release, such as the percentage of automated vs. non-automated tasks:

release_details_file = open(exportHook.releaseDetailsFile, 'a')
    if isEmpty(exportHook.releaseDetailsFile):
        # similar to task_details_file.write(...) above
    numTasks = 0
    numAutomatedTasks = 0
    numRetriedTasks = 0
    numCompletedTasks = 0
    numSkippedTasks = 0
    for phase in release.phases:
        for taskOrGroup in phase.tasks:
            for task in expandGroup(taskOrGroup):
                numTasks += 1
                if oneIfAutomated(task.type):
                    numAutomatedTasks += 1
                if task.status == 'COMPLETED':
                    numCompletedTasks += 1
                elif task.status == 'SKIPPED':
                    numSkippedTasks += 1
                if task.failuresCount:
                    numRetriedTasks += 1
    # similar to task_details_file.write(...) above

The type definition and implementation of the example task are available as a Gist in the XebiaLabs community Gist repository. Note that the code linked to is sample code only that is not officially supported by XebiaLabs. Here are some very simple reports I put together using the resulting CSV files:

Task result status by team


Avg. task duration (sec) by team, for automated and non-automated tasks


Failures by task, by team


Level of automation by release template


Task result status by release template


Task result status for automated and non-automated tasks


Delayed and on-time releases by release owner


% of delayed releases (y-axis) by level of automation

delayed-by-level-of-automation The underlying data set is just a sample - the idea here is more to demonstrate what's possible, and to provide some inspiration and ideas for your own export hooks. Data-driven Continuous Delivery, here we go!


More from the Blog

View more
Ascension Launch Banner
Apr 26, 2022

Get ready for peak performance with’s newest AI-Powered DevOps Platform Ascension Release

Today, is excited to announce our latest AI-Powered DevOps ...
Read More
Jan 24, 2022 Value Stream Delivery for SAFe®: The key to amazing business outcomes

The Scaled Agile Framework (SAFe) is the world’s leading framework for ...
Read More
Dec 09, 2021

How SaaS and cloud-based solutions helped the U.S. Department of Veterans Affairs achieve digital transformation

Modernizing legacy systems was an ongoing goal for the U.S. Department ...
Read More
Nov 29, 2021

Increase velocity and reduce risk with AI and machine learning

Artificial Intelligence (AI) and machine learning (ML) have proven use ...
Read More
Contact Us