Stefan Stranger's Blog

Using Azure Logic Apps to create an Azure DevOps Bug WorkItem

2020-06-05 00:00:00 +0000 ·

Table of contents

  • Table of contents
  • Azure Logic Apps
    • Trigger
    • Azure DevOps Service Hook
    • Configuration of Logic App
    • Configuration of Azure DevOps Web Hook
    • Add Logic App Actions
    • Update Azure DevOps Web Hook
  • Trigger Web hook and Logic App in failed Pipeline
  • Logic App Code View

Some time ago I created a private Azure DevOps Extension for a customer which would create an Azure DevOps Bug WorkItem when a task in a Release would fail.

When returning at that customer this Extension was still used but it didn’t work for (yaml) build pipelines. Instead of updating this Extension I investigated if I was able to use Azure DevOps Service Hooks to trigger an Azure Logic App which would create a Bug WorkItem for failed (build) Yaml Pipelines.

After sharing a successful test implementation result on Twitter it seemed that some more people are interested in how I got this working.

Screenshot Twitter communication

So here is that blog post :-)

Azure Logic Apps

For those unfamiliar with Logic Apps, it is a cloud service that helps you schedule, automate, and orchestrate tasks, business processes, and workflows when you need to integrate apps, data, systems, and services across enterprises or organizations.

Logic Apps simplifies how you design and build scalable solutions for app integration, data integration, system integration, enterprise application integration (EAI), and business-to-business (B2B) communication, whether in the cloud, on premises, or both. You can find more information here.

Trigger

For creating the Azure DevOps Bug WorkItem we need a trigger, which fires when a specific event (like a failed task in a build pipeline) happens. Each time that the trigger fires, the Logic Apps engine creates a logic app instance that runs the actions in the workflow.

For our Logic App we are going to use a Response Request trigger.

This Trigger will be run by the Azure DevOps Service Hooks Web Hook.

Azure DevOps Service Hook

Web Hooks provides a way to send a JSON representation of an event to any service. All that is required is a public endpoint (HTTP or HTTPS).

screenshot of Web Hook in Azure DevOps

Later in this blog post I’ll explain the configuration needed here to trigger the Logic App. Let’s now first start with the creation of the Logic App.

Configuration of Logic App

Go to the Azure Portal and create a new Logic App Resource

Screenshot Azure Portal Logic App Creation

You should now have an empty Logic App Resource created.

Screenshot Logic App in Azure Portal

Let’s add our first Action “When a HTTP request is received” by opening the Designer.

Screenshot HTTP request

At a new POST Method Parameter and save the Logic App and copy the HTTP Get URL.

Screenshot HTTP Request Post Method

We need to use this URL in the Azure DevOps Web Hook later.

Configuration of Azure DevOps Web Hook

Open Azure DevOps Project Settings and create a new Web Hook using the URL from previous step.

Screenshot Azure DevOps Web Hook Configuration

Click on next after selecting Web Hooks.

We want for each of the completed Builds with Status Failed to have the Logic App being triggered.

Screenshot of Web Hook settings

Configure the properties of the Web Hook and test the webhook.

Screenshot Web Hook Properties

Copy the Request from the Test to the Logic App to be used as sample payload to generate schema.

Screenshot of the Request

Sceenshot of sample payload to generate schema in Logic Apps

Sceenshot of sample payload to generate schema in Logic Apps

Click on Done and save Logic App.

Add Logic App Actions

The following Actions need to be added.

Screenshot of the Actions in Logic Apps

  1. Initialize variable - Iteration

Screenshot Initialize Iteration variable

Save Logic App.

  1. Initialize - Organization

Screenshot of Initialize Organization Variable

Save Logic App.

  1. Set variable - Organization

Screenshot of Set Organization Variable

Screenshot of Initialize Organization Variable Expression

Expression:

replace(split(triggerBody()?['resource']?['url'],'.')[0],'https://','')

Explanation:

From the “When a HTTP request is received” output we want to extract the Organization name and store that value in a variable.

Screenshot of When a HTTP request is received output

Save Logic App.

  1. Send a HTTP request to Azure DevOps - Get Iteration

With this action we want to retrieve the current Azure DevOps Iteration. More info on this REST API can be found here.

Screenshot Send an HTTP request to Azure DevOps to get Iteration

You first need to create a connection to Azure DevOps.

Screenshot to Sign in to Azure DevOps Connection

Configure the Send an HTTP Request to Azure DevOps without configuring the Relative URL.

Screenshot to configure Send an HTTP request to Azure DevOps

Go to Code View after first saving the Logic App.

Screenshot of Code View of Logic App

https://dev.azure.com/@{variables('Organization')}/@{triggerOutputs()['queries']['Project']}/@{triggerOutputs()['queries']['Team']}/_apis/work/teamsettings?api-version=5.1

Explanation:

The URL should look something like this:

https://dev.azure.com/stefanstranger/Contoso/Contoso Team/_apis/work/teamsettings?api-version=5.1
Expression Value Explanation
@{variables(‘Organization’)} stefanstranger This value is from the earlier variable
@{triggerOutputs()[‘queries’][‘Project’]} Contoso This value is coming from a parameter input on the Web Hook Webrequest. Example: https://foo.com?Project=Contoso
@{triggerOutputs()[‘queries’][‘Team’]} Contoso Team This value is coming from a parameter input on the Web Hook Webrequest. Example: https://foo.com?Team=Contoso%20Team

Save Logic App.

  1. Set Variable - Iteration

Screenshot of Set Iteration Variable Action

Use the following Dynamic content. We want to use the DefaultIteration path from the previous Action.

"@body('Send_an_HTTP_request_to_Azure_DevOps_-_Get_Iteration')['DefaultIteration']['path']"

Go to the Designer add temp value and save Action before going to View Code.

Screenshot of the Designer

Screenshot of the Code View

Screenshot of the Code View for the Set_Variable

Save Logic App.

  1. Create a Work Item

Add a new Azure DevOps Action to your Logic App.

Screenshot of Create a Work Item Action

Configure the following properties:

  • Organization Name,
  • Project Name
  • WorkItem Type
  • Title
  • Description
  • Area Path
  • Iteration Path
  • Repro Steps

Screenshot of temp parameters for Create a Work Item Action

Save Action and open Code View Editor and replace with the following:

"Create_a_work_item": {
                "inputs": {
                    "body": {
                        "area": "@{triggerOutputs()['queries']['Area']}",
                        "description": "@{triggerBody()?['detailedMessage'].text}",
                        "dynamicFields": {
                            "Microsoft.VSTS.TCM.ReproSteps": "@{triggerBody()?['detailedMessage'].html}"
                        },
                        "iteration": "@{triggerOutputs()['queries']['Area']}@{variables('Iteration')}",
                        "title": "@{triggerBody()?['message']?['text']} - @{triggerBody()?['resource']?['finishTime']}"
                    },
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['visualstudioteamservices']['connectionId']"
                        }
                    },
                    "method": "patch",
                    "path": "/@{encodeURIComponent('Contoso')}/_apis/wit/workitems/$Bug",
                    "queries": {
                        "account": "stefanstranger"
                    }
                },
                "runAfter": {
                    "Set_variable_-_Iteration": [
                        "Succeeded"
                    ]
                },
                "type": "ApiConnection"
            },

Screenshot of Code View of Create a Work Item Action

Save Logic App.

Update Azure DevOps Web Hook

We now need to add the following parameter to the web hook url:

  • Area
  • Project
  • Team

Open the configured Web Hook in Azure DevOps and copy the content to Notepad and add the following:

Screenshot of Azure DevOp Web Hook test

Test the updated Web Hook in both Azure DevOps and in Azure.

Screenshot of Azure DevOps Web Hook test

If you get an unauthorized error message on the Send an HTTP request to Azure DevOps Action.

Screenshot of Unauthorized Error in Logic App

Go to API Connections and Delete the Azure DevOps Connection.

Screenshot of API Connection setting in Azure Portal

Go to the Logic Analytics Designer and re-add the Connection by Adding a new one.

Screenshot of adding API Connection

Save the Logic App. And Authorize API Connection again and save.

Screenshot or Authorizing the API Connection

Trigger Web hook and Logic App in failed Pipeline

The final test is when we create a (build) pipeline which fails to validate the Logic App being triggered and the Bug Work Item being created.

yaml pipeline code:

trigger:
  - none

steps:
  - powershell: |
      throw 'Something failed!'

Trigger the pipeline.

Screenshot of Azure DevOps Pipeline

Check the Logic App Runs History in the Portal.

Screenshot of Logic App Runs History

And finally check in Azure DevOps if the Bug Work Item is being created.

