This document outlines a workflow using Microsoft Power Automate to report test failures to a specific Microsoft Teams channel. The workflow enables users to take immediate action to either resolve the issue or automatically create a Jira ticket.
It is part of a medium article I wrote here.
Whenever a test fails, a dynamic card is sent to the designated Teams channel. The card includes test failure details and offers two primary actions:
- Mark as Fixed – Allows the user to mark the issue as resolved and add any relevant comments.
- Create a Jira Ticket – Opens a Jira ticket to track the issue.
Additionally, a text field is provided for users to include extra details related to the failure, fix, or Jira ticket creation.
In this workflow example, a dynamic card is triggered for every failed test on the exaprint.fr
website's product pages.
Below is a visual representation of the entire workflow:
The workflow begins with an HTTP request that serves as the trigger for the test report.
{
"type": "Request",
"kind": "Http",
"inputs": {
"triggerAuthenticationType": "All",
"schema": { "type": "string" },
"method": "POST"
}
}
In our use case, the payload (body) is simply the url of the failed product page.
In this step, an adaptive card is posted to the Teams channel. The card displays the failed test URL as a clickable link and includes two action buttons: Mark as Fixed and Create a Jira Ticket.
Once the user selects an action, the workflow proceeds based on their choice.
Connector: MS Teams / Operation ID: PostCardAndWaitForResponse
{
"type": "OpenApiConnectionWebhook",
"inputs": {
"parameters": {
"poster": "Flow bot",
"location": "Group chat",
"body/body/messageBody": "{\n \"type\": \"AdaptiveCard\",\n \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n \"version\": \"1.2\",\n \"msteams\": {\n \"width\": \"Full\"\n },\n \"body\": [\n {\n \"type\": \"Container\",\n \"items\": [\n {\n \"type\": \"TextBlock\",\n \"text\": \"[@{triggerBody()}](@{triggerBody()})\",\n \"wrap\": true,\n \"id\": \"failedUrl\"\n },\n {\n \"type\": \"Input.Text\",\n \"placeholder\": \"Ajouter des détails ici sur la correction ou pour le Jira\",\n \"isMultiline\": true,\n \"id\": \"moreInfo\",\n \"separator\": true,\n \"label\": \"Plus d'info (facultatif) :\"\n }\n ]\n },\n {\n \"type\": \"ActionSet\",\n \"actions\": [\n {\n \"type\": \"Action.Submit\",\n \"title\": \"Corrigé\",\n \"id\": \"fixed\",\n \"style\": \"positive\"\n },\n {\n \"type\": \"Action.Submit\",\n \"title\": \"Faire un JIRA\",\n \"id\": \"jira\",\n \"style\": \"default\"\n }\n ]\n }\n ]\n}",
"body/body/updateMessage": "Un instant, je traite l'info.... :)",
"body/body/recipient/recipient": "id of your channel.."
},
"host": {
"apiId": "/providers/Microsoft.PowerApps/apis/shared_teams",
"connection": "shared_teams",
"operationId": "PostCardAndWaitForResponse"
}
},
"runAfter": {}
}
You can design and test your adaptive card using the Adaptive Card Designer.
Once the card is sent, the workflow waits for user input, retrieving details about their selected action.
The following block composes data about the user's action:
{
"type": "Compose",
"inputs": {
"submitActionId": "@body('ReportCard').submitActionId",
"messageId": "@body('ReportCard').messageId"
},
"runAfter": {
"ReportCard": ["Succeeded"]
}
}
If no action is taken, the workflow includes a timeout mechanism. After 2 weeks, the workflow automatically terminates.
A timer variable is initialized for the timeout duration.
{
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "endTime",
"type": "string",
"value": "@{getFutureTime(2, 'Week')}"
}
]
},
"runAfter": {}
}
The workflow waits for 2 weeks before terminating.
{
"type": "Wait",
"inputs": {
"until": {
"timestamp": "@variables('endTime')"
}
},
"runAfter": {
"End_Time": ["Succeeded"]
}
}
After the wait period, the workflow fails if no action was taken.
{
"type": "Terminate",
"inputs": {
"runStatus": "Cancelled"
},
"runAfter": {
"Delay_Until": ["Succeeded"]
}
}
This step creates two branches, based on whether the user clicked Fixed or Create a Jira Ticket.
{
"type": "If",
"expression": {
"and": [
{
"equals": [
"@outputs('Retrieve_infos_from_card_(action_n_more_info)').submitActionId",
"fixed"
]
}
]
},
"actions": "Will go into details bellow",
"runAfter": {
"Retrieve_infos_from_card_(action_n_more_info)": ["Succeeded"]
}
}
If the user selects Fixed, the adaptive card is updated with relevant details, including the user who resolved the issue and any additional comments.
{
"type": "OpenApiConnection",
"inputs": {
"parameters": {
"poster": "Flow bot",
"location": "Group chat",
"body/messageId": "@{outputs('Retrieve_infos_from_card_(action_n_more_info)').messageId}",
"body/recipient": "id of your channel..",
"body/messageBody": "{\n \"type\": \"AdaptiveCard\",\n \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n \"version\": \"1.2\",\n \"msteams\": {\n \"width\": \"Full\"\n },\n \"body\": [\n {\n \"type\": \"Container\",\n \"items\": [\n {\n \"type\": \"TextBlock\",\n \"text\": \"[@{triggerBody()}](@{triggerBody()})\",\n \"wrap\": true,\n \"id\": \"failedUrl\"\n },\n {\n \"type\": \"TextBlock\",\n \"text\": \"Le problème a été noté comme corrigé par @{body('ReportCard').responder.displayName}\",\n \"wrap\": true\n },\n {\n \"type\": \"TextBlock\",\n \"text\": \"Infos aditionnelles : @{body('ReportCard')?.data?.moreInfo}\",\n \"wrap\": true\n }\n ]\n }\n ]\n}"
},
"host": {
"apiId": "/providers/Microsoft.PowerApps/apis/shared_teams",
"connection": "shared_teams",
"operationId": "UpdateCardInConversation"
}
}
}
If the user selects Create a Jira Ticket, the workflow calls the Jira REST API to create a new ticket.
{
"type": "Http",
"inputs": {
"uri": "https://exaprint.atlassian.net/rest/api/3/issue",
"method": "POST",
"headers": {
"Accept": "application/json",
"Content-Type": "application/json"
},
"body": {
"fields": {
"description": {
"content": [
{
"content": [
{
"text": "This url is failing: @{triggerBody()}\n\nAdditional info:\n@{body('ReportCard')?.data?.moreInfo}",
"type": "text"
}
],
"type": "paragraph"
}
],
"type": "doc",
"version": 1
},
"issuetype": {
"id": "10320"
},
"project": {
"key": "BUG"
},
"customfield_11129": "@body('ReportCard').responder.email",
"summary": "Bug price can't order from Teams: @{concat(slice(triggerBody(), 23, 73),'[...]')}"
},
"update": {}
},
"authentication": {
"type": "Basic",
"username": "your email adress",
"password": "token"
}
}
}
Use the API Get issue
from Jira to get information on what is expected as fields and values for the Create issue
route. See the documentation here.
customfield_11129
is a custom field in our Jira instance that stores the sender's email address. You can create this custom field by visiting your Jira instance at: https://your-domain.atlassian.net/secure/admin/ViewCustomFields.jspa
.
This field is necessary because Jira will automatically assign the issue to you as the reporter by default. By including the sender's email in this custom field, you can use Jira automation to reassign the issue to the correct user. Whenever an issue is created and the email field is populated, an automated Jira action can change the reporter from the default to the user who initiated the request.
Maybe an edit with the API is possible to change the reporter. We had that automation for a other use case, so we went with it.
The adaptive card is updated again to reflect that a Jira ticket has been created, along with a link to the new Jira issue.
{
"type": "OpenApiConnection",
"inputs": {
"parameters": {
"poster": "Flow bot",
"location": "Group chat",
"body/messageId": "@{outputs('Retrieve_infos_from_card_(action_n_more_info)').messageId}",
"body/recipient": "id of the channel..",
"body/messageBody": "{\n \"type\": \"AdaptiveCard\",\n \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n \"version\": \"1.2\",\n \"msteams\": {\n \"width\": \"Full\"\n },\n \"body\": [\n {\n \"type\": \"Container\",\n \"items\": [\n {\n \"type\": \"TextBlock\",\n \"text\": \"[@{triggerBody()}](@{triggerBody()})\",\n \"wrap\": true,\n \"id\": \"failedUrl\"\n },\n {\n \"type\": \"TextBlock\",\n \"text\": \"Un jira a été créé par @{body('ReportCard').responder.displayName} :\",\n \"wrap\": true\n },\n {\n \"type\": \"TextBlock\",\n \"text\": \"[@{body('Send_POST_to_create_Jira').key}](https://exaprint.atlassian.net/browse/@{body('Send_POST_to_create_Jira').key})\",\n \"wrap\": true\n }\n ]\n }\n ]\n}"
},
"host": {
"apiId": "/providers/Microsoft.PowerApps/apis/shared_teams",
"connection": "shared_teams",
"operationId": "UpdateCardInConversation"
}
},
"runAfter": {
"Send_POST_to_create_Jira": ["SUCCEEDED"]
}
}
Once either action is completed, the workflow is marked as successful.
{
"type": "Terminate",
"inputs": {
"runStatus": "Succeeded"
},
"runAfter": {
"If_error_fixed": ["Succeeded"]
}
}