From 7cd38ee0257f9b94c144ecb4ca0b16f81b980ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Sat, 7 Mar 2026 08:15:03 +0100 Subject: [PATCH 01/27] fix(Test-MyDebug): correct trace condition logic --- include/MyWrite.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/MyWrite.ps1 b/include/MyWrite.ps1 index 5226eab..1b04866 100644 --- a/include/MyWrite.ps1 +++ b/include/MyWrite.ps1 @@ -190,7 +190,7 @@ function Test-MyDebug { $flag = $flag.ToLower() $section = $section.ToLower() - $trace = ($flag -like '*all*') -or ( $section -like "*$flag*") + $trace = ($flag -like '*all*') -or ( $flag -like "*$section*") return $trace } From 4f597839f41478f46e1a703b785068de29922842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Sat, 7 Mar 2026 08:22:56 +0100 Subject: [PATCH 02/27] feat(project-item): add assignee display in Show-ProjectItem function --- public/items/project_item_show.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/items/project_item_show.ps1 b/public/items/project_item_show.ps1 index 3f175fa..d193713 100644 --- a/public/items/project_item_show.ps1 +++ b/public/items/project_item_show.ps1 @@ -65,8 +65,11 @@ function Show-ProjectItem{ # URL $item.url | write -Color White - addJumpLine -message "End Url" + + # Assignee + $item.Assignees | write -color DarkGreen + addJumpLine -message "End Assignees" # Fields by line if($FieldsToShow){ From c0cb651de56fb1d4d41128a0b675d4eb334a0824 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 11:01:40 +0100 Subject: [PATCH 03/27] refactor(Invoke-GetItem): simplify GraphQL request by removing unnecessary token handling --- public/driver/issue/Invoke-GetItem.ps1 | 33 +------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/public/driver/issue/Invoke-GetItem.ps1 b/public/driver/issue/Invoke-GetItem.ps1 index fef6600..646d185 100644 --- a/public/driver/issue/Invoke-GetItem.ps1 +++ b/public/driver/issue/Invoke-GetItem.ps1 @@ -4,45 +4,14 @@ function Invoke-GetItem { [Parameter(Mandatory = $true)][string]$ItemId ) - # 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 - $query = Get-GraphQLString "getProjectV2Item.query" - - # Define the headers for the request - $headers = @{ - "Authorization" = "Bearer $token" - "Content-Type" = "application/json" - } - # Define the variables for the request $variables = @{ itemId = $ItemId } - # Define the body for the request - $body = @{ - query= $query - 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 { - "RESPONSE Type[$($_.type)] $($_.message)" | Write-MyError - } - return $null - } + $response = Invoke-GraphQL -Query $query -Variables $variables - # Return the field names return $response } Export-ModuleMember -Function Invoke-GetItem \ No newline at end of file From 6f9e03222a5622636c5d88fa708b00cfdfbecb13 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 11:04:53 +0100 Subject: [PATCH 04/27] fix(Resolve-ProjectItem): add sanity check for item database consistency --- .../projectDatabase/project_database_Item_Resolve.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/private/projectDatabase/project_database_Item_Resolve.ps1 b/private/projectDatabase/project_database_Item_Resolve.ps1 index e2b0a65..49e05ad 100644 --- a/private/projectDatabase/project_database_Item_Resolve.ps1 +++ b/private/projectDatabase/project_database_Item_Resolve.ps1 @@ -22,6 +22,16 @@ function Resolve-ProjectItem { return $null, $false } + # Add Sanity check for item + # Ensure that we hare retrieved an item for the correct database + # As Get-ProjectItemDirect retrives a node based on item we do not know + # if this itemid belongs to this project or not until here. + # + # Find bugs early when calling to resolve ItemId of wrong projects. + if($Database.ProjectId -ne $Item.projectId){ + Wait-Debugger + } + # Add to database Set-Item $Database $item From 4963cab597f5731b944b6c761c189a2a258f00ad Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 11:14:53 +0100 Subject: [PATCH 05/27] feat(Update-ProjectItemsWithIntegration): add CommitMaxItems parameter for controlled updates --- .../update-ProjectItemsWithIntegration.ps1 | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/public/integrations/update-ProjectItemsWithIntegration.ps1 b/public/integrations/update-ProjectItemsWithIntegration.ps1 index 0db7187..80831ab 100644 --- a/public/integrations/update-ProjectItemsWithIntegration.ps1 +++ b/public/integrations/update-ProjectItemsWithIntegration.ps1 @@ -19,15 +19,25 @@ function Update-ProjectItemsWithIntegration{ [Parameter(Mandatory)][string]$IntegrationField, [Parameter(Mandatory)][string]$IntegrationCommand, [Parameter()] [string]$Slug, - [Parameter()] [switch]$IncludeDoneItems + [Parameter()] [switch]$IncludeDoneItems, + [Parameter()] [int32]$CommitMaxItems = -1 ) ($Owner,$ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber # Sync project if needed $null = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber -Force:$Force + $params = @{ + Owner = $Owner + ProjectNumber = $ProjectNumber + IntegrationField = $IntegrationField + IntegrationCommand = $IntegrationCommand + Slug = $Slug + IncludeDoneItems = $IncludeDoneItems + CommitMaxItems = $CommitMaxItems + } # Call the injection type function - $ret = Invoke-ProjectInjectionWithIntegration -Owner $Owner -ProjectNumber $ProjectNumber -IntegrationField $IntegrationField -IntegrationCommand $IntegrationCommand -Slug $Slug -IncludeDoneItems:$IncludeDoneItems + $ret = Invoke-ProjectInjectionWithIntegration @params return $ret @@ -41,23 +51,26 @@ function Invoke-ProjectInjectionWithIntegration{ [Parameter(Mandatory)][string]$IntegrationField, [Parameter(Mandatory)][string]$IntegrationCommand, [Parameter()] [string]$Slug, - [Parameter()] [switch]$IncludeDoneItems + [Parameter()] [switch]$IncludeDoneItems, + [Parameter()] [int32]$CommitMaxItems = -1 ) ($Owner,$ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber + $updateCount = 0 + $items = Get-ProjectItems -Owner $Owner -ProjectNumber $ProjectNumber -IncludeDone:$IncludeDoneItems foreach($item in $items){ # Skip if the item does not have the integration field if(-not $item.$IntegrationField){ - "Item $($item.id) does not have the integration field $IntegrationField, skipping" | Write-MyVerbose + "[Update-ProjectItemsWithIntegration] $($item.id) does not have the integration field $IntegrationField, skipping" | Write-MyDebug -section "Integration" continue } try { - "Calling integration [ $IntegrationCommand $($item.$IntegrationField)]" | Write-MyHost + "Calling integration - $IntegrationCommand $($item.$IntegrationField)]" | Write-MyHost $command = $IntegrationCommand + " " + '"{key}"' $command = $command -replace '{key}', $item.$IntegrationField $values = Invoke-MyCommand -Command $command @@ -67,12 +80,14 @@ function Invoke-ProjectInjectionWithIntegration{ } # Call the ingetration Command with the integration field value as parameter + Write-MyDebug "[Update-ProjectItemsWithIntegration] Values" -Section "Integration" -Object $values + # Check if Values is empty or null if($null -eq $values -or $values.Count -eq 0){ "No values returned from the integration command for $($item.id)" | Write-MyVerbose continue } - + # Edit item with the value $param = @{ Owner = $owner @@ -80,8 +95,20 @@ function Invoke-ProjectInjectionWithIntegration{ ItemId = $item.id Values = $values FieldSlug = $Slug + Fields = $Fields } - + + Write-MyDebug "[Update-ProjectItemsWithIntegration] >> Editing with values" -Section "Integration" Edit-ProjectItemWithValues @param + Write-MyDebug "[Update-ProjectItemsWithIntegration] << Editing with values" -Section "Integration" + + # Commit + $updateCount++ + if(($CommitMaxItems -ne -1) -and $updateCount -ge $CommitMaxItems){ + Sync-ProjectItemStagedAsync -Owner $Owner -ProjectNumber $ProjectNumber + $updateCount = 0 + } else { + "[Update-ProjectItemsWithIntegration] Count $updateCount / $CommitMaxItems" | Write-MyDebug -section "Integration" + } } } # Do not export this function to avoid conflicts with Update-ProjectItemsWithIntegration \ No newline at end of file From 3b884b7bd79d656552de6ff1d88774e9de2930b2 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 11:16:12 +0100 Subject: [PATCH 06/27] wip - try improve performance caching fields --- public/fields/project_fields_list.ps1 | 50 ++++++++++++++++--- .../Edit-ProjectItemWithValues.ps1 | 15 ++++-- .../update-ProjectItemsWithIntegration.ps1 | 3 ++ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/public/fields/project_fields_list.ps1 b/public/fields/project_fields_list.ps1 index ec3f67f..ba16601 100644 --- a/public/fields/project_fields_list.ps1 +++ b/public/fields/project_fields_list.ps1 @@ -30,6 +30,7 @@ Gets all fields from project 1 in the octocat repository. Get-ProjectFields -Owner "octocat" -ProjectNumber "1" -Name "status" Gets all fields from project 1 that contain "status" in their name. #> + function Get-ProjectFields{ [CmdletBinding()] [OutputType([string[]])] @@ -42,16 +43,23 @@ function Get-ProjectFields{ ($Owner,$ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber - $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber -Force:$Force -SkipItems + $fieldList = getFieldsCache -Owner $Owner -ProjectNumber $ProjectNumber - # Check if $db is null - if($null -eq $db){ - "Project not found. Check owner and projectnumber" | Write-MyError - return $null - } + if(-Not $fieldList -or $Force){ + + $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber -Force:$Force -SkipItems + + # Check if $db is null + if($null -eq $db){ + "Project not found. Check owner and projectnumber" | Write-MyError + return $null + } + + # if $db is null it rill return null + $fieldList = $db.fields.Values - # if $db is null it rill return null - $fieldList = $db.fields.Values + setFieldsCache -Owner $Owner -ProjectNumber $ProjectNumber -FieldList $fieldList + } # if name if($Name){ @@ -99,4 +107,30 @@ function ConvertToFieldDisplay{ return $ret } +} + +$script:fieldsCache = @{} + +function getFieldsCache{ + param( + [Parameter(Mandatory,Position = 0)][string]$Owner, + [Parameter(Mandatory,Position = 1)][string]$ProjectNumber + ) + + $key = "$Owner-$ProjectNumber" + $ret = $script:fieldsCache[$key] + + return $ret + +} + +function setFieldsCache{ + param( + [Parameter(Mandatory,Position = 0)][string]$Owner, + [Parameter(Mandatory,Position = 1)][string]$ProjectNumber, + [Parameter(Mandatory,Position = 2)][object]$FieldList + ) + + $key = "$Owner-$ProjectNumber" + $script:fieldsCache[$key] = $FieldList } \ No newline at end of file diff --git a/public/integrations/Edit-ProjectItemWithValues.ps1 b/public/integrations/Edit-ProjectItemWithValues.ps1 index 761d7e8..aeb56bc 100644 --- a/public/integrations/Edit-ProjectItemWithValues.ps1 +++ b/public/integrations/Edit-ProjectItemWithValues.ps1 @@ -12,10 +12,16 @@ function Edit-ProjectItemWithValues { [Parameter(Position = 1)][string]$ProjectNumber, [Parameter(Mandatory)][string]$ItemId, [Parameter(Mandatory)][hashtable]$Values, - [Parameter()][string]$FieldSlug + [Parameter()][string]$FieldSlug, + [Parameter()][object]$Fields ) - $fields = Get-ProjectFields -Owner $owner -ProjectNumber $projectNumber + if($null -eq $Fields){ + "[Edit-ProjectItemWithValues] Retriving fields" | Write-MyDebug -Section "EditProjectItem" + $Fields = Get-ProjectFields -Owner $owner -ProjectNumber $projectNumber + } else { + "[Edit-ProjectItemWithValues] Using provided fields" | Write-MyDebug -Section "EditProjectItem" + } # forech key in data do foreach ($key in $Values.Keys) { @@ -24,10 +30,11 @@ function Edit-ProjectItemWithValues { # Check if field exists $field = $fields | Where-Object { $_.name -eq $fieldName } if ($null -eq $field) { - "Field $fieldName not found" | Write-MyVerbose + "[Edit-ProjectItemWithValues]Field $fieldName not found" | Write-MyDebug -Section "EditProjectItem" continue } - + + "[Edit-ProjectItemWithValues]Editing field $fieldName with value $($Values[$key])" | Write-MyDebug -Section "EditProjectItem" Edit-ProjectItem -Owner $owner -ProjectNumber $projectNumber -ItemId $ItemId -FieldName $fieldName -Value $Values[$key] } diff --git a/public/integrations/update-ProjectItemsWithIntegration.ps1 b/public/integrations/update-ProjectItemsWithIntegration.ps1 index 80831ab..9818884 100644 --- a/public/integrations/update-ProjectItemsWithIntegration.ps1 +++ b/public/integrations/update-ProjectItemsWithIntegration.ps1 @@ -61,6 +61,9 @@ function Invoke-ProjectInjectionWithIntegration{ $items = Get-ProjectItems -Owner $Owner -ProjectNumber $ProjectNumber -IncludeDone:$IncludeDoneItems + $Fields = Get-ProjectFields -Owner $owner -ProjectNumber $projectNumber + + foreach($item in $items){ # Skip if the item does not have the integration field From a0920b05e7f85838b8723f4974c472f3b72ce9a8 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 13:07:00 +0100 Subject: [PATCH 07/27] feat(items): add normalization for project item titles --- ...TI_lADOAlIw4c4BCe3Vzgeio4o-Normalized.json | 185 ++++++++++++++++++ Test/public/items/edit_project_item.test.ps1 | 51 +++++ public/items/edit_project_item.ps1 | 61 ++++-- public/items/project_item.ps1 | 6 +- 4 files changed, 289 insertions(+), 14 deletions(-) create mode 100644 Test/private/mocks/invoke-getitem-PVTI_lADOAlIw4c4BCe3Vzgeio4o-Normalized.json diff --git a/Test/private/mocks/invoke-getitem-PVTI_lADOAlIw4c4BCe3Vzgeio4o-Normalized.json b/Test/private/mocks/invoke-getitem-PVTI_lADOAlIw4c4BCe3Vzgeio4o-Normalized.json new file mode 100644 index 0000000..f25a1af --- /dev/null +++ b/Test/private/mocks/invoke-getitem-PVTI_lADOAlIw4c4BCe3Vzgeio4o-Normalized.json @@ -0,0 +1,185 @@ +{ + "data": { + "node": { + "id": "PVTI_lADOAlIw4c4BCe3Vzgeio4o", + "type": "ISSUE", + "fullDatabaseId": "128099210", + "project": { + "id": "PVT_kwDOAlIw4c4BCe3V", + "url": "https://github.com/orgs/octodemo/projects/700" + }, + "content": { + "__typename": "Issue", + "id": "I_kwDOPrRnkc7KkwSq", + "body": "Body of issue for development", + "title": "[RULaSG-DeV-1] Issue [RULASG-DEV-1] for [value between] development", + "updatedAt": "2025-10-15T21:30:02Z", + "createdAt": "2025-09-09T14:01:17Z", + "number": 26, + "url": "https://github.com/octodemo/rulasg-dev-1/issues/26", + "state": "OPEN", + "repository": { + "name": "rulasg-dev-1", + "owner": { + "login": "octodemo" + } + }, + "comments": { + "totalCount": 3, + "nodes": [ + { + "createdAt": "2025-09-23T17:51:06Z", + "updatedAt": "2025-10-15T21:29:45Z", + "url": "https://github.com/octodemo/rulasg-dev-1/issues/26#issuecomment-3324995787", + "body": "Sample comment 1", + "fullDatabaseId": "3324995787", + "author": { + "login": "rulasg" + } + }, + { + "createdAt": "2025-09-24T08:29:13Z", + "updatedAt": "2025-10-15T21:29:55Z", + "url": "https://github.com/octodemo/rulasg-dev-1/issues/26#issuecomment-3327194303", + "body": "Sample comment 2", + "fullDatabaseId": "3327194303", + "author": { + "login": "rulasg" + } + }, + { + "createdAt": "2025-09-30T05:42:49Z", + "updatedAt": "2025-10-15T21:30:02Z", + "url": "https://github.com/octodemo/rulasg-dev-1/issues/26#issuecomment-3350059109", + "body": "Sample comment 3", + "fullDatabaseId": "3350059109", + "author": { + "login": "rulasg" + } + } + ] + } + }, + "fieldValues": { + "nodes": [ + { + "__typename": "ProjectV2ItemFieldRepositoryValue", + "repository": { + "url": "https://github.com/octodemo/rulasg-dev-1" + }, + "field": { + "__typename": "ProjectV2Field", + "id": "PVTF_lADOAlIw4c4BCe3Vzg0rg-k", + "name": "Repository", + "dataType": "REPOSITORY" + } + }, + { + "__typename": "ProjectV2ItemFieldMilestoneValue", + "milestone": { + "title": "Milestone 3: Quality and Deployment", + "description": "Testing, documentation, and deployment pipeline setup", + "dueOn": "2025-10-18T00:00:00Z" + }, + "field": { + "__typename": "ProjectV2Field", + "id": "PVTF_lADOAlIw4c4BCe3Vzg0rg-g", + "name": "Milestone", + "dataType": "MILESTONE" + } + }, + { + "__typename": "ProjectV2ItemFieldTextValue", + "text": "[RULaSG-DeV-1] Issue [RULASG-DEV-1] for [value between] development", + "field": { + "__typename": "ProjectV2Field", + "id": "PVTF_lADOAlIw4c4BCe3Vzg0rg-M", + "name": "Title", + "dataType": "TITLE" + } + }, + { + "__typename": "ProjectV2ItemFieldSingleSelectValue", + "name": "In Progress", + "field": { + "__typename": "ProjectV2SingleSelectField", + "id": "PVTSSF_lADOAlIw4c4BCe3Vzg0rg-U", + "name": "Status", + "dataType": "SINGLE_SELECT", + "options": [ + { + "id": "f75ad846", + "name": "Todo" + }, + { + "id": "47fc9ee4", + "name": "In Progress" + }, + { + "id": "98236657", + "name": "Done" + } + ] + } + }, + { + "__typename": "ProjectV2ItemFieldNumberValue", + "number": 333.0, + "field": { + "__typename": "ProjectV2Field", + "id": "PVTF_lADOAlIw4c4BCe3Vzg0rhjU", + "name": "field-number", + "dataType": "NUMBER" + } + }, + { + "__typename": "ProjectV2ItemFieldIterationValue", + "title": "field-iteration 3", + "startDate": "2025-10-05", + "duration": 14, + "field": { + "__typename": "ProjectV2IterationField", + "id": "PVTIF_lADOAlIw4c4BCe3Vzg0rhqQ", + "name": "field-iteration", + "dataType": "ITERATION" + } + }, + { + "__typename": "ProjectV2ItemFieldTextValue", + "text": "text3", + "field": { + "__typename": "ProjectV2Field", + "id": "PVTF_lADOAlIw4c4BCe3Vzg0rhko", + "name": "field-text", + "dataType": "TEXT" + } + }, + { + "__typename": "ProjectV2ItemFieldSingleSelectValue", + "name": "option-3", + "field": { + "__typename": "ProjectV2SingleSelectField", + "id": "PVTSSF_lADOAlIw4c4BCe3Vzg0rhno", + "name": "field-singleselect", + "dataType": "SINGLE_SELECT", + "options": [ + { + "id": "7d96a925", + "name": "option-1" + }, + { + "id": "cd02c585", + "name": "option-2" + }, + { + "id": "6b66c93a", + "name": "option-3" + } + ] + } + } + ] + } + } + } +} diff --git a/Test/public/items/edit_project_item.test.ps1 b/Test/public/items/edit_project_item.test.ps1 index 99815d0..73fc2f5 100644 --- a/Test/public/items/edit_project_item.test.ps1 +++ b/Test/public/items/edit_project_item.test.ps1 @@ -263,6 +263,57 @@ function Test_EditProjectItems_NormalizeTitle{ Assert-AreEqual -Expected $newTitle -Presented $result.$itemId.title.Value } +function Test_EditProjectItems_NormalizeTitle_AlreadyNormalized{ + + # Arrange + $p = Get-Mock_Project_700 ; $Owner = $p.owner ; $ProjectNumber = $p.number + MockCall_GetProject $p -skipItems + $i= $p.issue ; $itemId = $i.id + + MockCall_GetProject $p -SkipItems + + $newTitle = "[rulasg-dev-1] Issue [rulasg-dev-1] for [value between] development" + + # Mock the direct call for item + MockCallJson -Command "Invoke-GetItem -itemid $itemId" -FileName "invoke-getitem-$itemId-Normalized.json" + # Act + Edit-ProjectItem -Owner $owner -ProjectNumber $projectNumber -ItemId $itemId -NormalizeTitle + + # Assert + $result = Get-ProjectItemStaged -Owner $owner -ProjectNumber $projectNumber + + Assert-Count -Expected 1 -Presented $result.$itemId.Keys + Assert-AreEqual -Expected $newTitle -Presented $result.$itemId.title.Value +} + +function Test_NormalizedTitle{ + + Invoke-PrivateContext{ + $cases = @( + @{item = @{Title= "Test"; repositoryName = "rulasg-dev-1"}; expected = "[rulasg-dev-1] Test"} + @{item = @{Title= "[rulasg-dev-1] Test"; repositoryName = "rulasg-dev-1"}; expected = "[rulasg-dev-1] Test"} + + @{item = @{Title= "[BBVA] Test"; repositoryName = "bBva"}; expected = "[bBva] Test"} + + @{item = @{Title= "Test [rulasg-dev-1]"; repositoryName = "rulasg-dev-1"}; expected = "Test [rulasg-dev-1]"} + + @{item = @{Title= "Test [rulasg-dEv-1]"; repositoryName = "rulasg-dev-1"}; expected = "Test [rulasg-dev-1]"} + + @{item = @{Title= "[RULaSG-DeV-1] Test"; repositoryName = "rulasg-dev-1"}; expected = "[rulasg-dev-1] Test"} + @{item = @{Title= "[RULaSG-DeV-1] Test [value between] development"; repositoryName = "rulasg-dev-1"}; ` + expected = "[rulasg-dev-1] Test [value between] development"} + @{item = @{Title= "[RULaSG-DeV-1] Issue [RULASG-DEV-1] for [value between] development"; repositoryName = "rulasg-dev-1"}; ` + expected = "[rulasg-dev-1] Issue [rulasg-dev-1] for [value between] development"} + ) + + foreach($case in $cases){ + $result = Get-NormalizedTitle -Item $case.item + Assert-AreEqual -Expected $case.expected -Presented $result + } + + } +} + function Test_EditProjectItems_OpenInBrowser{ # Arrange diff --git a/public/items/edit_project_item.ps1 b/public/items/edit_project_item.ps1 index 7b6988a..4fed57a 100644 --- a/public/items/edit_project_item.ps1 +++ b/public/items/edit_project_item.ps1 @@ -80,16 +80,6 @@ function Edit-ProjectItem { $AddComment = Get-LongText -Text $AddComment } - # NormalizeTitle - if ($NormalizeTitle) { - if([string]::IsNullOrWhiteSpace($Title)){ - $Title = "[{{RepositoryName}}] {{Title}}" - } else { - # Editing Title at the same time - $Title = "[{{RepositoryName}}] $Title" - } - } - # Default if($DefaultValues){ Write-MyWarning "No Default values are currently setup. Please use other parameters to set values or update the DefaultValues parameter with default values" @@ -174,6 +164,8 @@ function Edit-ProjectItem { } } + # With Item + if ($OpenInBrowser) { $item = Get-ProjectItem -ItemId $Id -Owner $Owner -ProjectNumber $ProjectNumber if ($null -ne $item) { @@ -182,6 +174,14 @@ function Edit-ProjectItem { Write-Warning "Item not found. Cannot open in browser." } } + + # NormalizeTitle + if ($NormalizeTitle) { + $item = Get-ProjectItem -ItemId $Id -Owner $Owner -ProjectNumber $ProjectNumber + $params.fieldname = "Title" + $params.value = Get-NormalizedTitle -Item $item + edit $params + } } end { @@ -243,4 +243,43 @@ function Edit-ProjectItemValue { Save-ProjectDatabaseSafe -Database $db } -} \ No newline at end of file +} + +# function Get-NormalizedTitle { +# param( +# [Parameter(Mandatory)][hashtable]$Item +# ) +# $title = $Item.Title +# $header = "[{0}]" -f $Item.RepositoryName + +# if($title.ToLower().Contains($header.ToLower())){ +# "The title is already normalized" | Write-MyDebug -section "Edit-ProjectItem" +# # update title with proper repository name case +# $newTitle = $title -replace "^\[[^\]]*\]\s*", "$header" +# return $newTitle +# } else { +# return "$header $title" +# } +# } + +function Get-NormalizedTitle { + param( + [Parameter(Mandatory)][hashtable]$Item + ) + $title = $Item.Title + $header = "[{0}]" -f $Item.RepositoryName + $repoEscaped = [regex]::Escape($Item.RepositoryName) + + "Original title: $title" | Write-MyDebug -section "Edit-ProjectItem NormalizedTitle" + + if($title -imatch "\[$repoEscaped\]" -or $title -imatch "^\s*\[?$repoEscaped\]\s*"){ + # Ttile contains repo name. Normalize it to proper case and formatting. This will cover the following scenarios: + $ret = ($title -ireplace "^\s*\[?$repoEscaped\]\s*", "$header ") -ireplace "\[$repoEscaped\]", $header + + } else { + $ret = "$header $title" + } + + "Normalized title: $ret" | Write-MyDebug -section "Edit-ProjectItem NormalizedTitle" + return $ret +} \ No newline at end of file diff --git a/public/items/project_item.ps1 b/public/items/project_item.ps1 index a8aaa4f..cd11a01 100644 --- a/public/items/project_item.ps1 +++ b/public/items/project_item.ps1 @@ -733,15 +733,15 @@ function Test-WhereExactField { function AreEqual { param( - [object]$Object1, - [object]$Object2 + [string]$Object1, + [string]$Object2 ) $Object1 = [string]::IsNullOrEmpty($Object1) ? $null : $Object1 $Object2 = [string]::IsNullOrEmpty($Object2) ? $null : $Object2 # Check if the objects are equal - $ret = $Object1 -eq $Object2 + $ret = $Object1 -ceq $Object2 return $ret } From 80bcef83078adca683aab113b5c54ce0b516a8b1 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 21:47:50 +0100 Subject: [PATCH 08/27] refactor(Update-ProjectItemsWithIntegration): remove unnecessary project sync call --- public/integrations/update-ProjectItemsWithIntegration.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/integrations/update-ProjectItemsWithIntegration.ps1 b/public/integrations/update-ProjectItemsWithIntegration.ps1 index 9818884..2c3c1f7 100644 --- a/public/integrations/update-ProjectItemsWithIntegration.ps1 +++ b/public/integrations/update-ProjectItemsWithIntegration.ps1 @@ -24,9 +24,6 @@ function Update-ProjectItemsWithIntegration{ ) ($Owner,$ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber - # Sync project if needed - $null = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber -Force:$Force - $params = @{ Owner = $Owner ProjectNumber = $ProjectNumber From e44f80ce7e7c2802d92522d02c8dc62878ebffb5 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 22:04:54 +0100 Subject: [PATCH 09/27] feat(database): add Get-DatabaseKey function for key generation --- private/database/databaseV2.ps1 | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/private/database/databaseV2.ps1 b/private/database/databaseV2.ps1 index f01301e..b4b6252 100644 --- a/private/database/databaseV2.ps1 +++ b/private/database/databaseV2.ps1 @@ -95,6 +95,29 @@ function Save-Database { $Database | ConvertTo-Json -Depth 10 | Set-Content $path } +function Get-DatabaseKey{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Owner, + [Parameter(Position = 1)][int]$ProjectNumber, + [Parameter(Position = 2)][string] $Category + ) + + if([string]::IsNullOrWhiteSpace($Owner)){ + throw "Owner is null or empty" + } + if($ProjectNumber -le 0){ + throw "ProjectNumber is null or not a positive integer" + } + if([string]::IsNullOrWhiteSpace($Category)){ + throw "Category is null or empty" + } + + $ret = "db-{0}-{1}-{2}" -f $Owner, $ProjectNumber, $Category + + return $ret +} + function Get-DatabaseFile { [CmdletBinding()] param( From 5b86cd097ae69cbb6e9391d6f2dd05d7d91aaa6e Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 22:05:10 +0100 Subject: [PATCH 10/27] refactor(database): replace Get-DatabaseKey with Get-ProjectDatabaseKey for consistency --- Test/private/MockCall_Project700.ps1 | 2 +- private/projectDatabase/project_database.ps1 | 23 ++++++-------------- public/projectCache/projectCache.ps1 | 2 +- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/Test/private/MockCall_Project700.ps1 b/Test/private/MockCall_Project700.ps1 index 62079dd..8be1a0e 100644 --- a/Test/private/MockCall_Project700.ps1 +++ b/Test/private/MockCall_Project700.ps1 @@ -48,7 +48,7 @@ function Get-Mock_Project_700 { $project.title = $pActual.title $project.number = $pActual.number $project.url = $pActual.url - $project.cacheFileName = "$($pActual.owner.login)_$($pActual.number).json" + $project.cacheFileName = "db-$($pActual.owner.login)-$($pActual.number)-project.json" # Fields info $project.fields = @{} diff --git a/private/projectDatabase/project_database.ps1 b/private/projectDatabase/project_database.ps1 index 9a60638..d725b55 100644 --- a/private/projectDatabase/project_database.ps1 +++ b/private/projectDatabase/project_database.ps1 @@ -7,7 +7,7 @@ function Test-ProjectDatabase{ [Parameter(Position = 1)][int]$ProjectNumber ) - $key = Get-DatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + $key = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber $ret = Test-Database -Key $key @@ -21,7 +21,7 @@ function Test-ProjectDatabaseStaged{ [Parameter(Position = 1)][int]$ProjectNumber ) - $key = Get-DatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + $key = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber $prj = Get-Database -Key $key if($null -eq $prj){ @@ -46,7 +46,7 @@ function Get-ProjectFromDatabase{ [Parameter(Position = 1)][int]$ProjectNumber ) - $key = Get-DatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + $key = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber $prj = Get-Database -Key $key if($null -eq $prj){ @@ -67,7 +67,7 @@ function Reset-ProjectDatabase{ [Parameter(Position = 1)][int]$ProjectNumber ) - $dbKey = Get-DatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + $dbKey = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber Reset-Database -Key $dbKey } @@ -139,7 +139,7 @@ function Save-ProjectDatabase{ throw "Database.number is null or not a positive integer" } - $dbkey = Get-DatabaseKey -Owner $owner -ProjectNumber $projectnumber + $dbkey = Get-ProjectDatabaseKey -Owner $owner -ProjectNumber $projectnumber if($Safe){ $oldDatabase = Get-Database -Key $dbkey @@ -156,21 +156,12 @@ function Save-ProjectDatabase{ Save-Database -Key $dbkey -Database $Database } -function Get-DatabaseKey{ +function Get-ProjectDatabaseKey{ [CmdletBinding()] param( [Parameter(Position = 0)][string]$Owner, [Parameter(Position = 1)][int]$ProjectNumber ) - if([string]::IsNullOrWhiteSpace($Owner)){ - throw "Owner is null or empty" - } - if($ProjectNumber -le 0){ - throw "ProjectNumber is null or not a positive integer" - } - - $ret = "$($owner)_$($projectnumber)" - - return $ret + return Get-DatabaseKey $Owner $ProjectNumber "project" } \ No newline at end of file diff --git a/public/projectCache/projectCache.ps1 b/public/projectCache/projectCache.ps1 index 384a0ba..fad6049 100644 --- a/public/projectCache/projectCache.ps1 +++ b/public/projectCache/projectCache.ps1 @@ -24,7 +24,7 @@ function Get-ProjectCacheFile{ ($Owner, $ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber - $key = Get-DatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + $key = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber $path = Get-DatabaseFile -Key $key if($path | Test-Path ){ From 7a648736fd9b03730892e54abebc376f439592c3 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sat, 7 Mar 2026 22:07:36 +0100 Subject: [PATCH 11/27] fix(cache): improve field cache locking mechanism and management to avoid wrong field cache alive --- public/fields/project_fields_list.ps1 | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/public/fields/project_fields_list.ps1 b/public/fields/project_fields_list.ps1 index ba16601..73a3b46 100644 --- a/public/fields/project_fields_list.ps1 +++ b/public/fields/project_fields_list.ps1 @@ -118,10 +118,17 @@ function getFieldsCache{ ) $key = "$Owner-$ProjectNumber" - $ret = $script:fieldsCache[$key] + $lockKey = Get-DatabaseKey $Owner $ProjectNumber "field-cachelock" - return $ret + $lock = Get-Database -Key $lockKey + $cache = $script:fieldsCache[$key] + if($lock -cne $cache.SafeId) { + $script:fieldsCache.Remove($key) + return $null + } + + return $cache.List } function setFieldsCache{ @@ -132,5 +139,16 @@ function setFieldsCache{ ) $key = "$Owner-$ProjectNumber" - $script:fieldsCache[$key] = $FieldList + $lockKey = Get-DatabaseKey $Owner $ProjectNumber "field-cachelock" + + $safeId = [Guid]::NewGuid().ToString() + + # Save safeId to field-lock + Save-Database -Database $safeId -Key $lockKey + + # Set lock in database to prevent concurrent updates + $script:fieldsCache[$key] = @{ + List = $FieldList + SafeId = $safeId + } } \ No newline at end of file From fcd4e163b8a772b0b72e44399215b0b4ea076ce4 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 19:59:50 +0100 Subject: [PATCH 12/27] refactor(logging): enhance debug logging functionality and improve section handling --- include/MyWrite.ps1 | 126 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 22 deletions(-) diff --git a/include/MyWrite.ps1 b/include/MyWrite.ps1 index 1b04866..ca3269f 100644 --- a/include/MyWrite.ps1 +++ b/include/MyWrite.ps1 @@ -61,7 +61,7 @@ function Write-MyDebug { [CmdletBinding()] [Alias("Write-Debug")] param( - [Parameter(Position = 0)][string]$section, + [Parameter(Position = 0)][string]$section = "none", [Parameter(Position = 1, ValueFromPipeline)][string]$Message, [Parameter(Position = 2)][object]$Object ) @@ -103,14 +103,13 @@ function Write-MyDebugLogging { # Check if file exists # This should always exist as logging checks for parent path to be enabled # It may happen if since enable to execution the parent folder aka loggingFilePath is deleted. - if(-not (Test-Path -Path $loggingFilePath) ){ + if(-not (Test-Path -Path $loggingFilePath -PathType Leaf) ){ Write-Warning "Debug logging file path not accesible : '$loggingFilePath'" return $false } # Write to log file - $logFilePath = Join-Path -Path $loggingFilePath -ChildPath "$($MODULE_NAME)_debug.log" - Add-Content -Path $logFilePath -Value $LogMessage + Add-Content -Path $loggingFilePath -Value $LogMessage } } @@ -178,53 +177,100 @@ function Test-MyDebug { [Parameter()][switch]$Logging ) - # Get the module debug environment variable + function testSection($section,$flags){ + if($flags.Count -eq 0){ + return $false + } + $flags = $flags.ToLower() + $section = $section.ToLower() + + return ($flags.Contains("all")) -or ( $flags.Contains($section)) + } + $moduleDebugVarName = $MODULE_NAME + "_DEBUG" - $flag = [System.Environment]::GetEnvironmentVariable($moduleDebugVarName) + $flagsString = [System.Environment]::GetEnvironmentVariable($moduleDebugVarName) - # check if debugging is enabled - if ([string]::IsNullOrWhiteSpace( $flag )) { + # No configuration means no debug + if([string]::IsNullOrWhiteSpace( $flagsString )) { return $false } - $flag = $flag.ToLower() - $section = $section.ToLower() + # Get flags from flagdsString + $flags = getFlagsFromSectionsString $flagsString + + # Add all if allow is empty. + # This mean stat flagsString only contains filters. + $flags.allow = $flags.allow.Count -eq 0 ? @("all") : $flags.allow + + # Get the module debug environment variable + $isAllow = testSection -Section:$section -Flags:$flags.allow + $isFiltered = testSection -Section:$section -Flags:$flags.filter + + $trace = $isAllow -and -not $isFiltered - $trace = ($flag -like '*all*') -or ( $flag -like "*$section*") return $trace } function Enable-ModuleNameDebug{ param( - [Parameter(Position = 0)][string]$section, + [Parameter(Position = 0)][string[]]$Sections, + [Parameter()][string[]]$AddSections, [Parameter()][string]$LoggingFilePath ) # Check if logging file path is provided if( -Not ( [string]::IsNullOrWhiteSpace( $LoggingFilePath )) ) { - if(Test-Path -Path $LoggingFilePath){ - $moduleDEbugLoggingVarName = $MODULE_NAME + "_DEBUG_LOGGING_FILEPATH" - [System.Environment]::SetEnvironmentVariable($moduleDEbugLoggingVarName, $LoggingFilePath) + if(Test-Path -Path $LoggingFilePath -PathType Leaf){ + set-LogFile $LoggingFilePath } else { Write-Error "Logging file path '$LoggingFilePath' does not exist. Debug logging will not be enabled." return } } - # Check section value - if( [string]::IsNullOrWhiteSpace( $section )) { - $flag = "all" - } else { - $flag = $section + $flagsString = $sections -join " " + $addedFlagsString = $AddSections -join " " + + # if no section get value from env and is still mepty set to all + if([string]::IsNullOrWhiteSpace( $flagsString )) { + $flagsString = get-Sections + if( [string]::IsNullOrWhiteSpace( $flagsString )) { + $flagsString = "all" + } + } + + # Add added to flagsString if provided + if(-Not [string]::IsNullOrWhiteSpace( $addedFlagsString )) { + $flagsString += " " + $addedFlagsString } - $moduleDebugVarName = $MODULE_NAME + "_DEBUG" - [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $flag) + set-Sections $flagsString } Copy-Item -path Function:Enable-ModuleNameDebug -Destination Function:"Enable-$($MODULE_NAME)Debug" Export-ModuleMember -Function "Enable-$($MODULE_NAME)Debug" +function getFlagsFromSectionsString($sectionsString){ + $flags = @{ + allow = $null + filter = $null + } + + if([string]::IsNullOrWhiteSpace($sectionsString) ){ + $flags.allow = @("all") + return $flags + } + + $list = $sectionsString.Split(" ", [StringSplitOptions]::RemoveEmptyEntries) + + $split = @($list).Where({ $_ -like '-*' }, 'Split') + + $flags.filter = $split[0] | ForEach-Object { $_ -replace '^-', '' } # -> API, Auth + $flags.allow = $split[1] # -> Sync, Cache + + return $flags +} + function Disable-ModuleNameDebug { param() @@ -237,6 +283,18 @@ function Disable-ModuleNameDebug { Copy-Item -path Function:Disable-ModuleNameDebug -Destination Function:"Disable-$($MODULE_NAME)Debug" Export-ModuleMember -Function "Disable-$($MODULE_NAME)Debug" +function Get-ModuleNameDebug { + [cmdletbinding()] + param() + + return @{ + Sections = get-Sections + LoggingFilePath = get-LogFile + } +} +Copy-Item -path Function:Get-ModuleNameDebug -Destination Function:"Get-$($MODULE_NAME)Debug" +Export-ModuleMember -Function "Get-$($MODULE_NAME)Debug" + function Get-ObjetString { param( [Parameter(ValueFromPipeline, Position = 0)][object]$Object @@ -255,3 +313,27 @@ function Get-ObjetString { return $Object | ConvertTo-Json -Depth 10 -ErrorAction SilentlyContinue } } + +function get-Sections(){ + $moduleDebugVarName = $MODULE_NAME + "_DEBUG" + $sections = [System.Environment]::GetEnvironmentVariable($moduleDebugVarName) + + return $sections +} + +function set-Sections($sections){ + $moduleDebugVarName = $MODULE_NAME + "_DEBUG" + [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $sections) +} + +function get-LogFile(){ + $moduleDEbugLoggingVarName = $MODULE_NAME + "_DEBUG_LOGGING_FILEPATH" + $logfile = [System.Environment]::GetEnvironmentVariable($moduleDEbugLoggingVarName) + + return $logfile +} + +function set-LogFile($logFilePath){ + $moduleDEbugLoggingVarName = $MODULE_NAME + "_DEBUG_LOGGING_FILEPATH" + [System.Environment]::SetEnvironmentVariable($moduleDEbugLoggingVarName, $logFilePath) +} From 542f029274f1e2744210151636b3ec3b7a0b0dc6 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 20:21:06 +0100 Subject: [PATCH 13/27] refactor(environment): rename environment cache keys for consistency --- .../environment/projectParameters.test.ps1 | 6 ++-- .../project/updateprojectrecent.test.ps1 | 6 ++-- public/environment/environmentCache.ps1 | 28 +++++++++---------- public/environment/projectParameters.ps1 | 4 +-- public/repository/get-repository.ps1 | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Test/public/environment/projectParameters.test.ps1 b/Test/public/environment/projectParameters.test.ps1 index c2efa16..30cee65 100644 --- a/Test/public/environment/projectParameters.test.ps1 +++ b/Test/public/environment/projectParameters.test.ps1 @@ -8,9 +8,9 @@ function Test_SetProjectParameters_SUCCESS{ Set-ProjectParameters -Owner $owner -ProjectNumber $projectNumber $v = @{ - Owner = @{value =$owner ; file = $($dbPath | Join-Path -Child "EnvironmentCache_Owner.json")} - ProjectNumber = @{value =$projectNumber ; file = $($dbPath | Join-Path -Child "EnvironmentCache_ProjectNumber.json")} - ProjectTitle = @{value =$projectTitle ; file = $($dbPath | Join-Path -Child "EnvironmentCache_ProjectTitle.json")} + Owner = @{value =$owner ; file = $($dbPath | Join-Path -Child "env-owner.json")} + ProjectNumber = @{value =$projectNumber ; file = $($dbPath | Join-Path -Child "env-ProjectNumber.json")} + ProjectTitle = @{value =$projectTitle ; file = $($dbPath | Join-Path -Child "env-ProjectTitle.json")} } $v.keys | ForEach-Object { diff --git a/Test/public/project/updateprojectrecent.test.ps1 b/Test/public/project/updateprojectrecent.test.ps1 index e11b332..a275743 100644 --- a/Test/public/project/updateprojectrecent.test.ps1 +++ b/Test/public/project/updateprojectrecent.test.ps1 @@ -50,7 +50,7 @@ function Test_UpdateProjectRecent_UpdateBasedOn_FirstTime{ $result = Update-ProjectRecent -Owner $owner -ProjectNumber $projectNumber - ## Assert check that the EnvironmentCache_Last_RecentUpdate_octodemo_700.json is set to today + ## Assert check that the env-Last_RecentUpdate_octodemo_700.json is set to today $envValue = Invoke-PrivateContext { Get-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 } Assert-AreEqual -Expected $today -Presented $envValue @@ -64,7 +64,7 @@ function Test_UpdateProjectRecent_UpdateBasedOn_SetToToday{ # Act - use the mock to run the project full sync to set the last recent update to today MockCall_GetProject_700 -Cache - ## Assert check that the EnvironmentCache_Last_RecentUpdate_octodemo_700.json is set to today + ## Assert check that the env-Last_RecentUpdate_octodemo_700.json is set to today $envValue = Invoke-PrivateContext { Get-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 } Assert-AreEqual -Expected $today -Presented $envValue @@ -91,7 +91,7 @@ function Test_UpdateProjectRecent_UpdateBasedOn_SetToTehPast{ # Act - use the mock to run the project full sync to set the last recent update to today MockCall_GetProject_700 -Cache - ## Assert check that the EnvironmentCache_Last_RecentUpdate_octodemo_700.json is set to today + ## Assert check that the env-Last_RecentUpdate_octodemo_700.json is set to today Invoke-PrivateContext { Set-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 -Value "2024-02-18" } diff --git a/public/environment/environmentCache.ps1 b/public/environment/environmentCache.ps1 index e05d352..a513388 100644 --- a/public/environment/environmentCache.ps1 +++ b/public/environment/environmentCache.ps1 @@ -8,17 +8,17 @@ function Get-ProjectHelperEnvironment{ $ret = @{ # Last Known Good Owner - Owner = Get-EnvItem -Name "EnvironmentCache_Owner" + Owner = Get-EnvItem -Name "env-owner" # Last Known Good Project Number - ProjectNumber = Get-EnvItem -Name "EnvironmentCache_ProjectNumber" + ProjectNumber = Get-EnvItem -Name "env-ProjectNumber" # Last known good Project Name - ProjectTitle = Get-EnvItem -Name "EnvironmentCache_ProjectTitle" + ProjectTitle = Get-EnvItem -Name "env-ProjectTitle" # List of fields to display on Items display commands. Useful with ConvertToItemDisplay # TODO : Consider if its worth keeping this setting - DisplayFields = Get-EnvItem -Name "EnvironmentCache_Display_Fields" + DisplayFields = Get-EnvItem -Name "env-Display_Fields" } return $ret @@ -29,10 +29,10 @@ function Reset-ProjectHelperEnvironment{ [CmdletBinding()] param() - Set-EnvItem -Name "EnvironmentCache_Owner" -Value $null - Set-EnvItem -Name "EnvironmentCache_ProjectNumber" -Value $null - Set-EnvItem -Name "EnvironmentCache_ProjectTitle" -Value $null - Set-EnvItem -Name "EnvironmentCache_Display_Fields" -Value $null + Set-EnvItem -Name "env-owner" -Value $null + Set-EnvItem -Name "env-ProjectNumber" -Value $null + Set-EnvItem -Name "env-ProjectTitle" -Value $null + Set-EnvItem -Name "env-Display_Fields" -Value $null } Export-ModuleMember -Function Reset-ProjectHelperEnvironment @@ -46,19 +46,19 @@ function Set-ProjectHelperEnvironment{ ) if(! [string]::IsNullOrWhiteSpace($Owner)) { - Set-EnvItem -Name "EnvironmentCache_Owner" -Value $Owner + Set-EnvItem -Name "env-owner" -Value $Owner } if(! [string]::IsNullOrWhiteSpace($ProjectNumber)) { - Set-EnvItem -Name "EnvironmentCache_ProjectNumber" -Value $ProjectNumber + Set-EnvItem -Name "env-ProjectNumber" -Value $ProjectNumber } if(! [string]::IsNullOrWhiteSpace($ProjectTitle)) { - Set-EnvItem -Name "EnvironmentCache_ProjectTitle" -Value $ProjectTitle + Set-EnvItem -Name "env-ProjectTitle" -Value $ProjectTitle } if($DisplayFields) { - Set-EnvItem -Name "EnvironmentCache_Display_Fields" -Value $DisplayFields + Set-EnvItem -Name "env-Display_Fields" -Value $DisplayFields } } Export-ModuleMember -Function Set-ProjectHelperEnvironment @@ -69,7 +69,7 @@ function Get-EnvironmentDisplayFields{ [Parameter()][string[]]$Fields ) - $displayFields = Get-EnvItem -Name "EnvironmentCache_Display_Fields" + $displayFields = Get-EnvItem -Name "env-Display_Fields" # Use this order $fields_Options = @() if ($DEFAULT_DISPLAY_FIELDS) { $fields_Options += $DEFAULT_DISPLAY_FIELDS } @@ -114,6 +114,6 @@ function Get-DefaultDisplayFields{ [CmdletBinding()] param() - # return Get-EnvItem -Name "EnvironmentCache_Display_Fields" + # return Get-EnvItem -Name "env-Display_Fields" return $DEFAULT_DISPLAY_FIELDS } diff --git a/public/environment/projectParameters.ps1 b/public/environment/projectParameters.ps1 index b5d32bd..bd9894d 100644 --- a/public/environment/projectParameters.ps1 +++ b/public/environment/projectParameters.ps1 @@ -12,11 +12,11 @@ function Resolve-ProjectParameters { } if([string]::IsNullOrWhiteSpace($Owner)){ - $Owner = Get-EnvItem -Name "EnvironmentCache_Owner" + $Owner = Get-EnvItem -Name "env-owner" } if([string]::IsNullOrWhiteSpace($ProjectNumber)){ - $ProjectNumber = Get-EnvItem -Name "EnvironmentCache_ProjectNumber" + $ProjectNumber = Get-EnvItem -Name "env-ProjectNumber" } if([string]::IsNullOrWhiteSpace($ProjectNumber) -or [string]::IsNullOrWhiteSpace($Owner)){ diff --git a/public/repository/get-repository.ps1 b/public/repository/get-repository.ps1 index 8f218b3..a84756c 100644 --- a/public/repository/get-repository.ps1 +++ b/public/repository/get-repository.ps1 @@ -9,7 +9,7 @@ function Get-Repository{ ) # Check database - $key = "$Owner-$Name" + $key = "repo-$Owner-$Name" $repodb = Get-Database -Key $key # use cache if available and not forced From 0a4d3f6773e59a9a5ec0f6279acac6e49ec454bc Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 20:26:38 +0100 Subject: [PATCH 14/27] refactor(tests): rename environment item functions for consistency --- .../project/updateprojectrecent.test.ps1 | 18 ++++++------ public/project/updateprojectrecent.ps1 | 28 +++++++++++-------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Test/public/project/updateprojectrecent.test.ps1 b/Test/public/project/updateprojectrecent.test.ps1 index a275743..c241b7f 100644 --- a/Test/public/project/updateprojectrecent.test.ps1 +++ b/Test/public/project/updateprojectrecent.test.ps1 @@ -12,10 +12,10 @@ function Test_UpdateProject_SetsRecentUpdateToday_WhenQueryIsNull{ # Assert Assert-IsTrue $result - # Verify Set-EnvItem_Last_RecentUpdate_Today was called - check the env item is set to today - $envValue = Invoke-PrivateContext { Get-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 } + # Verify Set-EnvProjectLastUpdate_Today was called - check the env item is set to today + $envValue = Invoke-PrivateContext { Get-EnvProjectLastUpdate -Owner "octodemo" -ProjectNumber 700 } - Assert-AreEqual -Expected $today -Presented $envValue -Comment "Set-EnvItem_Last_RecentUpdate_Today should set env item to today when Query is null" + Assert-AreEqual -Expected $today -Presented $envValue -Comment "Set-EnvProjectLastUpdate_Today should set env item to today when Query is null" } function Test_UpdateProjectRecent_FirstCAll_SetRecentUpdate_toToday{ @@ -34,8 +34,8 @@ function Test_UpdateProjectRecent_FirstCAll_SetRecentUpdate_toToday{ # Assert Assert-IsTrue $result - # Verify Set-EnvItem_Last_RecentUpdate_Today was NOT called - env item should be null/empty - $envValue = Invoke-PrivateContext { Get-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 } + # Verify Set-EnvProjectLastUpdate_Today was NOT called - env item should be null/empty + $envValue = Invoke-PrivateContext { Get-EnvProjectLastUpdate -Owner "octodemo" -ProjectNumber 700 } Assert-AreEqual -Expected $today -Presented $envValue } @@ -51,7 +51,7 @@ function Test_UpdateProjectRecent_UpdateBasedOn_FirstTime{ $result = Update-ProjectRecent -Owner $owner -ProjectNumber $projectNumber ## Assert check that the env-Last_RecentUpdate_octodemo_700.json is set to today - $envValue = Invoke-PrivateContext { Get-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 } + $envValue = Invoke-PrivateContext { Get-EnvProjectLastUpdate -Owner "octodemo" -ProjectNumber 700 } Assert-AreEqual -Expected $today -Presented $envValue } @@ -65,7 +65,7 @@ function Test_UpdateProjectRecent_UpdateBasedOn_SetToToday{ MockCall_GetProject_700 -Cache ## Assert check that the env-Last_RecentUpdate_octodemo_700.json is set to today - $envValue = Invoke-PrivateContext { Get-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 } + $envValue = Invoke-PrivateContext { Get-EnvProjectLastUpdate -Owner "octodemo" -ProjectNumber 700 } Assert-AreEqual -Expected $today -Presented $envValue # Reset Mocks to ensure no mocks functions left from caching project @@ -92,7 +92,7 @@ function Test_UpdateProjectRecent_UpdateBasedOn_SetToTehPast{ MockCall_GetProject_700 -Cache ## Assert check that the env-Last_RecentUpdate_octodemo_700.json is set to today - Invoke-PrivateContext { Set-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 -Value "2024-02-18" } + Invoke-PrivateContext { Set-EnvProjectLastUpdate -Owner "octodemo" -ProjectNumber 700 -Value "2024-02-18" } # Reset Mocks to ensure no mocks functions left from caching project @@ -110,6 +110,6 @@ function Test_UpdateProjectRecent_UpdateBasedOn_SetToTehPast{ Assert-IsTrue $result # ASsert that value has changed to today - $envValue = Invoke-PrivateContext { Get-EnvItem_Last_RecentUpdate -Owner "octodemo" -ProjectNumber 700 } + $envValue = Invoke-PrivateContext { Get-EnvProjectLastUpdate -Owner "octodemo" -ProjectNumber 700 } Assert-AreEqual -Expected $today -Presented $envValue } diff --git a/public/project/updateprojectrecent.ps1 b/public/project/updateprojectrecent.ps1 index 643cb39..f356d97 100644 --- a/public/project/updateprojectrecent.ps1 +++ b/public/project/updateprojectrecent.ps1 @@ -13,10 +13,10 @@ function Update-ProjectRecent{ # Get Last update date $query = Get-UpdateRecentQuery -Owner $Owner -ProjectNumber $ProjectNumber - $ret = Update-Project -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Query $Query + $ret = Update-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -Query $Query if($ret){ - Set-EnvItem_Last_RecentUpdate_Today -Owner $Owner -ProjectNumber $ProjectNumber + Set-EnvProjectLastUpdate_Today -Owner $Owner -ProjectNumber $ProjectNumber } return $ret @@ -29,7 +29,7 @@ function Get-UpdateRecentQuery{ [Parameter()][int]$ProjectNumber ) - $last = Get-EnvItem_Last_RecentUpdate -Owner $Owner -ProjectNumber $ProjectNumber + $last = Get-EnvProjectLastUpdate -Owner $Owner -ProjectNumber $ProjectNumber # If no last update return no filter to update all if ($null -eq $last){ @@ -38,24 +38,28 @@ function Get-UpdateRecentQuery{ $ret = $RECENT_QUERY -replace "{date}", $last + "Updated recent query [ $ret ]" | Write-MyDebug -Section "Update-Project" + return $ret } Export-ModuleMember -Function Get-UpdateRecentQuery -function Get-EnvItem_Last_RecentUpdate{ +function Get-EnvProjectLastUpdate{ [CmdletBinding()] param( [Parameter(Mandatory)][string]$Owner, [Parameter(Mandatory)][int]$ProjectNumber ) - $last = Get-EnvItem -Name "EnvironmentCache_Last_RecentUpdate_$($Owner)_$($ProjectNumber)" + $key = "db-$($Owner)-$($ProjectNumber)-project-LastUpdate" + + $last = Get-EnvItem -Name $key - Write-MyDebug "EnvItem_Last_RecentUpdate" "Last recent update for $Owner/$ProjectNumber is $last" + "Get last recent update for $Owner/$ProjectNumber with $last" | Write-MyDebug -Section "Update-Project" return $last } -function Set-EnvItem_Last_RecentUpdate{ +function Set-EnvProjectLastUpdate{ [CmdletBinding()] param( [Parameter(Mandatory)][string]$Owner, @@ -63,13 +67,15 @@ function Set-EnvItem_Last_RecentUpdate{ [Parameter(Mandatory)][string]$Value ) - Set-EnvItem -Name "EnvironmentCache_Last_RecentUpdate_$($Owner)_$($ProjectNumber)" -Value $Value + $key = "db-$($Owner)-$($ProjectNumber)-project-LastUpdate" + + Set-EnvItem -Name $key -Value $Value - Write-MyDebug "EnvItem_Last_RecentUpdate" "Set last recent update for $Owner/$ProjectNumber to $Value" + "Set last recent update for $Owner/$ProjectNumber to $Value" | Write-MyDebug -Section "Update-Project" } -function Set-EnvItem_Last_RecentUpdate_Today{ +function Set-EnvProjectLastUpdate_Today{ [CmdletBinding()] param( [Parameter(Mandatory)][string]$Owner, @@ -78,6 +84,6 @@ function Set-EnvItem_Last_RecentUpdate_Today{ $now = Get-DateToday - Set-EnvItem_Last_RecentUpdate -Owner $Owner -ProjectNumber $ProjectNumber -Value $now + Set-EnvProjectLastUpdate -Owner $Owner -ProjectNumber $ProjectNumber -Value $now } \ No newline at end of file From 185af5fc9844874dbf21aeb269b491fbc5e6de66 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 20:56:31 +0100 Subject: [PATCH 15/27] fix(cache): improve cache lock handling and add debug messages for clarity --- public/fields/project_fields_list.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/public/fields/project_fields_list.ps1 b/public/fields/project_fields_list.ps1 index 73a3b46..2b4f2e5 100644 --- a/public/fields/project_fields_list.ps1 +++ b/public/fields/project_fields_list.ps1 @@ -121,13 +121,21 @@ function getFieldsCache{ $lockKey = Get-DatabaseKey $Owner $ProjectNumber "field-cachelock" $lock = Get-Database -Key $lockKey + + if([string]::IsNullOrWhiteSpace($lock)){ + "No cache lock found for $Owner/$ProjectNumber. Cache will be ignored." | Write-MyDebug -Section "Get-ProjectFields" + return $null + } + $cache = $script:fieldsCache[$key] if($lock -cne $cache.SafeId) { + "Cache lock mismatch for $Owner/$ProjectNumber. Cache safeId [$($cache.SafeId)], lock [$lock]. Cache will be ignored." | Write-MyDebug -Section "Get-ProjectFields" $script:fieldsCache.Remove($key) return $null } + "Getting fields cache for $Owner/$ProjectNumber with lock [$lock] and cache safeId [$($cache.SafeId)]" | Write-MyDebug -Section "Get-ProjectFields" return $cache.List } From 6141aaa8bf7245faeb5900c7e517f19fbec547c3 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 20:56:48 +0100 Subject: [PATCH 16/27] refactor(database): enhance debug logging for database operations --- private/database/databaseV2.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/private/database/databaseV2.ps1 b/private/database/databaseV2.ps1 index b4b6252..c7a3ec2 100644 --- a/private/database/databaseV2.ps1 +++ b/private/database/databaseV2.ps1 @@ -47,6 +47,8 @@ function Get-Database { [Parameter(Position = 0)][string]$Key ) + "Getting database for key [$Key]" | Write-MyDebug -Section Database + $path = Get-DatabaseFile $Key if (-Not (Test-Path $path)) { @@ -67,6 +69,8 @@ function Test-Database { $path = Get-DatabaseFile -Key $Key $ret = Test-Path $path + + "Test [$ret] database for key [$Key] at path [$path]" | Write-MyDebug -Section Database return $ret } @@ -76,9 +80,12 @@ function Reset-Database { param( [Parameter(Position = 0)][string]$Key ) + $path = Get-DatabaseFile -Key $Key + + "Resetting database for key [$Key] at path [$path]" | Write-MyDebug -Section Database + Microsoft.PowerShell.Management\Remove-Item -Path $path -Force -ErrorAction SilentlyContinue - return } function Save-Database { From 687d92083d6167df953f389849da3ac867c2f081 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 21:27:17 +0100 Subject: [PATCH 17/27] feat(get-Project): create memory caching mechanism --- public/project/getproject.ps1 | 105 +++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/public/project/getproject.ps1 b/public/project/getproject.ps1 index cff6374..6d50079 100644 --- a/public/project/getproject.ps1 +++ b/public/project/getproject.ps1 @@ -9,17 +9,29 @@ function Get-Project { ($Owner, $ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber - if ($Force -or -Not (Test-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber)) { - $result = Update-Project -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems - if ( ! $result) { return } - } + $prj = getProjectCache -Owner $Owner -ProjectNumber $ProjectNumber + + if(-Not $prj -or $Force){ + "No cache found for $Owner/$ProjectNumber or force specified. Retrieving project from database." | Write-MyDebug -Section Get-Project + + if ($Force -or -Not (Test-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber)) { + "Project not found in database or force specified. Updating project for $Owner/$ProjectNumber." | Write-MyDebug -Section Get-Project + + $result = Update-Project -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems + + if ( ! $result) { + "Failed to update project for $Owner/$ProjectNumber. Project may not exist or there was an error during update." | Write-MyError + return + } + } else { + "Project found in database for $Owner/$ProjectNumber. Loading project." | Write-MyDebug -Section Get-Project + } - $prj = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber + $prj = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber - # if($SkipItems){ - # $prj.items = @() - # } + setProjectCache -Owner $Owner -ProjectNumber $ProjectNumber -Project $prj + } return $prj } Export-ModuleMember -Function Get-Project @@ -29,17 +41,28 @@ function Update-Project{ [Parameter()][string]$Owner, [Parameter()][int]$ProjectNumber, [parameter()][string]$Query, - [Parameter()][switch]$SkipItems + [Parameter()][switch]$SkipItems, + [Parameter()][switch]$Force ) ($Owner, $ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber - $ret = Update-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Query $Query - - # Check if we did a full projectupdate if([string]::IsNullOrEmpty($Query)){ - # Reset recent to today - Set-EnvItem_Last_RecentUpdate_Today -Owner $Owner -ProjectNumber $ProjectNumber + + # Update just the items that were modified unless -Force + if(! $Force){ + "Performing INCREMENTAL update for $Owner/$ProjectNumber" | Write-MyDebug -Section "Update-Project" + $recentQuery = Get-UpdateRecentQuery -Owner $Owner -ProjectNumber $ProjectNumber + $query += " " + $recentQuery + } else { + "Performing FULL update for $Owner/$ProjectNumber" | Write-MyDebug -Section "Update-Project" + } + $ret = Update-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Query $Query + Set-EnvProjectLastUpdate_Today -Owner $Owner -ProjectNumber $ProjectNumber + } + else{ + "Performing PARTIAL update for $Owner/$ProjectNumber with query [$Query]" | Write-MyDebug -Section "Update-Project" + $ret = Update-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Query $Query } return $ret @@ -101,3 +124,57 @@ function Open-Project{ } } Export-ModuleMember -Function Open-Project + +$script:ProjectsCache = @{} + +function getProjectCache{ + param( + [Parameter(Mandatory,Position = 0)][string]$Owner, + [Parameter(Mandatory,Position = 1)][string]$ProjectNumber + ) + + $key = "$Owner-$ProjectNumber" + $lockKey = Get-DatabaseKey $Owner $ProjectNumber "project-cachelock" + + $lock = Get-Database -Key $lockKey + + if([string]::IsNullOrWhiteSpace($lock)){ + "No cache lock found for $Owner/$ProjectNumber. Cache will be ignored." | Write-MyDebug -Section "Get-Project" + return $null + } + + $cache = $script:ProjectsCache[$key] + + if($lock -cne $cache.SafeId) { + "Cache lock mismatch for $Owner/$ProjectNumber. Cache safeId [$($cache.SafeId)], lock [$lock]. Cache will be ignored." | Write-MyDebug -Section "Get-Project" + $script:ProjectsCache.Remove($key) + return $null + } + + "Getting fields cache for $Owner/$ProjectNumber with lock [$lock] and cache safeId [$($cache.SafeId)]" | Write-MyDebug -Section "Get-Project" + return $cache.List +} + +function setProjectCache{ + param( + [Parameter(Mandatory,Position = 0)][string]$Owner, + [Parameter(Mandatory,Position = 1)][string]$ProjectNumber, + [Parameter(Mandatory,Position = 2)][object]$Project + ) + + $key = "$Owner-$ProjectNumber" + $lockKey = Get-DatabaseKey $Owner $ProjectNumber "project-cachelock" + + $safeId = [Guid]::NewGuid().ToString() + + "Setting project cache for $Owner/$ProjectNumber with safeId [$safeId]" | Write-MyDebug -Section "Get-Project" + + # Save safeId to project-lock + Save-Database -Database $safeId -Key $lockKey + + # Set lock in database to prevent concurrent updates + $script:ProjectsCache[$key] = @{ + List = $Project + SafeId = $safeId + } +} From f7830b3517ab683455d88108ed8cedb601fd3035 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 22:40:43 +0100 Subject: [PATCH 18/27] fix(Update-Project): replace incremental query concatenation with recent query assignment --- public/project/getproject.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/project/getproject.ps1 b/public/project/getproject.ps1 index 6d50079..aa07b8f 100644 --- a/public/project/getproject.ps1 +++ b/public/project/getproject.ps1 @@ -53,7 +53,8 @@ function Update-Project{ if(! $Force){ "Performing INCREMENTAL update for $Owner/$ProjectNumber" | Write-MyDebug -Section "Update-Project" $recentQuery = Get-UpdateRecentQuery -Owner $Owner -ProjectNumber $ProjectNumber - $query += " " + $recentQuery + + $query = $recentQuery } else { "Performing FULL update for $Owner/$ProjectNumber" | Write-MyDebug -Section "Update-Project" } From 3dd6d99e5c8c6081852ae09425a0432225004a66 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 23:13:44 +0100 Subject: [PATCH 19/27] fix(test): update repository filename format in test assertion --- Test/public/repository/get-repository.test.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/public/repository/get-repository.test.ps1 b/Test/public/repository/get-repository.test.ps1 index a06711f..2cbe4e6 100644 --- a/Test/public/repository/get-repository.test.ps1 +++ b/Test/public/repository/get-repository.test.ps1 @@ -16,7 +16,7 @@ function Test_GetRepository{ # Assert repo cache created $dbpath = get-Mock_DatabaseRootPath - $dbname = "$($r.owner)-$($r.name).json" + $dbname = "repo-$($r.owner)-$($r.name).json" Assert-ItemExist -Path (Join-Path -Path $dbpath -ChildPath $dbname) From 136a64db5d7b76bf06e5198da21d22b491841164 Mon Sep 17 00:00:00 2001 From: rulasg Date: Sun, 8 Mar 2026 23:13:52 +0100 Subject: [PATCH 20/27] refactor(test): optimize project database test by using cached project retrieval --- .../projectDatabase/project_database.test.ps1 | 18 +++++++++++------- public/project/getproject.ps1 | 13 +++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Test/public/projectDatabase/project_database.test.ps1 b/Test/public/projectDatabase/project_database.test.ps1 index d8a36eb..dc3bc4b 100644 --- a/Test/public/projectDatabase/project_database.test.ps1 +++ b/Test/public/projectDatabase/project_database.test.ps1 @@ -27,25 +27,29 @@ function Test_SaveProjectDatabase_SafeId_Flag_PrivateCall{ function Test_SaveProjectDatabase_Safe_PrivateCall{ $p = Get-Mock_Project_700 ; $owner = $p.owner ; $projectNumber = $p.number - MockCall_GetProject_700 + MockCall_GetProject_700 -Cache Invoke-PrivateContext { $Owner = "octodemo" ; $ProjectNumber = 700 # Cache the project - $prj1 = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber + # $prj1 = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber # modify the project - $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber ; Save-ProjectDatabaseSafe -Database $db - + # $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber + $db1 = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber + $dbClone = $db1 | ConvertTo-Json | ConvertFrom-Json + Save-ProjectDatabaseSafe -Database $db1 + # Check that safeId has changed - $prj2 = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber - Assert-AreNotEqual -Presented $prj2.safeId -Expected $prj1.safeId + $db2 = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber + # $prj2 = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber + Assert-AreNotEqual -Presented $dbClone.safeId -Expected $db2.safeId ## When saving again as prj1 that has been saved before it will throw $hasThrow = $false try{ - Save-ProjectDatabaseSafe -Database $prj1 + Save-ProjectDatabaseSafe -Database $dbClone } catch { $hasThrow = $true } diff --git a/public/project/getproject.ps1 b/public/project/getproject.ps1 index aa07b8f..bf36ba7 100644 --- a/public/project/getproject.ps1 +++ b/public/project/getproject.ps1 @@ -21,6 +21,7 @@ function Get-Project { if ( ! $result) { "Failed to update project for $Owner/$ProjectNumber. Project may not exist or there was an error during update." | Write-MyError + resetProjectCache -Owner $Owner -ProjectNumber $ProjectNumber return } } else { @@ -179,3 +180,15 @@ function setProjectCache{ SafeId = $safeId } } + +function resetProjectCache{ + param( + [Parameter(Mandatory,Position = 0)][string]$Owner, + [Parameter(Mandatory,Position = 1)][string]$ProjectNumber + ) + + "Resetting project cache for $Owner/$ProjectNumber" | Write-MyDebug -Section "Get-Project" + + $key = "$Owner-$ProjectNumber" + $script:ProjectsCache.Remove($key) +} From 2fd55ddea0f3b021ec3b0be809ec67263ce7e0d0 Mon Sep 17 00:00:00 2001 From: rulasg Date: Mon, 9 Mar 2026 07:34:54 +0100 Subject: [PATCH 21/27] fix(get-Project): add force parameter to project update call --- Test/private/mockdatabase.ps1 | 5 +++++ public/project/getproject.ps1 | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Test/private/mockdatabase.ps1 b/Test/private/mockdatabase.ps1 index bd46e96..b6eb92c 100644 --- a/Test/private/mockdatabase.ps1 +++ b/Test/private/mockdatabase.ps1 @@ -24,8 +24,13 @@ function Get-Mock_DatabaseRootPath{ } function Update-Mock_DatabaseFileWithReplace([string]$FileName, [string]$SearchString, [string]$ReplaceString){ + $dbpath = Get-Mock_DatabaseRootPath | Join-Path -ChildPath $FileName $content = Get-Content $dbpath $content = $content -replace $SearchString, $ReplaceString $content | Set-Content $dbpath + + # Reset the memory cache deleting the cachelock file + $cachelock = $dbpath -replace '\.json$', '-cachelock.json' + Remove-Item -Path $cachelock -Force -ErrorAction SilentlyContinue } \ No newline at end of file diff --git a/public/project/getproject.ps1 b/public/project/getproject.ps1 index bf36ba7..c1fc59a 100644 --- a/public/project/getproject.ps1 +++ b/public/project/getproject.ps1 @@ -17,7 +17,7 @@ function Get-Project { if ($Force -or -Not (Test-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber)) { "Project not found in database or force specified. Updating project for $Owner/$ProjectNumber." | Write-MyDebug -Section Get-Project - $result = Update-Project -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems + $result = Update-Project -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Force:$Force if ( ! $result) { "Failed to update project for $Owner/$ProjectNumber. Project may not exist or there was an error during update." | Write-MyError From 76d0c39f59018144d75f59e4a6545eab27cdac7b Mon Sep 17 00:00:00 2001 From: rulasg Date: Mon, 9 Mar 2026 08:01:24 +0100 Subject: [PATCH 22/27] fix(Update-Project): remove unnecessary query parameter in database update call --- public/project/getproject.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/project/getproject.ps1 b/public/project/getproject.ps1 index c1fc59a..746f550 100644 --- a/public/project/getproject.ps1 +++ b/public/project/getproject.ps1 @@ -59,7 +59,7 @@ function Update-Project{ } else { "Performing FULL update for $Owner/$ProjectNumber" | Write-MyDebug -Section "Update-Project" } - $ret = Update-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Query $Query + $ret = Update-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems Set-EnvProjectLastUpdate_Today -Owner $Owner -ProjectNumber $ProjectNumber } else{ @@ -67,6 +67,8 @@ function Update-Project{ $ret = Update-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Query $Query } + resetProjectCache -Owner $Owner -ProjectNumber $ProjectNumber + return $ret } Export-ModuleMember -Function Update-Project From 1b93299f0642873b5d1f3a23b373141bcbce339a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 9 Mar 2026 10:16:28 +0100 Subject: [PATCH 23/27] feat(projectdatabase): project cache to projectdatabase --- private/projectDatabase/project_database.ps1 | 86 ++++++++++++++-- public/project/getproject.ps1 | 101 +++---------------- 2 files changed, 90 insertions(+), 97 deletions(-) diff --git a/private/projectDatabase/project_database.ps1 b/private/projectDatabase/project_database.ps1 index d725b55..de89889 100644 --- a/private/projectDatabase/project_database.ps1 +++ b/private/projectDatabase/project_database.ps1 @@ -7,7 +7,7 @@ function Test-ProjectDatabase{ [Parameter(Position = 1)][int]$ProjectNumber ) - $key = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + $key,$keyLock = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber $ret = Test-Database -Key $key @@ -21,8 +21,7 @@ function Test-ProjectDatabaseStaged{ [Parameter(Position = 1)][int]$ProjectNumber ) - $key = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber - $prj = Get-Database -Key $key + $prj = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber if($null -eq $prj){ return $false @@ -46,7 +45,16 @@ function Get-ProjectFromDatabase{ [Parameter(Position = 1)][int]$ProjectNumber ) - $key = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + $key,$keyLock = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + + $prj = getProjectDatabaseCache -KeyLock $keyLock + + if($null -ne $prj){ + "Project cache hit for $Owner/$ProjectNumber" | Write-MyDebug -Section "ProjectDatabase" + return $prj + } + + # No cache or cache mismatch, read from database $prj = Get-Database -Key $key if($null -eq $prj){ @@ -67,8 +75,9 @@ function Reset-ProjectDatabase{ [Parameter(Position = 1)][int]$ProjectNumber ) - $dbKey = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber + $dbKey, $dbKeyLock = Get-ProjectDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber Reset-Database -Key $dbKey + resetProjectDatabaseCache -Owner $Owner -ProjectNumber $ProjectNumber } function Save-ProjectV2toDatabase{ @@ -139,7 +148,7 @@ function Save-ProjectDatabase{ throw "Database.number is null or not a positive integer" } - $dbkey = Get-ProjectDatabaseKey -Owner $owner -ProjectNumber $projectnumber + $dbkey, $dbkeyLock = Get-ProjectDatabaseKey -Owner $owner -ProjectNumber $projectnumber if($Safe){ $oldDatabase = Get-Database -Key $dbkey @@ -147,13 +156,14 @@ function Save-ProjectDatabase{ if ($oldDatabase.safeId -ne $Database.safeId){ throw "The database has changed since it was read. Aborting save to prevent overwriting changes." } - } # Add safe mark $Database.safeId = [guid]::NewGuid().ToString() + # Save database Save-Database -Key $dbkey -Database $Database + setProjectDatabaseCache -KeyLock $dbkeyLock -SafeId $Database.safeId -Database $Database } function Get-ProjectDatabaseKey{ @@ -163,5 +173,65 @@ function Get-ProjectDatabaseKey{ [Parameter(Position = 1)][int]$ProjectNumber ) - return Get-DatabaseKey $Owner $ProjectNumber "project" + $key = Get-DatabaseKey $Owner $ProjectNumber "project" + $keylock = "$key-lock" + + return $key, $keylock +} + +$script:ProjectDatabaseCache = @{} + +function getProjectDatabaseCache{ + param( + [Parameter(Mandatory,Position = 0)][string]$KeyLock + ) + + $lock = Get-Database -Key $KeyLock + + if([string]::IsNullOrWhiteSpace($lock)){ + "No cache lock found for $KeyLock. Cache will be ignored." | Write-MyDebug -Section "ProjectDatabase" + return $null + } + + $cache = $script:ProjectDatabaseCache[$KeyLock] + + if($lock -cne $cache.safeId) { + "Cache lock mismatch for $KeyLock. Cache safeId [$($cache.SafeId)], lock [$lock]. Cache will be ignored." | Write-MyDebug -Section "ProjectDatabase" + resetProjectDatabaseCache -KeyLock $KeyLock + return $null + } + + "Getting fields cache for $KeyLock with lock [$lock] and cache safeId [$($cache.SafeId)]" | Write-MyDebug -Section "ProjectDatabase" + return $cache.Database +} + +function setProjectDatabaseCache{ + param( + [Parameter(Mandatory,Position = 0)][string]$KeyLock, + [Parameter(Mandatory,Position = 1)][string]$SafeId, + [Parameter(Mandatory,Position = 2)][object]$Database + ) + + "Setting project cache for $KeyLock with safeId [$SafeId]" | Write-MyDebug -Section "ProjectDatabase" + + # Save safeId to project-lock + Save-Database -Database $SafeId -Key $KeyLock + + # Set lock in database to prevent concurrent updates + $script:ProjectDatabaseCache[$KeyLock] = @{ + Database = $Database + SafeId = $SafeId + } +} + +function resetProjectDatabaseCache{ + param( + [Parameter(Mandatory,Position = 0)][string]$KeyLock + ) + + "Resetting project cache for $KeyLock" | Write-MyDebug -Section "ProjectDatabase" + + Reset.Database -Key $KeyLock + + $script:ProjectDatabaseCache.Remove($KeyLock) } \ No newline at end of file diff --git a/public/project/getproject.ps1 b/public/project/getproject.ps1 index 746f550..89e2c4a 100644 --- a/public/project/getproject.ps1 +++ b/public/project/getproject.ps1 @@ -9,30 +9,21 @@ function Get-Project { ($Owner, $ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber - $prj = getProjectCache -Owner $Owner -ProjectNumber $ProjectNumber - - if(-Not $prj -or $Force){ - "No cache found for $Owner/$ProjectNumber or force specified. Retrieving project from database." | Write-MyDebug -Section Get-Project - - if ($Force -or -Not (Test-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber)) { - "Project not found in database or force specified. Updating project for $Owner/$ProjectNumber." | Write-MyDebug -Section Get-Project - - $result = Update-Project -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Force:$Force - - if ( ! $result) { - "Failed to update project for $Owner/$ProjectNumber. Project may not exist or there was an error during update." | Write-MyError - resetProjectCache -Owner $Owner -ProjectNumber $ProjectNumber - return - } - } else { - "Project found in database for $Owner/$ProjectNumber. Loading project." | Write-MyDebug -Section Get-Project + if ($Force -or -Not (Test-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber)) { + "Project not found in database or force specified. Updating project for $Owner/$ProjectNumber." | Write-MyDebug -Section Get-Project + + $result = Update-Project -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Force:$Force + + if ( ! $result) { + "Failed to update project for $Owner/$ProjectNumber. Project may not exist or there was an error during update." | Write-MyError + return } + } else { + "Project found in database for $Owner/$ProjectNumber. Loading project." | Write-MyDebug -Section Get-Project + } - $prj = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber - - setProjectCache -Owner $Owner -ProjectNumber $ProjectNumber -Project $prj + $prj = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber - } return $prj } Export-ModuleMember -Function Get-Project @@ -67,8 +58,6 @@ function Update-Project{ $ret = Update-ProjectDatabase -Owner $Owner -ProjectNumber $ProjectNumber -SkipItems:$SkipItems -Query $Query } - resetProjectCache -Owner $Owner -ProjectNumber $ProjectNumber - return $ret } Export-ModuleMember -Function Update-Project @@ -128,69 +117,3 @@ function Open-Project{ } } Export-ModuleMember -Function Open-Project - -$script:ProjectsCache = @{} - -function getProjectCache{ - param( - [Parameter(Mandatory,Position = 0)][string]$Owner, - [Parameter(Mandatory,Position = 1)][string]$ProjectNumber - ) - - $key = "$Owner-$ProjectNumber" - $lockKey = Get-DatabaseKey $Owner $ProjectNumber "project-cachelock" - - $lock = Get-Database -Key $lockKey - - if([string]::IsNullOrWhiteSpace($lock)){ - "No cache lock found for $Owner/$ProjectNumber. Cache will be ignored." | Write-MyDebug -Section "Get-Project" - return $null - } - - $cache = $script:ProjectsCache[$key] - - if($lock -cne $cache.SafeId) { - "Cache lock mismatch for $Owner/$ProjectNumber. Cache safeId [$($cache.SafeId)], lock [$lock]. Cache will be ignored." | Write-MyDebug -Section "Get-Project" - $script:ProjectsCache.Remove($key) - return $null - } - - "Getting fields cache for $Owner/$ProjectNumber with lock [$lock] and cache safeId [$($cache.SafeId)]" | Write-MyDebug -Section "Get-Project" - return $cache.List -} - -function setProjectCache{ - param( - [Parameter(Mandatory,Position = 0)][string]$Owner, - [Parameter(Mandatory,Position = 1)][string]$ProjectNumber, - [Parameter(Mandatory,Position = 2)][object]$Project - ) - - $key = "$Owner-$ProjectNumber" - $lockKey = Get-DatabaseKey $Owner $ProjectNumber "project-cachelock" - - $safeId = [Guid]::NewGuid().ToString() - - "Setting project cache for $Owner/$ProjectNumber with safeId [$safeId]" | Write-MyDebug -Section "Get-Project" - - # Save safeId to project-lock - Save-Database -Database $safeId -Key $lockKey - - # Set lock in database to prevent concurrent updates - $script:ProjectsCache[$key] = @{ - List = $Project - SafeId = $safeId - } -} - -function resetProjectCache{ - param( - [Parameter(Mandatory,Position = 0)][string]$Owner, - [Parameter(Mandatory,Position = 1)][string]$ProjectNumber - ) - - "Resetting project cache for $Owner/$ProjectNumber" | Write-MyDebug -Section "Get-Project" - - $key = "$Owner-$ProjectNumber" - $script:ProjectsCache.Remove($key) -} From c5a24c74d76969d74ed873f680558ba6b79c2285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 9 Mar 2026 10:41:25 +0100 Subject: [PATCH 24/27] fix(Save-ProjectV2toDatabase): change Fields parameter type from Object[] to hashtable --- private/projectDatabase/project_database.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/private/projectDatabase/project_database.ps1 b/private/projectDatabase/project_database.ps1 index de89889..0793c9f 100644 --- a/private/projectDatabase/project_database.ps1 +++ b/private/projectDatabase/project_database.ps1 @@ -86,7 +86,7 @@ function Save-ProjectV2toDatabase{ [Parameter(Position = 0)][object]$ProjectV2, [Parameter(Position = 1)][hashtable]$Items, [Parameter(Position = 1)][hashtable]$QueryItems, - [Parameter(Position = 2)][Object[]]$Fields + [Parameter(Position = 2)][hashtable]$Fields ) $owner = $ProjectV2.owner.login From b7bcf467444080f8c34812bd2f5433d0a8efae95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 9 Mar 2026 10:48:14 +0100 Subject: [PATCH 25/27] fix(mockdatabase): update cachelock file naming convention --- Test/private/mockdatabase.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/private/mockdatabase.ps1 b/Test/private/mockdatabase.ps1 index b6eb92c..3019de1 100644 --- a/Test/private/mockdatabase.ps1 +++ b/Test/private/mockdatabase.ps1 @@ -31,6 +31,6 @@ function Update-Mock_DatabaseFileWithReplace([string]$FileName, [string]$SearchS $content | Set-Content $dbpath # Reset the memory cache deleting the cachelock file - $cachelock = $dbpath -replace '\.json$', '-cachelock.json' + $cachelock = $dbpath -replace '\.json$', '-lock.json' Remove-Item -Path $cachelock -Force -ErrorAction SilentlyContinue } \ No newline at end of file From a642a372973f0657cfaf36e392b1dc15fc8fd3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 9 Mar 2026 10:49:17 +0100 Subject: [PATCH 26/27] refactor(Update-ProjectDatabase): retain existing items when skipping updates --- private/projectDatabase/project_database_update.ps1 | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/private/projectDatabase/project_database_update.ps1 b/private/projectDatabase/project_database_update.ps1 index b7c3ca2..0a84604 100644 --- a/private/projectDatabase/project_database_update.ps1 +++ b/private/projectDatabase/project_database_update.ps1 @@ -93,20 +93,14 @@ function Update-ProjectDatabase { $queryItems = $items $actualprj = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber - - # Check if project has no items or the project is not cached yet $items = $actualprj.items ?? $(New-HashTable) } - # If we are Skiping items on the update we need to keep the existing items in the database and just update the fields + # We need to keep actualitems if SkipedItems to avoid losing them in the DB if($SkipItems){ $actualprj = Get-ProjectFromDatabase -Owner $Owner -ProjectNumber $ProjectNumber - - # Check if project has no items or the project is not cached yet - $actualItems = $actualprj.items ?? $(New-HashTable) - - $items = $actualItems + $items = $actualprj.items ?? $(New-HashTable) } # Save ProjectV2 object to ProjectDatabase From 15a240e6b8129d26973ba02c5cb8fce8b17fa755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 9 Mar 2026 11:28:57 +0100 Subject: [PATCH 27/27] refactor(Get-ProjectFields): remove caching functions and streamline field retrieval --- public/fields/project_fields_list.ps1 | 55 --------------------------- 1 file changed, 55 deletions(-) diff --git a/public/fields/project_fields_list.ps1 b/public/fields/project_fields_list.ps1 index 2b4f2e5..8e62cee 100644 --- a/public/fields/project_fields_list.ps1 +++ b/public/fields/project_fields_list.ps1 @@ -43,8 +43,6 @@ function Get-ProjectFields{ ($Owner,$ProjectNumber) = Resolve-ProjectParameters -Owner $Owner -ProjectNumber $ProjectNumber - $fieldList = getFieldsCache -Owner $Owner -ProjectNumber $ProjectNumber - if(-Not $fieldList -or $Force){ $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber -Force:$Force -SkipItems @@ -58,7 +56,6 @@ function Get-ProjectFields{ # if $db is null it rill return null $fieldList = $db.fields.Values - setFieldsCache -Owner $Owner -ProjectNumber $ProjectNumber -FieldList $fieldList } # if name @@ -107,56 +104,4 @@ function ConvertToFieldDisplay{ return $ret } -} - -$script:fieldsCache = @{} - -function getFieldsCache{ - param( - [Parameter(Mandatory,Position = 0)][string]$Owner, - [Parameter(Mandatory,Position = 1)][string]$ProjectNumber - ) - - $key = "$Owner-$ProjectNumber" - $lockKey = Get-DatabaseKey $Owner $ProjectNumber "field-cachelock" - - $lock = Get-Database -Key $lockKey - - if([string]::IsNullOrWhiteSpace($lock)){ - "No cache lock found for $Owner/$ProjectNumber. Cache will be ignored." | Write-MyDebug -Section "Get-ProjectFields" - return $null - } - - $cache = $script:fieldsCache[$key] - - if($lock -cne $cache.SafeId) { - "Cache lock mismatch for $Owner/$ProjectNumber. Cache safeId [$($cache.SafeId)], lock [$lock]. Cache will be ignored." | Write-MyDebug -Section "Get-ProjectFields" - $script:fieldsCache.Remove($key) - return $null - } - - "Getting fields cache for $Owner/$ProjectNumber with lock [$lock] and cache safeId [$($cache.SafeId)]" | Write-MyDebug -Section "Get-ProjectFields" - return $cache.List -} - -function setFieldsCache{ - param( - [Parameter(Mandatory,Position = 0)][string]$Owner, - [Parameter(Mandatory,Position = 1)][string]$ProjectNumber, - [Parameter(Mandatory,Position = 2)][object]$FieldList - ) - - $key = "$Owner-$ProjectNumber" - $lockKey = Get-DatabaseKey $Owner $ProjectNumber "field-cachelock" - - $safeId = [Guid]::NewGuid().ToString() - - # Save safeId to field-lock - Save-Database -Database $safeId -Key $lockKey - - # Set lock in database to prevent concurrent updates - $script:fieldsCache[$key] = @{ - List = $FieldList - SafeId = $safeId - } } \ No newline at end of file