Wednesday, May 8, 2024
HomePowershellGitHub Scripting Problem Resolution • The Lonely Administrator

GitHub Scripting Problem Resolution • The Lonely Administrator


Earlier this yr I appeared on the PowerShell Podcast. I ended the interview with a scripting problem.

The Core Problem

Utilizing no matter instruments and methods you need, write a PowerShell operate that
will question the Points part of a GitHub repository and create output exhibiting
the variety of open points by label and the proportion of all open points.
Keep in mind that a number of labels could also be used with a problem. For instance, if there are 54 open points and the bug label is used 23 instances, your output would present a depend of 23 and a complete proportion of 42.59 for the bug label. The operate ought to work for any GitHub repository, however check it with the PowerShell repository. Naturally, the operate ought to observe group accepted finest practices, have parameter validation, and correct error dealing with.

Bonus Problem

I additionally included extra-credit for extra skilled scripters.

Upon getting the operate, add customized formatting to show the leads to a desk,together with the repository identify or path. Create an alternate view that can even show the repository and the label URI that GitHub makes use of to create a filtered web page view. Lastly, create a management script utilizing the operate to create a markdown report for the PowerShell repository exhibiting the highest 25 labels. The markdown report ought to have clickable hyperlinks.

My Resolution

I posted the problem as a gist and requested individuals to submit their options by posting a remark with a hyperlink to their work. I encourage you to see how individuals approached the problem. After all, you might be greater than welcome to develop your personal resolution and share a hyperlink to your work.

However here is how I approached the problem. My resolution will not be essentially one of the best ways or solely strategy to meet the necessities. However it’s all the time instructional to match how individuals resolve the identical drawback. On this case, many individuals would possibly assume the most effective strategy is to make use of the GitHub API and Invoke-RestMethod. I selected a unique strategy.

I’m an enormous consumer of the GitHub command-line consumer, gh.exe. You possibly can simply set up it with winget.

winget set up --id github.cli --source winget

You will want to restart your PowerShell session earlier than you should use the command. You may additionally must authenticate.

gh auth login

Observe the prompts. As soon as you might be authenticated, you should use the gh command to question GitHub with out having to take care of API tokens, headers, and so forth. What is particularly compelling is you can have gh.exe output JSON. This implies I can use it in PowerShell.

gh.exe difficulty checklist --repo jdhitsolutions/psscripttools --limit 50 --json 'id,title,labels' | ConvertFrom-Json

I wrote a PowerShell 7 operate wrapped round this command.

Operate 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]$Restrict = 1000
    )

    Start {
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Beginning $($MyInvocation.MyCommand)"
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Working beneath PowerShell model $($PSVersionTable.PSVersion)"
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Utilizing host: $($Host.Title)"
        $ReportDate = Get-Date
    } #start

    Course of {
        Strive  Choose-Object -First 1)"
            Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Processing $Restrict points from $Repository"
            $ghData = gh.exe difficulty checklist --repo $Repository --limit $Restrict --json 'id,title,labels'  #Strive
        Catch {
            Write-Warning 'This command requires the gh.exe command-line utility.'
        } #Catch

        If ($ghData.depend -gt 0) {
            Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Getting prime $First difficulty labels"
            $knowledge = $ghData.labels |
            Group-Object -Property Title -NoElement |
            Type-Object Depend, Title -Descending |
            Choose-Object -First $First

            foreach ($Merchandise in $knowledge) {
                #create a customized object
                if ($merchandise.Title -match 's') {
                    $escName = '%22{0}%22' -f ($merchandise.Title -replace 's', '+')
                    $uri = "https://github.com/$Repository/points?q=ispercent3Aissue+ispercent3Aopen+labelpercent3A$escName"
                }
                else {
                    $uri = "https://github.com/$Repository/points?q=ispercent3Aissue+ispercent3Aopen+labelpercent3A$($Merchandise.Title)"
                }
                [PSCustomObject]@{
                    PStypeName = 'ghLabelStatus'
                    Depend      = $Merchandise.Depend
                    PctTotal   = ($merchandise.Depend / $ghData.Depend) * 100
                    Label      = $Merchandise.Title
                    LabelUri   = $uri
                    Repository = $Repository
                    IssueCount = $ghData.Depend
                    ReportDate = $ReportDate
                }
            }
        } #if knowledge discovered
        else {
            Write-Warning "No open points present in $Repository"
        }
    } #course of

    Finish {
        Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Ending $($MyInvocation.MyCommand)"
    } #finish
}

The operate teams the problems by label and writes a customized object to the pipeline.

PS C:> Get-ghIssueLabelCount -Repository jdhitsolutions/psscripttools

Depend      : 4
PctTotal   : 80
Label      : bug
LabelUri   : https://github.com/jdhitsolutions/psscripttools/points?q=ispercent3Aissue+ispercent3Aopen+labelpercent3Abug
Repository : jdhitsolutions/psscripttools
IssueCount : 5
ReportDate : 3/7/2024 11:29:38 AM
...

