diff --git a/CHANGELOG.md b/CHANGELOG.md index feb2fb383..c8ca6af44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +# Fix Get-VSTeamWiql #314 +Changed file : Source\Public\Get-VSTeamWIQL.ps1 | Team parameter is now optional, removed dynamic param, completer & name-to-id trnasformer for ID parameter. +New files : + Source\Classes\VSTeamQueryCache.ps1 | implments cache for completer and transformer + Source\Classes\QueryCompleter.ps1 | argument completer - completes by friendly name + Source\Classes\QueryTransformToIDAttribute.ps1 | argument transformer turns name of query into its GUID + Source\Clasess\_classes.json | Add three classes above, **build classes into PSM1 file.** + +The change to the build destination in claseses.json follows on from https://github.com/PowerShell/PowerShell/issues/12132 (TLDR put classes directly into PSM1 and load with "Using module") and need two changes which are not in this PR +In build-module.ps1 change line 85 from +Copy-Item -Path ./Source/VSTeam.psm1 -Destination "$output/VSTeam.psm1" -Force +to +Get-Content -Path ./Source/VSTeam.psm1 | Out-File -Append -FilePath "$output/VSTeam.psm1" -Encoding ascii +and In source\VSteam.psm1 +remove line 10 +. "$PSScriptRoot\vsteam.classes.ps1" +And update preceding comment to reflect the change + ## 6.4.8 You can now tab complete Area and Resource of Invoke-VSTeamRequest. diff --git a/Source/Classes/QueryCompleter.ps1 b/Source/Classes/QueryCompleter.ps1 new file mode 100644 index 000000000..5742d26fc --- /dev/null +++ b/Source/Classes/QueryCompleter.ps1 @@ -0,0 +1,41 @@ +using namespace System.Collections +using namespace System.Collections.Generic +using namespace System.Management.Automation + +class QueryCompleter : IArgumentCompleter { + [IEnumerable[CompletionResult]] CompleteArgument( + [string] $CommandName, + [string] $ParameterName, + [string] $WordToComplete, + [Language.CommandAst] $CommandAst, + [IDictionary] $FakeBoundParameters) { + + $results = [List[CompletionResult]]::new() + + + # If the user has explictly added the -ProjectName parameter + # to the command use that instead of the default project. + $projectName = $FakeBoundParameters['ProjectName'] + + # Only use the default project if the ProjectName parameter was + # not used + if (-not $projectName) { + $projectName = _getDefaultProject + } + + # If there is no projectName by this point just return a empty + # list. + if ($projectName) { + + foreach ( $q in [vsteamquerycache]::GetCurrent() ) { + if ($q.name -like "*$WordToComplete*" -and $q.name -notmatch '\w') { + $results.Add([CompletionResult]::new($q.name)) + } + elseif ($q.name -like "*$WordToComplete*") { + $results.Add([CompletionResult]::new("'$($q.name.replace("'","''"))'", $q.name, 0, $q.name)) + } + } + } + return $results + } +} \ No newline at end of file diff --git a/Source/Classes/QueryTransformToIDAttribute.ps1 b/Source/Classes/QueryTransformToIDAttribute.ps1 new file mode 100644 index 000000000..680ff2777 --- /dev/null +++ b/Source/Classes/QueryTransformToIDAttribute.ps1 @@ -0,0 +1,16 @@ +using namespace System.Management.Automation + +class QueryTransformToIDAttribute : ArgumentTransformationAttribute { + [object] Transform( + [EngineIntrinsics]$EngineIntrinsics, + [object] $InputData + ) { + #If input data is not a GUID, and it is found as a name in the cache, + #then replace it with the match ID from the cache + if ($InputData -notmatch "[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}" -and + [VSTeamQueryCache]::queries.where({$_.name -eq $InputData}).count) { + $InputData = [VSTeamQueryCache]::queries.where({$_.name -eq $InputData}).id + } + return ($InputData) + } +} diff --git a/Source/Classes/VSTeamQueryCache.ps1 b/Source/Classes/VSTeamQueryCache.ps1 new file mode 100644 index 000000000..c733435d0 --- /dev/null +++ b/Source/Classes/VSTeamQueryCache.ps1 @@ -0,0 +1,22 @@ +class VSTeamQueryCache { + static [int] $timestamp = -1 + static [object] $queries = $null + static [Void] Update () { + $projectName = (_getDefaultProject) + if ($projectName) { + [VSTeamQueryCache]::queries = (_callAPi -ProjectName $projectName -Area wit -Resource queries -version (_getApiVersion core ) -QueryString @{'$depth'=1} + ).value.children | Where-Object -property isfolder -ne "True" | Select-Object Name,ID | Sort-Object Name + [VSTeamQueryCache]::timestamp = (Get-Date).TimeOfDay.TotalMinutes + } + } + static [void] Invalidate () { + [VSTeamQueryCache]::timestamp = -1 + } + static [object] GetCurrent () { + if ([VSTeamQueryCache]::timestamp -lt 0 -or + [VSTeamQueryCache]::timestamp -lt [datetime]::Now.TimeOfDay.TotalMinutes -5) { + [VSTeamQueryCache]::Update() + } + return ([VSTeamQueryCache]::queries) + } +} \ No newline at end of file diff --git a/Source/Classes/_classes.json b/Source/Classes/_classes.json index 6194da4c3..e4e1e86cb 100644 --- a/Source/Classes/_classes.json +++ b/Source/Classes/_classes.json @@ -1,5 +1,5 @@ { - "outputFile": "vsteam.classes.ps1", + "outputFile": "vsteam.psm1", "fileType": "classes", "files": [ "InvokeCompleter.ps1", @@ -13,11 +13,14 @@ "WorkItemTypeCompleter.ps1", "ProcessValidateAttribute.ps1", "ProjectValidateAttribute.ps1", + "QueryCompleter.ps1", + "QueryTransformToIDAttribute.ps1", "UncachedProjectValidateAttribute.ps1", "WorkItemTypeValidateAttribute.ps1", "VSTeamVersions.ps1", "VSTeamProjectCache.ps1", "VSTeamProcessCache.ps1", + "VSTeamQueryCache.ps1", "VSTeamDirectory.ps1", "VSTeamLeaf.ps1", "VSTeamPools.ps1", diff --git a/Source/Public/Get-VSTeamWiql.ps1 b/Source/Public/Get-VSTeamWiql.ps1 index 00b34dd02..cf44bcca3 100644 --- a/Source/Public/Get-VSTeamWiql.ps1 +++ b/Source/Public/Get-VSTeamWiql.ps1 @@ -1,75 +1,68 @@ function Get-VSTeamWiql { [CmdletBinding(DefaultParameterSetName = 'ByID')] param( + [QueryTransformToID()] + [ArgumentCompleter([QueryCompleter])] [Parameter(ParameterSetName = 'ByID', Mandatory = $true, Position = 0)] [string] $Id, - [Parameter(ParameterSetName = 'ByQuery', Mandatory = $true, Position = 0)] + [Parameter(ParameterSetName = 'ByQuery', Mandatory = $true)] [string] $Query, - [Parameter(Mandatory = $true, Position = 1)] [string] $Team, - [int] $Top = 100, + [Parameter(Position = 2)] + [ProjectValidateAttribute()] + [ArgumentCompleter([ProjectCompleter])] + $ProjectName, + [int] $Top = 100, [Switch] $TimePrecision, - [Switch] $Expand ) - DynamicParam { - #$arrSet = Get-VSTeamProject | Select-Object -ExpandProperty Name - _buildProjectNameDynamicParam -mandatory $true #-arrSet $arrSet - } - - Process { - - # Bind the parameter to a friendly variable - $ProjectName = $PSBoundParameters["ProjectName"] - - $QueryString = @{ - '$top' = $Top - timePrecision = $TimePrecision + process { + #build paramters for _callAPI + $params = @{ + ProjectName = $ProjectName + Area = 'wit' + Resource = 'wiql' + Version = [VSTeamVersions]::Core + QueryString = @{ + '$top' = $Top + timePrecision = $TimePrecision + } } - - # Call the REST API if ($Query) { - - $body = (@{ - query = $Query - }) | ConvertTo-Json - - $resp = _callAPI -ProjectName $ProjectName -Team $Team -Area 'wit' -Resource 'wiql' ` - -method "POST" -ContentType "application/json" ` - -Version $(_getApiVersion Core) ` - -Querystring $QueryString ` - -Body $body + $params['body'] = @{query = $Query} | ConvertTo-Json + $params['method'] = 'POST' + $params['ContentType'] = 'application/json' } else { - $resp = _callAPI -ProjectName $ProjectName -Team $Team -Area 'wit' -Resource 'wiql' ` - -Version $(_getApiVersion Core) -id "$Id" ` - -Querystring $QueryString + $params['id']= $Id + $params['Team']= $Team } + $resp = _callAPI @params if ($Expand) { + #Handle queries for work item links + if ($resp.queryResultType -eq 'workItemLink') { + Add-Member -InputObject $resp -MemberType NoteProperty -Name Workitems -Value @() + $Ids = $resp.workItemRelations.Target.id + } + else { $Ids = $resp.workItems.id } - [array]$Ids = $resp.workItems.id - $Fields = $resp.columns.referenceName - - $resp.workItems = @() #splitting id array by 200, since a maximum of 200 ids are allowed per call $countIds = $Ids.Count $resp.workItems = for ($beginRange = 0; $beginRange -lt $countIds; $beginRange += 200) { - - $endRange = ($beginRange + 199) - - if ($endRange -gt $countIds) { - $idArray = $Ids[$beginRange..($countIds - 1)] + #in case strict mode is on,pick lesser of 0..199 and 0..count-1 + $endRange = [math]::Min(($beginRange + 199),($countIds - 1)) + #if query contains "*" don't specify fields, otherwise use fields returned. + if ($Query -match "\*") { + Get-VSTeamWorkItem -Id $Ids[$beginRange..$endRange] } else { - $idArray = $Ids[$beginRange..($endRange)] + Get-VSTeamWorkItem -Id $Ids[$beginRange..$endRange] -Fields $resp.columns.referenceName } - - (Get-VSTeamWorkItem -Fields $Fields -Id $idArray).value } } @@ -77,4 +70,4 @@ function Get-VSTeamWiql { return $resp } -} \ No newline at end of file +}