diff --git a/Test/private/mocks/invoke-createDraftItem.json b/Test/private/mocks/invoke-createDraftItem.json new file mode 100644 index 0000000..009a9f8 --- /dev/null +++ b/Test/private/mocks/invoke-createDraftItem.json @@ -0,0 +1,37 @@ +{ + "data": { + "addProjectV2DraftIssue": { + "projectItem": { + "id": "PVTI_lADOAlIw4c4BCe3VzggDT7s", + "type": "DRAFT_ISSUE", + "fullDatabaseId": "134434747", + "project": { + "id": "PVT_kwDOAlIw4c4BCe3V", + "url": "https://github.com/orgs/octodemo/projects/700" + }, + "content": { + "__typename": "DraftIssue", + "id": "DI_lADOAlIw4c4BCe3VzgJ29YM", + "body": "Body of draftissue", + "title": "DraftIssue created for test ", + "updatedAt": "2025-10-19T10:50:07Z", + "createdAt": "2025-10-19T10:50:07Z" + }, + "fieldValues": { + "nodes": [ + { + "__typename": "ProjectV2ItemFieldTextValue", + "text": "DraftIssue created for test ", + "field": { + "__typename": "ProjectV2Field", + "id": "PVTF_lADOAlIw4c4BCe3Vzg0rg-M", + "name": "Title", + "dataType": "TITLE" + } + } + ] + } + } + } + } +} diff --git a/Test/public/items/project_item_draftissue.test.ps1 b/Test/public/items/project_item_draftissue.test.ps1 new file mode 100644 index 0000000..f329074 --- /dev/null +++ b/Test/public/items/project_item_draftissue.test.ps1 @@ -0,0 +1,25 @@ +function Test_NewProjectDraftIssue { + + Reset-InvokeCommandMock + Mock_DatabaseRoot + + $p = Get-Mock_Project_700 ; $owner = $p.owner ; $projectNumber = $p.number + MockCall_GetProject -MockProject $p + + $title = "DraftIssue created for test " + $body = "Body of draftissue" + + MockCallJson -Command "Invoke-CreateDraftItem -ProjectId $($p.id) -Title ""$title"" -Body ""$body""" -FileName "invoke-createDraftItem.json" + + # Act + $draftIssueId = New-ProjectDraftIssue -Owner $owner -ProjectNumber $projectNumber -Title $title -Body $body + + + $item = Get-ProjectItem -ItemId $draftIssueId + + # Assert + Assert-AreEqual -Expected $draftIssueId -Presented $item.id + Assert-AreEqual -Expected $title -Presented $item.Title + Assert-AreEqual -Expected $body -Presented $item.Body + +} \ No newline at end of file diff --git a/public/driver/driver_gh.ps1 b/public/driver/driver_gh.ps1 index 94c9cbd..725724f 100644 --- a/public/driver/driver_gh.ps1 +++ b/public/driver/driver_gh.ps1 @@ -499,16 +499,56 @@ function Get-GraphQLString{ Write-MyDebug -section "driver_gh" -message "Getting GraphQL string from file: $FileName" + $path = getmockfilepath -FileName $FileName + + $content = get-content -path $path | Out-String + + $content = Expand-GraphQLString -GraphQLString $content + + return $content +} + +function getmockfilepath{ + param( + [Parameter(Mandatory, Position = 0)] [string]$FileName + ) + $local = $PSScriptRoot $public = $local | Split-Path -Parent $path = $public | Join-Path -ChildPath "graphql" -AdditionalChildPath $FileName + # Verify that the file exists if(! (Test-Path -Path $path)){ throw "GraphQL file not found at path: $path" } - $content = get-content -path $path | Out-String + return $path +} - return $content -} \ No newline at end of file +function Expand-GraphQLString{ + param( + [Parameter(Mandatory, Position = 0)] [string]$GraphQLString + ) + + $tags = [regex]::Matches($GraphQLString,'\{\{(\w+)\}\}') | ForEach-Object { $_.Groups[1].Value } + + # If no tags found, return the original string + if($tags.Count -eq 0){ + return $GraphQLString + } + + $content = $GraphQLString + + foreach($tag in $tags){ + $path = getmockfilepath -FileName "_$tag.tag" + $tagContent = Get-Content -Path $path | Out-String + + $content = $content -replace "\{\{$tag\}\}", $tagContent + } + + # loop back to expand nested tags + $ret = Expand-GraphQLString -GraphQLString $content + + return $ret +} \ No newline at end of file diff --git a/public/driver/issue/Invoke-CreateDraftItem.ps1 b/public/driver/issue/Invoke-CreateDraftItem.ps1 new file mode 100644 index 0000000..c6cdaca --- /dev/null +++ b/public/driver/issue/Invoke-CreateDraftItem.ps1 @@ -0,0 +1,52 @@ +function Invoke-CreateDraftItem{ + param( + [Parameter(Mandatory=$true)] [string]$ProjectId, + [Parameter(Mandatory=$true)] [string]$Title, + [Parameter(Mandatory=$false)] [string]$Body + ) + + # Use the environmentraviable + $token = Get-GithubToken + if(-not $token){ + throw "GH Cli Auth Token not available. Run 'gh auth login' in your terminal." + } + + # Define the GraphQL query with variables + $mutation = Get-GraphQLString "createDraftItem.mutant" + + # Define the headers for the request + $headers = @{ + "Authorization" = "Bearer $token" + "Content-Type" = "application/json" + } + + # Define the variables for the request + $variables = @{ + input = @{ + projectId = $ProjectId + title = $Title + body = $Body + } + } + + # Define the body for the request + $body = @{ + query= $mutation + variables = $variables + } | ConvertTo-Json -Depth 10 + + # Send the request + $response = Invoke-RestMethod -Uri 'https://api.github.com/graphql' -Method Post -Body $body -Headers $headers + + # Check if here are errors + if($response.errors){ + $response.errors | ForEach-Object { + getErrrorString -Errors $response.errors | Write-MyError + } + return $null + } + + # Return the field names + return $response + +} Export-ModuleMember -Function Invoke-CreateDraftItem \ No newline at end of file diff --git a/public/graphql/_projectv2item.tag b/public/graphql/_projectv2item.tag new file mode 100644 index 0000000..822f003 --- /dev/null +++ b/public/graphql/_projectv2item.tag @@ -0,0 +1,102 @@ + { + id, type, fullDatabaseId, + project{ id, url}, + content{__typename, + ... on DraftIssue {id,body,title,updatedAt,createdAt}, + ... on PullRequest{id,body,title,updatedAt,createdAt,number,url,state,repository{name,owner{login}} + comments(last: $lastComments){ + totalCount, + nodes{createdAt,updatedAt,url,body,fullDatabaseId,author{login}} + } + }, + ... on Issue{id,body,title,updatedAt,createdAt,number,url,state,repository{name,owner{login}} + comments(last: $lastComments){ + totalCount, + nodes{createdAt,updatedAt,url,body,fullDatabaseId,author{login}} + } + } + }, + fieldValues(first: 100){ + nodes{__typename, + ... on ProjectV2ItemFieldDateValue{date,field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldIterationValue{title,startDate,duration,field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldLabelValue{labels(first: 10){nodes{name + } + },field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldNumberValue{number,field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldSingleSelectValue{name,field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldTextValue{text,field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldMilestoneValue{milestone{title,description,dueOn + },field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldPullRequestValue{pullRequests(first: 10){nodes{url + } + },field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldRepositoryValue{repository{url + },field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldUserValue{users(first: 10){nodes{login + } + },field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + },... on ProjectV2ItemFieldReviewerValue{reviewers(first: 10){nodes{__typename,... on Team{name + },... on User{login + } + } + },field{__typename,... on ProjectV2Field{id,name,dataType + },... on ProjectV2IterationField{id,name,dataType + },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name + } + } + } + } + } + } +} \ No newline at end of file diff --git a/public/graphql/createDraftItem.mutant b/public/graphql/createDraftItem.mutant new file mode 100644 index 0000000..dd650e7 --- /dev/null +++ b/public/graphql/createDraftItem.mutant @@ -0,0 +1,5 @@ +mutation CreateDraftItem($input:AddProjectV2DraftIssueInput! $lastComments: Int=5 ){ + addProjectV2DraftIssue(input:$input){ + projectItem {{projectv2item}} + } +} \ No newline at end of file diff --git a/public/items/project_item_draftissue.ps1 b/public/items/project_item_draftissue.ps1 new file mode 100644 index 0000000..1609499 --- /dev/null +++ b/public/items/project_item_draftissue.ps1 @@ -0,0 +1,54 @@ + +Set-MyInvokeCommandAlias -Alias createDraftItem -Command 'Invoke-CreateDraftItem -ProjectId {projectid} -Title "{title}" -Body "{body}"' + + +function New-ProjectDraftIssue { + [CmdletBinding()] + param ( + [Parameter()][string]$Owner, + [Parameter()][string] $ProjectNumber, + [Parameter(Mandatory,Position=0)][string]$Title, + [Parameter(Position=1)][string]$Body, + [Parameter()][switch]$NoCache, + [Parameter()][switch]$OpenOnCreation + ) + + ($Owner, $ProjectNumber) = Get-OwnerAndProjectNumber -Owner $Owner -ProjectNumber $ProjectNumber + + $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber + + $params = @{ + title = $Title | ConvertTo-InvokeParameterString + body = $Body | ConvertTo-InvokeParameterString + projectid = $db.ProjectId + } + + $params | ConvertTo-Json | Write-Verbose + + $response = Invoke-MyCommand -Command "createDraftItem" -Parameters $params + + $item = $response.data.addProjectV2DraftIssue.projectItem + + if ($item) { + $ret = $item.id + + if (! $NoCache) { + "Adding item [$ret] to cache" | Write-Verbose + + $item = $item | Convert-NodeItemToHash + + Set-Item $db $item + + Save-ProjectDatabaseSafe -Database $db + + } + + return $ret + + } + else { + "Item not added to project" | Write-MyError + return $null + } + +} Export-ModuleMember -Function New-ProjectDraftIssue