Publishing Results Natively in Azure DevOps
Dashboards, Markdown Widgets, and REST Automation
In the previous posts, we covered data retrieval and status evaluation. Once a KPI value is calculated and its RAG status is determined, the final step is publication. Publication is not a cosmetic step. It completes the KPI lifecycle.
If KPI logic runs correctly but results are copied manually into dashboards, the solution is only partially automated. True KPI-as-code requires automated publication into Azure DevOps itself, without external tools and without manual intervention.
This post explains how dashboards and Markdown widgets can be managed programmatically using Azure DevOps REST APIs.
Why Native Publication Matters
Many organizations export KPI results to external reporting tools. While that approach can work, it introduces additional governance layers and operational complexity.
In a constrained environment where extensions and external integrations are limited, publishing directly into Azure DevOps dashboards provides several advantages:
- No external dependencies
- Immediate visibility for delivery teams
- Consistent permission model
- Full traceability through pipeline execution
The dashboard becomes the natural output surface of the KPI framework.
Understanding Azure DevOps Dashboards
Azure DevOps dashboards are scoped at the team level. Each dashboard contains a collection of widgets arranged on a grid.
Widgets include built-in types such as charts, query tiles, and Markdown widgets. The Markdown widget is particularly powerful because it allows structured, formatted KPI output without requiring a custom extension.
Dashboards and widgets are accessible via REST APIs. This makes them manageable through automation scripts.
REST API Pattern for Dashboard Management
To manage dashboards and widgets programmatically, the KPI framework uses the Dashboard REST API.
For example, to list dashboards:
1
GET https://dev.azure.com/{organization}/{project}/{team}/_apis/dashboard/dashboards
To list widgets within a dashboard:
1
GET https://dev.azure.com/{organization}/{project}/{team}/_apis/dashboard/dashboards/{dashboardId}/widgets
To create a Markdown widget, you must provide:
- Widget name
- Contribution ID (Markdown widget type)
- Position and size
- Markdown content
This is handled entirely through REST.
Publishing Markdown Content from Script
Your approved PowerShell script includes the function responsible for creating a Markdown widget:
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
function New-MarkdownWidget {
param(
[Parameter(Mandatory)] $OrgUrl,
[Parameter(Mandatory)] $Project,
[Parameter(Mandatory)] $Team,
[Parameter(Mandatory)] $DashboardId,
[Parameter(Mandatory)] $WidgetName,
[Parameter(Mandatory)] $MarkdownContent,
[Parameter(Mandatory)] [int] $Row,
[Parameter(Mandatory)] [int] $Column,
[Parameter(Mandatory)] [int] $RowSpan,
[Parameter(Mandatory)] [int] $ColumnSpan,
[Parameter(Mandatory)] $Headers
)
$ContributionId = "ms.vss-dashboards-web.Microsoft.VisualStudioOnline.Dashboards.MarkdownWidget"
$teamEnc = [uri]::EscapeDataString($Team)
$createUrl = "$OrgUrl/$Project/$teamEnc/_apis/dashboard/dashboards/$DashboardId/widgets?api-version=7.1-preview.2"
$body = @{
name = $WidgetName
contributionId = $ContributionId
position = @{ row = $Row; column = $Column }
size = @{ rowSpan = $RowSpan; columnSpan = $ColumnSpan }
settings = $MarkdownContent
settingsVersion = @{ major = 1; minor = 0; patch = 0 }
}
Invoke-Ado POST $createUrl $Headers $body
}
This function demonstrates how KPI output transitions from computed value to visible dashboard artifact.
The Markdown content itself is generated earlier in the pipeline and passed into this function as a fully structured string.
Idempotent Dashboard Behavior
A key design consideration is idempotency. If the pipeline runs multiple times, it must not create duplicate widgets or corrupt dashboard layout.
Your script addresses this by:
- Checking if a widget already exists.
- Deleting and recreating it if necessary.
- Preserving its original position and size when recreating.
This ensures:
- Stable dashboard layout.
- Predictable automation behavior.
- No manual cleanup required.
Automation must not degrade the dashboard experience over time.
Example: Commitment Ratio Widget Output
The Markdown output generated by the framework might look like:
1
2
3
4
5
6
7
## Commitment Ratio
| Metric | Sprint 10 | Sprint 11 | Sprint 12 |
|--------|-----------|-----------|-----------|
| Planned Stories | 20 | 18 | 22 |
| Completed Stories | 14 | 15 | 19 |
| Commitment Ratio | 🟠70% | 🟢 83% | 🟢 86% |
This content is injected directly into the Markdown widget through REST. No manual editing is required.
Because the pipeline owns the content, the dashboard becomes an extension of the codebase.
Security and Identity Considerations
Publishing dashboards requires appropriate permissions. In a governed environment, this must be done using a scoped service account.
The service account must:
- Have permission to edit dashboards in the target team.
- Have access only to required projects.
- Follow credential rotation and retention policies.
The publication step should not rely on elevated administrative privileges. It should align with least privilege principles established earlier.
Completing the KPI Lifecycle
At this stage, the KPI lifecycle becomes complete:
- Retrieve data correctly.
- Apply formula deterministically.
- Evaluate thresholds via governed logic.
- Publish results natively into dashboards.
No spreadsheets.
No external tools.
No manual copying.
The KPI exists entirely inside Azure DevOps as version-controlled logic executed by pipelines and rendered on dashboards.
What Comes Next
In the next post, we will examine the Commitment Ratio KPI as a full end-to-end case study, walking through the complete journey from raw data retrieval to final dashboard visualization.