private/projectDatabase/project_database_Async.ps1

Set-MyInvokeCommandAlias -Alias GitHub_UpdateProjectV2ItemFieldValueAsync -Command 'Import-Module {projecthelper} ; Invoke-GitHubUpdateItemValues -ProjectId {projectid} -ItemId {itemid} -FieldId {fieldid} -Value "{value}" -Type {type}'

function Sync-ProjectDatabaseAsync{
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([bool])]
    param(
        [Parameter(Position = 0)][string]$Owner,
        [Parameter(Position = 1)][int]$ProjectNumber,
        [Parameter()][int]$SyncBatchSize = 30
    )

    ($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}

    if(! $(Test-ProjectDatabaseStaged -Owner $Owner -ProjectNumber $ProjectNumber)){
        "Nothing to commit" | Write-MyHost
        return
    }

    $dbkey = GetDatabaseKey -Owner $Owner -ProjectNumber $ProjectNumber

    $db = Get-Project -Owner $Owner -ProjectNumber $ProjectNumber

    # Send update to project
    $result = Sync-ProjectAsync -Database $db -SyncBatchSize $SyncBatchSize
    if ($null -eq $result) {
        return $false
    }

    # Clear the values that are the same
    $different = New-Object System.Collections.Hashtable
    $equal = New-Object System.Collections.Hashtable

    # Make a copy of the staged keys before processing
    $stagedItemKeys = @($db.Staged.Keys)

    foreach($itemId in $stagedItemKeys){

        # Make a copy of the staged fields keys before processing
        $stagedFieldsKeys = @($db.Staged.$itemId.Keys)

        # Process each staged field for the item
        foreach($fieldId in $stagedFieldsKeys){
            $fieldName = $db.fields.$fieldId.name

            # Skip if actual and staged values are the same
            $stagedV = $db.Staged.$itemId.$fieldId.Value
            $actualV = $db.items.$itemId.$fieldName

            if(!($stagedV -eq $actualV)){
                # Create refe to failing
                $different."$($itemId)_$($Fieldid)" = @{
                    Id = $itemId
                    Field = $fieldId
                    Staged = $stagedV
                    Actual = $actualV
                }
            } else {
                # Create refe success
                $equal."$($itemId)_$($Fieldid)" = @{
                    Id = $itemId
                    Field = $fieldId
                }
                # Remove staged field
                $db.Staged.$itemId.Remove($fieldId)
            }
        }

        # Remove staged item if all fields are removed
        if($db.Staged.$itemId.Keys.Count -eq 0){
            $db.Staged.Remove($itemId)
        }

    }

    $SyncedCount = $equal.Keys.Count
    $NotSyncedCount = $different.Keys.Count

    #null Staged if empty
    if($db.Staged.Keys.Count -eq 0){
        $db.Staged = $null
    }
    
    Save-Database -Key $dbkey -Database $db

    if($different.Count -ne 0){
        "Not all Staged values are not equal to actual values" | Write-MyError
        $different | convertto-json | Write-MyError
        return $false
    }

    "Synced $SyncedCount values (Failed: $NotSyncedCount)" | Write-MyHost

    return $true

}

