|
| 1 | +#!/usr/bin/env pwsh |
| 2 | +# Shows all PR checks as a summary table, then expands AzDO build timelines for any |
| 3 | +# checks that point at Azure Pipelines (https://dev.azure.com/...). |
| 4 | +# Requires `gh` CLI authenticated against the target repo. |
| 5 | +# |
| 6 | +# Usage: |
| 7 | +# ./Show-PullRequestBuilds.ps1 -PullRequest 2100 |
| 8 | +# ./Show-PullRequestBuilds.ps1 -PullRequest 2100 -Repo dotnet/docker-tools |
| 9 | +# ./Show-PullRequestBuilds.ps1 -PullRequest 2100 -ShowAllTasks |
| 10 | + |
| 11 | +[CmdletBinding()] |
| 12 | +param( |
| 13 | + [Parameter(Mandatory)][int] $PullRequest, |
| 14 | + [string] $Repo, |
| 15 | + [switch] $ShowAllTasks |
| 16 | +) |
| 17 | + |
| 18 | +$ErrorActionPreference = "Stop" |
| 19 | + |
| 20 | +$ghArgs = @("pr", "view", $PullRequest, "--json", "statusCheckRollup") |
| 21 | +if ($Repo) { $ghArgs += @("--repo", $Repo) } |
| 22 | + |
| 23 | +$checksJson = & gh @ghArgs 2>&1 |
| 24 | +if ($LASTEXITCODE -ne 0) { |
| 25 | + throw "gh pr view failed: $checksJson" |
| 26 | +} |
| 27 | + |
| 28 | +$checks = ($checksJson | ConvertFrom-Json).statusCheckRollup |
| 29 | + |
| 30 | +# statusCheckRollup mixes two shapes: |
| 31 | +# CheckRun: { name, status, conclusion, detailsUrl, workflowName } |
| 32 | +# StatusContext: { context, state, targetUrl, description } |
| 33 | +# Normalize them. |
| 34 | +$normalized = foreach ($check in $checks) { |
| 35 | + if ($check.PSObject.Properties.Name -contains "context") { |
| 36 | + [pscustomobject]@{ |
| 37 | + Name = $check.context |
| 38 | + State = $check.state |
| 39 | + Url = $check.targetUrl |
| 40 | + } |
| 41 | + } |
| 42 | + else { |
| 43 | + $state = if ($check.conclusion) { $check.conclusion } else { $check.status } |
| 44 | + [pscustomobject]@{ |
| 45 | + Name = $check.name |
| 46 | + State = $state |
| 47 | + Url = $check.detailsUrl |
| 48 | + } |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +# AzDO build results URLs look like: |
| 53 | +# https://dev.azure.com/<org>/<project>/_build/results?buildId=<id>... |
| 54 | +$pattern = '^https?://dev\.azure\.com/(?<org>[^/]+)/(?<project>[^/]+)/_build/results\?.*buildId=(?<buildId>\d+)' |
| 55 | + |
| 56 | +$builds = @() |
| 57 | +foreach ($check in $normalized) { |
| 58 | + if (-not $check.Url) { continue } |
| 59 | + $match = [regex]::Match($check.Url, $pattern) |
| 60 | + if (-not $match.Success) { continue } |
| 61 | + |
| 62 | + $builds += [pscustomobject]@{ |
| 63 | + Org = $match.Groups["org"].Value |
| 64 | + Project = $match.Groups["project"].Value |
| 65 | + BuildId = [int]$match.Groups["buildId"].Value |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +# Deduplicate by buildId (a single build can produce multiple check-run rows). |
| 70 | +$builds = $builds | Sort-Object BuildId -Unique |
| 71 | + |
| 72 | +$title = if ($Repo) { "$Repo#$PullRequest" } else { "PR #$PullRequest" } |
| 73 | +Write-Host "## Checks for $title" |
| 74 | +Write-Host "" |
| 75 | +Write-Host "$($normalized.Count) check(s); $($builds.Count) Azure Pipelines build(s)." |
| 76 | +Write-Host "" |
| 77 | + |
| 78 | +if ($normalized.Count -gt 0) { |
| 79 | + Write-Host "Check | State | URL" |
| 80 | + Write-Host "--- | --- | ---" |
| 81 | + foreach ($check in $normalized | Sort-Object Name) { |
| 82 | + Write-Host "$($check.Name) | $($check.State) | $($check.Url)" |
| 83 | + } |
| 84 | + Write-Host "" |
| 85 | +} |
| 86 | + |
| 87 | +if ($builds.Count -eq 0) { return } |
| 88 | + |
| 89 | +$timelineScript = "$PSScriptRoot/Show-BuildTimeline.ps1" |
| 90 | + |
| 91 | +foreach ($build in $builds) { |
| 92 | + Write-Host "---" |
| 93 | + Write-Host "" |
| 94 | + & $timelineScript ` |
| 95 | + -Organization $build.Org ` |
| 96 | + -Project $build.Project ` |
| 97 | + -BuildId $build.BuildId ` |
| 98 | + -ShowAllTasks:$ShowAllTasks |
| 99 | +} |
0 commit comments