Skip to content

Commit

Permalink
Add Undo-TfsWorkItemQuery[Folder]Removal (#196)
Browse files Browse the repository at this point in the history
* Fix retrieval of deleted items

* Add new cmdlets

* Update release notes

+semver: minor
  • Loading branch information
igoravl authored Sep 30, 2022
1 parent 4b27681 commit 276cfc9
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Management.Automation;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;

namespace TfsCmdlets.Cmdlets.WorkItem.Query
{
/// <summary>
/// Restores a deleted work item query folder.
/// </summary>
[TfsCmdlet(CmdletScope.Project, SupportsShouldProcess = true, OutputType = typeof(QueryHierarchyItem))]
partial class UndoWorkItemQueryFolderRemoval
{
/// <summary>
/// Specifies one or more query folders to restore. Wildcards supported.
/// </summary>
[Parameter(Position = 0, ValueFromPipeline = true, Mandatory = true)]
[ValidateNotNull()]
[SupportsWildcards()]
[Alias("Path")]
public object Folder { get; set; }

/// <summary>
/// Specifies the scope of the item to restore. Personal refers to the
/// "My Queries" folder", whereas Shared refers to the "Shared Queries"
/// folder. When omitted defaults to "Both", effectively searching for items
/// in both scopes.
/// </summary>
[Parameter]
public QueryItemScope Scope { get; set; } = QueryItemScope.Both;

/// <summary>
/// Restores the specified query folder and all its descendants.
/// When omitted, the specified folder is restored but not its contents (queries and/or sub-folders).
/// </summary>
[Parameter]
public SwitchParameter Recursive {get;set;}

[Parameter]
internal bool Deleted => true;

[Parameter]
internal string ItemType => "Folder";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ partial class GetWorkItemQuery
public QueryItemScope Scope { get; set; } = QueryItemScope.Both;

/// <summary>
/// Returns deleted items.
/// Returns only deleted items.
/// </summary>
[Parameter()]
public SwitchParameter Deleted { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Management.Automation;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;

namespace TfsCmdlets.Cmdlets.WorkItem.Query
{
/// <summary>
/// Restores a deleted work item query.
/// </summary>
[TfsCmdlet(CmdletScope.Project, SupportsShouldProcess = true, OutputType = typeof(QueryHierarchyItem))]
partial class UndoWorkItemQueryRemoval
{
/// <summary>
/// Specifies one or more saved queries to restore. Wildcards supported.
/// </summary>
[Parameter(Position = 0, ValueFromPipeline = true, Mandatory = true)]
[ValidateNotNull()]
[SupportsWildcards()]
[Alias("Path")]
public object Query { get; set; }

/// <summary>
/// Specifies the scope of the item to restore. Personal refers to the
/// "My Queries" folder", whereas Shared refers to the "Shared Queries"
/// folder. When omitted defaults to "Both", effectively searching for items
/// in both scopes.
/// </summary>
[Parameter]
public QueryItemScope Scope { get; set; } = QueryItemScope.Both;

[Parameter]
internal SwitchParameter Recursive => false;

[Parameter]
internal bool Deleted => true;

[Parameter]
internal string ItemType => "Query";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ protected override IEnumerable Run()

foreach (var rootFolder in rootFolders)
{
foreach (var c in GetItemsRecursively(rootFolder, path, Project.Name, !isFolder, client))
foreach (var c in GetItemsRecursively(rootFolder, path, Project.Name, !isFolder, Deleted, client))
{
yield return c;
}
Expand All @@ -88,30 +88,30 @@ protected override IEnumerable Run()

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

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

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

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

if (item.Children == null) yield break;
if (item.Children == null || item.IsDeleted) yield break;

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

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

if (!isFolder) continue;

foreach (var c1 in GetItemsRecursively(c, pattern, projectName, queriesOnly, client))
foreach (var c1 in GetItemsRecursively(c, pattern, projectName, queriesOnly, deletedOnly, client))
{
yield return c1;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Xml.Linq;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using TfsCmdlets.Cmdlets.WorkItem.Query;

namespace TfsCmdlets.Controllers.WorkItem.Query
{
[CmdletController(typeof(QueryHierarchyItem))]
partial class UndoWorkItemQueryRemovalController
{
protected override IEnumerable Run()
{
var client = GetClient<WorkItemTrackingHttpClient>();

foreach(var item in Items)
{
if(!PowerShell.ShouldProcess(Project, $"Restore {ItemType} '{item.Path}'")) continue;

yield return client.UpdateQueryAsync(new QueryHierarchyItem(){IsDeleted=false}, Project.Id, item.Id.ToString(), undeleteDescendants: Recursive)
.GetResult($"Error restoring {ItemType} '{item.Path}'");
}
}
}
}
33 changes: 33 additions & 0 deletions Docs/ReleaseNotes/2.6.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# TfsCmdlets Release Notes

## Version 2.6.0 (_30/Sep/2022_)

This release fixes a bug in `Get-TfsWorkItemQuery` and `Get-TfsWorkItemQueryFolder`, and adds two new cmdlets.

## New cmdlets

* `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.

To restore a deleted query:

```powershell
# You can either pipe the deleted query from Get-TfsWorkItemQuery to Undo-TfsWorkItemQueryRemoval...
Get-TfsWorkItemQuery 'My Deleted Query' -Scope Personal -Deleted | Undo-TfsWorkItemQueryRemoval
# ... or you can specify the query directly when calling Undo-TfsWorkItemQueryRemoval
Undo-TfsWorkItemQueryRemoval 'My Deleted Query' -Scope Personal
```

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`.

```powershell
# You can either pipe the deleted folder from Get-TfsWorkItemQueryFolder to Undo-TfsWorkItemQueryFolderRemoval...
Get-TfsWorkItemQueryFolder 'My Deleted Folder' -Scope Personal -Deleted | Undo-TfsWorkItemQueryRemoval -Recursive
# ... or you can specify the folder directly when calling Undo-TfsWorkItemQueryFolderRemoval
Undo-TfsWorkItemQueryFolderRemoval 'My Deleted Folder' -Scope Personal -Recursive
```

## Fixes

* Fixes a bug in `Get-TfsWorkItemQuery` and `Get-TfsWorkItemQueryFolder` where the `-Deleted` switch was not respected and deleted items would not be returned.
34 changes: 31 additions & 3 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
# TfsCmdlets Release Notes

## Version 2.5.1 (_22/Aug/2022_)
## Version 2.6.0 (_30/Sep/2022_)

This release fixes a bug in `New-TfsWorkItem`.
This release fixes a bug in `Get-TfsWorkItemQuery` and `Get-TfsWorkItemQueryFolder`, and adds two new cmdlets.

## New cmdlets

* `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.

To restore a deleted query:

```powershell
# You can either pipe the deleted query from Get-TfsWorkItemQuery to Undo-TfsWorkItemQueryRemoval...
Get-TfsWorkItemQuery 'My Deleted Query' -Scope Personal -Deleted | Undo-TfsWorkItemQueryRemoval
# ... or you can specify the query directly when calling Undo-TfsWorkItemQueryRemoval
Undo-TfsWorkItemQueryRemoval 'My Deleted Query' -Scope Personal
```

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`.

```powershell
# You can either pipe the deleted folder from Get-TfsWorkItemQueryFolder to Undo-TfsWorkItemQueryFolderRemoval...
Get-TfsWorkItemQueryFolder 'My Deleted Folder' -Scope Personal -Deleted | Undo-TfsWorkItemQueryRemoval -Recursive
# ... or you can specify the folder directly when calling Undo-TfsWorkItemQueryFolderRemoval
Undo-TfsWorkItemQueryFolderRemoval 'My Deleted Folder' -Scope Personal -Recursive
```

## Fixes

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

-----------------------

## Previous Versions

### Version 2.5.1 (_22/Aug/2022_)

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

### Version 2.5.0 (_03/Aug/2022_)

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

0 comments on commit 276cfc9

Please sign in to comment.