DEV Community

Cover image for Use .Net Interactive with Azure DevOps
Antoine
Antoine

Posted on

Use .Net Interactive with Azure DevOps

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>";
Enter fullscreen mode Exit fullscreen mode

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;}
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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)