Skip to content

Commit 276cfc9

Browse files
authored
Add Undo-TfsWorkItemQuery[Folder]Removal (#196)
* Fix retrieval of deleted items * Add new cmdlets * Update release notes +semver: minor
1 parent 4b27681 commit 276cfc9

File tree

7 files changed

+179
-12
lines changed

7 files changed

+179
-12
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Management.Automation;
2+
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
3+
4+
namespace TfsCmdlets.Cmdlets.WorkItem.Query
5+
{
6+
/// <summary>
7+
/// Restores a deleted work item query folder.
8+
/// </summary>
9+
[TfsCmdlet(CmdletScope.Project, SupportsShouldProcess = true, OutputType = typeof(QueryHierarchyItem))]
10+
partial class UndoWorkItemQueryFolderRemoval
11+
{
12+
/// <summary>
13+
/// Specifies one or more query folders to restore. Wildcards supported.
14+
/// </summary>
15+
[Parameter(Position = 0, ValueFromPipeline = true, Mandatory = true)]
16+
[ValidateNotNull()]
17+
[SupportsWildcards()]
18+
[Alias("Path")]
19+
public object Folder { get; set; }
20+
21+
/// <summary>
22+
/// Specifies the scope of the item to restore. Personal refers to the
23+
/// "My Queries" folder", whereas Shared refers to the "Shared Queries"
24+
/// folder. When omitted defaults to "Both", effectively searching for items
25+
/// in both scopes.
26+
/// </summary>
27+
[Parameter]
28+
public QueryItemScope Scope { get; set; } = QueryItemScope.Both;
29+
30+
/// <summary>
31+
/// Restores the specified query folder and all its descendants.
32+
/// When omitted, the specified folder is restored but not its contents (queries and/or sub-folders).
33+
/// </summary>
34+
[Parameter]
35+
public SwitchParameter Recursive {get;set;}
36+
37+
[Parameter]
38+
internal bool Deleted => true;
39+
40+
[Parameter]
41+
internal string ItemType => "Folder";
42+
}
43+
}

CSharp/TfsCmdlets/Cmdlets/WorkItem/Query/GetWorkItemQuery.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ partial class GetWorkItemQuery
2929
public QueryItemScope Scope { get; set; } = QueryItemScope.Both;
3030

3131
/// <summary>
32-
/// Returns deleted items.
32+
/// Returns only deleted items.
3333
/// </summary>
3434
[Parameter()]
3535
public SwitchParameter Deleted { get; set; }
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Management.Automation;
2+
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
3+
4+
namespace TfsCmdlets.Cmdlets.WorkItem.Query
5+
{
6+
/// <summary>
7+
/// Restores a deleted work item query.
8+
/// </summary>
9+
[TfsCmdlet(CmdletScope.Project, SupportsShouldProcess = true, OutputType = typeof(QueryHierarchyItem))]
10+
partial class UndoWorkItemQueryRemoval
11+
{
12+
/// <summary>
13+
/// Specifies one or more saved queries to restore. Wildcards supported.
14+
/// </summary>
15+
[Parameter(Position = 0, ValueFromPipeline = true, Mandatory = true)]
16+
[ValidateNotNull()]
17+
[SupportsWildcards()]
18+
[Alias("Path")]
19+
public object Query { get; set; }
20+
21+
/// <summary>
22+
/// Specifies the scope of the item to restore. Personal refers to the
23+
/// "My Queries" folder", whereas Shared refers to the "Shared Queries"
24+
/// folder. When omitted defaults to "Both", effectively searching for items
25+
/// in both scopes.
26+
/// </summary>
27+
[Parameter]
28+
public QueryItemScope Scope { get; set; } = QueryItemScope.Both;
29+
30+
[Parameter]
31+
internal SwitchParameter Recursive => false;
32+
33+
[Parameter]
34+
internal bool Deleted => true;
35+
36+
[Parameter]
37+
internal string ItemType => "Query";
38+
}
39+
}

CSharp/TfsCmdlets/Controllers/WorkItem/Query/GetWorkItemQueryItemController.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ protected override IEnumerable Run()
7070

7171
foreach (var rootFolder in rootFolders)
7272
{
73-
foreach (var c in GetItemsRecursively(rootFolder, path, Project.Name, !isFolder, client))
73+
foreach (var c in GetItemsRecursively(rootFolder, path, Project.Name, !isFolder, Deleted, client))
7474
{
7575
yield return c;
7676
}
@@ -88,30 +88,30 @@ protected override IEnumerable Run()
8888

8989
private (QueryHierarchyItem personal, QueryHierarchyItem shared) GetRootFolders(string projectName, QueryItemScope scope, WorkItemTrackingHttpClient client, int depth = 2, QueryExpand expand = QueryExpand.All)
9090
{
91-
var result = client.GetQueriesAsync(projectName, expand, depth)
91+
var result = client.GetQueriesAsync(projectName, expand, depth, true)
9292
.GetResult("Error getting work item query root folders")
9393
.ToList();
9494

9595
return (result.First(q => !(bool)q.IsPublic), result.First(q => (bool)q.IsPublic));
9696
}
9797

98-
private IEnumerable<QueryHierarchyItem> GetItemsRecursively(QueryHierarchyItem item, string pattern, string projectName, bool queriesOnly, WorkItemTrackingHttpClient client)
98+
private IEnumerable<QueryHierarchyItem> GetItemsRecursively(QueryHierarchyItem item, string pattern, string projectName, bool queriesOnly, bool deletedOnly, WorkItemTrackingHttpClient client)
9999
{
100-
if ((item.HasChildren ?? false) && (item.Children == null || item.Children.ToList().Count == 0))
100+
if ((item.HasChildren ?? false) && (item.Children == null || item.Children.ToList().Count == 0) && !item.IsDeleted)
101101
{
102102
Logger.Log($"Fetching child nodes for node '{item.Path}'");
103103

104-
item = client.GetQueryAsync(projectName, item.Path, QueryExpand.All, 2, false)
104+
item = client.GetQueryAsync(projectName, item.Path, QueryExpand.All, 2, true)
105105
.GetResult($"Error retrieving folder from path '{item.Path}'");
106106
}
107107

108-
if (item.Children == null) yield break;
108+
if (item.Children == null || item.IsDeleted) yield break;
109109

110110
foreach (var c in item.Children)
111111
{
112112
var isFolder = c.IsFolder ?? false;
113113
var relativePath = c.Path.Substring(c.Path.IndexOf("/") + 1);
114-
var isMatch = relativePath.IsLike(pattern);
114+
var isMatch = relativePath.IsLike(pattern) && c.IsDeleted == deletedOnly;
115115

116116
if (isMatch && (!isFolder == queriesOnly)) yield return c;
117117
}
@@ -126,7 +126,7 @@ private IEnumerable<QueryHierarchyItem> GetItemsRecursively(QueryHierarchyItem i
126126

127127
if (!isFolder) continue;
128128

129-
foreach (var c1 in GetItemsRecursively(c, pattern, projectName, queriesOnly, client))
129+
foreach (var c1 in GetItemsRecursively(c, pattern, projectName, queriesOnly, deletedOnly, client))
130130
{
131131
yield return c1;
132132
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Xml.Linq;
2+
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
3+
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
4+
using TfsCmdlets.Cmdlets.WorkItem.Query;
5+
6+
namespace TfsCmdlets.Controllers.WorkItem.Query
7+
{
8+
[CmdletController(typeof(QueryHierarchyItem))]
9+
partial class UndoWorkItemQueryRemovalController
10+
{
11+
protected override IEnumerable Run()
12+
{
13+
var client = GetClient<WorkItemTrackingHttpClient>();
14+
15+
foreach(var item in Items)
16+
{
17+
if(!PowerShell.ShouldProcess(Project, $"Restore {ItemType} '{item.Path}'")) continue;
18+
19+
yield return client.UpdateQueryAsync(new QueryHierarchyItem(){IsDeleted=false}, Project.Id, item.Id.ToString(), undeleteDescendants: Recursive)
20+
.GetResult($"Error restoring {ItemType} '{item.Path}'");
21+
}
22+
}
23+
}
24+
}

Docs/ReleaseNotes/2.6.0.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# TfsCmdlets Release Notes
2+
3+
## Version 2.6.0 (_30/Sep/2022_)
4+
5+
This release fixes a bug in `Get-TfsWorkItemQuery` and `Get-TfsWorkItemQueryFolder`, and adds two new cmdlets.
6+
7+
## New cmdlets
8+
9+
* `Undo-TfsWorkItemQueryRemoval` and `Undo-TfsWorkItemQueryFolderRemoval` allow you to undo the deletion of a query or query folder. This is useful when you accidentally delete a query or query folder and want to restore it.
10+
11+
To restore a deleted query:
12+
13+
```powershell
14+
# You can either pipe the deleted query from Get-TfsWorkItemQuery to Undo-TfsWorkItemQueryRemoval...
15+
Get-TfsWorkItemQuery 'My Deleted Query' -Scope Personal -Deleted | Undo-TfsWorkItemQueryRemoval
16+
17+
# ... or you can specify the query directly when calling Undo-TfsWorkItemQueryRemoval
18+
Undo-TfsWorkItemQueryRemoval 'My Deleted Query' -Scope Personal
19+
```
20+
21+
The same applies to query folders - with the distinction that folder can be restored recursively by specifying the `-Recursive` switch. When `-Recursive` is omitted, only the folder itself is restored, without any of its contents. You can then restore its contents by issuing further calls to `Undo-TfsWorkItemQueryRemoval` and/or `Undo-TfsWorkItemQueryFolderRemoval`.
22+
23+
```powershell
24+
# You can either pipe the deleted folder from Get-TfsWorkItemQueryFolder to Undo-TfsWorkItemQueryFolderRemoval...
25+
Get-TfsWorkItemQueryFolder 'My Deleted Folder' -Scope Personal -Deleted | Undo-TfsWorkItemQueryRemoval -Recursive
26+
27+
# ... or you can specify the folder directly when calling Undo-TfsWorkItemQueryFolderRemoval
28+
Undo-TfsWorkItemQueryFolderRemoval 'My Deleted Folder' -Scope Personal -Recursive
29+
```
30+
31+
## Fixes
32+
33+
* Fixes a bug in `Get-TfsWorkItemQuery` and `Get-TfsWorkItemQueryFolder` where the `-Deleted` switch was not respected and deleted items would not be returned.

RELEASENOTES.md

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,45 @@
11
# TfsCmdlets Release Notes
22

3-
## Version 2.5.1 (_22/Aug/2022_)
3+
## Version 2.6.0 (_30/Sep/2022_)
44

5-
This release fixes a bug in `New-TfsWorkItem`.
5+
This release fixes a bug in `Get-TfsWorkItemQuery` and `Get-TfsWorkItemQueryFolder`, and adds two new cmdlets.
6+
7+
## New cmdlets
8+
9+
* `Undo-TfsWorkItemQueryRemoval` and `Undo-TfsWorkItemQueryFolderRemoval` allow you to undo the deletion of a query or query folder. This is useful when you accidentally delete a query or query folder and want to restore it.
10+
11+
To restore a deleted query:
12+
13+
```powershell
14+
# You can either pipe the deleted query from Get-TfsWorkItemQuery to Undo-TfsWorkItemQueryRemoval...
15+
Get-TfsWorkItemQuery 'My Deleted Query' -Scope Personal -Deleted | Undo-TfsWorkItemQueryRemoval
16+
17+
# ... or you can specify the query directly when calling Undo-TfsWorkItemQueryRemoval
18+
Undo-TfsWorkItemQueryRemoval 'My Deleted Query' -Scope Personal
19+
```
20+
21+
The same applies to query folders - with the distinction that folder can be restored recursively by specifying the `-Recursive` switch. When `-Recursive` is omitted, only the folder itself is restored, without any of its contents. You can then restore its contents by issuing further calls to `Undo-TfsWorkItemQueryRemoval` and/or `Undo-TfsWorkItemQueryFolderRemoval`.
22+
23+
```powershell
24+
# You can either pipe the deleted folder from Get-TfsWorkItemQueryFolder to Undo-TfsWorkItemQueryFolderRemoval...
25+
Get-TfsWorkItemQueryFolder 'My Deleted Folder' -Scope Personal -Deleted | Undo-TfsWorkItemQueryRemoval -Recursive
26+
27+
# ... or you can specify the folder directly when calling Undo-TfsWorkItemQueryFolderRemoval
28+
Undo-TfsWorkItemQueryFolderRemoval 'My Deleted Folder' -Scope Personal -Recursive
29+
```
630

731
## Fixes
832

9-
* Fixes a bug in `New-TfsWorkItem` where AreaPath and IterationPath arguments are switched.
33+
* Fixes a bug in `Get-TfsWorkItemQuery` and `Get-TfsWorkItemQueryFolder` where the `-Deleted` switch was not respected and deleted items would not be returned.
1034

1135
-----------------------
1236

1337
## Previous Versions
1438

39+
### Version 2.5.1 (_22/Aug/2022_)
40+
41+
See release notes [here](Docs/ReleaseNotes/2.5.1.md).
42+
1543
### Version 2.5.0 (_03/Aug/2022_)
1644

1745
See release notes [here](Docs/ReleaseNotes/2.5.0.md).

0 commit comments

Comments
 (0)