Fetching Child Tasks via Azure DevOps REST API and Syncing Aggregated Time Spent to Jira

Hi everyone,

I wanted to share an approach that can be used when time tracking information is stored on child Tasks in Azure DevOps, but needs to be synchronized to a parent work item and ultimately sent to Jira via Exalate.

Overview

In many Azure DevOps projects, users log time against child Tasks rather than against the parent User Story or PBI. Since the parent work item does not automatically contain the aggregated time spent, we may need to retrieve this information manually using Azure DevOps REST APIs.

One possible approach is to use Exalate’s httpClient() to inspect the work item’s relationships, identify its child Tasks, retrieve their details, and calculate the total time spent.

Retrieving Work Item Information

For example, the following call can be used to retrieve information related to a work item:

def res = httpClient.get("/Demo1/_apis/wit/workItems/${workItem.id}/revisions", true)
debug.error("${res}")

Note: Replace Demo1 with your Azure DevOps project name. If the project name contains spaces, replace them with %20.

Depending on the use case, you may also retrieve the work item itself and inspect its relations section.

Identifying Child Work Items

The returned payload contains relationship information that can be parsed to identify links such as:

  • Parent

  • Child

  • Related

  • Successor

  • Predecessor

For a parent work item, look for links of type:

System.LinkTypes.Hierarchy-Forward

These typically represent child work items.

Example structure:

{
  "rel": "System.LinkTypes.Hierarchy-Forward",
  "url": "https://dev.azure.com/.../workItems/12345"
}

From these links, the child Task IDs can be extracted.

Retrieving Child Task Details

Once the child IDs are known, additional REST API calls can be made to retrieve each child Task.

The response can then be inspected for fields such as:

Microsoft.VSTS.Scheduling.CompletedWork
Microsoft.VSTS.Scheduling.RemainingWork
Microsoft.VSTS.Scheduling.OriginalEstimate

For example:

def child = httpClient.get(
    "/Demo1/_apis/wit/workItems/${childId}",
    true
)

The values can then be accumulated:

totalCompletedWork += child.fields["Microsoft.VSTS.Scheduling.CompletedWork"] ?: 0

Sending the Aggregated Value to Jira

After processing all child Tasks, the total can be stored in a custom key:

replica.totalTimeSpent = totalCompletedWork

and synchronized to Jira:

issue.customFields."Time Spent".value = replica.totalTimeSpent

Important Consideration

While this approach can successfully retrieve and aggregate child Task values, it does not solve the synchronization trigger problem.

When a child Task’s time spent changes, the parent work item is not automatically updated. Since Exalate synchronizes based on changes to the synchronized entity, the parent work item may not be queued for synchronization when only a child Task is modified.

Therefore, an additional mechanism may be required to update or “touch” the parent whenever a child Task changes, allowing Exalate to detect the change and trigger a new synchronization cycle.

I hope this helps anyone exploring parent-child aggregation scenarios in Azure DevOps. I’d be interested to hear how others have implemented similar solutions and whether there are alternative approaches that work well in production environments.

Thanks, Dhiren

Thanks for sharing this detailed approach, Dhiren! You’ve outlined a solid method for aggregating time-tracking data from child Tasks in Azure DevOps and syncing it to Jira via Exalate. Your use of the httpClient to traverse work item relationships and sum up fields like CompletedWork is spot on.

A couple of key points to highlight and consider:

  • The aggregation logic you described—fetching child work items via System.LinkTypes.Hierarchy-Forward and summing their time fields—is a common pattern for scenarios where time is logged at the Task level.
  • Storing the aggregated value in a custom replica key and mapping it to a Jira custom field is the recommended way to transfer this data between systems.

Regarding the synchronization trigger issue: you’re absolutely right that Exalate’s sync is event-driven, so changes to child Tasks won’t automatically trigger a sync for the parent. Some possible workarounds include:

  • Implementing a process in Azure DevOps (like a webhook, automation rule, or scheduled job) that updates a field on the parent work item whenever a child Task’s time changes. This “touches” the parent and ensures Exalate picks up the change.
  • Using Exalate’s scripting capabilities to periodically poll and update the parent, though this can be less efficient.

For more details on using httpClient in Azure DevOps sync scripts, check out the documentation:
https://docs.exalate.com/docs/azure-devops-httpclient

And for best practices on handling parent-child relationships and custom field mappings:
https://docs.exalate.com/docs/azure-devops-script-mode-examples

Your approach is well-structured, and your note about the need for a trigger mechanism is crucial for anyone implementing this in production. If others have alternative strategies or automation tips for keeping parent items in sync, it’d be great to hear those as well!