DEV Community

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

Posted on

2 1

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 !

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs