Documentation for automated readers
A curated documentation index is available at: https://grafana.com/llms.txt
A complete documentation index is available at: https://grafana.com/llms-full.txt
These indexes can help with page discovery before fetching individual documents.
This page is also available in Markdown, which may be easier for automated readers and AI tools to parse than HTML. The Markdown version is available at https://grafana.com/docs/grafana-cloud/platform/workflows/reference/blueprints/weekly-incident-metrics-teams.md, or by sending Accept: text/markdown to https://grafana.com/docs/grafana-cloud/platform/workflows/reference/blueprints/weekly-incident-metrics-teams/. For broader documentation discovery, the curated index is available at https://grafana.com/llms.txt and the complete index is available at https://grafana.com/llms-full.txt.
Weekly incident metrics to Microsoft Teams
Note
Grafana Workflows is currently in public preview. Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.
Every Monday, pull last week’s incidents from the Grafana IRM API, compute counts and time to resolve in Python, and post a scorecard to Microsoft Teams.
This Grafana Workflows blueprint is in the Reports category of the Blueprints library.
Before you begin
To use this blueprint, you need:
- The Grafana IRM Microsoft Teams integration. For details, refer to Microsoft Teams integration for Grafana IRM.
- A workflow secret named
grafana-api-token.
To create a secret, refer to Manage secrets.
Steps
Steps run in order from first to last. A step inside a switch branch runs only when Workflows chooses that branch.
| Step | ID | Type | Description |
|---|---|---|---|
| Fetch recent incidents | fetch-incidents | http.call | Queries the 100 most recent incidents from the Incident API. Replace your-stack.grafana.net with your Grafana Cloud stack, for example mycompany.grafana.net. Also create a workflow secret named grafana-api-token that holds a service account token with permission to read incidents. Raise the limit if you have more than 100 incidents a week. |
| Fail if the API call failed | check-response | filter | Fails the run with an error instead of posting an empty report when the API call does not succeed. No changes needed. |
| Compute the metrics | compute | code.execute | Computes weekly incident metrics and builds the Teams card. Change lookbackDays in args, or add your own metrics. |
| Post the scorecard to Teams | post-scorecard | msteams.message.post | Posts the metrics card. Pick the Teams team and channel for your weekly ops review. |
For the inputs and outputs of each step type, refer to Step and trigger types reference.
Before you turn on the workflow, customize any steps that need input from you, such as a channel ID or a schedule name. Each step’s description says what to set.
Workflow definition
Save this definition as a YAML file to create the workflow with gcx, the Workflows API, or Terraform, or give it to a coding agent as an example.
Replace FOLDER_UID with the UID of the folder for the workflow.
Blueprints start disabled, so finish customizing the steps before you set enabled to true.
For details, refer to Manage workflows as code.
apiVersion: workflows.ext.grafana.app/v1alpha1
kind: Definition
metadata:
name: weekly-incident-metrics-teams
annotations:
grafana.app/folder: <FOLDER_UID>
spec:
name: Weekly incident metrics to Microsoft Teams
enabled: false
startWhen:
matchingRules: []
schedules:
- '0 9 * * 1'
resources:
secrets:
grafanaApiToken: 'secret://grafana-api-token'
steps:
- id: fetch-incidents
type: http.call
name: Fetch recent incidents
description: >-
Queries the 100 most recent incidents from the Incident API. Replace your-stack.grafana.net
with your Grafana Cloud stack, for example mycompany.grafana.net. Also create a workflow
secret named grafana-api-token that holds a service account token with permission to read
incidents. Raise the limit if you have more than 100 incidents a week.
inputs:
method: 'POST'
url: 'https://your-stack.grafana.net/api/plugins/grafana-irm-app/resources/api/v1/IncidentsService.QueryIncidents'
body: '{"query": {"limit": 100, "orderDirection": "DESC"}}'
authMethod: 'bearer'
authSecret: '${resources.secret.grafanaApiToken}'
- id: check-response
type: filter
name: Fail if the API call failed
description: Fails the run with an error instead of posting an empty report when the API call does not succeed. No changes needed.
inputs:
condition: "steps['fetch-incidents'].outputs.responseCode == 200"
failOnExit: 'true'
- id: compute
type: code.execute
name: Compute the metrics
description: Computes weekly incident metrics and builds the Teams card. Change lookbackDays in args, or add your own metrics.
inputs:
language: 'python'
args: '{"lookbackDays": 7}'
code: |
import json
from collections import Counter
from datetime import datetime, timedelta, timezone
def parse_time(value):
if not value:
return None
return datetime.fromisoformat(value.replace("Z", "+00:00"))
body = json.loads(context["steps"]["fetch-incidents"]["outputs"]["responseBody"])
since = datetime.now(timezone.utc) - timedelta(days=args.get("lookbackDays", 7))
incidents = [
i for i in body.get("incidents", [])
if not i.get("isDrill") and (parse_time(i.get("createdTime")) or since) >= since
]
by_severity = Counter((i.get("severity") or "unknown").lower() for i in incidents)
resolve_hours = []
for i in incidents:
start = parse_time(i.get("incidentStart") or i.get("createdTime"))
end = parse_time(i.get("closedTime"))
if start and end:
resolve_hours.append((end - start).total_seconds() / 3600)
still_open = sum(1 for i in incidents if i.get("status") != "resolved")
mttr = f"{sum(resolve_hours) / len(resolve_hours):.1f} h" if resolve_hours else "n/a"
severity_facts = [
{"title": severity.title(), "value": str(count)}
for severity, count in by_severity.most_common()
]
card = {
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{"type": "TextBlock", "text": "Weekly incident metrics", "size": "Large", "weight": "Bolder"},
{"type": "TextBlock", "text": f"Since {since:%Y-%m-%d}", "isSubtle": True, "spacing": "None"},
{
"type": "FactSet",
"facts": [
{"title": "Incidents", "value": str(len(incidents))},
{"title": "Still open", "value": str(still_open)},
{"title": "Mean time to resolve", "value": mttr},
],
},
{"type": "TextBlock", "text": "By severity", "weight": "Bolder"},
{"type": "FactSet", "facts": severity_facts or [{"title": "None", "value": "0"}]},
],
}
print(json.dumps(card))
- id: post-scorecard
type: msteams.message.post
name: Post the scorecard to Teams
description: Posts the metrics card. Pick the Teams team and channel for your weekly ops review.
inputs:
teamID: ''
channelID: ''
messageText: 'Weekly incident metrics'
adaptiveCard: '${steps.compute.outputs.stdout}'How it works
This section explains when the workflow runs and how its steps work together.
When it runs
This blueprint runs on the schedule 0 9 * * 1.
How the steps fit together
These notes cover how the steps connect and which Workflows behaviors the blueprint relies on.
- Shows the HTTP, Python, and secrets pattern end to end.
- A schedule trigger runs Mondays at 09:00 UTC.
http.callPOSTs to the Grafana IRM Incident API (IncidentsService.QueryIncidents) with a service account token.- The token is stored as a workflow secret, declared under
resources.secrets, and referenced only inauthSecret, asecretStringparameter, so it is redacted from run history. - A filter step with
failOnExitset to true fails the run loudly when the API does not return 200. - The Python
code.executestep reads the response body from context[“steps”][“fetch-incidents”][“outputs”], keeps incidents created in the last 7 days, counts them by severity, computes mean time to resolve, and prints a complete Adaptive Card as JSON. - The sandbox has no network access, which is why the HTTP call happens in its own step.
- The Teams step passes stdout straight to
adaptiveCard. - Customize the stack host in the URL, the secret name, the lookback window in args, and the Teams team and channel.
Next steps
Was this page helpful?
Related resources from Grafana Labs


