diff --git a/GitHubAssignees.ps1 b/GitHubAssignees.ps1 new file mode 100644 index 0000000..5f5cdff --- /dev/null +++ b/GitHubAssignees.ps1 @@ -0,0 +1,386 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +function Get-GitHubAssignee +{ +<# + .DESCRIPTION + Lists the available assignees for issues in a repository. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Get-GitHubAsigneeList -OwnerName Powershell -RepositoryName PowerShellForGitHub + + Lists the available assignees for issues from the PowerShell\PowerShellForGitHub project. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParametersetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [string] $Uri, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/assignees" + 'Description' = "Getting assignee list for $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethodMultipleResult @params +} + +function Test-GitHubAssignee +{ +<# + .DESCRIPTION + Checks if a user has permission to be assigned to an issue in this repository. Returns a boolean. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Assignee + Username for the assignee + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .OUTPUTS + [bool] If the assignee can be assigned to issues in the repository. + + .EXAMPLE + Test-GitHubAssignee -OwnerName Powershell -RepositoryName PowerShellForGitHub -Assignee "LoginID123" + + Checks if a user has permission to be assigned to an issue from the PowerShell\PowerShellForGitHub project. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParametersetName='Elements')] + [OutputType([bool])] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [string] $Uri, + + [string] $Assignee, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'Asignee' = (Get-PiiSafeString -PlainText $Assignee) + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/assignees/$Assignee" + 'Method' = 'Get' + 'Description' = "Checking permission for $Assignee for $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'ExtendedResult'= $true + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + try + { + $response = Invoke-GHRestMethod @params + return $response.StatusCode -eq 204 + } + catch + { + return $false + } +} + +function New-GithubAssignee +{ +<# + .DESCRIPTION + Adds a list of assignees to a Github Issue for the given repository. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Issue + Issue number to add the assignees to. + + .PARAMETER Assignee + Usernames of users to assign this issue to. NOTE: Only users with push access can add assignees to an issue. + Assignees are silently ignored otherwise. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + New-GithubAssignee -OwnerName Powershell -RepositoryName PowerShellForGitHub -Assignee $assignee + + Lists the available assignees for issues from the PowerShell\PowerShellForGitHub project. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParametersetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory)] + [int] $Issue, + + [Parameter(Mandatory)] + [ValidateCount(1, 10)] + [string[]] $Assignee, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'AssigneeCount' = $Assignee.Count + 'Issue' = (Get-PiiSafeString -PlainText $Issue) + } + + $hashBody = @{ + 'assignees' = $Assignee + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Post' + 'Description' = "Add assignees to issue $Issue for $RepositoryName" + 'AccessToken' = $AccessToken + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} + +function Remove-GithubAssignee +{ +<# + .DESCRIPTION + Removes an assignee from a Github issue. + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Issue + Issue number to remove the assignees from. + + .PARAMETER Assignee + Usernames of assignees to remove from an issue. NOTE: Only users with push access can remove assignees from an issue. Assignees are silently ignored otherwise. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Remove-GithubAssignee -OwnerName Powershell -RepositoryName PowerShellForGitHub -Assignee $assignees + + Lists the available assignees for issues from the PowerShell\PowerShellForGitHub project. +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParametersetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory)] + [int] $Issue, + + [Parameter(Mandatory)] + [string[]] $Assignee, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'AssigneeCount' = $Assignee.Count + 'Issue' = (Get-PiiSafeString -PlainText $Issue) + } + + $hashBody = @{ + 'assignees' = $Assignee + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/issues/$Issue/assignees" + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Method' = 'Delete' + 'Description' = "Removing assignees from issue $Issue for $RepositoryName" + 'AccessToken' = $AccessToken + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 21f6dad..d682c2a 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -7,6 +7,8 @@ $script:gitHubApiOrgsUrl = "https://api.github.com/orgs" $script:defaultAcceptHeader = 'application/vnd.github.v3+json' +Set-Variable -Scope Script -Option ReadOnly -Name ValidBodyContainingRequestMethods -Value ('post', 'patch', 'put', 'delete') + function Invoke-GHRestMethod { <# @@ -165,7 +167,7 @@ function Invoke-GHRestMethod $headers['Authorization'] = "token $AccessToken" } - if ($Method -in ('post', 'patch', 'put')) + if ($Method -in $ValidBodyContainingRequestMethods) { $headers.Add("Content-Type", "application/json; charset=UTF-8") } @@ -188,7 +190,7 @@ function Invoke-GHRestMethod $params.Add("UseBasicParsing", $true) $params.Add("TimeoutSec", (Get-GitHubConfiguration -Name WebRequestTimeoutSec)) - if ($Method -in ('post', 'put', 'patch') -and (-not [String]::IsNullOrEmpty($Body))) + if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) { $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) $params.Add("Body", $bodyAsBytes) @@ -210,7 +212,7 @@ function Invoke-GHRestMethod if ($PSCmdlet.ShouldProcess($jobName, "Start-Job")) { [scriptblock]$scriptBlock = { - param($Url, $method, $Headers, $Body, $TimeoutSec, $ScriptRootPath) + param($Url, $Method, $Headers, $Body, $ValidBodyContainingRequestMethods, $TimeoutSec, $ScriptRootPath) # We need to "dot invoke" Helpers.ps1 and GitHubConfiguration.ps1 within # the context of this script block since we're running in a different @@ -227,7 +229,7 @@ function Invoke-GHRestMethod $params.Add("UseBasicParsing", $true) $params.Add("TimeoutSec", $TimeoutSec) - if ($Method -in ('post', 'put', 'patch') -and (-not [String]::IsNullOrEmpty($Body))) + if ($Method -in $ValidBodyContainingRequestMethods -and (-not [String]::IsNullOrEmpty($Body))) { $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) $params.Add("Body", $bodyAsBytes) @@ -265,7 +267,7 @@ function Invoke-GHRestMethod } } - $null = Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @($url, $Method, $headers, $Body, (Get-GitHubConfiguration -Name WebRequestTimeoutSec), $PSScriptRoot) + $null = Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @($url, $Method, $headers, $Body, $ValidBodyContainingRequestMethods, (Get-GitHubConfiguration -Name WebRequestTimeoutSec), $PSScriptRoot) if ($PSCmdlet.ShouldProcess($jobName, "Wait-JobWithAnimation")) { diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 2b2fa39..b26c3c7 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -20,6 +20,7 @@ 'Helpers.ps1', 'GitHubConfiguration.ps1', 'GitHubAnalytics.ps1', + 'GitHubAssignees.ps1', 'GitHubBranches.ps1', 'GitHubCore.ps1', 'GitHubIssues.ps1', @@ -43,6 +44,7 @@ 'Backup-GitHubConfiguration', 'Clear-GitHubAuthentication', 'ConvertFrom-Markdown', + 'Get-GitHubAssignee', 'Get-GitHubCloneTraffic', 'Get-GitHubCodeOfConduct', 'Get-GitHubConfiguration', @@ -76,10 +78,12 @@ 'Invoke-GHRestMethodMultipleResult', 'Lock-GitHubIssue', 'Move-GitHubRepositoryOwnership', + 'New-GithubAssignee', 'New-GitHubIssue', 'New-GitHubLabel', 'New-GitHubRepository', 'New-GitHubRepositoryFork', + 'Remove-GithubAssignee', 'Remove-GitHubLabel', 'Remove-GitHubRepository', 'Reset-GitHubConfiguration', @@ -89,6 +93,7 @@ 'Set-GitHubLabel', 'Set-GitHubRepositoryTopic', 'Split-GitHubUri', + 'Test-GitHubAssignee', 'Test-GitHubAuthenticationConfigured', 'Unlock-GitHubIssue', 'Update-GitHubCurrentUser', diff --git a/Tests/GitHubAssignees.tests.ps1 b/Tests/GitHubAssignees.tests.ps1 new file mode 100644 index 0000000..b88516a --- /dev/null +++ b/Tests/GitHubAssignees.tests.ps1 @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubAssignees.ps1 module +#> + +[String] $root = Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path) +. (Join-Path -Path $root -ChildPath 'Tests\Config\Settings.ps1') +Import-Module -Name $root -Force + +function Initialize-AppVeyor +{ +<# + .SYNOPSIS + Configures the tests to run with the authentication information stored in AppVeyor + (if that information exists in the environment). + + .DESCRIPTION + Configures the tests to run with the authentication information stored in AppVeyor + (if that information exists in the environment). + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .NOTES + Internal-only helper method. + + The only reason this exists is so that we can leverage CodeAnalysis.SuppressMessageAttribute, + which can only be applied to functions. + + We call this immediately after the declaration so that AppVeyor initialization can happen + (if applicable). + +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification="Needed to configure with the stored, encrypted string value in AppVeyor.")] + param() + + if ($env:AppVeyor) + { + $secureString = $env:avAccessToken | ConvertTo-SecureString -AsPlainText -Force + $cred = New-Object System.Management.Automation.PSCredential "", $secureString + Set-GitHubAuthentication -Credential $cred + + $script:ownerName = $env:avOwnerName + $script:organizationName = $env:avOrganizationName + + $message = @( + 'This run is executed in the AppVeyor environment.', + 'The GitHub Api Token won''t be decrypted in PR runs causing some tests to fail.', + '403 errors possible due to GitHub hourly limit for unauthenticated queries.', + 'Use Set-GitHubAuthentication manually. modify the values in Tests\Config\Settings.ps1,', + 'and run tests on your machine first.') + Write-Warning -Message ($message -join [Environment]::NewLine) + } +} + +Initialize-AppVeyor + +$script:accessTokenConfigured = Test-GitHubAuthenticationConfigured +if (-not $script:accessTokenConfigured) +{ + $message = @( + 'GitHub API Token not defined, some of the tests will be skipped.', + '403 errors possible due to GitHub hourly limit for unauthenticated queries.') + Write-Warning -Message ($message -join [Environment]::NewLine) +} + +# Backup the user's configuration before we begin, and ensure we're at a pure state before running +# the tests. We'll restore it at the end. +$configFile = New-TemporaryFile +try +{ + Backup-GitHubConfiguration -Path $configFile + Reset-GitHubConfiguration + Set-GitHubConfiguration -DisableTelemetry # We don't want UT's to impact telemetry + + $repo = New-GitHubRepository -RepositoryName ([Guid]::NewGuid().Guid) -AutoInit + $issue = New-GitHubIssue -Uri $repo.svn_url -Title "Test issue" + + Describe 'Getting a valid assignee' { + + Context 'For getting a valid assignee' { + $assigneeList = @(Get-GitHubAssignee -Uri $repo.svn_url) + + It 'Should have returned the one assignee' { + $assigneeList.Count | Should be 1 + } + + $assigneeUserName = $assigneeList[0].login + + It 'Should have returned an assignee with a login'{ + $assigneeUserName | Should not be $null + } + + $hasPermission = Test-GitHubAssignee -Uri $repo.svn_url -Assignee $assigneeUserName + + It 'Should have returned an assignee with permission to be assigned to an issue'{ + $hasPermission | Should be $true + } + + } + } + + Describe 'Adding and removing an assignee to an issue'{ + + Context 'For adding an assignee to an issue'{ + $assigneeList = @(Get-GitHubAssignee -Uri $repo.svn_url) + $assigneeUserName = $assigneeList[0].login + $assignees = @($assigneeUserName) + New-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees + $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + + It 'Should have assigned the user to the issue' { + $issue.assignee.login | Should be $assigneeUserName + } + + Remove-GithubAssignee -Uri $repo.svn_url -Issue $issue.number -Assignee $assignees + $issue = Get-GitHubIssue -Uri $repo.svn_url -Issue $issue.number + + It 'Should have removed the user from issue' { + $issue.assignees.Count | Should be 0 + } + } + } + + Remove-GitHubRepository -Uri $repo.svn_url +} +finally +{ + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $configFile +} diff --git a/USAGE.md b/USAGE.md index da6af70..9e4d7e9 100644 --- a/USAGE.md +++ b/USAGE.md @@ -30,6 +30,11 @@ * [Get the popular content for a repository](#get-the-popular-content-for-a-repository) * [Get the number of views for a repository](#get-the-number-of-views-for-a-repository) * [Get the number of clones for a repository](#get-the-number-of-clones-for-a-repository) + * [Assignees](#assignees) + * [Get assignees](#get-assignees) + * [Check assignee permission](#check-assignee-permission) + * [Add assignee to an issue](#add-assignee-to-an-issue) + * [Remove assignee from an issue](#remove-assignee-from-an-issue) ---------- @@ -342,3 +347,27 @@ Get-GitHubViewTraffic -OwnerName PowerShell -RepositoryName PowerShellForGitHub ```powershell Get-GitHubCloneTraffic -OwnerName PowerShell -RepositoryName PowerShellForGitHub -Per 'day' ``` + +---------- + +### Assignees + +#### Get assignees +```powershell +Get-GitHubAsignee -OwnerName Powershell -RepositoryName PowerShellForGitHub +``` + +#### Check assignee permission +```powershell +$HasPermission = Test-GitHubAssignee -OwnerName Powershell -RepositoryName PowerShellForGitHub -Assignee "LoginID123" +``` + +#### Add assignee to an issue +```powershell +New-GithubAssignee -OwnerName Powershell -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 +``` + +#### Remove assignee from an issue +```powershell +Remove-GithubAssignee -OwnerName Powershell -RepositoryName PowerShellForGitHub -Assignees $assignees -Issue 1 +```