Post

Using Azure Logic Apps to create an Azure DevOps Bug WorkItem

Using Azure Logic Apps to create an Azure DevOps Bug WorkItem

Table of contents

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
ExpressionValueExplanation
@{variables(‘Organization’)}stefanstrangerThis value is from the earlier variable
@{triggerOutputs()[‘queries’][‘Project’]}ContosoThis value is coming from a parameter input on the Web Hook Webrequest. Example: https://foo.com?Project=Contoso
@{triggerOutputs()[‘queries’][‘Team’]}Contoso TeamThis 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:

1
2
3
4
5
6
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
{
    "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"
                }
            }
        }
    }
}
This post is licensed under CC BY 4.0 by the author.