Azure DevOps - Status Propagation
Tired of manually updating parent work items every time there’s a status change in Azure DevOps? In this post, I’ll show you how to automate the status propagation process across all levels of your work item hierarchy with the help of Integray. We will propagate status from Task to User Story, from User Story to Feature, and from Feature to Epic. By using Azure DevOps webhooks, Integray, and Rest API, you can ensure that parent work items (like Features) are automatically updated based on changes in their child items (like User Stories). Plus, when a Feature is updated by the automation, the Start Date field is automatically filled in, saving you time and ensuring accuracy.
Process Flow
When the State
field of a Task / User Story / Feature is changed and the item is saved, a webhook is triggered to initiate the status propagation process.
Webhook Processing
The webhook sends a POST request containing relevant data (such as Work Item ID, New State, and Parent ID) to an Integray endpoint and starts a Task.
Parent Work Item Retrieval
The parent work item (e.g., Feature) associated with the child item (e.g. User story) is retrieved using the Node.js connector with a fetch method.
State Transition Logic
Based on the current state of the parent and the new state of the child item, the following actions are taken:
-
If child item (e.g., User Story) is Active and the parent item (e.g., Feature) is in the ‘New’ state:
- The parent item is updated to Active.
- If the Feature does not have a start date, the Start Date is set to the current date.
-
If the parent item (e.g., Feature) is already in Active, Resolved, Rel, Close, Remove, or Merged, no state change is made.
API Response Handling
Success and error messages are logged to ensure visibility into the automation process. The appropriate status messages are returned for further processing or monitoring.
Benefits
- Automated Tracking: This automation ensures that parent work items reflect the most current progress based on their child work items, eliminating manual updates.
- Accurate Reporting: The consistent propagation of status between related work items helps maintain accurate tracking in Azure DevOps.
- Improved Visibility: Development teams have a clearer understanding of work progress across multiple levels (from Tasks to Epics) directly in Azure DevOps.
Implementation Details
Tools Used: Azure DevOps Board + Webhooks, Integray
Fields Managed: System.State
(for Tasks, User Stories, and Features), Microsoft.VSTS.Common.StartDate
(for Features when transitioning to Active if the field was previously empty)
Example Code Snippets from Integray
1. Webhook - triggered by status change on Task (Task to User Story):
This code handles the propagation of status from Task to User Story. The Integray endpoint processes the webhook and updates the parent work item accordingly using the Node.js connector.
async function updateParentWorkItem(parentID, newState) {
const parentWorkItem = await getWorkItemById(parentID);
if (parentWorkItem) {
const parentState = parentWorkItem.fields["System.State"] || null;
if (newState === "Active") {
if (parentState === "New") {
// Set parent to Active
await updateWorkItemField(parentID, "System.State", "Active");
return { success: true, message: `Parent work item ${parentID} updated to Active.` };
}
}
}
}
2. Webhook - triggered by status change on User Story (User Story to Feature):
This code propagates status from User Story to Feature, updating both the state and the start date if applicable.
async function updateParentWorkItem(parentID, newState) {
const parentWorkItem = await getWorkItemById(parentID);
if (parentWorkItem) {
const parentState = parentWorkItem.fields["System.State"] || null;
const startDate = parentWorkItem.fields["Microsoft.VSTS.Scheduling.StartDate"] || null;
if (newState === "Active") {
if (parentState === "New") {
// Set parent to Active
await updateWorkItemField(parentID, "System.State", "Active");
// If Start Date field isn't filled set it to current day
if (!startDate) {
const today = new Date().toISOString();
await updateWorkItemField(parentID, "Microsoft.VSTS.Scheduling.StartDate", today);
}
}
}
}
}
3. Task (Feature to Epic) - triggered by webhook (change of Feature’s status):
Finally, the following snippet handles the propagation of status from Feature to Epic.
async function updateParentWorkItem(parentID, newState) {
const parentWorkItem = await getWorkItemById(parentID);
if (parentWorkItem) {
const parentState = parentWorkItem.fields["System.State"] || null;
if (newState === "Active") {
if (parentState === "New") {
// Set parent to Active
await updateWorkItemField(parentID, "System.State", "Active");
}
}
}
}
4. Additional Code Snippets of Functions
The two functions below, updateWorkItemField
and getWorkItemById
, handle updating and retrieving work items from Azure DevOps, respectively. The first one updates a specific field on a work item by sending a PATCH request, and the second one retrieves the work item details based on the provided work item ID.
Function to update a work item field by ID
async function updateWorkItemField(workItemId, field, value) {
const url = `https://dev.azure.com/${organization}/${project}/_apis/wit/workitems/${workItemId}?api-version=7.0`;
const patchData = [
{
"op": "add",
"path": `/fields/${field}`,
"value": value
}
];
try {
const response = await fetch(url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json-patch+json',
'Authorization': `Basic ${auth}`
},
body: JSON.stringify(patchData)
});
if (!response.ok) {
log.error(`Error updating work item ${workItemId}: ${response.statusText}`);
}
const responseData = await response.json();
log.info(`Successfully updated work item ${workItemId} with ${field}: ${value}`);
return responseData;
} catch (error) {
log.error(`Error updating work item ${workItemId}:`, error);
}
}
Function to get a work item by ID
async function getWorkItemById(workItemId) {
const url = `https://dev.azure.com/${organization}/${project}/_apis/wit/workitems/${workItemId}?api-version=7.0`;
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`
}
});
if (!response.ok) {
log.error(`Error fetching work item ${workItemId}: ${response.statusText}`);
return null;
}
const responseData = await response.json();
return responseData;
} catch (error) {
log.error(`Error fetching work item ${workItemId}:`, error);
}
}
Conclusion
By leveraging Azure DevOps webhooks and the Integray platform, this automation ensures that status updates are propagated consistently across the hierarchy of tasks, user stories, features, and epics. This approach significantly reduces manual intervention, improves visibility, and ensures more accurate reporting of progress across different levels.