Skip to content

Instantly share code, notes, and snippets.

@danmoseley
Last active July 22, 2025 19:22
Show Gist options
  • Save danmoseley/5b0ff875be8fbb5814becfabd102b155 to your computer and use it in GitHub Desktop.
Save danmoseley/5b0ff875be8fbb5814becfabd102b155 to your computer and use it in GitHub Desktop.
triage query (no milestone+rc1+p7)
# GitHub Issue Label Counter for dotnet/aspnetcore
# Counts issues by label in 10.0-rc1, 10.0-preview7 milestones, or no milestone
# GitHub Personal Access Token (set this to your token)
$GitHubToken = $env:GITHUB_TOKEN
if (-not $GitHubToken) {
$GitHubToken = Read-Host "Enter your GitHub Personal Access Token" -AsSecureString
$GitHubToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($GitHubToken))
}
$repo = "dotnet/aspnetcore"
$baseUrl = "https://api.github.com/repos/$repo"
# Headers with authentication
$headers = @{
"Accept" = "application/vnd.github.v3+json"
"User-Agent" = "PowerShell-GitHubScript"
"Authorization" = "token $GitHubToken"
}
# Check current rate limit status
Write-Host "Checking GitHub API rate limit..." -ForegroundColor Yellow
try {
$rateLimitResponse = Invoke-WebRequest -Uri "https://api.github.com/rate_limit" -Headers $headers
$rateLimitData = $rateLimitResponse.Content | ConvertFrom-Json
$remaining = $rateLimitData.rate.remaining
$limit = $rateLimitData.rate.limit
$resetTime = [DateTimeOffset]::FromUnixTimeSeconds($rateLimitData.rate.reset).ToLocalTime()
Write-Host "Rate limit: $remaining/$limit requests remaining" -ForegroundColor Cyan
Write-Host "Rate limit resets at: $resetTime" -ForegroundColor Cyan
if ($remaining -lt 50) {
Write-Warning "Low rate limit remaining. Consider waiting until reset time."
$continue = Read-Host "Continue anyway? (y/n)"
if ($continue -notmatch "^y|yes$") {
Write-Host "Exiting. Please run again after rate limit reset." -ForegroundColor Red
exit
}
}
} catch {
Write-Warning "Could not check rate limit. Proceeding with caution..."
}
# Function to make API requests with cursor-based pagination for large datasets
function Get-GitHubData {
param(
[string]$Url,
[int]$MaxRequests = 100
)
$allData = @()
$requestCount = 0
$currentUrl = "$Url$(if ($Url -match '\?') { '&' } else { '?' })per_page=100"
do {
$requestCount++
try {
Write-Host "Fetching request $requestCount..." -ForegroundColor Yellow
# Use Invoke-WebRequest to get headers for pagination
$response = Invoke-WebRequest -Uri $currentUrl -Headers $headers
$data = $response.Content | ConvertFrom-Json
$allData += $data
# Check rate limit headers
$rateLimitRemaining = $response.Headers["X-RateLimit-Remaining"]
$rateLimitReset = $response.Headers["X-RateLimit-Reset"]
if ($rateLimitRemaining) {
# Handle case where headers might be arrays
$remainingValue = if ($rateLimitRemaining -is [array]) { $rateLimitRemaining[0] } else { $rateLimitRemaining }
$resetValue = if ($rateLimitReset -is [array]) { $rateLimitReset[0] } else { $rateLimitReset }
Write-Host "Rate limit remaining: $remainingValue" -ForegroundColor Cyan
# If we're getting low on requests, warn user
if ([int]$remainingValue -lt 10) {
$resetTime = [DateTimeOffset]::FromUnixTimeSeconds([int]$resetValue).ToLocalTime()
Write-Warning "Rate limit low! Resets at: $resetTime"
}
}
# Check for Link header to get next page URL
$linkHeader = $response.Headers["Link"]
$nextUrl = $null
if ($linkHeader) {
# Parse Link header to find 'next' URL
$links = $linkHeader -split ','
foreach ($link in $links) {
if ($link -match '<([^>]+)>;\s*rel="next"') {
$nextUrl = $matches[1]
break
}
}
}
# If no next URL or we got less than 100 items, we're done
if (-not $nextUrl -or $data.Count -lt 100) {
break
}
$currentUrl = $nextUrl
# Add small delay to be respectful to API
Start-Sleep -Milliseconds 100
}
catch {
$errorMessage = $_.Exception.Message
$statusCode = $_.Exception.Response.StatusCode
if ($statusCode -eq 403) {
Write-Warning "Rate limit exceeded (403 Forbidden). You may need to wait before making more requests."
Write-Host "Collected $($allData.Count) items before hitting rate limit." -ForegroundColor Yellow
# Try to get rate limit reset time from headers if available
try {
$errorResponse = $_.Exception.Response
$rateLimitReset = $errorResponse.Headers["X-RateLimit-Reset"]
if ($rateLimitReset) {
$resetValue = if ($rateLimitReset -is [array]) { $rateLimitReset[0] } else { $rateLimitReset }
$resetTime = [DateTimeOffset]::FromUnixTimeSeconds([int]$resetValue).ToLocalTime()
Write-Host "Rate limit resets at: $resetTime" -ForegroundColor Cyan
}
} catch {
Write-Host "Rate limit typically resets every hour for unauthenticated requests." -ForegroundColor Cyan
}
break
} elseif ($statusCode -eq 422) {
Write-Warning "Hit GitHub pagination limit. Collected $($allData.Count) items so far."
break
} else {
Write-Error "Error fetching data from $currentUrl : $errorMessage (Status: $statusCode)"
break
}
}
} while ($requestCount -lt $MaxRequests)
return $allData
}
Write-Host "Fetching open issues from $repo in '10.0-rc1', '10.0-preview7' milestones, or no milestone..." -ForegroundColor Green
# First, get the milestone IDs for "10.0-rc1" and "10.0-preview7"
Write-Host "Looking up '10.0-rc1' and '10.0-preview7' milestones..." -ForegroundColor Yellow
$milestonesUrl = "$baseUrl/milestones?state=open"
$milestones = Get-GitHubData -Url $milestonesUrl
$targetMilestones = $milestones | Where-Object { $_.title -eq "10.0-rc1" -or $_.title -eq "10.0-preview7" }
if (-not $targetMilestones -or $targetMilestones.Count -eq 0) {
Write-Warning "Could not find '10.0-rc1' or '10.0-preview7' milestones. Will only fetch issues with no milestone."
Write-Host "Available milestones:" -ForegroundColor Yellow
$milestones | ForEach-Object { Write-Host " - $($_.title)" }
} else {
Write-Host "Found milestone(s):" -ForegroundColor Cyan
$targetMilestones | ForEach-Object { Write-Host " - $($_.title) (ID: $($_.number))" -ForegroundColor Cyan }
}
# Get open issues from target milestones and no milestone
$allIssues = @()
# First, get issues with no milestone
Write-Host "Fetching issues with no milestone..." -ForegroundColor Yellow
$noMilestoneUrl = "$baseUrl/issues?state=open&milestone=none"
$noMilestoneIssues = Get-GitHubData -Url $noMilestoneUrl
$allIssues += $noMilestoneIssues
Write-Host "Found $($noMilestoneIssues.Count) issues with no milestone" -ForegroundColor Cyan
# Then get issues from target milestones if they exist
if ($targetMilestones -and $targetMilestones.Count -gt 0) {
foreach ($milestone in $targetMilestones) {
Write-Host "Fetching issues from milestone: $($milestone.title)..." -ForegroundColor Yellow
$openIssuesUrl = "$baseUrl/issues?state=open&milestone=$($milestone.number)"
$milestoneIssues = Get-GitHubData -Url $openIssuesUrl
$allIssues += $milestoneIssues
Write-Host "Found $($milestoneIssues.Count) issues in '$($milestone.title)'" -ForegroundColor Cyan
}
}
Write-Host "Found $($allIssues.Count) total open issues and pull requests across selected milestones and no milestone" -ForegroundColor Cyan
# Filter out pull requests to match GitHub's issue-only search
$issuesOnly = $allIssues | Where-Object { -not $_.pull_request }
Write-Host "Filtered to $($issuesOnly.Count) actual issues (excluding pull requests)" -ForegroundColor Cyan
# Filter to only issues that have at least one "area-" label
$areaLabeledIssues = $issuesOnly | Where-Object {
$_.labels | Where-Object { $_.name -like "area-*" }
}
Write-Host "Found $($areaLabeledIssues.Count) issues with area- labels" -ForegroundColor Cyan
# Count labels (only those prefixed with "area-")
$labelCounts = @{}
foreach ($issue in $areaLabeledIssues) {
foreach ($label in $issue.labels) {
$labelName = $label.name
# Only count labels that start with "area-"
if ($labelName -like "area-*") {
if ($labelCounts.ContainsKey($labelName)) {
$labelCounts[$labelName]++
} else {
$labelCounts[$labelName] = 1
}
}
}
}
Write-Host "`nArea label counts (sorted by frequency):" -ForegroundColor Green
Write-Host "=" * 50
# Sort by count descending
$sortedLabels = $labelCounts.GetEnumerator() | Sort-Object Value -Descending
foreach ($label in $sortedLabels) {
Write-Host ("{0,-40} : {1}" -f $label.Key, $label.Value)
}
Write-Host "`nSummary:" -ForegroundColor Yellow
Write-Host "Total unique area labels: $($labelCounts.Count)"
Write-Host "Total open issues with area- labels analyzed: $($areaLabeledIssues.Count)"
Write-Host "Total area label applications: $($labelCounts.Values | Measure-Object -Sum | Select-Object -ExpandProperty Sum)"
# Export to CSV if desired
$csvPath = "aspnetcore-area-labeled-issues-counts.csv"
$sortedLabels | Select-Object @{Name="Label";Expression={$_.Key}}, @{Name="Count";Expression={$_.Value}} |
Export-Csv -Path $csvPath -NoTypeInformation
Write-Host "`nResults exported to: $csvPath" -ForegroundColor Green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment