diff --git a/src/classes/public/Teams/GitHubTeam.ps1 b/src/classes/public/Teams/GitHubTeam.ps1 new file mode 100644 index 000000000..de046588c --- /dev/null +++ b/src/classes/public/Teams/GitHubTeam.ps1 @@ -0,0 +1,61 @@ +class GitHubTeam { + # The name of the team. + [string] $Name + + # The slug of the team. + [string] $Slug + + # The organization the team belongs to. + [string] $Organization + + # The combined slug of the team. + [string] $CombinedSlug + + # The ID of the team. + [string] $NodeID + + # The database ID of the team. + [string] $DatabaseID + + # The description of the team. + [string] $Description + + # The notification setting the team has chosen. + # $true = notifications_enabled - team members receive notifications when the team is @mentioned. + # $false = notifications_disabled - no one receives notifications. + [bool] $Notifications = $true + + # The privacy setting of the team. + # $true = closed - visible to all members of this organization. + # $false = secret - only visible to organization owners and members of this team. + [bool] $Visible = $true + + # The parent team of the team. + [string] $ParentTeam + + # The child teams of the team. + [string[]] $ChildTeams + + # The date and time the team was created. + [datetime] $CreatedAt + + # The date and time the team was last updated. + [datetime] $UpdatedAt + + # Simple parameterless constructor + GitHubTeam() {} + + # Creates a object from a hashtable of key-vaule pairs. + GitHubTeam([hashtable]$Properties) { + foreach ($Property in $Properties.Keys) { + $this.$Property = $Properties.$Property + } + } + + # Creates a object from a PSCustomObject. + GitHubTeam([PSCustomObject]$Object) { + $Object.PSObject.Properties | ForEach-Object { + $this.($_.Name) = $_.Value + } + } +} diff --git a/src/functions/private/Teams/Get-GitHubRESTTeam.ps1 b/src/functions/private/Teams/Get-GitHubRESTTeam.ps1 new file mode 100644 index 000000000..ed1859723 --- /dev/null +++ b/src/functions/private/Teams/Get-GitHubRESTTeam.ps1 @@ -0,0 +1,82 @@ +function Get-GitHubRESTTeam { + <# + .SYNOPSIS + List teams from an org or get a team by name + + .DESCRIPTION + Lists all teams in an organization that are visible to the authenticated user or gets a team using the team's slug. + To create the slug, GitHub replaces special characters in the name string, changes all words to lowercase, + and replaces spaces with a - separator. For example, "My TEam Näme" would become my-team-name. + + .EXAMPLE + Get-GitHubRESTTeam -Organization 'GitHub' + + Gets all teams in the `github` organization. + + .EXAMPLE + Get-GitHubRESTTeam -Organization 'github' -Name 'my-team-name' + + Gets the team with the slug 'my-team-name' in the `github` organization. + + .NOTES + [List teams](https://docs.github.com/rest/teams/teams#list-teams) + [Get team by name](https://docs.github.com/en/rest/teams/teams#get-a-team-by-name) + #> + [OutputType([pscustomobject])] + [CmdletBinding(DefaultParameterSetName = '__AllParameterSets')] + param( + # The organization name. The name is not case sensitive. + # If not provided, the organization from the context is used. + [Parameter()] + [Alias('Org')] + [string] $Organization, + + # The slug of the team name. + [Parameter( + Mandatory, + ParameterSetName = 'GetByName' + )] + [Alias('Team', 'TeamName', 'slug', 'team_slug')] + [string] $Name, + + # The context to run the command in. Used to get the details for the API call. + # Can be either a string or a GitHubContext object. + [Parameter()] + [object] $Context = (Get-GitHubContext) + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $Context = Resolve-GitHubContext -Context $Context + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + + if ([string]::IsNullOrEmpty($Organization)) { + $Organization = $Context.Owner + } + Write-Debug "Organization: [$Organization]" + } + + process { + try { + $params = @{ + Organization = $Organization + Context = $Context + } + switch ($PSCmdlet.ParameterSetName) { + 'GetByName' { + Get-GitHubTeamByName @params -Name $Name + } + '__AllParameterSets' { + Get-GitHubTeamListByOrg @params + } + } + } catch { + throw $_ + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/public/Teams/Get-GitHubTeamByName.ps1 b/src/functions/private/Teams/Get-GitHubRESTTeamByName.ps1 similarity index 94% rename from src/functions/public/Teams/Get-GitHubTeamByName.ps1 rename to src/functions/private/Teams/Get-GitHubRESTTeamByName.ps1 index 9c0231762..437c83b70 100644 --- a/src/functions/public/Teams/Get-GitHubTeamByName.ps1 +++ b/src/functions/private/Teams/Get-GitHubRESTTeamByName.ps1 @@ -9,23 +9,20 @@ .EXAMPLE Get-GitHubTeamByName -Organization 'github' -Name 'my-team-name' - - .NOTES - [Get team by name](https://docs.github.com/en/rest/teams/teams#get-a-team-by-name) #> [OutputType([void])] [CmdletBinding()] param( - # The organization name. The name is not case sensitive. - [Parameter(Mandatory)] - [Alias('Org')] - [string] $Organization, - # The slug of the team name. [Parameter(Mandatory)] [Alias('Team', 'TeamName', 'slug', 'team_slug')] [string] $Name, + # The organization name. The name is not case sensitive. + [Parameter(Mandatory)] + [Alias('Org')] + [string] $Organization, + # The context to run the command in. Used to get the details for the API call. # Can be either a string or a GitHubContext object. [Parameter()] diff --git a/src/functions/public/Teams/Get-GitHubTeamListByOrg.ps1 b/src/functions/private/Teams/Get-GitHubRESTTeamListByOrg.ps1 similarity index 100% rename from src/functions/public/Teams/Get-GitHubTeamListByOrg.ps1 rename to src/functions/private/Teams/Get-GitHubRESTTeamListByOrg.ps1 diff --git a/src/functions/public/Teams/Get-GitHubRepoTeam.ps1 b/src/functions/private/Teams/Get-GitHubRepoTeam.ps1 similarity index 100% rename from src/functions/public/Teams/Get-GitHubRepoTeam.ps1 rename to src/functions/private/Teams/Get-GitHubRepoTeam.ps1 diff --git a/src/functions/private/Teams/Get-GitHubTeamBySlug.ps1 b/src/functions/private/Teams/Get-GitHubTeamBySlug.ps1 new file mode 100644 index 000000000..7fb42f398 --- /dev/null +++ b/src/functions/private/Teams/Get-GitHubTeamBySlug.ps1 @@ -0,0 +1,121 @@ +function Get-GitHubTeamBySlug { + <# + .SYNOPSIS + Get a team by name + + .DESCRIPTION + Gets a team using the team's slug. To create the slug, GitHub replaces special characters in the name string, changes all words to lowercase, + and replaces spaces with a - separator. For example, "My TEam Näme" would become my-team-name. + + .EXAMPLE + Get-GitHubTeamBySlug -Organization 'github' -Slug 'my-team-name' + #> + [OutputType([GitHubTeam])] + [CmdletBinding()] + param( + # The slug of the team name. + [Parameter(Mandatory)] + [Alias('team_slug')] + [string] $Slug, + + # The organization name. The name is not case sensitive. + # If not provided, the owner from the context will be used. + [Parameter()] + [Alias('Org')] + [string] $Organization, + + # The context to run the command in. Used to get the details for the API call. + # Can be either a string or a GitHubContext object. + [Parameter()] + [object] $Context = (Get-GitHubContext) + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $Context = Resolve-GitHubContext -Context $Context + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + + if ([string]::IsNullOrEmpty($Organization)) { + $Organization = $Context.Owner + } + Write-Debug "Organization: [$Organization]" + } + + process { + try { + $query = @" +query(`$org: String!, `$teamSlug: String!) { + organization(login: `$org) { + team(slug: `$teamSlug) { + id + name + slug + combinedSlug + databaseId + description + notificationSetting + privacy + parentTeam { + name + slug + } + organization { + login + } + childTeams(first: 100) { + nodes { + name + } + } + createdAt + updatedAt + } + } + } +} +"@ + + # Variables hash that will be sent with the query + $variables = @{ + org = $Organization + teamSlug = $Slug + } + + # Send the request to the GitHub GraphQL API + $response = Invoke-GitHubGraphQLQuery -Query $query -Variables $variables + + # Extract team data + $team = $response.data.organization.team + + # Output the team object + if (-not $team) { + return + } + + [GitHubTeam]( + @{ + Name = $team.name + Slug = $team.slug + NodeID = $team.id + CombinedSlug = $team.CombinedSlug + DatabaseID = $team.DatabaseId + Description = $team.description + Notifications = $team.notificationSetting -eq 'NOTIFICATIONS_ENABLED' ? $true : $false + Visible = $team.privacy -eq 'VISIBLE' ? $true : $false + ParentTeam = $team.parentTeam.slug + Organization = $team.organization.login + ChildTeams = $team.childTeams.nodes.name + CreatedAt = $team.createdAt + UpdatedAt = $team.updatedAt + } + ) + } catch { + throw $_ + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Teams/Get-GitHubTeamListByOrg.ps1 b/src/functions/private/Teams/Get-GitHubTeamListByOrg.ps1 new file mode 100644 index 000000000..a1f90b4db --- /dev/null +++ b/src/functions/private/Teams/Get-GitHubTeamListByOrg.ps1 @@ -0,0 +1,133 @@ +function Get-GitHubTeamListByOrg { + <# + .SYNOPSIS + List teams + + .DESCRIPTION + Lists all teams in an organization that are visible to the authenticated user. + + .EXAMPLE + Get-GitHubTeamListByOrg -Organization 'github' + + .NOTES + [List teams](https://docs.github.com/rest/teams/teams#list-teams) + #> + [OutputType([GitHubTeam[]])] + [CmdletBinding()] + param( + # The organization name. The name is not case sensitive. + # If you don't provide this parameter, the command will use the owner of the context. + [Parameter()] + [Alias('Org')] + [string] $Organization, + + # The context to run the command in. Used to get the details for the API call. + # Can be either a string or a GitHubContext object. + [Parameter()] + [object] $Context = (Get-GitHubContext) + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $Context = Resolve-GitHubContext -Context $Context + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + + if ([string]::IsNullOrEmpty($Organization)) { + $Organization = $Context.Owner + } + Write-Debug "Organization: [$Organization]" + } + + process { + try { + $query = @" +query(`$org: String!, `$after: String) { + organization(login: `$org) { + teams(first: 100, after: `$after) { + nodes { + id + name + slug + combinedSlug + databaseId + description + notificationSetting + privacy + parentTeam { + name + slug + } + organization { + login + } + childTeams(first: 100) { + nodes { + name + } + } + createdAt + updatedAt + } + pageInfo { + endCursor + hasNextPage + } + } + } +} +"@ + + # Variables hash that will be sent with the query + $variables = @{ + org = $Organization + } + + # Prepare to store results and handle pagination + $hasNextPage = $true + $after = $null + + while ($hasNextPage) { + # Update the cursor for pagination + $variables['after'] = $after + + # Send the request to the GitHub GraphQL API + $response = Invoke-GitHubGraphQLQuery -Query $query -Variables $variables + + # Extract team data + $teams = $response.data.organization.teams + + # Accumulate the teams in results + $teams.nodes | ForEach-Object { + [GitHubTeam]( + @{ + Name = $_.name + Slug = $_.slug + NodeID = $_.id + CombinedSlug = $_.combinedSlug + DatabaseId = $_.databaseId + Description = $_.description + Notifications = $_.notificationSetting -eq 'NOTIFICATIONS_ENABLED' ? $true : $false + Visible = $_.privacy -eq 'VISIBLE' ? $true : $false + ParentTeam = $_.parentTeam.slug + Organization = $_.organization.login + ChildTeams = $_.childTeams.nodes.name + CreatedAt = $_.createdAt + UpdatedAt = $_.updatedAt + } + ) + } + + # Check if there's another page to fetch + $hasNextPage = $teams.pageInfo.hasNextPage + $after = $teams.pageInfo.endCursor + } + } catch { + throw $_ + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/public/Teams/Get-GitHubTeam.ps1 b/src/functions/public/Teams/Get-GitHubTeam.ps1 new file mode 100644 index 000000000..37b7347a8 --- /dev/null +++ b/src/functions/public/Teams/Get-GitHubTeam.ps1 @@ -0,0 +1,78 @@ +function Get-GitHubTeam { + <# + .SYNOPSIS + List teams from an org or get a team by name + + .DESCRIPTION + Lists all teams in an organization that are visible to the authenticated user or gets a team using the team's slug. + To create the slug, GitHub replaces special characters in the name string, changes all words to lowercase, + and replaces spaces with a - separator. For example, "My TEam Näme" would become my-team-name. + + .EXAMPLE + Get-GitHubTeam -Organization 'GitHub' + + Gets all teams in the `github` organization. + + .EXAMPLE + Get-GitHubTeam -Organization 'github' -Slug 'my-team-name' + + Gets the team with the slug 'my-team-name' in the `github` organization. + #> + [OutputType([GitHubTeam])] + [CmdletBinding(DefaultParameterSetName = '__AllParameterSets')] + param( + # The slug of the team name. + [Parameter( + Mandatory, + ParameterSetName = 'BySlug' + )] + [Alias('team_slug')] + [string] $Slug, + + # The organization name. The name is not case sensitive. + # If not provided, the owner from the context will be used. + [Parameter()] + [Alias('Org')] + [string] $Organization, + + # The context to run the command in. Used to get the details for the API call. + # Can be either a string or a GitHubContext object. + [Parameter()] + [object] $Context = (Get-GitHubContext) + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $Context = Resolve-GitHubContext -Context $Context + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + + if ([string]::IsNullOrEmpty($Organization)) { + $Organization = $Context.Owner + } + Write-Debug "Organization: [$Organization]" + } + + process { + try { + $params = @{ + Organization = $Organization + Context = $Context + } + switch ($PSCmdlet.ParameterSetName) { + 'BySlug' { + Get-GitHubTeamBySlug @params -Slug $Slug + } + '__AllParameterSets' { + Get-GitHubTeamListByOrg @params + } + } + } catch { + throw $_ + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/public/Teams/New-GitHubTeam.ps1 b/src/functions/public/Teams/New-GitHubTeam.ps1 index 858de9a7b..8ec457a84 100644 --- a/src/functions/public/Teams/New-GitHubTeam.ps1 +++ b/src/functions/public/Teams/New-GitHubTeam.ps1 @@ -27,17 +27,19 @@ .NOTES [Create a team](https://docs.github.com/rest/teams/teams#create-a-team) #> + [OutputType([GitHubTeam])] [CmdletBinding(SupportsShouldProcess)] param( - # The organization name. The name is not case sensitive. - [Parameter(Mandatory)] - [Alias('Org')] - [string] $Organization, - # The name of the team. [Parameter(Mandatory)] [string] $Name, + # The organization name. The name is not case sensitive. + # If not provided, the organization from the context is used. + [Parameter()] + [Alias('Org')] + [string] $Organization, + # The description of the team. [Parameter()] [string] $Description, @@ -59,16 +61,14 @@ # - closed - visible to all members of this organization. # Default for child team: closed [Parameter()] - [ValidateSet('secret', 'closed')] - [string] $Privacy = 'closed', + [bool] $Visible = $true, # The notification setting the team has chosen. The options are: # notifications_enabled - team members receive notifications when the team is @mentioned. # notifications_disabled - no one receives notifications. # Default: notifications_enabled [Parameter()] - [ValidateSet('notifications_enabled', 'notifications_disabled')] - [string] $NotificationSetting, + [bool] $Notifications = $true, # Closing down notice. The permission that new repositories will be added to the team with when none is specified. [Parameter()] @@ -77,7 +77,7 @@ # The ID of a team to set as the parent team. [Parameter()] - [int] $ParentTeamID, + [int] $ParentTeamID = 0, # The context to run the command in. Used to get the details for the API call. # Can be either a string or a GitHubContext object. @@ -95,6 +95,10 @@ $Organization = $Context.Owner } Write-Debug "Organization: [$Organization]" + + if (-not $Visible -and $ParentTeamID -gt 0) { + throw 'A nested team cannot be secret (invisible).' + } } process { @@ -104,8 +108,8 @@ description = $Description maintainers = $Maintainers repo_names = $RepoNames - privacy = $Privacy - notification_setting = $NotificationSetting + privacy = $Visible ? 'closed' : 'secret' + notification_setting = $Notifications ? 'notifications_enabled' : 'notifications_disabled' permission = $Permission parent_team_id = $ParentTeamID -eq 0 ? $null : $ParentTeamID } @@ -120,7 +124,24 @@ if ($PSCmdlet.ShouldProcess("'$Name' in '$Organization'", 'Create team')) { Invoke-GitHubAPI @inputObject | ForEach-Object { - Write-Output $_.Response + $team = $_.Response + [GitHubTeam]( + @{ + Name = $team.name + Slug = $team.slug + NodeID = $team.node_id + CombinedSlug = $Organization + '/' + $team.slug + DatabaseId = $team.id + Description = $team.description + Notifications = $team.notification_setting -eq 'notifications_enabled' ? $true : $false + Visible = $team.privacy -eq 'closed' ? $true : $false + ParentTeam = $team.parent.slug + Organization = $team.organization.login + ChildTeams = @() + CreatedAt = $team.created_at + UpdatedAt = $team.updated_at + } + ) } } } catch { diff --git a/src/functions/public/Teams/Remove-GitHubTeam.ps1 b/src/functions/public/Teams/Remove-GitHubTeam.ps1 index ac836f9b5..389b1fd42 100644 --- a/src/functions/public/Teams/Remove-GitHubTeam.ps1 +++ b/src/functions/public/Teams/Remove-GitHubTeam.ps1 @@ -16,16 +16,22 @@ [OutputType([void])] [CmdletBinding(SupportsShouldProcess)] param( + # The slug of the team name. + [Parameter( + Mandatory, + ValueFromPipelineByPropertyName + )] + [Alias('team_slug')] + [string] $Slug, + # The organization name. The name is not case sensitive. - [Parameter(Mandatory)] + # If not provided, the organization from the context is used. + [Parameter( + ValueFromPipelineByPropertyName + )] [Alias('Org')] [string] $Organization, - # The slug of the team name. - [Parameter(Mandatory)] - [Alias('Team', 'TeamName', 'slug', 'team_slug')] - [string] $Name, - # The context to run the command in. Used to get the details for the API call. # Can be either a string or a GitHubContext object. [Parameter()] @@ -49,10 +55,10 @@ $inputObject = @{ Context = $Context Method = 'Delete' - APIEndpoint = "/orgs/$Organization/teams/$Name" + APIEndpoint = "/orgs/$Organization/teams/$Slug" } - if ($PSCmdlet.ShouldProcess("$Organization/$Name", 'Delete')) { + if ($PSCmdlet.ShouldProcess("$Organization/$Slug", 'Delete')) { Invoke-GitHubAPI @inputObject | ForEach-Object { Write-Output $_.Response } diff --git a/src/functions/public/Teams/Update-GitHubTeam.ps1 b/src/functions/public/Teams/Update-GitHubTeam.ps1 index c87d1e8f9..7a43dcfc4 100644 --- a/src/functions/public/Teams/Update-GitHubTeam.ps1 +++ b/src/functions/public/Teams/Update-GitHubTeam.ps1 @@ -8,37 +8,41 @@ .EXAMPLE $params = @{ - Organization = 'github' - Name = 'team-name' - NewName = 'new-team-name' - Description = 'A new team' - Privacy = 'closed' - NotificationSetting = 'notifications_enabled' - Permission = 'pull' - ParentTeamID = 123456 + Organization = 'github' + Slug = 'team-name' + NewName = 'new team name' + Description = 'A new team' + Visible = $true + Notifications = $true + Permission = 'pull' + ParentTeamID = 123456 } Update-GitHubTeam @params + Updates the team with the slug 'team-name' in the `github` organization with the new name 'new team name', description 'A new team', + visibility set to 'closed', notifications enabled, permission set to 'pull', and the parent team ID set to 123456. + .NOTES [Update a team](https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#update-a-team) #> - [OutputType([pscustomobject])] + [OutputType([GitHubTeam])] [CmdletBinding(SupportsShouldProcess)] param( - # The organization name. The name is not case sensitive. + # The slug of the team name. [Parameter(Mandatory)] + [Alias('team_slug')] + [string] $Slug, + + # The organization name. The name is not case sensitive. + # If you do not provide this parameter, the command will use the organization from the context. + [Parameter()] [Alias('Org')] [string] $Organization, - # The slug of the team name. - [Parameter(Mandatory)] - [Alias('Team', 'TeamName', 'slug', 'team_slug')] - [string] $Name, - # The new team name. [Parameter()] [Alias()] - [string] $NewName, + [string] $Name, # The description of the team. [Parameter()] @@ -53,21 +57,19 @@ # - closed - visible to all members of this organization. # Default for child team: closed [Parameter()] - [ValidateSet('secret', 'closed')] - [string] $Privacy = 'closed', + [bool] $Visible, # The notification setting the team has chosen. The options are: # notifications_enabled - team members receive notifications when the team is @mentioned. # notifications_disabled - no one receives notifications. # Default: notifications_enabled [Parameter()] - [ValidateSet('notifications_enabled', 'notifications_disabled')] - [string] $NotificationSetting, + [bool] $Notifications, # Closing down notice. The permission that new repositories will be added to the team with when none is specified. [Parameter()] [ValidateSet('pull', 'push')] - [string] $Permission = 'pull', + [string] $Permission, # The ID of a team to set as the parent team. [Parameter()] @@ -94,25 +96,43 @@ process { try { $body = @{ - name = $NewName + name = $Name description = $Description - privacy = $Privacy - notification_setting = $NotificationSetting + privacy = $PSBoundParameters.ContainsKey('Visible') ? ($Visible ? 'closed' : 'secret') : $null + notification_setting = $PSBoundParameters.ContainsKey('Notifications') ? + ($Notifications ? 'notifications_enabled' : 'notifications_disabled') : $null permission = $Permission - parent_team_id = $ParentTeamID + parent_team_id = $ParentTeamID -eq 0 ? $null : $ParentTeamID } $body | Remove-HashtableEntry -NullOrEmptyValues $inputObject = @{ Context = $Context - APIEndpoint = "/orgs/$Organization/teams/$Name" + APIEndpoint = "/orgs/$Organization/teams/$Slug" Method = 'Patch' Body = $body } - if ($PSCmdlet.ShouldProcess("$Organization/$Name", 'Update')) { + if ($PSCmdlet.ShouldProcess("$Organization/$Slug", 'Update')) { Invoke-GitHubAPI @inputObject | ForEach-Object { - Write-Output $_.Response + $team = $_.Response + [GitHubTeam]( + @{ + Name = $team.name + Slug = $team.slug + NodeID = $team.node_id + CombinedSlug = $Organization + '/' + $team.slug + DatabaseId = $team.id + Description = $team.description + Notifications = $team.notification_setting -eq 'notifications_enabled' ? $true : $false + Visible = $team.privacy -eq 'closed' ? $true : $false + ParentTeam = $team.parent.slug + Organization = $team.organization.login + ChildTeams = @() + CreatedAt = $team.created_at + UpdatedAt = $team.updated_at + } + ) } } } catch {