This could meet the essential necessities.

Formatting

After I created my customized object, I additionally outlined a singular sort identify.

 [PSCustomObject]@{
    PStypeName = 'ghLabelStatus'
    Depend      = $Merchandise.Depend
    PctTotal   = ($merchandise.Depend / $ghData.Depend) * 100
    Label      = $Merchandise.Title
    LabelUri   = $uri
    Repository = $Repository
    IssueCount = $ghData.Depend
    ReportDate = $ReportDate
}

This enables me to create a customized format file utilizing New-PSFormatXML

<!--
Format sort knowledge generated 12/04/2023 12:58:47 by PROSPEROJeff

This file was created utilizing the New-PSFormatXML command that's half
of the PSScriptTools module.

https://github.com/jdhitsolutions/PSScriptTools
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 12/04/2023 12:58:47 by PROSPEROJeff-->
      <Title>default</Title>
      <ViewSelectedBy>
        <TypeName>ghLabelStatus</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <ScriptBlock>"{0} [{1}]" -f $_.Repository,$_.ReportDate</ScriptBlock>
        <Label>Repository</Label>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you wish to use the outlined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>Depend</Label>
            <Width>8</Width>
            <Alignment>proper</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>PctTotal</Label>
            <Width>8</Width>
            <Alignment>proper</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Label</Label>
            <Width>15</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Depend</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>"{0:p2}" -f ($_.Depend/$_.IssueCount)</ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Label</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <!--Created 12/04/2023 15:18:46 by PROSPEROJeff-->
      <Title>uri</Title>
      <ViewSelectedBy>
        <TypeName>ghLabelStatus</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <ScriptBlock>
        $hyperlink = $PSStyle.FormatHyperlink($_.Repository,"https://github.com/$($_.Repository)/points")
        "$($PSStyle.Foreground.Yellow +$PSStyle.Italic)$hyperlink$($PSStyle.Reset) [$($_.ReportDate)]"
        </ScriptBlock>
        <Label>Repository</Label>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you wish to use the outlined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>Depend</Label>
            <Width>8</Width>
            <Alignment>proper</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>PctTotal</Label>
            <Width>8</Width>
            <Alignment>proper</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Label</Label>
            <Width>14</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Depend</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>"{0:p2}" -f ($_.Depend/$_.IssueCount)</ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                  $hyperlink = $PSStyle.FormatHyperlink($_.Label,$_.LabelUri)
                  "$($PSStyle.Foreground.Yellow +$PSStyle.Italic)$hyperlink$($PSStyle.Reset)"
                </ScriptBlock>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

The default view shows the problems in a desk with the repository identify and date.

PS C:> Get-ghIssueLabelCount -Repository jdhitsolutions/psscripttools

   Repository: jdhitsolutions/psscripttools [3/7/2024 11:36:35 AM]

Depend PctTotal Label
----- -------- -----
    4   80.00% bug
    2   40.00% triage
    2   40.00% enhancement
    1   20.00% pending suggestions

The alternate view makes use of PSStyle to insert hyperlinks.

<Title>uri</Title>
    <ViewSelectedBy>
        <TypeName>ghLabelStatus</TypeName>
    </ViewSelectedBy>
    <GroupBy>
        <ScriptBlock>
            $hyperlink = $PSStyle.FormatHyperlink($_.Repository,"https://github.com/$($_.Repository)/points")
            "$($PSStyle.Foreground.Yellow +$PSStyle.Italic)$hyperlink$($PSStyle.Reset) [$($_.ReportDate)]"
        </ScriptBlock>
    <Label>Repository</Label>
    </GroupBy>

Utilizing Home windows Terminal, I now have clickable hyperlinks.

Management Script

The final a part of the problem was to put in writing a management script to create a markdown report.

#requires -version 7.4

#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]$Restrict = 1000

)

#dot supply the required operate
. $PSScriptRootGet-ghIssueStats.ps1

$knowledge = Get-ghIssueLabelCount @PSBoundParameters

if ($knowledge) -----

#write the markdown to the pipeline
$md

The script writes markdown to the pipeline. If I desire a file, I can use Out-File

C:scriptsghLabelReport.ps1 -Repository jdhitsolutions/psscripttools | Out-File c:temppsscripttools-issues.md

Here is the report for the PowerShell repository.

C:scriptsghLabelReport.ps1 -Repository powershell/powershell | Out-File c:temppowershell-issues.md

PowerShell Github Issue report

Abstract

Typically it helps to assume outdoors the field and even backwards. I knew what knowledge I wanted to assemble the objects I needed. Then it was a matter of discovering the most effective approach to get me the info. In my case, I opted for a command-line instrument. This provides a dependency for anybody else who desires to attempt my code. But it surely made it very simple for me to put in writing the code.

I’ll submit my recordsdata as a GitHub gist so you do not have to attempt to copy from right here. In case you have questions or feedback on the code, please submit them as feedback on the gist.


Behind the PowerShell Pipeline


RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments