Managing parent work item updates in Azure DevOps via Power Automate

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:

  • Updating parent statuses when the trigger work item state is updated
  • Adding a comment to a parent work item when the trigger work item is blocked
  • Updating a parent's target date and adding a comment when the target date of a child work item is later than a parent target date
  • And so on...

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:

  • Running criteria: What are the criteria when the flow should run, like frequency, whether the trigger work item has no parent (i.e. orphaned), matching area paths, etc. This is in essence the decision about which trigger should be used and the "Check state and parent" condition, documented below, and the supported activities (Initialising the parent variable and retrieving the revision history).
  • State Business Logic: For instance which states or combination of child states result in a specific parent state, which parent states are being set, and what happens if a parent state cannot be defined. This includes all the items in the "If Yes" branch of the above condition as well as the ChildString variable initialisation (which could arguably be moved to the top of the "If Yes" branch too).
  • Actions: When a parent state is ascertained, what will happen next. For instance, is the parent work item updated, is a comment added to the parent and/or child work item, is an email or similar sent, what happens if the running criteria are not met or a parent status cannot be determined (e.g. a comment added to the trigger work item), and so on. These are the actions in the results of the conditions and can be in any of the condition branches.

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:

No alt text provided for this image

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.

No alt text provided for this image

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.

No alt text provided for this image

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.

No alt text provided for this image

As can be seen, there are 2 criteria that need to be fulfilled:

  • The variable "Parent" should not be equal to 0 (as 0 means no parent).
  • The state in the current revision should not match the state in the previous revision. If the state of the trigger work item has not changed in this scenario, the parent state would not need to be updated either and the flow can be abandoned. If the state differs, the process would continue.

The state comparison would be in format:

  • In the left field:

triggerOutputs()?['body/fields/System_State']        

  • Comparator: Is not equal to
  • In the right field:

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>        
No alt text provided for this image

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:

  • Any of the child work items have one of the following states: In Progress, Ready or Validate
  • The child item states would be a combination Backlog and Done.

Parent State is set to Done if any of the following rules apply:

  • All child work items have state: Done
  • The child item states would be a combination Removed and Done.

Parent State is set to Backlog if any of the following rules apply:

  • All child work items have state: Backlog
  • The child item states would be a combination Removed and Backlog.

Parent State is set to Removed if any of the following rules apply:

  • All child work items have state: Removed

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. 

No alt text provided for this image

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.

No alt text provided for this image

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

No alt text provided for this image

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.

No alt text provided for this image

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:

No alt text provided for this image

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.

No alt text provided for this image

Add Action(s)

Finally, any actions are to be determined following any state changes. For example:

  • The parent work item is updated with the new state with optionally a comment with the rationale for the change
  • Someone is notified of a change or that a change is to be made (e.g. via email or Teams notification)
  • Actions are to be taken when no change is made (e.g. a comment against the trigger work item that no further action was needed or similar)
  • And so on.

These can be added to the "If Yes" and "If No" branches as required.

For instance, using the Update Work Item:

No alt text provided for this image

  • The work item Id is the Parent variable
  • The work item type can be defined via

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).

  • Set the state via the Other fields. Type System.State in the left column, which indicates the key) and the value (here: "In Progress") in the right column (indicating the value)
  • A comment can be added via the History key.

As mentioned, the parent can also be updated via a HTTP request, using the PATCH method:

  • Specify the Azure Devops organisation
  • Use the PATCH method with URI https://dev.azure.com/<azure devops organisation>/<azure devops project>/_apis/wit/workitems/<parent variable>?api-version=7.0 (replace Azure DevOps org and project, and insert the Parent valiable as indicated)
  • Add a "Content-Type Header" with value "application/json-patch+json"
  • Add the state and optional comment in the Body, e.g. 

[
   {
     "op": "add",
     "path": "/fields/System.State",
     "value": "<state>"
   },
   {
     "op": "add",
     "path": "/fields/History",
     "value": "<whatever comment is to be left>"
   }
]         

For example:

No alt text provided for this image

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:

No alt text provided for this image

Notes

  • The decision which flow trigger to use can significantly impact the rest of the process. Aside from the rationale described above, it could potentially also increase the number of API calls to Azure DevOps, which could impact performance or could be impacted by Power Automate limits. See also: https://learn.microsoft.com/en-us/power-automate/limits-and-config.
  • Within this process, multiple queries/API calls are made per work item in the branch, e.g. to retrieve the trigger work item (the trigger), get the data of the previous revision, get the parent information, get the child items of the parent and any actions to be implemented. Adding "entry" criteria to identify whether the process should be continued early in the process will limit this.
  • The conditions could be implemented as Switch statements or Condition statements. Here Conditions are used to improve visibility of the criteria when creating or troubleshooting the flow. Note also that Switch does not appear to support dynamic operations, so values need to be populated manually.
  • Updating the work item at the end of the process can be done via the Update a Work Item operation or a HTTP request (PATCH). The former would require the Work Item Type (even though it is not clear from the form), which means the parent information is required. The HTTP request does not need the Type, so if the HTTP request is used and the criteria not to update the parent if it already has the state is removed form the business logic, the parent information does not need to be requested since the Parent ID is already known.
  • If the business logic requires additional state criteria for the parent, additional criteria and if/switch statements can be added in a similar fashion as shown in the example.
  • The process above is a sample on how to implement updates to parent work items based on an updated work item. In this case State was used as an example, but a similar process can be applied and/or adapted for other fields.
  • While the process provides automation, it is always worthwhile sense checking any updates to parent work items. There might be unexpected behaviour as a result of unexpected changes in Azure DevOps, timeouts/delays in running the flow or updating Azure DevOps, etc. I have noticed that there can be a lag in status updates in the Azure DevOps user interface.
  • This process only update parent work items, not child work items. So if for instance a Product Backlog Item is set to Done, it will not automatically mark the underlying Tasks as Done.
  • If the parent work item is not updated and/or no comment is added as part of the actions, the process only runs once, otherwise the implementation will automatically crawl up the tree in Azure DevOps as an update to the parent will also trigger the flow on that parent. The process for the top level item (or orphaned items) would be shorter than lower level items since the top level item is orphaned.
  • The conditional check between the state in the current revision vs the state in the previous revision (or whichever field is checked as part of the process) should stop the process to run in an infinite loop when the trigger work item is updated since in the first run, the fields might be different, but in the subsequent step they might be the same if the same field is updated. Note however that updating the trigger work item in the "If No" branch (by e.g. adding a comment) would trigger the flow again, causing an infinite loop.
  • Updating the trigger work item as part of the flow could create an infinite loop, i.e. the work item triggers the flow and the flow updates the work item, which in turns triggers the flow again, and so on. Mitigation might be needed by for instance adding a check to identify if the action took place in the preceding run to avoid it recurring so the trigger work item won't be updated again.
  • Microsoft has also documented a process to implement this via a web hook : https://learn.microsoft.com/en-us/azure/devops/organizations/settings/work/apply-rules-to-workflow-states?view=azure-devops. Using Power Automated (as described here) is an alternative method.

Daniel Schwarze

Geschäftsführer von REDVILLE | Zertifizierter Scrum Product Owner

1y

Thank 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

Like
Reply
Nicolas Brown

Flow Consultant | Author of 'Real World Agility' | Keynote Speaker

2y

Looking 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)

Like
Reply

To view or add a comment, sign in

Others also viewed

Explore topics