Skip to content

Commit 54a5cfb

Browse files
Merge pull request #120 from StartAutomating/SharedQueries
Support for Shared queries
2 parents a3b4ec3 + 016f15b commit 54a5cfb

File tree

8 files changed

+276
-21
lines changed

8 files changed

+276
-21
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Write-FormatView -TypeName PSDevOps.SharedQuery -Property IsPublic, Path, Wiql -Wrap -GroupByProperty Project

Get-ADOWorkItem.ps1

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393

9494
# If provided, will only return the first N results from a query.
9595
[Parameter(ParameterSetName='/{Organization}/{Project}/{Team}/_apis/wit/wiql',ValueFromPipelineByPropertyName)]
96+
[Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
9697
[Alias('Top')]
9798
[uint32]
9899
$First,
@@ -103,6 +104,33 @@
103104
[switch]
104105
$WorkItemType,
105106

107+
# If set, will return work item shared queries
108+
[Parameter(Mandatory,ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
109+
[switch]
110+
$SharedQuery,
111+
112+
# If set, will return shared queries that have been deleted.
113+
[Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
114+
[switch]
115+
$IncludeDeleted,
116+
117+
# If provided, will return shared queries up to a given depth.
118+
[Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
119+
[ValidateRange(0,2)]
120+
[int]
121+
$Depth,
122+
123+
# If provided, will filter the shared queries returned
124+
[Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
125+
[int]
126+
$SharedQueryFilter,
127+
128+
# Determines how data from shared queries will be expanded. By default, expands all data.
129+
[Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
130+
[ValidateSet('All','Clauses','Minimal','None', 'Wiql')]
131+
[string]
132+
$ExpandSharedQuery = 'All',
133+
106134
# One or more fields.
107135
[Alias('Fields','Select')]
108136
[string[]]
@@ -170,6 +198,31 @@
170198
}
171199
#endregion Output Work Item
172200

201+
202+
#region ExpandSharedQueries
203+
$expandSharedQueries = {
204+
param([Parameter(ValueFromPipeline)]$node)
205+
process {
206+
if (-not $node) { return }
207+
$node.pstypenames.clear()
208+
foreach ($typeName in "$organization.SharedQuery",
209+
"$organization.$Project.SharedQuery",
210+
"PSDevOps.SharedQuery"
211+
) {
212+
$node.pstypenames.Add($typeName)
213+
}
214+
$node |
215+
Add-Member NoteProperty Organization $organization -Force -PassThru |
216+
Add-Member NoteProperty Project $Project -Force -PassThru |
217+
Add-Member NoteProperty Server $Server -Force -PassThru
218+
if ($node.haschildren) {
219+
$node.children |
220+
& $MyInvocation.MyCommand.ScriptBlock
221+
}
222+
}
223+
}
224+
#endregion ExpandSharedQueries
225+
173226
$allIDS = [Collections.ArrayList]::new()
174227
}
175228

@@ -181,6 +234,19 @@
181234
$selfSplat.Query = "Select [System.ID] from WorkItems Where [System.Title] contains '$title'"
182235
Get-ADOWorkItem @selfSplat
183236
}
237+
elseif ($psCmdlet.ParameterSetName -eq '/{Organization}/{Project}/_apis/wit/queries') {
238+
$myInvokeParams = @{} + $invokeParams
239+
$myInvokeParams.Url = "$Server".TrimEnd('/') + $psCmdlet.ParameterSetName
240+
241+
$myInvokeParams.QueryParameter = @{'$expand'= $ExpandSharedQuery}
242+
$myInvokeParams.UrlParameter = @{} + $psBoundParameters
243+
if ($IncludeDeleted) { $myInvokeParams.QueryParameter.'$includeDeleted' = $true }
244+
if ($First) { $myInvokeParams.QueryParameter.'$top' = $First}
245+
if ($Depth) { $myInvokeParams.QueryParameter.'$depth' = $Depth}
246+
$myInvokeParams.Property = @{Organization = $Organization;Project=$Project}
247+
Invoke-ADORestAPI @myInvokeParams | & $expandSharedQueries
248+
return
249+
}
184250
elseif (
185251
$PSCmdlet.ParameterSetName -in
186252
'/{Organization}/{Project}/_apis/wit/workitems/{id}',
@@ -198,7 +264,7 @@
198264
elseif ($PSCmdlet.ParameterSetName -eq '/{Organization}/{Project}/{Team}/_apis/wit/wiql')
199265
{
200266
$uri = "$Server".TrimEnd('/') + (. $ReplaceRouteParameter $PSCmdlet.ParameterSetName) + '?'
201-
$uri +=
267+
$uri +=
202268
@(if ($First) {
203269
"`$top=$First"
204270
}
@@ -214,7 +280,6 @@
214280
$realQuery += ' AND '
215281
}
216282

217-
218283
$realQuery +=
219284
@(
220285
if ($Project) {
@@ -261,6 +326,7 @@
261326
"api-version=$ApiVersion"
262327
}
263328
$invokeParams.Uri = $uri
329+
$invokeParams.Property = @{Organization = $Organization}
264330
$workItemTypes = Invoke-ADORestAPI @invokeParams
265331
$workItemTypes -replace '"":', '"_blank":' |
266332
ConvertFrom-Json |

New-ADOWorkItem.ps1

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,55 @@
1111
.Link
1212
Invoke-ADORestAPI
1313
#>
14-
[CmdletBinding(DefaultParameterSetName='ByID',SupportsShouldProcess=$true)]
14+
[CmdletBinding(DefaultParameterSetName='WorkItem',SupportsShouldProcess=$true)]
1515
[OutputType('PSDevOps.WorkItem')]
1616
param(
1717
# The InputObject
18-
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
18+
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
1919
[PSObject]
2020
$InputObject,
2121

2222
# The type of the work item.
23-
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
23+
[Parameter(Mandatory, ParameterSetName='WorkItem',ValueFromPipelineByPropertyName)]
2424
[Alias('WorkItemType')]
2525
[string]
2626
$Type,
2727

28+
# If set, will create a shared query for work items. The -InputObject will be passed to the body.
29+
[Parameter(Mandatory,ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
30+
[string]
31+
$QueryName,
32+
33+
# If provided, will create shared queries beneath a given folder.
34+
[Parameter(ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
35+
[Parameter(ParameterSetName='SharedQueryFolder',ValueFromPipelineByPropertyName)]
36+
[string]
37+
$QueryPath,
38+
39+
# If provided, create a shared query with a given WIQL.
40+
[Parameter(Mandatory, ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
41+
[string]
42+
$WIQL,
43+
44+
# If provided, the shared query created may be hierchical
45+
[Parameter(ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
46+
[ValidateSet('Flat','OneHop', 'Tree')]
47+
[string]
48+
$QueryType,
49+
50+
# The recursion option for use in a tree query.
51+
[Parameter(ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
52+
[ValidateSet('childFirst','parentFirst')]
53+
[string]
54+
$QueryRecursiveOption,
55+
56+
# If provided, create a shared query folder.
57+
[Parameter(Mandatory, ParameterSetName='SharedQueryFolder',ValueFromPipelineByPropertyName)]
58+
[string]
59+
$FolderName,
60+
2861
# The work item ParentID
29-
[Parameter(ValueFromPipelineByPropertyName)]
62+
[Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
3063
[string]
3164
$ParentID,
3265

@@ -42,13 +75,13 @@
4275
$Project,
4376

4477
# A collection of relationships for the work item.
45-
[Parameter(ValueFromPipelineByPropertyName)]
78+
[Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
4679
[Alias('Relationships')]
4780
[Collections.IDictionary]
4881
$Relationship,
4982

5083
# A list of comments to be added to the work item.
51-
[Parameter(ValueFromPipelineByPropertyName)]
84+
[Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
5285
[PSObject[]]
5386
$Comment,
5487

@@ -58,7 +91,7 @@
5891
$Tag,
5992

6093
# If set, will not validate rules.
61-
[Parameter(ValueFromPipelineByPropertyName)]
94+
[Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
6295
[Alias('BypassRules','NoRules','NoRule')]
6396
[switch]
6497
$BypassRule,
@@ -70,7 +103,7 @@
70103
$ValidateOnly,
71104

72105
# If set, will only validate rules, but will not update the work item.
73-
[Parameter(ValueFromPipelineByPropertyName)]
106+
[Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
74107
[Alias('SuppressNotifications','SkipNotification','SkipNotifications','NoNotify')]
75108
[switch]
76109
$SupressNotification,
@@ -160,6 +193,9 @@
160193
}
161194
}
162195
#endregion Output Work Item
196+
197+
198+
163199
$q = [Collections.Queue]::new()
164200
}
165201

@@ -177,14 +213,89 @@
177213

178214
$c++
179215
Write-Progress "Creating" "$type [$c/$t]" -PercentComplete ($c * 100 / $t) -Id $progId
180-
216+
$orgAndProject = @{Organization=$Organization;Project=$Project}
181217
$validFields =
182218
if ($script:ADOFieldCache.$uribase) {
183219
$script:ADOFieldCache.$uribase
184220
} else {
185-
Get-ADOField -Organization $Organization -Project $Project -Server $Server @invokeParams
221+
Get-ADOField @orgAndProject -Server $Server @invokeParams
186222
}
187223

224+
if ($psParameterSet -in 'SharedQuery', 'SharedQueryFolder') {
225+
if ($Server -ne 'https://dev.azure.com/' -and
226+
-not $PSBoundParameters.ApiVersion) {
227+
$ApiVersion = '2.0'
228+
}
229+
230+
$queryPathParts = @($QueryPath -split '/')
231+
$sharedQueries = $null
232+
foreach ($qp in $queryPathParts) {
233+
if (-not $qp) { continue }
234+
if (-not ($qp -as [guid])) {
235+
$sharedQueries = Get-ADOWorkItem -SharedQuery @orgAndProject -Depth 2
236+
break
237+
}
238+
}
239+
240+
if ($sharedQueries) {
241+
$queryPathId = $sharedQueries |
242+
Where-Object Path -eq $QueryPath |
243+
Select-Object -ExpandProperty ID
244+
if (-not $queryPathId) {
245+
Write-Error "Unable to find Query Path '$QueryPath'"
246+
continue
247+
} else {
248+
$QueryPath = $queryPathId
249+
}
250+
}
251+
252+
$uri = $uriBase, "_apis/wit/queries", $(if ($QueryPath) { $QueryPath }) -ne '' -join '/'
253+
$uri = $uri.ToString().TrimEnd('/')
254+
$uri += '?' +
255+
(@(
256+
if ($ApiVersion) { "api-version=$ApiVersion" }
257+
if ($validateOnly) { "validateWiqlOnly=true" }
258+
) -join '&')
259+
$invokeParams.uri = $uri
260+
261+
$queryObject = @{}
262+
if ($psParameterSet -eq 'SharedQueryFolder') {
263+
$queryObject['name'] = $FolderName
264+
$queryObject['isFolder'] = $true
265+
if ($QueryType) {
266+
$queryObject['queryType'] = $QueryType
267+
}
268+
if ($queryRecursionOption) {
269+
$queryObject['queryRecursionOption'] = $queryRecursionOption
270+
}
271+
272+
} else {
273+
$queryObject['name'] = $QueryName
274+
$queryObject['wiql'] = $WIQL
275+
276+
}
277+
278+
$invokeParams.Body = ConvertTo-Json $queryObject -Depth 100
279+
$invokeParams.Method = 'POST'
280+
$invokeParams.ContentType = 'application/json'
281+
$invokeParams.PSTypeName = @(
282+
"$Organization.$psParameterSet"
283+
"$Organization.$project.$psParameterSet"
284+
"PSDevOps.$psParameterSet"
285+
)
286+
if ($WhatIfPreference) {
287+
$invokeParams.Remove('PersonalAccessToken')
288+
$invokeParams
289+
continue
290+
}
291+
292+
if (-not $PSCmdlet.ShouldProcess("POST $uri with $($invokeParams.body)")) { continue }
293+
$restResponse = Invoke-ADORestAPI @invokeParams 2>&1
294+
$restResponse
295+
continue
296+
}
297+
298+
188299
$validFieldTable = $validFields | Group-Object ReferenceName -AsHashTable
189300
$uri = $uriBase, "_apis/wit/workitems", "`$$($Type)?" -join '/'
190301
if ($Server -ne 'https://dev.azure.com/' -and

PSDevOps.format.ps1xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,41 @@ $($ParentNode.CustomControl.CustomEntries.CustomEntry.CustomItem.ExpressionBindi
973973
</TableRowEntries>
974974
</TableControl>
975975
</View>
976+
<View>
977+
<Name>PSDevOps.SharedQuery</Name>
978+
<ViewSelectedBy>
979+
<TypeName>PSDevOps.SharedQuery</TypeName>
980+
</ViewSelectedBy>
981+
<GroupBy>
982+
<PropertyName>Project</PropertyName>
983+
</GroupBy>
984+
<TableControl>
985+
<TableHeaders>
986+
<TableColumnHeader>
987+
</TableColumnHeader>
988+
<TableColumnHeader>
989+
</TableColumnHeader>
990+
<TableColumnHeader>
991+
</TableColumnHeader>
992+
</TableHeaders>
993+
<TableRowEntries>
994+
<TableRowEntry>
995+
<Wrap />
996+
<TableColumnItems>
997+
<TableColumnItem>
998+
<PropertyName>IsPublic</PropertyName>
999+
</TableColumnItem>
1000+
<TableColumnItem>
1001+
<PropertyName>Path</PropertyName>
1002+
</TableColumnItem>
1003+
<TableColumnItem>
1004+
<PropertyName>Wiql</PropertyName>
1005+
</TableColumnItem>
1006+
</TableColumnItems>
1007+
</TableRowEntry>
1008+
</TableRowEntries>
1009+
</TableControl>
1010+
</View>
9761011
<View>
9771012
<Name>PSDevOps.Team</Name>
9781013
<ViewSelectedBy>

0 commit comments

Comments
 (0)