From c0ebf04350bc09c702072c75bd0a499b8fa42271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 03:23:49 +0200 Subject: [PATCH 01/24] added team name to _buildRequestURI and _callAPI --- Source/Private/common.ps1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Private/common.ps1 b/Source/Private/common.ps1 index b756b75de..464a236b3 100644 --- a/Source/Private/common.ps1 +++ b/Source/Private/common.ps1 @@ -65,6 +65,7 @@ function _hasAccount { function _buildRequestURI { [CmdletBinding()] param( + [string]$team, [string]$resource, [string]$area, [string]$id, @@ -90,6 +91,10 @@ function _buildRequestURI { $sb.Append("/$projectName") | Out-Null } + if ($team) { + $sb.Append("/$team") | Out-Null + } + $sb.Append("/_apis/") | Out-Null if ($area) { @@ -588,8 +593,9 @@ function _callAPI { [object]$body, [string]$InFile, [string]$OutFile, - [string]$ContentType, + [string]$ContentType, [string]$ProjectName, + [string]$Team, [string]$Url, [object]$QueryString ) @@ -628,7 +634,7 @@ function _callAPI { } # We have to remove any extra parameters not used by Invoke-RestMethod - $extra = 'Area', 'Resource', 'SubDomain', 'Id', 'Version', 'JSON', 'ProjectName', 'Url', 'QueryString' + $extra = 'Area', 'Resource', 'SubDomain', 'Id', 'Version', 'JSON', 'ProjectName', 'Team', 'Url', 'QueryString' foreach ($e in $extra) { $params.Remove($e) | Out-Null } try { From 17846e67add72446987e7438915821f8a2f397e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 03:24:17 +0200 Subject: [PATCH 02/24] added Wiql types --- Source/Private/applyTypes.ps1 | 7 +++++ Source/types/Team.Wiql.ps1xml | 49 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 Source/types/Team.Wiql.ps1xml diff --git a/Source/Private/applyTypes.ps1 b/Source/Private/applyTypes.ps1 index 91bf9324f..8e55440f5 100644 --- a/Source/Private/applyTypes.ps1 +++ b/Source/Private/applyTypes.ps1 @@ -25,6 +25,13 @@ function _applyTypesToWorkItem { } } +function _applyTypesToWiql { + param($item) + if ($item) { + $item.PSObject.TypeNames.Insert(0, 'Team.Wiql') + } +} + function _applyTypesToUser { param( [Parameter(Mandatory = $true)] diff --git a/Source/types/Team.Wiql.ps1xml b/Source/types/Team.Wiql.ps1xml new file mode 100644 index 000000000..fab3e0d4d --- /dev/null +++ b/Source/types/Team.Wiql.ps1xml @@ -0,0 +1,49 @@ + + + + Team.Wiql + + + QueryType + $this.PSObject.Properties['queryType'].Value + + + QueryResultType + $this.PSObject.Properties['queryResultType'].Value + + + AsOf + $this.PSObject.Properties['asOf'].Value + + + Columns + $this.PSObject.Properties['columns'].Value + + + SortColumns + $this.PSObject.Properties['sortColumns'].Value + + + WorkItemIDs + [int[]]$this.PSObject.Properties['workItems'].Value.id + + + WorkItems + $this.PSObject.Properties['workItems'].Value + + + PSStandardMembers + + + DefaultDisplayPropertySet + + queryType + WorkItemIDs + workItems + + + + + + + \ No newline at end of file From 1d18f3955b7b5eef417cd1e6526076de152116d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 03:24:31 +0200 Subject: [PATCH 03/24] added format files --- Source/formats/Team.Wiql.ListView.ps1xml | 28 ++++++++++++++++ Source/formats/Team.Wiql.TableView.ps1xml | 39 +++++++++++++++++++++++ Source/formats/_formats.json | 2 ++ 3 files changed, 69 insertions(+) create mode 100644 Source/formats/Team.Wiql.ListView.ps1xml create mode 100644 Source/formats/Team.Wiql.TableView.ps1xml diff --git a/Source/formats/Team.Wiql.ListView.ps1xml b/Source/formats/Team.Wiql.ListView.ps1xml new file mode 100644 index 000000000..cdde42ec8 --- /dev/null +++ b/Source/formats/Team.Wiql.ListView.ps1xml @@ -0,0 +1,28 @@ + + + + + Team.Wiql.ListView + + Team.Wiql + + + + + + + queryType + + + WorkItemIDs + + + workItems + + + + + + + + \ No newline at end of file diff --git a/Source/formats/Team.Wiql.TableView.ps1xml b/Source/formats/Team.Wiql.TableView.ps1xml new file mode 100644 index 000000000..c8c54361a --- /dev/null +++ b/Source/formats/Team.Wiql.TableView.ps1xml @@ -0,0 +1,39 @@ + + + + + Team.Wiql.TableView + + Team.Wiql + + + + + + + + + + + + + + + + + + queryType + + + WorkItemIDs + + + workItems + + + + + + + + \ No newline at end of file diff --git a/Source/formats/_formats.json b/Source/formats/_formats.json index 009abec4a..c965e5393 100644 --- a/Source/formats/_formats.json +++ b/Source/formats/_formats.json @@ -89,6 +89,8 @@ "Team.PSDrive.Leaf.Default.TableView.ps1xml", "Team.WorkItemType.TableView.ps1xml", "Team.WorkItemType.ListView.ps1xml", + "Team.Wiql.TableView.ps1xml", + "Team.Wiql.ListView.ps1xml", "Team.WorkItem.TableView.ps1xml", "Team.WorkItem.ListView.ps1xml", "Team.JobRequest.TableView.ps1xml" From 64bc1dd1e05321e3805936f2e629f78f2c551b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 03:25:00 +0200 Subject: [PATCH 04/24] added new cmdlet for querying workitems by WIQL --- Source/Public/Get-VSTeamWiql.ps1 | 82 ++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Source/Public/Get-VSTeamWiql.ps1 diff --git a/Source/Public/Get-VSTeamWiql.ps1 b/Source/Public/Get-VSTeamWiql.ps1 new file mode 100644 index 000000000..045e032ad --- /dev/null +++ b/Source/Public/Get-VSTeamWiql.ps1 @@ -0,0 +1,82 @@ +function Get-VSTeamWiql { + [CmdletBinding(DefaultParameterSetName = 'ByID')] + param( + [Parameter(ParameterSetName = 'ByID', Mandatory = $true, Position = 0)] + [string] $Id, + + [Parameter(ParameterSetName = 'ByQuery', Mandatory = $true, Position = 0)] + [string] $Query, + + [Parameter(Mandatory = $true, Position = 1)] + [string] $Team, + + [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 + } + + # 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 $([VSTeamVersions]::Core) ` + -Querystring $QueryString ` + -Body $body + } + else { + $resp = _callAPI -ProjectName $ProjectName -Team $Team -Area 'wit' -Resource 'wiql' ` + -Version $([VSTeamVersions]::Core) -id "$Id" ` + -Querystring $QueryString + } + + $workItems = @() + if ($Expand) { + + $Ids = $resp.workItems | Select-Object -ExpandProperty id + $Fields = $resp.columns | Select-Object -ExpandProperty referenceName + + $resp.workItems = @() + #splitting id array by 200, since a maximum of 200 ids are allowed per call + $countIds = $Ids.Count + for ($beginRange = 0; $beginRange -lt $countIds; $beginRange += 200) { + + $endRange = ($beginRange + 199) + + if ($endRange -gt $countIds) { + $idArray = $Ids[$beginRange..($countIds - 1)] + } + else { + $idArray = $Ids[$beginRange..($endRange)] + } + + $resp.workItems += (Get-VSTeamWorkItem -Fields $Fields -Ids $idArray).value + } + + } + + _applyTypesToWiql -item $resp + + return $resp + } +} \ No newline at end of file From c2433b6f7ff380799d98fde6bedc61a207e07092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 23:31:19 +0200 Subject: [PATCH 05/24] added unit tests for wiql cmdlet --- unit/test/wiql.Tests.ps1 | 166 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 unit/test/wiql.Tests.ps1 diff --git a/unit/test/wiql.Tests.ps1 b/unit/test/wiql.Tests.ps1 new file mode 100644 index 000000000..603899c69 --- /dev/null +++ b/unit/test/wiql.Tests.ps1 @@ -0,0 +1,166 @@ +Set-StrictMode -Version Latest + +InModuleScope VSTeam { + [VSTeamVersions]::Account = 'https://dev.azure.com/test' + + Describe 'wiql' { + # Mock the call to Get-Projects by the dynamic parameter for ProjectName + Mock Invoke-RestMethod { return @() } -ParameterFilter { + $Uri -like "*_apis/projects*" + } + + . "$PSScriptRoot\mocks\mockProjectNameDynamicParamNoPSet.ps1" + + $workItem = @{ + id = 47 + url = "https://dev.azure.com/test/_apis/wit/workItems/47" + } + + $column = @{ + referenceName = "System.Id" + name = "ID" + url = "https://dev.azure.com/razorspoint-test/_apis/wit/fields/System.Id" + } + + $sortColumn = @{ + field = $column + descending = $false + } + + $wiqlResult = @{ + querytype = "flat" + queryTypeResult = "worItem" + asOf = "2019-10-03T18:35:09.117Z" + columns = @($column) + sortColumns = @($sortColumn) + workItems = @($workItem) + } + + $expandedWorkItems = @{ + count = 1 + value = @($workItem) + } + + Context 'Get-Wiql' { + Mock Invoke-RestMethod { + # If this test fails uncomment the line below to see how the mock was called. + Write-Host $args + + return $wiqlResult + } + + # function is mocked because it is used when switch 'Expanded' is being used. + Mock Get-VSTeamWorkItem { + # If this test fails uncomment the line below to see how the mock was called. + Write-Host $args + + return $expandedWorkItems + } + + It 'Get work items with custom WIQL query' { + $Global:PSDefaultParameterValues.Remove("*:projectName") + $wiqlQuery = "Select [System.Id], [System.Title], [System.State] From WorkItems" + Get-VSTeamWiql -ProjectName "test" -Team "test team" -Query $wiqlQuery + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '`{*' -and # Make sure the body is an object + $Body -like "*[System.Id]*" -and + $Body -like "*[System.Title]*" -and + $Body -like "*[System.State]*" -and + $Body -like '*`}' -and # Make sure the body is an object + $ContentType -eq 'application/json' -and + $Uri -eq "https://dev.azure.com/test/test/test team/_apis/wit/wiql/?api-version=$([VSTeamVersions]::Core)&`$top=100" + } + } + + It 'Get work items with custom WIQL query with -Top 250' { + $Global:PSDefaultParameterValues.Remove("*:projectName") + $wiqlQuery = "Select [System.Id], [System.Title], [System.State] From WorkItems" + Get-VSTeamWiql -ProjectName "test" -Team "test team" -Query $wiqlQuery -Top 250 + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '`{*' -and # Make sure the body is an object + $Body -like "*[System.Id]*" -and + $Body -like "*[System.Title]*" -and + $Body -like "*[System.State]*" -and + $Body -like '*`}' -and # Make sure the body is an object + $ContentType -eq 'application/json' -and + $Uri -eq "https://dev.azure.com/test/test/test team/_apis/wit/wiql/?api-version=$([VSTeamVersions]::Core)&`$top=250" + } + } + + It 'Get work items with custom WIQL query with -Top 0' { + $Global:PSDefaultParameterValues.Remove("*:projectName") + $wiqlQuery = "Select [System.Id], [System.Title], [System.State] From WorkItems" + Get-VSTeamWiql -ProjectName "test" -Team "test team" -Query $wiqlQuery -Top 0 + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '`{*' -and # Make sure the body is an object + $Body -like "*[System.Id]*" -and + $Body -like "*[System.Title]*" -and + $Body -like "*[System.State]*" -and + $Body -like '*`}' -and # Make sure the body is an object + $ContentType -eq 'application/json' -and + $Uri -eq "https://dev.azure.com/test/test/test team/_apis/wit/wiql/?api-version=$([VSTeamVersions]::Core)" + } + } + + It 'Get work items with custom WIQL query with expanded work items' { + $Global:PSDefaultParameterValues.Remove("*:projectName") + $wiqlQuery = "Select [System.Id], [System.Title], [System.State] From WorkItems" + Get-VSTeamWiql -ProjectName "test" -Team "test team" -Query $wiqlQuery -Expand + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '`{*' -and # Make sure the body is an object + $Body -like "*[System.Id]*" -and + $Body -like "*[System.Title]*" -and + $Body -like "*[System.State]*" -and + $Body -like '*`}' -and # Make sure the body is an object + $ContentType -eq 'application/json' -and + $Uri -eq "https://dev.azure.com/test/test/test team/_apis/wit/wiql/?api-version=$([VSTeamVersions]::Core)&`$top=100" + } + } + + It 'Get work items with custom WIQL query with time precision' { + $Global:PSDefaultParameterValues.Remove("*:projectName") + $wiqlQuery = "Select [System.Id], [System.Title], [System.State] From WorkItems" + Get-VSTeamWiql -ProjectName "test" -Team "test team" -Query $wiqlQuery -TimePrecision + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Method -eq 'Post' -and + $Body -like '`{*' -and # Make sure the body is an object + $Body -like "*[System.Id]*" -and + $Body -like "*[System.Title]*" -and + $Body -like "*[System.State]*" -and + $Body -like '*`}' -and # Make sure the body is an object + $ContentType -eq 'application/json' -and + $Uri -eq "https://dev.azure.com/test/test/test team/_apis/wit/wiql/?api-version=$([VSTeamVersions]::Core)&`$top=100&timePrecision=True" + } + } + + It 'Get work items with query ID query' { + $Global:PSDefaultParameterValues.Remove("*:projectName") + Get-VSTeamWiql -ProjectName "test" -Team "test team" -Id 1 + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Uri -eq "https://dev.azure.com/test/test/test team/_apis/wit/wiql/1?api-version=$([VSTeamVersions]::Core)&`$top=100" + } + } + + It 'Get work items with query ID query with expanded work items' { + $Global:PSDefaultParameterValues.Remove("*:projectName") + Get-VSTeamWiql -ProjectName "test" -Team "test team" -Id 1 -Expand + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Uri -eq "https://dev.azure.com/test/test/test team/_apis/wit/wiql/1?api-version=$([VSTeamVersions]::Core)&`$top=100" + } + } + + + } + } +} \ No newline at end of file From 4769c10dcef9d872db417b9856bb797170d0a6ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 23:31:47 +0200 Subject: [PATCH 06/24] bug fix for wiql cmdlet --- Source/Public/Get-VSTeamWiql.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Public/Get-VSTeamWiql.ps1 b/Source/Public/Get-VSTeamWiql.ps1 index 045e032ad..d614d8517 100644 --- a/Source/Public/Get-VSTeamWiql.ps1 +++ b/Source/Public/Get-VSTeamWiql.ps1 @@ -17,8 +17,8 @@ function Get-VSTeamWiql { [Switch] $Expand ) DynamicParam { - $arrSet = Get-VSTeamProject | Select-Object -ExpandProperty Name - _buildProjectNameDynamicParam -mandatory $true -arrSet $arrSet + #$arrSet = Get-VSTeamProject | Select-Object -ExpandProperty Name + _buildProjectNameDynamicParam -mandatory $true #-arrSet $arrSet } Process { @@ -53,7 +53,7 @@ function Get-VSTeamWiql { $workItems = @() if ($Expand) { - $Ids = $resp.workItems | Select-Object -ExpandProperty id + [array]$Ids = $resp.workItems | Select-Object -ExpandProperty id $Fields = $resp.columns | Select-Object -ExpandProperty referenceName $resp.workItems = @() From ad025baed0e9d02e9377a07c7d3364f9f8db915d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 23:32:05 +0200 Subject: [PATCH 07/24] added documentation for wiql cdmlet --- .docs/Get-VSTeamWiql.md | 93 ++++++++++++++++++++++++++++++++ .docs/synopsis/Get-VSTeamWiql.md | 1 + 2 files changed, 94 insertions(+) create mode 100644 .docs/Get-VSTeamWiql.md create mode 100644 .docs/synopsis/Get-VSTeamWiql.md diff --git a/.docs/Get-VSTeamWiql.md b/.docs/Get-VSTeamWiql.md new file mode 100644 index 000000000..f8dbe2048 --- /dev/null +++ b/.docs/Get-VSTeamWiql.md @@ -0,0 +1,93 @@ + + +# Get-VSTeamWiqlItem + +## SYNOPSIS + + + +## SYNTAX + +## DESCRIPTION + + + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```PowerShell +PS C:\> Get-VSTeamWiql -Query "Select [System.Id], [System.Title], [System.State] From WorkItems" -Team "MyProject Team" -Project "MyProject" -Expand +``` + +This command gets work items via a WIQL query and expands the return work items with only the selected fields System.Id, System.Title and System.State. + +### -------------------------- EXAMPLE 2 -------------------------- + +```PowerShell +PS C:\> Get-VSTeamWiql -Query "Select [System.Id], [System.Title], [System.State] From WorkItems" -Team "MyProject Team" -Project "MyProject" +``` + +This command gets work items via a WIQL query and returns the WIQL query result with only work item IDs. + +## PARAMETERS + +### -Id + +The id query to return work items for. This is the ID of any saved query within a team in a project + +```yaml +Type: Int32 +Parameter Sets: ByID +Required: True +``` + +### -Query + +The WIQL query. For the syntax check [the official documentation](https://docs.microsoft.com/en-us/azure/devops/boards/queries/wiql-syntax?view=azure-devops). + +```yaml +Type: String +Parameter Sets: ByQuery +Required: True +``` + +### -Top + +The max number of results to return. + +```yaml +Type: String +Required: False +Default value: 100 +``` + +### -TimePrecision + +Whether or not to use time precision. + +```yaml +Type: Switch +``` + +### -Expand + +The expand the work items with the selected attributes in the WIQL query. + +```yaml +Type: Switch +``` + +## INPUTS + +### System.String + +ProjectName + +## OUTPUTS + +## NOTES + +If you do not set the default project by called Set-VSTeamDefaultProject before calling Get-VSTeamWiql you will have to type in the names. + +## RELATED LINKS diff --git a/.docs/synopsis/Get-VSTeamWiql.md b/.docs/synopsis/Get-VSTeamWiql.md new file mode 100644 index 000000000..fe760e523 --- /dev/null +++ b/.docs/synopsis/Get-VSTeamWiql.md @@ -0,0 +1 @@ +Returns work items from the given WIQL query or a saved query by ID from your projects team. \ No newline at end of file From bd203251ce3b5bbd663f18603e66e7ce848d47b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 23:36:53 +0200 Subject: [PATCH 08/24] fixed include link --- .docs/Get-VSTeamWiql.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.docs/Get-VSTeamWiql.md b/.docs/Get-VSTeamWiql.md index f8dbe2048..cd593f990 100644 --- a/.docs/Get-VSTeamWiql.md +++ b/.docs/Get-VSTeamWiql.md @@ -4,13 +4,13 @@ ## SYNOPSIS - + ## SYNTAX ## DESCRIPTION - + ## EXAMPLES From 5b2c97e960e6ce0490dee02a361975a10a8406f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 23:39:46 +0200 Subject: [PATCH 09/24] removed unsued variable --- Source/Public/Get-VSTeamWiql.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Public/Get-VSTeamWiql.ps1 b/Source/Public/Get-VSTeamWiql.ps1 index d614d8517..badc8f67b 100644 --- a/Source/Public/Get-VSTeamWiql.ps1 +++ b/Source/Public/Get-VSTeamWiql.ps1 @@ -50,7 +50,6 @@ function Get-VSTeamWiql { -Querystring $QueryString } - $workItems = @() if ($Expand) { [array]$Ids = $resp.workItems | Select-Object -ExpandProperty id From 5211cf5e409c842278eede8cb17e1d94282086f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Thu, 3 Oct 2019 23:43:02 +0200 Subject: [PATCH 10/24] commented debug logging --- unit/test/wiql.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/test/wiql.Tests.ps1 b/unit/test/wiql.Tests.ps1 index 603899c69..65d067d63 100644 --- a/unit/test/wiql.Tests.ps1 +++ b/unit/test/wiql.Tests.ps1 @@ -44,7 +44,7 @@ InModuleScope VSTeam { Context 'Get-Wiql' { Mock Invoke-RestMethod { # If this test fails uncomment the line below to see how the mock was called. - Write-Host $args + #Write-Host $args return $wiqlResult } @@ -52,7 +52,7 @@ InModuleScope VSTeam { # function is mocked because it is used when switch 'Expanded' is being used. Mock Get-VSTeamWorkItem { # If this test fails uncomment the line below to see how the mock was called. - Write-Host $args + #Write-Host $args return $expandedWorkItems } From ed3ba289400c9f990b0e2a6441cbaf069c29bd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Fri, 4 Oct 2019 00:04:52 +0200 Subject: [PATCH 11/24] uncommented debugging --- unit/test/wiql.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/test/wiql.Tests.ps1 b/unit/test/wiql.Tests.ps1 index 65d067d63..eeb06ab11 100644 --- a/unit/test/wiql.Tests.ps1 +++ b/unit/test/wiql.Tests.ps1 @@ -44,7 +44,7 @@ InModuleScope VSTeam { Context 'Get-Wiql' { Mock Invoke-RestMethod { # If this test fails uncomment the line below to see how the mock was called. - #Write-Host $args + Write-Host $args return $wiqlResult } From 13d621984dbe1ec8347f4466f80effd255e28768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Fri, 4 Oct 2019 12:40:08 +0200 Subject: [PATCH 12/24] fixed unit test --- unit/test/wiql.Tests.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unit/test/wiql.Tests.ps1 b/unit/test/wiql.Tests.ps1 index eeb06ab11..c6f8b7a84 100644 --- a/unit/test/wiql.Tests.ps1 +++ b/unit/test/wiql.Tests.ps1 @@ -138,7 +138,9 @@ InModuleScope VSTeam { $Body -like "*[System.State]*" -and $Body -like '*`}' -and # Make sure the body is an object $ContentType -eq 'application/json' -and - $Uri -eq "https://dev.azure.com/test/test/test team/_apis/wit/wiql/?api-version=$([VSTeamVersions]::Core)&`$top=100&timePrecision=True" + $Uri -like "*timePrecision=True*" + $Uri -like "*`$top=100*" + $Uri -like "https://dev.azure.com/test/test/test team/_apis/wit/wiql/?api-version=$([VSTeamVersions]::Core)*" } } From 2d6ccd58ea21a4df80f7244a0b6776d6d3ebc64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sun, 6 Oct 2019 20:37:52 +0200 Subject: [PATCH 13/24] unit test fix --- Source/Public/Get-VSTeamWiql.ps1 | 4 ++-- unit/test/wiql.Tests.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Public/Get-VSTeamWiql.ps1 b/Source/Public/Get-VSTeamWiql.ps1 index badc8f67b..d3326db3a 100644 --- a/Source/Public/Get-VSTeamWiql.ps1 +++ b/Source/Public/Get-VSTeamWiql.ps1 @@ -52,8 +52,8 @@ function Get-VSTeamWiql { if ($Expand) { - [array]$Ids = $resp.workItems | Select-Object -ExpandProperty id - $Fields = $resp.columns | Select-Object -ExpandProperty referenceName + [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 diff --git a/unit/test/wiql.Tests.ps1 b/unit/test/wiql.Tests.ps1 index c6f8b7a84..16739121d 100644 --- a/unit/test/wiql.Tests.ps1 +++ b/unit/test/wiql.Tests.ps1 @@ -33,12 +33,12 @@ InModuleScope VSTeam { asOf = "2019-10-03T18:35:09.117Z" columns = @($column) sortColumns = @($sortColumn) - workItems = @($workItem) + workItems = @($workItem,$workItem) } $expandedWorkItems = @{ count = 1 - value = @($workItem) + value = @($workItem,$workItem) } Context 'Get-Wiql' { From 9bcaac90c4ab7821e46bc8723e080fbf0d4912f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sat, 19 Oct 2019 17:34:14 +0200 Subject: [PATCH 14/24] PERF: optimized merging of returned work items --- Source/Public/Get-VSTeamWiql.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Public/Get-VSTeamWiql.ps1 b/Source/Public/Get-VSTeamWiql.ps1 index d3326db3a..c0b5b24ca 100644 --- a/Source/Public/Get-VSTeamWiql.ps1 +++ b/Source/Public/Get-VSTeamWiql.ps1 @@ -58,7 +58,7 @@ function Get-VSTeamWiql { $resp.workItems = @() #splitting id array by 200, since a maximum of 200 ids are allowed per call $countIds = $Ids.Count - for ($beginRange = 0; $beginRange -lt $countIds; $beginRange += 200) { + $resp.workItems = for ($beginRange = 0; $beginRange -lt $countIds; $beginRange += 200) { $endRange = ($beginRange + 199) @@ -69,7 +69,7 @@ function Get-VSTeamWiql { $idArray = $Ids[$beginRange..($endRange)] } - $resp.workItems += (Get-VSTeamWorkItem -Fields $Fields -Ids $idArray).value + (Get-VSTeamWorkItem -Fields $Fields -Ids $idArray).value } } From ceb0edc3f43fef6dde74731759289b146da1f1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sat, 19 Oct 2019 23:39:31 +0200 Subject: [PATCH 15/24] added formats for workitem deletion cmdlet --- .../Team.WorkItemDeleted.ListView.ps1xml | 34 +++++++++++++ .../Team.WorkItemDeleted.TableView.ps1xml | 51 +++++++++++++++++++ Source/formats/_formats.json | 2 + 3 files changed, 87 insertions(+) create mode 100644 Source/formats/Team.WorkItemDeleted.ListView.ps1xml create mode 100644 Source/formats/Team.WorkItemDeleted.TableView.ps1xml diff --git a/Source/formats/Team.WorkItemDeleted.ListView.ps1xml b/Source/formats/Team.WorkItemDeleted.ListView.ps1xml new file mode 100644 index 000000000..a21e0c9d3 --- /dev/null +++ b/Source/formats/Team.WorkItemDeleted.ListView.ps1xml @@ -0,0 +1,34 @@ + + + + + Team.WorkItemDeleted.ListView + + Team.WorkItemDeleted + + + + + + + id + + + name + + + deletedBy + + + deletedDate + + + code + + + + + + + + \ No newline at end of file diff --git a/Source/formats/Team.WorkItemDeleted.TableView.ps1xml b/Source/formats/Team.WorkItemDeleted.TableView.ps1xml new file mode 100644 index 000000000..16b92770a --- /dev/null +++ b/Source/formats/Team.WorkItemDeleted.TableView.ps1xml @@ -0,0 +1,51 @@ + + + + + Team.WorkItemDeleted.TableView + + Team.WorkItemDeleted + + + + + + + + + + + + + + + + + + + + + + + + id + + + name + + + deletedBy + + + deletedDate + + + code + + + + + + + + \ No newline at end of file diff --git a/Source/formats/_formats.json b/Source/formats/_formats.json index c965e5393..f4f6ff1eb 100644 --- a/Source/formats/_formats.json +++ b/Source/formats/_formats.json @@ -93,6 +93,8 @@ "Team.Wiql.ListView.ps1xml", "Team.WorkItem.TableView.ps1xml", "Team.WorkItem.ListView.ps1xml", + "Team.WorkItemDeleted.TableView.ps1xml", + "Team.WorkItemDeleted.ListView.ps1xml", "Team.JobRequest.TableView.ps1xml" ] } \ No newline at end of file From ee3ed91049851379f073e58805ca90e950655ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sat, 19 Oct 2019 23:40:33 +0200 Subject: [PATCH 16/24] added type for workitem deletion cmdlet --- Source/types/Team.WorkItemDeleted.ps1xml | 59 ++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Source/types/Team.WorkItemDeleted.ps1xml diff --git a/Source/types/Team.WorkItemDeleted.ps1xml b/Source/types/Team.WorkItemDeleted.ps1xml new file mode 100644 index 000000000..17254712a --- /dev/null +++ b/Source/types/Team.WorkItemDeleted.ps1xml @@ -0,0 +1,59 @@ + + + + Team.WorkItemDeleted + + + Name + $this.fields.PSObject.Properties['name'].Value + + + Project + $this.fields.PSObject.Properties['project'].Value + + + Type + $this.fields.PSObject.Properties['type'].Value + + + Id + $this.fields.PSObject.Properties['id'].Value + + + Resource + $this.fields.PSObject.Properties['resource'].Value + + + DeletedBy + $this.fields.PSObject.Properties['deletedBy'].Value + + + DeletedDate + $this.fields.PSObject.Properties['deletedDate'].Value + + + Code + $this.fields.PSObject.Properties['code'].Value + + + Url + $this.fields.PSObject.Properties['url'].Value + + + PSStandardMembers + + + DefaultDisplayPropertySet + + id + name + deletedBy + deletedDate + code + + + + + + + \ No newline at end of file From e126a25f2e11ac350917cbf825400b4928ed39d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sat, 19 Oct 2019 23:40:51 +0200 Subject: [PATCH 17/24] added documentation for workitem deletion cmdlet --- .docs/Remove-VSTeamWorkItem.md | 81 +++++++++++++++++++++++++ .docs/synopsis/Remove-VSTeamWorkItem.md | 1 + 2 files changed, 82 insertions(+) create mode 100644 .docs/Remove-VSTeamWorkItem.md create mode 100644 .docs/synopsis/Remove-VSTeamWorkItem.md diff --git a/.docs/Remove-VSTeamWorkItem.md b/.docs/Remove-VSTeamWorkItem.md new file mode 100644 index 000000000..8c98557ca --- /dev/null +++ b/.docs/Remove-VSTeamWorkItem.md @@ -0,0 +1,81 @@ + + +# Remove-VSTeamWorkItem + +## SYNOPSIS + + + +## SYNTAX + +## DESCRIPTION + + + +## EXAMPLES + +### -------------------------- EXAMPLE 1 -------------------------- + +```PowerShell +PS C:\> Remove-VSTeamWorkItem -Ids 47,48 +``` + +This command deletes work items with IDs 47 and 48 by using the IDs parameter. + +```PowerShell +PS C:\> Remove-VSTeamWorkItem -Id 47 +``` + +This command deletes the work item with ID 47 by using the ID parameter. + +```PowerShell +PS C:\> Remove-VSTeamWorkItem -Ids 47 -Destroy +``` + +This command deletes work item with IDs 47 **permanently** by using the Destroy parameter. + +## PARAMETERS + +### -Id + +The id of the work item. + +```yaml +Type: Int32 +Parameter Sets: ByID +Required: True +Accept pipeline input: true (ByPropertyName, ByValue) +``` + +### -Ids + +The id of the work item. + +```yaml +Type: Int32[] +Parameter Sets: List +Required: True +Accept pipeline input: true (ByPropertyName, ByValue) +``` + +### -Destroy + +Optional parameter, if set to true, the work item is deleted permanently. **Please note: the destroy action is PERMANENT and cannot be undone.** + +```yaml +Type: Switch +``` + +## INPUTS + +### System.String + +ProjectName + +## OUTPUTS + +## NOTES + +If you do not set the default project by called Set-VSTeamDefaultProject before calling Get-VSTeamWorkItem you will have to type in the names. + +## RELATED LINKS diff --git a/.docs/synopsis/Remove-VSTeamWorkItem.md b/.docs/synopsis/Remove-VSTeamWorkItem.md new file mode 100644 index 000000000..f75bda78d --- /dev/null +++ b/.docs/synopsis/Remove-VSTeamWorkItem.md @@ -0,0 +1 @@ +Deletes one or more a work items from your project. \ No newline at end of file From daae137bc68b4e77a4adfdf624a3ff355ee07ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sat, 19 Oct 2019 23:41:05 +0200 Subject: [PATCH 18/24] added type conversion for workitem deletion cmdlet --- Source/Private/applyTypes.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Private/applyTypes.ps1 b/Source/Private/applyTypes.ps1 index 8e55440f5..85015dce3 100644 --- a/Source/Private/applyTypes.ps1 +++ b/Source/Private/applyTypes.ps1 @@ -25,6 +25,12 @@ function _applyTypesToWorkItem { } } +function _applyTypesToWorkItemDeleted { + param($item) + $item.PSObject.TypeNames.Insert(0, 'Team.WorkItemDeleted') + $item.resource.PSObject.TypeNames.Insert(0,'Team.WorkItem') +} + function _applyTypesToWiql { param($item) if ($item) { From b416321985128485bb86b6d066df6b0161dc0a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sat, 19 Oct 2019 23:41:21 +0200 Subject: [PATCH 19/24] added workitem deletion cmdlet --- Source/Public/Remove-VSTeamWorkItem.ps1 | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Source/Public/Remove-VSTeamWorkItem.ps1 diff --git a/Source/Public/Remove-VSTeamWorkItem.ps1 b/Source/Public/Remove-VSTeamWorkItem.ps1 new file mode 100644 index 000000000..ffa587c7c --- /dev/null +++ b/Source/Public/Remove-VSTeamWorkItem.ps1 @@ -0,0 +1,39 @@ +function Remove-VSTeamWorkItem { + [CmdletBinding(DefaultParameterSetName = 'ByID')] + param( + [Parameter(ParameterSetName = 'ByID', Mandatory = $true, ValueFromPipeline = $true, Position = 0)] + [int] $Id, + + [Parameter(ParameterSetName = 'List', Mandatory = $true, ValueFromPipeline = $true, Position = 0)] + [int[]] $Ids, + + [switch] $Destroy + ) + + Process { + # Call the REST API + + $idsToDelete = @() + + if ($Ids) { + $idsToDelete = $Ids + } + else { + $idsToDelete = @($id[0]) + } + + $deletedWorkItems = foreach ($wiId in $idsToDelete) { + $resp = _callAPI -Method "DELETE" -Area 'wit' -Resource 'workitems' ` + -Version $([VSTeamVersions]::Core) -id "$wiId" ` + -Querystring @{ + destroy = $Destroy + } + + _applyTypesToWorkItemDeleted -item $resp + + $resp + } + + return $deletedWorkItems + } +} \ No newline at end of file From 1e645042f7889b1ec38afddba2163467f4f8d425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sat, 19 Oct 2019 23:58:24 +0200 Subject: [PATCH 20/24] added missing headings in documentation --- .docs/Remove-VSTeamWorkItem.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.docs/Remove-VSTeamWorkItem.md b/.docs/Remove-VSTeamWorkItem.md index 8c98557ca..caa013a9e 100644 --- a/.docs/Remove-VSTeamWorkItem.md +++ b/.docs/Remove-VSTeamWorkItem.md @@ -22,12 +22,16 @@ PS C:\> Remove-VSTeamWorkItem -Ids 47,48 This command deletes work items with IDs 47 and 48 by using the IDs parameter. +### -------------------------- EXAMPLE 2 -------------------------- + ```PowerShell PS C:\> Remove-VSTeamWorkItem -Id 47 ``` This command deletes the work item with ID 47 by using the ID parameter. +### -------------------------- EXAMPLE 3 -------------------------- + ```PowerShell PS C:\> Remove-VSTeamWorkItem -Ids 47 -Destroy ``` From ddce1e6e0b5425ca76e57310fb55ebb3b65416c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sun, 20 Oct 2019 03:18:26 +0200 Subject: [PATCH 21/24] updated cmdlet synopsis --- .docs/synopsis/Remove-VSTeamWorkItem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docs/synopsis/Remove-VSTeamWorkItem.md b/.docs/synopsis/Remove-VSTeamWorkItem.md index f75bda78d..988f2c6da 100644 --- a/.docs/synopsis/Remove-VSTeamWorkItem.md +++ b/.docs/synopsis/Remove-VSTeamWorkItem.md @@ -1 +1 @@ -Deletes one or more a work items from your project. \ No newline at end of file +Deletes the specified work item and sends it to the Recycle Bin, so that it can be restored back, if required. Optionally, if the destroy parameter has been set to true, it destroys the work item permanently. WARNING: If the destroy parameter is set to true, work items deleted by this command will NOT go to recycle-bin and there is no way to restore/recover them after deletion. It is recommended NOT to use this parameter. If you do, please use this parameter with extreme caution. \ No newline at end of file From a64de8472f80da80cf65917cdf951e4fac831764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sun, 20 Oct 2019 13:32:30 +0200 Subject: [PATCH 22/24] improved cmdlet to throw exceptions --- Source/Public/Remove-VSTeamWorkItem.ps1 | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Public/Remove-VSTeamWorkItem.ps1 b/Source/Public/Remove-VSTeamWorkItem.ps1 index ffa587c7c..922d2d1d6 100644 --- a/Source/Public/Remove-VSTeamWorkItem.ps1 +++ b/Source/Public/Remove-VSTeamWorkItem.ps1 @@ -15,10 +15,21 @@ function Remove-VSTeamWorkItem { $idsToDelete = @() - if ($Ids) { + if ($PSCmdlet.ParameterSetName -eq "List") { + + if($null -eq $Ids) { + throw "No Ids given, array was null" + } + $idsToDelete = $Ids } else { + + # work item IDs in AzD cannot be lower than 1. First work item id is always 1. + if($Id -lt 1) { + throw "given work item Id has to be greater than 0" + } + $idsToDelete = @($id[0]) } From e93d98bb61867cbfbe11acc108dc0544b8b110d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sun, 20 Oct 2019 13:33:01 +0200 Subject: [PATCH 23/24] added unit tests for cmdlet --- unit/test/workitem.Tests.ps1 | 78 +++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/unit/test/workitem.Tests.ps1 b/unit/test/workitem.Tests.ps1 index a344f23d4..e00874637 100644 --- a/unit/test/workitem.Tests.ps1 +++ b/unit/test/workitem.Tests.ps1 @@ -15,13 +15,26 @@ InModuleScope VSTeam { id = 47 rev = 1 url = "https://dev.azure.com/test/_apis/wit/workItems/47" - } + } $collection = @{ count = 1 value = @($obj) } + $objDeleted = @{ + id = 47 + name = "Test Work Item 47" + deletedBy = "Theobald Test " + deletedDate = "10/19/2019 9:08:48 PM" + code = 200 + resource = $obj + } + + $collectionDeleted = @( + $objDeleted + ) + Context 'Add-WorkItem' { Mock Invoke-RestMethod { # If this test fails uncomment the line below to see how the mock was called. @@ -284,5 +297,68 @@ InModuleScope VSTeam { } } } + + Context 'Remove-WorkItem'{ + + It 'Should delete single work item' { + Mock Invoke-RestMethod { + # If this test fails uncomment the line below to see how the mock was called. + #Write-Host $args + + return $collectionDeleted + } + + Remove-VSTeamWorkItem -Id 47 + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter { + $Uri -like "*https://dev.azure.com/test/_apis/wit/workitems/*" -and + $Uri -like "*api-version=$([VSTeamVersions]::Core)*" -and + $Uri -like "*workitems/47*" + } + } + + It 'Should throw single work item with id equals $null' { + {Remove-VSTeamWorkItem -Id $null} | Should -Throw + } + + It 'Should delete multipe work items' { + Mock Invoke-RestMethod { + # If this test fails uncomment the line below to see how the mock was called. + #Write-Host $args + + return $collectionDeleted + } + + Remove-VSTeamWorkItem -Ids 47, 48 + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 2 -ParameterFilter { + $Uri -like "*https://dev.azure.com/test/_apis/wit/workitems/*" -and + $Uri -like "*api-version=$([VSTeamVersions]::Core)*" -and + ($Uri -like "*workitems/47*" -or $Uri -like "*workitems/48*") + } + } + + It 'Single Work Item Should be deleted permanently' { + Mock Invoke-RestMethod { + # If this test fails uncomment the line below to see how the mock was called. + #Write-Host $args + + return $collectionDeleted + } + + Remove-VSTeamWorkItem -Ids 47, 48 -Destroy + + Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 2 -ParameterFilter { + $Uri -like "*https://dev.azure.com/test/_apis/wit/workitems/*" -and + $Uri -like "*api-version=$([VSTeamVersions]::Core)*" -and + ($Uri -like "*workitems/47*" -or $Uri -like "*workitems/48*") -and + $Uri -like "*destroy=True*" + } + } + + It 'Should throw multiple work item with array equals $null' { + {Remove-VSTeamWorkItem -Ids $null} | Should -Throw + } + } } } \ No newline at end of file From 9a9f0b09e56545171d0d65045094f67460f98afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Sch=C3=BCtze?= Date: Sun, 20 Oct 2019 13:37:24 +0200 Subject: [PATCH 24/24] updated changelog and module version --- CHANGELOG.md | 4 ++++ Source/VSTeam.psd1 | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a187dbb1..0ee0bd8a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 6.4.0 + +Added Remove-VSTeamWorkItem to delete work items + ## 6.3.6 Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/200) from [Chris Gardner](https://github.com/ChrisLGardner) which included the following: diff --git a/Source/VSTeam.psd1 b/Source/VSTeam.psd1 index 68c96ca65..746cdda3b 100644 --- a/Source/VSTeam.psd1 +++ b/Source/VSTeam.psd1 @@ -12,7 +12,7 @@ RootModule = 'VSTeam.psm1' # Version number of this module. - ModuleVersion = '6.3.6' + ModuleVersion = '6.4.0' # Supported PSEditions # CompatiblePSEditions = @()