Skip to content

Instantly share code, notes, and snippets.

@pauljnav
Last active April 7, 2024 13:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pauljnav/c767bd811d3334f7218f670b3cf46299 to your computer and use it in GitHub Desktop.
Save pauljnav/c767bd811d3334f7218f670b3cf46299 to your computer and use it in GitHub Desktop.
Queries the issues section of a GitHub repository using GitHub REST API and tabulates output showing the number of open issues by label with percentage detail. Pull Requests are issues, but issues are not Pull Requests, this function provides filtering for Pull Requests. The command collects a maximum 1000 issues, max 10 Pages having 100 issues …
#requires -version 5.1
<#
.Synopsis
Queries the issues section of a GitHub repository and tabulates open issue statistics.
.DESCRIPTION
Queries the issues section of a GitHub repository using GitHub REST API and tabulates output showing the number of open issues by label with percentage detail.
Pull Requests are issues, but issues wre not Pull Requests, this function provides filtering for Pull Requests.
The customer collects a maximum 1000 issues, max 10 Pages having 100 issues to comply with GitHub rate limits.
.EXAMPLE
This example tabulates open issues from PowerShell\PowerShell
$issuTable = Get-GitHubOpenIssueCount -Owner PowerShell -Repo PowerShell
$issuTable
.EXAMPLE
This example tabulates open issues from PowerShell\PowerShell (omitting pull requests)
$issuTable = Get-GitHubOpenIssueCount -Owner PowerShell -Repo PowerShell -OmitPullRequests
$issuTable
.EXAMPLE
This example tabulates open issues from the PowerShell\DscResources repo.
$issuTable = Get-GitHubOpenIssueCount -Owner PowerShell -Repo DscResources
$issuTable
.NOTES
This is code is written in response to the PowerShell Podcast scripting challenge from Jeff Hicks.
Jeff appearing on the PowerShell Podcast set a challenge to write PowerShell code to get GitHub issue statistics.
https://www.podbean.com/pu/pbblog-rtiuk-d20bd9
https://powershellpodcast.podbean.com/e/crafting-a-fulfilling-career-wisdom-from-industry-leaders-jeff-hicks-and-mike-f-robbins/
https://jdhitsolutions.com/blog/powershell/9343/github-scripting-challenge-solution/
#>
function Get-GitHubOpenIssueCount
{
[CmdletBinding()]
Param
(
# Repositary owner
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[String] $Owner,
# Repositary (repo) name
[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[String] $Repo,
# Omit Pull Requests from issue list (be like GuitHub web default list)
[Parameter(Mandatory=$false)]
[Switch] $OmitPullRequests,
# Maximum page count, Min 1, Max 10, pages contain up to 100 issues.
[Parameter(Mandatory=$false)]
[ValidateRange(1,10)]
[int] $MaxPages=10
)
Begin
{
# Define the GitHub REST API endpoint with pagelinmit 'per_page=100' to facilitate paginated responses.
$endpoint = "https://api.github.com/repos/$Owner/$Repo/issues?state=open&per_page=100"
$headers = @{Accept = 'application/vnd.github+json'}
}
Process
{
# Starting with a headers request, confirm if dataIsPaginated.
try {
$wr = Invoke-WebRequest -Uri $endpoint -Headers $headers -Method Head
$dataIsPaginated = $wr.Headers.Link -match 'last'
}
catch {
Write-Warning "Error reading headers from the endpoint."
throw $Error[0]
}
finally {
Write-Verbose "dataIsPaginated: [$dataIsPaginated]"
Write-Verbose "endpoint: [$endpoint]"
}
# Get page 1 of 1 issues when not paginated.
if (-not $dataIsPaginated)
{
try {
$wr = Invoke-WebRequest -Uri $endpoint -Headers $headers -Method Get
$issues = $wr.Content | ConvertFrom-Json
}
catch {
Write-Warning "Error reading headers from the endpoint."
throw $Error[0]
}
finally{
Write-Verbose "endpoint: [$endpoint]"
Write-Verbose "issueCount: [$($issues.Count)]"
Write-Verbose "dataIsPaginated: [$dataIsPaginated]"
}
}
if ($dataIsPaginated)
{
# Get the first page.
try {
$wr = Invoke-WebRequest -Uri $endpoint -Headers $headers -Method Get
$issues = $wr.Content | ConvertFrom-Json
# Capture rateLimitRemaining and RateLimitResetDateTime from the header response.
$rateLimitRemaining = [int]$wr.Headers.'X-RateLimit-Remaining'
$epochSecondsUTC = [int]$wr.Headers.'X-RateLimit-Reset'
}
catch {
$err = $Error[0]
Write-Debug "Encountered an error reading headers from the endpoint."
throw $err
}
finally {
Write-Verbose "dataIsPaginated: [$dataIsPaginated]"
Write-Verbose "endpoint: [$endpoint]"
}
# Capture pageCount by capturing the last page number from Headers.Link.
Remove-Variable Matches -ErrorAction SilentlyContinue
$wr.Headers.Link -match 'page=(\d+)>; rel="last"' | Out-Null
$pageCount = $Matches[1]
# The number of pages to read is the lesser of the pageCount and MaxPages values.
$pageCount = [Math]::Min($pageCount, $MaxPages)
# The number of pages to read is the lesser of the pageCount and rateLimitRemaining values.
$pageCount = [Math]::Min($pageCount, $rateLimitRemaining)
# decrement pageCount by 1 as page 1 is retrieved
$pageCount--
$np_pattern = 'rel="next"' # Define the next page pattern
# Capture nextPageEndpoint from page 1 response.
$nextPageEndpoint = [string]($wr.Headers.Link -split ',' -match $np_pattern -split '<|>' -match 'issues')
# Iterate the remaining pages, adding issue results to the current issue results set.
# Iterate do/until the nextPageEndpoint IsNullOrEmpty
do
{
try {
# $lastEndpoint ='https://api.github.com/repositories/49609581/issues?state=open&per_page=100&page=9'
# $nextPageEndpoint = $lastEndpoint
$wr = Invoke-WebRequest -Uri $nextPageEndpoint -Headers $headers -Method Get
# Update nextPageEndpoint
$nextPageEndpoint = [string]($wr.Headers.Link -split ',' -match $np_pattern -split '<|>' -match 'issues')
# Add to the issues collection
$issues += $wr.Content | ConvertFrom-Json
}
catch {
$err = $Error[0]
Write-Debug "Encountered an error reading headers from the endpoint."
throw $err
}
finally{
Write-Verbose "nextPageEndpoint: [$nextPageEndpoint]"
Write-Verbose "pageCount: [$pageCount]"
}
}#do
until ( [string]::IsNullOrEmpty($nextPageEndpoint) )
}
# Filter out PullRequests if specified.
$OmittingPullRequests = $null
# pull_request have NoteProperty named 'pull_request' - is:pr
if ($OmitPullRequests.IsPresent) {
$issues = $issues | Where-Object 'pull_request' -NotMatch '\d'
$OmittingPullRequests = "(omitting pull requests) "
}
Write-Verbose "Open issues count $($OmittingPullRequests): $($issues.Count)"
# Tabulate the data into results.
# Provide a count of open issue for unique labels with a percentage value.
$UniqueLabels = $issues.labels.name | Select-Object -Unique
$results = foreach ($label in $UniqueLabels) {
$obj = New-Object -TypeName PSCustomObject
$obj | Add-Member -NotePropertyName Label -NotePropertyValue $label
$obj | Add-Member -NotePropertyName Count -NotePropertyValue ($issues.labels.name -eq $label).Count
$obj | Add-Member -NotePropertyName Percent -NotePropertyValue ($obj.Count / $issues.Count).ToString("P")
$obj # feed into results
}#foreach UniqueLabel
$results | Sort-Object -Property Count,Label -Descending
}
End
{
# Update the rateLimitRemaining and warn user if low.
$rateLimitRemaining = [int]$wr.Headers.'X-RateLimit-Remaining'
$epochSecondsUTC = [int]$wr.Headers.'X-RateLimit-Reset'
$RateLimitReset = [DateTimeOffset]::FromUnixTimeSeconds($epochSecondsUTC)
$utcTime = $RateLimitReset.TimeOfDay.ToString()
$localTime = $RateLimitReset.LocalDateTime.ToString()
if (20 -ge $rateLimitRemaining) {
Write-Warning "Rate limit remaining is low: [$rateLimitRemaining], resetting at [$localTime] [UTC: $utcTime]"
}
}
}
@pauljnav
Copy link
Author

pauljnav commented Apr 7, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment