Photo by Dev Benjamin on Unsplash
I will use .Net interactive to connect to Azure DevOps, and generate markdown with the output of one query.
Connect to Azure DevOps
First, we will have to set a PAT for authentication.
string pat = "<YOUR PAT>";
Then, we have to define locally the type representing WorkItem.
class WorkItemPresentation
{
public string Id {get; set;}
public string Name {get; set;}
public string State {get; set;}
public string Description {get; set;}
}
Finally, we will call the query, map the output to WorkItemPresentation, and output it.
#r "nuget:Microsoft.TeamFoundationServer.Client"
#r "nuget:Microsoft.VisualStudio.Services.InteractiveClient,16.170.0"
#r "nuget:Microsoft.VisualStudio.Services.Client"
// https://www.nuget.org/packages/Microsoft.TeamFoundationServer.Client/
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
// https://www.nuget.org/packages/Microsoft.VisualStudio.Services.InteractiveClient/
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.WebApi;
// https://www.nuget.org/packages/Microsoft.VisualStudio.Services.Client/
using Microsoft.VisualStudio.Services.Common;
VssConnection connection = new VssConnection(new Uri("https://XXX.visualstudio.com"), new VssBasicCredential(string.Empty, pat));
// Create instance of WorkItemTrackingHttpClient using VssConnection
WorkItemTrackingHttpClient witClient = connection.GetClient<WorkItemTrackingHttpClient>();
// Get 2 levels of query hierarchy items
List<QueryHierarchyItem> queryHierarchyItems = witClient.GetQueriesAsync("<PROJECT>", depth: 2).Result;
// Search for 'My Queries' folder
QueryHierarchyItem myQueriesFolder = queryHierarchyItems.FirstOrDefault(qhi => qhi.Name.Equals("My Queries"));
if (myQueriesFolder != null)
{
string queryName = "<QUERY_NAME>";
// See if our 'REST Sample' query already exists under 'My Queries' folder.
QueryHierarchyItem newBugsQuery = null;
if (myQueriesFolder.Children != null)
{
newBugsQuery = myQueriesFolder.Children.FirstOrDefault(qhi => qhi.Name.Equals(queryName));
}
var queryResult = witClient.QueryByIdAsync(newBugsQuery.Id).Result;
// need to get the list of our work item id's and put them into an array
int[] workItemIds = queryResult.WorkItems.Select<WorkItemReference, int>(wif => { return wif.Id; }).ToArray();
// build a list of the fields we want to see
string[] fields = new []
{
"System.Id",
"System.Title",
"System.State",
"System.Description"
};
IEnumerable<WorkItemPresentation> workItems = witClient.GetWorkItemsAsync(workItemIds, fields, queryResult.AsOf).Result.Select(w => new WorkItemPresentation(){Id=w.Fields["System.Id"].ToString(),
Name=w.Fields["System.Title"].ToString(),
State=w.Fields["System.State"].ToString(),
Description=w.Fields.ContainsKey("System.Description") ? w.Fields["System.Description"].ToString() : "" });
display(workItems);
}
Modify Output Rendering
In order to manipulate the rendering, we need to define a special Formatter for our type.
using static Microsoft.DotNet.Interactive.Formatting.PocketViewTags;
using Microsoft.DotNet.Interactive.Formatting;
public string Clean(string content){
return content.Replace("[", " ").Replace("]", " ").Replace("(", " ").Replace(")", " ");
}
Formatter.ResetToDefault();
Formatter.Register<IEnumerable<WorkItemPresentation>>((workItems, writer) =>
{
foreach (var workItemPresentation in workItems)
{
writer.WriteLine("<br/>");
writer.WriteLine("---");
writer.WriteLine("<br/>");
writer.WriteLine("<br/>");
writer.WriteLine($"# {Clean(workItemPresentation.Name)}");
writer.WriteLine("<br/>");
writer.WriteLine("<br/>");
writer.WriteLine($"{Clean(!string.IsNullOrEmpty(workItemPresentation.Description) ? workItemPresentation.Description : workItemPresentation.Name)}");
writer.WriteLine("<br/>");
}
}, mimeType: "text/html");
See the docs for formatting.
Interesting post by Lady Nagaga on how to pass data from one kernel to another.
Hope this helps !
Top comments (0)