diff --git a/Test/public/project_item.test.ps1 b/Test/public/project_item.test.ps1 index 40cc102..56809e4 100644 --- a/Test/public/project_item.test.ps1 +++ b/Test/public/project_item.test.ps1 @@ -366,6 +366,9 @@ function Test_GetItemDirect_SUCCESS{ Assert-AreEqual -Expected $contentId -Presented $result.contentId } +function Test_GetItemDirect_SUCCESS_WithCache{ + Assert-NotImplemented +} function Test_ShowProjectItem_SUCCESS{ diff --git a/include/databaseV2.ps1 b/include/databaseV2.ps1 index 2f917ad..1333712 100644 --- a/include/databaseV2.ps1 +++ b/include/databaseV2.ps1 @@ -16,7 +16,7 @@ function Reset-DatabaseStore{ $databaseRoot = Invoke-MyCommand -Command GetDatabaseStorePath - Remove-Item -Path $databaseRoot -Recurse -Force -ErrorAction SilentlyContinue + Microsoft.PowerShell.Management\Remove-Item -Path $databaseRoot -Recurse -Force -ErrorAction SilentlyContinue New-Item -Path $databaseRoot -ItemType Directory @@ -55,7 +55,7 @@ function Reset-Database{ [Parameter(Position = 0)][string]$Key ) $path = Get-DatabaseFile -Key $Key - Remove-Item -Path $path -Force -ErrorAction SilentlyContinue + Microsoft.PowerShell.Management\Remove-Item -Path $path -Force -ErrorAction SilentlyContinue return } diff --git a/private/item/convertItemToHash.ps1 b/private/item/convertItemToHash.ps1 index cc0e756..f8cd487 100644 --- a/private/item/convertItemToHash.ps1 +++ b/private/item/convertItemToHash.ps1 @@ -20,6 +20,9 @@ function Convert-NodeItemToHash { $item.url = $NodeItem.content.url $item.state = $NodeItem.content.state + $item.projectId = $NodeItem.project.id + $item.projectUrl = $NodeItem.project.url + $item.createdAt = GetDateTime -DateTimeString $NodeItem.content.createdAt $item.updatedAt = GetDateTime -DateTimeString $NodeItem.content.updatedAt diff --git a/private/projectDatabase/project_database.ps1 b/private/projectDatabase/project_database.ps1 index 9e87dbd..19f88a8 100644 --- a/private/projectDatabase/project_database.ps1 +++ b/private/projectDatabase/project_database.ps1 @@ -95,17 +95,29 @@ function Set-ProjectDatabaseV2{ $db.items = $items $db.fields = $fields - Save-ProjectDatabase -Database $db -Owner $owner -ProjectNumber $projectnumber + Save-ProjectDatabase -Database $db } function Save-ProjectDatabase{ [CmdletBinding()] param( - [Parameter(Position = 0)][string]$Owner, - [Parameter(Position = 1)][int]$ProjectNumber, - [Parameter(Position = 2)][hashtable]$Database + [Parameter(Position = 0)][hashtable]$Database ) + $owner = $Database.owner + $projectnumber = $Database.number + + if($null -eq $Database){ + throw "Database parameter is required" + } + + if([string]::IsNullOrWhiteSpace($owner)){ + throw "Database.owner is null or empty" + } + if($projectnumber -le 0){ + throw "Database.number is null or not a positive integer" + } + $dbkey = Get-DatabaseKey -Owner $owner -ProjectNumber $projectnumber Save-Database -Key $dbkey -Database $Database } diff --git a/private/projectDatabase/project_database_Async.ps1 b/private/projectDatabase/project_database_Async.ps1 index 76599c6..4ef34f6 100644 --- a/private/projectDatabase/project_database_Async.ps1 +++ b/private/projectDatabase/project_database_Async.ps1 @@ -22,7 +22,7 @@ function Sync-ProjectDatabaseAsync { $db = Sync-ProjectAsync -Database $db -SyncBatchSize $SyncBatchSize # Saved changes to database - Save-ProjectDatabase -Database $db -Owner $Owner -ProjectNumber $ProjectNumber + Save-ProjectDatabase -Database $db if (Test-ProjectDatabaseStaged -Owner $Owner -ProjectNumber $ProjectNumber) { "Still pending staged values" | Write-MyError @@ -100,7 +100,7 @@ function Sync-ProjectAsync { Remove-ItemStaged -Database $db -ItemId $call.itemId -FieldId $call.FieldId } - Save-ProjectDatabase -Database $db -Owner $Owner -ProjectNumber $ProjectNumber + Save-ProjectDatabase -Database $db return $db } diff --git a/private/projectDatabase/project_database_Item.ps1 b/private/projectDatabase/project_database_Item.ps1 index 2651c62..1075034 100644 --- a/private/projectDatabase/project_database_Item.ps1 +++ b/private/projectDatabase/project_database_Item.ps1 @@ -36,7 +36,6 @@ function Get-Item{ function Set-Item{ [CmdletBinding()] - [OutputType([string])] param( [Parameter(Position = 0)][object[]]$Database, [Parameter(ValueFromPipeline, Position = 1)][PSCustomObject]$Item @@ -52,6 +51,19 @@ function Set-Item{ } +function Remove-Item{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][object[]]$Database, + [Parameter(ValueFromPipeline, Position = 1)][string]$ItemId + ) + + process{ + $Database.items.Remove($ItemId) | Out-Null + } + +} + function Set-ItemValue{ [CmdletBinding()] [OutputType([string])] diff --git a/private/projectDatabase/project_database_Sync.ps1 b/private/projectDatabase/project_database_Sync.ps1 index b7659a8..c9e2878 100644 --- a/private/projectDatabase/project_database_Sync.ps1 +++ b/private/projectDatabase/project_database_Sync.ps1 @@ -20,7 +20,7 @@ function Sync-ProjectDatabase{ # Send update to project $db = Sync-Project -Database $db - Save-ProjectDatabase -Database $db -Owner $Owner -ProjectNumber $ProjectNumber + Save-ProjectDatabase -Database $db if (Test-ProjectDatabaseStaged -Owner $Owner -ProjectNumber $ProjectNumber) { "Still pending staged values" | Write-MyError diff --git a/private/projectDatabase/project_database_call.ps1 b/private/projectDatabase/project_database_call.ps1 index c136a11..74ee7c3 100644 --- a/private/projectDatabase/project_database_call.ps1 +++ b/private/projectDatabase/project_database_call.ps1 @@ -94,6 +94,7 @@ function GetItemInfo { # TODO: Situation where we are updating an item that is not cached and therefore type is not known if (-not $item.type) { # Single fetch item from api + # Avoid caching as we are beeing called from a Database modification context $item = Get-ProjectItemDirect -ItemId $ItemId } diff --git a/public/graphql/addItemToProject.mutant b/public/graphql/addItemToProject.mutant index e03875c..efcb8f8 100644 --- a/public/graphql/addItemToProject.mutant +++ b/public/graphql/addItemToProject.mutant @@ -1,91 +1,98 @@ -mutation AddItem($input:AddProjectV2ItemByIdInput!){addProjectV2ItemById(input:$input){item{content{__typename,... on DraftIssue{id,body,title - },... on PullRequest{body,title,number,url,repository{nameWithOwner +mutation AddItem($input:AddProjectV2ItemByIdInput!){addProjectV2ItemById(input:$input){ + item{ +id, type, + project{ id, url}, + content{__typename, + ... on DraftIssue {id,body,title,updatedAt,createdAt}, + ... on PullRequest{id,body,title,updatedAt,createdAt,number,url,state,repository{nameWithOwner}}, + ... on Issue{id,body,title,updatedAt,createdAt,number,url,state,repository{nameWithOwner} + }, + } + 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 Issue{body,title,number,url,repository{nameWithOwner + } + },... 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 + } } } - },id,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 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 ProjectV2ItemFieldIterationValue{title,startDate,duration,field{__typename,... on ProjectV2Field{id,name,dataType - },... on ProjectV2IterationField{id,name,dataType - },... on ProjectV2SingleSelectField{id,name,dataType,options{id,name - } - } - },iterationId - },... 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 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 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 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 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 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 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 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 } - },... 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 - } - } + } + },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/getProjectV2Item.query b/public/graphql/getProjectV2Item.query index f9a3542..d9a99c2 100644 --- a/public/graphql/getProjectV2Item.query +++ b/public/graphql/getProjectV2Item.query @@ -2,6 +2,7 @@ query($itemId: ID!) { node(id: $itemId) { ... on ProjectV2Item { id, type, + project{ id, url}, content{__typename, ... on DraftIssue {id,body,title,updatedAt,createdAt}, ... on PullRequest{id,body,title,updatedAt,createdAt,number,url,state,repository{nameWithOwner}}, diff --git a/public/graphql/orgprojectwithfieldsAndItems.query b/public/graphql/orgprojectwithfieldsAndItems.query index 0ad9e64..07b67d4 100644 --- a/public/graphql/orgprojectwithfieldsAndItems.query +++ b/public/graphql/orgprojectwithfieldsAndItems.query @@ -1,10 +1,14 @@ query OrgProjectWithFields($afterFields:String$afterItems:String$firstFields:Int!$firstItems:Int!$login:String!$number:Int!){organization(login: $login){projectV2(number: $number){number,url,shortDescription,public,closed,title,id,readme,items(first: $firstItems, after: $afterItems){pageInfo{endCursor,hasNextPage - },totalCount,nodes{content{__typename + },totalCount, + nodes{ + project{ id, number, title, url}, + content{__typename ,... on DraftIssue {id,body,title,updatedAt,createdAt },... on PullRequest{id,body,title,updatedAt,createdAt,number,url,state,repository{nameWithOwner} },... on Issue{id,body,title,updatedAt,createdAt,number,url,state,repository{nameWithOwner} } - },id,fieldValues(first: 100){nodes{__typename,... on ProjectV2ItemFieldDateValue{date,field{__typename,... on ProjectV2Field{id,name,dataType + },id, + 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 } diff --git a/public/project_item.ps1 b/public/project_item.ps1 index 9b3c1e5..50eef53 100644 --- a/public/project_item.ps1 +++ b/public/project_item.ps1 @@ -19,25 +19,43 @@ function Get-ProjectItem{ [Parameter()][switch]$Force ) - ($Owner,$ProjectNumber) = Get-OwnerAndProjectNumber -Owner $Owner -ProjectNumber $ProjectNumber - if([string]::IsNullOrWhiteSpace($owner) -or [string]::IsNullOrWhiteSpace($ProjectNumber)){ "Owner and ProjectNumber are required" | Write-MyError; return $null} - - - # Get Item from Project database - $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber -Force:$Force + begin{ + ($Owner,$ProjectNumber) = Get-OwnerAndProjectNumber -Owner $Owner -ProjectNumber $ProjectNumber + if([string]::IsNullOrWhiteSpace($owner) -or [string]::IsNullOrWhiteSpace($ProjectNumber)){ "Owner and ProjectNumber are required" | Write-MyError; return $null} + + + # Get Item from Project database + $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber -Force:$Force - if($db){ - $item = Get-Item $db $itemId + # Durty flag + $durty= $false } - # If item not found on cache get it directly - if($null -eq $item){ - $item = Get-ProjectItemDirect -ItemId $ItemId + process{ + if($db){ + $item = Get-Item $db $itemId + } + if($null -eq $item){ + "Item [$ItemId] not found in cache, fetching from API" | Write-Verbose + + # Get direct. No cache as we are in a database modification context + $item = Get-ProjectItemDirect -ItemId $ItemId + + # Add to database + Set-Item $db $item + $durty = $true + } - Set-ProjectItem -Owner $Owner -ProjectNumber $ProjectNumber -Item $item + return $item + } + + end{ + if($durty){ + "Saving durty database" | Write-Verbose + Save-ProjectDatabase -Database $db + } } - return $item } Export-ModuleMember -Function Get-ProjectItem function Set-ProjectItem{ @@ -54,7 +72,32 @@ function Set-ProjectItem{ Set-Item $db $item - Save-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -Database $db + Save-ProjectDatabase -Database $db +} + +function Remove-ProjectItem{ + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline,Position = 0)][string]$ItemId, + [Parameter()][string]$Owner, + [Parameter()][string]$ProjectNumber + ) + + begin{ + ($Owner,$ProjectNumber) = Get-OwnerAndProjectNumber -Owner $Owner -ProjectNumber $ProjectNumber + if([string]::IsNullOrWhiteSpace($owner) -or [string]::IsNullOrWhiteSpace($ProjectNumber)){ "Owner and ProjectNumber are required" | Write-MyError; return $null} + + $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber + } + + process{ + Remove-Item $db $itemId + } + + end{ + Save-ProjectDatabase -Database $db + } + } function Find-ProjectItem{ @@ -163,7 +206,7 @@ function Edit-ProjectItem{ Save-ItemFieldValue $db $itemId $FieldName $Value # Commit changes to the database - Save-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -Database $db + Save-ProjectDatabase -Database $db } Export-ModuleMember -Function Edit-ProjectItem @@ -173,7 +216,8 @@ function Add-ProjectItemDirect{ param( [Parameter(ValueFromPipeline,Position = 0)][string]$Url, [Parameter()][string]$Owner, - [Parameter()][string]$ProjectNumber + [Parameter()][string]$ProjectNumber, + [Parameter()][switch]$NoCache ) process{ @@ -182,7 +226,8 @@ function Add-ProjectItemDirect{ if([string]::IsNullOrWhiteSpace($owner) -or [string]::IsNullOrWhiteSpace($ProjectNumber)){ "Owner and ProjectNumber are required" | Write-MyError; return $null} # Get project id - $projectId = Get-ProjectId -Owner $Owner -ProjectNumber $ProjectNumber + $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber + $projectId = $db.ProjectId if(-not $projectId){ "Project ID not found for Owner [$Owner] and ProjectNumber [$ProjectNumber]" | Write-MyError return $null @@ -204,9 +249,24 @@ function Add-ProjectItemDirect{ return $null } - if($response.data.addProjectV2ItemById.item.id) + $item = $response.data.addProjectV2ItemById.item + + if($item) { - $ret = $response.data.addProjectV2ItemById.item.id + $ret = $item.id + + if(! $NoCache){ + "Adding item [$ret] to cache" | Write-Verbose + + $item = $item | Convert-NodeItemToHash + + Set-ProjectItem -Owner $Owner -ProjectNumber $ProjectNumber -Item $item + + Save-ProjectDatabase -Database $db + + } + + "Setting global variable ItemId to [$ret]" | Write-Verbose $global:ItemId = $ret return $ret @@ -224,7 +284,8 @@ function Remove-ProjectItemDirect{ param( [Parameter()][string]$Owner, [Parameter()][string]$ProjectNumber, - [Parameter(Mandatory, ValueFromPipeline, Position = 0)][string]$ItemId + [Parameter(Mandatory, ValueFromPipeline, Position = 0)][string]$ItemId, + [Parameter()][switch]$NoCache ) begin{ @@ -232,9 +293,11 @@ function Remove-ProjectItemDirect{ if([string]::IsNullOrWhiteSpace($owner) -or [string]::IsNullOrWhiteSpace($ProjectNumber)){ "Owner and ProjectNumber are required" | Write-MyError; return $null} # Get project id - $projectId = Get-ProjectId -Owner $Owner -ProjectNumber $ProjectNumber - if(-not $projectId){ - "Project ID not found for Owner [$Owner] and ProjectNumber [$ProjectNumber]" | Write-MyError + $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber + if($db){ + $projectId = $db.ProjectId + } else { + throw "Project not found for Owner [$Owner] and ProjectNumber [$ProjectNumber]" } } @@ -259,7 +322,16 @@ function Remove-ProjectItemDirect{ return $null } - return $response.data.deleteProjectV2Item.deletedItemId + $ret = $response.data.deleteProjectV2Item.deletedItemId + + if(! $NoCache){ + # Remove item from cache + "Removing item [$ItemId] from cache" | Write-Verbose + Remove-Item $db $ItemId + Save-ProjectDatabase $db + } + + return $ret } } Export-ModuleMember -Function Remove-ProjectItemDirect diff --git a/public/project_item_staged.ps1 b/public/project_item_staged.ps1 index 42710d6..0aba28b 100644 --- a/public/project_item_staged.ps1 +++ b/public/project_item_staged.ps1 @@ -107,7 +107,7 @@ function Reset-ProjectItemStaged{ $db = Get-Project $Owner $ProjectNumber $db.Staged = $null - Save-ProjectDatabase -Database $db -Owner $Owner -ProjectNumber $ProjectNumber + Save-ProjectDatabase -Database $db } Export-ModuleMember -Function Reset-ProjectItemStaged