Screenshot of Azure DevOps Bug Work Item creation

Logic App Code View

Here is the complete Code for the Logic App.

{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "actions": {
            "Create_a_work_item": {
                "inputs": {
                    "body": {
                        "area": "@{triggerOutputs()['queries']['Area']}",
                        "description": "@{triggerBody()?['detailedMessage'].text}",
                        "dynamicFields": {
                            "Microsoft.VSTS.TCM.ReproSteps": "@{triggerBody()?['detailedMessage'].html}"
                        },
                        "iteration": "@{triggerOutputs()['queries']['Area']}@{variables('Iteration')}",
                        "title": "@{triggerBody()?['message']?['text']} - @{triggerBody()?['resource']?['finishTime']}"
                    },
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['visualstudioteamservices']['connectionId']"
                        }
                    },
                    "method": "patch",
                    "path": "/@{encodeURIComponent('Contoso')}/_apis/wit/workitems/$Bug",
                    "queries": {
                        "account": "contosodemo"
                    }
                },
                "runAfter": {
                    "Set_variable_-_Iteration": [
                        "Succeeded"
                    ]
                },
                "type": "ApiConnection"
            },
            "Initialize_variable_-_Iteration": {
                "inputs": {
                    "variables": [
                        {
                            "name": "Iteration",
                            "type": "string"
                        }
                    ]
                },
                "runAfter": {},
                "type": "InitializeVariable"
            },
            "Initialize_variable_-_Organization": {
                "inputs": {
                    "variables": [
                        {
                            "name": "Organization",
                            "type": "string"
                        }
                    ]
                },
                "runAfter": {
                    "Initialize_variable_-_Iteration": [
                        "Succeeded"
                    ]
                },
                "type": "InitializeVariable"
            },
            "Send_an_HTTP_request_to_Azure_DevOps_-_Get_Iteration": {
                "inputs": {
                    "body": {
                        "Method": "GET",
                        "Uri": "https://dev.azure.com/@{variables('Organization')}/@{triggerOutputs()['queries']['Project']}/@{triggerOutputs()['queries']['Team']}/_apis/work/teamsettings?api-version=5.1"
                    },
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['visualstudioteamservices']['connectionId']"
                        }
                    },
                    "method": "post",
                    "path": "/httprequest",
                    "queries": {
                        "account": "contosodemo"
                    }
                },
                "runAfter": {
                    "Set_variable_-_Organization": [
                        "Succeeded"
                    ]
                },
                "type": "ApiConnection"
            },
            "Set_variable_-_Iteration": {
                "inputs": {
                    "name": "Iteration",
                    "value": "@body('Send_an_HTTP_request_to_Azure_DevOps_-_Get_Iteration')['DefaultIteration']['path']"
                },
                "runAfter": {
                    "Send_an_HTTP_request_to_Azure_DevOps_-_Get_Iteration": [
                        "Succeeded"
                    ]
                },
                "type": "SetVariable"
            },
            "Set_variable_-_Organization": {
                "inputs": {
                    "name": "Organization",
                    "value": "@{replace(split(triggerBody()?['resource']?['url'],'.')[0],'https://','')}"
                },
                "runAfter": {
                    "Initialize_variable_-_Organization": [
                        "Succeeded"
                    ]
                },
                "type": "SetVariable"
            }
        },
        "contentVersion": "1.0.0.0",
        "outputs": {},
        "parameters": {
            "$connections": {
                "defaultValue": {},
                "type": "Object"
            }
        },
        "triggers": {
            "manual": {
                "inputs": {
                    "method": "POST",
                    "schema": {
                        "properties": {
                            "createdDate": {
                                "type": "string"
                            },
                            "detailedMessage": {
                                "properties": {
                                    "html": {
                                        "type": "string"
                                    },
                                    "markdown": {
                                        "type": "string"
                                    },
                                    "text": {
                                        "type": "string"
                                    }
                                },
                                "type": "object"
                            },
                            "eventType": {
                                "type": "string"
                            },
                            "id": {
                                "type": "string"
                            },
                            "message": {
                                "properties": {
                                    "html": {
                                        "type": "string"
                                    },
                                    "markdown": {
                                        "type": "string"
                                    },
                                    "text": {
                                        "type": "string"
                                    }
                                },
                                "type": "object"
                            },
                            "notificationId": {
                                "type": "integer"
                            },
                            "publisherId": {
                                "type": "string"
                            },
                            "resource": {
                                "properties": {
                                    "buildNumber": {
                                        "type": "string"
                                    },
                                    "definition": {
                                        "properties": {
                                            "batchSize": {
                                                "type": "integer"
                                            },
                                            "definitionType": {
                                                "type": "string"
                                            },
                                            "id": {
                                                "type": "integer"
                                            },
                                            "name": {
                                                "type": "string"
                                            },
                                            "triggerType": {
                                                "type": "string"
                                            },
                                            "url": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "drop": {
                                        "properties": {
                                            "downloadUrl": {
                                                "type": "string"
                                            },
                                            "location": {
                                                "type": "string"
                                            },
                                            "type": {
                                                "type": "string"
                                            },
                                            "url": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "dropLocation": {
                                        "type": "string"
                                    },
                                    "finishTime": {
                                        "type": "string"
                                    },
                                    "hasDiagnostics": {
                                        "type": "boolean"
                                    },
                                    "id": {
                                        "type": "integer"
                                    },
                                    "lastChangedBy": {
                                        "properties": {
                                            "displayName": {
                                                "type": "string"
                                            },
                                            "id": {
                                                "type": "string"
                                            },
                                            "imageUrl": {
                                                "type": "string"
                                            },
                                            "uniqueName": {
                                                "type": "string"
                                            },
                                            "url": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "log": {
                                        "properties": {
                                            "downloadUrl": {
                                                "type": "string"
                                            },
                                            "type": {
                                                "type": "string"
                                            },
                                            "url": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "queue": {
                                        "properties": {
                                            "id": {
                                                "type": "integer"
                                            },
                                            "name": {
                                                "type": "string"
                                            },
                                            "queueType": {
                                                "type": "string"
                                            },
                                            "url": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "reason": {
                                        "type": "string"
                                    },
                                    "requests": {
                                        "items": {
                                            "properties": {
                                                "id": {
                                                    "type": "integer"
                                                },
                                                "requestedFor": {
                                                    "properties": {
                                                        "displayName": {
                                                            "type": "string"
                                                        },
                                                        "id": {
                                                            "type": "string"
                                                        },
                                                        "imageUrl": {
                                                            "type": "string"
                                                        },
                                                        "uniqueName": {
                                                            "type": "string"
                                                        },
                                                        "url": {
                                                            "type": "string"
                                                        }
                                                    },
                                                    "type": "object"
                                                },
                                                "url": {
                                                    "type": "string"
                                                }
                                            },
                                            "required": [
                                                "id",
                                                "url",
                                                "requestedFor"
                                            ],
                                            "type": "object"
                                        },
                                        "type": "array"
                                    },
                                    "retainIndefinitely": {
                                        "type": "boolean"
                                    },
                                    "sourceGetVersion": {
                                        "type": "string"
                                    },
                                    "startTime": {
                                        "type": "string"
                                    },
                                    "status": {
                                        "type": "string"
                                    },
                                    "uri": {
                                        "type": "string"
                                    },
                                    "url": {
                                        "type": "string"
                                    }
                                },
                                "type": "object"
                            },
                            "resourceContainers": {
                                "properties": {
                                    "account": {
                                        "properties": {
                                            "id": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "collection": {
                                        "properties": {
                                            "id": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    },
                                    "project": {
                                        "properties": {
                                            "id": {
                                                "type": "string"
                                            }
                                        },
                                        "type": "object"
                                    }
                                },
                                "type": "object"
                            },
                            "resourceVersion": {
                                "type": "string"
                            },
                            "subscriptionId": {
                                "type": "string"
                            }
                        },
                        "type": "object"
                    }
                },
                "kind": "Http",
                "type": "Request"
            }
        }
    },
    "parameters": {
        "$connections": {
            "value": {
                "visualstudioteamservices": {
                    "connectionId": "/subscriptions/13bd0bca-5bf3-4c8b-be25-3661251884b8/resourceGroups/bug-workitem-demo-rg/providers/Microsoft.Web/connections/visualstudioteamservices",
                    "connectionName": "visualstudioteamservices",
                    "id": "/subscriptions/13bd0bca-5bf3-4c8b-be25-3661251884b8/providers/Microsoft.Web/locations/westeurope/managedApis/visualstudioteamservices"
                }
            }
        }
    }
}








  • About
  • Contact
  • Search
  • Powered by Jekyll using the Trio theme