Skip to content

Commit

Permalink
Fix Get-VSTeamWiql MethodsAndPractices#314
Browse files Browse the repository at this point in the history
  • Loading branch information
James O'Neill authored and James O'Neill committed May 14, 2020
1 parent fd9f922 commit 9feee71
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 46 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
41 changes: 41 additions & 0 deletions Source/Classes/QueryCompleter.ps1
Original file line number Diff line number Diff line change
@@ -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
}
}
16 changes: 16 additions & 0 deletions Source/Classes/QueryTransformToIDAttribute.ps1
Original file line number Diff line number Diff line change
@@ -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)
}
}
22 changes: 22 additions & 0 deletions Source/Classes/VSTeamQueryCache.ps1
Original file line number Diff line number Diff line change
@@ -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)
}
}
5 changes: 4 additions & 1 deletion Source/Classes/_classes.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"outputFile": "vsteam.classes.ps1",
"outputFile": "vsteam.psm1",
"fileType": "classes",
"files": [
"InvokeCompleter.ps1",
Expand All @@ -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",
Expand Down
83 changes: 38 additions & 45 deletions Source/Public/Get-VSTeamWiql.ps1
Original file line number Diff line number Diff line change
@@ -1,80 +1,73 @@
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
}
}

_applyTypesToWiql -item $resp

return $resp
}
}
}

0 comments on commit 9feee71

Please sign in to comment.