Using Azure Logic Apps to create an Azure DevOps Bug WorkItem
Table of contents
- Table of contents
- Azure Logic Apps
- 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.
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).
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
You should now have an empty Logic App Resource created.
Let’s add our first Action “When a HTTP request is received” by opening the Designer.
At a new POST Method Parameter and save the Logic App and copy the HTTP Get URL.
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.
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.
Configure the properties of the Web Hook and test the webhook.
Copy the Request from the Test to the Logic App to be used as sample payload to generate schema.
Click on Done and save Logic App.
Add Logic App Actions
The following Actions need to be added.
- Initialize variable - Iteration
Save Logic App.
- Initialize - Organization
Save Logic App.
- Set variable - Organization
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.
Save Logic App.
- 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.
You first need to create a connection to Azure DevOps.
Configure the Send an HTTP Request to Azure DevOps without configuring the Relative URL.
Go to Code View after first saving the 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.
- Set Variable - Iteration
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.
Save Logic App.
- Create a Work Item
Add a new Azure DevOps Action to your Logic App.
Configure the following properties:
- Organization Name,
- Project Name
- WorkItem Type
- Title
- Description
- Area Path
- Iteration Path
- Repro Steps
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"
},
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:
Test the updated Web Hook in both Azure DevOps and in Azure.
If you get an unauthorized error message on the Send an HTTP request to Azure DevOps Action.
Go to API Connections and Delete the Azure DevOps Connection.
Go to the Logic Analytics Designer and re-add the Connection by Adding a new one.
Save the Logic App. And Authorize API Connection again and save.
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.
Check the Logic App Runs History in the Portal.
And finally check in Azure DevOps if the Bug Work Item is being created.
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"
}
}
}
}
}






































