Managing parent work item updates in Azure DevOps via Power Automate
Note: In this article, the work item that triggers the process will be referred to as the trigger work item for clarity.
When using Azure DevOps, it can be quite tricky to keep changes impacting higher level items in a structure (like status information) aligned, especially when teams are self-organising and individual team members manage their own activity updates. Azure DevOps does not automatically update parent work items based on changes on a trigger work item by default and a structure can have multiple levels.
Depending on the Backlog structure, Tasks can for instance be nested under a Backlog Work Item (PBI, Support Ticket, Bug, User Story, etc), which can be nested under Features, Features under Epics, Epics under Initiatives, and so on, so a change on a trigger work item could potentially impact multiple layers.
Note that the structure and naming of work items and field values is dependent on the Azure DevOps setup in an organisation.
What this means is that if for instance a Task is moved from Backlog to In Progress state, the parent Backlog Work Item is technically In Progress state, which also means the parent Feature, Epic, Initiative, etc are In Progress state also, so multiple work items need to be updated to ensure everything remains aligned. Since the full structure is not always clear due to the selected view of the board or backlog, keeping this all aligned can be subject to errors, and if the backlog is extensive with larger self-managing teams keeping everything aligned can be difficult and time-consuming.
When using the Dashboards, Plan functionalities or external BI reporting tools, this can also cause misalignment in e.g. management or leadership reporting as these stakeholders might be less interested in progress or changes on work item level, but more interested in Feature or Initiative level changes.
To streamline processes, Power Automate can be used and some scenarios where Power Automate can be beneficial are:
In this example, the process is described how to automatically update parent statuses when the trigger work item state is updated, and describes the approach taken as well as the physical implementation in Power Automate.
Approach
The approach taken is defined by the business rules and the problem the flow is addressing. The approach of the example in this article can roughly be broken up as follows:
Creating the flow
The Trigger
Within Power Automate it is possible to run a manual flow, a scheduled flow or an automated flow. The selection of the flow determines the frequency and process that activates the remaining process in the flow. While an option would be to select a scheduled flow or manual flow, an automated flow is used here based on a work item being updated.
The reasoning behind this is that it simplifies the process since the automated flow can be triggered when the trigger work item is created or updated, which automatically provides you with the parent ID if there is one (the Parent is not identified in the JSON output when there is none). This will help identification of the branch within the backlog that needs updating and since the change on the state only impacts parent items, there is no need to identify sibling items of the updated trigger work item as they don't need changing.
When triggering the flow manually or as a scheduled flow, there is no easy way of easily identifying which items have been updated, so it requires checking of each work item in the backlog. It also does not provide an easy way to identify which items require updating (a PBI might for instance have Tasks, while a Bug might not, so the levels might differ).
The trigger is as follows:
The Azure DevOps Organisation name and Azure DevOps Project Name are required, but additional options can also be added, like which Area Path the work item should be under.
Initialising Variables
In this flow, two variables are initialised - one for the Parent ID and one to store the child states in.
Since the process is aimed to update the parent state, it should first be identify if a parent exists. If there is no parent, there is no need to continue.
While the Parent ID technically does not need to be stored, it simplifies the use of the Parent ID later on.
The "Parent" variable is of type Integer, since the ID itself is an integer, and will store the following as a value:
triggerOutputs()?['body/fields/System_Parent']
and is accessing the key "System_Parent" in the JSON structure, which looks similar to:
{
"headers": {
...
},
"body": {
"rev": ...
...
"fields": {
...
"System_Parent": ...,
...
}
}
}
When no parent exists, the System_Parent key is not available in the returned JSON structure of the trigger work item, and when stored in an integer variable, the value for a missing parent returns 0 (if there is a parent, the ID of the parent is returned).
The ChildString variable is of type String and is left empty for the moment. While the variable is populated with the values from JSON objects later on, it is stored in a string so we can easily use it for the condition matching and in comments for example.
Get Previous Revision Info
Another decision criteria is to only update the parent if the State of the trigger work item changed and all other changes to the work item can be ignored. The easiest way to ascertain this is to compare the State in the current revision with the State of the previous revision of the same trigger work item. If they match, the state has not changed and otherwise it has.
In order to make that comparison, the information of the previous revision need to be retrieved, which can be done by sending a HTTP GET request to Azure DevOps.
The format of the GET request is:
https://dev.azure.com/<azure devops organisation>/<azure devops project>/_apis/wit/workItems/<id>/revisions/<previous revision number>
and the previous revision number can be ascertained via the expression below, which subtracts 1 from the revision number of the trigger work item revision number:
sub(triggerOutputs()?['body/rev'],1)
The data retrieved provides the information of the previous revision of work item with a specific id (here: the id of the trigger work item), including the Work item State.
Check Parent and State change condition
Now the State can be compared and it can be ascertained if the work item has a parent, a condition (If statement) can be used to identify if additional operations are required.
Note: A Switch operation can be used also if preferred. However Switch does not appear to support dynamic operations, so values need to be populated manually.
As can be seen, there are 2 criteria that need to be fulfilled:
The state comparison would be in format:
triggerOutputs()?['body/fields/System_State']
outputs('<name of the previous step>')?['body/fields/System.State']
Note: In the screenshot above, the name of the previous step would be "Send_an_HTTP_request_to_Azure_DevOps_-_Previous_Revision". Note also that in the trigger work item, the state is key System_State and in the revision System.State due to some inconsistency in the naming conventions.
In this case, the If No branch (criteria not met) is empty as no further action is needed. If preferred, an alternative action can be added, but be aware that if an update is made to the trigger work item, this can result in an infinite loop (see also the notes at the bottom of this article).
Note: All subsequent steps below will be included in the Yes branch.
Get Parent Details (optional)
If the intent is to update the parent work item via the "Update a work item" operation, the Work Item Type of the Parent is needed. Similarly, if a check is to be made in the subsequent condition to identify if the state of the parent already holds the conditional state, the parent work item's State is needed.
Note: If a PATCH HTTP request is made to update the work item, the Work Item Type is not needed, and if the Parent State check is not added, the Parent State is not required. If neither are part of the remaining flow, this operation can be omitted.
In this example, both the State check and the Update a work item operation is used, so the operation is included.
The Parent information can be retrieved via a Send an HTTP Request to Azure DevOps operation, using the GET method via URI:
https://dev.azure.com/<azure devops organisation>/<azure devops project>/_apis/wit/workitems/<parent variable>
Note that the previous condition already filtered out orphaned items, so the Parent ID should be a valid work item ID.
Get work item children & Parse JSON
Once it has been identified whether the process should be continued or abandoned, the logic is to be defined how the parent state is decided. A decision is made that the parent can have one of the following states: Backlog, In Progress, Done or Removed.
The decision which states are used is in part based on the available valid states for the parent items in Azure DevOps and while more states would be available, more states adds more complexity to the definition.
While the available states are now determined, the logic how to decide which state is to be used for the parent still needs to be identified. In this instance, it will be based on the states of the child items (trigger work item & its sibling work items). For Instance:
Parent State is set to In Progress if any of the following rules apply:
Parent State is set to Done if any of the following rules apply:
Parent State is set to Backlog if any of the following rules apply:
Parent State is set to Removed if any of the following rules apply:
If none of the criteria are met, the parent state will not be affected (this would cover any state not explicitly identified, like On Hold). Furthermore, a rule can be applied that if the parent state already has the determined state, no further action is taken.
Note: When implementing this, the order of the rules matters as subsequent rules in the flow conditions will be ignored if a preceding rule is satisfied.
To retrieve the child work items of the parent in Power Automate, an operation, called "Get work item children", can be used to retrieve the information required.
Simply provide the Azure DevOps organisation, Azure DevOps project, and use the Parent variable from above as the work item id.
While not required, the JSON output can subsequently be parsed, which makes it easier to select the data in subsequent steps.
The content is the output of the Get work item children (value) and since we're only interested in the work item id and state, we include this in the schema.
Note: The work item id is included, so it can be used in comments, potential variable value verification, etc, but is technically not needed.
The schema is as follows:
{
"type": "array",
"items": {
"type": "object",
"properties": {
"System.Id": {
"type": "integer"
},
"System.State": {
"type": "string"
}
}
}
}
Note: To generate this, select "Generate from sample", copy the output from the "Get work item children" in as the sample. Press Done will create the full schema, which can be amended to suit by e.g. removing keys that are not needed.
Populate the ChildString variable
To identify the states of the child work items, the ChildString variable can be populated with the values from the Parse JSON (or Get work item children output, but here the Parse JSON is used). This is the Body from the Parse JSON operation as the Source
The output is looped through to populate the variable (via the Append to string variable operation and the Apply to Each loop). As can be seen in the screenshot, the variable is appended with "<System.Id>:<System.State> " (omit quotes and brackets - the brackets simply indicate the selected keys). The space is added for readability.
Note: Since the output of the loop is not clear, the Compose element shows the full output of the loop as without this, it only shows the last input. The Compose element is technically not needed, but is simply added to verify the child IDs and states for flow troubleshooting purposes.
Insert State Conditions
At this point all information required is available and the conditions to determine the states can be established. This can be done via a Switch operation, but in this example, the Condition operation is selected as the condition definition is clearer in my opinion.
Note: Switch does not appear to support dynamic operations, so values need to be populated manually.
In the screenshot, the condition for the Parent "In Progress" state is defined. Where applicable, values are grouped. In this example, the criteria can be read as:
(variable ChildString contains "In Progress" OR ChildString contains "Ready" OR ChildString contains "Validate" OR (ChildString contains "Backlog" AND "Done")) AND the Parent System.State does not contain "In Progress".
The Parent state is defined as
outputs('<Parent info HTTP request>')?['body/fields/System.State']
The Parent Info step here is called: Send_an_HTTP_request_to_Azure_DevOps_-_Parent_Details (optional step).
If this is satisfied, the "If Yes" branch is started, which could contain the update parent action, etc and if not the "If No" branch is started. The No branch contains the next condition, e.g. the Done state condition:
Repeat the "If Yes" an "If No" conditions to fulfil all state options. The final "If No" option could remain empty or an alternate action could be taken (be aware of the infinite loop possibility when updating the trigger item).
For instance, for the condition for the Parent "Done" state, the only check that needs to be made is whether the Child string contains Done and if the parent state is not Done.
Note: Since State "Removed" is allowed in all conditions, this does not explicitly have to be identified. If is only added in e.g. the last condition to check if the parent needs to be marked as Removed or whether no further action is required.
For the Done condition, if is also not required to check if any of the other states are not in the string, since the "In Progress" condition already takes care of this by specifying that In Progress will be set when the ChildString contains any of the In flight states (Ready, In Progress or Validate) or has the combination of states Done and Backlog.
Add Action(s)
Finally, any actions are to be determined following any state changes. For example:
These can be added to the "If Yes" and "If No" branches as required.
For instance, using the Update Work Item:
outputs('<Parent info HTTP request>')?['body/fields/System.WorkItemType']
Note: The Parent Info step here is called: Send_an_HTTP_request_to_Azure_DevOps_-_Parent_Details (optional step).
As mentioned, the parent can also be updated via a HTTP request, using the PATCH method:
[
{
"op": "add",
"path": "/fields/System.State",
"value": "<state>"
},
{
"op": "add",
"path": "/fields/History",
"value": "<whatever comment is to be left>"
}
]
For example:
The state is set via
{
"op": "add",
"path": "/fields/System.State",
"value": "<state>"
}
And the comment via
{
"op": "add",
"path": "/fields/History",
"value": "<whatever comment is to be left>"
}
Repeat for the other state conditions as needed.
The full flow would look something similar to the below screenshot:
Notes
Geschäftsführer von REDVILLE | Zertifizierter Scrum Product Owner
1yThank you so much for that amazing guide! I have never read such a good and detailed guide that is also free of errors. Thank you very much, you helped me a lot! 👍 Greetings from REDVILLE GmbH - Deine CRM Agentur
Flow Consultant | Author of 'Real World Agility' | Keynote Speaker
2yLooking forward to giving this a go - could a similar logic be applied to auto close Features if all child items are closed? (perhaps this is a future post)