Last active
July 22, 2025 19:22
-
-
Save danmoseley/5b0ff875be8fbb5814becfabd102b155 to your computer and use it in GitHub Desktop.
triage query (no milestone+rc1+p7)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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