Skip to content

Instantly share code, notes, and snippets.

@FH-Inway
Last active October 22, 2024 20:29
Show Gist options
  • Select an option

  • Save FH-Inway/9f1de243fbbdd612590935d0d46c8275 to your computer and use it in GitHub Desktop.

Select an option

Save FH-Inway/9f1de243fbbdd612590935d0d46c8275 to your computer and use it in GitHub Desktop.
Draft for some module functions for powershelling the GitHub ProjectV2 GraphQL API
# Example showcasing the use of the GitHub-Project-Manager.ps1
# Iterates over all cmdlets of the d365fo.tools module
# and creates a draft issue for each cmdlet that is missing a draft issue.
Import-Module .\TempPoint\GitHub-Project-Manager.ps1
$project = Get-UserProject -User FH-Inway -ProjectNumber 2
$existingItems = Get-ProjectItems -ProjectId $project.id
# Import-Module d365fo.tools
Import-Module -Name .\d365fo.tools -Verbose -Force
$cmdlets = Get-Command -Module d365fo.tools
# Test with only 5 cmdlets
$cmdlets = $cmdlets | Select-Object -First 5
Write-Host "Creating draft issues for $($cmdlets.Count) cmdlets"
foreach ($cmdlet in $cmdlets) {
$cmdletName = $cmdlet.Name
New-ProjectDraftItem -ProjectId $project.id -Title $cmdletName -Items $existingItems
}
Remove-Module GitHub-Project-Manager
Import-Module .\GitHub-Project-Manager.ps1
$project = Get-UserProject -User FH-Inway -ProjectNumber 2
$items = Get-ProjectItems -ProjectId $project.id
$fields = Get-ProjectFields -ProjectId $project.id
$fieldTypes = Get-ProjectFieldTypes -ProjectId $project.id
$fieldValues = @(
@{ Name = "Status"; Value = "Todo"},
@{ Name = "Support"; Value = "Triage pending"},
@{ Name = "Complexity"; Value = "Triage pending" }
)
$params = @{
ProjectId = $project.id
Title = "New-D365Bacpac"
Items = $items
Fields = $fields
FieldTypes = $fieldTypes
FieldValues = $fieldValues
}
$startTime = Get-Date
$item = New-ProjectDraftItem @params
$endTime = Get-Date
$elapsedTime = $endTime - $startTime
Write-Host "Elapsed time: $($elapsedTime.TotalSeconds) seconds"
$item
# A PowerShell module file that contains the functions for the GitHub Project Manager module.
# It requires the GitHub CLI to be installed and authenticated.
function Get-Node {
param (
[string] $NodeId,
[string] $Type,
[string[]] $Fields = @("id")
)
$GitHubGraphQLAPIQuery = "
query(`$node_id: ID!){
node(id: `$node_id) {
... on $Type {
$Fields
}
}
}"
$node = gh api graphql -f query=$GitHubGraphQLAPIQuery -f node_id=$NodeId
$node = $node | ConvertFrom-Json
$node.data.node
}
function Get-UserProject {
[CmdletBinding()]
param (
[string] $User,
[int] $ProjectNumber,
[string[]] $Fields = @("id", "title")
)
$GitHubGraphQLAPIQuery = "
query(`$user: String! `$project_number: Int!){
user(login: `$user){
projectV2(number: `$project_number) {
$Fields
}
}
}
"
$project = gh api graphql -f query=$GitHubGraphQLAPIQuery -f user=$User -F project_number=$ProjectNumber
$project = $project | ConvertFrom-Json
$project.data.user.projectV2
}
function Get-ProjectFields {
[CmdletBinding()]
param (
[string] $ProjectId
)
$GitHubGraphQLAPIQuery = "
query(`$project_id: ID!){
node(id: `$project_id) {
... on ProjectV2 {
fields(first: 20) {
nodes {
... on ProjectV2Field {
id
name
}
... on ProjectV2IterationField {
id
name
configuration {
iterations {
startDate
id
}
}
}
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}
"
$projectFields = gh api graphql -f query=$GitHubGraphQLAPIQuery -f project_id=$ProjectId
$projectFields = $projectFields | ConvertFrom-Json
$projectFields.data.node.fields.nodes
}
function Get-ProjectFieldTypes {
param (
[string] $ProjectId
)
$GitHubGraphQLAPIQuery = "
query(`$project_id: ID!){
node(id: `$project_id) {
... on ProjectV2 {
fields(first: 100) {
nodes {
... on ProjectV2FieldCommon {
__typename
id
name
}
}
}
}
}
}"
$fieldTypes = gh api graphql -f query=$GitHubGraphQLAPIQuery -f project_id=$ProjectId
$fieldTypes = $fieldTypes | ConvertFrom-Json
$fieldTypes.data.node.fields.nodes
}
function New-ProjectDraftItem {
param (
[string] $ProjectId,
[string] $Title,
[Object[]] $FieldValues,
[Object[]] $Items = $null,
[Object[]] $Fields = $null,
[Object[]] $FieldTypes = $null
)
$existingItem = Get-ProjectItemByTitle -ProjectId $ProjectId -Title $Title -Items $Items
if ($existingItem) {
Write-Host "Draft item with title: $Title already exists with id: $($existingItem.id)"
return
}
Write-Host "Creating new draft item with title: $Title"
$GitHubGraphQLAPIQuery = "
mutation(`$project_id: ID! `$title: String!){
addProjectV2DraftIssue(input: {projectId: `$project_id title: `$title}) {
projectItem {
id
}
}
}"
$item = gh api graphql -f query=$GitHubGraphQLAPIQuery -f project_id=$ProjectId -f title=$Title
$item = $item | ConvertFrom-Json
Write-Host "Draft item created with id: $($item.data.addProjectV2DraftIssue.projectItem.id)"
if (-not $FieldValues) {
return $item
}
$params = @{
ProjectId = $ProjectId
ItemId = $item.data.addProjectV2DraftIssue.projectItem.id
}
$projectFields = $Fields
if (-not $projectFields) {
$projectFields = Get-ProjectFields -ProjectId $ProjectId
}
$fieldValuesIds = @()
foreach ($field in $FieldValues) {
$projectField = $projectFields | Where-Object { $_.name -eq $field.Name }
$fieldId = $projectField.id
if ($projectField.options) {
$projectFieldOption = $projectField.options | Where-Object { $_.name -eq $field.Value }
$fieldValuesIds += @{ FieldId = $fieldId; Value = $projectFieldOption.id }
}
else {
$fieldValuesIds += @{ FieldId = $fieldId; Value = $field.Value }
}
}
$params.FieldValues = $fieldValuesIds
if (-not $FieldTypes) {
$FieldTypes = Get-ProjectFieldTypes -ProjectId $ProjectId
}
$params.FieldTypes = $FieldTypes
Write-Host "Updating draft item fields with values"
$item = Update-ProjectDraftItemFieldsWithValues @params
$item = $item | ConvertFrom-Json
Write-Host "Draft item updated with id: $($item.data.fieldUpdate2.projectV2Item.id)"
$item
}
function Get-ProjectItems {
param (
[string] $ProjectId
)
$firstPage = Get-ProjectItemsPage -ProjectId $ProjectId
$items = $firstPage.data.node.items.nodes
$pageInfo = $firstPage.data.node.items.pageInfo
while ($pageInfo.hasNextPage) {
$nextPage = Get-ProjectItemsPage -ProjectId $ProjectId -After $pageInfo.endCursor
$items += $nextPage.data.node.items.nodes
$pageInfo = $nextPage.data.node.items.pageInfo
}
$items
}
function Get-ProjectItemsPage {
param (
[string] $ProjectId,
[string] $After = "null"
)
$GitHubGraphQLAPIQuery = "
query(`$project_id: ID! `$after: String){
node(id: `$project_id) {
... on ProjectV2 {
items(first: 100, after: `$after) {
nodes {
id
content{
... on DraftIssue {
title
}
...on Issue {
title
}
...on PullRequest {
title
}
}
}
pageInfo {
endCursor
startCursor
hasNextPage
hasPreviousPage
}
}
}
}
}"
$itemsPage = gh api graphql -f query=$GitHubGraphQLAPIQuery -f project_id=$ProjectId -f after=$After
$itemsPage = $itemsPage | ConvertFrom-Json
$itemsPage
}
function Get-ProjectItemByTitle {
param (
[string] $ProjectId,
[string] $Title,
[Object[]] $Items = $null
)
if (-not $Items) {
$Items = Get-ProjectItems -ProjectId $ProjectId
}
$item = $Items | Where-Object { $_.content.title -eq $Title }
$item
}
function Update-ProjectDraftItemFieldValue {
param (
[string] $ProjectId,
[string] $ItemId,
[string] $FieldId,
[string] $Value
)
$field = Get-Node -NodeId $FieldId -Type "ProjectV2FieldCommon" -Fields "__typename"
$fieldValue = ""
switch ($field.__typename) {
"ProjectV2SingleSelectField" {
$fieldValue = "{ singleSelectOptionId: `$value }"
}
"ProjectV2IterationField" {
$fieldValue = "{ iterationId: `$value }"
}
"ProjectV2TextField" {
$fieldValue = "{ text: `$value }"
}
Default {
$fieldValue = "{ text: `$value }"
}
}
$GitHubGraphQLAPIQuery = "
mutation(`$project_id: ID! `$item_id: ID! `$field_id: ID! `$value: String!){
updateProjectV2ItemFieldValue(
input: {
projectId: `$project_id
itemId: `$item_id
fieldId: `$field_id
value: $fieldValue
}
) {
projectV2Item {
id
}
}
}"
gh api graphql -f query=$GitHubGraphQLAPIQuery -f project_id=$ProjectId -f item_id=$ItemId -f field_id=$FieldId -f value=$Value
}
function Update-ProjectDraftItemFieldsWithValues {
param (
[string] $ProjectId,
[string] $ItemId,
[Object[]] $FieldValues,
[Object[]] $FieldTypes
)
# Define the base GraphQL mutation
$baseMutation = @"
mutation(`$projectId: ID!, `$itemId: ID! {0}) {{
{1}
}}
"@ # Has to finish on first column
# Initialize the dynamic parts of the mutation and parameters
$fieldMutations = @()
$fieldVars = @()
$fieldParams = @{}
# Loop through the dynamic fields to construct the mutation and parameters
for ($i = 0; $i -lt $FieldValues.Count; $i++) {
$field = $FieldValues[$i]
$fieldIdParam = "fieldId$i"
$valueParam = "value$i"
$typename = ""
if ($FieldTypes) {
$typename = $FieldTypes | Where-Object { $_.id -eq $field.FieldId } | Select-Object -ExpandProperty __typename
}
else {
$params = @{
NodeId = $field.FieldId
Type = "ProjectV2FieldCommon"
Fields = @("__typename")
FieldTypes = $FieldTypes
}
$fieldNode = Get-Node @params
$typename = $fieldNode.__typename
}
$fieldValue = ""
switch ($typename) {
"ProjectV2SingleSelectField" {
$fieldValue = "{ singleSelectOptionId: `$$valueParam }"
}
"ProjectV2IterationField" {
$fieldValue = "{ iterationId: `$$valueParam }"
}
"ProjectV2TextField" {
$fieldValue = "{ text: `$$valueParam }"
}
Default {
$fieldValue = "{ text: `$$valueParam }"
}
}
# Append to the field mutations
$fieldMutations += "fieldUpdate$($i): updateProjectV2ItemFieldValue(input: {projectId: `$projectId, itemId: `$itemId, fieldId: `$$fieldIdParam, value: $fieldValue }) { projectV2Item { id } }"
# Add to the field parameters
$fieldParams["$fieldIdParam"] = $field.FieldId
$fieldParams["$valueParam"] = $field.Value
# Add to the field variables
$fieldVars += "`$$($fieldIdParam): ID!"
$fieldVars += "`$$($valueParam): String!"
}
# Construct the final mutation
$variables = (", " + ($fieldVars -join ", "))
$mutations = ($fieldMutations -join "`n")
$finalMutation = $baseMutation -f $variables, $mutations
# Add static parameters
$fieldParams["projectId"] = $ProjectId
$fieldParams["itemId"] = $ItemId
# Execute the gh api graphql command
$ghParams = @("-f", "query=$finalMutation")
foreach ($key in $fieldParams.Keys) {
$ghParams += @("-f", "$key=$($fieldParams[$key])")
}
# Run the command
& gh api graphql @ghParams
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment