Skip to content

Instantly share code, notes, and snippets.

@jdhitsolutions
Created March 7, 2024 17:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdhitsolutions/30cd246fd99dd442e4a75ddf0261c2ce to your computer and use it in GitHub Desktop.
Save jdhitsolutions/30cd246fd99dd442e4a75ddf0261c2ce to your computer and use it in GitHub Desktop.
My solution for the PowerShell Podcast scripting challenge to write PowerShell code to get GitHub issue statistics.
#requires -version 7.4
<#
This function requires the gh.exe command line tool.
You many encounter API rate restrictions under heavy use.
#>
#load the custom formatting file
Update-FormatData $PSScriptRoot\ghLabelStatus.format.ps1xml
Function Get-ghIssueLabelCount {
[cmdletbinding()]
[OutputType('ghLabelStatus')]
[alias('ghlc')]
Param(
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
HelpMessage = 'Specify the Github owner and repo in the format: owner/repo. You might need to match casing with GitHub.'
)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('\w+/\w+$', ErrorMessage = 'Please use the format OWNER/Repository. e.g. jdhitsolutions/psreleasetools')]
[string]$Repository,
[Parameter(HelpMessage = 'Specify the first X number of issue labels sorted by count in descending order.')]
[ValidateScript({ $_ -gt 0 }, ErrorMessage = 'Enter a value greater than 0.')]
[int]$First = 25,
[Parameter(HelpMessage = 'Specify the number of issues to analyze')]
[ValidateScript({ $_ -gt 0 }, ErrorMessage = 'Enter a value greater than 0.')]
[int]$Limit = 1000
)
Begin {
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)"
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Running under PowerShell version $($PSVersionTable.PSVersion)"
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Using host: $($Host.Name)"
$ReportDate = Get-Date
} #begin
Process {
Try {
$gh = Get-Command -Name gh.exe -ErrorAction Stop
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Using $( gh.exe --version | Select-Object -First 1)"
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Processing $Limit issues from $Repository"
<#
gitHub issue Available JSON fields:
assignees
author
body
closed
closedAt
comments
createdAt
id
labels
milestone
number
projectCards
projectItems
reactionGroups
state
title
updatedAt
url
#>
$ghData = gh.exe issue list --repo $Repository --limit $Limit --json 'id,title,labels' | ConvertFrom-Json
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Found $($ghData.count) items"
} #Try
Catch {
Write-Warning 'This command requires the gh.exe command-line utility.'
} #Catch
If ($ghData.count -gt 0) {
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Getting top $First issue labels"
$data = $ghData.labels |
Group-Object -Property Name -NoElement |
Sort-Object Count, Name -Descending |
Select-Object -First $First
foreach ($Item in $data) {
#create a custom object
if ($item.Name -match '\s') {
$escName = '%22{0}%22' -f ($item.Name -replace '\s', '+')
$uri = "https://github.com/$Repository/issues?q=is%3Aissue+is%3Aopen+label%3A$escName"
}
else {
$uri = "https://github.com/$Repository/issues?q=is%3Aissue+is%3Aopen+label%3A$($Item.Name)"
}
[PSCustomObject]@{
PStypeName = 'ghLabelStatus'
Count = $Item.Count
PctTotal = ($item.Count / $ghData.Count) * 100
Label = $Item.Name
LabelUri = $uri
Repository = $Repository
IssueCount = $ghData.Count
ReportDate = $ReportDate
}
}
} #if data found
else {
Write-Warning "No open issues found in $Repository"
}
} #process
End {
Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)"
} #end
} #close Get-ghIssueLabelCount
#requires -version 7.4
#a control script to generate a markdown report.
#ghLabelReport.ps1
Param(
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
HelpMessage = 'Specify the Github owner and repo in the format: owner/repo. You might need to match casing with GitHub.'
)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('\w+/\w+$', ErrorMessage = 'Please use the format OWNER/Repository. e.g. jdhitsolutions/psreleasetools')]
[string]$Repository,
[Parameter(HelpMessage = 'Specify the first X number of issue labels sorted by count in descending order.')]
[ValidateScript({ $_ -gt 0 }, ErrorMessage = 'Enter a value greater than 0.')]
[int]$First = 25,
[Parameter(HelpMessage = 'Specify the number of issues to analyze')]
[ValidateScript({ $_ -gt 0 }, ErrorMessage = 'Enter a value greater than 0.')]
[int]$Limit = 1000
)
#dot source the required function
. $PSScriptRoot\Get-ghIssueStats.ps1
$data = Get-ghIssueLabelCount @PSBoundParameters
if ($data) {
$repoIssues = "https://github.com/$($data[0].Repository)/issues"
$md = [System.Collections.Generic.List[string]]::New()
$md.Add("# Label Report for [$($data[0].Repository)]($repoIssues)`n")
$md.Add("This is the breakdown of labels based on __$($data[0].IssueCount)__ open issues. Total percentages might be more than 100% as issues can have multiple labels.`n")
$md.Add("Count| Percent of Total | Label|")
$md.Add("|-----|-----|-----|")
foreach ($item in $data) {
$md.Add("| $($item.count) | $([math]::Round($item.PctTotal,2)) | [$($item.Label)]($($item.LabelUri))|")
}
$md.Add("`n_$($data[0].ReportDate)_")
}
#write the markdown to the pipeline
$md
<!--
Format type data generated 12/04/2023 12:58:47 by PROSPERO\Jeff
This file was created using the New-PSFormatXML command that is part
of the PSScriptTools module.
https://github.com/jdhitsolutions/PSScriptTools
-->
<Configuration>
<ViewDefinitions>
<View>
<!--Created 12/04/2023 12:58:47 by PROSPERO\Jeff-->
<Name>default</Name>
<ViewSelectedBy>
<TypeName>ghLabelStatus</TypeName>
</ViewSelectedBy>
<GroupBy>
<ScriptBlock>"{0} [{1}]" -f $_.Repository,$_.ReportDate</ScriptBlock>
<Label>Repository</Label>
</GroupBy>
<TableControl>
<!--Delete the AutoSize node if you want to use the defined widths.-->
<AutoSize />
<TableHeaders>
<TableColumnHeader>
<Label>Count</Label>
<Width>8</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>PctTotal</Label>
<Width>8</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Label</Label>
<Width>15</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Count</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>"{0:p2}" -f ($_.Count/$_.IssueCount)</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Label</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<View>
<!--Created 12/04/2023 15:18:46 by PROSPERO\Jeff-->
<Name>uri</Name>
<ViewSelectedBy>
<TypeName>ghLabelStatus</TypeName>
</ViewSelectedBy>
<GroupBy>
<ScriptBlock>
$link = $PSStyle.FormatHyperlink($_.Repository,"https://github.com/$($_.Repository)/issues")
"$($PSStyle.Foreground.Yellow +$PSStyle.Italic)$link$($PSStyle.Reset) [$($_.ReportDate)]"
</ScriptBlock>
<Label>Repository</Label>
</GroupBy>
<TableControl>
<!--Delete the AutoSize node if you want to use the defined widths.-->
<AutoSize />
<TableHeaders>
<TableColumnHeader>
<Label>Count</Label>
<Width>8</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>PctTotal</Label>
<Width>8</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Label</Label>
<Width>14</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Count</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>"{0:p2}" -f ($_.Count/$_.IssueCount)</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>
$link = $PSStyle.FormatHyperlink($_.Label,$_.LabelUri)
"$($PSStyle.Foreground.Yellow +$PSStyle.Italic)$link$($PSStyle.Reset)"
</ScriptBlock>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>