DEV Community

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

Use .Net Interactive with Azure DevOps

Antoine
mainly .Net but looking for great stuff about anything
・2 min read

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 !

Discussion (0)