mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
added documetation
This commit is contained in:
@@ -1,20 +1,129 @@
|
||||
# PowerShell script to analyze renovate PRs across repositories with detailed statistics
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Analyzes Renovate pull requests across all repositories in an Azure DevOps project and generates detailed statistics.
|
||||
|
||||
.DESCRIPTION
|
||||
This script connects to an Azure DevOps organization and project to analyze Renovate dependency update pull requests.
|
||||
It processes all repositories (active, disabled, and locked) and generates comprehensive statistics including:
|
||||
- Total Renovate PRs per repository
|
||||
- Open, completed, and abandoned PR counts
|
||||
- Latest creation and completion dates
|
||||
- Branch information for latest PRs
|
||||
- Repository status (active, disabled, locked, error)
|
||||
|
||||
The script outputs both a detailed text report and a CSV file for further analysis.
|
||||
Renovate PRs are identified by their branch naming pattern: "refs/heads/renovate/*"
|
||||
|
||||
.PARAMETER Organization
|
||||
The Azure DevOps organization name. Defaults to "effectory" if not specified.
|
||||
|
||||
.PARAMETER Project
|
||||
The Azure DevOps project name. Defaults to "Survey Software" if not specified.
|
||||
|
||||
.PARAMETER PAT
|
||||
The Azure DevOps Personal Access Token (PAT) used for authentication. This token must have
|
||||
'Code (Read)' permissions to access repository and pull request information.
|
||||
|
||||
.PARAMETER OutputFile
|
||||
The path and filename for the output text report. Defaults to a timestamped filename:
|
||||
"RenovatePRs_Stats_yyyyMMdd_HHmmss.txt"
|
||||
|
||||
.EXAMPLE
|
||||
.\renovate-stats.ps1 -PAT "your-personal-access-token"
|
||||
|
||||
Analyzes Renovate PRs using default organization and project settings.
|
||||
|
||||
.EXAMPLE
|
||||
.\renovate-stats.ps1 -PAT "your-pat-token" -Organization "myorg" -Project "MyProject"
|
||||
|
||||
Analyzes Renovate PRs for a specific organization and project.
|
||||
|
||||
.EXAMPLE
|
||||
.\renovate-stats.ps1 -PAT "your-token" -OutputFile "C:\Reports\renovate-analysis.txt"
|
||||
|
||||
Analyzes Renovate PRs and saves the report to a custom location.
|
||||
|
||||
.OUTPUTS
|
||||
Creates two output files:
|
||||
1. Text Report: Detailed statistics with tables and summaries (.txt)
|
||||
2. CSV Export: Raw data for further analysis (.csv)
|
||||
|
||||
The text report includes:
|
||||
- Repository-by-repository statistics table (sorted by last created date)
|
||||
- Summary statistics (total repos, repos with/without Renovate, PR counts)
|
||||
- List of disabled/locked/error repositories
|
||||
|
||||
The CSV contains columns:
|
||||
- Repository: Repository name
|
||||
- TotalRenovatePRs: Total count of Renovate PRs
|
||||
- OpenPRs: Count of active Renovate PRs
|
||||
- CompletedPRs: Count of completed Renovate PRs
|
||||
- AbandonedPRs: Count of abandoned Renovate PRs
|
||||
- LastCreated: Date of most recent Renovate PR creation (yyyy-MM-dd format)
|
||||
- LastCompleted: Date of most recent Renovate PR completion (yyyy-MM-dd format)
|
||||
- LatestOpenBranch: Branch name of most recent open Renovate PR
|
||||
- LatestCompletedBranch: Branch name of most recent completed Renovate PR
|
||||
- LastCompletedPRTitle: Title of most recent completed Renovate PR
|
||||
|
||||
.NOTES
|
||||
Author: Cloud Engineering Team
|
||||
Created: 2025
|
||||
Requires: PowerShell 5.1 or later
|
||||
Dependencies: Internet connectivity to Azure DevOps
|
||||
|
||||
The script uses Azure DevOps REST API version 6.0 to retrieve repository and pull request information.
|
||||
Ensure your Personal Access Token has the necessary permissions before running the script.
|
||||
|
||||
Renovate is a dependency update tool that creates automated pull requests. This script specifically
|
||||
looks for PRs with branch names matching the pattern "refs/heads/renovate/*"
|
||||
|
||||
The script handles various repository states:
|
||||
- Active: Normal repositories that can be analyzed
|
||||
- Disabled: Repositories marked as disabled in Azure DevOps
|
||||
- Locked: Repositories that are locked (read-only)
|
||||
- Error: Repositories that couldn't be accessed due to API errors
|
||||
|
||||
Date parsing is culture-invariant and includes fallback mechanisms to handle various date formats
|
||||
from the Azure DevOps API.
|
||||
|
||||
.LINK
|
||||
https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull-requests/get-pull-requests
|
||||
https://renovatebot.com/
|
||||
#>
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Parameter(Mandatory=$false, HelpMessage="Azure DevOps organization name")]
|
||||
[string]$Organization = "effectory",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Parameter(Mandatory=$false, HelpMessage="Azure DevOps project name")]
|
||||
[string]$Project = "Survey Software",
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Parameter(Mandatory=$true, HelpMessage="Azure DevOps Personal Access Token with Code (Read) permissions")]
|
||||
[string]$PAT,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Parameter(Mandatory=$false, HelpMessage="Output file path for the text report")]
|
||||
[string]$OutputFile = "RenovatePRs_Stats_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
|
||||
)
|
||||
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Outputs a message to both the console and the output file.
|
||||
|
||||
.DESCRIPTION
|
||||
This helper function writes messages to the console with optional color formatting
|
||||
and simultaneously appends the same message to the output file, unless ConsoleOnly is specified.
|
||||
|
||||
.PARAMETER Message
|
||||
The message to output.
|
||||
|
||||
.PARAMETER ForegroundColor
|
||||
The console text color. Defaults to "White".
|
||||
|
||||
.PARAMETER ConsoleOnly
|
||||
If specified, the message will only be written to the console and not the output file.
|
||||
Useful for progress messages that shouldn't clutter the report.
|
||||
#>
|
||||
function Write-Output-Both {
|
||||
param (
|
||||
[string]$Message,
|
||||
@@ -29,28 +138,34 @@ function Write-Output-Both {
|
||||
}
|
||||
|
||||
|
||||
# Initialize the output file with a header
|
||||
Set-Content -Path $OutputFile -Value "Renovate Pull Requests Statistics - $(Get-Date)`n"
|
||||
|
||||
# Prepare authentication for Azure DevOps REST API calls
|
||||
# Personal Access Token must be base64 encoded with a colon prefix
|
||||
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$PAT"))
|
||||
$headers = @{
|
||||
Authorization = "Basic $base64AuthInfo"
|
||||
}
|
||||
|
||||
|
||||
# Retrieve all repositories from the Azure DevOps project
|
||||
$reposUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories?api-version=6.0"
|
||||
$repositories = Invoke-RestMethod -Uri $reposUrl -Method Get -Headers $headers
|
||||
|
||||
|
||||
$repoStats = @()
|
||||
$reposWithoutRenovate = @()
|
||||
$disabledRepos = @()
|
||||
# Initialize arrays to categorize repositories and store statistics
|
||||
$repoStats = @() # Active repositories with detailed statistics
|
||||
$reposWithoutRenovate = @() # Active repositories with no Renovate PRs
|
||||
$disabledRepos = @() # Disabled, locked, or error repositories
|
||||
|
||||
|
||||
# Process each repository in the project
|
||||
foreach ($repo in $repositories.value) {
|
||||
$repoName = $repo.name
|
||||
$repoId = $repo.id
|
||||
Write-Output-Both "Analyzing repository: $repoName" -ForegroundColor Gray -ConsoleOnly
|
||||
|
||||
|
||||
# Check repository status (disabled, locked, or active)
|
||||
$isDisabled = $repo.isDisabled -eq $true
|
||||
$repoDetailsUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$repoId`?api-version=6.0"
|
||||
try {
|
||||
@@ -62,10 +177,12 @@ foreach ($repo in $repositories.value) {
|
||||
$isLocked = $false
|
||||
}
|
||||
|
||||
# Skip analysis for disabled or locked repositories
|
||||
if ($isDisabled -or $isLocked) {
|
||||
$status = if ($isDisabled) { "DISABLED" } elseif ($isLocked) { "LOCKED" } else { "UNKNOWN" }
|
||||
Write-Output-Both " Repository status: $status - Skipping analysis" -ForegroundColor Yellow -ConsoleOnly
|
||||
|
||||
# Create entry for disabled/locked repository with N/A values
|
||||
$disabledRepo = [PSCustomObject]@{
|
||||
Repository = "$repoName ($status)"
|
||||
TotalRenovatePRs = "N/A"
|
||||
@@ -83,23 +200,31 @@ foreach ($repo in $repositories.value) {
|
||||
continue
|
||||
}
|
||||
|
||||
# Retrieve all pull requests for the current repository
|
||||
$prsUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$repoId/pullrequests`?api-version=6.0&searchCriteria.status=all"
|
||||
try {
|
||||
$pullRequests = Invoke-RestMethod -Uri $prsUrl -Method Get -Headers $headers
|
||||
|
||||
# Filter for Renovate PRs based on branch naming pattern
|
||||
$renovatePRs = $pullRequests.value | Where-Object { $_.sourceRefName -like "refs/heads/renovate/*" }
|
||||
|
||||
if ($renovatePRs.Count -gt 0) {
|
||||
# Categorize Renovate PRs by status
|
||||
$openPRs = $renovatePRs | Where-Object { $_.status -eq "active" }
|
||||
$completedPRs = $renovatePRs | Where-Object { $_.status -eq "completed" }
|
||||
$abandonedPRs = $renovatePRs | Where-Object { $_.status -eq "abandoned" }
|
||||
|
||||
# Count PRs in each category
|
||||
$openCount = $openPRs.Count
|
||||
$completedCount = $completedPRs.Count
|
||||
$abandonedCount = $abandonedPRs.Count
|
||||
|
||||
# Find the most recent PRs in each category for detailed reporting
|
||||
$latestOpen = $openPRs | Sort-Object creationDate -Descending | Select-Object -First 1
|
||||
$latestCompleted = $completedPRs | Sort-Object closedDate -Descending | Select-Object -First 1
|
||||
$latestCreated = $renovatePRs | Sort-Object creationDate -Descending | Select-Object -First 1
|
||||
|
||||
# Extract key information from the latest PRs
|
||||
$lastCreatedDate = if ($latestCreated) { $latestCreated.creationDate } else { "N/A" }
|
||||
$lastCompletedDate = if ($latestCompleted) { $latestCompleted.closedDate } else { "N/A" }
|
||||
$lastCompletedPRTitle = if ($latestCompleted) { $latestCompleted.title } else { "N/A" }
|
||||
@@ -118,7 +243,20 @@ foreach ($repo in $repositories.value) {
|
||||
LatestCompletedBranch = $latestCompletedBranch
|
||||
LastCompletedPRTitle = $lastCompletedPRTitle
|
||||
RepoStatus = "ACTIVE"
|
||||
SortDate = if ($lastCreatedDate -eq "N/A") { [DateTime]::MinValue } else { [DateTime]::Parse($lastCreatedDate) }
|
||||
SortDate = if ($lastCreatedDate -eq "N/A") {
|
||||
[DateTime]::MinValue
|
||||
} else {
|
||||
try {
|
||||
[DateTime]::Parse($lastCreatedDate, [System.Globalization.CultureInfo]::InvariantCulture)
|
||||
} catch {
|
||||
try {
|
||||
[DateTime]::ParseExact($lastCreatedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture)
|
||||
} catch {
|
||||
Write-Output-Both " Warning: Could not parse date '$lastCreatedDate' for repository $repoName" -ForegroundColor Yellow -ConsoleOnly
|
||||
[DateTime]::MinValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$repoStats += $repoStat
|
||||
@@ -160,41 +298,71 @@ foreach ($repo in $repositories.value) {
|
||||
}
|
||||
}
|
||||
|
||||
# Generate and display the main statistics report
|
||||
Write-Output-Both "`n===== RENOVATE PULL REQUEST STATISTICS BY REPOSITORY (SORTED BY LAST CREATED DATE) =====" -ForegroundColor Green
|
||||
if ($repoStats.Count -gt 0) {
|
||||
# Sort repositories by last created date (most recent first)
|
||||
$sortedStats = $repoStats | Sort-Object -Property SortDate -Descending
|
||||
|
||||
# Format the data for display with culture-invariant date parsing
|
||||
$displayStats = $sortedStats | Select-Object Repository, TotalRenovatePRs, OpenPRs, CompletedPRs, AbandonedPRs,
|
||||
@{Name="LastCreated"; Expression={
|
||||
if ($_.LastCreatedDate -eq "N/A" -or $_.LastCreatedDate -eq "Error") { $_.LastCreatedDate }
|
||||
else { [DateTime]::Parse($_.LastCreatedDate).ToString("yyyy-MM-dd") }
|
||||
if ($_.LastCreatedDate -eq "N/A" -or $_.LastCreatedDate -eq "Error") {
|
||||
$_.LastCreatedDate
|
||||
} else {
|
||||
try {
|
||||
[DateTime]::Parse($_.LastCreatedDate, [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
|
||||
} catch {
|
||||
try {
|
||||
[DateTime]::ParseExact($_.LastCreatedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
|
||||
} catch {
|
||||
$_.LastCreatedDate
|
||||
}
|
||||
}
|
||||
}
|
||||
}},
|
||||
@{Name="LastCompleted"; Expression={
|
||||
if ($_.LastCompletedDate -eq "N/A" -or $_.LastCompletedDate -eq "Error") { $_.LastCompletedDate }
|
||||
else { [DateTime]::Parse($_.LastCompletedDate).ToString("yyyy-MM-dd") }
|
||||
if ($_.LastCompletedDate -eq "N/A" -or $_.LastCompletedDate -eq "Error") {
|
||||
$_.LastCompletedDate
|
||||
} else {
|
||||
try {
|
||||
[DateTime]::Parse($_.LastCompletedDate, [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
|
||||
} catch {
|
||||
try {
|
||||
[DateTime]::ParseExact($_.LastCompletedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
|
||||
} catch {
|
||||
$_.LastCompletedDate
|
||||
}
|
||||
}
|
||||
}
|
||||
}},
|
||||
LatestOpenBranch, LatestCompletedBranch, LastCompletedPRTitle
|
||||
|
||||
# Export detailed data to CSV for further analysis
|
||||
$displayStats | Export-Csv -Path "$($OutputFile).csv" -NoTypeInformation
|
||||
# Add a note to the original output file
|
||||
Add-Content -Path $OutputFile -Value "Full data available in: $($OutputFile).csv"
|
||||
|
||||
# Add formatted table to the text report and display on console
|
||||
$statsTable = $displayStats | Format-Table -Property Repository, TotalRenovatePRs, OpenPRs, CompletedPRs, AbandonedPRs, LastCreated, LastCompleted, LatestCompletedBranch, LastCompletedPRTitle | Out-String
|
||||
Add-Content -Path $OutputFile -Value $statsTable.Trim() # Trim to remove extra whitespace
|
||||
Add-Content -Path $OutputFile -Value $statsTable.Trim()
|
||||
$displayStats | Format-Table -AutoSize
|
||||
|
||||
|
||||
# Calculate summary statistics
|
||||
$totalRepos = $repositories.value.Count
|
||||
$reposWithRenovate = ($repoStats | Where-Object { $_.TotalRenovatePRs -gt 0 }).Count
|
||||
$reposDisabledOrLocked = $disabledRepos.Count
|
||||
$activeRepos = $totalRepos - $reposDisabledOrLocked
|
||||
|
||||
# Calculate percentage of active repositories with Renovate PRs
|
||||
$percentWithRenovate = if ($activeRepos -gt 0) { [math]::Round(($reposWithRenovate / $activeRepos) * 100, 2) } else { 0 }
|
||||
|
||||
# Calculate totals across all active repositories
|
||||
$totalPRs = ($repoStats | Measure-Object -Property TotalRenovatePRs -Sum).Sum
|
||||
$totalOpenPRs = ($repoStats | Measure-Object -Property OpenPRs -Sum).Sum
|
||||
$totalCompletedPRs = ($repoStats | Measure-Object -Property CompletedPRs -Sum).Sum
|
||||
$totalAbandonedPRs = ($repoStats | Measure-Object -Property AbandonedPRs -Sum).Sum
|
||||
|
||||
# Display comprehensive summary statistics
|
||||
Write-Output-Both "`n===== SUMMARY STATISTICS =====" -ForegroundColor Cyan
|
||||
Write-Output-Both "Total repositories: $totalRepos"
|
||||
Write-Output-Both "Disabled, locked, or error repositories: $reposDisabledOrLocked"
|
||||
@@ -206,6 +374,7 @@ if ($repoStats.Count -gt 0) {
|
||||
Write-Output-Both "Total completed renovate PRs: $totalCompletedPRs"
|
||||
Write-Output-Both "Total abandoned renovate PRs: $totalAbandonedPRs"
|
||||
|
||||
# Display list of repositories that couldn't be analyzed
|
||||
if ($disabledRepos.Count -gt 0) {
|
||||
Write-Output-Both "`n===== DISABLED/LOCKED/ERROR REPOSITORIES (NOT INCLUDED IN MAIN REPORT) =====" -ForegroundColor Yellow
|
||||
$disabledList = $disabledRepos | ForEach-Object { $_.Repository }
|
||||
@@ -215,4 +384,5 @@ if ($repoStats.Count -gt 0) {
|
||||
Write-Output-Both "No active repositories found." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Display completion message
|
||||
Write-Output-Both "`nReport saved to: $OutputFile" -ForegroundColor Cyan
|
||||
Reference in New Issue
Block a user