February 1st, 2012

Listing the work items associated with changesets for a path

Buck Hodges
Director of Engineering

Philip wrote a simple app to list the work items associated with the changesets for a given path, and it’s in some ways an enhanced update of Naren’s post.

Given an URL to a collection and a server path (e.g., $/myproject/coolthing), it will list the work items that are associated with the most recent 25 checkins.  This sample shows how to use the linking service to convert the work item artifact URIs that are stored with the changesets to get the core work item fields (ID, assigned to, state, type, and title).

It will produce output like the following.

Id: 352694 Title: Improve performance of queuing servicing jobs on Azure.

You will need to reference the following DLLs to build this, all of which are found on the .NET tab of the Add Reference dialog in Visual Studio 2010.

  • Microsoft.TeamFoundation.Client.dll
  • Microsoft.TeamFoundation.Common.dll
  • Microsoft.TeamFoundation.VersionControl.Client.dll
using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using Microsoft.TeamFoundation; 
using Microsoft.TeamFoundation.Client; 
using Microsoft.TeamFoundation.VersionControl.Client; 

namespace ListWorkItems 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
            if (args.Length < 2)
            { 
                Console.WriteLine("Usage: listworkitems <URL for TFS> <server path>"); 
                Environment.Exit(1); 
            } 

            TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri(args[0]));
            VersionControlServer vcs = tpc.GetService<VersionControlServer>(); 

            // Get the changeset artifact URIs for each changeset in the history query
            List<String> changesetArtifactUris = new List<String>(); 

            foreach (Object obj in vcs.QueryHistory(args[1],                       // path we care about ($/project/whatever) 
                                                    VersionSpec.Latest,            // version of that path
                                                    0,                             // deletion ID (0 = not deleted) 
                                                    RecursionType.Full,            // entire tree - full recursion
                                                    null,                          // include changesets from all users
                                                    new ChangesetVersionSpec(1),   // start at the beginning of time
                                                    VersionSpec.Latest,            // end at latest
                                                    25,                            // only return this many
                                                    false,                         // we don't want the files changed
                                                    true))                         // do history on the path
            { 
                Changeset c = obj as Changeset; 
                changesetArtifactUris.Add(c.ArtifactUri.AbsoluteUri); 
            } 

            // We'll use the linking service to get information about the associated work items
            ILinking linkingService = tpc.GetService<ILinking>(); 
            LinkFilter linkFilter = new LinkFilter(); 
            linkFilter.FilterType = FilterType.ToolType; 
            linkFilter.FilterValues = new String[1] { ToolNames.WorkItemTracking };  // we only want work itms

            // Convert the artifact URIs for the work items into strongly-typed objects holding the properties rather than name/value pairs 
            Artifact[] artifacts = linkingService.GetReferencingArtifacts(changesetArtifactUris.ToArray(), new LinkFilter[1] { linkFilter });
            AssociatedWorkItemInfo[] workItemInfos = AssociatedWorkItemInfo.FromArtifacts(artifacts);

            // Here we'll just print the IDs and titles of the work items
            foreach (AssociatedWorkItemInfo workItemInfo in workItemInfos)
            { 
                Console.WriteLine("Id: " + workItemInfo.Id + " Title: " + workItemInfo.Title); 
            } 
        } 
    } 

    internal class AssociatedWorkItemInfo
    { 
        private AssociatedWorkItemInfo() 
        { 
        } 

        public int Id 
        { 
            get 
            { 
                return m_id; 
            } 
        } 

        public String Title 
        { 
            get 
            { 
                return m_title; 
            } 
        } 

        public String AssignedTo 
        { 
            get 
            { 
                return m_assignedTo; 
            } 
        } 

        public String WorkItemType 
        { 
            get 
            { 
                return m_type; 
            } 
        } 

        public String State 
        { 
            get 
            { 
                return m_state; 
            } 
        } 

        internal static AssociatedWorkItemInfo[] FromArtifacts(IEnumerable<Artifact> artifacts)
        { 
            if (null == artifacts)
            { 
                return new AssociatedWorkItemInfo[0];
            } 

            List<AssociatedWorkItemInfo> toReturn = new List<AssociatedWorkItemInfo>(); 

            foreach (Artifact artifact in artifacts)
            { 
                if (artifact == null)
                { 
                    continue; 
                } 

                AssociatedWorkItemInfo awii = new AssociatedWorkItemInfo();

                // Convert the name/value pairs into strongly-typed objects containing the work item info 
                foreach (ExtendedAttribute ea in artifact.ExtendedAttributes)
                { 
                    if (String.Equals(ea.Name, "System.Id", StringComparison.OrdinalIgnoreCase)) 
                    { 
                        int workItemId; 

                        if (Int32.TryParse(ea.Value, out workItemId))
                        { 
                            awii.m_id = workItemId; 
                        } 
                    } 
                    else if (String.Equals(ea.Name, "System.Title", StringComparison.OrdinalIgnoreCase)) 
                    { 
                        awii.m_title = ea.Value; 
                    } 
                    else if (String.Equals(ea.Name, "System.AssignedTo", StringComparison.OrdinalIgnoreCase)) 
                    { 
                        awii.m_assignedTo = ea.Value; 
                    } 
                    else if (String.Equals(ea.Name, "System.State", StringComparison.OrdinalIgnoreCase)) 
                    { 
                        awii.m_state = ea.Value; 
                    } 
                    else if (String.Equals(ea.Name, "System.WorkItemType", StringComparison.OrdinalIgnoreCase)) 
                    { 
                        awii.m_type = ea.Value; 
                    } 
                } 

                Debug.Assert(0 != awii.m_id, "Unable to decode artifact into AssociatedWorkItemInfo object."); 

                if (0 != awii.m_id)
                { 
                    toReturn.Add(awii); 
                } 
            } 

            return toReturn.ToArray(); 
        } 

        private int m_id; 
        private String m_title; 
        private String m_assignedTo; 
        private String m_type; 
        private String m_state; 
    } 
}

Author

Buck Hodges
Director of Engineering

Director of Engineering, Azure DevOps

0 comments