function Sync-ProjectAsync{
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Position = 0)][object]$Database,
        [Parameter()][int]$SyncBatchSize = 30
    )

    $db = $Database
    $calls = @()
    $callsBatch = @()

    foreach($itemId in $db.Staged.Keys){
        foreach($fieldId in $db.Staged.$itemId.Keys){

            # Get actual value on the database
            $fieldName = $db.fields.$fieldId

            # Get actual value on the database
            $actualValue = $db.items.$itemId.$fieldName

            # Skip if database has already the same value
            if($actualValue -eq $db.Staged.$itemId.$fieldId.Value){
                "Skipping [$itemId/$fieldName] as actual value is the same as staged value [$actualValue]" | Write-MyHost
                continue
            }

            $projectId = $db.ProjectId
            $value = $db.Staged.$itemId.$fieldId.Value
            $type = ConvertTo-UpdateType $db.Staged.$itemId.$fieldId.Field.dataType

            $params = @{
                projecthelper = $MODULE_PATH
                projectid = $projectId
                itemid = $itemId
                fieldid = $fieldId
                value = $value
                type = $type
            }

            "Calling to save [$projectId/$itemId/$fieldId ($type) = $value ]" | Write-MyHost

            $job = Start-MyJob -Command GitHub_UpdateProjectV2ItemFieldValueAsync -Parameters $params

            $call = [PSCustomObject]@{
                job = $job
                projectId = $projectId
                itemId = $itemId
                value = $value
                fieldId = $fieldId
                type = $type
                fieldName = $db.fields.$fieldId.name
            }

            $calls += $call
            $callsBatch += $call

            # Call batch size if we reached the maximum batch size
            if($callsBatch.count -eq $SyncBatchSize){
                Waiting -Calls $callsBatch
                $callsBatch = @() # Reset the batch
            }

        }
    }

    #Remaining calls
    Waiting -Calls $callsBatch

    # Process all the calls
    foreach($call in $calls){

        $result = Receive-Job -Job $call.job

        $projectId = $call.projectId
        $itemId = $call.itemId
        $fieldId = $call.fieldId
        $fieldName = $call.fieldName
        $value = $call.value

        if ($null -eq $result.data.updateProjectV2ItemFieldValue.projectV2Item) {
            # TODO: Maybe worth checking response values to confirm change was made correctly even without error
            "Updating Project Item call Failed [$itemId/$fieldName/$value]" | Write-MyError
            continue
        }

        "Saving to database [$projectId/$itemId/$fieldName ($type) = $value ]" | Write-MyHost
        $db.items.$itemId.$fieldName = $value
    }

    return $db
}

function Waiting($Calls){
    $waitingJobs = $Calls.job

    $all = $Calls.Count
    "Waiting for [$all] jobs to complete " | Write-MyHost -noNewline

    while($waitingJobs.Count -ne 0){

        $waitings = $waitingJobs | Wait-Job -Any

        "." | Write-MyHost -NoNewline

        # Remove completed jobs from the waiting list
        $waitingJobs = $waitingJobs | Where-Object { $_.Id -ne $waitings.Id }

    }

    $completed = $Calls | Where-Object { $_.job.State -eq 'Completed' } | Measure-Object | Select-Object -ExpandProperty Count
    $failed = $Calls | Where-Object { $_.job.State -eq 'Failed' } | Measure-Object | Select-Object -ExpandProperty Count
    $all = $Calls.Count
    
    "" | Write-MyHost
    "Completed [$completed] Failed [$failed]" | Write-MyHost
}

function Sync-Project{
    [CmdletBinding()]
    param(
        [Parameter(Position = 0)][object]$Database
    )

    $db = $Database

    foreach($idemId in $db.Staged.Keys){
        foreach($fieldId in $db.Staged.$idemId.Keys){

            # Get actual value on the database
            $fieldName = $db.fields.$fieldId.name

            # Skip if database has already the same value as staged
            if($db.items.$itemId.$fieldName -eq $db.Staged.$itemId.$fieldId.Value){
                "Skipping [$itemId/$fieldName] as actual value is the same as staged value [$actualValue]" | Write-MyHost
                continue
            }

            $project_id = $db.ProjectId
            $item_id = $idemId
            $field_id = $fieldId
            $value = $db.Staged.$idemId.$fieldId.Value
            $type = ConvertTo-UpdateType $db.Staged.$idemId.$fieldId.Field.dataType

            $params = @{
                projectid = $project_id
                itemid = $item_id
                fieldid = $field_id
                value = $value
                type = $type
            }

            "Saving [$project_id/$item_id/$field_id ($type) = $value ]" | Write-MyHost

            $result = Invoke-MyCommand -Command GitHub_UpdateProjectV2ItemFieldValue -Parameters $params

            if ($null -eq $result) {
                "Updating Project Item Field [$item_id/$field_id/$value]" | Write-MyError
                return $null
            }

            # update database with change
            $db.items.$item_id.$fieldName = $value

        }
    }

    return $db
}